deburr
Version:
Utility that simplifies substring search by normalizing character set. Diacritic marks and supplementary letters are converted to nearest ASCII character sequence before search.
91 lines (80 loc) • 2.44 kB
JavaScript
// Characters considered to be invisible.
export const DEBURR_DROP_RE = /[\u0300-\u036f\ufe20-\ufe23\ufeff]/g;
// Mapping from UTF character to simplified character sequence.
export const DEBURR = require('./deburr.json');
const CACHE = {};
export class Deburr {
_chars = [];
_offsets = [];
_string = '';
constructor(value) {
value = String(value);
if (value in CACHE) {
return CACHE[value];
}
CACHE[value] = this;
// Compute deburred characters and offset mapping for defined argument.
let offset = 0;
for (let i = 0; i < value.length; ++i) {
let char = value.charAt(i);
if (DEBURR_DROP_RE.test(char)) {
char = '';
} else {
char = DEBURR[char] || char;
}
this._chars[i] = char;
this._offsets[i] = offset;
offset += char.length;
}
this._string = this._chars.join('');
}
toString() {
return this._string;
}
lookup(value, index = 0) {
let {_chars, _offsets} = this;
if (value == null || _chars.length == 0) {
return null;
}
let that = new Deburr(value);
if (index >= _chars.length) {
// Offset index exceeds total number of chars in string, so no matches would be found for sure.
return null;
}
let i = this._string.indexOf(that._string, _offsets[index]);
if (i < 0) {
// Deburred content of strings did not match.
return null;
}
let offset = _offsets.indexOf(i);
if (offset < 0) {
// Partial character matching occurred at beginning, ex. match second char in `ss` mapped to `ß`.
return null;
}
for (var length = 0, count = 0; count < that._string.length; ++length) {
count += _chars[offset + length].length;
}
if (count > that._string.length) {
// Deburred string breaks last character.
return null;
}
return {offset, length};
}
lookupAll(value, index = 0, count = this._chars.length) {
let ranges = [];
while (count > 0) {
let range = this.lookup(value, index);
if (!range) {
break;
}
ranges.push(range);
index = range.offset + range.length;
// If we matched zero-length substring force shift to next character to prevent infinite loop.
if (range.length == 0) {
index++;
}
count--;
}
return ranges;
}
}