UNPKG

nysiis

Version:

NYSIIS phonetic encoding algorithm.

279 lines (278 loc) 9.7 kB
"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;