kordoc 3.0.1 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +559 -536
  2. package/dist/{-5BWAV4ZY.js → -UCE73NNK.js} +24 -6
  3. package/dist/{chunk-MEPHGCPQ.js → chunk-5CJGKKMZ.js} +1 -1
  4. package/dist/chunk-5CJGKKMZ.js.map +1 -0
  5. package/dist/{chunk-MUOQXDZ4.cjs → chunk-DCZVOIEO.cjs} +1 -1
  6. package/dist/chunk-DCZVOIEO.cjs.map +1 -0
  7. package/dist/{chunk-NBJB6TJB.cjs → chunk-FJON4QNB.cjs} +2 -2
  8. package/dist/chunk-FJON4QNB.cjs.map +1 -0
  9. package/dist/{chunk-SBVRCJFH.js → chunk-GE43BE46.js} +1 -1
  10. package/dist/chunk-GS7T56RP.cjs +8 -0
  11. package/dist/chunk-GS7T56RP.cjs.map +1 -0
  12. package/dist/{chunk-O5P6EG5L.js → chunk-KR6DSGM7.js} +2 -2
  13. package/dist/chunk-KR6DSGM7.js.map +1 -0
  14. package/dist/chunk-MOL7MDBG.js +0 -0
  15. package/dist/{chunk-X7VQVMXQ.js → chunk-VMUP5WPI.js} +2043 -1606
  16. package/dist/chunk-VMUP5WPI.js.map +1 -0
  17. package/dist/{chunk-X3SCCO5Q.js → chunk-ZZNSI4GV.js} +2 -2
  18. package/dist/chunk-ZZNSI4GV.js.map +1 -0
  19. package/dist/cli.js +5 -5
  20. package/dist/cli.js.map +1 -1
  21. package/dist/{detect-RI2MQ33K.js → detect-PJZMUL2Z.js} +2 -2
  22. package/dist/{formula-XGG6ZP42.cjs → formula-5NKVS2LR.cjs} +4 -2
  23. package/dist/formula-5NKVS2LR.cjs.map +1 -0
  24. package/dist/formula-JCNF43NE.js +0 -0
  25. package/dist/{formula-3AQUUIRF.js → formula-RXVSQPXI.js} +1 -1
  26. package/dist/index.cjs +2190 -1750
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +263 -6
  29. package/dist/index.d.ts +263 -6
  30. package/dist/index.js +2045 -1608
  31. package/dist/index.js.map +1 -1
  32. package/dist/mcp.js +5 -5
  33. package/dist/mcp.js.map +1 -1
  34. package/dist/page-range-737B4EZW.js +0 -0
  35. package/dist/page-range-LNMXJU6W.js +7 -0
  36. package/dist/page-range-P7SDW6LR.cjs +8 -0
  37. package/dist/page-range-P7SDW6LR.cjs.map +1 -0
  38. package/dist/{parser-3N6FZSKU.js → parser-4L7UFVEP.js} +3 -3
  39. package/dist/parser-4L7UFVEP.js.map +1 -0
  40. package/dist/{parser-LZH7ZELV.js → parser-QTJL6IQY.js} +5 -5
  41. package/dist/parser-QTJL6IQY.js.map +1 -0
  42. package/dist/{parser-5FZJVLQL.cjs → parser-XULVNDMN.cjs} +19 -18
  43. package/dist/parser-XULVNDMN.cjs.map +1 -0
  44. package/dist/{provider-SNONEZNW.cjs → provider-5K7O3Z2O.cjs} +4 -2
  45. package/dist/provider-5K7O3Z2O.cjs.map +1 -0
  46. package/dist/{provider-AKROB7WQ.js → provider-7H4CPZYS.js} +1 -1
  47. package/dist/provider-7H4CPZYS.js.map +1 -0
  48. package/dist/{provider-2SEHU2FM.js → provider-H4WWSPOV.js} +1 -1
  49. package/dist/provider-H4WWSPOV.js.map +1 -0
  50. package/dist/setup-57FB3LSP.js +0 -0
  51. package/dist/{watch-4FMRS7QU.js → watch-FPDSHB23.js} +4 -4
  52. package/dist/watch-FPDSHB23.js.map +1 -0
  53. package/package.json +98 -98
  54. package/dist/chunk-MEPHGCPQ.js.map +0 -1
  55. package/dist/chunk-MUOQXDZ4.cjs.map +0 -1
  56. package/dist/chunk-NBJB6TJB.cjs.map +0 -1
  57. package/dist/chunk-O5P6EG5L.js.map +0 -1
  58. package/dist/chunk-X3SCCO5Q.js.map +0 -1
  59. package/dist/chunk-X7VQVMXQ.js.map +0 -1
  60. package/dist/formula-XGG6ZP42.cjs.map +0 -1
  61. package/dist/page-range-3C7UGGEK.cjs +0 -7
  62. package/dist/page-range-3C7UGGEK.cjs.map +0 -1
  63. package/dist/page-range-H35FN3OQ.js +0 -7
  64. package/dist/parser-3N6FZSKU.js.map +0 -1
  65. package/dist/parser-5FZJVLQL.cjs.map +0 -1
  66. package/dist/parser-LZH7ZELV.js.map +0 -1
  67. package/dist/provider-2SEHU2FM.js.map +0 -1
  68. package/dist/provider-AKROB7WQ.js.map +0 -1
  69. package/dist/provider-SNONEZNW.cjs.map +0 -1
  70. package/dist/watch-4FMRS7QU.js.map +0 -1
  71. /package/dist/{-5BWAV4ZY.js.map → -UCE73NNK.js.map} +0 -0
  72. /package/dist/{chunk-SBVRCJFH.js.map → chunk-GE43BE46.js.map} +0 -0
  73. /package/dist/{detect-RI2MQ33K.js.map → detect-PJZMUL2Z.js.map} +0 -0
  74. /package/dist/{formula-3AQUUIRF.js.map → formula-RXVSQPXI.js.map} +0 -0
  75. /package/dist/{page-range-H35FN3OQ.js.map → page-range-LNMXJU6W.js.map} +0 -0
package/dist/index.js CHANGED
@@ -17,10 +17,10 @@ import {
17
17
  sanitizeHref,
18
18
  stripDtd,
19
19
  toArrayBuffer
20
- } from "./chunk-X3SCCO5Q.js";
20
+ } from "./chunk-ZZNSI4GV.js";
21
21
  import {
22
22
  parsePageRange
23
- } from "./chunk-SBVRCJFH.js";
23
+ } from "./chunk-GE43BE46.js";
24
24
 
25
25
  // src/index.ts
26
26
  import { readFile } from "fs/promises";
@@ -16907,7 +16907,7 @@ async function parseXlsxDocument(buffer, options) {
16907
16907
  }
16908
16908
  let pageFilter = null;
16909
16909
  if (options?.pages) {
16910
- const { parsePageRange: parsePageRange2 } = await import("./page-range-H35FN3OQ.js");
16910
+ const { parsePageRange: parsePageRange2 } = await import("./page-range-LNMXJU6W.js");
16911
16911
  pageFilter = parsePageRange2(options.pages, sheets.length);
16912
16912
  }
16913
16913
  const blocks = [];
@@ -17545,7 +17545,7 @@ async function parseXlsDocument(buffer, options) {
17545
17545
  const totalSheets = Math.min(globals.sheets.length, MAX_SHEETS2);
17546
17546
  let pageFilter = null;
17547
17547
  if (options?.pages) {
17548
- const { parsePageRange: parsePageRange2 } = await import("./page-range-H35FN3OQ.js");
17548
+ const { parsePageRange: parsePageRange2 } = await import("./page-range-LNMXJU6W.js");
17549
17549
  pageFilter = parsePageRange2(options.pages, totalSheets);
17550
17550
  }
17551
17551
  const allBlocks = [];
@@ -17968,21 +17968,21 @@ function isDisplayMath(el) {
17968
17968
 
17969
17969
  // src/docx/parser.ts
17970
17970
  var MAX_DECOMPRESS_SIZE4 = 100 * 1024 * 1024;
17971
- function getChildElements(parent, localName3) {
17971
+ function getChildElements(parent, localName2) {
17972
17972
  const result = [];
17973
17973
  const children = parent.childNodes;
17974
17974
  for (let i = 0; i < children.length; i++) {
17975
17975
  const node = children[i];
17976
17976
  if (node.nodeType === 1) {
17977
17977
  const el = node;
17978
- if (el.localName === localName3 || el.tagName?.endsWith(`:${localName3}`)) {
17978
+ if (el.localName === localName2 || el.tagName?.endsWith(`:${localName2}`)) {
17979
17979
  result.push(el);
17980
17980
  }
17981
17981
  }
17982
17982
  }
17983
17983
  return result;
17984
17984
  }
17985
- function findElements(parent, localName3) {
17985
+ function findElements(parent, localName2) {
17986
17986
  const result = [];
17987
17987
  const walk = (node) => {
17988
17988
  const children = node.childNodes;
@@ -17990,7 +17990,7 @@ function findElements(parent, localName3) {
17990
17990
  const child = children[i];
17991
17991
  if (child.nodeType === 1) {
17992
17992
  const el = child;
17993
- if (el.localName === localName3 || el.tagName?.endsWith(`:${localName3}`)) {
17993
+ if (el.localName === localName2 || el.tagName?.endsWith(`:${localName2}`)) {
17994
17994
  result.push(el);
17995
17995
  }
17996
17996
  walk(el);
@@ -18000,11 +18000,11 @@ function findElements(parent, localName3) {
18000
18000
  walk(parent);
18001
18001
  return result;
18002
18002
  }
18003
- function getAttr(el, localName3) {
18003
+ function getAttr(el, localName2) {
18004
18004
  const attrs = el.attributes;
18005
18005
  for (let i = 0; i < attrs.length; i++) {
18006
18006
  const attr = attrs[i];
18007
- if (attr.localName === localName3 || attr.name === localName3) return attr.value;
18007
+ if (attr.localName === localName2 || attr.name === localName2) return attr.value;
18008
18008
  }
18009
18009
  return null;
18010
18010
  }
@@ -18377,11 +18377,11 @@ async function parseDocxDocument(buffer, options) {
18377
18377
  const node = children[i];
18378
18378
  if (node.nodeType !== 1) continue;
18379
18379
  const el = node;
18380
- const localName3 = el.localName ?? el.tagName?.split(":").pop();
18381
- if (localName3 === "p") {
18380
+ const localName2 = el.localName ?? el.tagName?.split(":").pop();
18381
+ if (localName2 === "p") {
18382
18382
  const block = parseParagraph2(el, styles, numbering, footnotes, rels);
18383
18383
  if (block) blocks.push(block);
18384
- } else if (localName3 === "tbl") {
18384
+ } else if (localName2 === "tbl") {
18385
18385
  const block = parseTable(el, styles, numbering, footnotes, rels);
18386
18386
  if (block) blocks.push(block);
18387
18387
  }
@@ -18672,6 +18672,132 @@ function countSections(body) {
18672
18672
  return count;
18673
18673
  }
18674
18674
 
18675
+ // src/form/match.ts
18676
+ function normalizeLabel(label) {
18677
+ return label.trim().replace(/[::\s()()·]/g, "");
18678
+ }
18679
+ function findMatchingKey(cellLabel, values) {
18680
+ if (values.has(cellLabel)) return cellLabel;
18681
+ let bestKey;
18682
+ let bestLen = 0;
18683
+ for (const key of values.keys()) {
18684
+ if (cellLabel.startsWith(key)) {
18685
+ if (key.length >= cellLabel.length * 0.6 && key.length > bestLen) {
18686
+ bestLen = key.length;
18687
+ bestKey = key;
18688
+ }
18689
+ } else if (key.startsWith(cellLabel)) {
18690
+ if (cellLabel.length >= key.length * 0.6 && cellLabel.length > bestLen) {
18691
+ bestLen = cellLabel.length;
18692
+ bestKey = key;
18693
+ }
18694
+ }
18695
+ }
18696
+ return bestKey;
18697
+ }
18698
+ function isKeywordLabel(text) {
18699
+ const trimmed = text.trim().replace(/[¹²³⁴⁵⁶⁷⁸⁹⁰*※]+$/g, "").trim();
18700
+ if (!trimmed || trimmed.length > 15) return false;
18701
+ for (const kw of LABEL_KEYWORDS) {
18702
+ if (trimmed.includes(kw)) return true;
18703
+ }
18704
+ return false;
18705
+ }
18706
+ function fillInCellPatterns(cellText, values, matchedLabels) {
18707
+ let text = cellText;
18708
+ const matches = [];
18709
+ text = text.replace(
18710
+ /([가-힣A-Za-z]+)\(\s{1,}\)([가-힣A-Za-z]*)/g,
18711
+ (match, prefix, suffix) => {
18712
+ const label = prefix + suffix;
18713
+ const normalizedLabel = normalizeLabel(label);
18714
+ const matchKey = values.has(normalizedLabel) ? normalizedLabel : values.has(normalizeLabel(prefix)) ? normalizeLabel(prefix) : void 0;
18715
+ if (matchKey === void 0) return match;
18716
+ const newValue = values.get(matchKey);
18717
+ matchedLabels.add(matchKey);
18718
+ matches.push({ key: matchKey, label, value: newValue });
18719
+ return `${prefix}(${newValue})${suffix}`;
18720
+ }
18721
+ );
18722
+ text = text.replace(
18723
+ /□([가-힣A-Za-z]+)/g,
18724
+ (match, keyword) => {
18725
+ const normalizedKw = normalizeLabel(keyword);
18726
+ const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18727
+ if (matchKey === void 0) return match;
18728
+ const val = values.get(matchKey);
18729
+ const isTruthy = ["\u2611", "\u2713", "\u2714", "v", "V", "true", "1", "yes", "o", "O"].includes(val.trim()) || val.trim() === "";
18730
+ if (!isTruthy) return match;
18731
+ matchedLabels.add(matchKey);
18732
+ matches.push({ key: matchKey, label: `\u25A1${keyword}`, value: "\u2611" });
18733
+ return `\u2611${keyword}`;
18734
+ }
18735
+ );
18736
+ text = text.replace(
18737
+ /\(([가-힣A-Za-z]+)[::]\s{1,}\)/g,
18738
+ (match, keyword) => {
18739
+ const normalizedKw = normalizeLabel(keyword);
18740
+ const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18741
+ if (matchKey === void 0) return match;
18742
+ const newValue = values.get(matchKey);
18743
+ matchedLabels.add(matchKey);
18744
+ matches.push({ key: matchKey, label: keyword, value: newValue });
18745
+ return `(${keyword}\uFF1A${newValue})`;
18746
+ }
18747
+ );
18748
+ return matches.length > 0 ? { text, matches } : null;
18749
+ }
18750
+ var INLINE_LABEL_RE = /([가-힣A-Za-z]{2,10})\s*[::]/g;
18751
+ function scanInlineSegments(text) {
18752
+ const labels = [];
18753
+ INLINE_LABEL_RE.lastIndex = 0;
18754
+ let m;
18755
+ while ((m = INLINE_LABEL_RE.exec(text)) !== null) {
18756
+ if (text[INLINE_LABEL_RE.lastIndex] === "/") continue;
18757
+ labels.push({ label: m[1], start: m.index, end: INLINE_LABEL_RE.lastIndex });
18758
+ }
18759
+ const segments = [];
18760
+ for (let i = 0; i < labels.length; i++) {
18761
+ const cur = labels[i];
18762
+ let vs = cur.end;
18763
+ while (vs < text.length && (text[vs] === " " || text[vs] === " ")) vs++;
18764
+ let ve = i + 1 < labels.length ? labels[i + 1].start : text.length;
18765
+ if (ve < vs) ve = vs;
18766
+ const sep = text.slice(vs, ve).search(/[\n,;]/);
18767
+ if (sep !== -1) ve = vs + sep;
18768
+ if (ve - vs > 100) ve = vs + 100;
18769
+ while (ve > vs && /\s/.test(text[ve - 1])) ve--;
18770
+ segments.push({
18771
+ label: cur.label,
18772
+ labelStart: cur.start,
18773
+ valueStart: vs,
18774
+ valueEnd: ve,
18775
+ value: text.slice(vs, ve)
18776
+ });
18777
+ }
18778
+ return segments;
18779
+ }
18780
+ function padInsertion(text, pos, value) {
18781
+ const lead = pos > 0 && !/\s/.test(text[pos - 1]) ? " " : "";
18782
+ const trail = pos < text.length && !/\s/.test(text[pos]) ? " " : "";
18783
+ return lead + value + trail;
18784
+ }
18785
+ function normalizeValues(values) {
18786
+ const map = /* @__PURE__ */ new Map();
18787
+ for (const [label, value] of Object.entries(values)) {
18788
+ map.set(normalizeLabel(label), value);
18789
+ }
18790
+ return map;
18791
+ }
18792
+ function resolveUnmatched(normalizedValues, matchedLabels, originalValues) {
18793
+ return [...normalizedValues.keys()].filter((k) => !matchedLabels.has(k)).map((k) => {
18794
+ for (const orig of Object.keys(originalValues)) {
18795
+ if (normalizeLabel(orig) === k) return orig;
18796
+ }
18797
+ return k;
18798
+ });
18799
+ }
18800
+
18675
18801
  // src/form/recognize.ts
18676
18802
  var LABEL_KEYWORDS = /* @__PURE__ */ new Set([
18677
18803
  "\uC131\uBA85",
@@ -18791,107 +18917,73 @@ function extractFromTable(table) {
18791
18917
  }
18792
18918
  function extractInlineFields(text) {
18793
18919
  const fields = [];
18794
- const pattern = /([가-힣A-Za-z]{2,10})\s*[::]\s*([^\n,;]{1,100})/g;
18795
- let match;
18796
- while ((match = pattern.exec(text)) !== null) {
18797
- const label = match[1].trim();
18798
- const value = match[2].trim();
18799
- if (value) {
18800
- fields.push({ label, value, row: -1, col: -1 });
18920
+ for (const seg of scanInlineSegments(text)) {
18921
+ if (seg.value) {
18922
+ fields.push({ label: seg.label, value: seg.value, row: -1, col: -1 });
18801
18923
  }
18802
18924
  }
18803
18925
  return fields;
18804
18926
  }
18805
-
18806
- // src/form/match.ts
18807
- function normalizeLabel(label) {
18808
- return label.trim().replace(/[::\s()()·]/g, "");
18809
- }
18810
- function findMatchingKey(cellLabel, values) {
18811
- if (values.has(cellLabel)) return cellLabel;
18812
- let bestKey;
18813
- let bestLen = 0;
18814
- for (const key of values.keys()) {
18815
- if (cellLabel.startsWith(key)) {
18816
- if (key.length >= cellLabel.length * 0.6 && key.length > bestLen) {
18817
- bestLen = key.length;
18818
- bestKey = key;
18819
- }
18820
- } else if (key.startsWith(cellLabel)) {
18821
- if (cellLabel.length >= key.length * 0.6 && cellLabel.length > bestLen) {
18822
- bestLen = cellLabel.length;
18823
- bestKey = key;
18824
- }
18825
- }
18826
- }
18827
- return bestKey;
18828
- }
18829
- function isKeywordLabel(text) {
18830
- const trimmed = text.trim().replace(/[¹²³⁴⁵⁶⁷⁸⁹⁰*※]+$/g, "").trim();
18831
- if (!trimmed || trimmed.length > 15) return false;
18832
- for (const kw of LABEL_KEYWORDS) {
18833
- if (trimmed.includes(kw)) return true;
18834
- }
18835
- return false;
18836
- }
18837
- function fillInCellPatterns(cellText, values, matchedLabels) {
18838
- let text = cellText;
18839
- const matches = [];
18840
- text = text.replace(
18841
- /([가-힣A-Za-z]+)\(\s{1,}\)([가-힣A-Za-z]*)/g,
18842
- (match, prefix, suffix) => {
18843
- const label = prefix + suffix;
18844
- const normalizedLabel = normalizeLabel(label);
18845
- const matchKey = values.has(normalizedLabel) ? normalizedLabel : values.has(normalizeLabel(prefix)) ? normalizeLabel(prefix) : void 0;
18846
- if (matchKey === void 0) return match;
18847
- const newValue = values.get(matchKey);
18848
- matchedLabels.add(matchKey);
18849
- matches.push({ key: matchKey, label, value: newValue });
18850
- return `${prefix}(${newValue})${suffix}`;
18851
- }
18852
- );
18853
- text = text.replace(
18854
- /□([가-힣A-Za-z]+)/g,
18855
- (match, keyword) => {
18856
- const normalizedKw = normalizeLabel(keyword);
18857
- const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18858
- if (matchKey === void 0) return match;
18859
- const val = values.get(matchKey);
18860
- const isTruthy = ["\u2611", "\u2713", "\u2714", "v", "V", "true", "1", "yes", "o", "O"].includes(val.trim()) || val.trim() === "";
18861
- if (!isTruthy) return match;
18862
- matchedLabels.add(matchKey);
18863
- matches.push({ key: matchKey, label: `\u25A1${keyword}`, value: "\u2611" });
18864
- return `\u2611${keyword}`;
18865
- }
18866
- );
18867
- text = text.replace(
18868
- /\(([가-힣A-Za-z]+)[::]\s{1,}\)/g,
18869
- (match, keyword) => {
18870
- const normalizedKw = normalizeLabel(keyword);
18871
- const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18872
- if (matchKey === void 0) return match;
18873
- const newValue = values.get(matchKey);
18874
- matchedLabels.add(matchKey);
18875
- matches.push({ key: matchKey, label: keyword, value: newValue });
18876
- return `(${keyword}\uFF1A${newValue})`;
18927
+ var LABEL_TYPE_RULES = [
18928
+ [/주민등록번호|외국인등록번호/, "idnum"],
18929
+ [/생년월일|일시|날짜|일자|기간|연월일|년월일|신청일|작성일|발급일|접수일/, "date"],
18930
+ [/전화|연락처|휴대폰|핸드폰|팩스/, "phone"],
18931
+ [/이메일|전자우편|email/i, "email"],
18932
+ [/금액|단가|수량|합계|소계|예산|비용|인원|급여|연봉/, "amount"]
18933
+ ];
18934
+ function inferFieldType(label, value) {
18935
+ if (/[□☑✓✔]/.test(value) || /[□☑✓✔]/.test(label)) return "checkbox";
18936
+ const v = value.trim();
18937
+ if (v) {
18938
+ if (/^\d{6}[-\s]?[1-4]\d{6}$/.test(v)) return "idnum";
18939
+ if (/^\d{4}\s*[-./년]\s*\d{1,2}\s*[-./월]\s*\d{1,2}\s*일?\s*\.?$/.test(v)) return "date";
18940
+ if (/^0\d{1,2}[-.)\s]?\d{3,4}[-.\s]?\d{4}$/.test(v)) return "phone";
18941
+ if (/^[\w.+-]+@[\w-]+(?:\.[\w-]+)+$/.test(v)) return "email";
18942
+ if (/^[\d,.\s]+(?:원|명|건|개|회|부|매|%)$/.test(v) && /\d/.test(v)) return "amount";
18943
+ if (/^\d{1,3}(?:,\d{3})+$/.test(v)) return "amount";
18944
+ }
18945
+ const norm = label.replace(/\s/g, "");
18946
+ for (const [re, type] of LABEL_TYPE_RULES) {
18947
+ if (re.test(norm)) return type;
18948
+ }
18949
+ return "text";
18950
+ }
18951
+ function isRequiredLabel(label) {
18952
+ return /[*※★]|\(\s*필수\s*\)|(\s*필수\s*)/.test(label);
18953
+ }
18954
+ function isEmptyValue(value) {
18955
+ const v = value.trim();
18956
+ if (!v) return true;
18957
+ return /^[\s_()()\-—–~.·,]*$/.test(v);
18958
+ }
18959
+ function extractFormSchema(blocks) {
18960
+ const { fields, confidence } = extractFormFields(blocks);
18961
+ const schemaFields = fields.map((f) => ({
18962
+ ...f,
18963
+ type: inferFieldType(f.label, f.value),
18964
+ required: isRequiredLabel(f.label) || void 0,
18965
+ empty: isEmptyValue(f.value)
18966
+ }));
18967
+ const seen = new Set(schemaFields.map((f) => normalizeLabel(f.label)));
18968
+ for (const block of blocks) {
18969
+ if (block.type !== "paragraph" || !block.text) continue;
18970
+ for (const seg of scanInlineSegments(block.text)) {
18971
+ if (seg.value) continue;
18972
+ const key = normalizeLabel(seg.label);
18973
+ if (seen.has(key)) continue;
18974
+ seen.add(key);
18975
+ schemaFields.push({
18976
+ label: seg.label,
18977
+ value: "",
18978
+ row: -1,
18979
+ col: -1,
18980
+ type: inferFieldType(seg.label, ""),
18981
+ required: isRequiredLabel(seg.label) || void 0,
18982
+ empty: true
18983
+ });
18877
18984
  }
18878
- );
18879
- return matches.length > 0 ? { text, matches } : null;
18880
- }
18881
- function normalizeValues(values) {
18882
- const map = /* @__PURE__ */ new Map();
18883
- for (const [label, value] of Object.entries(values)) {
18884
- map.set(normalizeLabel(label), value);
18885
18985
  }
18886
- return map;
18887
- }
18888
- function resolveUnmatched(normalizedValues, matchedLabels, originalValues) {
18889
- return [...normalizedValues.keys()].filter((k) => !matchedLabels.has(k)).map((k) => {
18890
- for (const orig of Object.keys(originalValues)) {
18891
- if (normalizeLabel(orig) === k) return orig;
18892
- }
18893
- return k;
18894
- });
18986
+ return { confidence, fields: schemaFields };
18895
18987
  }
18896
18988
 
18897
18989
  // src/form/filler.ts
@@ -18985,1579 +19077,1569 @@ function fillTable(table, values, filled, matchedLabels, patternFilledCells) {
18985
19077
  }
18986
19078
  }
18987
19079
  function fillInlineFields(text, values, filled, matchedLabels) {
18988
- return text.replace(
18989
- /([가-힣A-Za-z]{2,10})\s*[::]\s*([^\n,;]{0,100})/g,
18990
- (match, rawLabel, _oldValue) => {
18991
- const normalized = normalizeLabel(rawLabel);
18992
- const matchKey = findMatchingKey(normalized, values);
18993
- if (matchKey === void 0) return match;
18994
- const newValue = values.get(matchKey);
18995
- matchedLabels.add(matchKey);
18996
- filled.push({
18997
- label: rawLabel.trim(),
18998
- value: newValue,
18999
- row: -1,
19000
- col: -1
19001
- });
19002
- return `${rawLabel}: ${newValue}`;
19003
- }
19004
- );
19080
+ const segments = scanInlineSegments(text);
19081
+ if (segments.length === 0) return text;
19082
+ let out = "";
19083
+ let pos = 0;
19084
+ for (const seg of segments) {
19085
+ const matchKey = findMatchingKey(normalizeLabel(seg.label), values);
19086
+ if (matchKey === void 0) continue;
19087
+ const newValue = values.get(matchKey);
19088
+ matchedLabels.add(matchKey);
19089
+ filled.push({ label: seg.label.trim(), value: newValue, row: -1, col: -1 });
19090
+ out += text.slice(pos, seg.valueStart);
19091
+ out += seg.valueStart === seg.valueEnd ? padInsertion(text, seg.valueStart, newValue) : newValue;
19092
+ pos = seg.valueEnd;
19093
+ }
19094
+ out += text.slice(pos);
19095
+ return out;
19005
19096
  }
19006
19097
 
19007
19098
  // src/form/filler-hwpx.ts
19008
19099
  import JSZip5 from "jszip";
19009
- import { DOMParser as DOMParser5, XMLSerializer } from "@xmldom/xmldom";
19010
- async function fillHwpx(hwpxBuffer, values) {
19011
- const zip = await JSZip5.loadAsync(hwpxBuffer);
19012
- const filled = [];
19013
- const matchedLabels = /* @__PURE__ */ new Set();
19014
- const normalizedValues = normalizeValues(values);
19015
- const sectionFiles = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
19016
- if (sectionFiles.length === 0) {
19017
- throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
19018
- }
19019
- const xmlParser = new DOMParser5();
19020
- const xmlSerializer = new XMLSerializer();
19021
- for (const sectionPath of sectionFiles) {
19022
- const zipEntry = zip.file(sectionPath);
19023
- if (!zipEntry) continue;
19024
- const rawXml = await zipEntry.async("text");
19025
- const doc = xmlParser.parseFromString(stripDtd(rawXml), "text/xml");
19026
- if (!doc.documentElement) continue;
19027
- let modified = false;
19028
- const tables = findAllElements(doc.documentElement, "tbl");
19029
- const cellPatternApplied = /* @__PURE__ */ new Set();
19030
- for (const tblEl of tables) {
19031
- const allCells = findAllElements(tblEl, "tc");
19032
- for (const tcEl of allCells) {
19033
- const tNodes = collectCellTextNodes(tcEl);
19034
- const fullText = tNodes.map((n) => n.text).join("");
19035
- const result = fillInCellPatterns(fullText, normalizedValues, matchedLabels);
19036
- if (!result) continue;
19037
- applyTextReplacements(tNodes, fullText, result.text);
19038
- cellPatternApplied.add(tcEl);
19039
- for (const m of result.matches) {
19040
- filled.push({ label: m.label, value: m.value, row: -1, col: -1 });
19041
- }
19042
- modified = true;
19043
- }
19100
+
19101
+ // src/roundtrip/source-map.ts
19102
+ function escapeXmlText(text) {
19103
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
19104
+ }
19105
+ function decodeXmlEntities(text) {
19106
+ return text.replace(/&(lt|gt|amp|quot|apos|#x?[0-9a-fA-F]+);/g, (m, ent) => {
19107
+ switch (ent) {
19108
+ case "lt":
19109
+ return "<";
19110
+ case "gt":
19111
+ return ">";
19112
+ case "amp":
19113
+ return "&";
19114
+ case "quot":
19115
+ return '"';
19116
+ case "apos":
19117
+ return "'";
19044
19118
  }
19045
- for (const tblEl of tables) {
19046
- const rows = findDirectChildren(tblEl, "tr");
19047
- for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
19048
- const trEl = rows[rowIdx];
19049
- const cells = findDirectChildren(trEl, "tc");
19050
- for (let colIdx = 0; colIdx < cells.length - 1; colIdx++) {
19051
- const labelText = extractCellText2(cells[colIdx]);
19052
- if (!isLabelCell(labelText)) continue;
19053
- const valueCell = cells[colIdx + 1];
19054
- const valueText = extractCellText2(valueCell);
19055
- if (isKeywordLabel(valueText)) continue;
19056
- const normalizedCellLabel = normalizeLabel(labelText);
19057
- if (!normalizedCellLabel) continue;
19058
- const matchKey = findMatchingKey(normalizedCellLabel, normalizedValues);
19059
- if (matchKey === void 0) continue;
19060
- const newValue = normalizedValues.get(matchKey);
19061
- if (cellPatternApplied.has(valueCell)) {
19062
- prependCellText(valueCell, newValue);
19063
- } else {
19064
- replaceCellText(valueCell, newValue);
19065
- }
19066
- matchedLabels.add(matchKey);
19067
- filled.push({
19068
- label: labelText.trim().replace(/[::]\s*$/, ""),
19069
- value: newValue,
19070
- row: rowIdx,
19071
- col: colIdx
19072
- });
19073
- modified = true;
19074
- }
19075
- }
19076
- if (rows.length >= 2) {
19077
- const headerCells = findDirectChildren(rows[0], "tc");
19078
- const allLabels = headerCells.every((cell) => {
19079
- const t = extractCellText2(cell).trim();
19080
- return t.length > 0 && t.length <= 20 && isLabelCell(t);
19081
- });
19082
- if (allLabels) {
19083
- for (let rowIdx = 1; rowIdx < rows.length; rowIdx++) {
19084
- const dataCells = findDirectChildren(rows[rowIdx], "tc");
19085
- for (let colIdx = 0; colIdx < Math.min(headerCells.length, dataCells.length); colIdx++) {
19086
- const headerLabel = normalizeLabel(extractCellText2(headerCells[colIdx]));
19087
- const matchKey = findMatchingKey(headerLabel, normalizedValues);
19088
- if (matchKey === void 0) continue;
19089
- if (matchedLabels.has(matchKey)) continue;
19090
- const newValue = normalizedValues.get(matchKey);
19091
- replaceCellText(dataCells[colIdx], newValue);
19092
- matchedLabels.add(matchKey);
19093
- filled.push({
19094
- label: extractCellText2(headerCells[colIdx]).trim(),
19095
- value: newValue,
19096
- row: rowIdx,
19097
- col: colIdx
19098
- });
19099
- modified = true;
19100
- }
19101
- }
19102
- }
19103
- }
19104
- }
19105
- const allParagraphs = findAllElements(doc.documentElement, "p");
19106
- for (const pEl of allParagraphs) {
19107
- if (isInsideTable(pEl)) continue;
19108
- const tNodes = collectTextNodes(pEl);
19109
- const fullText = tNodes.map((n) => n.text).join("");
19110
- const pattern = /([가-힣A-Za-z]{2,10})\s*[::]\s*([^\n,;]{0,100})/g;
19111
- let match;
19112
- while ((match = pattern.exec(fullText)) !== null) {
19113
- const rawLabel = match[1];
19114
- const normalized = normalizeLabel(rawLabel);
19115
- const matchKey = findMatchingKey(normalized, normalizedValues);
19116
- if (matchKey === void 0) continue;
19117
- const newValue = normalizedValues.get(matchKey);
19118
- const valueStart = match.index + match[0].length - match[2].length;
19119
- const valueEnd = match.index + match[0].length;
19120
- replaceTextRange(tNodes, valueStart, valueEnd, newValue);
19121
- matchedLabels.add(matchKey);
19122
- filled.push({ label: rawLabel.trim(), value: newValue, row: -1, col: -1 });
19123
- modified = true;
19124
- break;
19125
- }
19126
- }
19127
- if (modified) {
19128
- const newXml = xmlSerializer.serializeToString(doc);
19129
- zip.file(sectionPath, newXml);
19119
+ try {
19120
+ const code = ent[1] === "x" || ent[1] === "X" ? parseInt(ent.slice(2), 16) : parseInt(ent.slice(1), 10);
19121
+ if (!isNaN(code) && code >= 0 && code <= 1114111) return String.fromCodePoint(code);
19122
+ } catch {
19130
19123
  }
19131
- }
19132
- const unmatched = resolveUnmatched(normalizedValues, matchedLabels, values);
19133
- const buffer = await zip.generateAsync({ type: "arraybuffer" });
19134
- return { buffer, filled, unmatched };
19135
- }
19136
- function localName2(el) {
19137
- return (el.tagName || el.localName || "").replace(/^[^:]+:/, "");
19124
+ return m;
19125
+ });
19138
19126
  }
19139
- function findAllElements(node, tagLocalName) {
19140
- const result = [];
19141
- const walk = (n) => {
19142
- const children = n.childNodes;
19143
- if (!children) return;
19144
- for (let i = 0; i < children.length; i++) {
19145
- const child = children[i];
19146
- if (child.nodeType !== 1) continue;
19147
- if (localName2(child) === tagLocalName) result.push(child);
19148
- walk(child);
19149
- }
19150
- };
19151
- walk(node);
19152
- return result;
19127
+ function tContentToText(raw) {
19128
+ return decodeXmlEntities(
19129
+ raw.replace(/<\/?(?:[A-Za-z0-9_]+:)?(?:tab|fwSpace|hwSpace|br|lineBreak)(?:\s[^>]*)?\/?>/g, " ").replace(/<[^>]*>/g, "")
19130
+ );
19153
19131
  }
19154
- function findDirectChildren(parent, tagLocalName) {
19155
- const result = [];
19156
- const children = parent.childNodes;
19157
- if (!children) return result;
19158
- for (let i = 0; i < children.length; i++) {
19159
- const child = children[i];
19160
- if (child.nodeType === 1 && localName2(child) === tagLocalName) {
19161
- result.push(child);
19162
- }
19163
- }
19164
- return result;
19132
+ var TAG_RE = /<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<\?[\s\S]*?\?>|<!(?:"[^"]*"|'[^']*'|[^>"'])*>|<\/([^\s>]+)\s*>|<([^\s/>!?]+)((?:"[^"]*"|'[^']*'|[^>"'])*?)(\/?)>/g;
19133
+ var T_BARRIER = /* @__PURE__ */ new Set([
19134
+ "tbl",
19135
+ "ctrl",
19136
+ "caption",
19137
+ "pic",
19138
+ "shape",
19139
+ "drawingObject",
19140
+ "drawText",
19141
+ "shapeComment",
19142
+ "memogroup",
19143
+ "memo",
19144
+ "hiddenComment",
19145
+ "equation",
19146
+ "parameters",
19147
+ "subList",
19148
+ "p"
19149
+ ]);
19150
+ var PARA_CONTAINER = /* @__PURE__ */ new Set([
19151
+ "tc",
19152
+ "ctrl",
19153
+ "caption",
19154
+ "drawText",
19155
+ "pic",
19156
+ "shape",
19157
+ "drawingObject",
19158
+ "memogroup",
19159
+ "memo",
19160
+ "hiddenComment",
19161
+ "footNote",
19162
+ "endNote",
19163
+ "fn",
19164
+ "en"
19165
+ // 각주/미주 — 파서는 호스트 블록 footnoteText로만 흡수
19166
+ ]);
19167
+ var TABLE_BARRIER = /* @__PURE__ */ new Set([
19168
+ "tbl",
19169
+ "ctrl",
19170
+ "caption",
19171
+ "memogroup",
19172
+ "memo",
19173
+ "hiddenComment"
19174
+ ]);
19175
+ function localOf(qname) {
19176
+ const i = qname.indexOf(":");
19177
+ return i >= 0 ? qname.slice(i + 1) : qname;
19165
19178
  }
19166
- function isInsideTable(el) {
19167
- let parent = el.parentNode;
19168
- while (parent) {
19169
- if (parent.nodeType === 1 && localName2(parent) === "tbl") return true;
19170
- parent = parent.parentNode;
19171
- }
19172
- return false;
19179
+ function prefixOf(qname) {
19180
+ const i = qname.indexOf(":");
19181
+ return i >= 0 ? qname.slice(0, i) : "";
19173
19182
  }
19174
- function extractCellText2(tcEl) {
19175
- const parts = [];
19176
- const walk = (node) => {
19177
- const children = node.childNodes;
19178
- if (!children) return;
19179
- for (let i = 0; i < children.length; i++) {
19180
- const child = children[i];
19181
- if (child.nodeType === 3) {
19182
- parts.push(child.textContent || "");
19183
- } else if (child.nodeType === 1) {
19184
- const tag = localName2(child);
19185
- if (tag === "t") walk(child);
19186
- else if (tag === "run" || tag === "r" || tag === "p" || tag === "subList") walk(child);
19187
- else if (tag === "tab") parts.push(" ");
19188
- else if (tag === "br") parts.push("\n");
19183
+ function scanSectionXml(xml, sectionIndex) {
19184
+ const stack = [];
19185
+ const bodyParagraphs = [];
19186
+ const tables = [];
19187
+ const headerTexts = [];
19188
+ const footerTexts = [];
19189
+ const excludedParagraphs = [];
19190
+ const orphanTables = [];
19191
+ const paraStack = [];
19192
+ const tableStack = [];
19193
+ const rowStack = [];
19194
+ const cellStack = [];
19195
+ let pendingT = null;
19196
+ const ctrlSubStack = [];
19197
+ const classifyPara = () => {
19198
+ let sawDrawText = false;
19199
+ for (let i = stack.length - 1; i >= 0; i--) {
19200
+ const l = stack[i].local;
19201
+ if (l === "tc") return { kind: "cell", inTextbox: sawDrawText };
19202
+ if (l === "drawText") {
19203
+ sawDrawText = true;
19204
+ continue;
19189
19205
  }
19206
+ if (PARA_CONTAINER.has(l)) return { kind: "excluded", inTextbox: sawDrawText };
19190
19207
  }
19208
+ return sawDrawText ? { kind: "draw", inTextbox: true } : { kind: "body", inTextbox: false };
19191
19209
  };
19192
- walk(tcEl);
19193
- return parts.join("");
19194
- }
19195
- function prependCellText(tcEl, text) {
19196
- const tElements = findAllElements(tcEl, "t");
19197
- if (tElements.length === 0) return;
19198
- const firstT = tElements[0];
19199
- const existing = firstT.textContent || "";
19200
- clearChildren(firstT);
19201
- firstT.appendChild(firstT.ownerDocument.createTextNode(text + " " + existing));
19202
- }
19203
- function replaceCellText(tcEl, newValue) {
19204
- const paragraphs = findAllElements(tcEl, "p");
19205
- if (paragraphs.length === 0) return;
19206
- const firstP = paragraphs[0];
19207
- const runs = findAllElements(firstP, "run").concat(findAllElements(firstP, "r"));
19208
- if (runs.length > 0) {
19209
- setRunText(runs[0], newValue);
19210
- for (let i = 1; i < runs.length; i++) {
19211
- setRunText(runs[i], "");
19210
+ const owningPara = () => {
19211
+ if (paraStack.length === 0) return null;
19212
+ for (let i = stack.length - 1; i >= 0; i--) {
19213
+ const l = stack[i].local;
19214
+ if (l === "p") return paraStack[paraStack.length - 1];
19215
+ if (T_BARRIER.has(l)) return null;
19212
19216
  }
19213
- } else {
19214
- const tElements = findAllElements(firstP, "t");
19215
- if (tElements.length > 0) {
19216
- clearChildren(tElements[0]);
19217
- tElements[0].appendChild(tElements[0].ownerDocument.createTextNode(newValue));
19218
- for (let i = 1; i < tElements.length; i++) {
19219
- clearChildren(tElements[i]);
19220
- }
19217
+ return null;
19218
+ };
19219
+ const isTableTopLevel = () => {
19220
+ for (let i = stack.length - 1; i >= 0; i--) {
19221
+ if (TABLE_BARRIER.has(stack[i].local)) return false;
19221
19222
  }
19222
- }
19223
- for (let i = 1; i < paragraphs.length; i++) {
19224
- const p = paragraphs[i];
19225
- if (p.parentNode) {
19226
- const pRuns = findAllElements(p, "run").concat(findAllElements(p, "r"));
19227
- for (const run of pRuns) setRunText(run, "");
19228
- const pTs = findAllElements(p, "t");
19229
- for (const t of pTs) clearChildren(t);
19223
+ return true;
19224
+ };
19225
+ const currentCtrlSub = () => ctrlSubStack.length > 0 ? ctrlSubStack[ctrlSubStack.length - 1] : null;
19226
+ TAG_RE.lastIndex = 0;
19227
+ let m;
19228
+ while ((m = TAG_RE.exec(xml)) !== null) {
19229
+ const [full, closeName, openName, , selfClose] = m;
19230
+ if (closeName === void 0 && openName === void 0) continue;
19231
+ if (closeName !== void 0) {
19232
+ const local2 = localOf(closeName);
19233
+ if (local2 === "t" && pendingT) {
19234
+ const { para, contentStart: contentStart2 } = pendingT;
19235
+ para.tRanges.push({ contentStart: contentStart2, contentEnd: m.index });
19236
+ para.text += tContentToText(xml.slice(contentStart2, m.index));
19237
+ pendingT = null;
19238
+ }
19239
+ for (let i = stack.length - 1; i >= 0; i--) {
19240
+ if (stack[i].local === local2) {
19241
+ stack.length = i;
19242
+ break;
19243
+ }
19244
+ }
19245
+ if (local2 === "p") {
19246
+ const para = paraStack.pop();
19247
+ if (para && para.kind === "excluded") {
19248
+ const sub = currentCtrlSub();
19249
+ if (sub && para.text.trim()) sub.texts.push(para.text);
19250
+ }
19251
+ } else if (local2 === "tc") {
19252
+ const cell = cellStack.pop();
19253
+ const row = rowStack[rowStack.length - 1];
19254
+ if (cell && row) row.push(cell);
19255
+ } else if (local2 === "tr") {
19256
+ const row = rowStack[rowStack.length - 1];
19257
+ const table = tableStack[tableStack.length - 1];
19258
+ if (row && table && row.length > 0) table.rows.push(row);
19259
+ if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
19260
+ } else if (local2 === "tbl") {
19261
+ const table = tableStack.pop();
19262
+ rowStack.pop();
19263
+ if (table) {
19264
+ finalizeTable(table);
19265
+ if (!table.topLevel) {
19266
+ const cell = cellStack[cellStack.length - 1];
19267
+ if (cell) cell.tables.push(table);
19268
+ else orphanTables.push(table);
19269
+ }
19270
+ }
19271
+ } else if (local2 === "header" || local2 === "footer") {
19272
+ const sub = ctrlSubStack[ctrlSubStack.length - 1];
19273
+ if (sub) {
19274
+ ctrlSubStack.pop();
19275
+ const joined = sub.texts.join("\n").trim();
19276
+ if (joined) (sub.kind === "header" ? headerTexts : footerTexts).push(joined);
19277
+ }
19278
+ }
19279
+ continue;
19230
19280
  }
19231
- }
19232
- }
19233
- function setRunText(runEl, text) {
19234
- const tElements = findAllElements(runEl, "t");
19235
- if (tElements.length > 0) {
19236
- clearChildren(tElements[0]);
19237
- tElements[0].appendChild(tElements[0].ownerDocument.createTextNode(text));
19238
- for (let i = 1; i < tElements.length; i++) {
19239
- clearChildren(tElements[i]);
19281
+ const qname = openName;
19282
+ const local = localOf(qname);
19283
+ const attrsRaw = m[3] || "";
19284
+ const isSelfClose = selfClose === "/";
19285
+ const contentStart = m.index + full.length;
19286
+ if (isSelfClose) {
19287
+ if (local === "t") {
19288
+ const para = owningPara();
19289
+ if (para) para.tRanges.push({ contentStart: m.index, contentEnd: m.index + full.length, selfClosing: true, prefix: prefixOf(qname) });
19290
+ } else if (local === "tab" || local === "fwSpace" || local === "hwSpace" || local === "br" || local === "lineBreak") {
19291
+ if (!pendingT) {
19292
+ const para = owningPara();
19293
+ if (para) para.text += " ";
19294
+ }
19295
+ } else if (local === "run" || local === "r") {
19296
+ const para = owningPara();
19297
+ if (para && !para.selfCloseRun) para.selfCloseRun = { start: m.index, end: m.index + full.length };
19298
+ } else if (local === "cellAddr") {
19299
+ const cell = cellStack[cellStack.length - 1];
19300
+ if (cell && insideCurrentTable(stack, tableStack)) {
19301
+ const ca = parseInt(getAttr2(attrsRaw, "colAddr") || "", 10);
19302
+ const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
19303
+ if (!isNaN(ca)) cell.colAddr = ca;
19304
+ if (!isNaN(ra)) cell.rowAddr = ra;
19305
+ }
19306
+ } else if (local === "cellSpan") {
19307
+ const cell = cellStack[cellStack.length - 1];
19308
+ if (cell && insideCurrentTable(stack, tableStack)) {
19309
+ const cs = parseInt(getAttr2(attrsRaw, "colSpan") || "1", 10);
19310
+ const rs = parseInt(getAttr2(attrsRaw, "rowSpan") || "1", 10);
19311
+ cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs;
19312
+ cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs;
19313
+ }
19314
+ }
19315
+ continue;
19240
19316
  }
19241
- return;
19242
- }
19243
- if (!text) return;
19244
- const doc = runEl.ownerDocument;
19245
- const ns = runEl.namespaceURI;
19246
- const qualifiedName = runEl.prefix ? `${runEl.prefix}:t` : "t";
19247
- const tEl = ns ? doc.createElementNS(ns, qualifiedName) : doc.createElement(qualifiedName);
19248
- tEl.appendChild(doc.createTextNode(text));
19249
- runEl.appendChild(tEl);
19250
- }
19251
- function clearChildren(el) {
19252
- while (el.firstChild) el.removeChild(el.firstChild);
19253
- }
19254
- function collectTextNodes(pEl) {
19255
- const tElements = findAllElements(pEl, "t");
19256
- const result = [];
19257
- let offset = 0;
19258
- for (const t of tElements) {
19259
- const text = t.textContent || "";
19260
- result.push({ element: t, text, offset });
19261
- offset += text.length;
19262
- }
19263
- return result;
19264
- }
19265
- function replaceTextRange(tNodes, globalStart, globalEnd, newValue) {
19266
- let replaced = false;
19267
- for (const node of tNodes) {
19268
- const nodeStart = node.offset;
19269
- const nodeEnd = node.offset + node.text.length;
19270
- if (nodeEnd <= globalStart || nodeStart >= globalEnd) continue;
19271
- const localStart = Math.max(0, globalStart - nodeStart);
19272
- const localEnd = Math.min(node.text.length, globalEnd - nodeStart);
19273
- if (!replaced) {
19274
- const before = node.text.slice(0, localStart);
19275
- const after = node.text.slice(localEnd);
19276
- const newText = before + newValue + after;
19277
- clearChildren(node.element);
19278
- node.element.appendChild(node.element.ownerDocument.createTextNode(newText));
19279
- replaced = true;
19280
- } else {
19281
- const before = node.text.slice(0, localStart);
19282
- const after = node.text.slice(localEnd);
19283
- const newText = before + after;
19284
- clearChildren(node.element);
19285
- node.element.appendChild(node.element.ownerDocument.createTextNode(newText));
19317
+ if (local === "t") {
19318
+ const para = owningPara();
19319
+ if (para) pendingT = { para, contentStart };
19320
+ stack.push({ local, qname, contentStart });
19321
+ continue;
19322
+ }
19323
+ stack.push({ local, qname, contentStart });
19324
+ if (local === "p") {
19325
+ const para = {
19326
+ sectionIndex,
19327
+ kind: "excluded",
19328
+ // 분류는 push 직후 스택 기준 (자기 자신 제외)
19329
+ start: m.index,
19330
+ tRanges: [],
19331
+ text: ""
19332
+ };
19333
+ stack.pop();
19334
+ const cls = classifyPara();
19335
+ para.kind = cls.kind;
19336
+ if (cls.inTextbox) para.inTextbox = true;
19337
+ stack.push({ local, qname, contentStart });
19338
+ paraStack.push(para);
19339
+ if (para.kind === "body" || para.kind === "draw") bodyParagraphs.push(para);
19340
+ else if (para.kind === "cell") {
19341
+ const cell = cellStack[cellStack.length - 1];
19342
+ if (cell) cell.paragraphs.push(para);
19343
+ } else if (para.kind === "excluded") {
19344
+ excludedParagraphs.push(para);
19345
+ }
19346
+ } else if (local === "run" || local === "r") {
19347
+ const para = owningPara();
19348
+ if (para && para.runPrefix === void 0) para.runPrefix = prefixOf(qname);
19349
+ } else if (local === "tbl") {
19350
+ const table = {
19351
+ sectionIndex,
19352
+ start: m.index,
19353
+ topLevel: false,
19354
+ rows: [],
19355
+ cellByAnchor: /* @__PURE__ */ new Map()
19356
+ };
19357
+ stack.pop();
19358
+ table.topLevel = isTableTopLevel();
19359
+ stack.push({ local, qname, contentStart });
19360
+ tableStack.push(table);
19361
+ rowStack.push([]);
19362
+ if (table.topLevel) tables.push(table);
19363
+ } else if (local === "tr") {
19364
+ if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
19365
+ } else if (local === "tc") {
19366
+ cellStack.push({ colSpan: 1, rowSpan: 1, paragraphs: [], tables: [] });
19367
+ } else if (local === "cellAddr" || local === "cellSpan") {
19368
+ const cell = cellStack[cellStack.length - 1];
19369
+ if (cell && insideCurrentTable(stack, tableStack)) {
19370
+ if (local === "cellAddr") {
19371
+ const ca = parseInt(getAttr2(attrsRaw, "colAddr") || "", 10);
19372
+ const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
19373
+ if (!isNaN(ca)) cell.colAddr = ca;
19374
+ if (!isNaN(ra)) cell.rowAddr = ra;
19375
+ } else {
19376
+ const cs = parseInt(getAttr2(attrsRaw, "colSpan") || "1", 10);
19377
+ const rs = parseInt(getAttr2(attrsRaw, "rowSpan") || "1", 10);
19378
+ cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs;
19379
+ cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs;
19380
+ }
19381
+ }
19382
+ } else if (local === "header" || local === "footer") {
19383
+ if (stack.some((f) => f.local === "ctrl")) {
19384
+ ctrlSubStack.push({ kind: local, texts: [] });
19385
+ }
19386
+ } else if (local === "tab" || local === "fwSpace" || local === "hwSpace" || local === "br" || local === "lineBreak") {
19387
+ const para = owningPara();
19388
+ if (para) para.text += " ";
19286
19389
  }
19287
19390
  }
19288
- }
19289
- function collectCellTextNodes(tcEl) {
19290
- const tElements = findAllElements(tcEl, "t");
19291
- const result = [];
19292
- let offset = 0;
19293
- for (const t of tElements) {
19294
- const text = t.textContent || "";
19295
- result.push({ element: t, text, offset });
19296
- offset += text.length;
19297
- }
19298
- return result;
19299
- }
19300
- function applyTextReplacements(tNodes, originalFull, replacedFull) {
19301
- if (originalFull === replacedFull) return;
19302
- if (tNodes.length === 1) {
19303
- clearChildren(tNodes[0].element);
19304
- tNodes[0].element.appendChild(
19305
- tNodes[0].element.ownerDocument.createTextNode(replacedFull)
19306
- );
19307
- return;
19308
- }
19309
- let diffStart = 0;
19310
- while (diffStart < originalFull.length && diffStart < replacedFull.length && originalFull[diffStart] === replacedFull[diffStart]) {
19311
- diffStart++;
19312
- }
19313
- let diffEndOrig = originalFull.length;
19314
- let diffEndRepl = replacedFull.length;
19315
- while (diffEndOrig > diffStart && diffEndRepl > diffStart && originalFull[diffEndOrig - 1] === replacedFull[diffEndRepl - 1]) {
19316
- diffEndOrig--;
19317
- diffEndRepl--;
19318
- }
19319
- const newPart = replacedFull.slice(diffStart, diffEndRepl);
19320
- replaceTextRange(tNodes, diffStart, diffEndOrig, newPart);
19321
- }
19322
-
19323
- // src/hwpx/generator.ts
19324
- import JSZip6 from "jszip";
19325
- var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
19326
- var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
19327
- var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
19328
- var NS_OPF = "http://www.idpf.org/2007/opf/";
19329
- var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
19330
- var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
19331
- var CHAR_NORMAL = 0;
19332
- var CHAR_BOLD = 1;
19333
- var CHAR_ITALIC = 2;
19334
- var CHAR_BOLD_ITALIC = 3;
19335
- var CHAR_CODE = 4;
19336
- var CHAR_H1 = 5;
19337
- var CHAR_H2 = 6;
19338
- var CHAR_H3 = 7;
19339
- var CHAR_H4 = 8;
19340
- var CHAR_TABLE_HEADER = 9;
19341
- var CHAR_QUOTE = 10;
19342
- var PARA_NORMAL = 0;
19343
- var PARA_H1 = 1;
19344
- var PARA_H2 = 2;
19345
- var PARA_H3 = 3;
19346
- var PARA_H4 = 4;
19347
- var PARA_CODE = 5;
19348
- var PARA_QUOTE = 6;
19349
- var PARA_LIST = 7;
19350
- var DEFAULT_TEXT_COLOR = "#000000";
19351
- function resolveTheme(theme) {
19352
- return {
19353
- h1: theme?.headingColors?.[1] ?? DEFAULT_TEXT_COLOR,
19354
- h2: theme?.headingColors?.[2] ?? DEFAULT_TEXT_COLOR,
19355
- h3: theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
19356
- h4: theme?.headingColors?.[4] ?? theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
19357
- body: theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
19358
- quote: theme?.quoteColor ?? DEFAULT_TEXT_COLOR,
19359
- /** quoteColor가 명시되었는지 — blockquote charPr 분기에 사용 (baseline 호환) */
19360
- hasQuoteOption: theme?.quoteColor !== void 0,
19361
- tableHeader: theme?.tableHeaderColor ?? theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
19362
- tableHeaderBold: !!theme?.tableHeaderBold
19391
+ for (const para of bodyParagraphs) fillRunInsertPos(para, xml);
19392
+ for (const para of excludedParagraphs) fillRunInsertPos(para, xml);
19393
+ const fillTableInsertPos = (table, depth = 0) => {
19394
+ if (depth > 16) return;
19395
+ for (const row of table.rows) {
19396
+ for (const cell of row) {
19397
+ for (const para of cell.paragraphs) fillRunInsertPos(para, xml);
19398
+ for (const nested of cell.tables) fillTableInsertPos(nested, depth + 1);
19399
+ }
19400
+ }
19363
19401
  };
19402
+ for (const table of tables) fillTableInsertPos(table);
19403
+ for (const table of orphanTables) fillTableInsertPos(table);
19404
+ return { sectionIndex, xml, bodyParagraphs, tables, headerTexts, footerTexts, excludedParagraphs, orphanTables };
19364
19405
  }
19365
- async function markdownToHwpx(markdown, options) {
19366
- const theme = resolveTheme(options?.theme);
19367
- const blocks = parseMarkdownToBlocks(markdown);
19368
- const sectionXml = blocksToSectionXml(blocks, theme);
19369
- const zip = new JSZip6();
19370
- zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
19371
- zip.file("META-INF/container.xml", generateContainerXml());
19372
- zip.file("Contents/content.hpf", generateManifest());
19373
- zip.file("Contents/header.xml", generateHeaderXml(theme));
19374
- zip.file("Contents/section0.xml", sectionXml);
19375
- zip.file("Preview/PrvText.txt", buildPrvText(blocks));
19376
- return await zip.generateAsync({ type: "arraybuffer" });
19406
+ function getAttr2(attrsRaw, name) {
19407
+ const re = new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`);
19408
+ const m = attrsRaw.match(re);
19409
+ return m ? m[1] ?? m[2] : void 0;
19377
19410
  }
19378
- function buildPrvText(blocks) {
19379
- const lines = [];
19380
- let bytes = 0;
19381
- for (const b of blocks) {
19382
- const text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
19383
- if (!text) continue;
19384
- lines.push(text);
19385
- bytes += text.length * 3;
19386
- if (bytes > 1024) break;
19411
+ function insideCurrentTable(stack, tableStack) {
19412
+ if (tableStack.length === 0) return false;
19413
+ for (let i = stack.length - 1; i >= 0; i--) {
19414
+ const l = stack[i].local;
19415
+ if (l === "tc") return true;
19416
+ if (l === "tbl") return false;
19387
19417
  }
19388
- return lines.join("\n").slice(0, 1024);
19418
+ return false;
19389
19419
  }
19390
- function parseMarkdownToBlocks(md2) {
19391
- const lines = md2.split("\n");
19392
- const blocks = [];
19393
- let i = 0;
19394
- while (i < lines.length) {
19395
- const line = lines[i];
19396
- if (!line.trim()) {
19397
- i++;
19398
- continue;
19420
+ function fillRunInsertPos(para, xml) {
19421
+ if (para.tRanges.length > 0) return;
19422
+ const pEnd = findElementEnd(xml, para.start);
19423
+ if (pEnd < 0) return;
19424
+ const slice = xml.slice(para.start, pEnd);
19425
+ const runOpen = slice.match(/<((?:[A-Za-z0-9_]+:)?run)(?:\s(?:"[^"]*"|'[^']*'|[^>"'])*?)?(\/?)>/);
19426
+ if (!runOpen || runOpen.index === void 0) return;
19427
+ if (runOpen[2] === "/") return;
19428
+ const qname = runOpen[1];
19429
+ const closeIdx = slice.indexOf(`</${qname}>`, runOpen.index);
19430
+ if (closeIdx < 0) return;
19431
+ para.runInsertPos = para.start + closeIdx;
19432
+ para.runPrefix = prefixOf(qname);
19433
+ }
19434
+ function findElementEnd(xml, start) {
19435
+ const open = xml.slice(start).match(/^<([^\s/>!?]+)/);
19436
+ if (!open) return -1;
19437
+ const qname = open[1];
19438
+ const re = new RegExp(`<${qname}(?=[\\s/>])(?:"[^"]*"|'[^']*'|[^>"'])*?(/?)>|</${qname}\\s*>`, "g");
19439
+ re.lastIndex = start;
19440
+ let depth = 0;
19441
+ let mm;
19442
+ while ((mm = re.exec(xml)) !== null) {
19443
+ if (mm[0].startsWith("</")) {
19444
+ depth--;
19445
+ if (depth === 0) return mm.index + mm[0].length;
19446
+ } else if (mm[1] !== "/") {
19447
+ depth++;
19399
19448
  }
19400
- const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
19401
- if (fenceMatch) {
19402
- const fence = fenceMatch[1];
19403
- const lang = fenceMatch[2].trim();
19404
- const codeLines = [];
19405
- i++;
19406
- while (i < lines.length && !lines[i].startsWith(fence)) {
19407
- codeLines.push(lines[i]);
19408
- i++;
19449
+ }
19450
+ return -1;
19451
+ }
19452
+ function finalizeTable(table) {
19453
+ const hasAddr = table.rows.some((row) => row.some((c) => c.colAddr !== void 0 && c.rowAddr !== void 0));
19454
+ if (hasAddr) {
19455
+ for (const row of table.rows) {
19456
+ for (const cell of row) {
19457
+ if (cell.rowAddr !== void 0 && cell.colAddr !== void 0) {
19458
+ table.cellByAnchor.set(`${cell.rowAddr},${cell.colAddr}`, cell);
19459
+ }
19409
19460
  }
19410
- if (i < lines.length) i++;
19411
- blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
19412
- continue;
19413
- }
19414
- if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
19415
- blocks.push({ type: "hr" });
19416
- i++;
19417
- continue;
19418
19461
  }
19419
- const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
19420
- if (headingMatch) {
19421
- blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
19422
- i++;
19423
- continue;
19424
- }
19425
- if (line.trimStart().startsWith("|")) {
19426
- const tableRows = [];
19427
- while (i < lines.length && lines[i].trimStart().startsWith("|")) {
19428
- const row = lines[i];
19429
- if (/^[\s|:\-]+$/.test(row)) {
19430
- i++;
19431
- continue;
19462
+ return;
19463
+ }
19464
+ const numRows = table.rows.length;
19465
+ const occupied = Array.from({ length: numRows }, () => []);
19466
+ for (let rowIdx = 0; rowIdx < numRows; rowIdx++) {
19467
+ let colIdx = 0;
19468
+ for (const cell of table.rows[rowIdx]) {
19469
+ while (occupied[rowIdx][colIdx]) colIdx++;
19470
+ cell.rowAddr = rowIdx;
19471
+ cell.colAddr = colIdx;
19472
+ table.cellByAnchor.set(`${rowIdx},${colIdx}`, cell);
19473
+ for (let r = rowIdx; r < Math.min(rowIdx + cell.rowSpan, numRows); r++) {
19474
+ for (let c = colIdx; c < colIdx + cell.colSpan; c++) {
19475
+ occupied[r][c] = true;
19432
19476
  }
19433
- const cells = row.split("|").slice(1, -1).map((c) => c.trim());
19434
- if (cells.length > 0) tableRows.push(cells);
19435
- i++;
19436
19477
  }
19437
- if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
19438
- continue;
19478
+ colIdx += cell.colSpan;
19439
19479
  }
19440
- if (line.trimStart().startsWith("> ")) {
19441
- const quoteLines = [];
19442
- while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
19443
- quoteLines.push(lines[i].replace(/^>\s?/, ""));
19444
- i++;
19445
- }
19446
- for (const ql of quoteLines) {
19447
- blocks.push({ type: "blockquote", text: ql.trim() || "" });
19448
- }
19449
- continue;
19450
- }
19451
- const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
19452
- if (listMatch) {
19453
- const indent = Math.floor(listMatch[1].length / 2);
19454
- const ordered = /\d/.test(listMatch[2]);
19455
- blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
19456
- i++;
19457
- continue;
19458
- }
19459
- blocks.push({ type: "paragraph", text: line.trim() });
19460
- i++;
19461
19480
  }
19462
- return blocks;
19463
19481
  }
19464
- function parseInlineMarkdown(text) {
19465
- text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
19466
- text = text.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_, t, u) => t || u);
19467
- text = text.replace(/~~([^~]+)~~/g, "$1");
19468
- const spans = [];
19469
- const regex = /(`[^`]+`|\*{3}[^*]+\*{3}|\*{2}[^*]+\*{2}|\*[^*]+\*|_{2}[^_]+_{2}|_[^_]+_)/g;
19470
- let lastIdx = 0;
19471
- for (const match of text.matchAll(regex)) {
19472
- const idx = match.index;
19473
- if (idx > lastIdx) {
19474
- spans.push({ text: text.slice(lastIdx, idx), bold: false, italic: false, code: false });
19482
+ function buildParagraphSplices(para, newText, xml) {
19483
+ if (newText && xml) {
19484
+ const orig = paraTText(para, xml);
19485
+ if (orig && orig.trim() !== "") {
19486
+ const lead = orig.match(/^\s*/)[0];
19487
+ const trail = orig.match(/\s*$/)[0];
19488
+ if ((lead || trail) && newText.trim() !== "") {
19489
+ newText = lead + newText.replace(/^\s+|\s+$/g, "") + trail;
19490
+ }
19475
19491
  }
19476
- const raw = match[0];
19477
- if (raw.startsWith("`")) {
19478
- spans.push({ text: raw.slice(1, -1), bold: false, italic: false, code: true });
19479
- } else if (raw.startsWith("***") || raw.startsWith("___")) {
19480
- spans.push({ text: raw.slice(3, -3), bold: true, italic: true, code: false });
19481
- } else if (raw.startsWith("**") || raw.startsWith("__")) {
19482
- spans.push({ text: raw.slice(2, -2), bold: true, italic: false, code: false });
19492
+ }
19493
+ const escaped = escapeXmlText(newText);
19494
+ if (para.tRanges.length > 0) {
19495
+ const splices = [];
19496
+ const first = para.tRanges[0];
19497
+ if (first.selfClosing) {
19498
+ const prefix = first.prefix ? first.prefix + ":" : "";
19499
+ splices.push({ start: first.contentStart, end: first.contentEnd, replacement: `<${prefix}t>${escaped}</${prefix}t>` });
19483
19500
  } else {
19484
- spans.push({ text: raw.slice(1, -1), bold: false, italic: true, code: false });
19501
+ splices.push({ start: first.contentStart, end: first.contentEnd, replacement: escaped });
19485
19502
  }
19486
- lastIdx = idx + raw.length;
19503
+ for (let i = 1; i < para.tRanges.length; i++) {
19504
+ const r = para.tRanges[i];
19505
+ if (!r.selfClosing && r.contentStart < r.contentEnd) {
19506
+ splices.push({ start: r.contentStart, end: r.contentEnd, replacement: "" });
19507
+ }
19508
+ }
19509
+ return splices;
19487
19510
  }
19488
- if (lastIdx < text.length) {
19489
- spans.push({ text: text.slice(lastIdx), bold: false, italic: false, code: false });
19511
+ if (para.runInsertPos !== void 0) {
19512
+ if (!newText) return [];
19513
+ const prefix = para.runPrefix ? para.runPrefix + ":" : "";
19514
+ return [{ start: para.runInsertPos, end: para.runInsertPos, replacement: `<${prefix}t>${escaped}</${prefix}t>` }];
19490
19515
  }
19491
- if (spans.length === 0) {
19492
- spans.push({ text, bold: false, italic: false, code: false });
19516
+ if (para.selfCloseRun && xml) {
19517
+ if (!newText) return [];
19518
+ const { start, end } = para.selfCloseRun;
19519
+ const tag = xml.slice(start, end);
19520
+ const qm = tag.match(/^<([^\s/>]+)/);
19521
+ if (!qm || !tag.endsWith("/>")) return null;
19522
+ const qname = qm[1];
19523
+ const colon = qname.indexOf(":");
19524
+ const prefix = colon >= 0 ? qname.slice(0, colon) + ":" : "";
19525
+ const opened = tag.slice(0, tag.length - 2).trimEnd() + ">";
19526
+ return [{ start, end, replacement: `${opened}<${prefix}t>${escaped}</${prefix}t></${qname}>` }];
19493
19527
  }
19494
- return spans;
19495
- }
19496
- function spanToCharPrId(span) {
19497
- if (span.code) return CHAR_CODE;
19498
- if (span.bold && span.italic) return CHAR_BOLD_ITALIC;
19499
- if (span.bold) return CHAR_BOLD;
19500
- if (span.italic) return CHAR_ITALIC;
19501
- return CHAR_NORMAL;
19502
- }
19503
- function escapeXml(text) {
19504
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
19505
- }
19506
- function generateRuns(text, defaultCharPr = CHAR_NORMAL) {
19507
- const spans = parseInlineMarkdown(text);
19508
- return spans.map((span) => {
19509
- const charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
19510
- return `<hp:run charPrIDRef="${charId}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`;
19511
- }).join("");
19528
+ return newText ? null : [];
19512
19529
  }
19513
- function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL) {
19514
- if (paraPrId === PARA_CODE) {
19515
- return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
19530
+ function paraTText(para, xml) {
19531
+ let text = "";
19532
+ for (const t of para.tRanges) {
19533
+ if (t.selfClosing) continue;
19534
+ const raw = xml.slice(t.contentStart, t.contentEnd);
19535
+ if (/[<&]/.test(raw)) return null;
19536
+ text += raw;
19516
19537
  }
19517
- const runs = generateRuns(text, charPrId);
19518
- return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
19538
+ return text;
19519
19539
  }
19520
- function headingParaPrId(level) {
19521
- if (level === 1) return PARA_H1;
19522
- if (level === 2) return PARA_H2;
19523
- if (level === 3) return PARA_H3;
19524
- return PARA_H4;
19540
+ function paraTextPureT(para, xml) {
19541
+ let len = 0;
19542
+ for (const t of para.tRanges) {
19543
+ if (t.selfClosing) continue;
19544
+ len += tContentToText(xml.slice(t.contentStart, t.contentEnd)).length;
19545
+ }
19546
+ return len === para.text.length;
19525
19547
  }
19526
- function headingCharPrId(level) {
19527
- if (level === 1) return CHAR_H1;
19528
- if (level === 2) return CHAR_H2;
19529
- if (level === 3) return CHAR_H3;
19530
- return CHAR_H4;
19548
+ function buildRangeSplices(para, xml, start, end, replacement) {
19549
+ if (start < 0 || end < start) return null;
19550
+ const segs = [];
19551
+ let offset = 0;
19552
+ for (const t of para.tRanges) {
19553
+ if (t.selfClosing) continue;
19554
+ const raw = xml.slice(t.contentStart, t.contentEnd);
19555
+ if (/[<&]/.test(raw)) return null;
19556
+ segs.push({ contentStart: t.contentStart, from: offset, to: offset + raw.length });
19557
+ offset += raw.length;
19558
+ }
19559
+ if (segs.length === 0 || end > offset) return null;
19560
+ const escaped = escapeXmlText(replacement);
19561
+ if (start === end) {
19562
+ for (const seg of segs) {
19563
+ if (start >= seg.from && start <= seg.to) {
19564
+ const at = seg.contentStart + (start - seg.from);
19565
+ return [{ start: at, end: at, replacement: escaped }];
19566
+ }
19567
+ }
19568
+ return null;
19569
+ }
19570
+ const splices = [];
19571
+ let placed = false;
19572
+ for (const seg of segs) {
19573
+ if (seg.to <= start || seg.from >= end) continue;
19574
+ const localStart = Math.max(seg.from, start) - seg.from;
19575
+ const localEnd = Math.min(seg.to, end) - seg.from;
19576
+ splices.push({
19577
+ start: seg.contentStart + localStart,
19578
+ end: seg.contentStart + localEnd,
19579
+ replacement: placed ? "" : escaped
19580
+ });
19581
+ placed = true;
19582
+ }
19583
+ return placed ? splices : null;
19531
19584
  }
19532
- function generateContainerXml() {
19533
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
19534
- <ocf:container xmlns:ocf="${NS_OCF}" xmlns:hpf="${NS_HPF}">
19535
- <ocf:rootfiles>
19536
- <ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/>
19537
- </ocf:rootfiles>
19538
- </ocf:container>`;
19585
+ function applySplices(xml, splices) {
19586
+ const sorted = [...splices].sort((a, b) => a.start - b.start);
19587
+ for (let i = 1; i < sorted.length; i++) {
19588
+ if (sorted[i].start < sorted[i - 1].end) {
19589
+ throw new Error("\uC18C\uC2A4\uB9F5 splice \uBC94\uC704 \uACB9\uCE68 \u2014 \uB0B4\uBD80 \uC624\uB958");
19590
+ }
19591
+ }
19592
+ let result = xml;
19593
+ for (let i = sorted.length - 1; i >= 0; i--) {
19594
+ const s = sorted[i];
19595
+ result = result.slice(0, s.start) + s.replacement + result.slice(s.end);
19596
+ }
19597
+ return result;
19539
19598
  }
19540
- function generateManifest() {
19541
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
19542
- <opf:package xmlns:opf="${NS_OPF}" xmlns:hpf="${NS_HPF}" xmlns:hh="${NS_HEAD}">
19543
- <opf:manifest>
19544
- <opf:item id="header" href="Contents/header.xml" media-type="application/xml"/>
19545
- <opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/>
19546
- </opf:manifest>
19547
- <opf:spine>
19548
- <opf:itemref idref="header" linear="no"/>
19549
- <opf:itemref idref="section0" linear="yes"/>
19550
- </opf:spine>
19551
- </opf:package>`;
19599
+
19600
+ // src/roundtrip/zip-patch.ts
19601
+ import { deflateRawSync } from "zlib";
19602
+ var EOCD_SIG = 101010256;
19603
+ var CD_SIG = 33639248;
19604
+ var LOCAL_SIG = 67324752;
19605
+ var ZIP64_EOCD_LOC_SIG = 117853008;
19606
+ function copyBytes(buf, start, end) {
19607
+ return new Uint8Array(buf.subarray(start, end));
19552
19608
  }
19553
- function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR) {
19554
- const boldAttr = bold ? ` bold="1"` : "";
19555
- const italicAttr = italic ? ` italic="1"` : "";
19556
- const effFont = bold ? 2 : fontId;
19557
- return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="0"${boldAttr}${italicAttr}>
19558
- <hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${effFont}" japanese="${effFont}" other="${effFont}" symbol="${effFont}" user="${effFont}"/>
19559
- <hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
19560
- <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
19561
- <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
19562
- <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
19563
- </hh:charPr>`;
19609
+ function parseCentralDirectory(buf) {
19610
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
19611
+ const minEocd = Math.max(0, buf.length - 22 - 65535);
19612
+ let eocdOffset = -1;
19613
+ for (let i = buf.length - 22; i >= minEocd; i--) {
19614
+ if (view.getUint32(i, true) === EOCD_SIG && i + 22 + view.getUint16(i + 20, true) === buf.length) {
19615
+ eocdOffset = i;
19616
+ break;
19617
+ }
19618
+ }
19619
+ if (eocdOffset < 0) {
19620
+ for (let i = buf.length - 22; i >= minEocd; i--) {
19621
+ if (view.getUint32(i, true) !== EOCD_SIG) continue;
19622
+ if (i + 22 + view.getUint16(i + 20, true) > buf.length) continue;
19623
+ const cand = view.getUint32(i + 16, true);
19624
+ if (cand < buf.length - 4 && view.getUint32(cand, true) === CD_SIG) {
19625
+ eocdOffset = i;
19626
+ break;
19627
+ }
19628
+ }
19629
+ }
19630
+ if (eocdOffset < 0) throw new KordocError("ZIP EOCD\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
19631
+ const totalEntries = view.getUint16(eocdOffset + 10, true);
19632
+ const cdSize = view.getUint32(eocdOffset + 12, true);
19633
+ const cdOffset = view.getUint32(eocdOffset + 16, true);
19634
+ if (cdOffset === 4294967295 || totalEntries === 65535) throw new KordocError("ZIP64\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
19635
+ if (eocdOffset >= 20 && view.getUint32(eocdOffset - 20, true) === ZIP64_EOCD_LOC_SIG) {
19636
+ throw new KordocError("ZIP64\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
19637
+ }
19638
+ const decoder = new TextDecoder("utf-8");
19639
+ const entries = [];
19640
+ let pos = cdOffset;
19641
+ for (let i = 0; i < totalEntries; i++) {
19642
+ if (view.getUint32(pos, true) !== CD_SIG) throw new KordocError("ZIP Central Directory \uC190\uC0C1");
19643
+ const flags = view.getUint16(pos + 8, true);
19644
+ const method = view.getUint16(pos + 10, true);
19645
+ const crc = view.getUint32(pos + 16, true);
19646
+ const compSize = view.getUint32(pos + 20, true);
19647
+ const uncompSize = view.getUint32(pos + 24, true);
19648
+ const nameLen = view.getUint16(pos + 28, true);
19649
+ const extraLen = view.getUint16(pos + 30, true);
19650
+ const commentLen = view.getUint16(pos + 32, true);
19651
+ const localOffset = view.getUint32(pos + 42, true);
19652
+ if (compSize === 4294967295 || uncompSize === 4294967295 || localOffset === 4294967295) {
19653
+ throw new KordocError("ZIP64\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
19654
+ }
19655
+ const name = decoder.decode(buf.subarray(pos + 46, pos + 46 + nameLen));
19656
+ const cdEnd = pos + 46 + nameLen + extraLen + commentLen;
19657
+ entries.push({ cdStart: pos, cdEnd, name, flags, method, crc, compSize, uncompSize, localOffset });
19658
+ pos = cdEnd;
19659
+ }
19660
+ return { entries, cdOffset, cdSize, eocdOffset };
19564
19661
  }
19565
- function paraPr(id, opts = {}) {
19566
- const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0 } = opts;
19567
- return ` <hh:paraPr id="${id}" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="1" suppressLineNumbers="0" checked="0" textDir="AUTO">
19568
- <hh:align horizontal="${align}" vertical="BASELINE"/>
19569
- <hh:heading type="NONE" idRef="0" level="0"/>
19570
- <hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="BREAK_WORD" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
19571
- <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
19572
- <hh:margin indent="${indent}" left="0" right="0" prev="${spaceBefore}" next="${spaceAfter}"/>
19573
- <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
19574
- <hh:border borderFillIDRef="0" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
19575
- </hh:paraPr>`;
19662
+ var CRC_TABLE = (() => {
19663
+ const table = new Uint32Array(256);
19664
+ for (let n = 0; n < 256; n++) {
19665
+ let c = n;
19666
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
19667
+ table[n] = c >>> 0;
19668
+ }
19669
+ return table;
19670
+ })();
19671
+ function crc32(data) {
19672
+ let crc = 4294967295;
19673
+ for (let i = 0; i < data.length; i++) {
19674
+ crc = CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
19675
+ }
19676
+ return (crc ^ 4294967295) >>> 0;
19576
19677
  }
19577
- function generateHeaderXml(theme) {
19578
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
19579
- <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" version="1.4" secCnt="1">
19580
- <hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/>
19581
- <hh:refList>
19582
- <hh:fontfaces itemCnt="7">
19583
- <hh:fontface lang="HANGUL" fontCnt="3">
19584
- <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
19585
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19586
- </hh:font>
19587
- <hh:font id="1" face="\uD568\uCD08\uB86C\uB3CB\uC6C0" type="TTF" isEmbedded="0">
19588
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19589
- </hh:font>
19590
- <hh:font id="2" face="HY\uACAC\uACE0\uB515" type="TTF" isEmbedded="0">
19591
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19592
- </hh:font>
19593
- </hh:fontface>
19594
- <hh:fontface lang="LATIN" fontCnt="3">
19595
- <hh:font id="0" face="Times New Roman" type="TTF" isEmbedded="0">
19596
- <hh:typeInfo familyType="FCAT_OLDSTYLE" weight="5" proportion="4" contrast="2" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="4"/>
19597
- </hh:font>
19598
- <hh:font id="1" face="Consolas" type="TTF" isEmbedded="0">
19599
- <hh:typeInfo familyType="FCAT_MODERN" weight="5" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
19600
- </hh:font>
19601
- <hh:font id="2" face="Arial Black" type="TTF" isEmbedded="0">
19602
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
19603
- </hh:font>
19604
- </hh:fontface>
19605
- <hh:fontface lang="HANJA" fontCnt="1">
19606
- <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
19607
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19608
- </hh:font>
19609
- </hh:fontface>
19610
- <hh:fontface lang="JAPANESE" fontCnt="1">
19611
- <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
19612
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19613
- </hh:font>
19614
- </hh:fontface>
19615
- <hh:fontface lang="OTHER" fontCnt="1">
19616
- <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
19617
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19618
- </hh:font>
19619
- </hh:fontface>
19620
- <hh:fontface lang="SYMBOL" fontCnt="1">
19621
- <hh:font id="0" face="Symbol" type="TTF" isEmbedded="0">
19622
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19623
- </hh:font>
19624
- </hh:fontface>
19625
- <hh:fontface lang="USER" fontCnt="1">
19626
- <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
19627
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19628
- </hh:font>
19629
- </hh:fontface>
19630
- </hh:fontfaces>
19631
- <hh:borderFills itemCnt="2">
19632
- <hh:borderFill id="0" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
19633
- <hh:slash type="NONE" Crooked="0" isCounter="0"/>
19634
- <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
19635
- <hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/>
19636
- <hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/>
19637
- <hh:topBorder type="NONE" width="0.1 mm" color="#000000"/>
19638
- <hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/>
19639
- <hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>
19640
- <hh:fillInfo/>
19641
- </hh:borderFill>
19642
- <hh:borderFill id="1" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
19643
- <hh:slash type="NONE" Crooked="0" isCounter="0"/>
19644
- <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
19645
- <hh:leftBorder type="SOLID" width="0.12 mm" color="#000000"/>
19646
- <hh:rightBorder type="SOLID" width="0.12 mm" color="#000000"/>
19647
- <hh:topBorder type="SOLID" width="0.12 mm" color="#000000"/>
19648
- <hh:bottomBorder type="SOLID" width="0.12 mm" color="#000000"/>
19649
- <hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>
19650
- <hh:fillInfo/>
19651
- </hh:borderFill>
19652
- </hh:borderFills>
19653
- <hh:charProperties itemCnt="11">
19654
- ${charPr(0, 1e3, false, false, 0, theme.body)}
19655
- ${charPr(1, 1e3, true, false, 0, theme.body)}
19656
- ${charPr(2, 1e3, false, true, 0, theme.body)}
19657
- ${charPr(3, 1e3, true, true, 0, theme.body)}
19658
- ${charPr(4, 900, false, false, 1)}
19659
- ${charPr(5, 1800, true, false, 1, theme.h1)}
19660
- ${charPr(6, 1400, true, false, 1, theme.h2)}
19661
- ${charPr(7, 1200, true, false, 1, theme.h3)}
19662
- ${charPr(8, 1100, true, false, 1, theme.h4)}
19663
- ${charPr(CHAR_TABLE_HEADER, 1e3, theme.tableHeaderBold, false, 0, theme.tableHeader)}
19664
- ${charPr(CHAR_QUOTE, 1e3, false, true, 0, theme.quote)}
19665
- </hh:charProperties>
19666
- <hh:tabProperties itemCnt="0"/>
19667
- <hh:numberings itemCnt="0"/>
19668
- <hh:bullets itemCnt="0"/>
19669
- <hh:paraProperties itemCnt="8">
19670
- ${paraPr(0)}
19671
- ${paraPr(1, { align: "LEFT", spaceBefore: 800, spaceAfter: 200, lineSpacing: 180 })}
19672
- ${paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: 170 })}
19673
- ${paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: 160 })}
19674
- ${paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: 160 })}
19675
- ${paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400 })}
19676
- ${paraPr(6, { align: "LEFT", lineSpacing: 150, indent: 600 })}
19677
- ${paraPr(7, { align: "LEFT", lineSpacing: 160, indent: 600 })}
19678
- </hh:paraProperties>
19679
- <hh:styles itemCnt="1">
19680
- <hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langIDRef="1042" lockForm="0"/>
19681
- </hh:styles>
19682
- </hh:refList>
19683
- <hh:compatibleDocument targetProgram="HWP2018"/>
19684
- </hh:head>`;
19685
- }
19686
- function generateSecPr() {
19687
- return `<hp:secPr textDirection="HORIZONTAL" spaceColumns="1134" tabStop="8000" outlineShapeIDRef="0" memoShapeIDRef="0" textVerticalWidthHead="0" masterPageCnt="0"><hp:grid lineGrid="0" charGrid="0" wonggojiFormat="0"/><hp:startNum pageStartsOn="BOTH" page="0" pic="0" tbl="0" equation="0"/><hp:visibility hideFirstHeader="0" hideFirstFooter="0" hideFirstMasterPage="0" border="SHOW_ALL" fill="SHOW_ALL" hideFirstPageNum="0" hideFirstEmptyLine="0" showLineNumber="0"/><hp:pagePr landscape="WIDELY" width="59528" height="84188" gutterType="LEFT_ONLY"><hp:margin header="2835" footer="2835" gutter="0" left="5670" right="4252" top="8504" bottom="4252"/></hp:pagePr><hp:footNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="-1" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="283" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="EACH_COLUMN" beneathText="0"/></hp:footNotePr><hp:endNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="14692344" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="0" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="END_OF_DOCUMENT" beneathText="0"/></hp:endNotePr></hp:secPr>`;
19688
- }
19689
- var TABLE_ID_BASE = 1e3;
19690
- var tableIdCounter = TABLE_ID_BASE;
19691
- function nextTableId() {
19692
- return ++tableIdCounter;
19693
- }
19694
- function generateTable(rows, theme) {
19695
- const rowCnt = rows.length;
19696
- const colCnt = Math.max(...rows.map((r) => r.length), 1);
19697
- const cellW = Math.floor(44e3 / colCnt);
19698
- const cellH = 1500;
19699
- const tblW = cellW * colCnt;
19700
- const tblH = cellH * rowCnt;
19701
- const tblId = nextTableId();
19702
- const useHeaderStyle = theme.tableHeader !== theme.body || theme.tableHeaderBold;
19703
- const trElements = rows.map((row, rowIdx) => {
19704
- const cells = row.length < colCnt ? [...row, ...Array(colCnt - row.length).fill("")] : row;
19705
- const isHeaderRow = rowIdx === 0;
19706
- const headerCharPr = isHeaderRow && useHeaderStyle ? CHAR_TABLE_HEADER : CHAR_NORMAL;
19707
- const tdElements = cells.map((cell, colIdx) => {
19708
- const runs = generateRuns(cell, headerCharPr);
19709
- const p = `<hp:p paraPrIDRef="0" styleIDRef="0">${runs}</hp:p>`;
19710
- return `<hp:tc name="" header="${isHeaderRow ? 1 : 0}" hasMargin="0" protect="0" editable="1" dirty="0" borderFillIDRef="1"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="TOP" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">${p}</hp:subList><hp:cellAddr colAddr="${colIdx}" rowAddr="${rowIdx}"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="${cellW}" height="${cellH}"/><hp:cellMargin left="141" right="141" top="141" bottom="141"/></hp:tc>`;
19711
- }).join("");
19712
- return `<hp:tr>${tdElements}</hp:tr>`;
19713
- }).join("");
19714
- const tblInner = `<hp:sz width="${tblW}" widthRelTo="ABSOLUTE" height="${tblH}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="0" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="0" right="0" top="0" bottom="0"/><hp:inMargin left="510" right="510" top="141" bottom="141"/>` + trElements;
19715
- const tbl = `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE" pageBreak="CELL" repeatHeader="0" rowCnt="${rowCnt}" colCnt="${colCnt}" cellSpacing="0" borderFillIDRef="1" noShading="0">${tblInner}</hp:tbl>`;
19716
- return `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${tbl}</hp:run></hp:p>`;
19717
- }
19718
- function blocksToSectionXml(blocks, theme) {
19719
- const paraXmls = [];
19720
- let isFirst = true;
19721
- const orderedCounters = {};
19722
- let prevWasOrdered = false;
19723
- for (const block of blocks) {
19724
- let xml = "";
19725
- if (block.type !== "list_item" || !block.ordered) {
19726
- if (prevWasOrdered) {
19727
- for (const k of Object.keys(orderedCounters)) delete orderedCounters[+k];
19728
- }
19729
- prevWasOrdered = false;
19730
- }
19731
- switch (block.type) {
19732
- case "heading": {
19733
- const pId = headingParaPrId(block.level || 1);
19734
- const cId = headingCharPrId(block.level || 1);
19735
- xml = generateParagraph(block.text || "", pId, cId);
19736
- break;
19737
- }
19738
- case "paragraph":
19739
- xml = generateParagraph(block.text || "");
19740
- break;
19741
- case "code_block": {
19742
- const codeLines = (block.text || "").split("\n");
19743
- xml = codeLines.map((line) => generateParagraph(line || " ", PARA_CODE)).join("\n ");
19744
- break;
19745
- }
19746
- case "blockquote":
19747
- xml = generateParagraph(
19748
- block.text || "",
19749
- PARA_QUOTE,
19750
- theme.hasQuoteOption ? CHAR_QUOTE : CHAR_NORMAL
19751
- );
19752
- break;
19753
- case "list_item": {
19754
- const indent = block.indent || 0;
19755
- let marker;
19756
- if (block.ordered) {
19757
- orderedCounters[indent] = (orderedCounters[indent] || 0) + 1;
19758
- for (const k of Object.keys(orderedCounters)) {
19759
- if (+k > indent) delete orderedCounters[+k];
19760
- }
19761
- marker = `${orderedCounters[indent]}. `;
19762
- prevWasOrdered = true;
19763
- } else {
19764
- marker = "\xB7 ";
19765
- if (prevWasOrdered) {
19766
- for (const k of Object.keys(orderedCounters)) delete orderedCounters[+k];
19767
- }
19768
- prevWasOrdered = false;
19769
- }
19770
- const indentPrefix = " ".repeat(indent);
19771
- xml = generateParagraph(indentPrefix + marker + (block.text || ""), PARA_LIST);
19772
- break;
19773
- }
19774
- case "hr":
19775
- xml = `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0"><hp:t>\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500</hp:t></hp:run></hp:p>`;
19776
- break;
19777
- case "table":
19778
- if (block.rows) {
19779
- if (isFirst) {
19780
- const secRun = `<hp:run charPrIDRef="0">${generateSecPr()}<hp:t></hp:t></hp:run>`;
19781
- paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
19782
- isFirst = false;
19783
- }
19784
- xml = generateTable(block.rows, theme);
19785
- }
19786
- break;
19787
- }
19788
- if (!xml) continue;
19789
- if (isFirst && block.type !== "table") {
19790
- xml = xml.replace(
19791
- /<hp:run charPrIDRef="(\d+)">/,
19792
- `<hp:run charPrIDRef="$1">${generateSecPr()}`
19793
- );
19794
- isFirst = false;
19795
- }
19796
- paraXmls.push(xml);
19797
- }
19798
- if (paraXmls.length === 0) {
19799
- paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${generateSecPr()}<hp:t></hp:t></hp:run></hp:p>`);
19800
- }
19801
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
19802
- <hs:sec xmlns:hs="${NS_SECTION}" xmlns:hp="${NS_PARA}">
19803
- ${paraXmls.join("\n ")}
19804
- </hs:sec>`;
19805
- }
19806
-
19807
- // src/diff/text-diff.ts
19808
- function similarity(a, b) {
19809
- if (a === b) return 1;
19810
- if (!a || !b) return 0;
19811
- const maxLen = Math.max(a.length, b.length);
19812
- if (maxLen === 0) return 1;
19813
- return 1 - levenshtein(a, b) / maxLen;
19814
- }
19815
- function normalizedSimilarity(a, b) {
19816
- return similarity(normalize(a), normalize(b));
19817
- }
19818
- function normalize(s) {
19819
- return s.replace(/\s+/g, " ").trim();
19820
- }
19821
- var MAX_LEVENSHTEIN_LEN = 1e4;
19822
- function levenshtein(a, b) {
19823
- if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
19824
- const sampleLen = Math.min(500, a.length, b.length);
19825
- let diffs = 0;
19826
- for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
19827
- const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
19828
- return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
19829
- }
19830
- if (a.length > b.length) [a, b] = [b, a];
19831
- const m = a.length;
19832
- const n = b.length;
19833
- let prev = Array.from({ length: m + 1 }, (_, i) => i);
19834
- let curr = new Array(m + 1);
19835
- for (let j = 1; j <= n; j++) {
19836
- curr[0] = j;
19837
- for (let i = 1; i <= m; i++) {
19838
- if (a[i - 1] === b[j - 1]) {
19839
- curr[i] = prev[i - 1];
19840
- } else {
19841
- curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
19842
- }
19843
- }
19844
- ;
19845
- [prev, curr] = [curr, prev];
19846
- }
19847
- return prev[m];
19848
- }
19849
-
19850
- // src/diff/compare.ts
19851
- var SIMILARITY_THRESHOLD = 0.4;
19852
- async function compare(bufferA, bufferB, options) {
19853
- const [resultA, resultB] = await Promise.all([
19854
- parse(bufferA, options),
19855
- parse(bufferB, options)
19856
- ]);
19857
- if (!resultA.success) throw new Error(`\uBB38\uC11CA \uD30C\uC2F1 \uC2E4\uD328: ${resultA.error}`);
19858
- if (!resultB.success) throw new Error(`\uBB38\uC11CB \uD30C\uC2F1 \uC2E4\uD328: ${resultB.error}`);
19859
- return diffBlocks(resultA.blocks, resultB.blocks);
19860
- }
19861
- function diffBlocks(blocksA, blocksB) {
19862
- const aligned = alignBlocks(blocksA, blocksB);
19863
- const stats = { added: 0, removed: 0, modified: 0, unchanged: 0 };
19864
- const diffs = [];
19865
- for (const [a, b] of aligned) {
19866
- if (a && b) {
19867
- const sim = blockSimilarity(a, b);
19868
- if (sim >= 0.99) {
19869
- diffs.push({ type: "unchanged", before: a, after: b, similarity: 1 });
19870
- stats.unchanged++;
19871
- } else {
19872
- const diff = { type: "modified", before: a, after: b, similarity: sim };
19873
- if (a.type === "table" && b.type === "table" && a.table && b.table) {
19874
- diff.cellDiffs = diffTableCells(a.table, b.table);
19875
- }
19876
- diffs.push(diff);
19877
- stats.modified++;
19878
- }
19879
- } else if (a) {
19880
- diffs.push({ type: "removed", before: a });
19881
- stats.removed++;
19882
- } else if (b) {
19883
- diffs.push({ type: "added", after: b });
19884
- stats.added++;
19885
- }
19886
- }
19887
- return { stats, diffs };
19888
- }
19889
- function alignBlocks(a, b) {
19890
- const m = a.length, n = b.length;
19891
- if (m * n > 1e7) return fallbackAlign(a, b);
19892
- const simCache = /* @__PURE__ */ new Map();
19893
- const getSim = (i2, j2) => {
19894
- const key = `${i2},${j2}`;
19895
- let v = simCache.get(key);
19896
- if (v === void 0) {
19897
- v = blockSimilarity(a[i2], b[j2]);
19898
- simCache.set(key, v);
19899
- }
19900
- return v;
19901
- };
19902
- const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
19903
- for (let i2 = 1; i2 <= m; i2++) {
19904
- for (let j2 = 1; j2 <= n; j2++) {
19905
- if (getSim(i2 - 1, j2 - 1) >= SIMILARITY_THRESHOLD) {
19906
- dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
19907
- } else {
19908
- dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
19909
- }
19910
- }
19911
- }
19912
- const pairs = [];
19913
- let i = m, j = n;
19914
- while (i > 0 && j > 0) {
19915
- if (getSim(i - 1, j - 1) >= SIMILARITY_THRESHOLD && dp[i][j] === dp[i - 1][j - 1] + 1) {
19916
- pairs.push([i - 1, j - 1]);
19917
- i--;
19918
- j--;
19919
- } else if (dp[i - 1][j] >= dp[i][j - 1]) {
19920
- i--;
19921
- } else {
19922
- j--;
19923
- }
19924
- }
19925
- pairs.reverse();
19926
- const result = [];
19927
- let ai = 0, bi = 0;
19928
- for (const [pi, pj] of pairs) {
19929
- while (ai < pi) result.push([a[ai++], null]);
19930
- while (bi < pj) result.push([null, b[bi++]]);
19931
- result.push([a[ai++], b[bi++]]);
19932
- }
19933
- while (ai < m) result.push([a[ai++], null]);
19934
- while (bi < n) result.push([null, b[bi++]]);
19935
- return result;
19936
- }
19937
- function fallbackAlign(a, b) {
19938
- const result = [];
19939
- const len = Math.max(a.length, b.length);
19940
- for (let i = 0; i < len; i++) {
19941
- result.push([a[i] || null, b[i] || null]);
19942
- }
19943
- return result;
19944
- }
19945
- function blockSimilarity(a, b) {
19946
- if (a.type !== b.type) return 0;
19947
- if (a.text !== void 0 && b.text !== void 0) {
19948
- return normalizedSimilarity(a.text || "", b.text || "");
19949
- }
19950
- if (a.type === "table" && a.table && b.table) {
19951
- return tableSimilarity(a.table, b.table);
19678
+ function patchZipEntries(original, replacements) {
19679
+ const { entries, cdOffset, eocdOffset } = parseCentralDirectory(original);
19680
+ const view = new DataView(original.buffer, original.byteOffset, original.byteLength);
19681
+ for (const name of replacements.keys()) {
19682
+ if (!entries.some((e) => e.name === name)) throw new KordocError(`ZIP\uC5D0 \uC5C6\uB294 \uC5D4\uD2B8\uB9AC: ${name}`);
19952
19683
  }
19953
- if (a.type === b.type) return 1;
19954
- return 0;
19955
- }
19956
- function tableSimilarity(a, b) {
19957
- const dimSim = 1 - Math.abs(a.rows * a.cols - b.rows * b.cols) / Math.max(a.rows * a.cols, b.rows * b.cols, 1);
19958
- const textsA = a.cells.flat().map((c) => c.text).join(" ");
19959
- const textsB = b.cells.flat().map((c) => c.text).join(" ");
19960
- const contentSim = normalizedSimilarity(textsA, textsB);
19961
- return dimSim * 0.3 + contentSim * 0.7;
19962
- }
19963
- function diffTableCells(a, b) {
19964
- const maxRows = Math.max(a.rows, b.rows);
19965
- const maxCols = Math.max(a.cols, b.cols);
19966
- const result = [];
19967
- for (let r = 0; r < maxRows; r++) {
19968
- const row = [];
19969
- for (let c = 0; c < maxCols; c++) {
19970
- const cellA = r < a.rows && c < a.cols ? a.cells[r][c].text : void 0;
19971
- const cellB = r < b.rows && c < b.cols ? b.cells[r][c].text : void 0;
19972
- let type;
19973
- if (cellA === void 0) type = "added";
19974
- else if (cellB === void 0) type = "removed";
19975
- else if (cellA === cellB) type = "unchanged";
19976
- else type = "modified";
19977
- row.push({ type, before: cellA, after: cellB });
19684
+ const byLocal = [...entries].sort((a, b) => a.localOffset - b.localOffset);
19685
+ const segments = [];
19686
+ const newLocalOffset = /* @__PURE__ */ new Map();
19687
+ const newMeta = /* @__PURE__ */ new Map();
19688
+ let offset = 0;
19689
+ for (let i = 0; i < byLocal.length; i++) {
19690
+ const e = byLocal[i];
19691
+ const segEnd = i + 1 < byLocal.length ? byLocal[i + 1].localOffset : cdOffset;
19692
+ newLocalOffset.set(e, offset);
19693
+ const newData = replacements.get(e.name);
19694
+ if (newData === void 0) {
19695
+ const seg = original.subarray(e.localOffset, segEnd);
19696
+ segments.push(seg);
19697
+ offset += seg.length;
19698
+ continue;
19978
19699
  }
19979
- result.push(row);
19700
+ if (view.getUint32(e.localOffset, true) !== LOCAL_SIG) throw new KordocError("ZIP \uB85C\uCEEC \uD5E4\uB354 \uC2DC\uADF8\uB2C8\uCC98 \uBD88\uC77C\uCE58");
19701
+ const nameLen = view.getUint16(e.localOffset + 26, true);
19702
+ const extraLen = view.getUint16(e.localOffset + 28, true);
19703
+ const headerLen = 30 + nameLen + extraLen;
19704
+ const header = copyBytes(original, e.localOffset, e.localOffset + headerLen);
19705
+ const hview = new DataView(header.buffer, header.byteOffset, header.byteLength);
19706
+ const method = e.method;
19707
+ const compData = method === 0 ? newData : new Uint8Array(deflateRawSync(newData));
19708
+ const crc = crc32(newData);
19709
+ const flags = e.flags & ~8;
19710
+ hview.setUint16(6, flags, true);
19711
+ hview.setUint32(14, crc, true);
19712
+ hview.setUint32(18, compData.length, true);
19713
+ hview.setUint32(22, newData.length, true);
19714
+ segments.push(header, compData);
19715
+ offset += headerLen + compData.length;
19716
+ newMeta.set(e, { crc, compSize: compData.length, uncompSize: newData.length, flags });
19980
19717
  }
19981
- return result;
19982
- }
19983
-
19984
- // src/roundtrip/patcher.ts
19985
- import JSZip7 from "jszip";
19986
-
19987
- // src/roundtrip/source-map.ts
19988
- function escapeXmlText(text) {
19989
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
19990
- }
19991
- function decodeXmlEntities(text) {
19992
- return text.replace(/&(lt|gt|amp|quot|apos|#x?[0-9a-fA-F]+);/g, (m, ent) => {
19993
- switch (ent) {
19994
- case "lt":
19995
- return "<";
19996
- case "gt":
19997
- return ">";
19998
- case "amp":
19999
- return "&";
20000
- case "quot":
20001
- return '"';
20002
- case "apos":
20003
- return "'";
20004
- }
20005
- try {
20006
- const code = ent[1] === "x" || ent[1] === "X" ? parseInt(ent.slice(2), 16) : parseInt(ent.slice(1), 10);
20007
- if (!isNaN(code) && code >= 0 && code <= 1114111) return String.fromCodePoint(code);
20008
- } catch {
20009
- }
20010
- return m;
20011
- });
20012
- }
20013
- function tContentToText(raw) {
20014
- return decodeXmlEntities(
20015
- raw.replace(/<\/?(?:[A-Za-z0-9_]+:)?(?:tab|fwSpace|hwSpace|br|lineBreak)(?:\s[^>]*)?\/?>/g, " ").replace(/<[^>]*>/g, "")
20016
- );
20017
- }
20018
- var TAG_RE = /<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<\?[\s\S]*?\?>|<!(?:"[^"]*"|'[^']*'|[^>"'])*>|<\/([^\s>]+)\s*>|<([^\s/>!?]+)((?:"[^"]*"|'[^']*'|[^>"'])*?)(\/?)>/g;
20019
- var T_BARRIER = /* @__PURE__ */ new Set([
20020
- "tbl",
20021
- "ctrl",
20022
- "caption",
20023
- "pic",
20024
- "shape",
20025
- "drawingObject",
20026
- "drawText",
20027
- "shapeComment",
20028
- "memogroup",
20029
- "memo",
20030
- "hiddenComment",
20031
- "equation",
20032
- "parameters",
20033
- "subList",
20034
- "p"
20035
- ]);
20036
- var PARA_CONTAINER = /* @__PURE__ */ new Set([
20037
- "tc",
20038
- "ctrl",
20039
- "caption",
20040
- "drawText",
20041
- "pic",
20042
- "shape",
20043
- "drawingObject",
20044
- "memogroup",
20045
- "memo",
20046
- "hiddenComment",
20047
- "footNote",
20048
- "endNote",
20049
- "fn",
20050
- "en"
20051
- // 각주/미주 — 파서는 호스트 블록 footnoteText로만 흡수
20052
- ]);
20053
- var TABLE_BARRIER = /* @__PURE__ */ new Set([
20054
- "tbl",
20055
- "ctrl",
20056
- "caption",
20057
- "memogroup",
20058
- "memo",
20059
- "hiddenComment"
20060
- ]);
20061
- function localOf(qname) {
20062
- const i = qname.indexOf(":");
20063
- return i >= 0 ? qname.slice(i + 1) : qname;
20064
- }
20065
- function prefixOf(qname) {
20066
- const i = qname.indexOf(":");
20067
- return i >= 0 ? qname.slice(0, i) : "";
20068
- }
20069
- function scanSectionXml(xml, sectionIndex) {
20070
- const stack = [];
20071
- const bodyParagraphs = [];
20072
- const tables = [];
20073
- const headerTexts = [];
20074
- const footerTexts = [];
20075
- const paraStack = [];
20076
- const tableStack = [];
20077
- const rowStack = [];
20078
- const cellStack = [];
20079
- let pendingT = null;
20080
- const ctrlSubStack = [];
20081
- const classifyPara = () => {
20082
- let sawDrawText = false;
20083
- for (let i = stack.length - 1; i >= 0; i--) {
20084
- const l = stack[i].local;
20085
- if (l === "tc") return "cell";
20086
- if (l === "drawText") {
20087
- sawDrawText = true;
20088
- continue;
20089
- }
20090
- if (PARA_CONTAINER.has(l)) return "excluded";
20091
- }
20092
- return sawDrawText ? "draw" : "body";
20093
- };
20094
- const owningPara = () => {
20095
- if (paraStack.length === 0) return null;
20096
- for (let i = stack.length - 1; i >= 0; i--) {
20097
- const l = stack[i].local;
20098
- if (l === "p") return paraStack[paraStack.length - 1];
20099
- if (T_BARRIER.has(l)) return null;
20100
- }
20101
- return null;
20102
- };
20103
- const isTableTopLevel = () => {
20104
- for (let i = stack.length - 1; i >= 0; i--) {
20105
- if (TABLE_BARRIER.has(stack[i].local)) return false;
19718
+ const newCdOffset = offset;
19719
+ for (const e of entries) {
19720
+ const cd = copyBytes(original, e.cdStart, e.cdEnd);
19721
+ const cview = new DataView(cd.buffer, cd.byteOffset, cd.byteLength);
19722
+ cview.setUint32(42, newLocalOffset.get(e), true);
19723
+ const meta = newMeta.get(e);
19724
+ if (meta) {
19725
+ cview.setUint16(8, meta.flags, true);
19726
+ cview.setUint32(16, meta.crc, true);
19727
+ cview.setUint32(20, meta.compSize, true);
19728
+ cview.setUint32(24, meta.uncompSize, true);
20106
19729
  }
20107
- return true;
20108
- };
20109
- const currentCtrlSub = () => ctrlSubStack.length > 0 ? ctrlSubStack[ctrlSubStack.length - 1] : null;
20110
- TAG_RE.lastIndex = 0;
20111
- let m;
20112
- while ((m = TAG_RE.exec(xml)) !== null) {
20113
- const [full, closeName, openName, , selfClose] = m;
20114
- if (closeName === void 0 && openName === void 0) continue;
20115
- if (closeName !== void 0) {
20116
- const local2 = localOf(closeName);
20117
- if (local2 === "t" && pendingT) {
20118
- const { para, contentStart: contentStart2 } = pendingT;
20119
- para.tRanges.push({ contentStart: contentStart2, contentEnd: m.index });
20120
- para.text += tContentToText(xml.slice(contentStart2, m.index));
20121
- pendingT = null;
20122
- }
20123
- for (let i = stack.length - 1; i >= 0; i--) {
20124
- if (stack[i].local === local2) {
20125
- stack.length = i;
20126
- break;
19730
+ segments.push(cd);
19731
+ offset += cd.length;
19732
+ }
19733
+ const newCdSize = offset - newCdOffset;
19734
+ const eocd = copyBytes(original, eocdOffset);
19735
+ const eview = new DataView(eocd.buffer, eocd.byteOffset, eocd.byteLength);
19736
+ eview.setUint32(12, newCdSize, true);
19737
+ eview.setUint32(16, newCdOffset, true);
19738
+ segments.push(eocd);
19739
+ offset += eocd.length;
19740
+ const result = new Uint8Array(offset);
19741
+ let pos = 0;
19742
+ for (const seg of segments) {
19743
+ result.set(seg, pos);
19744
+ pos += seg.length;
19745
+ }
19746
+ return result;
19747
+ }
19748
+
19749
+ // src/form/filler-hwpx.ts
19750
+ async function fillHwpx(hwpxBuffer, values) {
19751
+ const u8 = new Uint8Array(hwpxBuffer);
19752
+ const zip = await JSZip5.loadAsync(hwpxBuffer);
19753
+ const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
19754
+ if (sectionPaths.length === 0) {
19755
+ throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
19756
+ }
19757
+ const normalizedValues = normalizeValues(values);
19758
+ const matchedLabels = /* @__PURE__ */ new Set();
19759
+ const filled = [];
19760
+ const failedKeys = /* @__PURE__ */ new Set();
19761
+ const succeededKeys = /* @__PURE__ */ new Set();
19762
+ const replacements = /* @__PURE__ */ new Map();
19763
+ const encoder = new TextEncoder();
19764
+ for (let si = 0; si < sectionPaths.length; si++) {
19765
+ const xml = await zip.file(sectionPaths[si]).async("text");
19766
+ const scan = scanSectionXml(xml, si);
19767
+ const ledger = /* @__PURE__ */ new Map();
19768
+ const led = (p) => {
19769
+ let l = ledger.get(p);
19770
+ if (!l) ledger.set(p, l = { ranges: [], filledIdx: [], matchKeys: [] });
19771
+ return l;
19772
+ };
19773
+ const matchText = (p) => paraTText(p, xml) ?? p.text;
19774
+ const cellLabelText = (cell) => cell.paragraphs.filter((p) => !p.inTextbox).map((p) => matchText(p)).join("");
19775
+ const allTables = [];
19776
+ const collectTables = (tables, depth) => {
19777
+ if (depth > 16) return;
19778
+ for (const t of tables) {
19779
+ allTables.push(t);
19780
+ for (const row of t.rows) {
19781
+ for (const cell of row) collectTables(cell.tables, depth + 1);
20127
19782
  }
20128
19783
  }
20129
- if (local2 === "p") {
20130
- const para = paraStack.pop();
20131
- if (para && para.kind === "excluded") {
20132
- const sub = currentCtrlSub();
20133
- if (sub && para.text.trim()) sub.texts.push(para.text);
19784
+ };
19785
+ collectTables(scan.tables, 0);
19786
+ collectTables(scan.orphanTables, 0);
19787
+ const patternApplied = /* @__PURE__ */ new Set();
19788
+ for (const table of allTables) {
19789
+ for (const row of table.rows) {
19790
+ for (const cell of row) {
19791
+ for (const para of cell.paragraphs) {
19792
+ const text = matchText(para);
19793
+ const result = fillInCellPatterns(text, normalizedValues, matchedLabels);
19794
+ if (!result) continue;
19795
+ const l = led(para);
19796
+ if (l.fullText !== void 0) continue;
19797
+ const newT = result.text;
19798
+ let s = 0;
19799
+ while (s < text.length && s < newT.length && text[s] === newT[s]) s++;
19800
+ let eo = text.length;
19801
+ let en = newT.length;
19802
+ while (eo > s && en > s && text[eo - 1] === newT[en - 1]) {
19803
+ eo--;
19804
+ en--;
19805
+ }
19806
+ l.ranges.push({ start: s, end: eo, replacement: newT.slice(s, en) });
19807
+ patternApplied.add(cell);
19808
+ for (const m of result.matches) {
19809
+ l.filledIdx.push(filled.length);
19810
+ l.matchKeys.push(m.key);
19811
+ filled.push({ label: m.label, value: m.value, row: -1, col: -1 });
19812
+ }
19813
+ }
20134
19814
  }
20135
- } else if (local2 === "tc") {
20136
- const cell = cellStack.pop();
20137
- const row = rowStack[rowStack.length - 1];
20138
- if (cell && row) row.push(cell);
20139
- } else if (local2 === "tr") {
20140
- const row = rowStack[rowStack.length - 1];
20141
- const table = tableStack[tableStack.length - 1];
20142
- if (row && table && row.length > 0) table.rows.push(row);
20143
- if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
20144
- } else if (local2 === "tbl") {
20145
- const table = tableStack.pop();
20146
- rowStack.pop();
20147
- if (table) {
20148
- finalizeTable(table);
20149
- if (!table.topLevel) {
20150
- const cell = cellStack[cellStack.length - 1];
20151
- if (cell) cell.tables.push(table);
19815
+ }
19816
+ }
19817
+ for (const table of allTables) {
19818
+ for (let rowIdx = 0; rowIdx < table.rows.length; rowIdx++) {
19819
+ const cells = table.rows[rowIdx];
19820
+ for (let colIdx = 0; colIdx < cells.length - 1; colIdx++) {
19821
+ const labelText = cellLabelText(cells[colIdx]);
19822
+ if (!isLabelCell(labelText)) continue;
19823
+ const valueCell = cells[colIdx + 1];
19824
+ if (isKeywordLabel(cellLabelText(valueCell))) continue;
19825
+ const normalizedCellLabel = normalizeLabel(labelText);
19826
+ if (!normalizedCellLabel) continue;
19827
+ const matchKey = findMatchingKey(normalizedCellLabel, normalizedValues);
19828
+ if (matchKey === void 0) continue;
19829
+ const newValue = normalizedValues.get(matchKey);
19830
+ if (patternApplied.has(valueCell)) {
19831
+ const target = valueCell.paragraphs.find((p) => p.tRanges.length > 0) ?? valueCell.paragraphs[0];
19832
+ if (!target) continue;
19833
+ const l = led(target);
19834
+ if (l.fullText === void 0) {
19835
+ l.ranges.push({ start: 0, end: 0, replacement: newValue + " " });
19836
+ l.filledIdx.push(filled.length);
19837
+ l.matchKeys.push(matchKey);
19838
+ }
19839
+ } else {
19840
+ const paras = valueCell.paragraphs;
19841
+ if (paras.length === 0) continue;
19842
+ const l0 = led(paras[0]);
19843
+ l0.fullText = newValue;
19844
+ l0.ranges = [];
19845
+ l0.filledIdx.push(filled.length);
19846
+ l0.matchKeys.push(matchKey);
19847
+ for (let k = 1; k < paras.length; k++) {
19848
+ const lk = led(paras[k]);
19849
+ lk.fullText = "";
19850
+ lk.ranges = [];
19851
+ }
20152
19852
  }
19853
+ matchedLabels.add(matchKey);
19854
+ filled.push({
19855
+ label: labelText.trim().replace(/[::]\s*$/, ""),
19856
+ value: newValue,
19857
+ row: rowIdx,
19858
+ col: colIdx
19859
+ });
20153
19860
  }
20154
- } else if (local2 === "header" || local2 === "footer") {
20155
- const sub = ctrlSubStack[ctrlSubStack.length - 1];
20156
- if (sub) {
20157
- ctrlSubStack.pop();
20158
- const joined = sub.texts.join("\n").trim();
20159
- if (joined) (sub.kind === "header" ? headerTexts : footerTexts).push(joined);
19861
+ }
19862
+ if (table.rows.length >= 2) {
19863
+ const headerCells = table.rows[0];
19864
+ const allLabels = headerCells.length > 0 && headerCells.every((cell) => {
19865
+ const t = cellLabelText(cell).trim();
19866
+ return t.length > 0 && t.length <= 20 && isLabelCell(t);
19867
+ });
19868
+ if (allLabels) {
19869
+ for (let rowIdx = 1; rowIdx < table.rows.length; rowIdx++) {
19870
+ const dataCells = table.rows[rowIdx];
19871
+ for (let colIdx = 0; colIdx < Math.min(headerCells.length, dataCells.length); colIdx++) {
19872
+ const headerLabel = normalizeLabel(cellLabelText(headerCells[colIdx]));
19873
+ const matchKey = findMatchingKey(headerLabel, normalizedValues);
19874
+ if (matchKey === void 0) continue;
19875
+ if (matchedLabels.has(matchKey)) continue;
19876
+ const newValue = normalizedValues.get(matchKey);
19877
+ const paras = dataCells[colIdx].paragraphs;
19878
+ if (paras.length === 0) continue;
19879
+ const l0 = led(paras[0]);
19880
+ l0.fullText = newValue;
19881
+ l0.ranges = [];
19882
+ l0.filledIdx.push(filled.length);
19883
+ l0.matchKeys.push(matchKey);
19884
+ for (let k = 1; k < paras.length; k++) {
19885
+ const lk = led(paras[k]);
19886
+ lk.fullText = "";
19887
+ lk.ranges = [];
19888
+ }
19889
+ matchedLabels.add(matchKey);
19890
+ filled.push({
19891
+ label: cellLabelText(headerCells[colIdx]).trim(),
19892
+ value: newValue,
19893
+ row: rowIdx,
19894
+ col: colIdx
19895
+ });
19896
+ }
19897
+ }
20160
19898
  }
20161
19899
  }
20162
- continue;
20163
19900
  }
20164
- const qname = openName;
20165
- const local = localOf(qname);
20166
- const attrsRaw = m[3] || "";
20167
- const isSelfClose = selfClose === "/";
20168
- const contentStart = m.index + full.length;
20169
- if (isSelfClose) {
20170
- if (local === "t") {
20171
- const para = owningPara();
20172
- if (para) para.tRanges.push({ contentStart: m.index, contentEnd: m.index + full.length, selfClosing: true, prefix: prefixOf(qname) });
20173
- } else if (local === "tab" || local === "fwSpace" || local === "hwSpace" || local === "br" || local === "lineBreak") {
20174
- if (!pendingT) {
20175
- const para = owningPara();
20176
- if (para) para.text += " ";
20177
- }
20178
- } else if (local === "run" || local === "r") {
20179
- const para = owningPara();
20180
- if (para && !para.selfCloseRun) para.selfCloseRun = { start: m.index, end: m.index + full.length };
20181
- } else if (local === "cellAddr") {
20182
- const cell = cellStack[cellStack.length - 1];
20183
- if (cell && insideCurrentTable(stack, tableStack)) {
20184
- const ca = parseInt(getAttr2(attrsRaw, "colAddr") || "", 10);
20185
- const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
20186
- if (!isNaN(ca)) cell.colAddr = ca;
20187
- if (!isNaN(ra)) cell.rowAddr = ra;
19901
+ for (const para of [...scan.bodyParagraphs, ...scan.excludedParagraphs]) {
19902
+ const existing = ledger.get(para);
19903
+ if (existing?.fullText !== void 0) continue;
19904
+ const text = matchText(para);
19905
+ for (const seg of scanInlineSegments(text)) {
19906
+ const matchKey = findMatchingKey(normalizeLabel(seg.label), normalizedValues);
19907
+ if (matchKey === void 0) continue;
19908
+ const newValue = normalizedValues.get(matchKey);
19909
+ const replacement = seg.valueStart === seg.valueEnd ? padInsertion(text, seg.valueStart, newValue) : newValue;
19910
+ const l = led(para);
19911
+ l.ranges.push({ start: seg.valueStart, end: seg.valueEnd, replacement });
19912
+ matchedLabels.add(matchKey);
19913
+ l.filledIdx.push(filled.length);
19914
+ l.matchKeys.push(matchKey);
19915
+ filled.push({ label: seg.label.trim(), value: newValue, row: -1, col: -1 });
19916
+ }
19917
+ }
19918
+ const splices = [];
19919
+ for (const [para, l] of ledger) {
19920
+ let paraSplices = null;
19921
+ if (l.fullText !== void 0) {
19922
+ paraSplices = buildParagraphSplices(para, l.fullText, xml);
19923
+ } else if (l.ranges.length > 0) {
19924
+ const sorted = [...l.ranges].sort((a, b) => a.start - b.start || a.end - b.end);
19925
+ const merged = [];
19926
+ for (const r of sorted) {
19927
+ const prev = merged[merged.length - 1];
19928
+ if (prev && r.start < prev.end) continue;
19929
+ merged.push(r);
20188
19930
  }
20189
- } else if (local === "cellSpan") {
20190
- const cell = cellStack[cellStack.length - 1];
20191
- if (cell && insideCurrentTable(stack, tableStack)) {
20192
- const cs = parseInt(getAttr2(attrsRaw, "colSpan") || "1", 10);
20193
- const rs = parseInt(getAttr2(attrsRaw, "rowSpan") || "1", 10);
20194
- cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs;
20195
- cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs;
19931
+ if (paraTText(para, xml) !== null) {
19932
+ const precise = [];
19933
+ let ok = true;
19934
+ for (const r of merged) {
19935
+ const sp = buildRangeSplices(para, xml, r.start, r.end, r.replacement);
19936
+ if (!sp) {
19937
+ ok = false;
19938
+ break;
19939
+ }
19940
+ precise.push(...sp);
19941
+ }
19942
+ paraSplices = ok ? precise : null;
19943
+ } else if (paraTextPureT(para, xml)) {
19944
+ let text = para.text;
19945
+ for (let k = merged.length - 1; k >= 0; k--) {
19946
+ const r = merged[k];
19947
+ text = text.slice(0, r.start) + r.replacement + text.slice(r.end);
19948
+ }
19949
+ paraSplices = buildParagraphSplices(para, text, xml);
19950
+ } else {
19951
+ paraSplices = null;
20196
19952
  }
20197
19953
  }
19954
+ if (paraSplices === null) {
19955
+ for (const idx of l.filledIdx) filled[idx] = null;
19956
+ for (const k of l.matchKeys) failedKeys.add(k);
19957
+ continue;
19958
+ }
19959
+ for (const k of l.matchKeys) succeededKeys.add(k);
19960
+ splices.push(...paraSplices);
19961
+ }
19962
+ if (splices.length > 0) {
19963
+ replacements.set(sectionPaths[si], encoder.encode(applySplices(xml, splices)));
19964
+ }
19965
+ }
19966
+ for (const k of failedKeys) {
19967
+ if (!succeededKeys.has(k)) matchedLabels.delete(k);
19968
+ }
19969
+ const cleanFilled = filled.filter((f) => f !== null);
19970
+ const unmatched = resolveUnmatched(normalizedValues, matchedLabels, values);
19971
+ const out = replacements.size > 0 ? patchZipEntries(u8, replacements) : new Uint8Array(u8);
19972
+ return {
19973
+ buffer: out.buffer.slice(out.byteOffset, out.byteOffset + out.byteLength),
19974
+ filled: cleanFilled,
19975
+ unmatched
19976
+ };
19977
+ }
19978
+
19979
+ // src/hwpx/generator.ts
19980
+ import JSZip6 from "jszip";
19981
+ var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
19982
+ var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
19983
+ var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
19984
+ var NS_OPF = "http://www.idpf.org/2007/opf/";
19985
+ var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
19986
+ var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
19987
+ var CHAR_NORMAL = 0;
19988
+ var CHAR_BOLD = 1;
19989
+ var CHAR_ITALIC = 2;
19990
+ var CHAR_BOLD_ITALIC = 3;
19991
+ var CHAR_CODE = 4;
19992
+ var CHAR_H1 = 5;
19993
+ var CHAR_H2 = 6;
19994
+ var CHAR_H3 = 7;
19995
+ var CHAR_H4 = 8;
19996
+ var CHAR_TABLE_HEADER = 9;
19997
+ var CHAR_QUOTE = 10;
19998
+ var PARA_NORMAL = 0;
19999
+ var PARA_H1 = 1;
20000
+ var PARA_H2 = 2;
20001
+ var PARA_H3 = 3;
20002
+ var PARA_H4 = 4;
20003
+ var PARA_CODE = 5;
20004
+ var PARA_QUOTE = 6;
20005
+ var PARA_LIST = 7;
20006
+ var DEFAULT_TEXT_COLOR = "#000000";
20007
+ function resolveTheme(theme) {
20008
+ return {
20009
+ h1: theme?.headingColors?.[1] ?? DEFAULT_TEXT_COLOR,
20010
+ h2: theme?.headingColors?.[2] ?? DEFAULT_TEXT_COLOR,
20011
+ h3: theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
20012
+ h4: theme?.headingColors?.[4] ?? theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
20013
+ body: theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
20014
+ quote: theme?.quoteColor ?? DEFAULT_TEXT_COLOR,
20015
+ /** quoteColor가 명시되었는지 — blockquote charPr 분기에 사용 (baseline 호환) */
20016
+ hasQuoteOption: theme?.quoteColor !== void 0,
20017
+ tableHeader: theme?.tableHeaderColor ?? theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
20018
+ tableHeaderBold: !!theme?.tableHeaderBold
20019
+ };
20020
+ }
20021
+ async function markdownToHwpx(markdown, options) {
20022
+ const theme = resolveTheme(options?.theme);
20023
+ const blocks = parseMarkdownToBlocks(markdown);
20024
+ const sectionXml = blocksToSectionXml(blocks, theme);
20025
+ const zip = new JSZip6();
20026
+ zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
20027
+ zip.file("META-INF/container.xml", generateContainerXml());
20028
+ zip.file("Contents/content.hpf", generateManifest());
20029
+ zip.file("Contents/header.xml", generateHeaderXml(theme));
20030
+ zip.file("Contents/section0.xml", sectionXml);
20031
+ zip.file("Preview/PrvText.txt", buildPrvText(blocks));
20032
+ return await zip.generateAsync({ type: "arraybuffer" });
20033
+ }
20034
+ function buildPrvText(blocks) {
20035
+ const lines = [];
20036
+ let bytes = 0;
20037
+ for (const b of blocks) {
20038
+ const text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
20039
+ if (!text) continue;
20040
+ lines.push(text);
20041
+ bytes += text.length * 3;
20042
+ if (bytes > 1024) break;
20043
+ }
20044
+ return lines.join("\n").slice(0, 1024);
20045
+ }
20046
+ function parseMarkdownToBlocks(md2) {
20047
+ const lines = md2.split("\n");
20048
+ const blocks = [];
20049
+ let i = 0;
20050
+ while (i < lines.length) {
20051
+ const line = lines[i];
20052
+ if (!line.trim()) {
20053
+ i++;
20054
+ continue;
20055
+ }
20056
+ const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
20057
+ if (fenceMatch) {
20058
+ const fence = fenceMatch[1];
20059
+ const lang = fenceMatch[2].trim();
20060
+ const codeLines = [];
20061
+ i++;
20062
+ while (i < lines.length && !lines[i].startsWith(fence)) {
20063
+ codeLines.push(lines[i]);
20064
+ i++;
20065
+ }
20066
+ if (i < lines.length) i++;
20067
+ blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
20198
20068
  continue;
20199
20069
  }
20200
- if (local === "t") {
20201
- const para = owningPara();
20202
- if (para) pendingT = { para, contentStart };
20203
- stack.push({ local, qname, contentStart });
20070
+ if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
20071
+ blocks.push({ type: "hr" });
20072
+ i++;
20204
20073
  continue;
20205
20074
  }
20206
- stack.push({ local, qname, contentStart });
20207
- if (local === "p") {
20208
- const para = {
20209
- sectionIndex,
20210
- kind: "excluded",
20211
- // 분류는 push 직후 스택 기준 (자기 자신 제외)
20212
- start: m.index,
20213
- tRanges: [],
20214
- text: ""
20215
- };
20216
- stack.pop();
20217
- para.kind = classifyPara();
20218
- stack.push({ local, qname, contentStart });
20219
- paraStack.push(para);
20220
- if (para.kind === "body" || para.kind === "draw") bodyParagraphs.push(para);
20221
- else if (para.kind === "cell") {
20222
- const cell = cellStack[cellStack.length - 1];
20223
- if (cell) cell.paragraphs.push(para);
20224
- }
20225
- } else if (local === "run" || local === "r") {
20226
- const para = owningPara();
20227
- if (para && para.runPrefix === void 0) para.runPrefix = prefixOf(qname);
20228
- } else if (local === "tbl") {
20229
- const table = {
20230
- sectionIndex,
20231
- start: m.index,
20232
- topLevel: false,
20233
- rows: [],
20234
- cellByAnchor: /* @__PURE__ */ new Map()
20235
- };
20236
- stack.pop();
20237
- table.topLevel = isTableTopLevel();
20238
- stack.push({ local, qname, contentStart });
20239
- tableStack.push(table);
20240
- rowStack.push([]);
20241
- if (table.topLevel) tables.push(table);
20242
- } else if (local === "tr") {
20243
- if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
20244
- } else if (local === "tc") {
20245
- cellStack.push({ colSpan: 1, rowSpan: 1, paragraphs: [], tables: [] });
20246
- } else if (local === "cellAddr" || local === "cellSpan") {
20247
- const cell = cellStack[cellStack.length - 1];
20248
- if (cell && insideCurrentTable(stack, tableStack)) {
20249
- if (local === "cellAddr") {
20250
- const ca = parseInt(getAttr2(attrsRaw, "colAddr") || "", 10);
20251
- const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
20252
- if (!isNaN(ca)) cell.colAddr = ca;
20253
- if (!isNaN(ra)) cell.rowAddr = ra;
20254
- } else {
20255
- const cs = parseInt(getAttr2(attrsRaw, "colSpan") || "1", 10);
20256
- const rs = parseInt(getAttr2(attrsRaw, "rowSpan") || "1", 10);
20257
- cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs;
20258
- cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs;
20075
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
20076
+ if (headingMatch) {
20077
+ blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
20078
+ i++;
20079
+ continue;
20080
+ }
20081
+ if (line.trimStart().startsWith("|")) {
20082
+ const tableRows = [];
20083
+ while (i < lines.length && lines[i].trimStart().startsWith("|")) {
20084
+ const row = lines[i];
20085
+ if (/^[\s|:\-]+$/.test(row)) {
20086
+ i++;
20087
+ continue;
20259
20088
  }
20089
+ const cells = row.split("|").slice(1, -1).map((c) => c.trim());
20090
+ if (cells.length > 0) tableRows.push(cells);
20091
+ i++;
20260
20092
  }
20261
- } else if (local === "header" || local === "footer") {
20262
- if (stack.some((f) => f.local === "ctrl")) {
20263
- ctrlSubStack.push({ kind: local, texts: [] });
20093
+ if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
20094
+ continue;
20095
+ }
20096
+ if (line.trimStart().startsWith("> ")) {
20097
+ const quoteLines = [];
20098
+ while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
20099
+ quoteLines.push(lines[i].replace(/^>\s?/, ""));
20100
+ i++;
20264
20101
  }
20265
- } else if (local === "tab" || local === "fwSpace" || local === "hwSpace" || local === "br" || local === "lineBreak") {
20266
- const para = owningPara();
20267
- if (para) para.text += " ";
20102
+ for (const ql of quoteLines) {
20103
+ blocks.push({ type: "blockquote", text: ql.trim() || "" });
20104
+ }
20105
+ continue;
20106
+ }
20107
+ const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
20108
+ if (listMatch) {
20109
+ const indent = Math.floor(listMatch[1].length / 2);
20110
+ const ordered = /\d/.test(listMatch[2]);
20111
+ blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
20112
+ i++;
20113
+ continue;
20268
20114
  }
20115
+ blocks.push({ type: "paragraph", text: line.trim() });
20116
+ i++;
20269
20117
  }
20270
- for (const para of bodyParagraphs) fillRunInsertPos(para, xml);
20271
- const fillTableInsertPos = (table, depth = 0) => {
20272
- if (depth > 16) return;
20273
- for (const row of table.rows) {
20274
- for (const cell of row) {
20275
- for (const para of cell.paragraphs) fillRunInsertPos(para, xml);
20276
- for (const nested of cell.tables) fillTableInsertPos(nested, depth + 1);
20277
- }
20118
+ return blocks;
20119
+ }
20120
+ function parseInlineMarkdown(text) {
20121
+ text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
20122
+ text = text.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_, t, u) => t || u);
20123
+ text = text.replace(/~~([^~]+)~~/g, "$1");
20124
+ const spans = [];
20125
+ const regex = /(`[^`]+`|\*{3}[^*]+\*{3}|\*{2}[^*]+\*{2}|\*[^*]+\*|_{2}[^_]+_{2}|_[^_]+_)/g;
20126
+ let lastIdx = 0;
20127
+ for (const match of text.matchAll(regex)) {
20128
+ const idx = match.index;
20129
+ if (idx > lastIdx) {
20130
+ spans.push({ text: text.slice(lastIdx, idx), bold: false, italic: false, code: false });
20278
20131
  }
20279
- };
20280
- for (const table of tables) fillTableInsertPos(table);
20281
- return { sectionIndex, xml, bodyParagraphs, tables, headerTexts, footerTexts };
20132
+ const raw = match[0];
20133
+ if (raw.startsWith("`")) {
20134
+ spans.push({ text: raw.slice(1, -1), bold: false, italic: false, code: true });
20135
+ } else if (raw.startsWith("***") || raw.startsWith("___")) {
20136
+ spans.push({ text: raw.slice(3, -3), bold: true, italic: true, code: false });
20137
+ } else if (raw.startsWith("**") || raw.startsWith("__")) {
20138
+ spans.push({ text: raw.slice(2, -2), bold: true, italic: false, code: false });
20139
+ } else {
20140
+ spans.push({ text: raw.slice(1, -1), bold: false, italic: true, code: false });
20141
+ }
20142
+ lastIdx = idx + raw.length;
20143
+ }
20144
+ if (lastIdx < text.length) {
20145
+ spans.push({ text: text.slice(lastIdx), bold: false, italic: false, code: false });
20146
+ }
20147
+ if (spans.length === 0) {
20148
+ spans.push({ text, bold: false, italic: false, code: false });
20149
+ }
20150
+ return spans;
20282
20151
  }
20283
- function getAttr2(attrsRaw, name) {
20284
- const re = new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`);
20285
- const m = attrsRaw.match(re);
20286
- return m ? m[1] ?? m[2] : void 0;
20152
+ function spanToCharPrId(span) {
20153
+ if (span.code) return CHAR_CODE;
20154
+ if (span.bold && span.italic) return CHAR_BOLD_ITALIC;
20155
+ if (span.bold) return CHAR_BOLD;
20156
+ if (span.italic) return CHAR_ITALIC;
20157
+ return CHAR_NORMAL;
20158
+ }
20159
+ function escapeXml(text) {
20160
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
20161
+ }
20162
+ function generateRuns(text, defaultCharPr = CHAR_NORMAL) {
20163
+ const spans = parseInlineMarkdown(text);
20164
+ return spans.map((span) => {
20165
+ const charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
20166
+ return `<hp:run charPrIDRef="${charId}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`;
20167
+ }).join("");
20168
+ }
20169
+ function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL) {
20170
+ if (paraPrId === PARA_CODE) {
20171
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
20172
+ }
20173
+ const runs = generateRuns(text, charPrId);
20174
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
20175
+ }
20176
+ function headingParaPrId(level) {
20177
+ if (level === 1) return PARA_H1;
20178
+ if (level === 2) return PARA_H2;
20179
+ if (level === 3) return PARA_H3;
20180
+ return PARA_H4;
20181
+ }
20182
+ function headingCharPrId(level) {
20183
+ if (level === 1) return CHAR_H1;
20184
+ if (level === 2) return CHAR_H2;
20185
+ if (level === 3) return CHAR_H3;
20186
+ return CHAR_H4;
20187
+ }
20188
+ function generateContainerXml() {
20189
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20190
+ <ocf:container xmlns:ocf="${NS_OCF}" xmlns:hpf="${NS_HPF}">
20191
+ <ocf:rootfiles>
20192
+ <ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/>
20193
+ </ocf:rootfiles>
20194
+ </ocf:container>`;
20195
+ }
20196
+ function generateManifest() {
20197
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20198
+ <opf:package xmlns:opf="${NS_OPF}" xmlns:hpf="${NS_HPF}" xmlns:hh="${NS_HEAD}">
20199
+ <opf:manifest>
20200
+ <opf:item id="header" href="Contents/header.xml" media-type="application/xml"/>
20201
+ <opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/>
20202
+ </opf:manifest>
20203
+ <opf:spine>
20204
+ <opf:itemref idref="header" linear="no"/>
20205
+ <opf:itemref idref="section0" linear="yes"/>
20206
+ </opf:spine>
20207
+ </opf:package>`;
20208
+ }
20209
+ function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR) {
20210
+ const boldAttr = bold ? ` bold="1"` : "";
20211
+ const italicAttr = italic ? ` italic="1"` : "";
20212
+ const effFont = bold ? 2 : fontId;
20213
+ return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="0"${boldAttr}${italicAttr}>
20214
+ <hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${effFont}" japanese="${effFont}" other="${effFont}" symbol="${effFont}" user="${effFont}"/>
20215
+ <hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
20216
+ <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
20217
+ <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
20218
+ <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
20219
+ </hh:charPr>`;
20220
+ }
20221
+ function paraPr(id, opts = {}) {
20222
+ const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0 } = opts;
20223
+ return ` <hh:paraPr id="${id}" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="1" suppressLineNumbers="0" checked="0" textDir="AUTO">
20224
+ <hh:align horizontal="${align}" vertical="BASELINE"/>
20225
+ <hh:heading type="NONE" idRef="0" level="0"/>
20226
+ <hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="BREAK_WORD" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
20227
+ <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
20228
+ <hh:margin indent="${indent}" left="0" right="0" prev="${spaceBefore}" next="${spaceAfter}"/>
20229
+ <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
20230
+ <hh:border borderFillIDRef="0" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
20231
+ </hh:paraPr>`;
20232
+ }
20233
+ function generateHeaderXml(theme) {
20234
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20235
+ <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" version="1.4" secCnt="1">
20236
+ <hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/>
20237
+ <hh:refList>
20238
+ <hh:fontfaces itemCnt="7">
20239
+ <hh:fontface lang="HANGUL" fontCnt="3">
20240
+ <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
20241
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20242
+ </hh:font>
20243
+ <hh:font id="1" face="\uD568\uCD08\uB86C\uB3CB\uC6C0" type="TTF" isEmbedded="0">
20244
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20245
+ </hh:font>
20246
+ <hh:font id="2" face="HY\uACAC\uACE0\uB515" type="TTF" isEmbedded="0">
20247
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20248
+ </hh:font>
20249
+ </hh:fontface>
20250
+ <hh:fontface lang="LATIN" fontCnt="3">
20251
+ <hh:font id="0" face="Times New Roman" type="TTF" isEmbedded="0">
20252
+ <hh:typeInfo familyType="FCAT_OLDSTYLE" weight="5" proportion="4" contrast="2" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="4"/>
20253
+ </hh:font>
20254
+ <hh:font id="1" face="Consolas" type="TTF" isEmbedded="0">
20255
+ <hh:typeInfo familyType="FCAT_MODERN" weight="5" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
20256
+ </hh:font>
20257
+ <hh:font id="2" face="Arial Black" type="TTF" isEmbedded="0">
20258
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
20259
+ </hh:font>
20260
+ </hh:fontface>
20261
+ <hh:fontface lang="HANJA" fontCnt="1">
20262
+ <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
20263
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20264
+ </hh:font>
20265
+ </hh:fontface>
20266
+ <hh:fontface lang="JAPANESE" fontCnt="1">
20267
+ <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
20268
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20269
+ </hh:font>
20270
+ </hh:fontface>
20271
+ <hh:fontface lang="OTHER" fontCnt="1">
20272
+ <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
20273
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20274
+ </hh:font>
20275
+ </hh:fontface>
20276
+ <hh:fontface lang="SYMBOL" fontCnt="1">
20277
+ <hh:font id="0" face="Symbol" type="TTF" isEmbedded="0">
20278
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20279
+ </hh:font>
20280
+ </hh:fontface>
20281
+ <hh:fontface lang="USER" fontCnt="1">
20282
+ <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
20283
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20284
+ </hh:font>
20285
+ </hh:fontface>
20286
+ </hh:fontfaces>
20287
+ <hh:borderFills itemCnt="2">
20288
+ <hh:borderFill id="0" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
20289
+ <hh:slash type="NONE" Crooked="0" isCounter="0"/>
20290
+ <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
20291
+ <hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/>
20292
+ <hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/>
20293
+ <hh:topBorder type="NONE" width="0.1 mm" color="#000000"/>
20294
+ <hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/>
20295
+ <hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>
20296
+ <hh:fillInfo/>
20297
+ </hh:borderFill>
20298
+ <hh:borderFill id="1" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
20299
+ <hh:slash type="NONE" Crooked="0" isCounter="0"/>
20300
+ <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
20301
+ <hh:leftBorder type="SOLID" width="0.12 mm" color="#000000"/>
20302
+ <hh:rightBorder type="SOLID" width="0.12 mm" color="#000000"/>
20303
+ <hh:topBorder type="SOLID" width="0.12 mm" color="#000000"/>
20304
+ <hh:bottomBorder type="SOLID" width="0.12 mm" color="#000000"/>
20305
+ <hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>
20306
+ <hh:fillInfo/>
20307
+ </hh:borderFill>
20308
+ </hh:borderFills>
20309
+ <hh:charProperties itemCnt="11">
20310
+ ${charPr(0, 1e3, false, false, 0, theme.body)}
20311
+ ${charPr(1, 1e3, true, false, 0, theme.body)}
20312
+ ${charPr(2, 1e3, false, true, 0, theme.body)}
20313
+ ${charPr(3, 1e3, true, true, 0, theme.body)}
20314
+ ${charPr(4, 900, false, false, 1)}
20315
+ ${charPr(5, 1800, true, false, 1, theme.h1)}
20316
+ ${charPr(6, 1400, true, false, 1, theme.h2)}
20317
+ ${charPr(7, 1200, true, false, 1, theme.h3)}
20318
+ ${charPr(8, 1100, true, false, 1, theme.h4)}
20319
+ ${charPr(CHAR_TABLE_HEADER, 1e3, theme.tableHeaderBold, false, 0, theme.tableHeader)}
20320
+ ${charPr(CHAR_QUOTE, 1e3, false, true, 0, theme.quote)}
20321
+ </hh:charProperties>
20322
+ <hh:tabProperties itemCnt="0"/>
20323
+ <hh:numberings itemCnt="0"/>
20324
+ <hh:bullets itemCnt="0"/>
20325
+ <hh:paraProperties itemCnt="8">
20326
+ ${paraPr(0)}
20327
+ ${paraPr(1, { align: "LEFT", spaceBefore: 800, spaceAfter: 200, lineSpacing: 180 })}
20328
+ ${paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: 170 })}
20329
+ ${paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: 160 })}
20330
+ ${paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: 160 })}
20331
+ ${paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400 })}
20332
+ ${paraPr(6, { align: "LEFT", lineSpacing: 150, indent: 600 })}
20333
+ ${paraPr(7, { align: "LEFT", lineSpacing: 160, indent: 600 })}
20334
+ </hh:paraProperties>
20335
+ <hh:styles itemCnt="1">
20336
+ <hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langIDRef="1042" lockForm="0"/>
20337
+ </hh:styles>
20338
+ </hh:refList>
20339
+ <hh:compatibleDocument targetProgram="HWP2018"/>
20340
+ </hh:head>`;
20287
20341
  }
20288
- function insideCurrentTable(stack, tableStack) {
20289
- if (tableStack.length === 0) return false;
20290
- for (let i = stack.length - 1; i >= 0; i--) {
20291
- const l = stack[i].local;
20292
- if (l === "tc") return true;
20293
- if (l === "tbl") return false;
20294
- }
20295
- return false;
20342
+ function generateSecPr() {
20343
+ return `<hp:secPr textDirection="HORIZONTAL" spaceColumns="1134" tabStop="8000" outlineShapeIDRef="0" memoShapeIDRef="0" textVerticalWidthHead="0" masterPageCnt="0"><hp:grid lineGrid="0" charGrid="0" wonggojiFormat="0"/><hp:startNum pageStartsOn="BOTH" page="0" pic="0" tbl="0" equation="0"/><hp:visibility hideFirstHeader="0" hideFirstFooter="0" hideFirstMasterPage="0" border="SHOW_ALL" fill="SHOW_ALL" hideFirstPageNum="0" hideFirstEmptyLine="0" showLineNumber="0"/><hp:pagePr landscape="WIDELY" width="59528" height="84188" gutterType="LEFT_ONLY"><hp:margin header="2835" footer="2835" gutter="0" left="5670" right="4252" top="8504" bottom="4252"/></hp:pagePr><hp:footNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="-1" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="283" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="EACH_COLUMN" beneathText="0"/></hp:footNotePr><hp:endNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="14692344" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="0" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="END_OF_DOCUMENT" beneathText="0"/></hp:endNotePr></hp:secPr>`;
20296
20344
  }
20297
- function fillRunInsertPos(para, xml) {
20298
- if (para.tRanges.length > 0) return;
20299
- const pEnd = findElementEnd(xml, para.start);
20300
- if (pEnd < 0) return;
20301
- const slice = xml.slice(para.start, pEnd);
20302
- const runOpen = slice.match(/<((?:[A-Za-z0-9_]+:)?run)(?:\s(?:"[^"]*"|'[^']*'|[^>"'])*?)?(\/?)>/);
20303
- if (!runOpen || runOpen.index === void 0) return;
20304
- if (runOpen[2] === "/") return;
20305
- const qname = runOpen[1];
20306
- const closeIdx = slice.indexOf(`</${qname}>`, runOpen.index);
20307
- if (closeIdx < 0) return;
20308
- para.runInsertPos = para.start + closeIdx;
20309
- para.runPrefix = prefixOf(qname);
20345
+ var TABLE_ID_BASE = 1e3;
20346
+ var tableIdCounter = TABLE_ID_BASE;
20347
+ function nextTableId() {
20348
+ return ++tableIdCounter;
20310
20349
  }
20311
- function findElementEnd(xml, start) {
20312
- const open = xml.slice(start).match(/^<([^\s/>!?]+)/);
20313
- if (!open) return -1;
20314
- const qname = open[1];
20315
- const re = new RegExp(`<${qname}(?=[\\s/>])(?:"[^"]*"|'[^']*'|[^>"'])*?(/?)>|</${qname}\\s*>`, "g");
20316
- re.lastIndex = start;
20317
- let depth = 0;
20318
- let mm;
20319
- while ((mm = re.exec(xml)) !== null) {
20320
- if (mm[0].startsWith("</")) {
20321
- depth--;
20322
- if (depth === 0) return mm.index + mm[0].length;
20323
- } else if (mm[1] !== "/") {
20324
- depth++;
20325
- }
20326
- }
20327
- return -1;
20350
+ function generateTable(rows, theme) {
20351
+ const rowCnt = rows.length;
20352
+ const colCnt = Math.max(...rows.map((r) => r.length), 1);
20353
+ const cellW = Math.floor(44e3 / colCnt);
20354
+ const cellH = 1500;
20355
+ const tblW = cellW * colCnt;
20356
+ const tblH = cellH * rowCnt;
20357
+ const tblId = nextTableId();
20358
+ const useHeaderStyle = theme.tableHeader !== theme.body || theme.tableHeaderBold;
20359
+ const trElements = rows.map((row, rowIdx) => {
20360
+ const cells = row.length < colCnt ? [...row, ...Array(colCnt - row.length).fill("")] : row;
20361
+ const isHeaderRow = rowIdx === 0;
20362
+ const headerCharPr = isHeaderRow && useHeaderStyle ? CHAR_TABLE_HEADER : CHAR_NORMAL;
20363
+ const tdElements = cells.map((cell, colIdx) => {
20364
+ const runs = generateRuns(cell, headerCharPr);
20365
+ const p = `<hp:p paraPrIDRef="0" styleIDRef="0">${runs}</hp:p>`;
20366
+ return `<hp:tc name="" header="${isHeaderRow ? 1 : 0}" hasMargin="0" protect="0" editable="1" dirty="0" borderFillIDRef="1"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="TOP" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">${p}</hp:subList><hp:cellAddr colAddr="${colIdx}" rowAddr="${rowIdx}"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="${cellW}" height="${cellH}"/><hp:cellMargin left="141" right="141" top="141" bottom="141"/></hp:tc>`;
20367
+ }).join("");
20368
+ return `<hp:tr>${tdElements}</hp:tr>`;
20369
+ }).join("");
20370
+ const tblInner = `<hp:sz width="${tblW}" widthRelTo="ABSOLUTE" height="${tblH}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="0" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="0" right="0" top="0" bottom="0"/><hp:inMargin left="510" right="510" top="141" bottom="141"/>` + trElements;
20371
+ const tbl = `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE" pageBreak="CELL" repeatHeader="0" rowCnt="${rowCnt}" colCnt="${colCnt}" cellSpacing="0" borderFillIDRef="1" noShading="0">${tblInner}</hp:tbl>`;
20372
+ return `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${tbl}</hp:run></hp:p>`;
20328
20373
  }
20329
- function finalizeTable(table) {
20330
- const hasAddr = table.rows.some((row) => row.some((c) => c.colAddr !== void 0 && c.rowAddr !== void 0));
20331
- if (hasAddr) {
20332
- for (const row of table.rows) {
20333
- for (const cell of row) {
20334
- if (cell.rowAddr !== void 0 && cell.colAddr !== void 0) {
20335
- table.cellByAnchor.set(`${cell.rowAddr},${cell.colAddr}`, cell);
20336
- }
20374
+ function blocksToSectionXml(blocks, theme) {
20375
+ const paraXmls = [];
20376
+ let isFirst = true;
20377
+ const orderedCounters = {};
20378
+ let prevWasOrdered = false;
20379
+ for (const block of blocks) {
20380
+ let xml = "";
20381
+ if (block.type !== "list_item" || !block.ordered) {
20382
+ if (prevWasOrdered) {
20383
+ for (const k of Object.keys(orderedCounters)) delete orderedCounters[+k];
20337
20384
  }
20385
+ prevWasOrdered = false;
20338
20386
  }
20339
- return;
20340
- }
20341
- const numRows = table.rows.length;
20342
- const occupied = Array.from({ length: numRows }, () => []);
20343
- for (let rowIdx = 0; rowIdx < numRows; rowIdx++) {
20344
- let colIdx = 0;
20345
- for (const cell of table.rows[rowIdx]) {
20346
- while (occupied[rowIdx][colIdx]) colIdx++;
20347
- cell.rowAddr = rowIdx;
20348
- cell.colAddr = colIdx;
20349
- table.cellByAnchor.set(`${rowIdx},${colIdx}`, cell);
20350
- for (let r = rowIdx; r < Math.min(rowIdx + cell.rowSpan, numRows); r++) {
20351
- for (let c = colIdx; c < colIdx + cell.colSpan; c++) {
20352
- occupied[r][c] = true;
20387
+ switch (block.type) {
20388
+ case "heading": {
20389
+ const pId = headingParaPrId(block.level || 1);
20390
+ const cId = headingCharPrId(block.level || 1);
20391
+ xml = generateParagraph(block.text || "", pId, cId);
20392
+ break;
20393
+ }
20394
+ case "paragraph":
20395
+ xml = generateParagraph(block.text || "");
20396
+ break;
20397
+ case "code_block": {
20398
+ const codeLines = (block.text || "").split("\n");
20399
+ xml = codeLines.map((line) => generateParagraph(line || " ", PARA_CODE)).join("\n ");
20400
+ break;
20401
+ }
20402
+ case "blockquote":
20403
+ xml = generateParagraph(
20404
+ block.text || "",
20405
+ PARA_QUOTE,
20406
+ theme.hasQuoteOption ? CHAR_QUOTE : CHAR_NORMAL
20407
+ );
20408
+ break;
20409
+ case "list_item": {
20410
+ const indent = block.indent || 0;
20411
+ let marker;
20412
+ if (block.ordered) {
20413
+ orderedCounters[indent] = (orderedCounters[indent] || 0) + 1;
20414
+ for (const k of Object.keys(orderedCounters)) {
20415
+ if (+k > indent) delete orderedCounters[+k];
20416
+ }
20417
+ marker = `${orderedCounters[indent]}. `;
20418
+ prevWasOrdered = true;
20419
+ } else {
20420
+ marker = "\xB7 ";
20421
+ if (prevWasOrdered) {
20422
+ for (const k of Object.keys(orderedCounters)) delete orderedCounters[+k];
20423
+ }
20424
+ prevWasOrdered = false;
20353
20425
  }
20426
+ const indentPrefix = " ".repeat(indent);
20427
+ xml = generateParagraph(indentPrefix + marker + (block.text || ""), PARA_LIST);
20428
+ break;
20354
20429
  }
20355
- colIdx += cell.colSpan;
20356
- }
20357
- }
20358
- }
20359
- function buildParagraphSplices(para, newText, xml) {
20360
- const escaped = escapeXmlText(newText);
20361
- if (para.tRanges.length > 0) {
20362
- const splices = [];
20363
- const first = para.tRanges[0];
20364
- if (first.selfClosing) {
20365
- const prefix = first.prefix ? first.prefix + ":" : "";
20366
- splices.push({ start: first.contentStart, end: first.contentEnd, replacement: `<${prefix}t>${escaped}</${prefix}t>` });
20367
- } else {
20368
- splices.push({ start: first.contentStart, end: first.contentEnd, replacement: escaped });
20430
+ case "hr":
20431
+ xml = `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0"><hp:t>\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500</hp:t></hp:run></hp:p>`;
20432
+ break;
20433
+ case "table":
20434
+ if (block.rows) {
20435
+ if (isFirst) {
20436
+ const secRun = `<hp:run charPrIDRef="0">${generateSecPr()}<hp:t></hp:t></hp:run>`;
20437
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
20438
+ isFirst = false;
20439
+ }
20440
+ xml = generateTable(block.rows, theme);
20441
+ }
20442
+ break;
20369
20443
  }
20370
- for (let i = 1; i < para.tRanges.length; i++) {
20371
- const r = para.tRanges[i];
20372
- if (!r.selfClosing && r.contentStart < r.contentEnd) {
20373
- splices.push({ start: r.contentStart, end: r.contentEnd, replacement: "" });
20374
- }
20444
+ if (!xml) continue;
20445
+ if (isFirst && block.type !== "table") {
20446
+ xml = xml.replace(
20447
+ /<hp:run charPrIDRef="(\d+)">/,
20448
+ `<hp:run charPrIDRef="$1">${generateSecPr()}`
20449
+ );
20450
+ isFirst = false;
20375
20451
  }
20376
- return splices;
20377
- }
20378
- if (para.runInsertPos !== void 0) {
20379
- if (!newText) return [];
20380
- const prefix = para.runPrefix ? para.runPrefix + ":" : "";
20381
- return [{ start: para.runInsertPos, end: para.runInsertPos, replacement: `<${prefix}t>${escaped}</${prefix}t>` }];
20452
+ paraXmls.push(xml);
20382
20453
  }
20383
- if (para.selfCloseRun && xml) {
20384
- if (!newText) return [];
20385
- const { start, end } = para.selfCloseRun;
20386
- const tag = xml.slice(start, end);
20387
- const qm = tag.match(/^<([^\s/>]+)/);
20388
- if (!qm || !tag.endsWith("/>")) return null;
20389
- const qname = qm[1];
20390
- const colon = qname.indexOf(":");
20391
- const prefix = colon >= 0 ? qname.slice(0, colon) + ":" : "";
20392
- const opened = tag.slice(0, tag.length - 2).trimEnd() + ">";
20393
- return [{ start, end, replacement: `${opened}<${prefix}t>${escaped}</${prefix}t></${qname}>` }];
20454
+ if (paraXmls.length === 0) {
20455
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${generateSecPr()}<hp:t></hp:t></hp:run></hp:p>`);
20394
20456
  }
20395
- return newText ? null : [];
20457
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20458
+ <hs:sec xmlns:hs="${NS_SECTION}" xmlns:hp="${NS_PARA}">
20459
+ ${paraXmls.join("\n ")}
20460
+ </hs:sec>`;
20396
20461
  }
20397
- function applySplices(xml, splices) {
20398
- const sorted = [...splices].sort((a, b) => a.start - b.start);
20399
- for (let i = 1; i < sorted.length; i++) {
20400
- if (sorted[i].start < sorted[i - 1].end) {
20401
- throw new Error("\uC18C\uC2A4\uB9F5 splice \uBC94\uC704 \uACB9\uCE68 \u2014 \uB0B4\uBD80 \uC624\uB958");
20402
- }
20462
+
20463
+ // src/diff/text-diff.ts
20464
+ function similarity(a, b) {
20465
+ if (a === b) return 1;
20466
+ if (!a || !b) return 0;
20467
+ const maxLen = Math.max(a.length, b.length);
20468
+ if (maxLen === 0) return 1;
20469
+ return 1 - levenshtein(a, b) / maxLen;
20470
+ }
20471
+ function normalizedSimilarity(a, b) {
20472
+ return similarity(normalize(a), normalize(b));
20473
+ }
20474
+ function normalize(s) {
20475
+ return s.replace(/\s+/g, " ").trim();
20476
+ }
20477
+ var MAX_LEVENSHTEIN_LEN = 1e4;
20478
+ function levenshtein(a, b) {
20479
+ if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
20480
+ const sampleLen = Math.min(500, a.length, b.length);
20481
+ let diffs = 0;
20482
+ for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
20483
+ const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
20484
+ return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
20403
20485
  }
20404
- let result = xml;
20405
- for (let i = sorted.length - 1; i >= 0; i--) {
20406
- const s = sorted[i];
20407
- result = result.slice(0, s.start) + s.replacement + result.slice(s.end);
20486
+ if (a.length > b.length) [a, b] = [b, a];
20487
+ const m = a.length;
20488
+ const n = b.length;
20489
+ let prev = Array.from({ length: m + 1 }, (_, i) => i);
20490
+ let curr = new Array(m + 1);
20491
+ for (let j = 1; j <= n; j++) {
20492
+ curr[0] = j;
20493
+ for (let i = 1; i <= m; i++) {
20494
+ if (a[i - 1] === b[j - 1]) {
20495
+ curr[i] = prev[i - 1];
20496
+ } else {
20497
+ curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
20498
+ }
20499
+ }
20500
+ ;
20501
+ [prev, curr] = [curr, prev];
20408
20502
  }
20409
- return result;
20503
+ return prev[m];
20410
20504
  }
20411
20505
 
20412
- // src/roundtrip/zip-patch.ts
20413
- import { deflateRawSync } from "zlib";
20414
- var EOCD_SIG = 101010256;
20415
- var CD_SIG = 33639248;
20416
- var LOCAL_SIG = 67324752;
20417
- var ZIP64_EOCD_LOC_SIG = 117853008;
20418
- function copyBytes(buf, start, end) {
20419
- return new Uint8Array(buf.subarray(start, end));
20506
+ // src/diff/compare.ts
20507
+ var SIMILARITY_THRESHOLD = 0.4;
20508
+ async function compare(bufferA, bufferB, options) {
20509
+ const [resultA, resultB] = await Promise.all([
20510
+ parse(bufferA, options),
20511
+ parse(bufferB, options)
20512
+ ]);
20513
+ if (!resultA.success) throw new Error(`\uBB38\uC11CA \uD30C\uC2F1 \uC2E4\uD328: ${resultA.error}`);
20514
+ if (!resultB.success) throw new Error(`\uBB38\uC11CB \uD30C\uC2F1 \uC2E4\uD328: ${resultB.error}`);
20515
+ return diffBlocks(resultA.blocks, resultB.blocks);
20420
20516
  }
20421
- function parseCentralDirectory(buf) {
20422
- const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
20423
- const minEocd = Math.max(0, buf.length - 22 - 65535);
20424
- let eocdOffset = -1;
20425
- for (let i = buf.length - 22; i >= minEocd; i--) {
20426
- if (view.getUint32(i, true) === EOCD_SIG && i + 22 + view.getUint16(i + 20, true) === buf.length) {
20427
- eocdOffset = i;
20428
- break;
20517
+ function diffBlocks(blocksA, blocksB) {
20518
+ const aligned = alignBlocks(blocksA, blocksB);
20519
+ const stats = { added: 0, removed: 0, modified: 0, unchanged: 0 };
20520
+ const diffs = [];
20521
+ for (const [a, b] of aligned) {
20522
+ if (a && b) {
20523
+ const sim = blockSimilarity(a, b);
20524
+ if (sim >= 0.99) {
20525
+ diffs.push({ type: "unchanged", before: a, after: b, similarity: 1 });
20526
+ stats.unchanged++;
20527
+ } else {
20528
+ const diff = { type: "modified", before: a, after: b, similarity: sim };
20529
+ if (a.type === "table" && b.type === "table" && a.table && b.table) {
20530
+ diff.cellDiffs = diffTableCells(a.table, b.table);
20531
+ }
20532
+ diffs.push(diff);
20533
+ stats.modified++;
20534
+ }
20535
+ } else if (a) {
20536
+ diffs.push({ type: "removed", before: a });
20537
+ stats.removed++;
20538
+ } else if (b) {
20539
+ diffs.push({ type: "added", after: b });
20540
+ stats.added++;
20429
20541
  }
20430
20542
  }
20431
- if (eocdOffset < 0) {
20432
- for (let i = buf.length - 22; i >= minEocd; i--) {
20433
- if (view.getUint32(i, true) !== EOCD_SIG) continue;
20434
- if (i + 22 + view.getUint16(i + 20, true) > buf.length) continue;
20435
- const cand = view.getUint32(i + 16, true);
20436
- if (cand < buf.length - 4 && view.getUint32(cand, true) === CD_SIG) {
20437
- eocdOffset = i;
20438
- break;
20543
+ return { stats, diffs };
20544
+ }
20545
+ function alignBlocks(a, b) {
20546
+ const m = a.length, n = b.length;
20547
+ if (m * n > 1e7) return fallbackAlign(a, b);
20548
+ const simCache = /* @__PURE__ */ new Map();
20549
+ const getSim = (i2, j2) => {
20550
+ const key = `${i2},${j2}`;
20551
+ let v = simCache.get(key);
20552
+ if (v === void 0) {
20553
+ v = blockSimilarity(a[i2], b[j2]);
20554
+ simCache.set(key, v);
20555
+ }
20556
+ return v;
20557
+ };
20558
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
20559
+ for (let i2 = 1; i2 <= m; i2++) {
20560
+ for (let j2 = 1; j2 <= n; j2++) {
20561
+ if (getSim(i2 - 1, j2 - 1) >= SIMILARITY_THRESHOLD) {
20562
+ dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
20563
+ } else {
20564
+ dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
20439
20565
  }
20440
20566
  }
20441
20567
  }
20442
- if (eocdOffset < 0) throw new KordocError("ZIP EOCD\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
20443
- const totalEntries = view.getUint16(eocdOffset + 10, true);
20444
- const cdSize = view.getUint32(eocdOffset + 12, true);
20445
- const cdOffset = view.getUint32(eocdOffset + 16, true);
20446
- if (cdOffset === 4294967295 || totalEntries === 65535) throw new KordocError("ZIP64\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
20447
- if (eocdOffset >= 20 && view.getUint32(eocdOffset - 20, true) === ZIP64_EOCD_LOC_SIG) {
20448
- throw new KordocError("ZIP64\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
20449
- }
20450
- const decoder = new TextDecoder("utf-8");
20451
- const entries = [];
20452
- let pos = cdOffset;
20453
- for (let i = 0; i < totalEntries; i++) {
20454
- if (view.getUint32(pos, true) !== CD_SIG) throw new KordocError("ZIP Central Directory \uC190\uC0C1");
20455
- const flags = view.getUint16(pos + 8, true);
20456
- const method = view.getUint16(pos + 10, true);
20457
- const crc = view.getUint32(pos + 16, true);
20458
- const compSize = view.getUint32(pos + 20, true);
20459
- const uncompSize = view.getUint32(pos + 24, true);
20460
- const nameLen = view.getUint16(pos + 28, true);
20461
- const extraLen = view.getUint16(pos + 30, true);
20462
- const commentLen = view.getUint16(pos + 32, true);
20463
- const localOffset = view.getUint32(pos + 42, true);
20464
- if (compSize === 4294967295 || uncompSize === 4294967295 || localOffset === 4294967295) {
20465
- throw new KordocError("ZIP64\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
20568
+ const pairs = [];
20569
+ let i = m, j = n;
20570
+ while (i > 0 && j > 0) {
20571
+ if (getSim(i - 1, j - 1) >= SIMILARITY_THRESHOLD && dp[i][j] === dp[i - 1][j - 1] + 1) {
20572
+ pairs.push([i - 1, j - 1]);
20573
+ i--;
20574
+ j--;
20575
+ } else if (dp[i - 1][j] >= dp[i][j - 1]) {
20576
+ i--;
20577
+ } else {
20578
+ j--;
20466
20579
  }
20467
- const name = decoder.decode(buf.subarray(pos + 46, pos + 46 + nameLen));
20468
- const cdEnd = pos + 46 + nameLen + extraLen + commentLen;
20469
- entries.push({ cdStart: pos, cdEnd, name, flags, method, crc, compSize, uncompSize, localOffset });
20470
- pos = cdEnd;
20471
20580
  }
20472
- return { entries, cdOffset, cdSize, eocdOffset };
20473
- }
20474
- var CRC_TABLE = (() => {
20475
- const table = new Uint32Array(256);
20476
- for (let n = 0; n < 256; n++) {
20477
- let c = n;
20478
- for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
20479
- table[n] = c >>> 0;
20581
+ pairs.reverse();
20582
+ const result = [];
20583
+ let ai = 0, bi = 0;
20584
+ for (const [pi, pj] of pairs) {
20585
+ while (ai < pi) result.push([a[ai++], null]);
20586
+ while (bi < pj) result.push([null, b[bi++]]);
20587
+ result.push([a[ai++], b[bi++]]);
20480
20588
  }
20481
- return table;
20482
- })();
20483
- function crc32(data) {
20484
- let crc = 4294967295;
20485
- for (let i = 0; i < data.length; i++) {
20486
- crc = CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
20589
+ while (ai < m) result.push([a[ai++], null]);
20590
+ while (bi < n) result.push([null, b[bi++]]);
20591
+ return result;
20592
+ }
20593
+ function fallbackAlign(a, b) {
20594
+ const result = [];
20595
+ const len = Math.max(a.length, b.length);
20596
+ for (let i = 0; i < len; i++) {
20597
+ result.push([a[i] || null, b[i] || null]);
20487
20598
  }
20488
- return (crc ^ 4294967295) >>> 0;
20599
+ return result;
20489
20600
  }
20490
- function patchZipEntries(original, replacements) {
20491
- const { entries, cdOffset, eocdOffset } = parseCentralDirectory(original);
20492
- const view = new DataView(original.buffer, original.byteOffset, original.byteLength);
20493
- for (const name of replacements.keys()) {
20494
- if (!entries.some((e) => e.name === name)) throw new KordocError(`ZIP\uC5D0 \uC5C6\uB294 \uC5D4\uD2B8\uB9AC: ${name}`);
20601
+ function blockSimilarity(a, b) {
20602
+ if (a.type !== b.type) return 0;
20603
+ if (a.text !== void 0 && b.text !== void 0) {
20604
+ return normalizedSimilarity(a.text || "", b.text || "");
20495
20605
  }
20496
- const byLocal = [...entries].sort((a, b) => a.localOffset - b.localOffset);
20497
- const segments = [];
20498
- const newLocalOffset = /* @__PURE__ */ new Map();
20499
- const newMeta = /* @__PURE__ */ new Map();
20500
- let offset = 0;
20501
- for (let i = 0; i < byLocal.length; i++) {
20502
- const e = byLocal[i];
20503
- const segEnd = i + 1 < byLocal.length ? byLocal[i + 1].localOffset : cdOffset;
20504
- newLocalOffset.set(e, offset);
20505
- const newData = replacements.get(e.name);
20506
- if (newData === void 0) {
20507
- const seg = original.subarray(e.localOffset, segEnd);
20508
- segments.push(seg);
20509
- offset += seg.length;
20510
- continue;
20511
- }
20512
- if (view.getUint32(e.localOffset, true) !== LOCAL_SIG) throw new KordocError("ZIP \uB85C\uCEEC \uD5E4\uB354 \uC2DC\uADF8\uB2C8\uCC98 \uBD88\uC77C\uCE58");
20513
- const nameLen = view.getUint16(e.localOffset + 26, true);
20514
- const extraLen = view.getUint16(e.localOffset + 28, true);
20515
- const headerLen = 30 + nameLen + extraLen;
20516
- const header = copyBytes(original, e.localOffset, e.localOffset + headerLen);
20517
- const hview = new DataView(header.buffer, header.byteOffset, header.byteLength);
20518
- const method = e.method;
20519
- const compData = method === 0 ? newData : new Uint8Array(deflateRawSync(newData));
20520
- const crc = crc32(newData);
20521
- const flags = e.flags & ~8;
20522
- hview.setUint16(6, flags, true);
20523
- hview.setUint32(14, crc, true);
20524
- hview.setUint32(18, compData.length, true);
20525
- hview.setUint32(22, newData.length, true);
20526
- segments.push(header, compData);
20527
- offset += headerLen + compData.length;
20528
- newMeta.set(e, { crc, compSize: compData.length, uncompSize: newData.length, flags });
20606
+ if (a.type === "table" && a.table && b.table) {
20607
+ return tableSimilarity(a.table, b.table);
20529
20608
  }
20530
- const newCdOffset = offset;
20531
- for (const e of entries) {
20532
- const cd = copyBytes(original, e.cdStart, e.cdEnd);
20533
- const cview = new DataView(cd.buffer, cd.byteOffset, cd.byteLength);
20534
- cview.setUint32(42, newLocalOffset.get(e), true);
20535
- const meta = newMeta.get(e);
20536
- if (meta) {
20537
- cview.setUint16(8, meta.flags, true);
20538
- cview.setUint32(16, meta.crc, true);
20539
- cview.setUint32(20, meta.compSize, true);
20540
- cview.setUint32(24, meta.uncompSize, true);
20609
+ if (a.type === b.type) return 1;
20610
+ return 0;
20611
+ }
20612
+ function tableSimilarity(a, b) {
20613
+ const dimSim = 1 - Math.abs(a.rows * a.cols - b.rows * b.cols) / Math.max(a.rows * a.cols, b.rows * b.cols, 1);
20614
+ const textsA = a.cells.flat().map((c) => c.text).join(" ");
20615
+ const textsB = b.cells.flat().map((c) => c.text).join(" ");
20616
+ const contentSim = normalizedSimilarity(textsA, textsB);
20617
+ return dimSim * 0.3 + contentSim * 0.7;
20618
+ }
20619
+ function diffTableCells(a, b) {
20620
+ const maxRows = Math.max(a.rows, b.rows);
20621
+ const maxCols = Math.max(a.cols, b.cols);
20622
+ const result = [];
20623
+ for (let r = 0; r < maxRows; r++) {
20624
+ const row = [];
20625
+ for (let c = 0; c < maxCols; c++) {
20626
+ const cellA = r < a.rows && c < a.cols ? a.cells[r][c].text : void 0;
20627
+ const cellB = r < b.rows && c < b.cols ? b.cells[r][c].text : void 0;
20628
+ let type;
20629
+ if (cellA === void 0) type = "added";
20630
+ else if (cellB === void 0) type = "removed";
20631
+ else if (cellA === cellB) type = "unchanged";
20632
+ else type = "modified";
20633
+ row.push({ type, before: cellA, after: cellB });
20541
20634
  }
20542
- segments.push(cd);
20543
- offset += cd.length;
20544
- }
20545
- const newCdSize = offset - newCdOffset;
20546
- const eocd = copyBytes(original, eocdOffset);
20547
- const eview = new DataView(eocd.buffer, eocd.byteOffset, eocd.byteLength);
20548
- eview.setUint32(12, newCdSize, true);
20549
- eview.setUint32(16, newCdOffset, true);
20550
- segments.push(eocd);
20551
- offset += eocd.length;
20552
- const result = new Uint8Array(offset);
20553
- let pos = 0;
20554
- for (const seg of segments) {
20555
- result.set(seg, pos);
20556
- pos += seg.length;
20635
+ result.push(row);
20557
20636
  }
20558
20637
  return result;
20559
20638
  }
20560
20639
 
20640
+ // src/roundtrip/patcher.ts
20641
+ import JSZip7 from "jszip";
20642
+
20561
20643
  // src/roundtrip/markdown-units.ts
20562
20644
  function splitMarkdownUnits(md2) {
20563
20645
  const lines = md2.split("\n");
@@ -21073,6 +21155,41 @@ function applyCellEdit(table, scanTable, gridR, gridC, newLines, ctx, before, af
21073
21155
  return 1;
21074
21156
  }
21075
21157
 
21158
+ // src/roundtrip/hwpx-entries.ts
21159
+ async function resolveSectionEntryNames(zip) {
21160
+ for (const mp of ["Contents/content.hpf", "content.hpf"]) {
21161
+ const f = zip.file(mp);
21162
+ if (!f) continue;
21163
+ const xml = await f.async("text");
21164
+ const paths = sectionPathsFromManifest(xml).filter((p) => zip.file(p) !== null);
21165
+ if (paths.length > 0) return paths;
21166
+ }
21167
+ return Object.keys(zip.files).filter((n) => /[Ss]ection\d+\.xml$/.test(n)).sort();
21168
+ }
21169
+ function sectionPathsFromManifest(xml) {
21170
+ const isSectionId = (id) => /^s/i.test(id) || id.toLowerCase().includes("section");
21171
+ const attr = (tag, name) => {
21172
+ const m = tag.match(new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`));
21173
+ return m ? m[1] ?? m[2] : "";
21174
+ };
21175
+ const idToHref = /* @__PURE__ */ new Map();
21176
+ for (const m of xml.matchAll(/<opf:item(\s(?:"[^"]*"|'[^']*'|[^>"'])*?)\/?>/g)) {
21177
+ const id = attr(m[1], "id");
21178
+ let href = attr(m[1], "href");
21179
+ const mediaType = attr(m[1], "media-type");
21180
+ if (!isSectionId(id) && !mediaType.includes("xml")) continue;
21181
+ if (!href.startsWith("/") && !href.startsWith("Contents/") && isSectionId(id)) href = "Contents/" + href;
21182
+ if (id) idToHref.set(id, href);
21183
+ }
21184
+ const ordered = [];
21185
+ for (const m of xml.matchAll(/<opf:itemref(\s(?:"[^"]*"|'[^']*'|[^>"'])*?)\/?>/g)) {
21186
+ const href = idToHref.get(attr(m[1], "idref"));
21187
+ if (href) ordered.push(href);
21188
+ }
21189
+ if (ordered.length > 0) return ordered;
21190
+ return Array.from(idToHref.entries()).filter(([id]) => isSectionId(id)).sort((a, b) => a[0].localeCompare(b[0])).map(([, href]) => href);
21191
+ }
21192
+
21076
21193
  // src/roundtrip/patcher.ts
21077
21194
  async function patchHwpx(original, editedMarkdown, options) {
21078
21195
  const skipped = [];
@@ -21431,39 +21548,6 @@ function unitToBlock(u) {
21431
21548
  function u8ToArrayBuffer(u8) {
21432
21549
  return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength);
21433
21550
  }
21434
- async function resolveSectionEntryNames(zip) {
21435
- for (const mp of ["Contents/content.hpf", "content.hpf"]) {
21436
- const f = zip.file(mp);
21437
- if (!f) continue;
21438
- const xml = await f.async("text");
21439
- const paths = sectionPathsFromManifest(xml).filter((p) => zip.file(p) !== null);
21440
- if (paths.length > 0) return paths;
21441
- }
21442
- return Object.keys(zip.files).filter((n) => /[Ss]ection\d+\.xml$/.test(n)).sort();
21443
- }
21444
- function sectionPathsFromManifest(xml) {
21445
- const isSectionId = (id) => /^s/i.test(id) || id.toLowerCase().includes("section");
21446
- const attr = (tag, name) => {
21447
- const m = tag.match(new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`));
21448
- return m ? m[1] ?? m[2] : "";
21449
- };
21450
- const idToHref = /* @__PURE__ */ new Map();
21451
- for (const m of xml.matchAll(/<opf:item(\s(?:"[^"]*"|'[^']*'|[^>"'])*?)\/?>/g)) {
21452
- const id = attr(m[1], "id");
21453
- let href = attr(m[1], "href");
21454
- const mediaType = attr(m[1], "media-type");
21455
- if (!isSectionId(id) && !mediaType.includes("xml")) continue;
21456
- if (!href.startsWith("/") && !href.startsWith("Contents/") && isSectionId(id)) href = "Contents/" + href;
21457
- if (id) idToHref.set(id, href);
21458
- }
21459
- const ordered = [];
21460
- for (const m of xml.matchAll(/<opf:itemref(\s(?:"[^"]*"|'[^']*'|[^>"'])*?)\/?>/g)) {
21461
- const href = idToHref.get(attr(m[1], "idref"));
21462
- if (href) ordered.push(href);
21463
- }
21464
- if (ordered.length > 0) return ordered;
21465
- return Array.from(idToHref.entries()).filter(([id]) => isSectionId(id)).sort((a, b) => a[0].localeCompare(b[0])).map(([, href]) => href);
21466
- }
21467
21551
 
21468
21552
  // src/roundtrip/hwp5-patch.ts
21469
21553
  import { deflateRawSync as deflateRawSync2 } from "zlib";
@@ -22297,6 +22381,350 @@ function stageParaPatch(scan, para, newPlain, skip) {
22297
22381
  return 1;
22298
22382
  }
22299
22383
 
22384
+ // src/roundtrip/session.ts
22385
+ import JSZip8 from "jszip";
22386
+ async function buildState(bytes) {
22387
+ const parsed = await parseHwpxDocument(u8ToArrayBuffer2(bytes));
22388
+ const zip = await JSZip8.loadAsync(bytes);
22389
+ const sectionPaths = await resolveSectionEntryNames(zip);
22390
+ if (sectionPaths.length === 0) {
22391
+ throw new Error("HWPX \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
22392
+ }
22393
+ const scans = [];
22394
+ for (let i = 0; i < sectionPaths.length; i++) {
22395
+ const xml = await zip.file(sectionPaths[i]).async("text");
22396
+ scans.push(scanSectionXml(xml, i));
22397
+ }
22398
+ const paraMap = resolveParagraphMappings(parsed.blocks, scans);
22399
+ const scanTables = scans.flatMap((s) => s.tables.filter((t) => t.rows.length > 0));
22400
+ const tableOrdinals = buildTableOrdinals(parsed.blocks);
22401
+ const fragmentBlocks = /* @__PURE__ */ new Set();
22402
+ const unitBlocks = /* @__PURE__ */ new Set();
22403
+ for (const u of buildOrigUnits(parsed.blocks)) {
22404
+ unitBlocks.add(u.blockIdx);
22405
+ if (u.fragment) fragmentBlocks.add(u.blockIdx);
22406
+ }
22407
+ for (let i = 0; i < parsed.blocks.length; i++) {
22408
+ const b = parsed.blocks[i];
22409
+ if ((b.type === "paragraph" || b.type === "heading") && b.text && !unitBlocks.has(i)) {
22410
+ fragmentBlocks.add(i);
22411
+ }
22412
+ }
22413
+ return {
22414
+ bytes,
22415
+ blocks: parsed.blocks,
22416
+ markdown: parsed.markdown,
22417
+ sectionPaths,
22418
+ scans,
22419
+ paraMap,
22420
+ scanTables,
22421
+ tableOrdinals,
22422
+ fragmentBlocks
22423
+ };
22424
+ }
22425
+ function u8ToArrayBuffer2(u8) {
22426
+ return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength);
22427
+ }
22428
+ function irCellLines(text) {
22429
+ return stripCellTokens(sanitizeText(text)).split("\n").map((s) => s.trim()).filter(Boolean);
22430
+ }
22431
+ var HwpxSession = class _HwpxSession {
22432
+ state;
22433
+ constructor(state) {
22434
+ this.state = state;
22435
+ }
22436
+ /** HWPX 바이트로 세션을 연다 (입력은 복사되어 외부 변이와 격리) */
22437
+ static async open(input) {
22438
+ const bytes = input instanceof Uint8Array ? new Uint8Array(input) : new Uint8Array(input.slice(0));
22439
+ return new _HwpxSession(await buildState(bytes));
22440
+ }
22441
+ /** 현재 문서의 IR 블록 — patchBlocks 후 갱신되므로 호출마다 다시 읽을 것 */
22442
+ get blocks() {
22443
+ return this.state.blocks;
22444
+ }
22445
+ /** 현재 문서의 마크다운 */
22446
+ get markdown() {
22447
+ return this.state.markdown;
22448
+ }
22449
+ /** 현재 문서 바이트 (복사본) */
22450
+ get bytes() {
22451
+ return new Uint8Array(this.state.bytes);
22452
+ }
22453
+ /** 블록 → 원본 위치 참조. 매핑 실패 시 undefined */
22454
+ sourceRef(blockIndex) {
22455
+ const st = this.state;
22456
+ const block = st.blocks[blockIndex];
22457
+ if (!block) return void 0;
22458
+ if (block.type === "paragraph" || block.type === "heading") {
22459
+ const para = st.paraMap.get(blockIndex)?.para;
22460
+ if (!para) return void 0;
22461
+ return { kind: "paragraph", sectionIndex: para.sectionIndex, xmlStart: para.start };
22462
+ }
22463
+ if (block.type === "table" && block.table) {
22464
+ if (st.tableOrdinals.size !== st.scanTables.length) return void 0;
22465
+ const ordinal = st.tableOrdinals.get(blockIndex);
22466
+ const t = ordinal !== void 0 ? st.scanTables[ordinal] : void 0;
22467
+ if (!t) return void 0;
22468
+ return { kind: "table", sectionIndex: t.sectionIndex, xmlStart: t.start };
22469
+ }
22470
+ return void 0;
22471
+ }
22472
+ /** 블록 편집 가능성 사전 판정 — patcher graceful-skip 게이트의 사전 버전 */
22473
+ capability(blockIndex) {
22474
+ const st = this.state;
22475
+ const block = st.blocks[blockIndex];
22476
+ if (!block) return { capability: "locked", reason: "\uBE14\uB85D \uC778\uB371\uC2A4 \uBC94\uC704 \uBC16" };
22477
+ if (block.type === "paragraph" || block.type === "heading") {
22478
+ if (st.fragmentBlocks.has(blockIndex)) {
22479
+ return { capability: "locked", reason: "\uBB38\uB2E8 \uBD84\uC808(\uAC15\uC81C \uC904\uBC14\uAFC8/\uBCD1\uD569 \uC720\uB2DB) \u2014 \uBD80\uBD84 \uC218\uC815\uC740 \uBBF8\uC9C0\uC6D0 (v1)" };
22480
+ }
22481
+ if (block.text && block.text.includes("\n")) {
22482
+ return { capability: "locked", reason: "\uBB38\uB2E8 \uB0B4 \uAC15\uC81C \uC904\uBC14\uAFC8 \uD3EC\uD568 \u2014 \uC218\uC815 \uC2DC \uC904\uBC14\uAFC8 \uBCF4\uC874 \uBD88\uAC00\uB85C \uBBF8\uC9C0\uC6D0 (v1)" };
22483
+ }
22484
+ if (!st.paraMap.get(blockIndex)?.para) {
22485
+ return { capability: "locked", reason: "\uBB38\uB2E8 \uC18C\uC2A4\uB9F5 \uB9E4\uD551 \uC2E4\uD328 (\uBA38\uB9AC\uB9D0/\uAE00\uC0C1\uC790/\uCEA1\uC158 \uC601\uC5ED\uC774\uAC70\uB098 \uD14D\uC2A4\uD2B8 \uBD88\uC77C\uCE58)" };
22486
+ }
22487
+ return { capability: "text" };
22488
+ }
22489
+ if (block.type === "table" && block.table) {
22490
+ if (st.tableOrdinals.size !== st.scanTables.length) {
22491
+ return { capability: "locked", reason: "\uD45C \uAC1C\uC218 \uBD88\uC77C\uCE58 \u2014 \uC18C\uC2A4\uB9F5 \uC2E0\uB8B0 \uBD88\uAC00" };
22492
+ }
22493
+ const ordinal = st.tableOrdinals.get(blockIndex);
22494
+ const scanTable = ordinal !== void 0 ? st.scanTables[ordinal] : void 0;
22495
+ if (!scanTable) {
22496
+ return { capability: "locked", reason: "\uD45C \uC18C\uC2A4\uB9F5 \uB9E4\uD551 \uC2E4\uD328" };
22497
+ }
22498
+ const table = block.table;
22499
+ const cells = [];
22500
+ let anyEditable = false;
22501
+ for (let r = 0; r < table.rows; r++) {
22502
+ const row = [];
22503
+ for (let c = 0; c < table.cols; c++) {
22504
+ const info = cellStaticCheck(table, scanTable, r, c);
22505
+ if (info.editable) anyEditable = true;
22506
+ row.push(info);
22507
+ }
22508
+ cells.push(row);
22509
+ }
22510
+ if (!anyEditable) return { capability: "locked", reason: "\uD3B8\uC9D1 \uAC00\uB2A5\uD55C \uC140 \uC5C6\uC74C", cells };
22511
+ return { capability: "cell-text", cells };
22512
+ }
22513
+ return { capability: "locked", reason: `${block.type} \uBE14\uB85D \uD3B8\uC9D1\uC740 \uBBF8\uC9C0\uC6D0 (v1)` };
22514
+ }
22515
+ /** 전 블록의 편집 가능성 */
22516
+ capabilities() {
22517
+ return this.state.blocks.map((_, i) => this.capability(i));
22518
+ }
22519
+ /**
22520
+ * 블록 단위 증분 패치 — 적용 후 세션 상태가 새 바이트로 갱신된다.
22521
+ *
22522
+ * - 호출은 내부적으로 직렬화된다 (동시 호출 시 도착 순서대로 누적 적용)
22523
+ * - 무변경 편집(현재 텍스트와 동일)은 조용히 건너뜀 (applied/skipped 모두 제외)
22524
+ * - 변경이 하나도 적용되지 않으면 반환 data는 현재 문서와 바이트 동일
22525
+ * - changes는 "패치 전 → 후" 문서 diff — modified 수가 기대 편집 수와
22526
+ * 일치하는지 확인 용도. patchHwpx의 verification(잔차 검증)과 의미가 다르다.
22527
+ */
22528
+ async patchBlocks(edits, options) {
22529
+ const run = this.opQueue.then(() => this.patchBlocksInner(edits, options));
22530
+ this.opQueue = run.then(() => void 0, () => void 0);
22531
+ return run;
22532
+ }
22533
+ opQueue = Promise.resolve();
22534
+ async patchBlocksInner(edits, options) {
22535
+ const st = this.state;
22536
+ const skipped = [];
22537
+ let applied = 0;
22538
+ const sectionSplices = st.scans.map(() => []);
22539
+ const cellCtx = { scans: st.scans, sectionSplices, skipped };
22540
+ const seenParas = /* @__PURE__ */ new Set();
22541
+ const seenCells = /* @__PURE__ */ new Set();
22542
+ for (const edit of edits) {
22543
+ const i = edit.blockIndex;
22544
+ const block = st.blocks[i];
22545
+ if (!block) {
22546
+ skipped.push({ reason: `\uBE14\uB85D \uC778\uB371\uC2A4 \uBC94\uC704 \uBC16: ${i}` });
22547
+ continue;
22548
+ }
22549
+ if (block.type === "table" && block.table) {
22550
+ if (!edit.cells?.length) {
22551
+ skipped.push({ reason: "\uD45C \uBE14\uB85D\uC5D0\uB294 cells \uD3B8\uC9D1\uB9CC \uC9C0\uC6D0", before: summarize(block.table.caption ?? "(\uD45C)") });
22552
+ continue;
22553
+ }
22554
+ if (st.tableOrdinals.size !== st.scanTables.length) {
22555
+ skipped.push({ reason: "\uD45C \uAC1C\uC218 \uBD88\uC77C\uCE58 \u2014 \uC18C\uC2A4\uB9F5 \uC2E0\uB8B0 \uBD88\uAC00" });
22556
+ continue;
22557
+ }
22558
+ const ordinal = st.tableOrdinals.get(i);
22559
+ const scanTable = ordinal !== void 0 ? st.scanTables[ordinal] : void 0;
22560
+ if (!scanTable) {
22561
+ skipped.push({ reason: "\uD45C \uC18C\uC2A4\uB9F5 \uB9E4\uD551 \uC2E4\uD328" });
22562
+ continue;
22563
+ }
22564
+ for (const cellEdit of edit.cells) {
22565
+ const key = `${i}:${cellEdit.row},${cellEdit.col}`;
22566
+ if (seenCells.has(key)) {
22567
+ skipped.push({ reason: "\uAC19\uC740 \uC140\uC5D0 \uC911\uBCF5 \uD3B8\uC9D1 \u2014 \uBA3C\uC800 \uC801\uC6A9\uB41C \uD3B8\uC9D1 \uC720\uC9C0", after: summarize(cellEdit.text) });
22568
+ continue;
22569
+ }
22570
+ const irCell = block.table.cells[cellEdit.row]?.[cellEdit.col];
22571
+ if (!irCell) {
22572
+ skipped.push({ reason: `\uC140 \uC88C\uD45C \uBC94\uC704 \uBC16: ${cellEdit.row},${cellEdit.col}`, after: summarize(cellEdit.text) });
22573
+ continue;
22574
+ }
22575
+ if (extractCellTokens(irCell.text) !== extractCellTokens(cellEdit.text)) {
22576
+ skipped.push({ reason: "\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0", before: summarize(irCell.text), after: summarize(cellEdit.text) });
22577
+ continue;
22578
+ }
22579
+ const newLines = stripCellTokens(cellEdit.text).split("\n").map((s) => s.trim()).filter(Boolean);
22580
+ const origLines = irCellLines(irCell.text);
22581
+ if (newLines.join("\n") === origLines.join("\n")) continue;
22582
+ const n = applyCellEdit(
22583
+ block.table,
22584
+ scanTable,
22585
+ cellEdit.row,
22586
+ cellEdit.col,
22587
+ newLines,
22588
+ cellCtx,
22589
+ irCell.text,
22590
+ cellEdit.text,
22591
+ origLines.length
22592
+ );
22593
+ if (n > 0) seenCells.add(key);
22594
+ applied += n;
22595
+ }
22596
+ continue;
22597
+ }
22598
+ if ((block.type === "paragraph" || block.type === "heading") && edit.newText !== void 0) {
22599
+ if (seenParas.has(i)) {
22600
+ skipped.push({ reason: "\uAC19\uC740 \uBE14\uB85D\uC5D0 \uC911\uBCF5 \uD3B8\uC9D1 \u2014 \uBA3C\uC800 \uC801\uC6A9\uB41C \uD3B8\uC9D1 \uC720\uC9C0", after: summarize(edit.newText) });
22601
+ continue;
22602
+ }
22603
+ const n = this.patchParagraphPlain(i, block, edit.newText, sectionSplices, skipped);
22604
+ if (n > 0) seenParas.add(i);
22605
+ applied += n;
22606
+ continue;
22607
+ }
22608
+ skipped.push({
22609
+ reason: `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBE14\uB85D \uC720\uD615(${block.type}) \uB610\uB294 \uD3B8\uC9D1 \uD615\uC2DD`,
22610
+ before: summarize(block.text ?? "")
22611
+ });
22612
+ }
22613
+ const replacements = /* @__PURE__ */ new Map();
22614
+ const encoder = new TextEncoder();
22615
+ try {
22616
+ for (let s = 0; s < st.scans.length; s++) {
22617
+ if (sectionSplices[s].length === 0) continue;
22618
+ replacements.set(st.sectionPaths[s], encoder.encode(applySplices(st.scans[s].xml, sectionSplices[s])));
22619
+ }
22620
+ } catch (err) {
22621
+ return { success: false, applied: 0, skipped, error: `\uC18C\uC2A4\uB9F5 splice \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}` };
22622
+ }
22623
+ if (replacements.size === 0) {
22624
+ let changes2;
22625
+ if (options?.verify !== false) {
22626
+ const units = splitMarkdownUnits(st.markdown);
22627
+ changes2 = diffUnitLists(units, units);
22628
+ }
22629
+ return { success: true, data: new Uint8Array(st.bytes), applied, skipped, changes: changes2 };
22630
+ }
22631
+ let data;
22632
+ try {
22633
+ data = patchZipEntries(st.bytes, replacements);
22634
+ } catch (err) {
22635
+ return { success: false, applied: 0, skipped, error: `ZIP \uC7AC\uC870\uB9BD \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}` };
22636
+ }
22637
+ const beforeMarkdown = st.markdown;
22638
+ let newState;
22639
+ try {
22640
+ newState = await buildState(data);
22641
+ } catch (err) {
22642
+ return { success: false, applied, skipped, error: `\uD328\uCE58\uBCF8 \uC7AC\uD30C\uC2F1 \uC2E4\uD328 \u2014 \uD328\uCE58 \uC911\uB2E8: ${err instanceof Error ? err.message : String(err)}` };
22643
+ }
22644
+ this.state = newState;
22645
+ let changes;
22646
+ if (options?.verify !== false) {
22647
+ changes = diffUnitLists(splitMarkdownUnits(beforeMarkdown), splitMarkdownUnits(newState.markdown));
22648
+ }
22649
+ return { success: true, data: new Uint8Array(data), applied, skipped, changes };
22650
+ }
22651
+ /** 문단/헤딩 평문 편집 — patcher.patchParagraphUnit의 평문 입력 버전 */
22652
+ patchParagraphPlain(blockIndex, block, newTextRaw, sectionSplices, skipped) {
22653
+ const skip = (reason) => {
22654
+ skipped.push({ reason, before: summarize(block.text ?? ""), after: summarize(newTextRaw) });
22655
+ return 0;
22656
+ };
22657
+ const st = this.state;
22658
+ if (newTextRaw === (block.text ?? "")) return 0;
22659
+ if (st.fragmentBlocks.has(blockIndex)) {
22660
+ return skip("\uBB38\uB2E8 \uBD84\uC808(\uAC15\uC81C \uC904\uBC14\uAFC8/\uBCD1\uD569 \uC720\uB2DB) \u2014 \uBD80\uBD84 \uC218\uC815\uC740 \uBBF8\uC9C0\uC6D0 (v1)");
22661
+ }
22662
+ if (block.text && block.text.includes("\n")) {
22663
+ return skip("\uBB38\uB2E8 \uB0B4 \uAC15\uC81C \uC904\uBC14\uAFC8 \uD3EC\uD568 \u2014 \uC218\uC815 \uC2DC \uC904\uBC14\uAFC8 \uBCF4\uC874 \uBD88\uAC00\uB85C \uBBF8\uC9C0\uC6D0 (v1)");
22664
+ }
22665
+ const mapping = st.paraMap.get(blockIndex);
22666
+ if (!mapping?.para) {
22667
+ return skip("\uBB38\uB2E8 \uC18C\uC2A4\uB9F5 \uB9E4\uD551 \uC2E4\uD328 (\uBA38\uB9AC\uB9D0/\uAE00\uC0C1\uC790/\uCEA1\uC158 \uC601\uC5ED\uC774\uAC70\uB098 \uD14D\uC2A4\uD2B8 \uBD88\uC77C\uCE58)");
22668
+ }
22669
+ let newPlain = newTextRaw.split("\n").map((l) => l.trim()).filter(Boolean).join(" ");
22670
+ if (mapping.prefixStripped) {
22671
+ const origPrefix = block.text.split(" ", 1)[0];
22672
+ const sp = newPlain.indexOf(" ");
22673
+ const newFirst = sp > 0 ? newPlain.slice(0, sp) : newPlain;
22674
+ if (newFirst === origPrefix || /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u.test(newFirst)) {
22675
+ newPlain = sp > 0 ? newPlain.slice(sp + 1) : "";
22676
+ } else {
22677
+ skipped.push({ reason: "\uC790\uB3D9\uBC88\uD638 \uC811\uB450 \uC2DD\uBCC4 \uC2E4\uD328 \u2014 \uBC88\uD638 \uD3EC\uD568 \uD14D\uC2A4\uD2B8\uB85C \uC801\uC6A9 (\uBDF0\uC5B4\uC5D0\uC11C \uC911\uBCF5 \uD45C\uC2DC \uAC00\uB2A5)", after: summarize(newPlain) });
22678
+ }
22679
+ }
22680
+ if (newPlain === "") {
22681
+ return skip("\uBE14\uB85D \uBE44\uC6B0\uAE30/\uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 (v1) \u2014 \uC6D0\uBCF8 \uC720\uC9C0");
22682
+ }
22683
+ if (sanitizeText(newPlain) !== newPlain) {
22684
+ return skip("\uACF5\uBC31 \uC815\uADDC\uD654 \uBD88\uC548\uC815 \uD14D\uC2A4\uD2B8 \u2014 \uD328\uCE58 \uC2DC \uC6D0\uBB38 \uBCF4\uC874 \uBD88\uAC00\uB85C \uBBF8\uC9C0\uC6D0");
22685
+ }
22686
+ const splices = buildParagraphSplices(mapping.para, newPlain, st.scans[mapping.para.sectionIndex]?.xml);
22687
+ if (splices === null) return skip("\uBB38\uB2E8\uC5D0 \uD14D\uC2A4\uD2B8 \uB178\uB4DC\uB97C \uB9CC\uB4E4 \uC218 \uC5C6\uC74C");
22688
+ sectionSplices[mapping.para.sectionIndex].push(...splices);
22689
+ return 1;
22690
+ }
22691
+ };
22692
+ function cellStaticCheck(table, scanTable, r, c) {
22693
+ const irCell = table.cells[r]?.[c];
22694
+ if (!irCell) return { editable: false, reason: "\uC140 \uC88C\uD45C \uBC94\uC704 \uBC16" };
22695
+ const cell = scanTable.cellByAnchor.get(`${r},${c}`);
22696
+ if (!cell) return { editable: false, reason: "\uBCD1\uD569 \uC601\uC5ED\uC758 \uBE48 \uCE78\uC774\uAC70\uB098 \uC88C\uD45C \uBD88\uC77C\uCE58" };
22697
+ const scanJoined = cell.paragraphs.map((p) => p.text).filter((t) => normForMatch(t)).join("\n");
22698
+ if (normForMatch(scanJoined) !== normForMatch(stripCellTokens(irCell.text))) {
22699
+ if (normForMatch(irCell.text) !== "" || normForMatch(scanJoined) !== "") {
22700
+ const flatBlocks = (irCell.blocks ?? []).filter((b) => b.type === "paragraph" || b.type === "heading");
22701
+ const flatJoined = flatBlocks.map((b) => b.text ?? "").join("\n");
22702
+ if (normForMatch(scanJoined) !== normForMatch(flatJoined)) {
22703
+ return { editable: false, reason: "\uC140 \uCF58\uD150\uCE20 \uAD6C\uC870 \uBCF5\uC7A1 (\uC911\uCCA9\uD45C/\uAE00\uC0C1\uC790) \u2014 \uB9E4\uD551 \uC2E0\uB8B0 \uBD88\uAC00" };
22704
+ }
22705
+ }
22706
+ }
22707
+ const nonEmpty = cell.paragraphs.filter((p) => normForMatch(p.text) !== "");
22708
+ if (nonEmpty.length === 0) {
22709
+ if (cell.paragraphs.length === 0) {
22710
+ return { editable: false, reason: "\uBE48 \uC140\uC5D0 \uBB38\uB2E8\uC774 \uC5C6\uC5B4 \uD14D\uC2A4\uD2B8 \uC0BD\uC785 \uBD88\uAC00" };
22711
+ }
22712
+ return { editable: true };
22713
+ }
22714
+ const lines = irCellLines(irCell.text);
22715
+ if (lines.length !== nonEmpty.length) {
22716
+ return { editable: false, reason: "\uC140 \uC904 \uACBD\uACC4 \uB9E4\uD551 \uBAA8\uD638 (\uB9AC\uD130\uB7F4 <br>/\uBB38\uB2E8 \uB0B4 \uC904\uBC14\uAFC8) \u2014 \uBBF8\uC9C0\uC6D0" };
22717
+ }
22718
+ return { editable: true };
22719
+ }
22720
+ async function openHwpxDocument(input) {
22721
+ return HwpxSession.open(input);
22722
+ }
22723
+ async function patchHwpxBlocks(original, edits, options) {
22724
+ const session = await HwpxSession.open(original);
22725
+ return session.patchBlocks(edits, options);
22726
+ }
22727
+
22300
22728
  // src/print/renderer.ts
22301
22729
  import { existsSync } from "fs";
22302
22730
  import MarkdownIt from "markdown-it";
@@ -22537,7 +22965,7 @@ async function parseHwp(buffer, options) {
22537
22965
  async function parsePdf(buffer, options) {
22538
22966
  let parsePdfDocument;
22539
22967
  try {
22540
- const mod = await import("./parser-LZH7ZELV.js");
22968
+ const mod = await import("./parser-QTJL6IQY.js");
22541
22969
  parsePdfDocument = mod.parsePdfDocument;
22542
22970
  } catch {
22543
22971
  return {
@@ -22627,18 +23055,24 @@ async function fillForm(input, values, outputFormat = "markdown") {
22627
23055
  return { output: markdown, format: "markdown", fill };
22628
23056
  }
22629
23057
  export {
23058
+ HwpxSession,
22630
23059
  VERSION,
23060
+ applySplices,
22631
23061
  blocksToMarkdown,
22632
23062
  blocksToPdf,
23063
+ buildParagraphSplices,
23064
+ buildRangeSplices,
22633
23065
  compare,
22634
23066
  detectFormat,
22635
23067
  detectOle2Format,
22636
23068
  detectZipFormat,
22637
23069
  diffBlocks,
22638
23070
  extractFormFields,
23071
+ extractFormSchema,
22639
23072
  fillForm,
22640
23073
  fillFormFields,
22641
23074
  fillHwpx,
23075
+ inferFieldType,
22642
23076
  isHwpxFile,
22643
23077
  isLabelCell,
22644
23078
  isOldHwpFile,
@@ -22646,6 +23080,7 @@ export {
22646
23080
  isZipFile,
22647
23081
  markdownToHwpx,
22648
23082
  markdownToPdf,
23083
+ openHwpxDocument,
22649
23084
  parse,
22650
23085
  parseDocx,
22651
23086
  parseHwp,
@@ -22657,6 +23092,8 @@ export {
22657
23092
  parseXlsx,
22658
23093
  patchHwp,
22659
23094
  patchHwpx,
22660
- renderHtml
23095
+ patchHwpxBlocks,
23096
+ renderHtml,
23097
+ scanSectionXml
22661
23098
  };
22662
23099
  //# sourceMappingURL=index.js.map