kordoc 4.19.2 → 4.20.0

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 (124) hide show
  1. package/NOTICE +25 -0
  2. package/README.md +2 -1
  3. package/THIRD_PARTY/hwp-auto-docfit.LICENSE +23 -0
  4. package/dist/{-3JIHF2AL.js → -TTRGLREH.js} +11 -11
  5. package/dist/{chunk-I2TBI7PN.js → chunk-2U475PJF.js} +2 -2
  6. package/dist/{chunk-R2JM2HOJ.js → chunk-4DIMX2KU.js} +4 -4
  7. package/dist/{chunk-GNUGD4RO.js → chunk-4WOG2SLT.js} +2 -2
  8. package/dist/{chunk-QZ63HXEG.js → chunk-527NBMUE.js} +6 -6
  9. package/dist/{chunk-PWQ2BVAT.js → chunk-7H5GT7HM.js} +22 -3
  10. package/dist/{chunk-PWQ2BVAT.js.map → chunk-7H5GT7HM.js.map} +1 -1
  11. package/dist/{chunk-T2DZTOGG.cjs → chunk-AXQDWE42.cjs} +12 -12
  12. package/dist/{chunk-T2DZTOGG.cjs.map → chunk-AXQDWE42.cjs.map} +1 -1
  13. package/dist/{chunk-ORGWGR4K.cjs → chunk-CJFIHH2N.cjs} +6 -6
  14. package/dist/{chunk-ORGWGR4K.cjs.map → chunk-CJFIHH2N.cjs.map} +1 -1
  15. package/dist/{chunk-XPM7WC54.js → chunk-CKFKC22O.js} +4 -4
  16. package/dist/{chunk-JK7N4R4K.js → chunk-CPZ2LLZA.js} +2 -2
  17. package/dist/{chunk-6ZBORHFB.js → chunk-CS2VWXDS.js} +2 -2
  18. package/dist/{chunk-EUCBMW6C.js → chunk-DPD2L5UK.js} +3 -3
  19. package/dist/{chunk-AOVMOCTK.js → chunk-GHAJHXYA.js} +2 -2
  20. package/dist/{chunk-4CS7XE2Q.js → chunk-JYV3XQYW.js} +2 -2
  21. package/dist/{chunk-MZ6XNFZL.cjs → chunk-K5GCIB2I.cjs} +2 -2
  22. package/dist/{chunk-MZ6XNFZL.cjs.map → chunk-K5GCIB2I.cjs.map} +1 -1
  23. package/dist/{chunk-5PGOZBEQ.cjs → chunk-KDCPBF4X.cjs} +22 -22
  24. package/dist/{chunk-5PGOZBEQ.cjs.map → chunk-KDCPBF4X.cjs.map} +1 -1
  25. package/dist/{chunk-NV46IWKF.js → chunk-LQ4IDHET.js} +234 -24
  26. package/dist/chunk-LQ4IDHET.js.map +1 -0
  27. package/dist/{chunk-U6AJU2VD.js → chunk-MNPPMJBN.js} +2 -2
  28. package/dist/{chunk-J75TVRJB.js → chunk-MXADBQ7E.js} +2 -2
  29. package/dist/{chunk-B4R443MV.js → chunk-NVBX2NKZ.js} +2 -2
  30. package/dist/{chunk-TM4T4NYJ.js → chunk-OX5FA2MU.js} +2 -2
  31. package/dist/{chunk-WQAGDJIP.cjs → chunk-RMDFN5ZF.cjs} +3 -3
  32. package/dist/{chunk-WQAGDJIP.cjs.map → chunk-RMDFN5ZF.cjs.map} +1 -1
  33. package/dist/{chunk-QYJPU4FZ.js → chunk-TM7IOP4P.js} +2 -2
  34. package/dist/{chunk-OKHKEYTL.js → chunk-URQM5TBD.js} +2 -2
  35. package/dist/{chunk-VO22VAR3.js → chunk-VVKMBLG2.js} +2 -2
  36. package/dist/{chunk-JXJDXDOU.js → chunk-WLNEQGQC.js} +3 -3
  37. package/dist/{chunk-56ZWAREM.js → chunk-WLWWODBQ.js} +2 -2
  38. package/dist/{chunk-RVBQT2PD.cjs → chunk-WWOU6ZQ3.cjs} +3 -3
  39. package/dist/{chunk-RVBQT2PD.cjs.map → chunk-WWOU6ZQ3.cjs.map} +1 -1
  40. package/dist/{chunk-AKSCTGKG.js → chunk-X5YHJHWB.js} +2 -2
  41. package/dist/chunks-3NIFJFRQ.js +10 -0
  42. package/dist/cli.js +366 -44
  43. package/dist/cli.js.map +1 -1
  44. package/dist/{formula-STIFIS57.js → formula-D6GGALWI.js} +3 -3
  45. package/dist/{formula-RNATQ3FQ.cjs → formula-FE6LEZTT.cjs} +9 -9
  46. package/dist/{formula-RNATQ3FQ.cjs.map → formula-FE6LEZTT.cjs.map} +1 -1
  47. package/dist/{formula-RSTTP626.js → formula-KWOAYEOG.js} +4 -4
  48. package/dist/{image-ocr-XKSAEEXP.js → image-ocr-XDODV43Y.js} +9 -9
  49. package/dist/{image-ocr-7L5TBBPA.cjs → image-ocr-XGGIYCZ2.cjs} +15 -15
  50. package/dist/{image-ocr-7L5TBBPA.cjs.map → image-ocr-XGGIYCZ2.cjs.map} +1 -1
  51. package/dist/{image-ocr-R6ZDFBAN.js → image-ocr-YYTFOUSY.js} +6 -6
  52. package/dist/index.cjs +682 -459
  53. package/dist/index.cjs.map +1 -1
  54. package/dist/index.d.cts +5 -0
  55. package/dist/index.d.ts +5 -0
  56. package/dist/index.js +242 -19
  57. package/dist/index.js.map +1 -1
  58. package/dist/mcp.js +22 -22
  59. package/dist/{model-bundle-NGTKULP7.js → model-bundle-WPFLIKYM.js} +5 -5
  60. package/dist/{models-QOSU5SYL.js → models-WJ5QYELV.js} +5 -5
  61. package/dist/{parser-AIQBU7FS.cjs → parser-6P5ZRWXG.cjs} +132 -132
  62. package/dist/{parser-AIQBU7FS.cjs.map → parser-6P5ZRWXG.cjs.map} +1 -1
  63. package/dist/{parser-5GUEP6EK.js → parser-LY67CB7A.js} +8 -8
  64. package/dist/{parser-3G4G6SST.js → parser-QKLUIMHG.js} +11 -11
  65. package/dist/pdf-ocr-6L4EDRVO.js +20 -0
  66. package/dist/pdf-ocr-J7DHJJ3I.js +16 -0
  67. package/dist/pdf-ocr-MODN6GBT.cjs +17 -0
  68. package/dist/{pdf-ocr-7OAFUDM4.cjs.map → pdf-ocr-MODN6GBT.cjs.map} +1 -1
  69. package/dist/{profile-io-ZVNMDLLS.js → profile-io-I4ORJOE4.js} +3 -3
  70. package/dist/{rasterize-Y2RLMVCL.js → rasterize-MZU5LIA5.js} +3 -3
  71. package/dist/{redact-doc-PNRDDBI3.js → redact-doc-NEUEBXFH.js} +11 -11
  72. package/dist/{render-S3ALARVO.js → render-IL2DYXDP.js} +7 -7
  73. package/dist/seal-O5DKZGTR.js +10 -0
  74. package/dist/{setup-JH66VSFZ.js → setup-PGOCB7YG.js} +3 -3
  75. package/dist/visual-PYV4ZMLH.js +20 -0
  76. package/dist/{watch-KMB2FTOD.js → watch-2F5AMHCW.js} +12 -12
  77. package/package.json +1 -1
  78. package/dist/chunk-NV46IWKF.js.map +0 -1
  79. package/dist/chunks-2EC2R7PY.js +0 -10
  80. package/dist/pdf-ocr-7OAFUDM4.cjs +0 -17
  81. package/dist/pdf-ocr-DAXS5RX4.js +0 -16
  82. package/dist/pdf-ocr-R3VGX6F2.js +0 -20
  83. package/dist/seal-EJWPS5VK.js +0 -10
  84. package/dist/visual-GDXMKDVY.js +0 -20
  85. /package/dist/{-3JIHF2AL.js.map → -TTRGLREH.js.map} +0 -0
  86. /package/dist/{chunk-I2TBI7PN.js.map → chunk-2U475PJF.js.map} +0 -0
  87. /package/dist/{chunk-R2JM2HOJ.js.map → chunk-4DIMX2KU.js.map} +0 -0
  88. /package/dist/{chunk-GNUGD4RO.js.map → chunk-4WOG2SLT.js.map} +0 -0
  89. /package/dist/{chunk-QZ63HXEG.js.map → chunk-527NBMUE.js.map} +0 -0
  90. /package/dist/{chunk-XPM7WC54.js.map → chunk-CKFKC22O.js.map} +0 -0
  91. /package/dist/{chunk-JK7N4R4K.js.map → chunk-CPZ2LLZA.js.map} +0 -0
  92. /package/dist/{chunk-6ZBORHFB.js.map → chunk-CS2VWXDS.js.map} +0 -0
  93. /package/dist/{chunk-EUCBMW6C.js.map → chunk-DPD2L5UK.js.map} +0 -0
  94. /package/dist/{chunk-AOVMOCTK.js.map → chunk-GHAJHXYA.js.map} +0 -0
  95. /package/dist/{chunk-4CS7XE2Q.js.map → chunk-JYV3XQYW.js.map} +0 -0
  96. /package/dist/{chunk-U6AJU2VD.js.map → chunk-MNPPMJBN.js.map} +0 -0
  97. /package/dist/{chunk-J75TVRJB.js.map → chunk-MXADBQ7E.js.map} +0 -0
  98. /package/dist/{chunk-B4R443MV.js.map → chunk-NVBX2NKZ.js.map} +0 -0
  99. /package/dist/{chunk-TM4T4NYJ.js.map → chunk-OX5FA2MU.js.map} +0 -0
  100. /package/dist/{chunk-QYJPU4FZ.js.map → chunk-TM7IOP4P.js.map} +0 -0
  101. /package/dist/{chunk-OKHKEYTL.js.map → chunk-URQM5TBD.js.map} +0 -0
  102. /package/dist/{chunk-VO22VAR3.js.map → chunk-VVKMBLG2.js.map} +0 -0
  103. /package/dist/{chunk-JXJDXDOU.js.map → chunk-WLNEQGQC.js.map} +0 -0
  104. /package/dist/{chunk-56ZWAREM.js.map → chunk-WLWWODBQ.js.map} +0 -0
  105. /package/dist/{chunk-AKSCTGKG.js.map → chunk-X5YHJHWB.js.map} +0 -0
  106. /package/dist/{chunks-2EC2R7PY.js.map → chunks-3NIFJFRQ.js.map} +0 -0
  107. /package/dist/{formula-STIFIS57.js.map → formula-D6GGALWI.js.map} +0 -0
  108. /package/dist/{formula-RSTTP626.js.map → formula-KWOAYEOG.js.map} +0 -0
  109. /package/dist/{image-ocr-XKSAEEXP.js.map → image-ocr-XDODV43Y.js.map} +0 -0
  110. /package/dist/{image-ocr-R6ZDFBAN.js.map → image-ocr-YYTFOUSY.js.map} +0 -0
  111. /package/dist/{model-bundle-NGTKULP7.js.map → model-bundle-WPFLIKYM.js.map} +0 -0
  112. /package/dist/{models-QOSU5SYL.js.map → models-WJ5QYELV.js.map} +0 -0
  113. /package/dist/{parser-5GUEP6EK.js.map → parser-LY67CB7A.js.map} +0 -0
  114. /package/dist/{parser-3G4G6SST.js.map → parser-QKLUIMHG.js.map} +0 -0
  115. /package/dist/{pdf-ocr-DAXS5RX4.js.map → pdf-ocr-6L4EDRVO.js.map} +0 -0
  116. /package/dist/{pdf-ocr-R3VGX6F2.js.map → pdf-ocr-J7DHJJ3I.js.map} +0 -0
  117. /package/dist/{profile-io-ZVNMDLLS.js.map → profile-io-I4ORJOE4.js.map} +0 -0
  118. /package/dist/{rasterize-Y2RLMVCL.js.map → rasterize-MZU5LIA5.js.map} +0 -0
  119. /package/dist/{redact-doc-PNRDDBI3.js.map → redact-doc-NEUEBXFH.js.map} +0 -0
  120. /package/dist/{render-S3ALARVO.js.map → render-IL2DYXDP.js.map} +0 -0
  121. /package/dist/{seal-EJWPS5VK.js.map → seal-O5DKZGTR.js.map} +0 -0
  122. /package/dist/{setup-JH66VSFZ.js.map → setup-PGOCB7YG.js.map} +0 -0
  123. /package/dist/{visual-GDXMKDVY.js.map → visual-PYV4ZMLH.js.map} +0 -0
  124. /package/dist/{watch-KMB2FTOD.js.map → watch-2F5AMHCW.js.map} +0 -0
package/dist/index.d.cts CHANGED
@@ -1284,6 +1284,8 @@ declare function unknownFontWarnings(fonts: Record<string, string | undefined>):
1284
1284
  * 외래어·차별 표현·"끝." 누락 6룰 보강 (pyhwpxlib Gongmun 검사 항목 대조). 검사는 조언용이다 — 생성은 막지 않고
1285
1285
  * 경고만 낸다 (A2 폰트경고와 같은 원칙). 별도 CLI `kordoc lint`는 error 시 exit 1.
1286
1286
  * v4.13.0: 하이픈 날짜(2026-07-18) 룰, 금액 한글 병기·하이픈 날짜는 실제 변환값을 제안.
1287
+ * v4.20.0: 기간 대시·요일·금액 한글 불일치·"끝."/붙임 2타·공공언어·맞춤법 룰 — 규칙 착안과 공공언어·맞춤법 낱말표·
1288
+ * 조사 경계식은 hwp-auto-docfit(MIT, haijun93 마포구청 공혁준, THIRD_PARTY/hwp-auto-docfit.LICENSE)에서 옮겼다.
1287
1289
  */
1288
1290
  interface GongmunLintFinding {
1289
1291
  /** 1-based 줄 번호 */
@@ -1299,9 +1301,12 @@ interface GongmunLintFinding {
1299
1301
  /**
1300
1302
  * 텍스트(마크다운 포함) 표기법 검수. 마크다운 펜스 코드블록(``` ~ ```) 안은
1301
1303
  * 건너뛴다 — 코드·URL이 날짜/쌍점 규칙에 오탐되는 것 방지.
1304
+ * opts.document: 완성 원고 검사(문서 단위 규칙 포함). opts.parsed: HWPX·HWP 등을 파싱한 글 — 연속 공백이 접히고
1305
+ * 원문 굵게가 `**` 로 옮겨져 원고 전용 규칙("끝."·붙임 2타, 강조 남발)은 판정할 수 없다(원문 "붙임 … 1부. 끝." 이 한 칸)
1302
1306
  */
1303
1307
  declare function lintGongmunText(text: string, opts?: {
1304
1308
  document?: boolean;
1309
+ parsed?: boolean;
1305
1310
  }): GongmunLintFinding[];
1306
1311
  /** 검수 결과를 사람이 읽는 경고 문자열 배열로 — generate 경고 채널(A2와 동일)용 */
1307
1312
  declare function gongmunLintWarnings(text: string, limit?: number): string[];
package/dist/index.d.ts CHANGED
@@ -1284,6 +1284,8 @@ declare function unknownFontWarnings(fonts: Record<string, string | undefined>):
1284
1284
  * 외래어·차별 표현·"끝." 누락 6룰 보강 (pyhwpxlib Gongmun 검사 항목 대조). 검사는 조언용이다 — 생성은 막지 않고
1285
1285
  * 경고만 낸다 (A2 폰트경고와 같은 원칙). 별도 CLI `kordoc lint`는 error 시 exit 1.
1286
1286
  * v4.13.0: 하이픈 날짜(2026-07-18) 룰, 금액 한글 병기·하이픈 날짜는 실제 변환값을 제안.
1287
+ * v4.20.0: 기간 대시·요일·금액 한글 불일치·"끝."/붙임 2타·공공언어·맞춤법 룰 — 규칙 착안과 공공언어·맞춤법 낱말표·
1288
+ * 조사 경계식은 hwp-auto-docfit(MIT, haijun93 마포구청 공혁준, THIRD_PARTY/hwp-auto-docfit.LICENSE)에서 옮겼다.
1287
1289
  */
1288
1290
  interface GongmunLintFinding {
1289
1291
  /** 1-based 줄 번호 */
@@ -1299,9 +1301,12 @@ interface GongmunLintFinding {
1299
1301
  /**
1300
1302
  * 텍스트(마크다운 포함) 표기법 검수. 마크다운 펜스 코드블록(``` ~ ```) 안은
1301
1303
  * 건너뛴다 — 코드·URL이 날짜/쌍점 규칙에 오탐되는 것 방지.
1304
+ * opts.document: 완성 원고 검사(문서 단위 규칙 포함). opts.parsed: HWPX·HWP 등을 파싱한 글 — 연속 공백이 접히고
1305
+ * 원문 굵게가 `**` 로 옮겨져 원고 전용 규칙("끝."·붙임 2타, 강조 남발)은 판정할 수 없다(원문 "붙임 … 1부. 끝." 이 한 칸)
1302
1306
  */
1303
1307
  declare function lintGongmunText(text: string, opts?: {
1304
1308
  document?: boolean;
1309
+ parsed?: boolean;
1305
1310
  }): GongmunLintFinding[];
1306
1311
  /** 검수 결과를 사람이 읽는 경고 문자열 배열로 — generate 경고 채널(A2와 동일)용 */
1307
1312
  declare function gongmunLintWarnings(text: string, limit?: number): string[];
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  blocksToPages,
3
3
  inlineImagesIntoMarkdown
4
- } from "./chunk-U6AJU2VD.js";
4
+ } from "./chunk-MNPPMJBN.js";
5
5
  import {
6
6
  parsePageRange
7
7
  } from "./chunk-V4BH5SRQ.js";
@@ -32,7 +32,7 @@ import {
32
32
  tidyScriptTags,
33
33
  unframeLayoutTables,
34
34
  wrapScript
35
- } from "./chunk-AKSCTGKG.js";
35
+ } from "./chunk-X5YHJHWB.js";
36
36
  import {
37
37
  KordocError,
38
38
  OPTIONAL_DEP_INSTALL_HINT,
@@ -50,7 +50,7 @@ import {
50
50
  toArrayBuffer,
51
51
  unescapeHtml,
52
52
  unzipLimitBytes
53
- } from "./chunk-J75TVRJB.js";
53
+ } from "./chunk-MXADBQ7E.js";
54
54
 
55
55
  // src/plain-markdown.ts
56
56
  function toPlainMarkdown(md2) {
@@ -23669,6 +23669,24 @@ var EOCD_SIG = 101010256;
23669
23669
  var CD_SIG = 33639248;
23670
23670
  var LOCAL_SIG = 67324752;
23671
23671
  var ZIP64_EOCD_LOC_SIG = 117853008;
23672
+ function readZipEntries(buf) {
23673
+ const { entries } = parseCentralDirectory(buf);
23674
+ const result = /* @__PURE__ */ new Map();
23675
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
23676
+ for (const e of entries) {
23677
+ const dataStart = localDataStart(view, e.localOffset);
23678
+ result.set(e.name, { method: e.method, compData: buf.subarray(dataStart, dataStart + e.compSize) });
23679
+ }
23680
+ return result;
23681
+ }
23682
+ function localDataStart(view, localOffset) {
23683
+ if (view.getUint32(localOffset, true) !== LOCAL_SIG) {
23684
+ throw new KordocError("ZIP \uB85C\uCEEC \uD5E4\uB354 \uC2DC\uADF8\uB2C8\uCC98 \uBD88\uC77C\uCE58");
23685
+ }
23686
+ const nameLen = view.getUint16(localOffset + 26, true);
23687
+ const extraLen = view.getUint16(localOffset + 28, true);
23688
+ return localOffset + 30 + nameLen + extraLen;
23689
+ }
23672
23690
  function copyBytes(buf, start, end) {
23673
23691
  return new Uint8Array(buf.subarray(start, end));
23674
23692
  }
@@ -27539,7 +27557,7 @@ function parseInlineMarkdown(text) {
27539
27557
  }
27540
27558
  return spans;
27541
27559
  }
27542
- var INLINE_REGEX = /(`[^`]+`|\*{3}(?:[^*]|\*(?!\*\*))+\*{3}|\*{2}(?:[^*]|\*(?!\*))+\*{2}|\*(?:[^*]|\*{2}(?:[^*]|\*(?!\*))+\*{2})+\*|(?<![^\s\p{P}\p{S}\x00])(?<!_)_{2}(?=\S)[^_]+(?<=\S)_{2}(?!_)(?![^\s\p{P}\p{S}\x00])|(?<![^\s\p{P}\p{S}\x00])(?<!_)_(?=\S)[^_]+(?<=\S)_(?!_)(?![^\s\p{P}\p{S}\x00]))/gu;
27560
+ var INLINE_REGEX = /(`[^`]+`|\*{3}(?=\S)(?:[^*]|\*(?!\*\*))+(?<=\S)\*{3}|\*{2}(?=\S)(?:[^*]|\*(?!\*))+(?<=\S)\*{2}|\*(?=\S)(?:[^*]|\*{2}(?:[^*]|\*(?!\*))+\*{2})+(?<=\S)\*|(?<![^\s\p{P}\p{S}\x00])(?<!_)_{2}(?=\S)[^_]+(?<=\S)_{2}(?!_)(?![^\s\p{P}\p{S}\x00])|(?<![^\s\p{P}\p{S}\x00])(?<!_)_(?=\S)[^_]+(?<=\S)_(?!_)(?![^\s\p{P}\p{S}\x00]))/gu;
27543
27561
  function emphasisSpans(text, depth = 0) {
27544
27562
  const spans = [];
27545
27563
  let lastIdx = 0;
@@ -31316,7 +31334,7 @@ function markerRunXml(marker, charPr2, lay) {
31316
31334
  var NBSP = "\xA0";
31317
31335
  var OPENER_BEFORE = /[\s(\[{>「『〈《【〔‘“·/:,~—–-]/u;
31318
31336
  function bindSpaces(s) {
31319
- return s.replace(/(\d{4}|['’]\d{2})\. (\d{1,2})\. (\d{1,2})\.(?!\d)/g, `$1.${NBSP}$2.${NBSP}$3.`).replace(/(\d{4}|['’]\d{2})\. (\d{1,2})\.(?!\d)/g, `$1.${NBSP}$2.`).replace(/(?<![\d.])(\d{1,2})\. (\d{1,2})\.(?!\d)/g, `$1.${NBSP}$2.`).replace(/(\d{1,2}:\d{2}) ([~∼-]) (\d{1,2}:\d{2})/g, `$1${NBSP}$2${NBSP}$3`).replace(/(\d(?:조|억|만|천)?) 원/g, `$1${NBSP}\uC6D0`);
31337
+ return s.replace(/(\d{4}|['’]\d{2})\. (\d{1,2})\. (\d{1,2})\.(?!\d)/g, `$1.${NBSP}$2.${NBSP}$3.`).replace(/(\d{4}|['’]\d{2})\. (\d{1,2})\.(?!\d)/g, `$1.${NBSP}$2.`).replace(/(?<![\d.])(\d{1,2})\. (\d{1,2})\.(?!\d)/g, `$1.${NBSP}$2.`).replace(/(\d{1,2}:\d{2}) ([~∼-]) (\d{1,2}:\d{2})/g, `$1${NBSP}$2${NBSP}$3`).replace(/(\d(?:조|억|만|천)?) 원/g, `$1${NBSP}\uC6D0`).replace(/(?<=[가-힣A-Za-z0-9))*.]) 등(?=(?:으로서|으로써|으로|에서|에게|까지|부터|이며|이고|이다|이나|이라|과|와|을|를|은|는|이|가|의|에|도|만)?[,,.))」』’”]*(?:\s|$))/g, `${NBSP}\uB4F1`).replace(/(?<=[가-힣A-Za-z]) (?=\d+(?:\.\d+)?[,,](?:\s|$))/g, NBSP).replace(/(?<=\d[,,][ \u00a0]+[가-힣A-Za-z]+) (?=\d+(?:\.\d+)?[^\s,,\d]*(?:\s|$))/g, NBSP);
31320
31338
  }
31321
31339
  function neighbor(s, i, dir) {
31322
31340
  for (let k = i + dir; k >= 0 && k < s.length; k += dir) if (!/[*_~]/.test(s[k])) return s[k];
@@ -32076,12 +32094,39 @@ function legalDepthOf(marker) {
32076
32094
  return 6;
32077
32095
  }
32078
32096
 
32097
+ // src/hwpx/paste-clean.ts
32098
+ var INVISIBLE = /[\u200b\u200c\u2060\ufeff]/g;
32099
+ var CITATION_TAG = /[ \t]?【[^【】†\n]*†[^【】\n]*】/g;
32100
+ var ESCAPED_BOLD = /(?<![\\*])\\\*\\\*(?=[^\s\\*<])([^\\\n<]+?)(?<=[^\s\\*>])\\\*\\\*(?!\\\*)/g;
32101
+ function cleanPastedMarkdown(md2) {
32102
+ let removed = 0;
32103
+ let fence = null;
32104
+ const count = (re, s, to) => s.replace(re, (...a) => {
32105
+ removed++;
32106
+ return typeof to === "string" ? to : to(...a);
32107
+ });
32108
+ const lines = md2.split("\n").map((line) => {
32109
+ const f = line.match(/^\s*(```+|~~~+)/);
32110
+ if (f) {
32111
+ fence = fence === null ? f[1][0] : fence === f[1][0] ? null : fence;
32112
+ return line;
32113
+ }
32114
+ if (fence !== null) return line;
32115
+ let out = count(INVISIBLE, line, "");
32116
+ out = count(CITATION_TAG, out, "");
32117
+ return count(ESCAPED_BOLD, out, (_m, inner) => `**${inner}**`);
32118
+ });
32119
+ return { md: lines.join("\n"), removed };
32120
+ }
32121
+
32079
32122
  // src/hwpx/generator.ts
32080
32123
  async function markdownToHwpx(markdown, options) {
32081
32124
  const theme = resolveTheme(options?.theme);
32082
32125
  const gongmun = options?.gongmun ? resolveGongmun(options.gongmun) : null;
32083
32126
  const page = resolvePage(options?.page);
32084
- const { md: md2, defs } = extractFootnoteDefs(markdown);
32127
+ const pasted = cleanPastedMarkdown(markdown);
32128
+ if (pasted.removed > 0) options?.warnings?.push(`\uBD99\uC5EC\uB123\uAE30 \uD754\uC801 ${pasted.removed}\uAC1C \uC815\uB9AC (\uBCF4\uC774\uC9C0 \uC54A\uB294 \uAE00\uC790\xB7\uC778\uC6A9 \uD45C\uC2DC\xB7\uC774\uC2A4\uCF00\uC774\uD504\uB41C \uAD75\uAC8C)`);
32129
+ const { md: md2, defs } = extractFootnoteDefs(pasted.md);
32085
32130
  beginInlineDoc(defs);
32086
32131
  try {
32087
32132
  if (gongmun && usesV5Engine(gongmun.preset)) {
@@ -33327,14 +33372,71 @@ var DISCRIM_FIXES = [
33327
33372
  ["\uADC0\uBA38\uAC70\uB9AC", "\uCCAD\uAC01\uC7A5\uC560\uC778"]
33328
33373
  ];
33329
33374
  var DISCRIM_RE = new RegExp(DISCRIM_FIXES.map(([w]) => w).sort((a, b) => b.length - a.length).join("|"), "g");
33375
+ var PLAIN_FIXES = [
33376
+ ["\uAE08\uBC88", "\uC774\uBC88"],
33377
+ ["\uAE08\uC77C", "\uC624\uB298"],
33378
+ ["\uBA85\uC77C", "\uB0B4\uC77C"],
33379
+ ["\uC791\uC77C", "\uC5B4\uC81C"],
33380
+ ["\uC775\uC77C", "\uB2E4\uC74C \uB0A0"],
33381
+ ["\uACE0\uC218\uBD80\uC9C0", "\uB454\uCE58"],
33382
+ ["\uBD88\uCCA0\uC8FC\uC57C", "\uBC24\uB0AE\uC5C6\uC774"]
33383
+ ];
33384
+ var SPELLING_FIXES = [
33385
+ ["\uBA87\uC77C", "\uBA70\uCE60"],
33386
+ ["\uAC2F\uC218", "\uAC1C\uC218"],
33387
+ ["\uB42C\uC2B5\uB2C8\uB2E4", "\uB410\uC2B5\uB2C8\uB2E4"],
33388
+ ["\uB42C\uB2E4", "\uB410\uB2E4"],
33389
+ ["\uC660\uB9CC\uD558\uBA74", "\uC6EC\uB9CC\uD558\uBA74"],
33390
+ ["\uC5B4\uC758\uC5C6\uB2E4", "\uC5B4\uC774\uC5C6\uB2E4"],
33391
+ ["\uB418\uC694", "\uB3FC\uC694"]
33392
+ ];
33393
+ var wordRe = (fixes) => new RegExp(
33394
+ "(?<![\uAC00-\uD7A3A-Za-z0-9])(?:" + fixes.map(([w]) => w).sort((a, b) => b.length - a.length).join("|") + ")(?=(?:\uC73C\uB85C|\uBD80\uD130|\uAE4C\uC9C0|\uB3D9\uC548|\uC774\uB098|\uC740|\uB294|\uC774|\uAC00|\uC744|\uB97C|\uC5D0|\uC758|\uB3C4|\uB9CC|\uB85C|\uACFC|\uC640|\uAC04)?(?![\uAC00-\uD7A3A-Za-z0-9]))",
33395
+ "g"
33396
+ );
33397
+ var PLAIN_RE = wordRe(PLAIN_FIXES);
33398
+ var SPELLING_RE = wordRe(SPELLING_FIXES);
33399
+ var WEEKDAYS = "\uC77C\uC6D4\uD654\uC218\uBAA9\uAE08\uD1A0";
33400
+ function weekdayCheck(m) {
33401
+ const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];
33402
+ const date = new Date(y, mo - 1, d);
33403
+ if (date.getFullYear() !== y || date.getMonth() !== mo - 1 || date.getDate() !== d) return null;
33404
+ const right = WEEKDAYS[date.getDay()];
33405
+ return right === m[4] ? null : `${m[0].trim()} \u2192 ${y}. ${mo}. ${d}.(${right})`;
33406
+ }
33407
+ var HANGUL_DIGIT = { \uC77C: 1, \uC774: 2, \uC0BC: 3, \uC0AC: 4, \uC624: 5, \uC721: 6, \uCE60: 7, \uD314: 8, \uAD6C: 9, \uC601: 0 };
33408
+ var HANGUL_SMALL = { \uC2ED: 10, \uBC31: 100, \uCC9C: 1e3 };
33409
+ var HANGUL_BIG = { \uB9CC: 1e4, \uC5B5: 1e8, \uC870: 1e12 };
33410
+ function hangulToNumber(s) {
33411
+ let total = 0, section = 0, digit = 0;
33412
+ for (const ch of s) {
33413
+ if (ch in HANGUL_DIGIT) digit = HANGUL_DIGIT[ch];
33414
+ else if (ch in HANGUL_SMALL) {
33415
+ section += (digit || 1) * HANGUL_SMALL[ch];
33416
+ digit = 0;
33417
+ } else if (ch in HANGUL_BIG) {
33418
+ total += (section + digit || 1) * HANGUL_BIG[ch];
33419
+ section = 0;
33420
+ digit = 0;
33421
+ } else return null;
33422
+ }
33423
+ return total + section + digit;
33424
+ }
33425
+ function moneyHangulCheck(m) {
33426
+ const n = Number(m[1].replace(/,/g, ""));
33427
+ const h = hangulToNumber(m[2].replace(/\s/g, ""));
33428
+ if (!Number.isFinite(n) || h === null || h === n) return null;
33429
+ return `\uAE08${m[1]}\uC6D0(\uAE08${hangulAmount(n)}\uC6D0)`;
33430
+ }
33330
33431
  var RULES = [
33331
33432
  // 날짜 ─ 온점 뒤 한 칸, 0 패딩 금지, 연도 4자리, 끝 마침표
33332
33433
  // 법령 연혁 표기 `<개정 2012.2.14>`·`삭제 <2016.2.29.>`·`[시행일:2017.9.8.]` 는 법제처 정본 형식(별지서식 표제) —
33333
33434
  // 편람 날짜 규칙 대상이 아니다 (v4.12.3: 서식 595건 DATE 156건 전부 이 꼴, 기안문 0건)
33435
+ // 날짜 모양이 시작되는 자리에서만 뒤쪽 검사 — 뒤쪽 검사(\s*)를 모든 자리에서 먼저 하면 긴 공백 줄에서 이차 폭주(2만 칸 0.7초)
33334
33436
  {
33335
33437
  code: "DATE_NO_SPACE",
33336
33438
  severity: "error",
33337
- pattern: /(?<!<(?:개정|신설|전문개정|전부개정|일부개정|제정)\s*)(?<!삭제\s*<)(?<!시행일\s*:\s*)\b\d{4}\.\d{1,2}\.\d{1,2}\.?/g,
33439
+ pattern: /\b(?=\d{4}\.\d{1,2}\.\d)(?<!<(?:개정|신설|전문개정|전부개정|일부개정|제정)\s*)(?<!삭제\s*<)(?<!시행일\s*:\s*)\d{4}\.\d{1,2}\.\d{1,2}\.?/g,
33338
33440
  message: "\uB0A0\uC9DC \uC628\uC810 \uB4A4\uC5D0 \uD55C \uCE78\uC529 \uB744\uC6CC\uC57C \uD568",
33339
33441
  suggest: "\uC608) 2025. 1. 6."
33340
33442
  },
@@ -33381,7 +33483,7 @@ var RULES = [
33381
33483
  code: "TIME_24H",
33382
33484
  severity: "warning",
33383
33485
  pattern: /(?<!\d)24\s*시(?!각)/g,
33384
- message: "'24\uC2DC'\uBCF4\uB2E4 \uC775\uC77C 00:00 \uB610\uB294 '18:00\uAE4C\uC9C0' \uAD8C\uC7A5",
33486
+ message: "'24\uC2DC'\uBCF4\uB2E4 \uB2E4\uC74C \uB0A0 00:00 \uB610\uB294 '18:00\uAE4C\uC9C0' \uAD8C\uC7A5",
33385
33487
  suggest: "\uC608) 18:00"
33386
33488
  },
33387
33489
  {
@@ -33392,10 +33494,11 @@ var RULES = [
33392
33494
  suggest: "\uC608) 13:20"
33393
33495
  },
33394
33496
  // 금액 ─ '천원' 금지, 금+숫자 붙여쓰기
33497
+ // 숫자 런 첫머리에서만 시작 — 아니면 긴 숫자 줄에서 시작 자리마다 런을 다시 훑어 이차 폭주(숫자 5만 자 2.9초)
33395
33498
  {
33396
33499
  code: "MONEY_CHEONWON",
33397
33500
  severity: "error",
33398
- pattern: /\d+\s*천\s*원/g,
33501
+ pattern: /(?<!\d)\d+\s*천\s*원/g,
33399
33502
  message: "\uAE08\uC561\uC740 '\uCC9C\uC6D0'\uC73C\uB85C \uC904\uC774\uC9C0 \uC54A\uACE0 \uC544\uB77C\uBE44\uC544 \uC22B\uC790\uB85C",
33400
33503
  suggest: "\uC608) 345,000\uC6D0"
33401
33504
  },
@@ -33444,6 +33547,14 @@ var RULES = [
33444
33547
  },
33445
33548
  // ── 편람 보강 (v4.12.1) — pyhwpxlib Gongmun 검사·hwpx-skill gonmun_lint 대조로 빈 축 보충 ──
33446
33549
  // 금액 한글 병기 — 규정 시행규칙 제2조: 아라비아 숫자 다음 괄호 안에 한글로 적는다
33550
+ // 한글 금액은 글자 묶음 사이에만 공백 — 공백 수량자가 겹치면 닫는 괄호 없는 긴 공백 줄에서 폭주했다(160칸 9.8초)
33551
+ {
33552
+ code: "MONEY_HANGUL_MISMATCH",
33553
+ severity: "error",
33554
+ pattern: /금\s?(\d[\d,]*)원\s*[((]\s*(?:금\s*)?([일이삼사오육칠팔구십백천만억조영]+(?:\s+[일이삼사오육칠팔구십백천만억조영]+)*)\s*원?\s*[))]/g,
33555
+ check: moneyHangulCheck,
33556
+ message: "\uBCD1\uAE30\uD55C \uD55C\uAE00 \uAE08\uC561\uC774 \uC22B\uC790\uC640 \uB2E4\uB984"
33557
+ },
33447
33558
  {
33448
33559
  code: "MONEY_NO_HANGUL",
33449
33560
  severity: "warning",
@@ -33454,6 +33565,22 @@ var RULES = [
33454
33565
  // 물결표 앞뒤 붙여쓰기 — 기간·범위는 "2. 20.∼2. 24." 처럼 붙여 씀. 숫자·날짜·시각 범위(앞은 숫자·
33455
33566
  // 온점·괄호·년월일시분, 뒤는 숫자·연도 생략부호)에만 건다 — "기간( 부터 ~ 까지)" 기입란·"~ ※ 비고"·
33456
33567
  // "~ 수리완료시까지" 처럼 낱말이 붙는 물결표는 범위 표기가 아니다 (v4.12.2, 별지서식 47→30·기안문 32→27 실측)
33568
+ // 기간·시각 범위의 대시 — 점 날짜(연·월·일)나 시:분 사이만 잡아 전화번호(02-123-4567)·조항(제4조-2)·코드(IT-2026-01)는
33569
+ // 걸리지 않는다. 자동 치환은 하지 않는다(명세 (d) 물결표 자동 치환 위험 — 검수만)
33570
+ {
33571
+ code: "RANGE_DASH",
33572
+ severity: "warning",
33573
+ pattern: /\d{2,4}\.\s*\d{1,2}\.\s*\d{1,2}\.?\s*[-–—]\s*(?:\d{2,4}\.\s*)?\d{1,2}\.\s*\d{1,2}\.?|(?<![\d:])\d{1,2}:\d{2}\s*[-–—]\s*\d{1,2}:\d{2}(?![\d:])/g,
33574
+ message: "\uAE30\uAC04\xB7\uC2DC\uAC01 \uBC94\uC704\uB294 \uBB3C\uACB0\uD45C(\u223C)\uB85C",
33575
+ suggest: "\uC608) 2026. 3. 1.\u223C6. 30., 14:00\u223C16:00"
33576
+ },
33577
+ {
33578
+ code: "WEEKDAY_MISMATCH",
33579
+ severity: "error",
33580
+ pattern: /(?<![\d.])(\d{4})\.\s*(\d{1,2})\.\s*(\d{1,2})\.?\s*\(([월화수목금토일])\)/g,
33581
+ check: weekdayCheck,
33582
+ message: "\uB0A0\uC9DC\uC640 \uC694\uC77C\uC774 \uB9DE\uC9C0 \uC54A\uC74C"
33583
+ },
33457
33584
  {
33458
33585
  code: "TILDE_SPACE",
33459
33586
  severity: "warning",
@@ -33480,6 +33607,39 @@ var RULES = [
33480
33607
  suggest: "\uC608) \uCF58\uD150\uCE20\xB7\uC560\uD50C\uB9AC\uCF00\uC774\uC158\xB7\uBA54\uC2DC\uC9C0\xB7\uC6CC\uD06C\uC20D\xB7\uC2A4\uCF00\uC904\xB7\uCF58\uC149\uD2B8"
33481
33608
  },
33482
33609
  // 차별·비하 표현 — 행안부 공문서 작성 지침·국립국어원 순화어
33610
+ // 표 줄은 건너뜀 — 법정 서식 라벨 칸("전일 지침 | 금일 지침")은 바꿀 대상이 아니다 (gate-fill·licbyl 801건 실측 7건 전부 표 라벨)
33611
+ {
33612
+ code: "PLAIN_LANGUAGE",
33613
+ severity: "warning",
33614
+ skipTable: true,
33615
+ pattern: PLAIN_RE,
33616
+ message: "\uACF5\uACF5\uC5B8\uC5B4 \uC26C\uC6B4 \uB9D0\uB85C \uBC14\uAFD4 \uC4F0\uAE30 \uAD8C\uC7A5 (\uD2C0\uB9B0 \uB9D0\uC740 \uC544\uB2D8)"
33617
+ },
33618
+ {
33619
+ code: "SPELLING_ERROR",
33620
+ severity: "warning",
33621
+ pattern: SPELLING_RE,
33622
+ message: "\uB9DE\uCDA4\uBC95 \uC624\uAE30"
33623
+ },
33624
+ // 끝 표시·붙임 표시 2타 (규칙 제2조제2항·편람) — 생성 경로는 official 프리셋이 스스로 맞춘다
33625
+ {
33626
+ code: "END_MARK_SPACE",
33627
+ severity: "warning",
33628
+ documentOnly: true,
33629
+ rawSource: true,
33630
+ pattern: /(?<=\.)(?:[ \t\u3000]?|[ \t\u3000]{3,})끝\.[ \t\u3000]*$/g,
33631
+ message: "'\uB05D.'\uC740 \uC55E \uAE00 \uB9C8\uCE68\uD45C\uC5D0\uC11C 2\uD0C0 \uB744\uC6C0",
33632
+ suggest: "\uC608) \u2026\uBC14\uB78D\uB2C8\uB2E4. \uB05D."
33633
+ },
33634
+ {
33635
+ code: "BUNIM_SPACE",
33636
+ severity: "warning",
33637
+ documentOnly: true,
33638
+ rawSource: true,
33639
+ pattern: /^[ \t\u3000]*붙임(?:[ \t\u3000]|[ \t\u3000]{3,})(?=[^ \t\u3000::])(?=.*(?<!\d)\d+\s*부\.(?:[ \t\u3000]*끝\.)?[ \t\u3000]*$)/g,
33640
+ message: "'\uBD99\uC784' \uB4A4\uB294 2\uD0C0 \uB744\uC6C0",
33641
+ suggest: "\uC608) \uBD99\uC784 \uACC4\uD68D\uC11C 1\uBD80. \uB05D."
33642
+ },
33483
33643
  {
33484
33644
  code: "DISCRIMINATORY_TERM",
33485
33645
  severity: "warning",
@@ -33499,6 +33659,7 @@ var RULES = [
33499
33659
  {
33500
33660
  code: "AI_BOLD_OVERUSE",
33501
33661
  severity: "warning",
33662
+ rawSource: true,
33502
33663
  pattern: /(?:\*\*[^*\n]+\*\*[^*\n]*){3}/g,
33503
33664
  message: "\uD55C \uC904\uC5D0 \uAC15\uC870(**) 3\uD68C \uC774\uC0C1 \u2014 \uAC15\uC870 \uB0A8\uBC1C\uC740 \uC0DD\uC131\uD615 AI \uBB38\uCCB4 \uD754\uC801",
33504
33665
  suggest: "\uB9AC\uB4DC\uC5B4\xB7\uD575\uC2EC \uC218\uCE58 \uD55C \uACF3\uB9CC \uAC15\uC870"
@@ -33521,15 +33682,19 @@ function lintGongmunText(text, opts) {
33521
33682
  const tableLine = /^\s*\|/.test(line) || /<t[dhr][\s>]/i.test(line);
33522
33683
  for (const r of RULES) {
33523
33684
  if (r.skipTable && tableLine) continue;
33685
+ if (r.documentOnly && !opts?.document) continue;
33686
+ if (r.rawSource && opts?.parsed) continue;
33524
33687
  r.pattern.lastIndex = 0;
33525
33688
  for (const m of line.matchAll(r.pattern)) {
33689
+ const checked = r.check ? r.check(m) : void 0;
33690
+ if (checked === null) continue;
33526
33691
  findings.push({
33527
33692
  line: i + 1,
33528
- match: m[0].trim(),
33693
+ match: m[0].trim() || m[0],
33529
33694
  rule: r.code,
33530
33695
  severity: r.severity,
33531
33696
  message: r.message,
33532
- suggest: dictSuggest(r.code, m[0]) ?? r.suggest
33697
+ suggest: checked ?? dictSuggest(r.code, m[0]) ?? r.suggest
33533
33698
  });
33534
33699
  }
33535
33700
  }
@@ -33553,7 +33718,7 @@ function dictSuggest(code, match) {
33553
33718
  const d = m.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
33554
33719
  if (d) return `${m} \u2192 ${d[1]}. ${Number(d[2])}. ${Number(d[3])}.`;
33555
33720
  }
33556
- const table2 = code === "LOANWORD_ERROR" ? LOANWORD_FIXES : code === "DISCRIMINATORY_TERM" ? DISCRIM_FIXES : null;
33721
+ const table2 = code === "LOANWORD_ERROR" ? LOANWORD_FIXES : code === "DISCRIMINATORY_TERM" ? DISCRIM_FIXES : code === "PLAIN_LANGUAGE" ? PLAIN_FIXES : code === "SPELLING_ERROR" ? SPELLING_FIXES : null;
33557
33722
  if (!table2) return void 0;
33558
33723
  const hit = table2.find(([w]) => w === match.trim());
33559
33724
  return hit ? `${hit[0]} \u2192 ${hit[1]}` : void 0;
@@ -33799,6 +33964,60 @@ function muncheLintWarnings(text, limit = 10) {
33799
33964
  // src/roundtrip/patcher.ts
33800
33965
  import JSZip12 from "jszip";
33801
33966
 
33967
+ // src/roundtrip/integrity.ts
33968
+ import { DOMParser as DOMParser6 } from "@xmldom/xmldom";
33969
+ import { inflateRawSync as inflateRawSync6 } from "zlib";
33970
+ var LINESEG_RE = /<(\w+:)?linesegarray\b[^>]*?(?:\/>|>[\s\S]*?<\/\1linesegarray>)/g;
33971
+ var TAG_RE2 = /<[^>]+>/g;
33972
+ var sameBytes = (a, b) => Buffer.compare(a, b) === 0;
33973
+ function xmlError(xml) {
33974
+ let error = null;
33975
+ try {
33976
+ new DOMParser6({ onError: (level, msg2) => {
33977
+ if (level !== "warning" && !error) error = String(msg2);
33978
+ } }).parseFromString(xml, "text/xml");
33979
+ } catch (e) {
33980
+ error ??= e instanceof Error ? e.message : String(e);
33981
+ }
33982
+ return error;
33983
+ }
33984
+ var textOnly = (xml, s) => !s.replacement.includes("<") && !xml.slice(s.start, s.end).includes("<");
33985
+ function checkPatchIntegrity(original, patched, replacements, sections) {
33986
+ const issues = [];
33987
+ const a = readZipEntries(original), b = readZipEntries(patched);
33988
+ const names = (m) => [...m.keys()].join("\n");
33989
+ if (names(a) !== names(b)) issues.push("ZIP \uC5D4\uD2B8\uB9AC \uBAA9\uB85D\xB7\uC21C\uC11C\uAC00 \uC6D0\uBCF8\uACFC \uB2E4\uB984");
33990
+ for (const [name, entry] of a) {
33991
+ if (replacements.has(name)) continue;
33992
+ const out = b.get(name);
33993
+ if (!out || out.method !== entry.method || !sameBytes(out.compData, entry.compData)) issues.push(`\uAD50\uCCB4\uD558\uC9C0 \uC54A\uC740 \uC5D4\uD2B8\uB9AC\uC758 \uBC14\uC774\uD2B8\uAC00 \uBC14\uB01C: ${name}`);
33994
+ }
33995
+ const decoder = new TextDecoder();
33996
+ const originalText = (name) => {
33997
+ const e = a.get(name);
33998
+ if (!e) return null;
33999
+ try {
34000
+ return decoder.decode(e.method === 8 ? inflateRawSync6(e.compData) : e.compData);
34001
+ } catch {
34002
+ return null;
34003
+ }
34004
+ };
34005
+ for (const [name, data] of replacements) {
34006
+ if (!/\.xml$/i.test(name)) continue;
34007
+ const err = xmlError(decoder.decode(data));
34008
+ const before2 = originalText(name);
34009
+ if (err && before2 !== null && !xmlError(before2)) issues.push(`\uAD50\uCCB4\uD55C XML \uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC74C: ${name} \u2014 ${err.split("\n")[0]}`);
34010
+ }
34011
+ for (const { name, xml, splices } of sections) {
34012
+ if (!splices.every((s) => textOnly(xml, s))) continue;
34013
+ const data = replacements.get(name);
34014
+ if (!data) continue;
34015
+ const tags = (x) => (x.replace(LINESEG_RE, "").match(TAG_RE2) ?? []).join("");
34016
+ if (tags(xml) !== tags(decoder.decode(data))) issues.push(`\uAE00\uB9CC \uACE0\uCE5C \uC139\uC158\uC758 \uD0DC\uADF8\xB7\uC18D\uC131\uC774 \uBC14\uB01C: ${name}`);
34017
+ }
34018
+ return issues;
34019
+ }
34020
+
33802
34021
  // src/roundtrip/table-rows.ts
33803
34022
  var ROW_OBJECT_RE = /<(?:[A-Za-z0-9_]+:)?(?:tbl|pic|equation|ole|container|shape|drawingObject|drawText|video|chart|fieldBegin|fieldEnd|ctrl)\b/;
33804
34023
  var TAG_AT_RE = /<[A-Za-z0-9_:]+(?:"[^"]*"|'[^']*'|[^>"'])*>/y;
@@ -35758,9 +35977,9 @@ async function patchHwpx(original, editedMarkdown, options) {
35758
35977
  for (const [oi, ei] of pairs) {
35759
35978
  if (oi !== null && ei !== null) {
35760
35979
  const orig = origUnits[oi];
35761
- const edited = editedUnits[ei];
35762
- if (orig.raw === edited.raw) continue;
35763
- applied += handleModifiedUnit(orig, edited, {
35980
+ const edited2 = editedUnits[ei];
35981
+ if (orig.raw === edited2.raw) continue;
35982
+ applied += handleModifiedUnit(orig, edited2, {
35764
35983
  origBlocks,
35765
35984
  paraMap,
35766
35985
  scans,
@@ -35777,10 +35996,12 @@ async function patchHwpx(original, editedMarkdown, options) {
35777
35996
  }
35778
35997
  }
35779
35998
  const replacements = /* @__PURE__ */ new Map();
35999
+ const edited = [];
35780
36000
  const encoder = new TextEncoder();
35781
36001
  try {
35782
36002
  for (let i = 0; i < scans.length; i++) {
35783
36003
  if (sectionSplices[i].length === 0) continue;
36004
+ edited.push({ name: sectionPaths[i], xml: scans[i].xml, splices: [...sectionSplices[i]] });
35784
36005
  const claimed = sectionSplices[i].filter((s) => s.end > s.start);
35785
36006
  sectionSplices[i].push(...allLinesegRemovalSplices(scans[i].xml).filter((ls) => !claimed.some((c) => ls.start >= c.start && ls.end <= c.end)));
35786
36007
  const newXml = applySplices(scans[i].xml, sectionSplices[i]);
@@ -35805,6 +36026,8 @@ async function patchHwpx(original, editedMarkdown, options) {
35805
36026
  }
35806
36027
  let verification;
35807
36028
  if (options?.verify !== false) {
36029
+ const issues = replacements.size > 0 ? checkPatchIntegrity(original, data, replacements, edited) : [];
36030
+ if (issues.length) return { success: false, applied, skipped, error: `\uD328\uCE58\uBCF8 \uBB34\uACB0\uC131 \uAC80\uC0AC \uC2E4\uD328 \u2014 \uD328\uCE58 \uC911\uB2E8: ${issues.join("; ")}` };
35808
36031
  try {
35809
36032
  const reparsed = await parseHwpxDocument(u8ToArrayBuffer(data), { layoutTables: "keep" });
35810
36033
  verification = diffUnitLists(splitMarkdownUnits(reparsed.markdown), editedUnits);
@@ -36174,7 +36397,7 @@ async function resolveHeaderEntryName(zip) {
36174
36397
 
36175
36398
  // src/validate.ts
36176
36399
  import JSZip13 from "jszip";
36177
- import { DOMParser as DOMParser6 } from "@xmldom/xmldom";
36400
+ import { DOMParser as DOMParser7 } from "@xmldom/xmldom";
36178
36401
  var REQUIRED_FILES = [
36179
36402
  "mimetype",
36180
36403
  "META-INF/container.xml",
@@ -36220,7 +36443,7 @@ async function validateHwpx(buffer) {
36220
36443
  const text = await zip.files[name].async("string");
36221
36444
  let firstError = null;
36222
36445
  try {
36223
- new DOMParser6({
36446
+ new DOMParser7({
36224
36447
  onError(level, msg2) {
36225
36448
  if (level !== "warning" && firstError === null) firstError = String(msg2);
36226
36449
  }
@@ -41703,7 +41926,7 @@ function scriptsOff(r, off) {
41703
41926
  }
41704
41927
  async function parseImage(buffer, options) {
41705
41928
  try {
41706
- const { parseImageDocument } = await import("./image-ocr-R6ZDFBAN.js");
41929
+ const { parseImageDocument } = await import("./image-ocr-YYTFOUSY.js");
41707
41930
  const { blocks, warnings } = await parseImageDocument(buffer, options);
41708
41931
  stripScriptTags({ blocks });
41709
41932
  return {
@@ -41763,7 +41986,7 @@ async function parseHwp(buffer, options) {
41763
41986
  async function parsePdf(buffer, options) {
41764
41987
  let parsePdfDocument;
41765
41988
  try {
41766
- const mod = await import("./parser-5GUEP6EK.js");
41989
+ const mod = await import("./parser-LY67CB7A.js");
41767
41990
  parsePdfDocument = mod.parsePdfDocument;
41768
41991
  } catch {
41769
41992
  return {