hangul-searcher
Version:
A module for searching and auto completing Hangul.
80 lines (79 loc) • 3.03 kB
JavaScript
import MiniSearch from 'minisearch';
import { disassemble, getChoseong } from 'es-hangul';
import { getRegExp } from 'korean-regexp';
export default class HangulSearcher {
searchOption;
autoOption;
#searcher;
#originalArr = [];
#choseongArr = [];
#encode(str) {
return str
.split('')
.map((c) => '0x' +
Array.from(new TextEncoder().encode(c))
.map((utf8) => utf8.toString(16))
.join(''))
.join('');
}
constructor(stringArr, searchOption = {}, autoOption = { startsWithQuery: true, alwaysUsesChoseong: true }) {
this.searchOption = searchOption;
this.autoOption = autoOption;
this.#originalArr = stringArr.toSorted();
this.#searcher = new MiniSearch({
fields: ['original', 'disassembled'],
storeFields: ['original'],
tokenize: (string, _fieldName) => string.split('0x'),
});
const documentArr = this.#originalArr.reduce((prev, original, i) => {
this.#choseongArr.push(disassemble(getChoseong(original)).replaceAll(' ', ''));
prev.push({
id: i,
original,
disassembled: this.#encode(disassemble(original)),
});
return prev;
}, []);
this.#searcher.addAll(documentArr);
}
search(query, option) {
option ??= this.searchOption;
if (this.#originalArr.indexOf(query) !== -1)
return [query];
return this.#searcher.search(this.#encode(disassemble(query)), option);
}
autoComplete(query, option) {
option ??= this.autoOption;
this.autoOption.startsWithQuery ??= true;
this.autoOption.alwaysUsesChoseong ??= true;
query = query.replaceAll(' ', '');
if (query === '')
return this.#originalArr;
const re = new RegExp(getRegExp(query, { startsWith: option.startsWithQuery }));
const resultArr = this.#originalArr.filter((word) => word.search(re) !== -1);
const resultSet = new Set(resultArr);
if (option.alwaysUsesChoseong || getChoseong(query) === query) {
this.#choseongComplete(disassemble(getChoseong(query)), option).forEach((string) => {
if (!resultSet.has(string)) {
resultSet.add(string);
resultArr.push(string);
}
});
}
return resultArr;
}
#choseongComplete(query, option) {
if (query === '')
return [];
const re = new RegExp(query, 'i');
return this.#choseongArr.reduce((prev, choseong, index) => {
if (choseong === '')
return prev;
if ((option.startsWithQuery && choseong.startsWith(query)) ||
(!option.startsWithQuery && choseong.search(re) !== -1)) {
prev.push(this.#originalArr[index]);
}
return prev;
}, []);
}
}