UNPKG

@dcoffey/espells

Version:

Pure JS/TS spellchecker, using Hunspell dictionaries. Based on Spylls.

162 lines 5.48 kB
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { CONSTANTS as C } from "../constants.js"; import { re, uppercase } from "../util.js"; // TODO: use homegrown metaphone implementation (maybe) /** * A table for metaphone transformations. These transformations may provide * superior suggestions because they describe similar sounding (as in * spoken) syllables, which will be used by the suggestion engine to find * words that may not be spelled similarly but *sound* similar. * * It roughly uses the following syntax: * * ```text * PHONE <number of entries> * PHONE <pattern> <replacement> * ``` * * `replacement` is a simple string, with the special value `_` * (underscore) meaning an empty string. `pattern` is complex, and * currently isn't very well documented. Additionally, both Spylls and * Espells do not fully implement Hunspell's more intricate details, such * as rule prioritizing and concepts like "follow-up rules". * * Dictionaries that use this feature are unfortunately quite rare. */ export class PhonetTable { constructor(table) { var _a, _b; Object.defineProperty(this, "rules", { enumerable: true, configurable: true, writable: true, value: {} }); for (const [search, replacement] of table) { const match = C.PHONET_RULE_REGEX.exec(search); if (!match) throw new SyntaxError(`Invalid PhonetTable pattern '${search}'`); const [letters, optional, lookahead, flags, priority] = match; let text = [...letters]; if (optional) text.push(`[${optional}]`); let regex; if (lookahead) { const la = lookahead.length; regex = re `/${text.slice(0, -la).join("")}(?=${text.slice(-la).join("")})/`; } else { regex = re `/${text.join("")}/`; } (_a = this.rules)[_b = search[0]] ?? (_a[_b] = []); this.rules[search[0]].push(new PhonetTableRule(regex, replacement, flags?.includes("^"), flags?.includes("$"), Boolean(lookahead), priority ? parseInt(priority) : 5)); } } /** * Returns the metaphone representation of a word. * * @param word - The word to transform. */ metaphone(word) { word = uppercase(word); let pos = 0; let res = ""; while (pos < word.length) { let match = false; if (this.rules[word[pos]]) { for (const rule of this.rules[word[pos]]) { match = rule.match(word, pos); if (match) { res += rule.replacement; pos += match.index + match[0].length - match.index; } } } if (!match) pos++; } return res; } } /** * An individual phonetic table rule. * * @see {@link PhonetTable} */ class PhonetTableRule { constructor( /** The `RegExp` used for checking if this rule applies. */ search, /** The string to represent a matched phoneme with. */ replacement, /** If true, this rule only applies at the start of a word. */ start = false, /** If true, this rule only applies at the end of a word. */ end = false, /** Currently unusued in both Spylls and Espells. */ followup = true, /** Currently unusued in both Spylls and Espells. */ priority = 5) { Object.defineProperty(this, "search", { enumerable: true, configurable: true, writable: true, value: search }); Object.defineProperty(this, "replacement", { enumerable: true, configurable: true, writable: true, value: replacement }); Object.defineProperty(this, "start", { enumerable: true, configurable: true, writable: true, value: start }); Object.defineProperty(this, "end", { enumerable: true, configurable: true, writable: true, value: end }); Object.defineProperty(this, "followup", { enumerable: true, configurable: true, writable: true, value: followup }); Object.defineProperty(this, "priority", { enumerable: true, configurable: true, writable: true, value: priority }); } /** * Checks if a rule is matched by this rule, and if it is, returns the * `RegExpExecArray` match, otherwise returning false. * * @param word - The word to check. * @param pos - The position in the word to check. */ match(word, pos) { if (this.start && pos > 0) return false; this.search.lastIndex = pos; const match = this.search.exec(word); if (match) { if (this.end) { return match[0].length !== word.length ? false : match; } else { return match; } } return false; } } //# sourceMappingURL=phonet-table.js.map