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.
Files changed (151) hide show
  1. package/README.md +38 -5
  2. package/dist/{-AQKXLFIM.js → -7XBPXCXN.js} +13 -11
  3. package/dist/{chunk-FOJZNMMK.js → chunk-2LQGSDZ6.js} +1 -1
  4. package/dist/chunk-2LQGSDZ6.js.map +1 -0
  5. package/dist/chunk-4R67INKT.js +1002 -0
  6. package/dist/chunk-4R67INKT.js.map +1 -0
  7. package/dist/{chunk-ILU76UF3.js → chunk-4RJ5D7MT.js} +2 -2
  8. package/dist/chunk-4RJ5D7MT.js.map +1 -0
  9. package/dist/{chunk-EQZI77PP.cjs → chunk-5VVS2QIJ.cjs} +3 -3
  10. package/dist/{chunk-EQZI77PP.cjs.map → chunk-5VVS2QIJ.cjs.map} +1 -1
  11. package/dist/{chunk-3DUADAY2.js → chunk-A275ZQLX.js} +209 -87
  12. package/dist/chunk-A275ZQLX.js.map +1 -0
  13. package/dist/{chunk-EISO3POT.js → chunk-CVEERPMP.js} +788 -194
  14. package/dist/chunk-CVEERPMP.js.map +1 -0
  15. package/dist/{chunk-ZWD734EY.js → chunk-D6KQKMU3.js} +2 -2
  16. package/dist/{chunk-QOMNEWCQ.cjs → chunk-EBZUZC6F.cjs} +305 -31
  17. package/dist/chunk-EBZUZC6F.cjs.map +1 -0
  18. package/dist/{chunk-HR2XNERB.cjs → chunk-EMGF6EEV.cjs} +2 -2
  19. package/dist/{chunk-HR2XNERB.cjs.map → chunk-EMGF6EEV.cjs.map} +1 -1
  20. package/dist/{chunk-GDW4YP7K.js → chunk-FAZR34CA.js} +2 -2
  21. package/dist/{chunk-TNCKOL2O.js → chunk-GTINVM5K.js} +2 -2
  22. package/dist/{chunk-L7DMSMAG.cjs → chunk-HX4UUF7Y.cjs} +1 -1
  23. package/dist/chunk-HX4UUF7Y.cjs.map +1 -0
  24. package/dist/{chunk-UET462HB.js → chunk-I4NNYWIR.js} +2 -2
  25. package/dist/chunk-IH56ZISC.js +620 -0
  26. package/dist/chunk-IH56ZISC.js.map +1 -0
  27. package/dist/{chunk-EDNJLUSE.js → chunk-IHZEFW4L.js} +2 -2
  28. package/dist/chunk-IW2CB755.cjs +1002 -0
  29. package/dist/chunk-IW2CB755.cjs.map +1 -0
  30. package/dist/{chunk-ZWOFQRIB.js → chunk-JCVIOLBF.js} +3 -3
  31. package/dist/{chunk-JQEFP45L.js → chunk-L5EH2NVO.js} +2 -2
  32. package/dist/{chunk-247ANM72.cjs → chunk-NOAQRCV6.cjs} +219 -97
  33. package/dist/chunk-NOAQRCV6.cjs.map +1 -0
  34. package/dist/{chunk-7IAQ4HOK.js → chunk-NQCUAGLR.js} +1 -1
  35. package/dist/chunk-NQCUAGLR.js.map +1 -0
  36. package/dist/{chunk-NS55SM7A.js → chunk-O2PGQNYE.js} +2 -2
  37. package/dist/{chunk-QC5T6D4J.js → chunk-QGW7NMTX.js} +299 -25
  38. package/dist/chunk-QGW7NMTX.js.map +1 -0
  39. package/dist/{chunk-A7QC2ZVP.js → chunk-UVVEBI22.js} +209 -87
  40. package/dist/chunk-UVVEBI22.js.map +1 -0
  41. package/dist/chunk-VJHAUMCF.js +949 -0
  42. package/dist/chunk-VJHAUMCF.js.map +1 -0
  43. package/dist/{chunk-C7FQBSON.js → chunk-VNFJPTU4.js} +2 -2
  44. package/dist/{chunk-VQ5W4MYO.js → chunk-VYOGXNQ3.js} +298 -24
  45. package/dist/chunk-VYOGXNQ3.js.map +1 -0
  46. package/dist/{chunk-DOGPOY4B.js → chunk-WB5LW52Z.js} +2 -2
  47. package/dist/{chunk-TSFS5VKD.js → chunk-YGBZABI4.js} +332 -141
  48. package/dist/chunk-YGBZABI4.js.map +1 -0
  49. package/dist/chunks-I7TEMJXH.js +10 -0
  50. package/dist/cli.js +228 -97
  51. package/dist/cli.js.map +1 -1
  52. package/dist/{formula-PJJ6S3Z2.cjs → formula-CPO2TZGB.cjs} +9 -9
  53. package/dist/{formula-PJJ6S3Z2.cjs.map → formula-CPO2TZGB.cjs.map} +1 -1
  54. package/dist/{formula-5S7WWXGH.js → formula-NOIDZEBA.js} +4 -4
  55. package/dist/{formula-JGFTXWYT.js → formula-X44GV7TN.js} +3 -3
  56. package/dist/{image-ocr-WCAYXLOA.js → image-ocr-72RAR4WR.js} +34 -16
  57. package/dist/image-ocr-72RAR4WR.js.map +1 -0
  58. package/dist/{image-ocr-FLIL7MGN.js → image-ocr-R6W6A75P.js} +32 -14
  59. package/dist/image-ocr-R6W6A75P.js.map +1 -0
  60. package/dist/{image-ocr-ZTYAGV3W.cjs → image-ocr-SUGT64ZM.cjs} +36 -18
  61. package/dist/image-ocr-SUGT64ZM.cjs.map +1 -0
  62. package/dist/index.cjs +2416 -1184
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +62 -25
  65. package/dist/index.d.ts +62 -25
  66. package/dist/index.js +1853 -621
  67. package/dist/index.js.map +1 -1
  68. package/dist/mcp.js +74 -53
  69. package/dist/mcp.js.map +1 -1
  70. package/dist/{model-bundle-FSPZZ2EM.js → model-bundle-57CLEBCO.js} +5 -5
  71. package/dist/{models-CJPAQK5R.js → models-7ELIFTD7.js} +5 -5
  72. package/dist/{parser-SCUTZAIJ.js → parser-4NKAYWAX.js} +261 -241
  73. package/dist/parser-4NKAYWAX.js.map +1 -0
  74. package/dist/{parser-A4TMPJAM.js → parser-HWFQRSUQ.js} +261 -241
  75. package/dist/parser-HWFQRSUQ.js.map +1 -0
  76. package/dist/{parser-VCXE4I5A.cjs → parser-RANGXGBD.cjs} +289 -269
  77. package/dist/parser-RANGXGBD.cjs.map +1 -0
  78. package/dist/pdf-ocr-BNC34MNU.js +13 -0
  79. package/dist/pdf-ocr-QX2GB33Q.js +16 -0
  80. package/dist/pdf-ocr-VHSFACH7.cjs +14 -0
  81. package/dist/{pdf-ocr-MBZZ57LD.cjs.map → pdf-ocr-VHSFACH7.cjs.map} +1 -1
  82. package/dist/{profile-io-LBM263K7.js → profile-io-V52PL5ZO.js} +3 -3
  83. package/dist/{rasterize-46RG4ZF6.js → rasterize-ZIGLJDOG.js} +3 -3
  84. package/dist/redact-TIQEQI3M.js +18 -0
  85. package/dist/redact-doc-KXZ3TZAG.js +1216 -0
  86. package/dist/redact-doc-KXZ3TZAG.js.map +1 -0
  87. package/dist/{render-NGP5T24S.js → render-U5PE6V2E.js} +7 -7
  88. package/dist/seal-DNXJBILR.js +10 -0
  89. package/dist/{setup-AU4OCHNH.js → setup-YRDM4ODP.js} +3 -3
  90. package/dist/visual-5XWMA4EV.js +19 -0
  91. package/dist/{watch-MKPUCPXM.js → watch-5IOLBATT.js} +12 -12
  92. package/package.json +2 -2
  93. package/dist/chunk-247ANM72.cjs.map +0 -1
  94. package/dist/chunk-3DUADAY2.js.map +0 -1
  95. package/dist/chunk-422427XJ.cjs +0 -571
  96. package/dist/chunk-422427XJ.cjs.map +0 -1
  97. package/dist/chunk-7IAQ4HOK.js.map +0 -1
  98. package/dist/chunk-A7QC2ZVP.js.map +0 -1
  99. package/dist/chunk-DZIXKL7E.js +0 -145
  100. package/dist/chunk-DZIXKL7E.js.map +0 -1
  101. package/dist/chunk-EISO3POT.js.map +0 -1
  102. package/dist/chunk-EP7KOI2V.js +0 -571
  103. package/dist/chunk-EP7KOI2V.js.map +0 -1
  104. package/dist/chunk-FOJZNMMK.js.map +0 -1
  105. package/dist/chunk-ILU76UF3.js.map +0 -1
  106. package/dist/chunk-KVWV4ULP.js +0 -516
  107. package/dist/chunk-KVWV4ULP.js.map +0 -1
  108. package/dist/chunk-L7DMSMAG.cjs.map +0 -1
  109. package/dist/chunk-QC5T6D4J.js.map +0 -1
  110. package/dist/chunk-QOMNEWCQ.cjs.map +0 -1
  111. package/dist/chunk-TSFS5VKD.js.map +0 -1
  112. package/dist/chunk-VQ5W4MYO.js.map +0 -1
  113. package/dist/chunks-SX77P5CV.js +0 -10
  114. package/dist/image-ocr-FLIL7MGN.js.map +0 -1
  115. package/dist/image-ocr-WCAYXLOA.js.map +0 -1
  116. package/dist/image-ocr-ZTYAGV3W.cjs.map +0 -1
  117. package/dist/parser-A4TMPJAM.js.map +0 -1
  118. package/dist/parser-SCUTZAIJ.js.map +0 -1
  119. package/dist/parser-VCXE4I5A.cjs.map +0 -1
  120. package/dist/pdf-ocr-76SAUXMZ.js +0 -13
  121. package/dist/pdf-ocr-CPU55CVF.js +0 -16
  122. package/dist/pdf-ocr-MBZZ57LD.cjs +0 -14
  123. package/dist/redact-7ILEAUTS.js +0 -12
  124. package/dist/seal-RGUPOLXD.js +0 -10
  125. package/dist/visual-NZLYIYEM.js +0 -19
  126. /package/dist/{-AQKXLFIM.js.map → -7XBPXCXN.js.map} +0 -0
  127. /package/dist/{chunk-ZWD734EY.js.map → chunk-D6KQKMU3.js.map} +0 -0
  128. /package/dist/{chunk-GDW4YP7K.js.map → chunk-FAZR34CA.js.map} +0 -0
  129. /package/dist/{chunk-TNCKOL2O.js.map → chunk-GTINVM5K.js.map} +0 -0
  130. /package/dist/{chunk-UET462HB.js.map → chunk-I4NNYWIR.js.map} +0 -0
  131. /package/dist/{chunk-EDNJLUSE.js.map → chunk-IHZEFW4L.js.map} +0 -0
  132. /package/dist/{chunk-ZWOFQRIB.js.map → chunk-JCVIOLBF.js.map} +0 -0
  133. /package/dist/{chunk-JQEFP45L.js.map → chunk-L5EH2NVO.js.map} +0 -0
  134. /package/dist/{chunk-NS55SM7A.js.map → chunk-O2PGQNYE.js.map} +0 -0
  135. /package/dist/{chunk-C7FQBSON.js.map → chunk-VNFJPTU4.js.map} +0 -0
  136. /package/dist/{chunk-DOGPOY4B.js.map → chunk-WB5LW52Z.js.map} +0 -0
  137. /package/dist/{chunks-SX77P5CV.js.map → chunks-I7TEMJXH.js.map} +0 -0
  138. /package/dist/{formula-5S7WWXGH.js.map → formula-NOIDZEBA.js.map} +0 -0
  139. /package/dist/{formula-JGFTXWYT.js.map → formula-X44GV7TN.js.map} +0 -0
  140. /package/dist/{model-bundle-FSPZZ2EM.js.map → model-bundle-57CLEBCO.js.map} +0 -0
  141. /package/dist/{models-CJPAQK5R.js.map → models-7ELIFTD7.js.map} +0 -0
  142. /package/dist/{pdf-ocr-76SAUXMZ.js.map → pdf-ocr-BNC34MNU.js.map} +0 -0
  143. /package/dist/{pdf-ocr-CPU55CVF.js.map → pdf-ocr-QX2GB33Q.js.map} +0 -0
  144. /package/dist/{profile-io-LBM263K7.js.map → profile-io-V52PL5ZO.js.map} +0 -0
  145. /package/dist/{rasterize-46RG4ZF6.js.map → rasterize-ZIGLJDOG.js.map} +0 -0
  146. /package/dist/{redact-7ILEAUTS.js.map → redact-TIQEQI3M.js.map} +0 -0
  147. /package/dist/{render-NGP5T24S.js.map → render-U5PE6V2E.js.map} +0 -0
  148. /package/dist/{seal-RGUPOLXD.js.map → seal-DNXJBILR.js.map} +0 -0
  149. /package/dist/{setup-AU4OCHNH.js.map → setup-YRDM4ODP.js.map} +0 -0
  150. /package/dist/{visual-NZLYIYEM.js.map → visual-5XWMA4EV.js.map} +0 -0
  151. /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