euredact 0.2.0 → 0.3.1
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/README.md +71 -1
- package/dist/cjs/cache.js +2 -2
- package/dist/cjs/index.js +10 -1
- package/dist/cjs/normalizer.js +16 -18
- package/dist/cjs/rules/bicRegistry.js +239 -0
- package/dist/cjs/rules/countries/index.js +102 -29
- package/dist/cjs/rules/deDistricts.js +92 -0
- package/dist/cjs/rules/engine.js +131 -19
- package/dist/cjs/rules/matchers.js +3 -1
- package/dist/cjs/rules/suppressors.js +286 -10
- package/dist/cjs/rules/validators.js +62 -12
- package/dist/cjs/sdk.js +20 -0
- package/dist/cjs/types.js +18 -2
- package/dist/esm/cache.js +2 -2
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +5 -1
- package/dist/esm/normalizer.js +16 -18
- package/dist/esm/rules/bicRegistry.d.ts +50 -0
- package/dist/esm/rules/bicRegistry.js +233 -0
- package/dist/esm/rules/countries/index.js +102 -29
- package/dist/esm/rules/deDistricts.d.ts +16 -0
- package/dist/esm/rules/deDistricts.js +89 -0
- package/dist/esm/rules/engine.d.ts +8 -0
- package/dist/esm/rules/engine.js +130 -19
- package/dist/esm/rules/matchers.d.ts +1 -1
- package/dist/esm/rules/matchers.js +3 -1
- package/dist/esm/rules/suppressors.js +286 -10
- package/dist/esm/rules/validators.d.ts +25 -0
- package/dist/esm/rules/validators.js +60 -11
- package/dist/esm/sdk.d.ts +7 -0
- package/dist/esm/sdk.js +20 -0
- package/dist/esm/types.d.ts +15 -1
- package/dist/esm/types.js +17 -1
- package/package.json +9 -4
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* German vehicle registration district codes (Unterscheidungszeichen).
|
|
4
|
+
*
|
|
5
|
+
* The set is closed: codes are fixed by the Fahrzeug-Zulassungsverordnung and
|
|
6
|
+
* maintained by the Kraftfahrt-Bundesamt, changing only when a district is
|
|
7
|
+
* created, merged, or a historic code is reinstated. That makes membership a
|
|
8
|
+
* usable precision signal — unlike the open-ended blocklist of standards
|
|
9
|
+
* prefixes it replaces, which only ever catches acronyms someone thought of.
|
|
10
|
+
*
|
|
11
|
+
* Used as a **tier**, not a filter: code on this list -> emit; code absent but
|
|
12
|
+
* a plate cue nearby -> emit; neither -> reject. So a missing code costs
|
|
13
|
+
* recall only where there is also no cue. Some entries are also standards
|
|
14
|
+
* prefixes — DIN is Dinslaken as well as the standards body — which is why the
|
|
15
|
+
* standards guard is kept alongside this one.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.DE_DISTRICT_CODES = void 0;
|
|
19
|
+
exports.DE_DISTRICT_CODES = new Set([
|
|
20
|
+
"A", "AA", "AB", "ABG", "AC", "AE", "AH", "AIB", "AIC", "AK", "AL", "ALF",
|
|
21
|
+
"ALS", "ALZ", "AM", "AN", "ANA", "ANG", "ANK", "AP", "APD", "AR", "ARN",
|
|
22
|
+
"ART", "AS", "ASD", "ASL", "ASZ", "AT", "AU", "AUR", "AW", "AZ", "AZE",
|
|
23
|
+
"AÖ", "B", "BA", "BAD", "BAR", "BB", "BBG", "BBL", "BC", "BCH", "BD",
|
|
24
|
+
"BE", "BED", "BEI", "BEL", "BER", "BF", "BG", "BGD", "BGL", "BH", "BI",
|
|
25
|
+
"BID", "BIN", "BIR", "BIT", "BIW", "BK", "BKS", "BL", "BLB", "BLK", "BM",
|
|
26
|
+
"BN", "BNA", "BO", "BOG", "BOH", "BOR", "BOT", "BP", "BR", "BRA", "BRB",
|
|
27
|
+
"BRG", "BRI", "BRK", "BRL", "BRV", "BS", "BSB", "BSK", "BT", "BTF", "BU",
|
|
28
|
+
"BUL", "BUR", "BW", "BWL", "BYL", "BZ", "BZA", "BÖ", "BÜD", "BÜS", "BÜZ",
|
|
29
|
+
"C", "CA", "CAS", "CB", "CE", "CHA", "CHL", "CLP", "CLZ", "CO", "COC",
|
|
30
|
+
"COE", "CR", "CT", "CUX", "CW", "D", "DA", "DAH", "DAN", "DAU", "DB",
|
|
31
|
+
"DBR", "DD", "DE", "DEG", "DEL", "DG", "DGF", "DH", "DI", "DIL", "DIN",
|
|
32
|
+
"DIZ", "DKB", "DL", "DLG", "DM", "DN", "DO", "DON", "DS", "DT", "DU",
|
|
33
|
+
"DUD", "DW", "DZ", "DÜW", "E", "EA", "EB", "EBE", "EBN", "EBS", "ECK",
|
|
34
|
+
"ED", "EE", "EF", "EG", "EH", "EHI", "EI", "EIC", "EIH", "EIL", "EIN",
|
|
35
|
+
"EIS", "EL", "EM", "EMD", "EMS", "EN", "ER", "ERB", "ERH", "ERK", "ES",
|
|
36
|
+
"ESA", "ESB", "ESW", "EU", "EUT", "EW", "F", "FAL", "FB", "FD", "FDB",
|
|
37
|
+
"FDS", "FEU", "FF", "FFB", "FG", "FH", "FI", "FKB", "FL", "FLÖ", "FN",
|
|
38
|
+
"FO", "FOR", "FR", "FRG", "FRI", "FRW", "FS", "FT", "FTL", "FW", "FZ",
|
|
39
|
+
"FÜ", "FÜS", "G", "GA", "GAN", "GAP", "GC", "GD", "GDB", "GE", "GEL",
|
|
40
|
+
"GEM", "GEO", "GER", "GF", "GG", "GHA", "GHC", "GI", "GK", "GL", "GLA",
|
|
41
|
+
"GM", "GMN", "GN", "GNT", "GOA", "GOH", "GP", "GR", "GRA", "GRH", "GRI",
|
|
42
|
+
"GRM", "GRS", "GRZ", "GS", "GT", "GTH", "GUB", "GUN", "GV", "GVM", "GW",
|
|
43
|
+
"GZ", "GÖ", "GÜ", "H", "HA", "HAB", "HAL", "HAM", "HAS", "HB", "HBN",
|
|
44
|
+
"HBS", "HC", "HCH", "HD", "HDH", "HDL", "HE", "HEB", "HEF", "HEI", "HEL",
|
|
45
|
+
"HER", "HET", "HF", "HG", "HGN", "HGW", "HH", "HHM", "HI", "HIG", "HIP",
|
|
46
|
+
"HK", "HL", "HM", "HMU", "HN", "HO", "HOG", "HOH", "HOL", "HOM", "HOR",
|
|
47
|
+
"HOT", "HP", "HR", "HRO", "HS", "HSK", "HST", "HU", "HUS", "HV", "HVL",
|
|
48
|
+
"HW", "HWI", "HX", "HY", "HZ", "HÖS", "HÜN", "IGB", "IK", "IL", "ILL",
|
|
49
|
+
"IN", "IS", "IZ", "J", "JB", "JE", "JEV", "JL", "JÜL", "K", "KA", "KAR",
|
|
50
|
+
"KB", "KC", "KE", "KEH", "KEL", "KEM", "KF", "KG", "KH", "KI", "KIB",
|
|
51
|
+
"KK", "KL", "KLE", "KLT", "KLZ", "KM", "KN", "KO", "KOZ", "KR", "KRU",
|
|
52
|
+
"KS", "KT", "KU", "KUS", "KW", "KY", "KYF", "KÖN", "KÖT", "KÜN", "L",
|
|
53
|
+
"LA", "LAN", "LAT", "LAU", "LB", "LBS", "LBZ", "LC", "LD", "LDK", "LDS",
|
|
54
|
+
"LE", "LEO", "LER", "LEV", "LF", "LG", "LH", "LI", "LIB", "LIF", "LIN",
|
|
55
|
+
"LIP", "LK", "LL", "LM", "LN", "LOH", "LOS", "LP", "LR", "LS", "LSA",
|
|
56
|
+
"LSN", "LSZ", "LU", "LUK", "LWL", "LÖ", "LÖB", "LÜD", "LÜN", "M", "MA",
|
|
57
|
+
"MAB", "MAI", "MAK", "MAL", "MAR", "MB", "MC", "MD", "ME", "MED", "MEG",
|
|
58
|
+
"MEI", "MEK", "MEL", "MEP", "MER", "MES", "MET", "MG", "MGH", "MGN", "MH",
|
|
59
|
+
"MHL", "MI", "MIL", "MK", "MKK", "ML", "MM", "MN", "MO", "MOD", "MOL",
|
|
60
|
+
"MON", "MOS", "MQ", "MR", "MS", "MSP", "MST", "MT", "MTK", "MTL", "MVL",
|
|
61
|
+
"MW", "MY", "MYK", "MZ", "MZG", "MÜ", "MÜB", "MÜL", "MÜN", "MÜR", "N",
|
|
62
|
+
"NAB", "NAI", "NAU", "NB", "ND", "NDH", "NE", "NEA", "NEB", "NEC", "NEN",
|
|
63
|
+
"NES", "NEU", "NEW", "NF", "NH", "NI", "NIB", "NK", "NL", "NM", "NMB",
|
|
64
|
+
"NMS", "NOH", "NOL", "NOM", "NOR", "NP", "NR", "NRW", "NRÜ", "NT", "NU",
|
|
65
|
+
"NVP", "NW", "NWM", "NY", "NZ", "NÖ", "OA", "OAL", "OB", "OBB", "OBG",
|
|
66
|
+
"OC", "OCH", "OD", "OE", "OF", "OG", "OH", "OHA", "OHV", "OHZ", "OK",
|
|
67
|
+
"OL", "OLD", "OP", "OPR", "OR", "OS", "OSL", "OTT", "OTW", "OVI", "OVL",
|
|
68
|
+
"OVP", "OZ", "P", "PA", "PAF", "PAN", "PAR", "PB", "PCH", "PE", "PEG",
|
|
69
|
+
"PER", "PF", "PI", "PIR", "PK", "PL", "PLÖ", "PM", "PN", "PR", "PRÜ",
|
|
70
|
+
"PS", "PW", "PZ", "QFT", "QLB", "R", "RA", "RC", "RD", "RDG", "RE", "REG",
|
|
71
|
+
"REH", "REI", "RG", "RH", "RI", "RID", "RIE", "RL", "RM", "RN", "RO",
|
|
72
|
+
"ROD", "ROF", "ROH", "ROK", "ROL", "ROS", "ROT", "ROW", "RPL", "RS",
|
|
73
|
+
"RSL", "RT", "RU", "RV", "RW", "RWL", "RY", "RZ", "RÜD", "RÜG", "S", "SA",
|
|
74
|
+
"SAB", "SAD", "SAL", "SAN", "SAO", "SAW", "SB", "SBG", "SBK", "SC", "SCZ",
|
|
75
|
+
"SDH", "SDL", "SDT", "SE", "SEB", "SEE", "SEF", "SEL", "SF", "SFA", "SFB",
|
|
76
|
+
"SFT", "SG", "SGH", "SH", "SHA", "SHG", "SHK", "SHL", "SHS", "SI", "SIG",
|
|
77
|
+
"SIM", "SK", "SL", "SLE", "SLF", "SLG", "SLK", "SLN", "SLS", "SLZ", "SLÜ",
|
|
78
|
+
"SM", "SMÜ", "SN", "SNH", "SO", "SOB", "SOG", "SOK", "SOL", "SON", "SP",
|
|
79
|
+
"SPB", "SPN", "SPR", "SR", "SRB", "SRO", "ST", "STA", "STB", "STD", "STE",
|
|
80
|
+
"STH", "STL", "STO", "SU", "SUL", "SW", "SWA", "SWZ", "SY", "SZ", "SZB",
|
|
81
|
+
"SÄK", "SÖM", "SÜW", "TBB", "TE", "TF", "TG", "TGO", "THL", "THW", "TIR",
|
|
82
|
+
"TO", "TP", "TR", "TS", "TT", "TUT", "TÖL", "TÖN", "TÜ", "UE", "UEM",
|
|
83
|
+
"UER", "UFF", "UH", "UL", "UM", "UN", "USI", "V", "VAI", "VB", "VEC",
|
|
84
|
+
"VER", "VIB", "VIE", "VIT", "VK", "VL", "VOF", "VOH", "VR", "VS", "W",
|
|
85
|
+
"WA", "WAF", "WAK", "WAM", "WAN", "WAR", "WAT", "WB", "WBS", "WD", "WDA",
|
|
86
|
+
"WE", "WEB", "WEG", "WEI", "WEL", "WEM", "WEN", "WER", "WES", "WF", "WG",
|
|
87
|
+
"WHV", "WI", "WIL", "WIS", "WIT", "WIZ", "WK", "WL", "WLG", "WM", "WMS",
|
|
88
|
+
"WN", "WND", "WO", "WOB", "WOH", "WOL", "WOR", "WOS", "WR", "WRN", "WS",
|
|
89
|
+
"WSF", "WST", "WSW", "WT", "WTL", "WTM", "WUG", "WUM", "WUN", "WUR", "WW",
|
|
90
|
+
"WZ", "WZL", "WÜ", "X", "Y", "Z", "ZE", "ZEL", "ZI", "ZIG", "ZP", "ZR",
|
|
91
|
+
"ZS", "ZW", "ZZ", "ÖHR", "ÜB",
|
|
92
|
+
]);
|
package/dist/cjs/rules/engine.js
CHANGED
|
@@ -1,15 +1,66 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RuleEngine = void 0;
|
|
3
|
+
exports.RuleEngine = exports.COUNTRY_CODE_ALIASES = void 0;
|
|
4
|
+
exports.resolveCountryCode = resolveCountryCode;
|
|
4
5
|
const types_js_1 = require("../types.js");
|
|
5
6
|
const matchers_js_1 = require("./matchers.js");
|
|
6
7
|
const suppressors_js_1 = require("./suppressors.js");
|
|
7
8
|
const structural_js_1 = require("./structural.js");
|
|
8
9
|
const index_js_1 = require("./countries/index.js");
|
|
10
|
+
/**
|
|
11
|
+
* Heuristic to detect nested quantifiers that cause catastrophic backtracking.
|
|
12
|
+
* After stripping escaped chars and character classes, look for an unbounded
|
|
13
|
+
* quantifier (+/*) closing a group that is itself quantified (+/*).
|
|
14
|
+
*/
|
|
15
|
+
const NESTED_QUANTIFIER_RE = /[+*]\)[+*]/;
|
|
16
|
+
function validateCustomPattern(pattern) {
|
|
17
|
+
try {
|
|
18
|
+
new RegExp(pattern, "gu");
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
throw new Error(`Invalid regex pattern: ${e instanceof Error ? e.message : e}`);
|
|
22
|
+
}
|
|
23
|
+
const stripped = pattern
|
|
24
|
+
.replace(/\\./g, "X")
|
|
25
|
+
.replace(/\[(?:[^\]\\]|\\.)*\]/g, "X");
|
|
26
|
+
if (NESTED_QUANTIFIER_RE.test(stripped)) {
|
|
27
|
+
throw new Error("Pattern contains nested quantifiers (e.g. (a+)+) which can cause " +
|
|
28
|
+
"catastrophic backtracking (ReDoS). Restructure the pattern to " +
|
|
29
|
+
"avoid quantifiers inside quantified groups.");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The country modules use the EU/VAT convention (UK, EL) while the public
|
|
34
|
+
* contract is ISO 3166-1 alpha-2 (GB, GR). Accept both spellings so a caller
|
|
35
|
+
* passing correct ISO codes is not punished for it.
|
|
36
|
+
*/
|
|
37
|
+
exports.COUNTRY_CODE_ALIASES = {
|
|
38
|
+
GB: "UK",
|
|
39
|
+
GR: "EL",
|
|
40
|
+
UK: "UK",
|
|
41
|
+
EL: "EL",
|
|
42
|
+
};
|
|
43
|
+
/** Map a caller-supplied code to a registered one, or null if unknown. */
|
|
44
|
+
function resolveCountryCode(countryCode) {
|
|
45
|
+
let code = countryCode.trim().toUpperCase();
|
|
46
|
+
code = exports.COUNTRY_CODE_ALIASES[code] ?? code;
|
|
47
|
+
return code in index_js_1.COUNTRY_CONFIGS && code !== "SHARED" ? code : null;
|
|
48
|
+
}
|
|
49
|
+
const warnedCountries = new Set();
|
|
50
|
+
function warnUnknownCountry(code) {
|
|
51
|
+
if (warnedCountries.has(code))
|
|
52
|
+
return;
|
|
53
|
+
warnedCountries.add(code);
|
|
54
|
+
const available = Object.keys(index_js_1.COUNTRY_CONFIGS).filter(c => c !== "SHARED").sort();
|
|
55
|
+
console.warn(`[euredact] Unknown country code: ${JSON.stringify(code)}. Continuing with ` +
|
|
56
|
+
`shared country-independent patterns only (email, IBAN, international ` +
|
|
57
|
+
`phone, credit card, ...). Available: ${available.join(", ")}`);
|
|
58
|
+
}
|
|
9
59
|
class RuleEngine {
|
|
10
60
|
matcher = new matchers_js_1.MultiPatternMatcher();
|
|
11
61
|
loadedCountries = new Set();
|
|
12
62
|
addCustomPattern(name, pattern) {
|
|
63
|
+
validateCustomPattern(pattern);
|
|
13
64
|
const pdef = {
|
|
14
65
|
entityType: name, pattern, validator: null,
|
|
15
66
|
description: "", contextKeywords: [], requiresContext: false,
|
|
@@ -25,7 +76,15 @@ class RuleEngine {
|
|
|
25
76
|
else {
|
|
26
77
|
configs = [];
|
|
27
78
|
for (const c of countryCodes) {
|
|
28
|
-
const code = c
|
|
79
|
+
const code = resolveCountryCode(c);
|
|
80
|
+
if (code === null) {
|
|
81
|
+
// Detection continues with the shared, country-independent
|
|
82
|
+
// patterns. Throwing on an unknown locale invites callers to wrap
|
|
83
|
+
// the call in try/catch and skip redaction — failing open, with
|
|
84
|
+
// unredacted PII in the output.
|
|
85
|
+
warnUnknownCountry(c);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
29
88
|
const config = index_js_1.COUNTRY_CONFIGS[code];
|
|
30
89
|
if (config)
|
|
31
90
|
configs.push(config);
|
|
@@ -47,38 +106,87 @@ class RuleEngine {
|
|
|
47
106
|
}
|
|
48
107
|
detect(text, countryCodes) {
|
|
49
108
|
this.loadCountries(countryCodes);
|
|
50
|
-
|
|
109
|
+
// Scope the scan to requested countries for efficiency
|
|
110
|
+
// Resolve through the alias table so `countries: ["GB"]` actually scans
|
|
111
|
+
// the patterns registered under "UK".
|
|
112
|
+
let scanCodes = null;
|
|
51
113
|
if (countryCodes !== null) {
|
|
52
|
-
|
|
53
|
-
|
|
114
|
+
scanCodes = new Set();
|
|
115
|
+
for (const c of countryCodes) {
|
|
116
|
+
const code = resolveCountryCode(c);
|
|
117
|
+
if (code !== null)
|
|
118
|
+
scanCodes.add(code);
|
|
119
|
+
}
|
|
120
|
+
scanCodes.add("SHARED");
|
|
121
|
+
scanCodes.add("CUSTOM");
|
|
54
122
|
}
|
|
123
|
+
let rawMatches = this.matcher.scan(text, scanCodes);
|
|
55
124
|
// Matches that have a validator but fail it create suppression zones:
|
|
56
125
|
// the span is recognisably a specific entity (e.g. IBAN-shaped) so
|
|
57
126
|
// overlapping regex-only matches (license plate, phone) are false
|
|
58
127
|
// positives and must be suppressed.
|
|
59
128
|
const validated = [];
|
|
60
|
-
const
|
|
129
|
+
const rawZones = [];
|
|
61
130
|
for (const m of rawMatches) {
|
|
62
|
-
|
|
63
|
-
|
|
131
|
+
const isValid = this.matcher.validate(m);
|
|
132
|
+
if (isValid) {
|
|
133
|
+
validated.push({ match: m, hasValidValidator: m.patternDef.validator !== null });
|
|
64
134
|
}
|
|
65
135
|
else if (m.patternDef.validator !== null && !m.patternDef.requiresContext) {
|
|
66
|
-
|
|
136
|
+
rawZones.push([m.start, m.end]);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Merge overlapping suppression zones for O(log n) containment checks
|
|
140
|
+
let zoneStarts = [];
|
|
141
|
+
let zoneEnds = [];
|
|
142
|
+
if (rawZones.length > 0) {
|
|
143
|
+
rawZones.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
144
|
+
let [ms, me] = rawZones[0];
|
|
145
|
+
for (let i = 1; i < rawZones.length; i++) {
|
|
146
|
+
const [zs, ze] = rawZones[i];
|
|
147
|
+
if (zs <= me) {
|
|
148
|
+
me = Math.max(me, ze);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
zoneStarts.push(ms);
|
|
152
|
+
zoneEnds.push(me);
|
|
153
|
+
ms = zs;
|
|
154
|
+
me = ze;
|
|
155
|
+
}
|
|
67
156
|
}
|
|
157
|
+
zoneStarts.push(ms);
|
|
158
|
+
zoneEnds.push(me);
|
|
68
159
|
}
|
|
69
160
|
// Build candidates with priority: validated (3) > custom (2) > regex-only (1)
|
|
70
161
|
const candidates = [];
|
|
71
|
-
for (const match of validated) {
|
|
162
|
+
for (const { match, hasValidValidator } of validated) {
|
|
72
163
|
if ((0, suppressors_js_1.shouldSuppress)(text, match))
|
|
73
164
|
continue;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
165
|
+
// O(log n) binary-search containment check on merged zones
|
|
166
|
+
if (match.patternDef.validator === null && zoneStarts.length > 0) {
|
|
167
|
+
let lo = 0, hi = zoneStarts.length;
|
|
168
|
+
while (lo < hi) {
|
|
169
|
+
const mid = (lo + hi) >>> 1;
|
|
170
|
+
if (zoneStarts[mid] <= match.start)
|
|
171
|
+
lo = mid + 1;
|
|
172
|
+
else
|
|
173
|
+
hi = mid;
|
|
77
174
|
}
|
|
175
|
+
const idx = lo - 1;
|
|
176
|
+
if (idx >= 0 && zoneEnds[idx] >= match.end)
|
|
177
|
+
continue;
|
|
78
178
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
179
|
+
// A bare digit run is the weakest evidence in the engine and must never
|
|
180
|
+
// re-cut a span a structured detector claims (PHONE, SSN, NATIONAL_ID,
|
|
181
|
+
// BANK_ACCOUNT, VAT...). Postal codes take unclaimed spans only,
|
|
182
|
+
// whatever the span lengths.
|
|
183
|
+
const priority = hasValidValidator
|
|
184
|
+
? 3
|
|
185
|
+
: match.countryCode === "CUSTOM"
|
|
186
|
+
? 2
|
|
187
|
+
: match.patternDef.entityType === types_js_1.EntityType.POSTAL_CODE
|
|
188
|
+
? 0
|
|
189
|
+
: 1;
|
|
82
190
|
const spanLength = match.end - match.start;
|
|
83
191
|
const score = priority * 1_000_000 + spanLength;
|
|
84
192
|
candidates.push({
|
|
@@ -109,16 +217,20 @@ class RuleEngine {
|
|
|
109
217
|
return [];
|
|
110
218
|
const sorted = [...candidates].sort((a, b) => b.score - a.score);
|
|
111
219
|
const result = [];
|
|
220
|
+
const occupied = new Set();
|
|
112
221
|
for (const { det } of sorted) {
|
|
113
222
|
let overlaps = false;
|
|
114
|
-
for (
|
|
115
|
-
if (
|
|
223
|
+
for (let p = det.start; p < det.end; p++) {
|
|
224
|
+
if (occupied.has(p)) {
|
|
116
225
|
overlaps = true;
|
|
117
226
|
break;
|
|
118
227
|
}
|
|
119
228
|
}
|
|
120
|
-
if (!overlaps)
|
|
229
|
+
if (!overlaps) {
|
|
230
|
+
for (let p = det.start; p < det.end; p++)
|
|
231
|
+
occupied.add(p);
|
|
121
232
|
result.push(det);
|
|
233
|
+
}
|
|
122
234
|
}
|
|
123
235
|
return result;
|
|
124
236
|
}
|
|
@@ -22,11 +22,13 @@ class MultiPatternMatcher {
|
|
|
22
22
|
compile() {
|
|
23
23
|
this.compiled = true;
|
|
24
24
|
}
|
|
25
|
-
scan(text) {
|
|
25
|
+
scan(text, countryCodes = null) {
|
|
26
26
|
if (!this.compiled)
|
|
27
27
|
this.compile();
|
|
28
28
|
const matches = [];
|
|
29
29
|
for (const { regex, patternDef, countryCode } of this.patterns) {
|
|
30
|
+
if (countryCodes !== null && !countryCodes.has(countryCode))
|
|
31
|
+
continue;
|
|
30
32
|
regex.lastIndex = 0;
|
|
31
33
|
let m;
|
|
32
34
|
while ((m = regex.exec(text)) !== null) {
|