@whiteslove/parsing-lexicon 0.10.2 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/normalization.js +33 -10
package/package.json
CHANGED
package/src/normalization.js
CHANGED
|
@@ -3,16 +3,39 @@ import { LEXICON_LANGUAGES } from './lexicon-core.js';
|
|
|
3
3
|
const APOSTROPHES_RE = /[’‘ʻʼ`´]/g;
|
|
4
4
|
const DASHES_RE = /[‐‑‒–—―]/g;
|
|
5
5
|
|
|
6
|
+
// Parsing one vacancy or listing runs dozens of matchers over the same text,
|
|
7
|
+
// and each normalizes it again from scratch: profiling the workforce
|
|
8
|
+
// enrichment showed normalization among the top costs at ~170-490 ms per
|
|
9
|
+
// vacancy. These functions are pure, so each keeps a small cache of its
|
|
10
|
+
// recent long inputs. Short strings (dictionary aliases, single words) are
|
|
11
|
+
// cheaper to normalize than to cache and are left alone; the cap bounds
|
|
12
|
+
// memory to a few recent texts per function.
|
|
13
|
+
const MEMO_MIN_LENGTH = 200;
|
|
14
|
+
const MEMO_MAX_ENTRIES = 32;
|
|
15
|
+
|
|
16
|
+
function memoizeLongText(fn) {
|
|
17
|
+
const cache = new Map();
|
|
18
|
+
return (value) => {
|
|
19
|
+
if (typeof value !== 'string' || value.length < MEMO_MIN_LENGTH) return fn(value);
|
|
20
|
+
const hit = cache.get(value);
|
|
21
|
+
if (hit !== undefined) return hit;
|
|
22
|
+
const result = fn(value);
|
|
23
|
+
if (cache.size >= MEMO_MAX_ENTRIES) cache.delete(cache.keys().next().value);
|
|
24
|
+
cache.set(value, result);
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
6
29
|
/** Normalize Unicode and punctuation variants without destroying letters. */
|
|
7
|
-
export function normalizeUnicode(value) {
|
|
30
|
+
export const normalizeUnicode = memoizeLongText(function normalizeUnicode(value) {
|
|
8
31
|
return String(value ?? '')
|
|
9
32
|
.normalize('NFKC')
|
|
10
33
|
.replace(APOSTROPHES_RE, "'")
|
|
11
34
|
.replace(DASHES_RE, '-');
|
|
12
|
-
}
|
|
35
|
+
});
|
|
13
36
|
|
|
14
37
|
/** Stable comparison form for parser dictionaries. */
|
|
15
|
-
export function normalizeForMatch(value) {
|
|
38
|
+
export const normalizeForMatch = memoizeLongText(function normalizeForMatch(value) {
|
|
16
39
|
return normalizeUnicode(value)
|
|
17
40
|
.toLocaleLowerCase()
|
|
18
41
|
.replace(/ё/g, 'е')
|
|
@@ -20,10 +43,10 @@ export function normalizeForMatch(value) {
|
|
|
20
43
|
.replace(/[^\p{L}\p{N}]+/gu, ' ')
|
|
21
44
|
.replace(/\s+/g, ' ')
|
|
22
45
|
.trim();
|
|
23
|
-
}
|
|
46
|
+
});
|
|
24
47
|
|
|
25
48
|
/** Search-only apostrophe compaction for Uzbek/Karakalpak variants. */
|
|
26
|
-
function normalizeCompactApostropheForMatch(value) {
|
|
49
|
+
const normalizeCompactApostropheForMatch = memoizeLongText(function normalizeCompactApostropheForMatch(value) {
|
|
27
50
|
return normalizeUnicode(value)
|
|
28
51
|
.toLocaleLowerCase()
|
|
29
52
|
.replace(/ё/g, 'е')
|
|
@@ -32,7 +55,7 @@ function normalizeCompactApostropheForMatch(value) {
|
|
|
32
55
|
.replace(/[^\p{L}\p{N}]+/gu, ' ')
|
|
33
56
|
.replace(/\s+/g, ' ')
|
|
34
57
|
.trim();
|
|
35
|
-
}
|
|
58
|
+
});
|
|
36
59
|
|
|
37
60
|
export const CYRILLIC_SEARCH_MAP = Object.freeze({
|
|
38
61
|
а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'e', ж: 'zh', з: 'z',
|
|
@@ -65,21 +88,21 @@ const KARAKALPAK_LATIN_PATTERN_EQUIVALENCE = Object.freeze({
|
|
|
65
88
|
});
|
|
66
89
|
|
|
67
90
|
/** Search-oriented Cyrillic folding; canonical identity still comes from explicit aliases. */
|
|
68
|
-
export function foldCyrillicForSearch(value) {
|
|
91
|
+
export const foldCyrillicForSearch = memoizeLongText(function foldCyrillicForSearch(value) {
|
|
69
92
|
return normalizeUnicode(value)
|
|
70
93
|
.toLocaleLowerCase()
|
|
71
94
|
.split('')
|
|
72
95
|
.map((char) => CYRILLIC_SEARCH_MAP[char] ?? char)
|
|
73
96
|
.join('');
|
|
74
|
-
}
|
|
97
|
+
});
|
|
75
98
|
|
|
76
|
-
function foldKazakhForSearch(value) {
|
|
99
|
+
const foldKazakhForSearch = memoizeLongText(function foldKazakhForSearch(value) {
|
|
77
100
|
return normalizeUnicode(value)
|
|
78
101
|
.toLocaleLowerCase()
|
|
79
102
|
.split('')
|
|
80
103
|
.map((char) => KAZAKH_SEARCH_EQUIVALENCE[char] ?? char)
|
|
81
104
|
.join('');
|
|
82
|
-
}
|
|
105
|
+
});
|
|
83
106
|
|
|
84
107
|
function foldKarakalpakLatinForSearch(value) {
|
|
85
108
|
return normalizeUnicode(value)
|