UNPKG

talisman

Version:

Straightforward fuzzy matching, information retrieval and NLP building blocks for JavaScript.

479 lines (385 loc) 14.4 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = doubleMetaphone; /* eslint no-constant-condition: 0 */ /** * Talisman phonetics/double-metaphone * ==================================== * * The double metaphone algorithm. * * [Reference]: * https://en.wikipedia.org/wiki/Metaphone#Double_Metaphone * * [Author]: * Lawrence Philips, 2000 */ /** * Helpers. */ var STARTING_REGEX = /^GN|KN|PN|WR|PS$/; var SLAVO_GERMANIC_REGEX = /W|K|CZ|WITZ/; function isSlavoGermanic(string) { return SLAVO_GERMANIC_REGEX.test(string); } var VOWELS = new Set(['A', 'E', 'I', 'O', 'U', 'Y']); function isVowel(string) { return string.length === 1 && VOWELS.has(string); } /** * Lookups. */ var CHSet1 = new Set(['HARAC', 'HARIS']), CHSet2 = new Set(['HOR', 'HYM', 'HIA', 'HEM']), CHSet3 = new Set(['VAN ', 'VON ']), CHSet4 = new Set(['ORCHES', 'ARCHIT', 'ORCHID']), ChSet5 = new Set(['T', 'S']), CHSet6 = new Set(['A', 'O', 'U', 'E']), CHSet7 = new Set(['L', 'R', 'N', 'M', 'B', 'H', 'F', 'V', 'W', ' ']), CSet1 = new Set(['CE', 'CI']); var LOOKUPS = { B: function B(string, pos) { return ['P', 'P', string.substr(pos + 1, 1) === 'B' ? 2 : 1]; }, CH: function CH(string, pos) { if (pos && string.substr(pos, 4) === 'CHAE') { return ['K', 'X', 2]; } else if (!pos && (CHSet1.has(string.substr(pos + 1, 5)) || CHSet2.has(string.substr(pos + 1, 3))) && string.substr(0, 5) !== 'CHORE') { return ['K', 'K', 2]; } else if (CHSet3.has(string.substr(0, 4)) || string.substr(0, 3) === 'SCH' || CHSet4.has(string.substr(pos - 2, 6)) || ChSet5.has(string.substr(pos + 2, 1)) || (!pos || CHSet6.has(string.substr(pos - 1, 1))) && CHSet7.has(string.substr(pos + 2, 1))) { return ['K', 'K', 2]; } else if (pos) { return [string.substr(0, 2) === 'MC' ? 'K' : 'X', 'K', 2]; } return ['X', 'X', 2]; }, CC: function CC(string, pos) { if (/^I|E|H$/.test(string.substr(pos + 2, 1)) && string.substr(pos + 2, 2) !== 'HU') { if (pos === 1 && string.substr(pos - 1, 1) === 'A' || /^UCCE(E|S)$/.test(string.substr(pos - 1, 5))) { return [['K', 'S'], ['K', 'S'], 3]; } else { return ['X', 'X', 3]; } } return ['K', 'K', 2]; }, C: function C(string, pos) { if (pos > 1 && isVowel(string.substr(pos - 2, 1)) && string.substr(pos - 1, 3) === 'ACH' && string.substr(pos + 2, 1) !== 'I' && (string.substr(pos + 2, 1) !== 'E' || /^(B|M)ACHER$/.test(string.substr(pos - 2, 6)))) { return ['K', 'K', 2]; } if (!pos && string.substr(pos, 6) === 'CAESAR') { return ['S', 'S', 2]; } if (string.substr(pos, 4) === 'CHIA') { return ['K', 'K', 2]; } if (string.substr(pos, 2) === 'CH') { return LOOKUPS.CH(string, pos); } if (string.substr(pos, 2) === 'CZ' && string.substr(pos - 2, 4) !== 'WICZ') { return ['S', 'X', 2]; } if (string.substr(pos + 1, 3) === 'CIA') { return ['X', 'X', 3]; } if (string.substr(pos, 2) === 'CC' && !(pos === 1 || string.substr(0, 1) === 'M')) { return LOOKUPS.CC(string, pos); } if (/^C(K|G|Q)$/.test(string.substr(pos, 2))) { return ['K', 'K', 2]; } if (/^C(I|E|Y)$/.test(string.substr(pos, 2))) { return ['S', /^CI(O|E|A)$/.test(string.substr(pos, 3)) ? 'X' : 'S', 2]; } if (/^ (C|Q|G)$/.test(string.substr(pos + 1, 2))) { return ['K', 'K', 3]; } var offset = 1; if (/^C|K|Q$/.test(string.substr(pos + 1, 1)) && !CSet1.has(string.substr(pos + 1, 2))) { offset = 2; } return ['K', 'K', offset]; }, Ç: function _() { return ['S', 'S', 1]; }, D: function D(string, pos) { if (string.substr(pos, 2) === 'DG') { return (/^I|E|Y$/.test(string.substr(pos + 2, 1)) ? ['J', 'J', 3] : [['T', 'K'], ['T', 'K'], 2] ); } return ['T', 'T', /^D(T|D)$/.test(string.substr(pos, 2)) ? 2 : 1]; }, F: function F(string, pos) { return ['F', 'F', string.substr(pos + 1, 1) === 'F' ? 2 : 1]; }, GH: function GH(string, pos) { if (pos && !isVowel(string.substr(pos - 1, 1))) { return ['K', 'K', 2]; } if (!pos) { return string.substr(pos + 2, 1) === 'I' ? ['J', 'J', 2] : ['K', 'K', 2]; } if (pos > 1 && /^B|H|D$/.test(string.substr(pos - 2, 1)) || pos > 2 && /^B|H|D$/.test(string.substr(pos - 3, 1)) || pos > 3 && /^B|H$/.test(string.substr(pos - 4, 1))) { return [null, null, 2]; } if (pos > 2 && string.substr(pos - 1, 1) === 'U' && /^C|G|L|R|T$/.test(string.substr(pos - 3, 1))) { return ['F', 'F', 2]; } if (pos && string.substr(pos - 1, 1) !== 'I') { return ['K', 'K', 2]; } return [null, null, 2]; }, GN: function GN(string, pos) { if (pos === 1 && isVowel(string.substr(0, 1)) && !isSlavoGermanic(string)) { return [['K', 'N'], 'N', 2]; } if (string.substr(pos + 2, 2) !== 'EY' && string.substr(pos + 1, 1) !== 'Y' && !isSlavoGermanic(string)) { return ['N', ['K', 'N'], 2]; } return [['K', 'N'], ['K', 'N'], 2]; }, G: function G(string, pos) { var nextLetter = string.substr(pos + 1, 1), nextPair = string.substr(pos + 1, 2); if (nextLetter === 'H') { return LOOKUPS.GH(string, pos); } if (nextLetter === 'N') { return LOOKUPS.GN(string, pos); } if (nextPair === 'LI' && !isSlavoGermanic(string)) { return [['K', 'L'], 'L', 2]; } if (!pos && (nextLetter === 'Y' || /^(E(S|P|B|L|Y|I|R)|I(B|L|N|E))$/.test(nextPair))) { return ['K', 'J', 2]; } if ((nextPair === 'ER' || nextLetter === 'Y') && !/^(D|R|M)ANGER$/.test(string.substr(0, 6)) && !/^E|I$/.test(string.substr(pos - 1, 1)) && !/^(R|O)GY$/.test(string.substr(pos - 1, 3))) { return ['K', 'J', 2]; } if (/^E|I|Y$/.test(nextLetter) || /^(A|O)GGI$/.test(string.substr(pos - 1, 4))) { if (/^V(A|O)N /.test(string.substr(0, 4)) || string.substr(0, 3) === 'SCH' || string.substr(pos + 1, 2 === 'ET')) { return ['K', 'K', 2]; } return string.substr(pos + 1, 4) === 'IER ' ? ['J', 'J', 2] : ['J', 'K', 2]; } return ['K', 'K', nextLetter === 'G' ? 2 : 1]; }, H: function H(string, pos) { if ((!pos || isVowel(string.substr(pos - 1, 1))) && isVowel(string.substr(pos + 1, 1))) { return ['H', 'H', 2]; } return [null, null, 1]; }, J: function J(string, pos, lastIndex) { if (string.substr(pos, 4) === 'JOSE' || string.substr(0, 4) === 'SAN ') { if (!pos && string.substr(pos + 4, 1) === ' ' || string.substr(0, 4) === 'SAN ') { return ['H', 'H', 1]; } return ['J', 'H', 1]; } var offset = string.substr(pos + 1, 1) === 'J' ? 2 : 1; if (!pos && string.substr(pos, 4) !== 'JOSE') { return ['J', 'A', offset]; } if (isVowel(string.substr(pos - 1, 1)) && !isSlavoGermanic(string) && /^A|O$/.test(string.substr(pos + 1, 1))) { return ['J', 'H', offset]; } if (lastIndex === pos) { return ['J', null, offset]; } if (!/^L|T|K|S|N|M|B|Z$/.test(string.substr(pos + 1, 1)) && !/^S|K|L$/.test(string.substr(pos - 1, 1))) { return ['J', 'J', offset]; } return [null, null, offset]; }, K: function K(string, pos) { return ['K', 'K', string.substr(pos + 1, 1) === 'K' ? 2 : 1]; }, L: function L(string, pos, lastIndex, length) { if (string.substr(pos + 1, 1) === 'L') { if (pos === length - 3 && /^(ILL(O|A)|ALLE)$/.test(string.substr(pos - 1, 4)) || /^(A|O)S$/.test(string.substr(lastIndex - 1, 2) || /^A|O$/.test(string.substr(lastIndex, 1))) && string.substr(pos - 1, 4) === 'ALLE') { return ['L', null, 2]; } return ['L', 'L', 2]; } return ['L', 'L', 1]; }, M: function M(string, pos, lastIndex) { if (string.substr(pos - 1, 3) === 'UMB' && (pos === lastIndex - 1 || string.substr(pos + 2, 2) === 'ER') || string.substr(pos + 1, 1) === 'M') { return ['M', 'M', 2]; } return ['M', 'M', 1]; }, N: function N(string, pos) { return ['N', 'N', string.substr(pos + 1, 1) === 'N' ? 2 : 1]; }, Ñ: function _() { return ['N', 'N', 1]; }, P: function P(string, pos) { if (string.substr(pos + 1, 1) === 'H') { return ['F', 'F', 2]; } return ['P', 'P', /^P|B$/.test(string.substr(pos + 1, 1)) ? 2 : 1]; }, Q: function Q(string, pos) { return ['K', 'K', string.substr(pos + 1, 1) === 'Q' ? 2 : 1]; }, R: function R(string, pos, lastIndex) { var offset = string.substr(pos + 1, 1) === 'R' ? 2 : 1; if (pos === lastIndex && !isSlavoGermanic(string) && string.substr(pos - 2, 2) === 'IE' && !/^M(E|A)$/.test(string.substr(pos - 4, 2))) { return [null, 'R', offset]; } return ['R', 'R', offset]; }, SH: function SH(string, pos) { return (/^H(EIM|OEK|OLM|OLZ)$/.test(string.substr(pos + 1, 4)) ? ['S', 'S', 2] : ['X', 'X', 2] ); }, SC: function SC(string, pos) { if (string.substr(pos + 2, 1) === 'H') { if (/^OO|ER|EN|UY|ED|EM$/.test(string.substr(pos + 3, 2))) { return [/^E(R|N)$/.test(string.substr(pos + 3, 2)) ? 'X' : ['S', 'K'], ['S', 'K'], 3]; } return ['X', !pos && !isVowel(string.substr(3, 1)) && string.substr(pos + 3, 1) !== 'W' ? 'S' : 'X', 3]; } if (/^I|E|Y$/.test(string.substr(pos + 2, 1))) { return ['S', 'S', 3]; } return [['S', 'K'], ['S', 'K'], 3]; }, S: function S(string, pos, lastIndex) { if (/^(I|Y)SL$/.test(string.substr(pos - 1, 3))) { return [null, null, 1]; } if (!pos && string.substr(pos, 5) === 'SUGAR') { return ['X', 'S', 1]; } if (string.substr(pos, 2) === 'SH') { return LOOKUPS.SH(string, pos); } if (/^SI(O|A)$/.test(string.substr(pos, 3)) || string.substr(pos, 4) === 'SIAN') { return ['S', isSlavoGermanic(string) ? 'S' : 'X', 3]; } if (!pos && /^M|N|L|W$/.test(string.substr(pos + 1, 1)) || string.substr(pos + 1, 1) === 'Z') { return ['S', 'X', string.substr(pos + 1, 1) === 'Z' ? 2 : 1]; } if (string.substr(pos, 2) === 'SC') { return LOOKUPS.SC(string, pos); } return [!(lastIndex === pos && /^(A|O)I$/.test(string.substr(pos - 2, 2))) ? 'S' : null, 'S', /^S|Z$/.test(string.substr(pos + 1, 1)) ? 2 : 1]; }, TH: function TH(string, pos) { if (/^(O|A)M$/.test(string.substr(pos + 2, 2)) || /^V(A|O)N /.test(string.substr(0, 4)) || string.substr(0, 3) === 'SCH') { return ['T', 'T', 2]; } return ['0', 'T', 2]; }, T: function T(string, pos) { if (string.substr(pos, 4) === 'TION' || /^T(IA|CH)$/.test(string.substr(pos, 3))) { return ['X', 'X', 3]; } if (string.substr(pos, 2) === 'TH' || string.substr(pos, 3) === 'TTH') { return LOOKUPS.TH(string, pos); } return ['T', 'T', /^T|D$/.test(string.substr(pos + 1, 1)) ? 2 : 1]; }, V: function V(string, pos) { return ['F', 'F', string.substr(pos + 1, 1) === 'V' ? 2 : 1]; }, W: function W(string, pos, lastIndex) { if (string.substr(pos, 2) === 'WR') { return ['R', 'R', 2]; } var primary = [], secondary = []; if (!pos && isVowel(string.substr(pos + 1, 1) || string.substr(pos, 2) === 'WH')) { primary.push('A'); secondary.push(isVowel(string.substr(pos + 1, 1)) ? 'F' : 'A'); } if (pos === lastIndex && isVowel(string.substr(pos - 1, 1)) || string.substr(0, 3) === 'SCH' || /^EWSKI|EWSKY|OWSKI|OWSKY$/.test(string.substr(pos - 1, 5))) { return [primary, secondary.concat('F'), 1]; } if (/^WI(C|T)Z$/.test(string.substr(pos, 4))) { return [primary.concat(['T', 'S']), secondary.concat(['F', 'X']), 4]; } return [primary, secondary, 1]; }, X: function X(string, pos, lastIndex) { if (!pos) { return ['S', 'S', 1]; } var offset = /^C|X$"/.test(string.substr(pos + 1, 1)) ? 2 : 1; if (pos === lastIndex && /^(I|E)AU$/.test(string.substr(pos - 3, 3)) || /^(A|O)U$/.test(string.substr(pos - 2, 2))) { return [null, null, offset]; } return [['K', 'S'], ['K', 'S'], offset]; }, Z: function Z(string, pos) { if (string.substr(pos + 1, 1) === 'H') { return ['J', 'J', 2]; } var offset = string.substr(pos + 1, 1) === 'Z' ? 2 : 1; if (/^Z(O|I|A)$/.test(string.substr(pos + 1, 2)) || pos && isSlavoGermanic(string) && string.substr(pos - 1, 1) === 'T') { return ['S', ['T', 'S'], offset]; } return ['S', 'S', offset]; } }; /** * Function taking a single word and computing its double metaphone code. * * @param {string} word - The word to process. * @return {array} - The double metaphone codes. * * @throws {Error} The function expects the word to be a string. */ function doubleMetaphone(word) { if (typeof word !== 'string') throw Error('talisman/phonetics/doubleMetaphone: the given word is not a string.'); // Preparing the word var preparedWord = word.toUpperCase() + ' '; // Defining the start position & finding necessary indexes var startPosition = STARTING_REGEX.test(preparedWord.slice(0, 2)) ? 1 : 0, length = word.length, lastIndex = length - 1; // Codes var primary = [], secondary = []; // Iterating var pos = startPosition; while (true) { if (pos > length || primary.length >= 4 && secondary.length >= 4) break; // Lookup the current letter var letter = preparedWord[pos]; var offset = 1; // Vowel lookup if (isVowel(letter)) { if (!pos) { primary.push('A'); secondary.push('A'); } } // Consonant lookup var method = LOOKUPS[letter]; if (method) { var _method = method(preparedWord, pos, lastIndex, length), _method$ = _method[0], one = _method$ === undefined ? null : _method$, _method$2 = _method[1], two = _method$2 === undefined ? null : _method$2, _method$3 = _method[2], newOffset = _method$3 === undefined ? 1 : _method$3; offset = newOffset; if (one) primary = primary.concat(one); if (two) secondary = secondary.concat(two); } // Incrementing position pos += offset; } return [primary.join('').slice(0, 4), secondary.join('').slice(0, 4)]; } module.exports = exports['default'];