kordoc 4.14.2 → 4.14.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/README.md +38 -5
- package/dist/{-AQKXLFIM.js → -7XBPXCXN.js} +13 -11
- package/dist/{chunk-FOJZNMMK.js → chunk-2LQGSDZ6.js} +1 -1
- package/dist/chunk-2LQGSDZ6.js.map +1 -0
- package/dist/chunk-4R67INKT.js +1002 -0
- package/dist/chunk-4R67INKT.js.map +1 -0
- package/dist/{chunk-ILU76UF3.js → chunk-4RJ5D7MT.js} +2 -2
- package/dist/chunk-4RJ5D7MT.js.map +1 -0
- package/dist/{chunk-EQZI77PP.cjs → chunk-5VVS2QIJ.cjs} +3 -3
- package/dist/{chunk-EQZI77PP.cjs.map → chunk-5VVS2QIJ.cjs.map} +1 -1
- package/dist/{chunk-3DUADAY2.js → chunk-A275ZQLX.js} +209 -87
- package/dist/chunk-A275ZQLX.js.map +1 -0
- package/dist/{chunk-EISO3POT.js → chunk-CVEERPMP.js} +788 -194
- package/dist/chunk-CVEERPMP.js.map +1 -0
- package/dist/{chunk-ZWD734EY.js → chunk-D6KQKMU3.js} +2 -2
- package/dist/{chunk-QOMNEWCQ.cjs → chunk-EBZUZC6F.cjs} +305 -31
- package/dist/chunk-EBZUZC6F.cjs.map +1 -0
- package/dist/{chunk-HR2XNERB.cjs → chunk-EMGF6EEV.cjs} +2 -2
- package/dist/{chunk-HR2XNERB.cjs.map → chunk-EMGF6EEV.cjs.map} +1 -1
- package/dist/{chunk-GDW4YP7K.js → chunk-FAZR34CA.js} +2 -2
- package/dist/{chunk-TNCKOL2O.js → chunk-GTINVM5K.js} +2 -2
- package/dist/{chunk-L7DMSMAG.cjs → chunk-HX4UUF7Y.cjs} +1 -1
- package/dist/chunk-HX4UUF7Y.cjs.map +1 -0
- package/dist/{chunk-UET462HB.js → chunk-I4NNYWIR.js} +2 -2
- package/dist/chunk-IH56ZISC.js +620 -0
- package/dist/chunk-IH56ZISC.js.map +1 -0
- package/dist/{chunk-EDNJLUSE.js → chunk-IHZEFW4L.js} +2 -2
- package/dist/chunk-IW2CB755.cjs +1002 -0
- package/dist/chunk-IW2CB755.cjs.map +1 -0
- package/dist/{chunk-ZWOFQRIB.js → chunk-JCVIOLBF.js} +3 -3
- package/dist/{chunk-JQEFP45L.js → chunk-L5EH2NVO.js} +2 -2
- package/dist/{chunk-247ANM72.cjs → chunk-NOAQRCV6.cjs} +219 -97
- package/dist/chunk-NOAQRCV6.cjs.map +1 -0
- package/dist/{chunk-7IAQ4HOK.js → chunk-NQCUAGLR.js} +1 -1
- package/dist/chunk-NQCUAGLR.js.map +1 -0
- package/dist/{chunk-NS55SM7A.js → chunk-O2PGQNYE.js} +2 -2
- package/dist/{chunk-QC5T6D4J.js → chunk-QGW7NMTX.js} +299 -25
- package/dist/chunk-QGW7NMTX.js.map +1 -0
- package/dist/{chunk-A7QC2ZVP.js → chunk-UVVEBI22.js} +209 -87
- package/dist/chunk-UVVEBI22.js.map +1 -0
- package/dist/chunk-VJHAUMCF.js +949 -0
- package/dist/chunk-VJHAUMCF.js.map +1 -0
- package/dist/{chunk-C7FQBSON.js → chunk-VNFJPTU4.js} +2 -2
- package/dist/{chunk-VQ5W4MYO.js → chunk-VYOGXNQ3.js} +298 -24
- package/dist/chunk-VYOGXNQ3.js.map +1 -0
- package/dist/{chunk-DOGPOY4B.js → chunk-WB5LW52Z.js} +2 -2
- package/dist/{chunk-TSFS5VKD.js → chunk-YGBZABI4.js} +332 -141
- package/dist/chunk-YGBZABI4.js.map +1 -0
- package/dist/chunks-I7TEMJXH.js +10 -0
- package/dist/cli.js +228 -97
- package/dist/cli.js.map +1 -1
- package/dist/{formula-PJJ6S3Z2.cjs → formula-CPO2TZGB.cjs} +9 -9
- package/dist/{formula-PJJ6S3Z2.cjs.map → formula-CPO2TZGB.cjs.map} +1 -1
- package/dist/{formula-5S7WWXGH.js → formula-NOIDZEBA.js} +4 -4
- package/dist/{formula-JGFTXWYT.js → formula-X44GV7TN.js} +3 -3
- package/dist/{image-ocr-WCAYXLOA.js → image-ocr-72RAR4WR.js} +34 -16
- package/dist/image-ocr-72RAR4WR.js.map +1 -0
- package/dist/{image-ocr-FLIL7MGN.js → image-ocr-R6W6A75P.js} +32 -14
- package/dist/image-ocr-R6W6A75P.js.map +1 -0
- package/dist/{image-ocr-ZTYAGV3W.cjs → image-ocr-SUGT64ZM.cjs} +36 -18
- package/dist/image-ocr-SUGT64ZM.cjs.map +1 -0
- package/dist/index.cjs +2416 -1184
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -25
- package/dist/index.d.ts +62 -25
- package/dist/index.js +1853 -621
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +74 -53
- package/dist/mcp.js.map +1 -1
- package/dist/{model-bundle-FSPZZ2EM.js → model-bundle-57CLEBCO.js} +5 -5
- package/dist/{models-CJPAQK5R.js → models-7ELIFTD7.js} +5 -5
- package/dist/{parser-SCUTZAIJ.js → parser-4NKAYWAX.js} +261 -241
- package/dist/parser-4NKAYWAX.js.map +1 -0
- package/dist/{parser-A4TMPJAM.js → parser-HWFQRSUQ.js} +261 -241
- package/dist/parser-HWFQRSUQ.js.map +1 -0
- package/dist/{parser-VCXE4I5A.cjs → parser-RANGXGBD.cjs} +289 -269
- package/dist/parser-RANGXGBD.cjs.map +1 -0
- package/dist/pdf-ocr-BNC34MNU.js +13 -0
- package/dist/pdf-ocr-QX2GB33Q.js +16 -0
- package/dist/pdf-ocr-VHSFACH7.cjs +14 -0
- package/dist/{pdf-ocr-MBZZ57LD.cjs.map → pdf-ocr-VHSFACH7.cjs.map} +1 -1
- package/dist/{profile-io-LBM263K7.js → profile-io-V52PL5ZO.js} +3 -3
- package/dist/{rasterize-46RG4ZF6.js → rasterize-ZIGLJDOG.js} +3 -3
- package/dist/redact-TIQEQI3M.js +18 -0
- package/dist/redact-doc-KXZ3TZAG.js +1216 -0
- package/dist/redact-doc-KXZ3TZAG.js.map +1 -0
- package/dist/{render-NGP5T24S.js → render-U5PE6V2E.js} +7 -7
- package/dist/seal-DNXJBILR.js +10 -0
- package/dist/{setup-AU4OCHNH.js → setup-YRDM4ODP.js} +3 -3
- package/dist/visual-5XWMA4EV.js +19 -0
- package/dist/{watch-MKPUCPXM.js → watch-5IOLBATT.js} +12 -12
- package/package.json +2 -2
- package/dist/chunk-247ANM72.cjs.map +0 -1
- package/dist/chunk-3DUADAY2.js.map +0 -1
- package/dist/chunk-422427XJ.cjs +0 -571
- package/dist/chunk-422427XJ.cjs.map +0 -1
- package/dist/chunk-7IAQ4HOK.js.map +0 -1
- package/dist/chunk-A7QC2ZVP.js.map +0 -1
- package/dist/chunk-DZIXKL7E.js +0 -145
- package/dist/chunk-DZIXKL7E.js.map +0 -1
- package/dist/chunk-EISO3POT.js.map +0 -1
- package/dist/chunk-EP7KOI2V.js +0 -571
- package/dist/chunk-EP7KOI2V.js.map +0 -1
- package/dist/chunk-FOJZNMMK.js.map +0 -1
- package/dist/chunk-ILU76UF3.js.map +0 -1
- package/dist/chunk-KVWV4ULP.js +0 -516
- package/dist/chunk-KVWV4ULP.js.map +0 -1
- package/dist/chunk-L7DMSMAG.cjs.map +0 -1
- package/dist/chunk-QC5T6D4J.js.map +0 -1
- package/dist/chunk-QOMNEWCQ.cjs.map +0 -1
- package/dist/chunk-TSFS5VKD.js.map +0 -1
- package/dist/chunk-VQ5W4MYO.js.map +0 -1
- package/dist/chunks-SX77P5CV.js +0 -10
- package/dist/image-ocr-FLIL7MGN.js.map +0 -1
- package/dist/image-ocr-WCAYXLOA.js.map +0 -1
- package/dist/image-ocr-ZTYAGV3W.cjs.map +0 -1
- package/dist/parser-A4TMPJAM.js.map +0 -1
- package/dist/parser-SCUTZAIJ.js.map +0 -1
- package/dist/parser-VCXE4I5A.cjs.map +0 -1
- package/dist/pdf-ocr-76SAUXMZ.js +0 -13
- package/dist/pdf-ocr-CPU55CVF.js +0 -16
- package/dist/pdf-ocr-MBZZ57LD.cjs +0 -14
- package/dist/redact-7ILEAUTS.js +0 -12
- package/dist/seal-RGUPOLXD.js +0 -10
- package/dist/visual-NZLYIYEM.js +0 -19
- /package/dist/{-AQKXLFIM.js.map → -7XBPXCXN.js.map} +0 -0
- /package/dist/{chunk-ZWD734EY.js.map → chunk-D6KQKMU3.js.map} +0 -0
- /package/dist/{chunk-GDW4YP7K.js.map → chunk-FAZR34CA.js.map} +0 -0
- /package/dist/{chunk-TNCKOL2O.js.map → chunk-GTINVM5K.js.map} +0 -0
- /package/dist/{chunk-UET462HB.js.map → chunk-I4NNYWIR.js.map} +0 -0
- /package/dist/{chunk-EDNJLUSE.js.map → chunk-IHZEFW4L.js.map} +0 -0
- /package/dist/{chunk-ZWOFQRIB.js.map → chunk-JCVIOLBF.js.map} +0 -0
- /package/dist/{chunk-JQEFP45L.js.map → chunk-L5EH2NVO.js.map} +0 -0
- /package/dist/{chunk-NS55SM7A.js.map → chunk-O2PGQNYE.js.map} +0 -0
- /package/dist/{chunk-C7FQBSON.js.map → chunk-VNFJPTU4.js.map} +0 -0
- /package/dist/{chunk-DOGPOY4B.js.map → chunk-WB5LW52Z.js.map} +0 -0
- /package/dist/{chunks-SX77P5CV.js.map → chunks-I7TEMJXH.js.map} +0 -0
- /package/dist/{formula-5S7WWXGH.js.map → formula-NOIDZEBA.js.map} +0 -0
- /package/dist/{formula-JGFTXWYT.js.map → formula-X44GV7TN.js.map} +0 -0
- /package/dist/{model-bundle-FSPZZ2EM.js.map → model-bundle-57CLEBCO.js.map} +0 -0
- /package/dist/{models-CJPAQK5R.js.map → models-7ELIFTD7.js.map} +0 -0
- /package/dist/{pdf-ocr-76SAUXMZ.js.map → pdf-ocr-BNC34MNU.js.map} +0 -0
- /package/dist/{pdf-ocr-CPU55CVF.js.map → pdf-ocr-QX2GB33Q.js.map} +0 -0
- /package/dist/{profile-io-LBM263K7.js.map → profile-io-V52PL5ZO.js.map} +0 -0
- /package/dist/{rasterize-46RG4ZF6.js.map → rasterize-ZIGLJDOG.js.map} +0 -0
- /package/dist/{redact-7ILEAUTS.js.map → redact-TIQEQI3M.js.map} +0 -0
- /package/dist/{render-NGP5T24S.js.map → render-U5PE6V2E.js.map} +0 -0
- /package/dist/{seal-RGUPOLXD.js.map → seal-DNXJBILR.js.map} +0 -0
- /package/dist/{setup-AU4OCHNH.js.map → setup-YRDM4ODP.js.map} +0 -0
- /package/dist/{visual-NZLYIYEM.js.map → visual-5XWMA4EV.js.map} +0 -0
- /package/dist/{watch-MKPUCPXM.js.map → watch-5IOLBATT.js.map} +0 -0
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/redact-rules.ts
|
|
4
|
+
var RULE_PRIORITY = [
|
|
5
|
+
"crn",
|
|
6
|
+
"rrn",
|
|
7
|
+
"email",
|
|
8
|
+
"card",
|
|
9
|
+
"brn",
|
|
10
|
+
"phone",
|
|
11
|
+
"driver",
|
|
12
|
+
"passport",
|
|
13
|
+
"account",
|
|
14
|
+
"ip"
|
|
15
|
+
];
|
|
16
|
+
function luhnValid(digits) {
|
|
17
|
+
let sum = 0;
|
|
18
|
+
for (let i = 0; i < digits.length; i++) {
|
|
19
|
+
let d = digits.charCodeAt(digits.length - 1 - i) - 48;
|
|
20
|
+
if (i % 2 === 1) {
|
|
21
|
+
d *= 2;
|
|
22
|
+
if (d > 9) d -= 9;
|
|
23
|
+
}
|
|
24
|
+
sum += d;
|
|
25
|
+
}
|
|
26
|
+
return sum % 10 === 0;
|
|
27
|
+
}
|
|
28
|
+
var DAYS_IN_MONTH = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
29
|
+
function birthdateValid(front6, genderDigit) {
|
|
30
|
+
const yy = Number(front6.slice(0, 2));
|
|
31
|
+
const mm = Number(front6.slice(2, 4));
|
|
32
|
+
const dd = Number(front6.slice(4, 6));
|
|
33
|
+
if (mm < 1 || mm > 12 || dd < 1) return false;
|
|
34
|
+
const g2 = Number(genderDigit);
|
|
35
|
+
const year = (g2 === 3 || g2 === 4 || g2 === 7 || g2 === 8 ? 2e3 : 1900) + yy;
|
|
36
|
+
if (year > (/* @__PURE__ */ new Date()).getFullYear()) return false;
|
|
37
|
+
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
38
|
+
const max = mm === 2 ? leap ? 29 : 28 : DAYS_IN_MONTH[mm - 1];
|
|
39
|
+
return dd <= max;
|
|
40
|
+
}
|
|
41
|
+
function brnChecksumValid(d) {
|
|
42
|
+
const w = [1, 3, 7, 1, 3, 7, 1, 3, 5];
|
|
43
|
+
let sum = 0;
|
|
44
|
+
for (let i = 0; i < 9; i++) sum += w[i] * (d.charCodeAt(i) - 48);
|
|
45
|
+
sum += Math.floor((d.charCodeAt(8) - 48) * 5 / 10);
|
|
46
|
+
return (10 - sum % 10) % 10 === d.charCodeAt(9) - 48;
|
|
47
|
+
}
|
|
48
|
+
function crnChecksumValid(d) {
|
|
49
|
+
let sum = 0;
|
|
50
|
+
for (let i = 0; i < 12; i++) sum += (i % 2 === 0 ? 1 : 2) * (d.charCodeAt(i) - 48);
|
|
51
|
+
return (10 - sum % 10) % 10 === d.charCodeAt(12) - 48;
|
|
52
|
+
}
|
|
53
|
+
var digitsOf = (s) => s.replace(/\D/g, "");
|
|
54
|
+
var allSameDigit = (d) => d.length > 0 && /^(\d)\1*$/.test(d);
|
|
55
|
+
var LABELS = [
|
|
56
|
+
["crn", /법\s*인\s*(?:등\s*록\s*)?번\s*호/g],
|
|
57
|
+
["rrn", /주\s*민\s*(?:등\s*록\s*)?번\s*호|주\s*민\s*등\s*록|외\s*국\s*인\s*(?:등\s*록\s*)?번\s*호|거\s*소\s*신\s*고\s*번\s*호|생\s*년\s*월\s*일|resident\s*registration|\bRRN\b/gi],
|
|
58
|
+
["brn", /사\s*업\s*자\s*(?:등\s*록\s*)?번\s*호|고\s*유\s*번\s*호|business\s*registration/gi],
|
|
59
|
+
["account", /계\s*좌|예\s*금\s*주|입\s*금|은\s*행|뱅\s*크|농\s*협|신\s*협|수\s*협|우\s*체\s*국|금\s*고|bank\s*account|account\s*(?:no|number)/gi],
|
|
60
|
+
["card", /카\s*드|credit\s*card|card\s*(?:no|number)/gi],
|
|
61
|
+
["phone", /전\s*화|연\s*락\s*처|휴\s*대|핸\s*드\s*폰|팩\s*스|☎|☏|콜\s*센\s*터|고\s*객\s*센\s*터|대\s*표\s*번\s*호|상\s*담|문\s*의|\b(?:tel|fax|mobile|phone|h\.?p|ARS)\b/gi],
|
|
62
|
+
["passport", /여\s*권|passport/gi],
|
|
63
|
+
["driver", /운\s*전\s*면\s*허|면\s*허\s*번\s*호|driver'?s?\s*licen[cs]e/gi],
|
|
64
|
+
["ip", /\bIP\b|아\s*이\s*피/g],
|
|
65
|
+
["other", /출\s*원\s*번\s*호|과\s*제\s*번\s*호|사\s*업\s*번\s*호|접\s*수\s*번\s*호|민\s*원\s*번\s*호|신\s*청\s*번\s*호|문\s*서\s*번\s*호|관\s*리\s*번\s*호|주\s*문\s*번\s*호|일\s*련\s*번\s*호|발\s*간\s*등\s*록|등\s*록\s*번\s*호\s*\(\s*기\s*록|ISBN|ISSN|예\s*산|과\s*목|코\s*드|모\s*델|제\s*품|부\s*품|시\s*리\s*얼|serial|model|\bS\/N\b|바\s*코\s*드|기\s*록\s*물|사\s*건\s*번\s*호/gi]
|
|
66
|
+
];
|
|
67
|
+
var CELL_SEP_RE = /(?<!\\)\||<\/t[dh]>/gi;
|
|
68
|
+
var PREV_VALUE_RE = /\d[\d\-.\s]{4,}\d/g;
|
|
69
|
+
function labelsIn(s) {
|
|
70
|
+
const out = /* @__PURE__ */ new Set();
|
|
71
|
+
for (const [ctx, re] of LABELS) {
|
|
72
|
+
re.lastIndex = 0;
|
|
73
|
+
if (re.test(s)) out.add(ctx);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
function labelsBefore(norm, start) {
|
|
78
|
+
let win = norm.slice(Math.max(0, start - 120), start);
|
|
79
|
+
const seps = [...win.matchAll(CELL_SEP_RE)];
|
|
80
|
+
if (seps.length >= 2) {
|
|
81
|
+
const s = seps[seps.length - 2];
|
|
82
|
+
win = win.slice(s.index + s[0].length);
|
|
83
|
+
}
|
|
84
|
+
win = win.replace(/<[^>]*>/g, " ").slice(-40);
|
|
85
|
+
const prev = [...win.matchAll(PREV_VALUE_RE)].pop();
|
|
86
|
+
if (prev) win = win.slice(prev.index + prev[0].length);
|
|
87
|
+
return labelsIn(win);
|
|
88
|
+
}
|
|
89
|
+
var OTHER_AFTER_RE = /^\s{0,2}[(\[]?\s{0,2}(?:접\s*수|신\s*청|주\s*문|사\s*건|과\s*제|문\s*서|관\s*리|부\s*품|제\s*품|모\s*델|코\s*드|시\s*리\s*얼|serial|model)/i;
|
|
90
|
+
var g = (x, name) => x.m.groups?.[name] ?? "";
|
|
91
|
+
var has = (x, c) => x.ctx().has(c);
|
|
92
|
+
var crnOnly = (x) => has(x, "crn") && !has(x, "rrn");
|
|
93
|
+
var after = (x, n = 16) => {
|
|
94
|
+
const end = x.m.index + x.m[0].length;
|
|
95
|
+
return x.m.input.slice(end, end + n);
|
|
96
|
+
};
|
|
97
|
+
var before = (x, n = 2) => {
|
|
98
|
+
const start = x.m.index;
|
|
99
|
+
return x.m.input.slice(Math.max(0, start - n), start);
|
|
100
|
+
};
|
|
101
|
+
var otherCtx = (x, own) => has(x, "other") && !has(x, own) || OTHER_AFTER_RE.test(after(x));
|
|
102
|
+
var SERIAL_BEFORE_RE = /(?:\bS\/N|\bserial(?:\s*(?:no|number))?|시\s*리\s*얼(?:\s*번\s*호)?|일\s*련\s*번\s*호|바\s*코\s*드)\s*[:.]?\s*$/i;
|
|
103
|
+
var serialBefore = (x) => SERIAL_BEFORE_RE.test(before(x, 20)) && !has(x, "phone");
|
|
104
|
+
var PHONE_PREFIX = "0(?:1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80|60|50[2-8])";
|
|
105
|
+
var PHONE_AREA = "0(?:1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80|50[2-8])";
|
|
106
|
+
var PHONE_SEP = "(?: {0,2}- {0,2}| {1,2}|\\.)";
|
|
107
|
+
var PHONE_END = "(?![\\w@-])";
|
|
108
|
+
function phoneSepsOk(x, s1, s2) {
|
|
109
|
+
if (s1 === "" !== (s2 === "")) return false;
|
|
110
|
+
if (s1 === "." !== (s2 === ".")) return false;
|
|
111
|
+
return s1 !== "." || !/^\.\d/.test(after(x, 2));
|
|
112
|
+
}
|
|
113
|
+
function phoneDigitsOk(pre, mid, last, bare) {
|
|
114
|
+
if (/^0+$/.test(mid + last)) return false;
|
|
115
|
+
return !bare || !/^0(?:2|3[1-3]|4[1-4]|5[1-5]|6[1-4])$/.test(pre) || mid[0] !== "0";
|
|
116
|
+
}
|
|
117
|
+
var EMAIL_START = "(?<![\\w%+])(?<!\\w[.\\-])(?<![A-Za-z0-9][*\u25CF\u25CB\u25EF\xD7#]+)";
|
|
118
|
+
var EMAIL_DOMAIN = "(?<domain>[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*\\.(?<tld>[A-Za-z]{2,24}))(?![\\w-])";
|
|
119
|
+
var FILE_EXT_TLD = /^(?:png|jpe?g|gif|bmp|svg|webp|tiff?|hwpx?|hwp|pdf|docx?|xlsx?|pptx?|txt|zip|csv|json|xml|html?|js|css|mp[34]|wav|avi|exe)$/i;
|
|
120
|
+
var RULES = {
|
|
121
|
+
// 주민·외국인등록번호 — 앞 6자리(생년월일) 유지, 뒤 7자리 마스크
|
|
122
|
+
rrn: [
|
|
123
|
+
{
|
|
124
|
+
// 하이픈형 (앞뒤 공백 0~2칸, 유니코드 대시는 정규화로 흡수). 뒤에 "-글자"는 허용, "-숫자"는 더 긴 코드.
|
|
125
|
+
// 법인 라벨만 있으면 제외
|
|
126
|
+
re: /(?<![\w-])(?<front>\d{6})(?<sep> {0,2}- {0,2})(?<back>[1-8]\d{6})(?![\w])(?!-\d)/dg,
|
|
127
|
+
ok: (x) => birthdateValid(g(x, "front"), g(x, "back")[0]) && !crnOnly(x),
|
|
128
|
+
mask: ["back"]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
// 뒷자리 일부만 가린 형태 (900101-1******, 900101-1694***) — 남은 숫자를 가린다
|
|
132
|
+
re: /(?<![\w-])(?<front>\d{6})(?<sep> {0,2}- {0,2})(?<back>[1-8][0-9*●○◯×xX#]{6})(?![\w*●○◯×#])(?!-\d)/dg,
|
|
133
|
+
ok: (x) => /[*●○◯×xX#]/.test(g(x, "back")) && birthdateValid(g(x, "front"), g(x, "back")[0]) && !crnOnly(x),
|
|
134
|
+
mask: ["back"]
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
// 구분자 없음·공백/온점/가운뎃점/빗금/물결 구분 — 주민/외국인등록번호(생년월일) 라벨이 있을 때만
|
|
138
|
+
re: /(?<![\w@.-])(?<front>\d{6})(?<sep>[ .·/~]?)(?<back>[1-8]\d{6})(?![\w@-])/dg,
|
|
139
|
+
needs: "rrn",
|
|
140
|
+
ok: (x) => birthdateValid(g(x, "front"), g(x, "back")[0]),
|
|
141
|
+
mask: ["back"]
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
// 법인등록번호 (opt-in) — 법인 라벨이 있거나, 뒷자리가 0으로 시작(주민번호 불가)하고 체크섬 유효
|
|
145
|
+
crn: [
|
|
146
|
+
{
|
|
147
|
+
re: /(?<![\w-])(?<front>\d{6})(?<sep> {0,2}- {0,2})(?<back>\d{7})(?![\w-])/dg,
|
|
148
|
+
ok: (x) => !allSameDigit(g(x, "front") + g(x, "back")) && (crnOnly(x) || g(x, "back")[0] === "0" && crnChecksumValid(g(x, "front") + g(x, "back"))),
|
|
149
|
+
mask: ["back"]
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
re: /(?<![\w@.-])(?<front>\d{6})(?<back>\d{7})(?![\w@-])/dg,
|
|
153
|
+
needs: "crn",
|
|
154
|
+
ok: (x) => !allSameDigit(g(x, "front") + g(x, "back")) && !has(x, "rrn"),
|
|
155
|
+
mask: ["back"]
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
// 이메일 — 로컬파트 첫 글자만 남기고 마스크, 도메인 유지. 파일명(image@2x.png)은 제외
|
|
159
|
+
// 이미 가린 로컬파트(j*********5@…) 뒤꼬리에서 시작하지 않는다 — 가림표 바로 뒤 매치 금지
|
|
160
|
+
email: [
|
|
161
|
+
{
|
|
162
|
+
re: new RegExp(`${EMAIL_START}(?<l1>[A-Za-z0-9])(?<lrest>[A-Za-z0-9._%+-]+)@${EMAIL_DOMAIN}`, "dg"),
|
|
163
|
+
ok: (x) => !FILE_EXT_TLD.test(g(x, "tld")),
|
|
164
|
+
mask: ["lrest"],
|
|
165
|
+
maskAll: true
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
// 한 글자 로컬파트 — 첫 글자를 남기면 가린 게 없으므로 전부 가린다
|
|
169
|
+
re: new RegExp(`${EMAIL_START}(?<l1>[A-Za-z0-9])@${EMAIL_DOMAIN}`, "dg"),
|
|
170
|
+
ok: (x) => !FILE_EXT_TLD.test(g(x, "tld")),
|
|
171
|
+
mask: ["l1"]
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
// 카드번호 — 가운데 마스크 + Luhn. 구분자 없는 15~16자리는 카드 라벨이 있을 때만
|
|
175
|
+
card: [
|
|
176
|
+
{
|
|
177
|
+
// 4-4-4-4 (같은 구분자 강제)
|
|
178
|
+
re: /(?<![\w-])(?<g1>\d{4})(?<sep>[- ])(?<g2>\d{4})\k<sep>(?<g3>\d{4})\k<sep>(?<g4>\d{4})(?![\w-])(?![- ]\d)/dg,
|
|
179
|
+
ok: (x) => {
|
|
180
|
+
const d = digitsOf(x.m[0]);
|
|
181
|
+
return luhnValid(d) && !allSameDigit(d);
|
|
182
|
+
},
|
|
183
|
+
mask: ["g2", "g3"]
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
// AMEX 4-6-5
|
|
187
|
+
re: /(?<![\w-])(?<g1>3[47]\d{2})(?<sep>[- ])(?<g2>\d{6})\k<sep>(?<g3a>\d)(?<g3b>\d{4})(?![\w-])(?![- ]\d)/dg,
|
|
188
|
+
ok: (x) => luhnValid(digitsOf(x.m[0])),
|
|
189
|
+
mask: ["g2", "g3a"]
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
// 온점 구분 4.4.4.4 — 카드 라벨 필요 (버전·좌표 모양과 겹침)
|
|
193
|
+
re: /(?<![\w.-])(?<g1>\d{4})\.(?<g2>\d{4})\.(?<g3>\d{4})\.(?<g4>\d{4})(?![\w-])(?!\.\d)/dg,
|
|
194
|
+
needs: "card",
|
|
195
|
+
ok: (x) => {
|
|
196
|
+
const d = digitsOf(x.m[0]);
|
|
197
|
+
return luhnValid(d) && !allSameDigit(d);
|
|
198
|
+
},
|
|
199
|
+
mask: ["g2", "g3"]
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
// 무구분 15~16자리 — 카드 라벨 필요
|
|
203
|
+
re: /(?<![\w@.-])(?<g1>\d{4})(?<mid>\d{7,8})(?<g4>\d{4})(?![\w@-])/dg,
|
|
204
|
+
needs: "card",
|
|
205
|
+
ok: (x) => {
|
|
206
|
+
const d = digitsOf(x.m[0]);
|
|
207
|
+
return luhnValid(d) && !allSameDigit(d);
|
|
208
|
+
},
|
|
209
|
+
mask: ["mid"]
|
|
210
|
+
}
|
|
211
|
+
],
|
|
212
|
+
// 사업자등록번호 3-2-5 — 체크섬 유효 또는 사업자 라벨. 뒤 5자리(일련·검증번호) 마스크
|
|
213
|
+
brn: [
|
|
214
|
+
{
|
|
215
|
+
re: /(?<![\w-])(?<a>\d{3})-(?<b>\d{2})-(?<c>\d{5})(?![\w-])/dg,
|
|
216
|
+
ok: (x) => {
|
|
217
|
+
const d = digitsOf(x.m[0]);
|
|
218
|
+
if (allSameDigit(d)) return false;
|
|
219
|
+
return has(x, "brn") || brnChecksumValid(d) && !has(x, "account") && !otherCtx(x, "brn");
|
|
220
|
+
},
|
|
221
|
+
mask: ["c"]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
re: /(?<![\w@.-])(?<a>\d{3})(?<b>\d{2})(?<c>\d{5})(?![\w@-])/dg,
|
|
225
|
+
needs: "brn",
|
|
226
|
+
ok: (x) => !allSameDigit(digitsOf(x.m[0])),
|
|
227
|
+
mask: ["c"]
|
|
228
|
+
}
|
|
229
|
+
],
|
|
230
|
+
// 전화번호 — 가운데 자리만 마스크 (대표번호는 뒤 4자리).
|
|
231
|
+
// 뒤에 "-"가 붙으면 더 긴 코드(예산과목 064-1100-1131-301-210)의 일부라 제외.
|
|
232
|
+
phone: [
|
|
233
|
+
{
|
|
234
|
+
// 국내 일반: 휴대폰·지역·070/080/060·050x 안심번호. 구분자 -·공백·온점(PHONE_SEP) 또는 무구분
|
|
235
|
+
re: new RegExp(`(?<![\\w@+-])(?<pre>${PHONE_PREFIX})(?<s1>${PHONE_SEP}?)(?<mid>\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\d{4})${PHONE_END}`, "dg"),
|
|
236
|
+
ok: (x) => {
|
|
237
|
+
const pre = g(x, "pre");
|
|
238
|
+
const bare = g(x, "s1") === "";
|
|
239
|
+
if (!phoneSepsOk(x, g(x, "s1"), g(x, "s2"))) return false;
|
|
240
|
+
if (!phoneDigitsOk(pre, g(x, "mid"), g(x, "last"), bare)) return false;
|
|
241
|
+
if (serialBefore(x)) return false;
|
|
242
|
+
if (!bare) return true;
|
|
243
|
+
if (/\d\.$/.test(before(x))) return false;
|
|
244
|
+
if (pre.startsWith("01")) return true;
|
|
245
|
+
return !/\d,$/.test(before(x)) && !/^,\d/.test(after(x, 2));
|
|
246
|
+
},
|
|
247
|
+
mask: ["mid"]
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
// 괄호 지역번호: (02) 123-4567, (042)481-6300
|
|
251
|
+
re: new RegExp(`(?<![\\w@])\\((?<pre>${PHONE_AREA})\\) ?(?<mid>\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\d{4})${PHONE_END}`, "dg"),
|
|
252
|
+
ok: (x) => phoneDigitsOk(g(x, "pre"), g(x, "mid"), g(x, "last"), false) && (g(x, "s2") !== "." || !/^\.\d/.test(after(x, 2))),
|
|
253
|
+
mask: ["mid"]
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
// 지역번호 뒤 닫는 괄호: 02)450-1234
|
|
257
|
+
re: new RegExp(`(?<![\\w@(+-])(?<!\\d\\.)(?<pre>${PHONE_AREA})\\) ?(?<mid>\\d{3,4})(?<s2>${PHONE_SEP})(?<last>\\d{4})${PHONE_END}`, "dg"),
|
|
258
|
+
ok: (x) => phoneDigitsOk(g(x, "pre"), g(x, "mid"), g(x, "last"), false) && (g(x, "s2") !== "." || !/^\.\d/.test(after(x, 2))),
|
|
259
|
+
mask: ["mid"]
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
// 국제 표기: +82 10-1234-5678, +82-2-123-4567, +82 (0)10 1234 5678, +821012345678
|
|
263
|
+
re: new RegExp(`(?<![\\w@+])(?<cc>\\+82)(?<s0>[-. ]?)(?:\\(0\\) ?)?(?<pre>1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80)(?<s1>${PHONE_SEP}?)(?<mid>\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\d{4})${PHONE_END}`, "dg"),
|
|
264
|
+
ok: (x) => phoneSepsOk(x, g(x, "s1"), g(x, "s2")) && phoneDigitsOk("0" + g(x, "pre"), g(x, "mid"), g(x, "last"), g(x, "s1") === ""),
|
|
265
|
+
mask: ["mid"]
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
// 대표번호 15xx·16xx·18xx — 구분자 필수. 연도 범위 모양(1592-1598)·온점/공백 구분은 전화 라벨이 있을 때만.
|
|
269
|
+
// 뒤 4자리 0000 은 자리표시자
|
|
270
|
+
re: new RegExp(`(?<![\\w@+-])(?<!\\d\\.)(?<pre>1(?:5[2-9]\\d|6\\d\\d|8\\d\\d))(?<sep> {0,2}- {0,2}| {1,2}|\\.)(?<last>\\d{4})${PHONE_END}`, "dg"),
|
|
271
|
+
ok: (x) => {
|
|
272
|
+
if (g(x, "last") === "0000" || serialBefore(x)) return false;
|
|
273
|
+
if (g(x, "sep") === "." && /^\.\d/.test(after(x, 2))) return false;
|
|
274
|
+
const a = Number(g(x, "pre"));
|
|
275
|
+
const b = Number(g(x, "last"));
|
|
276
|
+
const yearRange = b >= a && b <= (/* @__PURE__ */ new Date()).getFullYear();
|
|
277
|
+
return g(x, "sep").trim() === "-" && !yearRange || has(x, "phone");
|
|
278
|
+
},
|
|
279
|
+
mask: ["last"]
|
|
280
|
+
}
|
|
281
|
+
],
|
|
282
|
+
// 운전면허번호 — 지역코드(11~28)-연도-일련6-검증2 / 구형 지역명 + 2-6-2. 뒤 8자리 마스크
|
|
283
|
+
driver: [
|
|
284
|
+
{
|
|
285
|
+
// 신형 — 무문맥이지만 비개인 식별번호 문맥("(접수)" 등)이면 아님
|
|
286
|
+
re: /(?<![\w-])(?<r>1[1-9]|2[0-8])-(?<y>\d{2})-(?<s>\d{6})-(?<c>\d{2})(?![\w-])/dg,
|
|
287
|
+
ok: (x) => has(x, "driver") || !otherCtx(x, "driver"),
|
|
288
|
+
mask: ["s", "c"]
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
re: /(?<rn>서울|부산|경기|강원|충북|충남|전북|전남|경북|경남|제주|대구|인천|광주|대전|울산) ?(?<y>\d{2})-(?<s>\d{6})-(?<c>\d{2})(?![\w-])/dg,
|
|
292
|
+
mask: ["s", "c"]
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
re: /(?<![\w@.-])(?<r>1[1-9]|2[0-8])(?<y>\d{2})(?<s>\d{6})(?<c>\d{2})(?![\w@-])/dg,
|
|
296
|
+
needs: "driver",
|
|
297
|
+
mask: ["s", "c"]
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
// 여권번호 — 신형 복수여권(M123A4567)은 무문맥, 그 밖의 종류 글자(S·R·O·D·G)와 구형(M12345678)은 여권 라벨
|
|
301
|
+
// 필요 ("S123A4567 부품" 같은 제품 코드). 첫 글자만 남김
|
|
302
|
+
passport: [
|
|
303
|
+
{
|
|
304
|
+
re: /(?<![A-Za-z0-9])(?<l>[MSRODG])(?<num>\d{3}[A-Z]\d{4})(?![A-Za-z0-9])/dg,
|
|
305
|
+
ok: (x) => has(x, "passport") || g(x, "l") === "M" && !otherCtx(x, "passport"),
|
|
306
|
+
mask: ["num"]
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
re: /(?<![A-Za-z0-9])(?<l>[MSRODG])(?<num>\d{8})(?![A-Za-z0-9])/dg,
|
|
310
|
+
needs: "passport",
|
|
311
|
+
mask: ["num"]
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
// 소문자로 친 여권번호(m123a4567) — 여권 라벨이 있을 때만
|
|
315
|
+
re: /(?<![A-Za-z0-9])(?<l>[msrodg])(?<num>\d{3}[a-z]\d{4}|\d{8})(?![A-Za-z0-9])/dg,
|
|
316
|
+
needs: "passport",
|
|
317
|
+
mask: ["num"]
|
|
318
|
+
}
|
|
319
|
+
],
|
|
320
|
+
// 계좌번호 — 하이픈 3~4그룹, 총 11~16자리 (10자리는 계좌 라벨 필요). 마지막 그룹 빼고 마스크.
|
|
321
|
+
// 무구분 10~14자리는 계좌·은행 라벨이 있을 때만 (뒤 4자리 유지)
|
|
322
|
+
account: [
|
|
323
|
+
{
|
|
324
|
+
// 마지막 그룹 1자리 허용 — 새마을금고 9002-1234-5678-9, 3-2-6-1·3-3-6-1 형식
|
|
325
|
+
re: /(?<![\w-])(?<!\d\.)(?<head>\d{2,6}(?:-\d{2,6}){1,2})-(?<last>\d{1,8})(?![\w-])(?!\.\d)/dg,
|
|
326
|
+
ok: (x) => {
|
|
327
|
+
const s = x.m[0];
|
|
328
|
+
const d = digitsOf(s);
|
|
329
|
+
const acct = has(x, "account");
|
|
330
|
+
if (allSameDigit(d) || !acct && otherCtx(x, "account")) return false;
|
|
331
|
+
if (!acct && (/\d,$/.test(before(x)) || /^,\d/.test(after(x, 2)))) return false;
|
|
332
|
+
if (/^\d+(?:-0+)+$/.test(s)) return false;
|
|
333
|
+
if (d.length < 10 || d.length > 16 || d.length === 10 && !acct) return false;
|
|
334
|
+
if (/^\s*원/.test(after(x, 3))) return false;
|
|
335
|
+
if (acct) return true;
|
|
336
|
+
if (/^(?:10|20|30|40)-(?:19|20)\d\d-\d{6,7}$/.test(s)) return false;
|
|
337
|
+
if (/^(?:19|20)\d\d-(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])?-/.test(s)) return false;
|
|
338
|
+
if (s.split("-").filter((p) => /^(?:19|20)\d\d$/.test(p)).length >= 2) return false;
|
|
339
|
+
if (/^\d{3}-\d{2}-\d{5}$/.test(s) && brnChecksumValid(d)) return false;
|
|
340
|
+
return true;
|
|
341
|
+
},
|
|
342
|
+
mask: ["head"]
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
re: /(?<![\w@.-])(?<!\d,)(?<head>\d{6,10})(?<last>\d{4})(?![\w@-])(?!,\d)/dg,
|
|
346
|
+
needs: "account",
|
|
347
|
+
ok: (x) => !allSameDigit(x.m[0]),
|
|
348
|
+
mask: ["head"]
|
|
349
|
+
}
|
|
350
|
+
],
|
|
351
|
+
// IPv4 (opt-in) — 옥텟 0~255, 첫 옥텟 10 이상(1.2.3.4 절 번호·버전 제외) 또는 IP 라벨. 뒤 두 옥텟 마스크
|
|
352
|
+
ip: [
|
|
353
|
+
{
|
|
354
|
+
re: /(?<![\w.])(?<a>\d{1,3})\.(?<b>\d{1,3})\.(?<c>\d{1,3})\.(?<d>\d{1,3})(?![\w])(?!\.\d)/dg,
|
|
355
|
+
ok: (x) => {
|
|
356
|
+
const oct = [g(x, "a"), g(x, "b"), g(x, "c"), g(x, "d")];
|
|
357
|
+
if (oct.some((o) => Number(o) > 255 || o.length > 1 && o[0] === "0")) return false;
|
|
358
|
+
return Number(oct[0]) >= 10 || has(x, "ip");
|
|
359
|
+
},
|
|
360
|
+
mask: ["c", "d"]
|
|
361
|
+
}
|
|
362
|
+
]
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// src/redact.ts
|
|
366
|
+
var DEFAULT_REDACT_RULES = [
|
|
367
|
+
"rrn",
|
|
368
|
+
"phone",
|
|
369
|
+
"email",
|
|
370
|
+
"card",
|
|
371
|
+
"account",
|
|
372
|
+
"brn",
|
|
373
|
+
"passport",
|
|
374
|
+
"driver"
|
|
375
|
+
];
|
|
376
|
+
var ALL_REDACT_RULES = [...DEFAULT_REDACT_RULES, "crn", "ip"];
|
|
377
|
+
var DASHES = /* @__PURE__ */ new Set([8208, 8209, 8210, 8211, 8212, 8213, 8722, 65112, 65123, 65293, 9472, 9473, 12641, 12540]);
|
|
378
|
+
var SPACES = /* @__PURE__ */ new Set([160, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201, 8202, 8239, 12288]);
|
|
379
|
+
function normalizeForDetect(text) {
|
|
380
|
+
let out = "";
|
|
381
|
+
let changed = false;
|
|
382
|
+
for (let i = 0; i < text.length; i++) {
|
|
383
|
+
const c = text.charCodeAt(i);
|
|
384
|
+
let r = c;
|
|
385
|
+
if (c >= 65281 && c <= 65374) r = c - 65248;
|
|
386
|
+
if (DASHES.has(c)) r = 45;
|
|
387
|
+
else if (SPACES.has(c)) r = 32;
|
|
388
|
+
if (r !== c) changed = true;
|
|
389
|
+
out += r === c ? text[i] : String.fromCharCode(r);
|
|
390
|
+
}
|
|
391
|
+
return changed ? out : text;
|
|
392
|
+
}
|
|
393
|
+
function maskMatch(orig, x, v, maskChar) {
|
|
394
|
+
const base = x.m.index;
|
|
395
|
+
const norm = x.m[0];
|
|
396
|
+
const chars = orig.split("");
|
|
397
|
+
const idx = x.m.indices?.groups;
|
|
398
|
+
for (const name of v.mask) {
|
|
399
|
+
const r = idx?.[name];
|
|
400
|
+
if (!r) continue;
|
|
401
|
+
for (let i = r[0] - base; i < r[1] - base; i++) {
|
|
402
|
+
if (v.maskAll || /[0-9A-Za-z]/.test(norm[i])) chars[i] = maskChar;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return chars.join("");
|
|
406
|
+
}
|
|
407
|
+
function assertMaskChar(maskChar) {
|
|
408
|
+
const n = normalizeForDetect(maskChar);
|
|
409
|
+
if (maskChar.length !== 1 || /[0-9A-Za-z|\\<>&"'._\-@+:/,;]/.test(n) || /[\p{Z}\p{C}\p{M}]/u.test(maskChar) || /[\uFDD0-\uFDEF\uFFFE\uFFFF]/.test(maskChar)) {
|
|
410
|
+
throw new Error(`maskChar\uB294 \uC601\uC22B\uC790\xB7\uACF5\uBC31\xB7\uC81C\uC5B4/\uACB0\uD569 \uBB38\uC790\xB7\uAD6C\uBD84 \uAE30\uD638(|\\<>&"'._-@+:/,;)\uAC00 \uC544\uB2CC \uBCF4\uC774\uB294 1\uAE00\uC790\uC5EC\uC57C \uD568: ${JSON.stringify(maskChar)}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
var LIST_SEP_RE = /^\s{0,2}(?:[,;/·、]|및|또는)?\s{0,2}$/;
|
|
414
|
+
function detect(text, rules, maskChar, headerAt) {
|
|
415
|
+
const norm = normalizeForDetect(text);
|
|
416
|
+
const hits = [];
|
|
417
|
+
const occupied = [];
|
|
418
|
+
for (const rule of RULE_PRIORITY) {
|
|
419
|
+
if (!rules.includes(rule)) continue;
|
|
420
|
+
for (const v of RULES[rule]) {
|
|
421
|
+
for (const m of norm.matchAll(v.re)) {
|
|
422
|
+
const start = m.index;
|
|
423
|
+
const end = start + m[0].length;
|
|
424
|
+
if (occupied.some((o) => start < o.end && end > o.start)) continue;
|
|
425
|
+
let cached;
|
|
426
|
+
const ctx = () => {
|
|
427
|
+
if (!cached) {
|
|
428
|
+
let c = labelsBefore(norm, start);
|
|
429
|
+
if (c.size === 0) {
|
|
430
|
+
const prev = occupied.find((o) => o.end < start && start - o.end <= 6 && LIST_SEP_RE.test(norm.slice(o.end, start)));
|
|
431
|
+
if (prev) c = prev.rule === "email" ? prev.ctx() : /* @__PURE__ */ new Set([...prev.ctx(), prev.rule]);
|
|
432
|
+
}
|
|
433
|
+
if (c.size === 0 && headerAt) {
|
|
434
|
+
const h = headerAt(start);
|
|
435
|
+
if (h) c = labelsIn(normalizeForDetect(h));
|
|
436
|
+
}
|
|
437
|
+
cached = c;
|
|
438
|
+
}
|
|
439
|
+
return cached;
|
|
440
|
+
};
|
|
441
|
+
const x = { m, ctx };
|
|
442
|
+
if (v.needs && !ctx().has(v.needs)) continue;
|
|
443
|
+
if (v.ok && !v.ok(x)) continue;
|
|
444
|
+
occupied.push({ start, end, ctx, rule });
|
|
445
|
+
hits.push({ rule, masked: maskMatch(text.slice(start, end), x, v, maskChar), index: start, length: end - start });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return hits.sort((a, b) => a.index - b.index);
|
|
450
|
+
}
|
|
451
|
+
function applyHits(text, hits) {
|
|
452
|
+
let out = "";
|
|
453
|
+
let cursor = 0;
|
|
454
|
+
for (const h of hits) {
|
|
455
|
+
out += text.slice(cursor, h.index) + h.masked;
|
|
456
|
+
cursor = h.index + h.length;
|
|
457
|
+
}
|
|
458
|
+
return out + text.slice(cursor);
|
|
459
|
+
}
|
|
460
|
+
function redactText(text, options) {
|
|
461
|
+
const maskChar = options?.maskChar ?? "\u25CF";
|
|
462
|
+
assertMaskChar(maskChar);
|
|
463
|
+
const rules = options?.rules ?? DEFAULT_REDACT_RULES;
|
|
464
|
+
if (text === "" || rules.length === 0) return { text, hits: [] };
|
|
465
|
+
const hits = detect(text, rules, maskChar);
|
|
466
|
+
return { text: applyHits(text, hits), hits };
|
|
467
|
+
}
|
|
468
|
+
function pipePositions(line) {
|
|
469
|
+
const out = [];
|
|
470
|
+
for (let i = 0; i < line.length; i++) {
|
|
471
|
+
if (line[i] === "|" && line[i - 1] !== "\\") out.push(i);
|
|
472
|
+
}
|
|
473
|
+
return out;
|
|
474
|
+
}
|
|
475
|
+
var isGfmRow = (l) => /^\s*\|.*\|\s*$/.test(l);
|
|
476
|
+
var isGfmSep = (l) => /^\s*\|(?:\s*:?-{3,}:?\s*\|)+\s*$/.test(l);
|
|
477
|
+
function htmlCells(line) {
|
|
478
|
+
const out = [];
|
|
479
|
+
let col = 0;
|
|
480
|
+
for (const m of line.matchAll(/<t([dh])\b([^>]*)>([\s\S]*?)<\/t\1>/gi)) {
|
|
481
|
+
const span = Math.max(1, Number(m[2].match(/colspan="?(\d+)/i)?.[1] ?? 1));
|
|
482
|
+
out.push({ start: m.index, end: m.index + m[0].length, col, span, text: m[3].replace(/<[^>]*>/g, " ") });
|
|
483
|
+
col += span;
|
|
484
|
+
}
|
|
485
|
+
return out;
|
|
486
|
+
}
|
|
487
|
+
function tableHeaderContext(lines) {
|
|
488
|
+
const out = /* @__PURE__ */ new Map();
|
|
489
|
+
for (let i = 0; i < lines.length; i++) {
|
|
490
|
+
if (isGfmRow(lines[i]) && i + 1 < lines.length && isGfmSep(lines[i + 1])) {
|
|
491
|
+
const head = lines[i];
|
|
492
|
+
const hp = pipePositions(head);
|
|
493
|
+
const headers = hp.slice(0, -1).map((p, k) => head.slice(p + 1, hp[k + 1]));
|
|
494
|
+
let j = i + 2;
|
|
495
|
+
for (; j < lines.length && isGfmRow(lines[j]); j++) {
|
|
496
|
+
const rp = pipePositions(lines[j]);
|
|
497
|
+
out.set(j, (off) => {
|
|
498
|
+
let k = -1;
|
|
499
|
+
for (const p of rp) {
|
|
500
|
+
if (p < off) k++;
|
|
501
|
+
else break;
|
|
502
|
+
}
|
|
503
|
+
return k >= 0 ? headers[k] : void 0;
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
i = j - 1;
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (/<table\b/i.test(lines[i])) {
|
|
510
|
+
let headers = null;
|
|
511
|
+
let depth = 0;
|
|
512
|
+
for (let j = i; j < lines.length; j++) {
|
|
513
|
+
depth += (lines[j].match(/<table\b/gi) ?? []).length;
|
|
514
|
+
if (/<tr\b/i.test(lines[j]) && depth === 1) {
|
|
515
|
+
const cells = htmlCells(lines[j]);
|
|
516
|
+
if (!headers) headers = cells;
|
|
517
|
+
else if (cells.length > 0) {
|
|
518
|
+
const hdr = headers;
|
|
519
|
+
out.set(j, (off) => {
|
|
520
|
+
const c = cells.find((x) => off >= x.start && off < x.end);
|
|
521
|
+
if (!c) return void 0;
|
|
522
|
+
const h = hdr.find((x) => c.col >= x.col && c.col < x.col + x.span);
|
|
523
|
+
return h?.text;
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
depth -= (lines[j].match(/<\/table>/gi) ?? []).length;
|
|
528
|
+
if (depth <= 0) {
|
|
529
|
+
i = j;
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return out;
|
|
536
|
+
}
|
|
537
|
+
var MD_ALWAYS_MARKER_RE = new RegExp([
|
|
538
|
+
"(?<!\\*)\\*\\*(?!\\*)",
|
|
539
|
+
"(?<!_)__(?!_)",
|
|
540
|
+
"(?<!~)~~(?!~)",
|
|
541
|
+
"<\\/?(?:u|b|i|s|em|strong|sup|sub|del|span|mark|small|big|ins|strike|tt|code|kbd|samp|var|q|cite|abbr|dfn)>",
|
|
542
|
+
"\\\\(?=[\\\\`*_{}[\\]()#+\\-.!|~>])"
|
|
543
|
+
].join("|"), "g");
|
|
544
|
+
var MD_OPEN_RE = /(?<![\w*\\])(\*\*\*|\*|_)(?=[0-9A-Za-z0-9])/g;
|
|
545
|
+
var MD_CLOSE_RE = /(?<=[0-9A-Za-z0-9])(\*\*\*|\*|_)(?![\w*])/g;
|
|
546
|
+
function mdMarkerRanges(line) {
|
|
547
|
+
const out = [];
|
|
548
|
+
for (const m of line.matchAll(MD_ALWAYS_MARKER_RE)) out.push([m.index, m.index + m[0].length]);
|
|
549
|
+
const taken = (a, b) => out.some(([s, e]) => a < e && b > s);
|
|
550
|
+
const closes = [...line.matchAll(MD_CLOSE_RE)].filter((m) => !taken(m.index, m.index + m[0].length));
|
|
551
|
+
let from = 0;
|
|
552
|
+
for (const o of line.matchAll(MD_OPEN_RE)) {
|
|
553
|
+
const os = o.index;
|
|
554
|
+
const oe = os + o[0].length;
|
|
555
|
+
if (os < from || taken(os, oe)) continue;
|
|
556
|
+
const c = closes.find((m) => m.index >= oe && m[0] === o[0]);
|
|
557
|
+
if (!c) continue;
|
|
558
|
+
out.push([os, oe], [c.index, c.index + c[0].length]);
|
|
559
|
+
from = c.index + c[0].length;
|
|
560
|
+
}
|
|
561
|
+
return out.sort((a, b) => a[0] - b[0]);
|
|
562
|
+
}
|
|
563
|
+
function detectMarkdownLine(line, rules, maskChar, headerAt) {
|
|
564
|
+
const markers = mdMarkerRanges(line);
|
|
565
|
+
if (markers.length === 0) return detect(line, rules, maskChar, headerAt);
|
|
566
|
+
let clean = "";
|
|
567
|
+
const map = [];
|
|
568
|
+
let cursor = 0;
|
|
569
|
+
const keep = (to) => {
|
|
570
|
+
for (let i = cursor; i < to; i++) {
|
|
571
|
+
clean += line[i];
|
|
572
|
+
map.push(i);
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
for (const [ms, me] of markers) {
|
|
576
|
+
keep(ms);
|
|
577
|
+
cursor = me;
|
|
578
|
+
}
|
|
579
|
+
keep(line.length);
|
|
580
|
+
const hits = detect(clean, rules, maskChar, headerAt ? (i) => headerAt(map[i] ?? i) : void 0);
|
|
581
|
+
return hits.map((h) => {
|
|
582
|
+
const start = map[h.index];
|
|
583
|
+
const end = map[h.index + h.length - 1] + 1;
|
|
584
|
+
const chars = line.slice(start, end).split("");
|
|
585
|
+
for (let k = 0; k < h.length; k++) {
|
|
586
|
+
if (h.masked[k] !== clean[h.index + k]) chars[map[h.index + k] - start] = maskChar;
|
|
587
|
+
}
|
|
588
|
+
return { rule: h.rule, masked: chars.join(""), index: start, length: end - start };
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
function redactMarkdown(markdown, options) {
|
|
592
|
+
const maskChar = options?.maskChar ?? "\u25CF";
|
|
593
|
+
assertMaskChar(maskChar);
|
|
594
|
+
const rules = options?.rules ?? DEFAULT_REDACT_RULES;
|
|
595
|
+
const lines = markdown.split("\n");
|
|
596
|
+
const headers = tableHeaderContext(lines);
|
|
597
|
+
const hits = [];
|
|
598
|
+
let offset = 0;
|
|
599
|
+
const outLines = lines.map((line, i) => {
|
|
600
|
+
if (line.includes("data:image/") || line === "" || rules.length === 0) {
|
|
601
|
+
offset += line.length + 1;
|
|
602
|
+
return line;
|
|
603
|
+
}
|
|
604
|
+
const lineHits = detectMarkdownLine(line, rules, maskChar, headers.get(i));
|
|
605
|
+
for (const h of lineHits) hits.push({ ...h, index: h.index + offset });
|
|
606
|
+
offset += line.length + 1;
|
|
607
|
+
return applyHits(line, lineHits);
|
|
608
|
+
});
|
|
609
|
+
return { text: outLines.join("\n"), hits };
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export {
|
|
613
|
+
DEFAULT_REDACT_RULES,
|
|
614
|
+
ALL_REDACT_RULES,
|
|
615
|
+
normalizeForDetect,
|
|
616
|
+
redactText,
|
|
617
|
+
mdMarkerRanges,
|
|
618
|
+
redactMarkdown
|
|
619
|
+
};
|
|
620
|
+
//# sourceMappingURL=chunk-IH56ZISC.js.map
|