nysiis
Version:
NYSIIS phonetic encoding algorithm.
279 lines (278 loc) • 9.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const regex_parser_1 = __importDefault(require("regex-parser"));
class Nysiis {
constructor(options = {}) {
this.options = Object.assign(Object.assign({}, Nysiis.DEFAULT_OPTIONS), options);
this.vowels = new Set(["A", "E", "I", "O", "U"]);
// Initialize accent mapping
this.accentMap = new Map([
["Á", "A"],
["À", "A"],
["Â", "A"],
["Ã", "A"],
["Ä", "A"],
["Å", "A"],
["É", "E"],
["È", "E"],
["Ê", "E"],
["Ë", "E"],
["Í", "I"],
["Ì", "I"],
["Î", "I"],
["Ï", "I"],
["Ó", "O"],
["Ò", "O"],
["Ô", "O"],
["Õ", "O"],
["Ö", "O"],
["Ú", "U"],
["Ù", "U"],
["Û", "U"],
["Ü", "U"],
["Ý", "Y"],
["Ñ", "N"],
["Ç", "S"],
]);
// Initialize first character rules
this.firstCharRules = [
// English rules
{ pattern: "MAC", replacement: "MCC", languages: ["english"] },
{ pattern: "KN", replacement: "N", languages: ["english"] },
{ pattern: "K", replacement: "C", languages: ["english"] },
{ pattern: "PH", replacement: "F", languages: ["english"] },
{ pattern: "PF", replacement: "F", languages: ["english"] },
{ pattern: "SCH", replacement: "S", languages: ["english", "german"] },
// Igbo rules
{ pattern: "GB", replacement: "J", languages: ["igbo"] },
{ pattern: "KP", replacement: "P", languages: ["igbo"] },
{ pattern: "NW", replacement: "W", languages: ["igbo"] },
// Yoruba rules
{ pattern: "TS", replacement: "S", languages: ["yoruba"] },
// Hindi rules
{ pattern: "BH", replacement: "B", languages: ["hindi"] },
{ pattern: "DH", replacement: "D", languages: ["hindi"] },
{ pattern: "GH", replacement: "G", languages: ["hindi"] },
{ pattern: "JH", replacement: "J", languages: ["hindi"] },
{ pattern: "TH", replacement: "T", languages: ["hindi"] },
// Urdu rules
{ pattern: "CH", replacement: "C", languages: ["urdu"] },
{ pattern: "KH", replacement: "K", languages: ["urdu"] },
{ pattern: "SH", replacement: "S", languages: ["urdu"] },
{ pattern: "ZH", replacement: "J", languages: ["urdu"] },
];
// Initialize last character rules
this.lastCharRules = [
{ pattern: "EE", replacement: "Y" },
{ pattern: "IE", replacement: "Y" },
{ pattern: "DT", replacement: "D" },
{ pattern: "RT", replacement: "D" },
{ pattern: "RD", replacement: "D" },
{ pattern: "NT", replacement: "D" },
{ pattern: "ND", replacement: "D" },
];
// Initialize middle character rules
this.middleCharRules = [
{ pattern: "EV", replacement: "AF" },
{ pattern: "OV", replacement: "AF" },
{ pattern: "GN", replacement: "N" },
{ pattern: "KN", replacement: "N" },
{ pattern: "PH", replacement: "F" },
{ pattern: "PF", replacement: "F" },
{ pattern: "TCH", replacement: "CH" },
];
}
/**
* Encodes a name using the NYSIIS algorithm
*/
encode(name) {
if (!this.isValidInput(name)) {
if (this.options.strict) {
throw new Error("Invalid input: name must be a non-empty string");
}
return "";
}
let processedName = this.preprocessName(name);
if (processedName.length < 2) {
return processedName;
}
processedName = this.translateFirstCharacters(processedName);
processedName = this.translateLastCharacters(processedName);
let key = this.generateKey(processedName);
key = this.finaliseKey(key);
return key.slice(0, this.options.maxLength);
}
/**
* Validates the input string
*/
isValidInput(input) {
return typeof input === "string" && input.trim().length > 0;
}
/**
* Preprocesses the input name
*/
preprocessName(name) {
// First, handle accented characters
name = Array.from(name.toUpperCase())
.map((char) => this.accentMap.get(char) || char)
.join("");
// Then remove non-alphabetic characters
const regex = (0, regex_parser_1.default)("[^A-Z]");
return name.replace(regex, "");
}
/**
* Translates first characters according to language-specific rules
*/
translateFirstCharacters(name) {
for (const rule of this.firstCharRules) {
if (!rule.languages ||
rule.languages.some((lang) => this.options.languageRules.includes(lang))) {
if (name.startsWith(rule.pattern)) {
return rule.replacement + name.slice(rule.pattern.length);
}
}
}
return name;
}
/**
* Translates last characters according to NYSIIS rules
*/
translateLastCharacters(name) {
for (const rule of this.lastCharRules) {
if (name.endsWith(rule.pattern)) {
return name.slice(0, -rule.pattern.length) + rule.replacement;
}
}
return name;
}
/**
* Applies middle character rules
*/
applyMiddleCharRules(name, position) {
for (const rule of this.middleCharRules) {
if (name.slice(position).startsWith(rule.pattern)) {
return [rule.replacement, position + rule.pattern.length - 1];
}
}
return [name[position], position];
}
/**
* Handles vowel transformations with harmony
*/
handleVowel(char, prevChar) {
// Basic vowel transformation
if (this.vowels.has(prevChar)) {
// Apply vowel harmony rules
if (["A", "O", "U"].includes(prevChar)) {
return "A";
}
if (["E", "I"].includes(prevChar)) {
return "A";
}
}
return "A";
}
/**
* Generates the NYSIIS key
*/
generateKey(name) {
const chars = [];
let prevChar = "";
let i = 0;
while (i < name.length) {
let char = name[i];
let skipNext = false;
// Check for middle character rules
const [newChar, newPos] = this.applyMiddleCharRules(name, i);
if (newPos > i) {
char = newChar;
i = newPos;
skipNext = true;
}
// Apply character transformations
if (this.vowels.has(char)) {
char = this.handleVowel(char, prevChar);
}
else {
switch (char) {
case "Q":
char = "G";
break;
case "Z":
char = "S";
break;
case "M":
char = "N";
break;
case "K":
char = "C";
break;
case "W":
if (this.vowels.has(prevChar)) {
char = prevChar;
}
break;
case "H":
if (!this.vowels.has(prevChar) ||
(i + 1 < name.length && !this.vowels.has(name[i + 1]))) {
char = prevChar;
}
break;
}
}
// Add character if different from previous
if (char !== prevChar || char === "S") {
chars.push(char);
prevChar = char;
}
if (!skipNext) {
i++;
}
}
return chars.join("");
}
/**
* Finalises the NYSIIS key
*/
finaliseKey(key) {
// Remove trailing S
if (key.endsWith("S")) {
key = key.slice(0, -1);
}
// Replace trailing AY with Y
if (key.endsWith("AY")) {
key = key.slice(0, -2) + "Y";
}
// Remove trailing A
if (key.endsWith("A")) {
key = key.slice(0, -1);
}
return key;
}
/**
* Gets available language rules
*/
getAvailableLanguages() {
return ["english", "igbo", "yoruba", "hindi", "urdu", "german"];
}
/**
* Sets active language rules
*/
setLanguageRules(languages) {
const availableLanguages = this.getAvailableLanguages();
const invalidLanguages = languages.filter((lang) => !availableLanguages.includes(lang));
if (invalidLanguages.length > 0) {
throw new Error(`Invalid language rules: ${invalidLanguages.join(", ")}`);
}
this.options.languageRules = languages;
}
}
Nysiis.DEFAULT_OPTIONS = {
maxLength: 6,
strict: true,
languageRules: ["english", "igbo", "yoruba", "hindi", "urdu"],
};
exports.default = Nysiis;