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
@@ -4,7 +4,7 @@ import {
4
4
  detectOle2Format,
5
5
  detectZipFormat,
6
6
  parseLenientCfb
7
- } from "./chunk-MEPHGCPQ.js";
7
+ } from "./chunk-5CJGKKMZ.js";
8
8
  import {
9
9
  HEADING_RATIO_H1,
10
10
  HEADING_RATIO_H2,
@@ -23,7 +23,7 @@ import {
23
23
  sanitizeHref,
24
24
  stripDtd,
25
25
  toArrayBuffer
26
- } from "./chunk-O5P6EG5L.js";
26
+ } from "./chunk-KR6DSGM7.js";
27
27
  import {
28
28
  parsePageRange
29
29
  } from "./chunk-MOL7MDBG.js";
@@ -17750,21 +17750,21 @@ function isDisplayMath(el) {
17750
17750
 
17751
17751
  // src/docx/parser.ts
17752
17752
  var MAX_DECOMPRESS_SIZE4 = 100 * 1024 * 1024;
17753
- function getChildElements(parent, localName3) {
17753
+ function getChildElements(parent, localName2) {
17754
17754
  const result = [];
17755
17755
  const children = parent.childNodes;
17756
17756
  for (let i = 0; i < children.length; i++) {
17757
17757
  const node = children[i];
17758
17758
  if (node.nodeType === 1) {
17759
17759
  const el = node;
17760
- if (el.localName === localName3 || el.tagName?.endsWith(`:${localName3}`)) {
17760
+ if (el.localName === localName2 || el.tagName?.endsWith(`:${localName2}`)) {
17761
17761
  result.push(el);
17762
17762
  }
17763
17763
  }
17764
17764
  }
17765
17765
  return result;
17766
17766
  }
17767
- function findElements(parent, localName3) {
17767
+ function findElements(parent, localName2) {
17768
17768
  const result = [];
17769
17769
  const walk = (node) => {
17770
17770
  const children = node.childNodes;
@@ -17772,7 +17772,7 @@ function findElements(parent, localName3) {
17772
17772
  const child = children[i];
17773
17773
  if (child.nodeType === 1) {
17774
17774
  const el = child;
17775
- if (el.localName === localName3 || el.tagName?.endsWith(`:${localName3}`)) {
17775
+ if (el.localName === localName2 || el.tagName?.endsWith(`:${localName2}`)) {
17776
17776
  result.push(el);
17777
17777
  }
17778
17778
  walk(el);
@@ -17782,11 +17782,11 @@ function findElements(parent, localName3) {
17782
17782
  walk(parent);
17783
17783
  return result;
17784
17784
  }
17785
- function getAttr(el, localName3) {
17785
+ function getAttr(el, localName2) {
17786
17786
  const attrs = el.attributes;
17787
17787
  for (let i = 0; i < attrs.length; i++) {
17788
17788
  const attr = attrs[i];
17789
- if (attr.localName === localName3 || attr.name === localName3) return attr.value;
17789
+ if (attr.localName === localName2 || attr.name === localName2) return attr.value;
17790
17790
  }
17791
17791
  return null;
17792
17792
  }
@@ -18159,11 +18159,11 @@ async function parseDocxDocument(buffer, options) {
18159
18159
  const node = children[i];
18160
18160
  if (node.nodeType !== 1) continue;
18161
18161
  const el = node;
18162
- const localName3 = el.localName ?? el.tagName?.split(":").pop();
18163
- if (localName3 === "p") {
18162
+ const localName2 = el.localName ?? el.tagName?.split(":").pop();
18163
+ if (localName2 === "p") {
18164
18164
  const block = parseParagraph2(el, styles, numbering, footnotes, rels);
18165
18165
  if (block) blocks.push(block);
18166
- } else if (localName3 === "tbl") {
18166
+ } else if (localName2 === "tbl") {
18167
18167
  const block = parseTable(el, styles, numbering, footnotes, rels);
18168
18168
  if (block) blocks.push(block);
18169
18169
  }
@@ -18454,6 +18454,132 @@ function countSections(body) {
18454
18454
  return count;
18455
18455
  }
18456
18456
 
18457
+ // src/form/match.ts
18458
+ function normalizeLabel(label) {
18459
+ return label.trim().replace(/[::\s()()·]/g, "");
18460
+ }
18461
+ function findMatchingKey(cellLabel, values) {
18462
+ if (values.has(cellLabel)) return cellLabel;
18463
+ let bestKey;
18464
+ let bestLen = 0;
18465
+ for (const key of values.keys()) {
18466
+ if (cellLabel.startsWith(key)) {
18467
+ if (key.length >= cellLabel.length * 0.6 && key.length > bestLen) {
18468
+ bestLen = key.length;
18469
+ bestKey = key;
18470
+ }
18471
+ } else if (key.startsWith(cellLabel)) {
18472
+ if (cellLabel.length >= key.length * 0.6 && cellLabel.length > bestLen) {
18473
+ bestLen = cellLabel.length;
18474
+ bestKey = key;
18475
+ }
18476
+ }
18477
+ }
18478
+ return bestKey;
18479
+ }
18480
+ function isKeywordLabel(text) {
18481
+ const trimmed = text.trim().replace(/[¹²³⁴⁵⁶⁷⁸⁹⁰*※]+$/g, "").trim();
18482
+ if (!trimmed || trimmed.length > 15) return false;
18483
+ for (const kw of LABEL_KEYWORDS) {
18484
+ if (trimmed.includes(kw)) return true;
18485
+ }
18486
+ return false;
18487
+ }
18488
+ function fillInCellPatterns(cellText, values, matchedLabels) {
18489
+ let text = cellText;
18490
+ const matches = [];
18491
+ text = text.replace(
18492
+ /([가-힣A-Za-z]+)\(\s{1,}\)([가-힣A-Za-z]*)/g,
18493
+ (match, prefix, suffix) => {
18494
+ const label = prefix + suffix;
18495
+ const normalizedLabel = normalizeLabel(label);
18496
+ const matchKey = values.has(normalizedLabel) ? normalizedLabel : values.has(normalizeLabel(prefix)) ? normalizeLabel(prefix) : void 0;
18497
+ if (matchKey === void 0) return match;
18498
+ const newValue = values.get(matchKey);
18499
+ matchedLabels.add(matchKey);
18500
+ matches.push({ key: matchKey, label, value: newValue });
18501
+ return `${prefix}(${newValue})${suffix}`;
18502
+ }
18503
+ );
18504
+ text = text.replace(
18505
+ /□([가-힣A-Za-z]+)/g,
18506
+ (match, keyword) => {
18507
+ const normalizedKw = normalizeLabel(keyword);
18508
+ const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18509
+ if (matchKey === void 0) return match;
18510
+ const val = values.get(matchKey);
18511
+ const isTruthy = ["\u2611", "\u2713", "\u2714", "v", "V", "true", "1", "yes", "o", "O"].includes(val.trim()) || val.trim() === "";
18512
+ if (!isTruthy) return match;
18513
+ matchedLabels.add(matchKey);
18514
+ matches.push({ key: matchKey, label: `\u25A1${keyword}`, value: "\u2611" });
18515
+ return `\u2611${keyword}`;
18516
+ }
18517
+ );
18518
+ text = text.replace(
18519
+ /\(([가-힣A-Za-z]+)[::]\s{1,}\)/g,
18520
+ (match, keyword) => {
18521
+ const normalizedKw = normalizeLabel(keyword);
18522
+ const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18523
+ if (matchKey === void 0) return match;
18524
+ const newValue = values.get(matchKey);
18525
+ matchedLabels.add(matchKey);
18526
+ matches.push({ key: matchKey, label: keyword, value: newValue });
18527
+ return `(${keyword}\uFF1A${newValue})`;
18528
+ }
18529
+ );
18530
+ return matches.length > 0 ? { text, matches } : null;
18531
+ }
18532
+ var INLINE_LABEL_RE = /([가-힣A-Za-z]{2,10})\s*[::]/g;
18533
+ function scanInlineSegments(text) {
18534
+ const labels = [];
18535
+ INLINE_LABEL_RE.lastIndex = 0;
18536
+ let m;
18537
+ while ((m = INLINE_LABEL_RE.exec(text)) !== null) {
18538
+ if (text[INLINE_LABEL_RE.lastIndex] === "/") continue;
18539
+ labels.push({ label: m[1], start: m.index, end: INLINE_LABEL_RE.lastIndex });
18540
+ }
18541
+ const segments = [];
18542
+ for (let i = 0; i < labels.length; i++) {
18543
+ const cur = labels[i];
18544
+ let vs = cur.end;
18545
+ while (vs < text.length && (text[vs] === " " || text[vs] === " ")) vs++;
18546
+ let ve = i + 1 < labels.length ? labels[i + 1].start : text.length;
18547
+ if (ve < vs) ve = vs;
18548
+ const sep = text.slice(vs, ve).search(/[\n,;]/);
18549
+ if (sep !== -1) ve = vs + sep;
18550
+ if (ve - vs > 100) ve = vs + 100;
18551
+ while (ve > vs && /\s/.test(text[ve - 1])) ve--;
18552
+ segments.push({
18553
+ label: cur.label,
18554
+ labelStart: cur.start,
18555
+ valueStart: vs,
18556
+ valueEnd: ve,
18557
+ value: text.slice(vs, ve)
18558
+ });
18559
+ }
18560
+ return segments;
18561
+ }
18562
+ function padInsertion(text, pos, value) {
18563
+ const lead = pos > 0 && !/\s/.test(text[pos - 1]) ? " " : "";
18564
+ const trail = pos < text.length && !/\s/.test(text[pos]) ? " " : "";
18565
+ return lead + value + trail;
18566
+ }
18567
+ function normalizeValues(values) {
18568
+ const map = /* @__PURE__ */ new Map();
18569
+ for (const [label, value] of Object.entries(values)) {
18570
+ map.set(normalizeLabel(label), value);
18571
+ }
18572
+ return map;
18573
+ }
18574
+ function resolveUnmatched(normalizedValues, matchedLabels, originalValues) {
18575
+ return [...normalizedValues.keys()].filter((k) => !matchedLabels.has(k)).map((k) => {
18576
+ for (const orig of Object.keys(originalValues)) {
18577
+ if (normalizeLabel(orig) === k) return orig;
18578
+ }
18579
+ return k;
18580
+ });
18581
+ }
18582
+
18457
18583
  // src/form/recognize.ts
18458
18584
  var LABEL_KEYWORDS = /* @__PURE__ */ new Set([
18459
18585
  "\uC131\uBA85",
@@ -18573,107 +18699,73 @@ function extractFromTable(table) {
18573
18699
  }
18574
18700
  function extractInlineFields(text) {
18575
18701
  const fields = [];
18576
- const pattern = /([가-힣A-Za-z]{2,10})\s*[::]\s*([^\n,;]{1,100})/g;
18577
- let match;
18578
- while ((match = pattern.exec(text)) !== null) {
18579
- const label = match[1].trim();
18580
- const value = match[2].trim();
18581
- if (value) {
18582
- fields.push({ label, value, row: -1, col: -1 });
18702
+ for (const seg of scanInlineSegments(text)) {
18703
+ if (seg.value) {
18704
+ fields.push({ label: seg.label, value: seg.value, row: -1, col: -1 });
18583
18705
  }
18584
18706
  }
18585
18707
  return fields;
18586
18708
  }
18587
-
18588
- // src/form/match.ts
18589
- function normalizeLabel(label) {
18590
- return label.trim().replace(/[::\s()()·]/g, "");
18591
- }
18592
- function findMatchingKey(cellLabel, values) {
18593
- if (values.has(cellLabel)) return cellLabel;
18594
- let bestKey;
18595
- let bestLen = 0;
18596
- for (const key of values.keys()) {
18597
- if (cellLabel.startsWith(key)) {
18598
- if (key.length >= cellLabel.length * 0.6 && key.length > bestLen) {
18599
- bestLen = key.length;
18600
- bestKey = key;
18601
- }
18602
- } else if (key.startsWith(cellLabel)) {
18603
- if (cellLabel.length >= key.length * 0.6 && cellLabel.length > bestLen) {
18604
- bestLen = cellLabel.length;
18605
- bestKey = key;
18606
- }
18607
- }
18608
- }
18609
- return bestKey;
18610
- }
18611
- function isKeywordLabel(text) {
18612
- const trimmed = text.trim().replace(/[¹²³⁴⁵⁶⁷⁸⁹⁰*※]+$/g, "").trim();
18613
- if (!trimmed || trimmed.length > 15) return false;
18614
- for (const kw of LABEL_KEYWORDS) {
18615
- if (trimmed.includes(kw)) return true;
18616
- }
18617
- return false;
18618
- }
18619
- function fillInCellPatterns(cellText, values, matchedLabels) {
18620
- let text = cellText;
18621
- const matches = [];
18622
- text = text.replace(
18623
- /([가-힣A-Za-z]+)\(\s{1,}\)([가-힣A-Za-z]*)/g,
18624
- (match, prefix, suffix) => {
18625
- const label = prefix + suffix;
18626
- const normalizedLabel = normalizeLabel(label);
18627
- const matchKey = values.has(normalizedLabel) ? normalizedLabel : values.has(normalizeLabel(prefix)) ? normalizeLabel(prefix) : void 0;
18628
- if (matchKey === void 0) return match;
18629
- const newValue = values.get(matchKey);
18630
- matchedLabels.add(matchKey);
18631
- matches.push({ key: matchKey, label, value: newValue });
18632
- return `${prefix}(${newValue})${suffix}`;
18633
- }
18634
- );
18635
- text = text.replace(
18636
- /□([가-힣A-Za-z]+)/g,
18637
- (match, keyword) => {
18638
- const normalizedKw = normalizeLabel(keyword);
18639
- const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18640
- if (matchKey === void 0) return match;
18641
- const val = values.get(matchKey);
18642
- const isTruthy = ["\u2611", "\u2713", "\u2714", "v", "V", "true", "1", "yes", "o", "O"].includes(val.trim()) || val.trim() === "";
18643
- if (!isTruthy) return match;
18644
- matchedLabels.add(matchKey);
18645
- matches.push({ key: matchKey, label: `\u25A1${keyword}`, value: "\u2611" });
18646
- return `\u2611${keyword}`;
18647
- }
18648
- );
18649
- text = text.replace(
18650
- /\(([가-힣A-Za-z]+)[::]\s{1,}\)/g,
18651
- (match, keyword) => {
18652
- const normalizedKw = normalizeLabel(keyword);
18653
- const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18654
- if (matchKey === void 0) return match;
18655
- const newValue = values.get(matchKey);
18656
- matchedLabels.add(matchKey);
18657
- matches.push({ key: matchKey, label: keyword, value: newValue });
18658
- return `(${keyword}\uFF1A${newValue})`;
18709
+ var LABEL_TYPE_RULES = [
18710
+ [/주민등록번호|외국인등록번호/, "idnum"],
18711
+ [/생년월일|일시|날짜|일자|기간|연월일|년월일|신청일|작성일|발급일|접수일/, "date"],
18712
+ [/전화|연락처|휴대폰|핸드폰|팩스/, "phone"],
18713
+ [/이메일|전자우편|email/i, "email"],
18714
+ [/금액|단가|수량|합계|소계|예산|비용|인원|급여|연봉/, "amount"]
18715
+ ];
18716
+ function inferFieldType(label, value) {
18717
+ if (/[□☑✓✔]/.test(value) || /[□☑✓✔]/.test(label)) return "checkbox";
18718
+ const v = value.trim();
18719
+ if (v) {
18720
+ if (/^\d{6}[-\s]?[1-4]\d{6}$/.test(v)) return "idnum";
18721
+ if (/^\d{4}\s*[-./년]\s*\d{1,2}\s*[-./월]\s*\d{1,2}\s*일?\s*\.?$/.test(v)) return "date";
18722
+ if (/^0\d{1,2}[-.)\s]?\d{3,4}[-.\s]?\d{4}$/.test(v)) return "phone";
18723
+ if (/^[\w.+-]+@[\w-]+(?:\.[\w-]+)+$/.test(v)) return "email";
18724
+ if (/^[\d,.\s]+(?:원|명|건|개|회|부|매|%)$/.test(v) && /\d/.test(v)) return "amount";
18725
+ if (/^\d{1,3}(?:,\d{3})+$/.test(v)) return "amount";
18726
+ }
18727
+ const norm = label.replace(/\s/g, "");
18728
+ for (const [re, type] of LABEL_TYPE_RULES) {
18729
+ if (re.test(norm)) return type;
18730
+ }
18731
+ return "text";
18732
+ }
18733
+ function isRequiredLabel(label) {
18734
+ return /[*※★]|\(\s*필수\s*\)|(\s*필수\s*)/.test(label);
18735
+ }
18736
+ function isEmptyValue(value) {
18737
+ const v = value.trim();
18738
+ if (!v) return true;
18739
+ return /^[\s_()()\-—–~.·,]*$/.test(v);
18740
+ }
18741
+ function extractFormSchema(blocks) {
18742
+ const { fields, confidence } = extractFormFields(blocks);
18743
+ const schemaFields = fields.map((f) => ({
18744
+ ...f,
18745
+ type: inferFieldType(f.label, f.value),
18746
+ required: isRequiredLabel(f.label) || void 0,
18747
+ empty: isEmptyValue(f.value)
18748
+ }));
18749
+ const seen = new Set(schemaFields.map((f) => normalizeLabel(f.label)));
18750
+ for (const block of blocks) {
18751
+ if (block.type !== "paragraph" || !block.text) continue;
18752
+ for (const seg of scanInlineSegments(block.text)) {
18753
+ if (seg.value) continue;
18754
+ const key = normalizeLabel(seg.label);
18755
+ if (seen.has(key)) continue;
18756
+ seen.add(key);
18757
+ schemaFields.push({
18758
+ label: seg.label,
18759
+ value: "",
18760
+ row: -1,
18761
+ col: -1,
18762
+ type: inferFieldType(seg.label, ""),
18763
+ required: isRequiredLabel(seg.label) || void 0,
18764
+ empty: true
18765
+ });
18659
18766
  }
18660
- );
18661
- return matches.length > 0 ? { text, matches } : null;
18662
- }
18663
- function normalizeValues(values) {
18664
- const map = /* @__PURE__ */ new Map();
18665
- for (const [label, value] of Object.entries(values)) {
18666
- map.set(normalizeLabel(label), value);
18667
18767
  }
18668
- return map;
18669
- }
18670
- function resolveUnmatched(normalizedValues, matchedLabels, originalValues) {
18671
- return [...normalizedValues.keys()].filter((k) => !matchedLabels.has(k)).map((k) => {
18672
- for (const orig of Object.keys(originalValues)) {
18673
- if (normalizeLabel(orig) === k) return orig;
18674
- }
18675
- return k;
18676
- });
18768
+ return { confidence, fields: schemaFields };
18677
18769
  }
18678
18770
 
18679
18771
  // src/form/filler.ts
@@ -18767,1579 +18859,1569 @@ function fillTable(table, values, filled, matchedLabels, patternFilledCells) {
18767
18859
  }
18768
18860
  }
18769
18861
  function fillInlineFields(text, values, filled, matchedLabels) {
18770
- return text.replace(
18771
- /([가-힣A-Za-z]{2,10})\s*[::]\s*([^\n,;]{0,100})/g,
18772
- (match, rawLabel, _oldValue) => {
18773
- const normalized = normalizeLabel(rawLabel);
18774
- const matchKey = findMatchingKey(normalized, values);
18775
- if (matchKey === void 0) return match;
18776
- const newValue = values.get(matchKey);
18777
- matchedLabels.add(matchKey);
18778
- filled.push({
18779
- label: rawLabel.trim(),
18780
- value: newValue,
18781
- row: -1,
18782
- col: -1
18783
- });
18784
- return `${rawLabel}: ${newValue}`;
18785
- }
18786
- );
18862
+ const segments = scanInlineSegments(text);
18863
+ if (segments.length === 0) return text;
18864
+ let out = "";
18865
+ let pos = 0;
18866
+ for (const seg of segments) {
18867
+ const matchKey = findMatchingKey(normalizeLabel(seg.label), values);
18868
+ if (matchKey === void 0) continue;
18869
+ const newValue = values.get(matchKey);
18870
+ matchedLabels.add(matchKey);
18871
+ filled.push({ label: seg.label.trim(), value: newValue, row: -1, col: -1 });
18872
+ out += text.slice(pos, seg.valueStart);
18873
+ out += seg.valueStart === seg.valueEnd ? padInsertion(text, seg.valueStart, newValue) : newValue;
18874
+ pos = seg.valueEnd;
18875
+ }
18876
+ out += text.slice(pos);
18877
+ return out;
18787
18878
  }
18788
18879
 
18789
18880
  // src/form/filler-hwpx.ts
18790
18881
  import JSZip4 from "jszip";
18791
- import { DOMParser as DOMParser5, XMLSerializer } from "@xmldom/xmldom";
18792
- async function fillHwpx(hwpxBuffer, values) {
18793
- const zip = await JSZip4.loadAsync(hwpxBuffer);
18794
- const filled = [];
18795
- const matchedLabels = /* @__PURE__ */ new Set();
18796
- const normalizedValues = normalizeValues(values);
18797
- const sectionFiles = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
18798
- if (sectionFiles.length === 0) {
18799
- throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
18800
- }
18801
- const xmlParser = new DOMParser5();
18802
- const xmlSerializer = new XMLSerializer();
18803
- for (const sectionPath of sectionFiles) {
18804
- const zipEntry = zip.file(sectionPath);
18805
- if (!zipEntry) continue;
18806
- const rawXml = await zipEntry.async("text");
18807
- const doc = xmlParser.parseFromString(stripDtd(rawXml), "text/xml");
18808
- if (!doc.documentElement) continue;
18809
- let modified = false;
18810
- const tables = findAllElements(doc.documentElement, "tbl");
18811
- const cellPatternApplied = /* @__PURE__ */ new Set();
18812
- for (const tblEl of tables) {
18813
- const allCells = findAllElements(tblEl, "tc");
18814
- for (const tcEl of allCells) {
18815
- const tNodes = collectCellTextNodes(tcEl);
18816
- const fullText = tNodes.map((n) => n.text).join("");
18817
- const result = fillInCellPatterns(fullText, normalizedValues, matchedLabels);
18818
- if (!result) continue;
18819
- applyTextReplacements(tNodes, fullText, result.text);
18820
- cellPatternApplied.add(tcEl);
18821
- for (const m of result.matches) {
18822
- filled.push({ label: m.label, value: m.value, row: -1, col: -1 });
18823
- }
18824
- modified = true;
18825
- }
18882
+
18883
+ // src/roundtrip/source-map.ts
18884
+ function escapeXmlText(text) {
18885
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
18886
+ }
18887
+ function decodeXmlEntities(text) {
18888
+ return text.replace(/&(lt|gt|amp|quot|apos|#x?[0-9a-fA-F]+);/g, (m, ent) => {
18889
+ switch (ent) {
18890
+ case "lt":
18891
+ return "<";
18892
+ case "gt":
18893
+ return ">";
18894
+ case "amp":
18895
+ return "&";
18896
+ case "quot":
18897
+ return '"';
18898
+ case "apos":
18899
+ return "'";
18826
18900
  }
18827
- for (const tblEl of tables) {
18828
- const rows = findDirectChildren(tblEl, "tr");
18829
- for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
18830
- const trEl = rows[rowIdx];
18831
- const cells = findDirectChildren(trEl, "tc");
18832
- for (let colIdx = 0; colIdx < cells.length - 1; colIdx++) {
18833
- const labelText = extractCellText2(cells[colIdx]);
18834
- if (!isLabelCell(labelText)) continue;
18835
- const valueCell = cells[colIdx + 1];
18836
- const valueText = extractCellText2(valueCell);
18837
- if (isKeywordLabel(valueText)) continue;
18838
- const normalizedCellLabel = normalizeLabel(labelText);
18839
- if (!normalizedCellLabel) continue;
18840
- const matchKey = findMatchingKey(normalizedCellLabel, normalizedValues);
18841
- if (matchKey === void 0) continue;
18842
- const newValue = normalizedValues.get(matchKey);
18843
- if (cellPatternApplied.has(valueCell)) {
18844
- prependCellText(valueCell, newValue);
18845
- } else {
18846
- replaceCellText(valueCell, newValue);
18847
- }
18848
- matchedLabels.add(matchKey);
18849
- filled.push({
18850
- label: labelText.trim().replace(/[::]\s*$/, ""),
18851
- value: newValue,
18852
- row: rowIdx,
18853
- col: colIdx
18854
- });
18855
- modified = true;
18856
- }
18857
- }
18858
- if (rows.length >= 2) {
18859
- const headerCells = findDirectChildren(rows[0], "tc");
18860
- const allLabels = headerCells.every((cell) => {
18861
- const t = extractCellText2(cell).trim();
18862
- return t.length > 0 && t.length <= 20 && isLabelCell(t);
18863
- });
18864
- if (allLabels) {
18865
- for (let rowIdx = 1; rowIdx < rows.length; rowIdx++) {
18866
- const dataCells = findDirectChildren(rows[rowIdx], "tc");
18867
- for (let colIdx = 0; colIdx < Math.min(headerCells.length, dataCells.length); colIdx++) {
18868
- const headerLabel = normalizeLabel(extractCellText2(headerCells[colIdx]));
18869
- const matchKey = findMatchingKey(headerLabel, normalizedValues);
18870
- if (matchKey === void 0) continue;
18871
- if (matchedLabels.has(matchKey)) continue;
18872
- const newValue = normalizedValues.get(matchKey);
18873
- replaceCellText(dataCells[colIdx], newValue);
18874
- matchedLabels.add(matchKey);
18875
- filled.push({
18876
- label: extractCellText2(headerCells[colIdx]).trim(),
18877
- value: newValue,
18878
- row: rowIdx,
18879
- col: colIdx
18880
- });
18881
- modified = true;
18882
- }
18883
- }
18884
- }
18885
- }
18886
- }
18887
- const allParagraphs = findAllElements(doc.documentElement, "p");
18888
- for (const pEl of allParagraphs) {
18889
- if (isInsideTable(pEl)) continue;
18890
- const tNodes = collectTextNodes(pEl);
18891
- const fullText = tNodes.map((n) => n.text).join("");
18892
- const pattern = /([가-힣A-Za-z]{2,10})\s*[::]\s*([^\n,;]{0,100})/g;
18893
- let match;
18894
- while ((match = pattern.exec(fullText)) !== null) {
18895
- const rawLabel = match[1];
18896
- const normalized = normalizeLabel(rawLabel);
18897
- const matchKey = findMatchingKey(normalized, normalizedValues);
18898
- if (matchKey === void 0) continue;
18899
- const newValue = normalizedValues.get(matchKey);
18900
- const valueStart = match.index + match[0].length - match[2].length;
18901
- const valueEnd = match.index + match[0].length;
18902
- replaceTextRange(tNodes, valueStart, valueEnd, newValue);
18903
- matchedLabels.add(matchKey);
18904
- filled.push({ label: rawLabel.trim(), value: newValue, row: -1, col: -1 });
18905
- modified = true;
18906
- break;
18907
- }
18908
- }
18909
- if (modified) {
18910
- const newXml = xmlSerializer.serializeToString(doc);
18911
- zip.file(sectionPath, newXml);
18901
+ try {
18902
+ const code = ent[1] === "x" || ent[1] === "X" ? parseInt(ent.slice(2), 16) : parseInt(ent.slice(1), 10);
18903
+ if (!isNaN(code) && code >= 0 && code <= 1114111) return String.fromCodePoint(code);
18904
+ } catch {
18912
18905
  }
18913
- }
18914
- const unmatched = resolveUnmatched(normalizedValues, matchedLabels, values);
18915
- const buffer = await zip.generateAsync({ type: "arraybuffer" });
18916
- return { buffer, filled, unmatched };
18917
- }
18918
- function localName2(el) {
18919
- return (el.tagName || el.localName || "").replace(/^[^:]+:/, "");
18906
+ return m;
18907
+ });
18920
18908
  }
18921
- function findAllElements(node, tagLocalName) {
18922
- const result = [];
18923
- const walk = (n) => {
18924
- const children = n.childNodes;
18925
- if (!children) return;
18926
- for (let i = 0; i < children.length; i++) {
18927
- const child = children[i];
18928
- if (child.nodeType !== 1) continue;
18929
- if (localName2(child) === tagLocalName) result.push(child);
18930
- walk(child);
18931
- }
18932
- };
18933
- walk(node);
18934
- return result;
18909
+ function tContentToText(raw) {
18910
+ return decodeXmlEntities(
18911
+ raw.replace(/<\/?(?:[A-Za-z0-9_]+:)?(?:tab|fwSpace|hwSpace|br|lineBreak)(?:\s[^>]*)?\/?>/g, " ").replace(/<[^>]*>/g, "")
18912
+ );
18935
18913
  }
18936
- function findDirectChildren(parent, tagLocalName) {
18937
- const result = [];
18938
- const children = parent.childNodes;
18939
- if (!children) return result;
18940
- for (let i = 0; i < children.length; i++) {
18941
- const child = children[i];
18942
- if (child.nodeType === 1 && localName2(child) === tagLocalName) {
18943
- result.push(child);
18944
- }
18945
- }
18946
- return result;
18914
+ var TAG_RE = /<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<\?[\s\S]*?\?>|<!(?:"[^"]*"|'[^']*'|[^>"'])*>|<\/([^\s>]+)\s*>|<([^\s/>!?]+)((?:"[^"]*"|'[^']*'|[^>"'])*?)(\/?)>/g;
18915
+ var T_BARRIER = /* @__PURE__ */ new Set([
18916
+ "tbl",
18917
+ "ctrl",
18918
+ "caption",
18919
+ "pic",
18920
+ "shape",
18921
+ "drawingObject",
18922
+ "drawText",
18923
+ "shapeComment",
18924
+ "memogroup",
18925
+ "memo",
18926
+ "hiddenComment",
18927
+ "equation",
18928
+ "parameters",
18929
+ "subList",
18930
+ "p"
18931
+ ]);
18932
+ var PARA_CONTAINER = /* @__PURE__ */ new Set([
18933
+ "tc",
18934
+ "ctrl",
18935
+ "caption",
18936
+ "drawText",
18937
+ "pic",
18938
+ "shape",
18939
+ "drawingObject",
18940
+ "memogroup",
18941
+ "memo",
18942
+ "hiddenComment",
18943
+ "footNote",
18944
+ "endNote",
18945
+ "fn",
18946
+ "en"
18947
+ // 각주/미주 — 파서는 호스트 블록 footnoteText로만 흡수
18948
+ ]);
18949
+ var TABLE_BARRIER = /* @__PURE__ */ new Set([
18950
+ "tbl",
18951
+ "ctrl",
18952
+ "caption",
18953
+ "memogroup",
18954
+ "memo",
18955
+ "hiddenComment"
18956
+ ]);
18957
+ function localOf(qname) {
18958
+ const i = qname.indexOf(":");
18959
+ return i >= 0 ? qname.slice(i + 1) : qname;
18947
18960
  }
18948
- function isInsideTable(el) {
18949
- let parent = el.parentNode;
18950
- while (parent) {
18951
- if (parent.nodeType === 1 && localName2(parent) === "tbl") return true;
18952
- parent = parent.parentNode;
18953
- }
18954
- return false;
18961
+ function prefixOf(qname) {
18962
+ const i = qname.indexOf(":");
18963
+ return i >= 0 ? qname.slice(0, i) : "";
18955
18964
  }
18956
- function extractCellText2(tcEl) {
18957
- const parts = [];
18958
- const walk = (node) => {
18959
- const children = node.childNodes;
18960
- if (!children) return;
18961
- for (let i = 0; i < children.length; i++) {
18962
- const child = children[i];
18963
- if (child.nodeType === 3) {
18964
- parts.push(child.textContent || "");
18965
- } else if (child.nodeType === 1) {
18966
- const tag = localName2(child);
18967
- if (tag === "t") walk(child);
18968
- else if (tag === "run" || tag === "r" || tag === "p" || tag === "subList") walk(child);
18969
- else if (tag === "tab") parts.push(" ");
18970
- else if (tag === "br") parts.push("\n");
18965
+ function scanSectionXml(xml, sectionIndex) {
18966
+ const stack = [];
18967
+ const bodyParagraphs = [];
18968
+ const tables = [];
18969
+ const headerTexts = [];
18970
+ const footerTexts = [];
18971
+ const excludedParagraphs = [];
18972
+ const orphanTables = [];
18973
+ const paraStack = [];
18974
+ const tableStack = [];
18975
+ const rowStack = [];
18976
+ const cellStack = [];
18977
+ let pendingT = null;
18978
+ const ctrlSubStack = [];
18979
+ const classifyPara = () => {
18980
+ let sawDrawText = false;
18981
+ for (let i = stack.length - 1; i >= 0; i--) {
18982
+ const l = stack[i].local;
18983
+ if (l === "tc") return { kind: "cell", inTextbox: sawDrawText };
18984
+ if (l === "drawText") {
18985
+ sawDrawText = true;
18986
+ continue;
18971
18987
  }
18988
+ if (PARA_CONTAINER.has(l)) return { kind: "excluded", inTextbox: sawDrawText };
18972
18989
  }
18990
+ return sawDrawText ? { kind: "draw", inTextbox: true } : { kind: "body", inTextbox: false };
18973
18991
  };
18974
- walk(tcEl);
18975
- return parts.join("");
18976
- }
18977
- function prependCellText(tcEl, text) {
18978
- const tElements = findAllElements(tcEl, "t");
18979
- if (tElements.length === 0) return;
18980
- const firstT = tElements[0];
18981
- const existing = firstT.textContent || "";
18982
- clearChildren(firstT);
18983
- firstT.appendChild(firstT.ownerDocument.createTextNode(text + " " + existing));
18984
- }
18985
- function replaceCellText(tcEl, newValue) {
18986
- const paragraphs = findAllElements(tcEl, "p");
18987
- if (paragraphs.length === 0) return;
18988
- const firstP = paragraphs[0];
18989
- const runs = findAllElements(firstP, "run").concat(findAllElements(firstP, "r"));
18990
- if (runs.length > 0) {
18991
- setRunText(runs[0], newValue);
18992
- for (let i = 1; i < runs.length; i++) {
18993
- setRunText(runs[i], "");
18992
+ const owningPara = () => {
18993
+ if (paraStack.length === 0) return null;
18994
+ for (let i = stack.length - 1; i >= 0; i--) {
18995
+ const l = stack[i].local;
18996
+ if (l === "p") return paraStack[paraStack.length - 1];
18997
+ if (T_BARRIER.has(l)) return null;
18994
18998
  }
18995
- } else {
18996
- const tElements = findAllElements(firstP, "t");
18997
- if (tElements.length > 0) {
18998
- clearChildren(tElements[0]);
18999
- tElements[0].appendChild(tElements[0].ownerDocument.createTextNode(newValue));
19000
- for (let i = 1; i < tElements.length; i++) {
19001
- clearChildren(tElements[i]);
19002
- }
18999
+ return null;
19000
+ };
19001
+ const isTableTopLevel = () => {
19002
+ for (let i = stack.length - 1; i >= 0; i--) {
19003
+ if (TABLE_BARRIER.has(stack[i].local)) return false;
19003
19004
  }
19004
- }
19005
- for (let i = 1; i < paragraphs.length; i++) {
19006
- const p = paragraphs[i];
19007
- if (p.parentNode) {
19008
- const pRuns = findAllElements(p, "run").concat(findAllElements(p, "r"));
19009
- for (const run of pRuns) setRunText(run, "");
19010
- const pTs = findAllElements(p, "t");
19011
- for (const t of pTs) clearChildren(t);
19005
+ return true;
19006
+ };
19007
+ const currentCtrlSub = () => ctrlSubStack.length > 0 ? ctrlSubStack[ctrlSubStack.length - 1] : null;
19008
+ TAG_RE.lastIndex = 0;
19009
+ let m;
19010
+ while ((m = TAG_RE.exec(xml)) !== null) {
19011
+ const [full, closeName, openName, , selfClose] = m;
19012
+ if (closeName === void 0 && openName === void 0) continue;
19013
+ if (closeName !== void 0) {
19014
+ const local2 = localOf(closeName);
19015
+ if (local2 === "t" && pendingT) {
19016
+ const { para, contentStart: contentStart2 } = pendingT;
19017
+ para.tRanges.push({ contentStart: contentStart2, contentEnd: m.index });
19018
+ para.text += tContentToText(xml.slice(contentStart2, m.index));
19019
+ pendingT = null;
19020
+ }
19021
+ for (let i = stack.length - 1; i >= 0; i--) {
19022
+ if (stack[i].local === local2) {
19023
+ stack.length = i;
19024
+ break;
19025
+ }
19026
+ }
19027
+ if (local2 === "p") {
19028
+ const para = paraStack.pop();
19029
+ if (para && para.kind === "excluded") {
19030
+ const sub = currentCtrlSub();
19031
+ if (sub && para.text.trim()) sub.texts.push(para.text);
19032
+ }
19033
+ } else if (local2 === "tc") {
19034
+ const cell = cellStack.pop();
19035
+ const row = rowStack[rowStack.length - 1];
19036
+ if (cell && row) row.push(cell);
19037
+ } else if (local2 === "tr") {
19038
+ const row = rowStack[rowStack.length - 1];
19039
+ const table = tableStack[tableStack.length - 1];
19040
+ if (row && table && row.length > 0) table.rows.push(row);
19041
+ if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
19042
+ } else if (local2 === "tbl") {
19043
+ const table = tableStack.pop();
19044
+ rowStack.pop();
19045
+ if (table) {
19046
+ finalizeTable(table);
19047
+ if (!table.topLevel) {
19048
+ const cell = cellStack[cellStack.length - 1];
19049
+ if (cell) cell.tables.push(table);
19050
+ else orphanTables.push(table);
19051
+ }
19052
+ }
19053
+ } else if (local2 === "header" || local2 === "footer") {
19054
+ const sub = ctrlSubStack[ctrlSubStack.length - 1];
19055
+ if (sub) {
19056
+ ctrlSubStack.pop();
19057
+ const joined = sub.texts.join("\n").trim();
19058
+ if (joined) (sub.kind === "header" ? headerTexts : footerTexts).push(joined);
19059
+ }
19060
+ }
19061
+ continue;
19012
19062
  }
19013
- }
19014
- }
19015
- function setRunText(runEl, text) {
19016
- const tElements = findAllElements(runEl, "t");
19017
- if (tElements.length > 0) {
19018
- clearChildren(tElements[0]);
19019
- tElements[0].appendChild(tElements[0].ownerDocument.createTextNode(text));
19020
- for (let i = 1; i < tElements.length; i++) {
19021
- clearChildren(tElements[i]);
19063
+ const qname = openName;
19064
+ const local = localOf(qname);
19065
+ const attrsRaw = m[3] || "";
19066
+ const isSelfClose = selfClose === "/";
19067
+ const contentStart = m.index + full.length;
19068
+ if (isSelfClose) {
19069
+ if (local === "t") {
19070
+ const para = owningPara();
19071
+ if (para) para.tRanges.push({ contentStart: m.index, contentEnd: m.index + full.length, selfClosing: true, prefix: prefixOf(qname) });
19072
+ } else if (local === "tab" || local === "fwSpace" || local === "hwSpace" || local === "br" || local === "lineBreak") {
19073
+ if (!pendingT) {
19074
+ const para = owningPara();
19075
+ if (para) para.text += " ";
19076
+ }
19077
+ } else if (local === "run" || local === "r") {
19078
+ const para = owningPara();
19079
+ if (para && !para.selfCloseRun) para.selfCloseRun = { start: m.index, end: m.index + full.length };
19080
+ } else if (local === "cellAddr") {
19081
+ const cell = cellStack[cellStack.length - 1];
19082
+ if (cell && insideCurrentTable(stack, tableStack)) {
19083
+ const ca = parseInt(getAttr2(attrsRaw, "colAddr") || "", 10);
19084
+ const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
19085
+ if (!isNaN(ca)) cell.colAddr = ca;
19086
+ if (!isNaN(ra)) cell.rowAddr = ra;
19087
+ }
19088
+ } else if (local === "cellSpan") {
19089
+ const cell = cellStack[cellStack.length - 1];
19090
+ if (cell && insideCurrentTable(stack, tableStack)) {
19091
+ const cs = parseInt(getAttr2(attrsRaw, "colSpan") || "1", 10);
19092
+ const rs = parseInt(getAttr2(attrsRaw, "rowSpan") || "1", 10);
19093
+ cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs;
19094
+ cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs;
19095
+ }
19096
+ }
19097
+ continue;
19022
19098
  }
19023
- return;
19024
- }
19025
- if (!text) return;
19026
- const doc = runEl.ownerDocument;
19027
- const ns = runEl.namespaceURI;
19028
- const qualifiedName = runEl.prefix ? `${runEl.prefix}:t` : "t";
19029
- const tEl = ns ? doc.createElementNS(ns, qualifiedName) : doc.createElement(qualifiedName);
19030
- tEl.appendChild(doc.createTextNode(text));
19031
- runEl.appendChild(tEl);
19032
- }
19033
- function clearChildren(el) {
19034
- while (el.firstChild) el.removeChild(el.firstChild);
19035
- }
19036
- function collectTextNodes(pEl) {
19037
- const tElements = findAllElements(pEl, "t");
19038
- const result = [];
19039
- let offset = 0;
19040
- for (const t of tElements) {
19041
- const text = t.textContent || "";
19042
- result.push({ element: t, text, offset });
19043
- offset += text.length;
19044
- }
19045
- return result;
19046
- }
19047
- function replaceTextRange(tNodes, globalStart, globalEnd, newValue) {
19048
- let replaced = false;
19049
- for (const node of tNodes) {
19050
- const nodeStart = node.offset;
19051
- const nodeEnd = node.offset + node.text.length;
19052
- if (nodeEnd <= globalStart || nodeStart >= globalEnd) continue;
19053
- const localStart = Math.max(0, globalStart - nodeStart);
19054
- const localEnd = Math.min(node.text.length, globalEnd - nodeStart);
19055
- if (!replaced) {
19056
- const before = node.text.slice(0, localStart);
19057
- const after = node.text.slice(localEnd);
19058
- const newText = before + newValue + after;
19059
- clearChildren(node.element);
19060
- node.element.appendChild(node.element.ownerDocument.createTextNode(newText));
19061
- replaced = true;
19062
- } else {
19063
- const before = node.text.slice(0, localStart);
19064
- const after = node.text.slice(localEnd);
19065
- const newText = before + after;
19066
- clearChildren(node.element);
19067
- node.element.appendChild(node.element.ownerDocument.createTextNode(newText));
19099
+ if (local === "t") {
19100
+ const para = owningPara();
19101
+ if (para) pendingT = { para, contentStart };
19102
+ stack.push({ local, qname, contentStart });
19103
+ continue;
19104
+ }
19105
+ stack.push({ local, qname, contentStart });
19106
+ if (local === "p") {
19107
+ const para = {
19108
+ sectionIndex,
19109
+ kind: "excluded",
19110
+ // 분류는 push 직후 스택 기준 (자기 자신 제외)
19111
+ start: m.index,
19112
+ tRanges: [],
19113
+ text: ""
19114
+ };
19115
+ stack.pop();
19116
+ const cls = classifyPara();
19117
+ para.kind = cls.kind;
19118
+ if (cls.inTextbox) para.inTextbox = true;
19119
+ stack.push({ local, qname, contentStart });
19120
+ paraStack.push(para);
19121
+ if (para.kind === "body" || para.kind === "draw") bodyParagraphs.push(para);
19122
+ else if (para.kind === "cell") {
19123
+ const cell = cellStack[cellStack.length - 1];
19124
+ if (cell) cell.paragraphs.push(para);
19125
+ } else if (para.kind === "excluded") {
19126
+ excludedParagraphs.push(para);
19127
+ }
19128
+ } else if (local === "run" || local === "r") {
19129
+ const para = owningPara();
19130
+ if (para && para.runPrefix === void 0) para.runPrefix = prefixOf(qname);
19131
+ } else if (local === "tbl") {
19132
+ const table = {
19133
+ sectionIndex,
19134
+ start: m.index,
19135
+ topLevel: false,
19136
+ rows: [],
19137
+ cellByAnchor: /* @__PURE__ */ new Map()
19138
+ };
19139
+ stack.pop();
19140
+ table.topLevel = isTableTopLevel();
19141
+ stack.push({ local, qname, contentStart });
19142
+ tableStack.push(table);
19143
+ rowStack.push([]);
19144
+ if (table.topLevel) tables.push(table);
19145
+ } else if (local === "tr") {
19146
+ if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
19147
+ } else if (local === "tc") {
19148
+ cellStack.push({ colSpan: 1, rowSpan: 1, paragraphs: [], tables: [] });
19149
+ } else if (local === "cellAddr" || local === "cellSpan") {
19150
+ const cell = cellStack[cellStack.length - 1];
19151
+ if (cell && insideCurrentTable(stack, tableStack)) {
19152
+ if (local === "cellAddr") {
19153
+ const ca = parseInt(getAttr2(attrsRaw, "colAddr") || "", 10);
19154
+ const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
19155
+ if (!isNaN(ca)) cell.colAddr = ca;
19156
+ if (!isNaN(ra)) cell.rowAddr = ra;
19157
+ } else {
19158
+ const cs = parseInt(getAttr2(attrsRaw, "colSpan") || "1", 10);
19159
+ const rs = parseInt(getAttr2(attrsRaw, "rowSpan") || "1", 10);
19160
+ cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs;
19161
+ cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs;
19162
+ }
19163
+ }
19164
+ } else if (local === "header" || local === "footer") {
19165
+ if (stack.some((f) => f.local === "ctrl")) {
19166
+ ctrlSubStack.push({ kind: local, texts: [] });
19167
+ }
19168
+ } else if (local === "tab" || local === "fwSpace" || local === "hwSpace" || local === "br" || local === "lineBreak") {
19169
+ const para = owningPara();
19170
+ if (para) para.text += " ";
19068
19171
  }
19069
19172
  }
19070
- }
19071
- function collectCellTextNodes(tcEl) {
19072
- const tElements = findAllElements(tcEl, "t");
19073
- const result = [];
19074
- let offset = 0;
19075
- for (const t of tElements) {
19076
- const text = t.textContent || "";
19077
- result.push({ element: t, text, offset });
19078
- offset += text.length;
19079
- }
19080
- return result;
19081
- }
19082
- function applyTextReplacements(tNodes, originalFull, replacedFull) {
19083
- if (originalFull === replacedFull) return;
19084
- if (tNodes.length === 1) {
19085
- clearChildren(tNodes[0].element);
19086
- tNodes[0].element.appendChild(
19087
- tNodes[0].element.ownerDocument.createTextNode(replacedFull)
19088
- );
19089
- return;
19090
- }
19091
- let diffStart = 0;
19092
- while (diffStart < originalFull.length && diffStart < replacedFull.length && originalFull[diffStart] === replacedFull[diffStart]) {
19093
- diffStart++;
19094
- }
19095
- let diffEndOrig = originalFull.length;
19096
- let diffEndRepl = replacedFull.length;
19097
- while (diffEndOrig > diffStart && diffEndRepl > diffStart && originalFull[diffEndOrig - 1] === replacedFull[diffEndRepl - 1]) {
19098
- diffEndOrig--;
19099
- diffEndRepl--;
19100
- }
19101
- const newPart = replacedFull.slice(diffStart, diffEndRepl);
19102
- replaceTextRange(tNodes, diffStart, diffEndOrig, newPart);
19103
- }
19104
-
19105
- // src/hwpx/generator.ts
19106
- import JSZip5 from "jszip";
19107
- var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
19108
- var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
19109
- var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
19110
- var NS_OPF = "http://www.idpf.org/2007/opf/";
19111
- var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
19112
- var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
19113
- var CHAR_NORMAL = 0;
19114
- var CHAR_BOLD = 1;
19115
- var CHAR_ITALIC = 2;
19116
- var CHAR_BOLD_ITALIC = 3;
19117
- var CHAR_CODE = 4;
19118
- var CHAR_H1 = 5;
19119
- var CHAR_H2 = 6;
19120
- var CHAR_H3 = 7;
19121
- var CHAR_H4 = 8;
19122
- var CHAR_TABLE_HEADER = 9;
19123
- var CHAR_QUOTE = 10;
19124
- var PARA_NORMAL = 0;
19125
- var PARA_H1 = 1;
19126
- var PARA_H2 = 2;
19127
- var PARA_H3 = 3;
19128
- var PARA_H4 = 4;
19129
- var PARA_CODE = 5;
19130
- var PARA_QUOTE = 6;
19131
- var PARA_LIST = 7;
19132
- var DEFAULT_TEXT_COLOR = "#000000";
19133
- function resolveTheme(theme) {
19134
- return {
19135
- h1: theme?.headingColors?.[1] ?? DEFAULT_TEXT_COLOR,
19136
- h2: theme?.headingColors?.[2] ?? DEFAULT_TEXT_COLOR,
19137
- h3: theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
19138
- h4: theme?.headingColors?.[4] ?? theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
19139
- body: theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
19140
- quote: theme?.quoteColor ?? DEFAULT_TEXT_COLOR,
19141
- /** quoteColor가 명시되었는지 — blockquote charPr 분기에 사용 (baseline 호환) */
19142
- hasQuoteOption: theme?.quoteColor !== void 0,
19143
- tableHeader: theme?.tableHeaderColor ?? theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
19144
- tableHeaderBold: !!theme?.tableHeaderBold
19173
+ for (const para of bodyParagraphs) fillRunInsertPos(para, xml);
19174
+ for (const para of excludedParagraphs) fillRunInsertPos(para, xml);
19175
+ const fillTableInsertPos = (table, depth = 0) => {
19176
+ if (depth > 16) return;
19177
+ for (const row of table.rows) {
19178
+ for (const cell of row) {
19179
+ for (const para of cell.paragraphs) fillRunInsertPos(para, xml);
19180
+ for (const nested of cell.tables) fillTableInsertPos(nested, depth + 1);
19181
+ }
19182
+ }
19145
19183
  };
19184
+ for (const table of tables) fillTableInsertPos(table);
19185
+ for (const table of orphanTables) fillTableInsertPos(table);
19186
+ return { sectionIndex, xml, bodyParagraphs, tables, headerTexts, footerTexts, excludedParagraphs, orphanTables };
19146
19187
  }
19147
- async function markdownToHwpx(markdown, options) {
19148
- const theme = resolveTheme(options?.theme);
19149
- const blocks = parseMarkdownToBlocks(markdown);
19150
- const sectionXml = blocksToSectionXml(blocks, theme);
19151
- const zip = new JSZip5();
19152
- zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
19153
- zip.file("META-INF/container.xml", generateContainerXml());
19154
- zip.file("Contents/content.hpf", generateManifest());
19155
- zip.file("Contents/header.xml", generateHeaderXml(theme));
19156
- zip.file("Contents/section0.xml", sectionXml);
19157
- zip.file("Preview/PrvText.txt", buildPrvText(blocks));
19158
- return await zip.generateAsync({ type: "arraybuffer" });
19188
+ function getAttr2(attrsRaw, name) {
19189
+ const re = new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`);
19190
+ const m = attrsRaw.match(re);
19191
+ return m ? m[1] ?? m[2] : void 0;
19159
19192
  }
19160
- function buildPrvText(blocks) {
19161
- const lines = [];
19162
- let bytes = 0;
19163
- for (const b of blocks) {
19164
- const text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
19165
- if (!text) continue;
19166
- lines.push(text);
19167
- bytes += text.length * 3;
19168
- if (bytes > 1024) break;
19193
+ function insideCurrentTable(stack, tableStack) {
19194
+ if (tableStack.length === 0) return false;
19195
+ for (let i = stack.length - 1; i >= 0; i--) {
19196
+ const l = stack[i].local;
19197
+ if (l === "tc") return true;
19198
+ if (l === "tbl") return false;
19169
19199
  }
19170
- return lines.join("\n").slice(0, 1024);
19200
+ return false;
19171
19201
  }
19172
- function parseMarkdownToBlocks(md2) {
19173
- const lines = md2.split("\n");
19174
- const blocks = [];
19175
- let i = 0;
19176
- while (i < lines.length) {
19177
- const line = lines[i];
19178
- if (!line.trim()) {
19179
- i++;
19180
- continue;
19202
+ function fillRunInsertPos(para, xml) {
19203
+ if (para.tRanges.length > 0) return;
19204
+ const pEnd = findElementEnd(xml, para.start);
19205
+ if (pEnd < 0) return;
19206
+ const slice = xml.slice(para.start, pEnd);
19207
+ const runOpen = slice.match(/<((?:[A-Za-z0-9_]+:)?run)(?:\s(?:"[^"]*"|'[^']*'|[^>"'])*?)?(\/?)>/);
19208
+ if (!runOpen || runOpen.index === void 0) return;
19209
+ if (runOpen[2] === "/") return;
19210
+ const qname = runOpen[1];
19211
+ const closeIdx = slice.indexOf(`</${qname}>`, runOpen.index);
19212
+ if (closeIdx < 0) return;
19213
+ para.runInsertPos = para.start + closeIdx;
19214
+ para.runPrefix = prefixOf(qname);
19215
+ }
19216
+ function findElementEnd(xml, start) {
19217
+ const open = xml.slice(start).match(/^<([^\s/>!?]+)/);
19218
+ if (!open) return -1;
19219
+ const qname = open[1];
19220
+ const re = new RegExp(`<${qname}(?=[\\s/>])(?:"[^"]*"|'[^']*'|[^>"'])*?(/?)>|</${qname}\\s*>`, "g");
19221
+ re.lastIndex = start;
19222
+ let depth = 0;
19223
+ let mm;
19224
+ while ((mm = re.exec(xml)) !== null) {
19225
+ if (mm[0].startsWith("</")) {
19226
+ depth--;
19227
+ if (depth === 0) return mm.index + mm[0].length;
19228
+ } else if (mm[1] !== "/") {
19229
+ depth++;
19181
19230
  }
19182
- const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
19183
- if (fenceMatch) {
19184
- const fence = fenceMatch[1];
19185
- const lang = fenceMatch[2].trim();
19186
- const codeLines = [];
19187
- i++;
19188
- while (i < lines.length && !lines[i].startsWith(fence)) {
19189
- codeLines.push(lines[i]);
19190
- i++;
19231
+ }
19232
+ return -1;
19233
+ }
19234
+ function finalizeTable(table) {
19235
+ const hasAddr = table.rows.some((row) => row.some((c) => c.colAddr !== void 0 && c.rowAddr !== void 0));
19236
+ if (hasAddr) {
19237
+ for (const row of table.rows) {
19238
+ for (const cell of row) {
19239
+ if (cell.rowAddr !== void 0 && cell.colAddr !== void 0) {
19240
+ table.cellByAnchor.set(`${cell.rowAddr},${cell.colAddr}`, cell);
19241
+ }
19191
19242
  }
19192
- if (i < lines.length) i++;
19193
- blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
19194
- continue;
19195
- }
19196
- if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
19197
- blocks.push({ type: "hr" });
19198
- i++;
19199
- continue;
19200
19243
  }
19201
- const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
19202
- if (headingMatch) {
19203
- blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
19204
- i++;
19205
- continue;
19206
- }
19207
- if (line.trimStart().startsWith("|")) {
19208
- const tableRows = [];
19209
- while (i < lines.length && lines[i].trimStart().startsWith("|")) {
19210
- const row = lines[i];
19211
- if (/^[\s|:\-]+$/.test(row)) {
19212
- i++;
19213
- continue;
19244
+ return;
19245
+ }
19246
+ const numRows = table.rows.length;
19247
+ const occupied = Array.from({ length: numRows }, () => []);
19248
+ for (let rowIdx = 0; rowIdx < numRows; rowIdx++) {
19249
+ let colIdx = 0;
19250
+ for (const cell of table.rows[rowIdx]) {
19251
+ while (occupied[rowIdx][colIdx]) colIdx++;
19252
+ cell.rowAddr = rowIdx;
19253
+ cell.colAddr = colIdx;
19254
+ table.cellByAnchor.set(`${rowIdx},${colIdx}`, cell);
19255
+ for (let r = rowIdx; r < Math.min(rowIdx + cell.rowSpan, numRows); r++) {
19256
+ for (let c = colIdx; c < colIdx + cell.colSpan; c++) {
19257
+ occupied[r][c] = true;
19214
19258
  }
19215
- const cells = row.split("|").slice(1, -1).map((c) => c.trim());
19216
- if (cells.length > 0) tableRows.push(cells);
19217
- i++;
19218
19259
  }
19219
- if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
19220
- continue;
19260
+ colIdx += cell.colSpan;
19221
19261
  }
19222
- if (line.trimStart().startsWith("> ")) {
19223
- const quoteLines = [];
19224
- while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
19225
- quoteLines.push(lines[i].replace(/^>\s?/, ""));
19226
- i++;
19227
- }
19228
- for (const ql of quoteLines) {
19229
- blocks.push({ type: "blockquote", text: ql.trim() || "" });
19230
- }
19231
- continue;
19232
- }
19233
- const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
19234
- if (listMatch) {
19235
- const indent = Math.floor(listMatch[1].length / 2);
19236
- const ordered = /\d/.test(listMatch[2]);
19237
- blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
19238
- i++;
19239
- continue;
19240
- }
19241
- blocks.push({ type: "paragraph", text: line.trim() });
19242
- i++;
19243
19262
  }
19244
- return blocks;
19245
19263
  }
19246
- function parseInlineMarkdown(text) {
19247
- text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
19248
- text = text.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_, t, u) => t || u);
19249
- text = text.replace(/~~([^~]+)~~/g, "$1");
19250
- const spans = [];
19251
- const regex = /(`[^`]+`|\*{3}[^*]+\*{3}|\*{2}[^*]+\*{2}|\*[^*]+\*|_{2}[^_]+_{2}|_[^_]+_)/g;
19252
- let lastIdx = 0;
19253
- for (const match of text.matchAll(regex)) {
19254
- const idx = match.index;
19255
- if (idx > lastIdx) {
19256
- spans.push({ text: text.slice(lastIdx, idx), bold: false, italic: false, code: false });
19264
+ function buildParagraphSplices(para, newText, xml) {
19265
+ if (newText && xml) {
19266
+ const orig = paraTText(para, xml);
19267
+ if (orig && orig.trim() !== "") {
19268
+ const lead = orig.match(/^\s*/)[0];
19269
+ const trail = orig.match(/\s*$/)[0];
19270
+ if ((lead || trail) && newText.trim() !== "") {
19271
+ newText = lead + newText.replace(/^\s+|\s+$/g, "") + trail;
19272
+ }
19257
19273
  }
19258
- const raw = match[0];
19259
- if (raw.startsWith("`")) {
19260
- spans.push({ text: raw.slice(1, -1), bold: false, italic: false, code: true });
19261
- } else if (raw.startsWith("***") || raw.startsWith("___")) {
19262
- spans.push({ text: raw.slice(3, -3), bold: true, italic: true, code: false });
19263
- } else if (raw.startsWith("**") || raw.startsWith("__")) {
19264
- spans.push({ text: raw.slice(2, -2), bold: true, italic: false, code: false });
19274
+ }
19275
+ const escaped = escapeXmlText(newText);
19276
+ if (para.tRanges.length > 0) {
19277
+ const splices = [];
19278
+ const first = para.tRanges[0];
19279
+ if (first.selfClosing) {
19280
+ const prefix = first.prefix ? first.prefix + ":" : "";
19281
+ splices.push({ start: first.contentStart, end: first.contentEnd, replacement: `<${prefix}t>${escaped}</${prefix}t>` });
19265
19282
  } else {
19266
- spans.push({ text: raw.slice(1, -1), bold: false, italic: true, code: false });
19283
+ splices.push({ start: first.contentStart, end: first.contentEnd, replacement: escaped });
19267
19284
  }
19268
- lastIdx = idx + raw.length;
19285
+ for (let i = 1; i < para.tRanges.length; i++) {
19286
+ const r = para.tRanges[i];
19287
+ if (!r.selfClosing && r.contentStart < r.contentEnd) {
19288
+ splices.push({ start: r.contentStart, end: r.contentEnd, replacement: "" });
19289
+ }
19290
+ }
19291
+ return splices;
19269
19292
  }
19270
- if (lastIdx < text.length) {
19271
- spans.push({ text: text.slice(lastIdx), bold: false, italic: false, code: false });
19293
+ if (para.runInsertPos !== void 0) {
19294
+ if (!newText) return [];
19295
+ const prefix = para.runPrefix ? para.runPrefix + ":" : "";
19296
+ return [{ start: para.runInsertPos, end: para.runInsertPos, replacement: `<${prefix}t>${escaped}</${prefix}t>` }];
19272
19297
  }
19273
- if (spans.length === 0) {
19274
- spans.push({ text, bold: false, italic: false, code: false });
19298
+ if (para.selfCloseRun && xml) {
19299
+ if (!newText) return [];
19300
+ const { start, end } = para.selfCloseRun;
19301
+ const tag = xml.slice(start, end);
19302
+ const qm = tag.match(/^<([^\s/>]+)/);
19303
+ if (!qm || !tag.endsWith("/>")) return null;
19304
+ const qname = qm[1];
19305
+ const colon = qname.indexOf(":");
19306
+ const prefix = colon >= 0 ? qname.slice(0, colon) + ":" : "";
19307
+ const opened = tag.slice(0, tag.length - 2).trimEnd() + ">";
19308
+ return [{ start, end, replacement: `${opened}<${prefix}t>${escaped}</${prefix}t></${qname}>` }];
19275
19309
  }
19276
- return spans;
19277
- }
19278
- function spanToCharPrId(span) {
19279
- if (span.code) return CHAR_CODE;
19280
- if (span.bold && span.italic) return CHAR_BOLD_ITALIC;
19281
- if (span.bold) return CHAR_BOLD;
19282
- if (span.italic) return CHAR_ITALIC;
19283
- return CHAR_NORMAL;
19284
- }
19285
- function escapeXml(text) {
19286
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
19287
- }
19288
- function generateRuns(text, defaultCharPr = CHAR_NORMAL) {
19289
- const spans = parseInlineMarkdown(text);
19290
- return spans.map((span) => {
19291
- const charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
19292
- return `<hp:run charPrIDRef="${charId}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`;
19293
- }).join("");
19310
+ return newText ? null : [];
19294
19311
  }
19295
- function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL) {
19296
- if (paraPrId === PARA_CODE) {
19297
- return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
19312
+ function paraTText(para, xml) {
19313
+ let text = "";
19314
+ for (const t of para.tRanges) {
19315
+ if (t.selfClosing) continue;
19316
+ const raw = xml.slice(t.contentStart, t.contentEnd);
19317
+ if (/[<&]/.test(raw)) return null;
19318
+ text += raw;
19298
19319
  }
19299
- const runs = generateRuns(text, charPrId);
19300
- return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
19320
+ return text;
19301
19321
  }
19302
- function headingParaPrId(level) {
19303
- if (level === 1) return PARA_H1;
19304
- if (level === 2) return PARA_H2;
19305
- if (level === 3) return PARA_H3;
19306
- return PARA_H4;
19322
+ function paraTextPureT(para, xml) {
19323
+ let len = 0;
19324
+ for (const t of para.tRanges) {
19325
+ if (t.selfClosing) continue;
19326
+ len += tContentToText(xml.slice(t.contentStart, t.contentEnd)).length;
19327
+ }
19328
+ return len === para.text.length;
19307
19329
  }
19308
- function headingCharPrId(level) {
19309
- if (level === 1) return CHAR_H1;
19310
- if (level === 2) return CHAR_H2;
19311
- if (level === 3) return CHAR_H3;
19312
- return CHAR_H4;
19330
+ function buildRangeSplices(para, xml, start, end, replacement) {
19331
+ if (start < 0 || end < start) return null;
19332
+ const segs = [];
19333
+ let offset = 0;
19334
+ for (const t of para.tRanges) {
19335
+ if (t.selfClosing) continue;
19336
+ const raw = xml.slice(t.contentStart, t.contentEnd);
19337
+ if (/[<&]/.test(raw)) return null;
19338
+ segs.push({ contentStart: t.contentStart, from: offset, to: offset + raw.length });
19339
+ offset += raw.length;
19340
+ }
19341
+ if (segs.length === 0 || end > offset) return null;
19342
+ const escaped = escapeXmlText(replacement);
19343
+ if (start === end) {
19344
+ for (const seg of segs) {
19345
+ if (start >= seg.from && start <= seg.to) {
19346
+ const at = seg.contentStart + (start - seg.from);
19347
+ return [{ start: at, end: at, replacement: escaped }];
19348
+ }
19349
+ }
19350
+ return null;
19351
+ }
19352
+ const splices = [];
19353
+ let placed = false;
19354
+ for (const seg of segs) {
19355
+ if (seg.to <= start || seg.from >= end) continue;
19356
+ const localStart = Math.max(seg.from, start) - seg.from;
19357
+ const localEnd = Math.min(seg.to, end) - seg.from;
19358
+ splices.push({
19359
+ start: seg.contentStart + localStart,
19360
+ end: seg.contentStart + localEnd,
19361
+ replacement: placed ? "" : escaped
19362
+ });
19363
+ placed = true;
19364
+ }
19365
+ return placed ? splices : null;
19313
19366
  }
19314
- function generateContainerXml() {
19315
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
19316
- <ocf:container xmlns:ocf="${NS_OCF}" xmlns:hpf="${NS_HPF}">
19317
- <ocf:rootfiles>
19318
- <ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/>
19319
- </ocf:rootfiles>
19320
- </ocf:container>`;
19367
+ function applySplices(xml, splices) {
19368
+ const sorted = [...splices].sort((a, b) => a.start - b.start);
19369
+ for (let i = 1; i < sorted.length; i++) {
19370
+ if (sorted[i].start < sorted[i - 1].end) {
19371
+ throw new Error("\uC18C\uC2A4\uB9F5 splice \uBC94\uC704 \uACB9\uCE68 \u2014 \uB0B4\uBD80 \uC624\uB958");
19372
+ }
19373
+ }
19374
+ let result = xml;
19375
+ for (let i = sorted.length - 1; i >= 0; i--) {
19376
+ const s = sorted[i];
19377
+ result = result.slice(0, s.start) + s.replacement + result.slice(s.end);
19378
+ }
19379
+ return result;
19321
19380
  }
19322
- function generateManifest() {
19323
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
19324
- <opf:package xmlns:opf="${NS_OPF}" xmlns:hpf="${NS_HPF}" xmlns:hh="${NS_HEAD}">
19325
- <opf:manifest>
19326
- <opf:item id="header" href="Contents/header.xml" media-type="application/xml"/>
19327
- <opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/>
19328
- </opf:manifest>
19329
- <opf:spine>
19330
- <opf:itemref idref="header" linear="no"/>
19331
- <opf:itemref idref="section0" linear="yes"/>
19332
- </opf:spine>
19333
- </opf:package>`;
19381
+
19382
+ // src/roundtrip/zip-patch.ts
19383
+ import { deflateRawSync } from "zlib";
19384
+ var EOCD_SIG = 101010256;
19385
+ var CD_SIG = 33639248;
19386
+ var LOCAL_SIG = 67324752;
19387
+ var ZIP64_EOCD_LOC_SIG = 117853008;
19388
+ function copyBytes(buf, start, end) {
19389
+ return new Uint8Array(buf.subarray(start, end));
19334
19390
  }
19335
- function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR) {
19336
- const boldAttr = bold ? ` bold="1"` : "";
19337
- const italicAttr = italic ? ` italic="1"` : "";
19338
- const effFont = bold ? 2 : fontId;
19339
- return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="0"${boldAttr}${italicAttr}>
19340
- <hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${effFont}" japanese="${effFont}" other="${effFont}" symbol="${effFont}" user="${effFont}"/>
19341
- <hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
19342
- <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
19343
- <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
19344
- <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
19345
- </hh:charPr>`;
19391
+ function parseCentralDirectory(buf) {
19392
+ const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
19393
+ const minEocd = Math.max(0, buf.length - 22 - 65535);
19394
+ let eocdOffset = -1;
19395
+ for (let i = buf.length - 22; i >= minEocd; i--) {
19396
+ if (view.getUint32(i, true) === EOCD_SIG && i + 22 + view.getUint16(i + 20, true) === buf.length) {
19397
+ eocdOffset = i;
19398
+ break;
19399
+ }
19400
+ }
19401
+ if (eocdOffset < 0) {
19402
+ for (let i = buf.length - 22; i >= minEocd; i--) {
19403
+ if (view.getUint32(i, true) !== EOCD_SIG) continue;
19404
+ if (i + 22 + view.getUint16(i + 20, true) > buf.length) continue;
19405
+ const cand = view.getUint32(i + 16, true);
19406
+ if (cand < buf.length - 4 && view.getUint32(cand, true) === CD_SIG) {
19407
+ eocdOffset = i;
19408
+ break;
19409
+ }
19410
+ }
19411
+ }
19412
+ if (eocdOffset < 0) throw new KordocError("ZIP EOCD\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
19413
+ const totalEntries = view.getUint16(eocdOffset + 10, true);
19414
+ const cdSize = view.getUint32(eocdOffset + 12, true);
19415
+ const cdOffset = view.getUint32(eocdOffset + 16, true);
19416
+ if (cdOffset === 4294967295 || totalEntries === 65535) throw new KordocError("ZIP64\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
19417
+ if (eocdOffset >= 20 && view.getUint32(eocdOffset - 20, true) === ZIP64_EOCD_LOC_SIG) {
19418
+ throw new KordocError("ZIP64\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
19419
+ }
19420
+ const decoder = new TextDecoder("utf-8");
19421
+ const entries = [];
19422
+ let pos = cdOffset;
19423
+ for (let i = 0; i < totalEntries; i++) {
19424
+ if (view.getUint32(pos, true) !== CD_SIG) throw new KordocError("ZIP Central Directory \uC190\uC0C1");
19425
+ const flags = view.getUint16(pos + 8, true);
19426
+ const method = view.getUint16(pos + 10, true);
19427
+ const crc = view.getUint32(pos + 16, true);
19428
+ const compSize = view.getUint32(pos + 20, true);
19429
+ const uncompSize = view.getUint32(pos + 24, true);
19430
+ const nameLen = view.getUint16(pos + 28, true);
19431
+ const extraLen = view.getUint16(pos + 30, true);
19432
+ const commentLen = view.getUint16(pos + 32, true);
19433
+ const localOffset = view.getUint32(pos + 42, true);
19434
+ if (compSize === 4294967295 || uncompSize === 4294967295 || localOffset === 4294967295) {
19435
+ throw new KordocError("ZIP64\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
19436
+ }
19437
+ const name = decoder.decode(buf.subarray(pos + 46, pos + 46 + nameLen));
19438
+ const cdEnd = pos + 46 + nameLen + extraLen + commentLen;
19439
+ entries.push({ cdStart: pos, cdEnd, name, flags, method, crc, compSize, uncompSize, localOffset });
19440
+ pos = cdEnd;
19441
+ }
19442
+ return { entries, cdOffset, cdSize, eocdOffset };
19346
19443
  }
19347
- function paraPr(id, opts = {}) {
19348
- const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0 } = opts;
19349
- return ` <hh:paraPr id="${id}" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="1" suppressLineNumbers="0" checked="0" textDir="AUTO">
19350
- <hh:align horizontal="${align}" vertical="BASELINE"/>
19351
- <hh:heading type="NONE" idRef="0" level="0"/>
19352
- <hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="BREAK_WORD" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
19353
- <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
19354
- <hh:margin indent="${indent}" left="0" right="0" prev="${spaceBefore}" next="${spaceAfter}"/>
19355
- <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
19356
- <hh:border borderFillIDRef="0" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
19357
- </hh:paraPr>`;
19444
+ var CRC_TABLE = (() => {
19445
+ const table = new Uint32Array(256);
19446
+ for (let n = 0; n < 256; n++) {
19447
+ let c = n;
19448
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
19449
+ table[n] = c >>> 0;
19450
+ }
19451
+ return table;
19452
+ })();
19453
+ function crc32(data) {
19454
+ let crc = 4294967295;
19455
+ for (let i = 0; i < data.length; i++) {
19456
+ crc = CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
19457
+ }
19458
+ return (crc ^ 4294967295) >>> 0;
19358
19459
  }
19359
- function generateHeaderXml(theme) {
19360
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
19361
- <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" version="1.4" secCnt="1">
19362
- <hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/>
19363
- <hh:refList>
19364
- <hh:fontfaces itemCnt="7">
19365
- <hh:fontface lang="HANGUL" fontCnt="3">
19366
- <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
19367
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19368
- </hh:font>
19369
- <hh:font id="1" face="\uD568\uCD08\uB86C\uB3CB\uC6C0" type="TTF" isEmbedded="0">
19370
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19371
- </hh:font>
19372
- <hh:font id="2" face="HY\uACAC\uACE0\uB515" type="TTF" isEmbedded="0">
19373
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19374
- </hh:font>
19375
- </hh:fontface>
19376
- <hh:fontface lang="LATIN" fontCnt="3">
19377
- <hh:font id="0" face="Times New Roman" type="TTF" isEmbedded="0">
19378
- <hh:typeInfo familyType="FCAT_OLDSTYLE" weight="5" proportion="4" contrast="2" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="4"/>
19379
- </hh:font>
19380
- <hh:font id="1" face="Consolas" type="TTF" isEmbedded="0">
19381
- <hh:typeInfo familyType="FCAT_MODERN" weight="5" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
19382
- </hh:font>
19383
- <hh:font id="2" face="Arial Black" type="TTF" isEmbedded="0">
19384
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
19385
- </hh:font>
19386
- </hh:fontface>
19387
- <hh:fontface lang="HANJA" fontCnt="1">
19388
- <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
19389
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19390
- </hh:font>
19391
- </hh:fontface>
19392
- <hh:fontface lang="JAPANESE" fontCnt="1">
19393
- <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
19394
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19395
- </hh:font>
19396
- </hh:fontface>
19397
- <hh:fontface lang="OTHER" fontCnt="1">
19398
- <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
19399
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19400
- </hh:font>
19401
- </hh:fontface>
19402
- <hh:fontface lang="SYMBOL" fontCnt="1">
19403
- <hh:font id="0" face="Symbol" type="TTF" isEmbedded="0">
19404
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19405
- </hh:font>
19406
- </hh:fontface>
19407
- <hh:fontface lang="USER" fontCnt="1">
19408
- <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
19409
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
19410
- </hh:font>
19411
- </hh:fontface>
19412
- </hh:fontfaces>
19413
- <hh:borderFills itemCnt="2">
19414
- <hh:borderFill id="0" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
19415
- <hh:slash type="NONE" Crooked="0" isCounter="0"/>
19416
- <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
19417
- <hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/>
19418
- <hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/>
19419
- <hh:topBorder type="NONE" width="0.1 mm" color="#000000"/>
19420
- <hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/>
19421
- <hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>
19422
- <hh:fillInfo/>
19423
- </hh:borderFill>
19424
- <hh:borderFill id="1" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
19425
- <hh:slash type="NONE" Crooked="0" isCounter="0"/>
19426
- <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
19427
- <hh:leftBorder type="SOLID" width="0.12 mm" color="#000000"/>
19428
- <hh:rightBorder type="SOLID" width="0.12 mm" color="#000000"/>
19429
- <hh:topBorder type="SOLID" width="0.12 mm" color="#000000"/>
19430
- <hh:bottomBorder type="SOLID" width="0.12 mm" color="#000000"/>
19431
- <hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>
19432
- <hh:fillInfo/>
19433
- </hh:borderFill>
19434
- </hh:borderFills>
19435
- <hh:charProperties itemCnt="11">
19436
- ${charPr(0, 1e3, false, false, 0, theme.body)}
19437
- ${charPr(1, 1e3, true, false, 0, theme.body)}
19438
- ${charPr(2, 1e3, false, true, 0, theme.body)}
19439
- ${charPr(3, 1e3, true, true, 0, theme.body)}
19440
- ${charPr(4, 900, false, false, 1)}
19441
- ${charPr(5, 1800, true, false, 1, theme.h1)}
19442
- ${charPr(6, 1400, true, false, 1, theme.h2)}
19443
- ${charPr(7, 1200, true, false, 1, theme.h3)}
19444
- ${charPr(8, 1100, true, false, 1, theme.h4)}
19445
- ${charPr(CHAR_TABLE_HEADER, 1e3, theme.tableHeaderBold, false, 0, theme.tableHeader)}
19446
- ${charPr(CHAR_QUOTE, 1e3, false, true, 0, theme.quote)}
19447
- </hh:charProperties>
19448
- <hh:tabProperties itemCnt="0"/>
19449
- <hh:numberings itemCnt="0"/>
19450
- <hh:bullets itemCnt="0"/>
19451
- <hh:paraProperties itemCnt="8">
19452
- ${paraPr(0)}
19453
- ${paraPr(1, { align: "LEFT", spaceBefore: 800, spaceAfter: 200, lineSpacing: 180 })}
19454
- ${paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: 170 })}
19455
- ${paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: 160 })}
19456
- ${paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: 160 })}
19457
- ${paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400 })}
19458
- ${paraPr(6, { align: "LEFT", lineSpacing: 150, indent: 600 })}
19459
- ${paraPr(7, { align: "LEFT", lineSpacing: 160, indent: 600 })}
19460
- </hh:paraProperties>
19461
- <hh:styles itemCnt="1">
19462
- <hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langIDRef="1042" lockForm="0"/>
19463
- </hh:styles>
19464
- </hh:refList>
19465
- <hh:compatibleDocument targetProgram="HWP2018"/>
19466
- </hh:head>`;
19467
- }
19468
- function generateSecPr() {
19469
- 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>`;
19470
- }
19471
- var TABLE_ID_BASE = 1e3;
19472
- var tableIdCounter = TABLE_ID_BASE;
19473
- function nextTableId() {
19474
- return ++tableIdCounter;
19475
- }
19476
- function generateTable(rows, theme) {
19477
- const rowCnt = rows.length;
19478
- const colCnt = Math.max(...rows.map((r) => r.length), 1);
19479
- const cellW = Math.floor(44e3 / colCnt);
19480
- const cellH = 1500;
19481
- const tblW = cellW * colCnt;
19482
- const tblH = cellH * rowCnt;
19483
- const tblId = nextTableId();
19484
- const useHeaderStyle = theme.tableHeader !== theme.body || theme.tableHeaderBold;
19485
- const trElements = rows.map((row, rowIdx) => {
19486
- const cells = row.length < colCnt ? [...row, ...Array(colCnt - row.length).fill("")] : row;
19487
- const isHeaderRow = rowIdx === 0;
19488
- const headerCharPr = isHeaderRow && useHeaderStyle ? CHAR_TABLE_HEADER : CHAR_NORMAL;
19489
- const tdElements = cells.map((cell, colIdx) => {
19490
- const runs = generateRuns(cell, headerCharPr);
19491
- const p = `<hp:p paraPrIDRef="0" styleIDRef="0">${runs}</hp:p>`;
19492
- 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>`;
19493
- }).join("");
19494
- return `<hp:tr>${tdElements}</hp:tr>`;
19495
- }).join("");
19496
- 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;
19497
- 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>`;
19498
- return `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${tbl}</hp:run></hp:p>`;
19499
- }
19500
- function blocksToSectionXml(blocks, theme) {
19501
- const paraXmls = [];
19502
- let isFirst = true;
19503
- const orderedCounters = {};
19504
- let prevWasOrdered = false;
19505
- for (const block of blocks) {
19506
- let xml = "";
19507
- if (block.type !== "list_item" || !block.ordered) {
19508
- if (prevWasOrdered) {
19509
- for (const k of Object.keys(orderedCounters)) delete orderedCounters[+k];
19510
- }
19511
- prevWasOrdered = false;
19512
- }
19513
- switch (block.type) {
19514
- case "heading": {
19515
- const pId = headingParaPrId(block.level || 1);
19516
- const cId = headingCharPrId(block.level || 1);
19517
- xml = generateParagraph(block.text || "", pId, cId);
19518
- break;
19519
- }
19520
- case "paragraph":
19521
- xml = generateParagraph(block.text || "");
19522
- break;
19523
- case "code_block": {
19524
- const codeLines = (block.text || "").split("\n");
19525
- xml = codeLines.map((line) => generateParagraph(line || " ", PARA_CODE)).join("\n ");
19526
- break;
19527
- }
19528
- case "blockquote":
19529
- xml = generateParagraph(
19530
- block.text || "",
19531
- PARA_QUOTE,
19532
- theme.hasQuoteOption ? CHAR_QUOTE : CHAR_NORMAL
19533
- );
19534
- break;
19535
- case "list_item": {
19536
- const indent = block.indent || 0;
19537
- let marker;
19538
- if (block.ordered) {
19539
- orderedCounters[indent] = (orderedCounters[indent] || 0) + 1;
19540
- for (const k of Object.keys(orderedCounters)) {
19541
- if (+k > indent) delete orderedCounters[+k];
19542
- }
19543
- marker = `${orderedCounters[indent]}. `;
19544
- prevWasOrdered = true;
19545
- } else {
19546
- marker = "\xB7 ";
19547
- if (prevWasOrdered) {
19548
- for (const k of Object.keys(orderedCounters)) delete orderedCounters[+k];
19549
- }
19550
- prevWasOrdered = false;
19551
- }
19552
- const indentPrefix = " ".repeat(indent);
19553
- xml = generateParagraph(indentPrefix + marker + (block.text || ""), PARA_LIST);
19554
- break;
19555
- }
19556
- case "hr":
19557
- 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>`;
19558
- break;
19559
- case "table":
19560
- if (block.rows) {
19561
- if (isFirst) {
19562
- const secRun = `<hp:run charPrIDRef="0">${generateSecPr()}<hp:t></hp:t></hp:run>`;
19563
- paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
19564
- isFirst = false;
19565
- }
19566
- xml = generateTable(block.rows, theme);
19567
- }
19568
- break;
19569
- }
19570
- if (!xml) continue;
19571
- if (isFirst && block.type !== "table") {
19572
- xml = xml.replace(
19573
- /<hp:run charPrIDRef="(\d+)">/,
19574
- `<hp:run charPrIDRef="$1">${generateSecPr()}`
19575
- );
19576
- isFirst = false;
19577
- }
19578
- paraXmls.push(xml);
19579
- }
19580
- if (paraXmls.length === 0) {
19581
- paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${generateSecPr()}<hp:t></hp:t></hp:run></hp:p>`);
19582
- }
19583
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
19584
- <hs:sec xmlns:hs="${NS_SECTION}" xmlns:hp="${NS_PARA}">
19585
- ${paraXmls.join("\n ")}
19586
- </hs:sec>`;
19587
- }
19588
-
19589
- // src/diff/text-diff.ts
19590
- function similarity(a, b) {
19591
- if (a === b) return 1;
19592
- if (!a || !b) return 0;
19593
- const maxLen = Math.max(a.length, b.length);
19594
- if (maxLen === 0) return 1;
19595
- return 1 - levenshtein(a, b) / maxLen;
19596
- }
19597
- function normalizedSimilarity(a, b) {
19598
- return similarity(normalize(a), normalize(b));
19599
- }
19600
- function normalize(s) {
19601
- return s.replace(/\s+/g, " ").trim();
19602
- }
19603
- var MAX_LEVENSHTEIN_LEN = 1e4;
19604
- function levenshtein(a, b) {
19605
- if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
19606
- const sampleLen = Math.min(500, a.length, b.length);
19607
- let diffs = 0;
19608
- for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
19609
- const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
19610
- return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
19611
- }
19612
- if (a.length > b.length) [a, b] = [b, a];
19613
- const m = a.length;
19614
- const n = b.length;
19615
- let prev = Array.from({ length: m + 1 }, (_, i) => i);
19616
- let curr = new Array(m + 1);
19617
- for (let j = 1; j <= n; j++) {
19618
- curr[0] = j;
19619
- for (let i = 1; i <= m; i++) {
19620
- if (a[i - 1] === b[j - 1]) {
19621
- curr[i] = prev[i - 1];
19622
- } else {
19623
- curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
19624
- }
19625
- }
19626
- ;
19627
- [prev, curr] = [curr, prev];
19628
- }
19629
- return prev[m];
19630
- }
19631
-
19632
- // src/diff/compare.ts
19633
- var SIMILARITY_THRESHOLD = 0.4;
19634
- async function compare(bufferA, bufferB, options) {
19635
- const [resultA, resultB] = await Promise.all([
19636
- parse(bufferA, options),
19637
- parse(bufferB, options)
19638
- ]);
19639
- if (!resultA.success) throw new Error(`\uBB38\uC11CA \uD30C\uC2F1 \uC2E4\uD328: ${resultA.error}`);
19640
- if (!resultB.success) throw new Error(`\uBB38\uC11CB \uD30C\uC2F1 \uC2E4\uD328: ${resultB.error}`);
19641
- return diffBlocks(resultA.blocks, resultB.blocks);
19642
- }
19643
- function diffBlocks(blocksA, blocksB) {
19644
- const aligned = alignBlocks(blocksA, blocksB);
19645
- const stats = { added: 0, removed: 0, modified: 0, unchanged: 0 };
19646
- const diffs = [];
19647
- for (const [a, b] of aligned) {
19648
- if (a && b) {
19649
- const sim = blockSimilarity(a, b);
19650
- if (sim >= 0.99) {
19651
- diffs.push({ type: "unchanged", before: a, after: b, similarity: 1 });
19652
- stats.unchanged++;
19653
- } else {
19654
- const diff = { type: "modified", before: a, after: b, similarity: sim };
19655
- if (a.type === "table" && b.type === "table" && a.table && b.table) {
19656
- diff.cellDiffs = diffTableCells(a.table, b.table);
19657
- }
19658
- diffs.push(diff);
19659
- stats.modified++;
19660
- }
19661
- } else if (a) {
19662
- diffs.push({ type: "removed", before: a });
19663
- stats.removed++;
19664
- } else if (b) {
19665
- diffs.push({ type: "added", after: b });
19666
- stats.added++;
19667
- }
19668
- }
19669
- return { stats, diffs };
19670
- }
19671
- function alignBlocks(a, b) {
19672
- const m = a.length, n = b.length;
19673
- if (m * n > 1e7) return fallbackAlign(a, b);
19674
- const simCache = /* @__PURE__ */ new Map();
19675
- const getSim = (i2, j2) => {
19676
- const key = `${i2},${j2}`;
19677
- let v = simCache.get(key);
19678
- if (v === void 0) {
19679
- v = blockSimilarity(a[i2], b[j2]);
19680
- simCache.set(key, v);
19681
- }
19682
- return v;
19683
- };
19684
- const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
19685
- for (let i2 = 1; i2 <= m; i2++) {
19686
- for (let j2 = 1; j2 <= n; j2++) {
19687
- if (getSim(i2 - 1, j2 - 1) >= SIMILARITY_THRESHOLD) {
19688
- dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
19689
- } else {
19690
- dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
19691
- }
19692
- }
19693
- }
19694
- const pairs = [];
19695
- let i = m, j = n;
19696
- while (i > 0 && j > 0) {
19697
- if (getSim(i - 1, j - 1) >= SIMILARITY_THRESHOLD && dp[i][j] === dp[i - 1][j - 1] + 1) {
19698
- pairs.push([i - 1, j - 1]);
19699
- i--;
19700
- j--;
19701
- } else if (dp[i - 1][j] >= dp[i][j - 1]) {
19702
- i--;
19703
- } else {
19704
- j--;
19705
- }
19706
- }
19707
- pairs.reverse();
19708
- const result = [];
19709
- let ai = 0, bi = 0;
19710
- for (const [pi, pj] of pairs) {
19711
- while (ai < pi) result.push([a[ai++], null]);
19712
- while (bi < pj) result.push([null, b[bi++]]);
19713
- result.push([a[ai++], b[bi++]]);
19714
- }
19715
- while (ai < m) result.push([a[ai++], null]);
19716
- while (bi < n) result.push([null, b[bi++]]);
19717
- return result;
19718
- }
19719
- function fallbackAlign(a, b) {
19720
- const result = [];
19721
- const len = Math.max(a.length, b.length);
19722
- for (let i = 0; i < len; i++) {
19723
- result.push([a[i] || null, b[i] || null]);
19724
- }
19725
- return result;
19726
- }
19727
- function blockSimilarity(a, b) {
19728
- if (a.type !== b.type) return 0;
19729
- if (a.text !== void 0 && b.text !== void 0) {
19730
- return normalizedSimilarity(a.text || "", b.text || "");
19731
- }
19732
- if (a.type === "table" && a.table && b.table) {
19733
- return tableSimilarity(a.table, b.table);
19460
+ function patchZipEntries(original, replacements) {
19461
+ const { entries, cdOffset, eocdOffset } = parseCentralDirectory(original);
19462
+ const view = new DataView(original.buffer, original.byteOffset, original.byteLength);
19463
+ for (const name of replacements.keys()) {
19464
+ if (!entries.some((e) => e.name === name)) throw new KordocError(`ZIP\uC5D0 \uC5C6\uB294 \uC5D4\uD2B8\uB9AC: ${name}`);
19734
19465
  }
19735
- if (a.type === b.type) return 1;
19736
- return 0;
19737
- }
19738
- function tableSimilarity(a, b) {
19739
- const dimSim = 1 - Math.abs(a.rows * a.cols - b.rows * b.cols) / Math.max(a.rows * a.cols, b.rows * b.cols, 1);
19740
- const textsA = a.cells.flat().map((c) => c.text).join(" ");
19741
- const textsB = b.cells.flat().map((c) => c.text).join(" ");
19742
- const contentSim = normalizedSimilarity(textsA, textsB);
19743
- return dimSim * 0.3 + contentSim * 0.7;
19744
- }
19745
- function diffTableCells(a, b) {
19746
- const maxRows = Math.max(a.rows, b.rows);
19747
- const maxCols = Math.max(a.cols, b.cols);
19748
- const result = [];
19749
- for (let r = 0; r < maxRows; r++) {
19750
- const row = [];
19751
- for (let c = 0; c < maxCols; c++) {
19752
- const cellA = r < a.rows && c < a.cols ? a.cells[r][c].text : void 0;
19753
- const cellB = r < b.rows && c < b.cols ? b.cells[r][c].text : void 0;
19754
- let type;
19755
- if (cellA === void 0) type = "added";
19756
- else if (cellB === void 0) type = "removed";
19757
- else if (cellA === cellB) type = "unchanged";
19758
- else type = "modified";
19759
- row.push({ type, before: cellA, after: cellB });
19466
+ const byLocal = [...entries].sort((a, b) => a.localOffset - b.localOffset);
19467
+ const segments = [];
19468
+ const newLocalOffset = /* @__PURE__ */ new Map();
19469
+ const newMeta = /* @__PURE__ */ new Map();
19470
+ let offset = 0;
19471
+ for (let i = 0; i < byLocal.length; i++) {
19472
+ const e = byLocal[i];
19473
+ const segEnd = i + 1 < byLocal.length ? byLocal[i + 1].localOffset : cdOffset;
19474
+ newLocalOffset.set(e, offset);
19475
+ const newData = replacements.get(e.name);
19476
+ if (newData === void 0) {
19477
+ const seg = original.subarray(e.localOffset, segEnd);
19478
+ segments.push(seg);
19479
+ offset += seg.length;
19480
+ continue;
19760
19481
  }
19761
- result.push(row);
19482
+ if (view.getUint32(e.localOffset, true) !== LOCAL_SIG) throw new KordocError("ZIP \uB85C\uCEEC \uD5E4\uB354 \uC2DC\uADF8\uB2C8\uCC98 \uBD88\uC77C\uCE58");
19483
+ const nameLen = view.getUint16(e.localOffset + 26, true);
19484
+ const extraLen = view.getUint16(e.localOffset + 28, true);
19485
+ const headerLen = 30 + nameLen + extraLen;
19486
+ const header = copyBytes(original, e.localOffset, e.localOffset + headerLen);
19487
+ const hview = new DataView(header.buffer, header.byteOffset, header.byteLength);
19488
+ const method = e.method;
19489
+ const compData = method === 0 ? newData : new Uint8Array(deflateRawSync(newData));
19490
+ const crc = crc32(newData);
19491
+ const flags = e.flags & ~8;
19492
+ hview.setUint16(6, flags, true);
19493
+ hview.setUint32(14, crc, true);
19494
+ hview.setUint32(18, compData.length, true);
19495
+ hview.setUint32(22, newData.length, true);
19496
+ segments.push(header, compData);
19497
+ offset += headerLen + compData.length;
19498
+ newMeta.set(e, { crc, compSize: compData.length, uncompSize: newData.length, flags });
19762
19499
  }
19763
- return result;
19764
- }
19765
-
19766
- // src/roundtrip/patcher.ts
19767
- import JSZip6 from "jszip";
19768
-
19769
- // src/roundtrip/source-map.ts
19770
- function escapeXmlText(text) {
19771
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
19772
- }
19773
- function decodeXmlEntities(text) {
19774
- return text.replace(/&(lt|gt|amp|quot|apos|#x?[0-9a-fA-F]+);/g, (m, ent) => {
19775
- switch (ent) {
19776
- case "lt":
19777
- return "<";
19778
- case "gt":
19779
- return ">";
19780
- case "amp":
19781
- return "&";
19782
- case "quot":
19783
- return '"';
19784
- case "apos":
19785
- return "'";
19786
- }
19787
- try {
19788
- const code = ent[1] === "x" || ent[1] === "X" ? parseInt(ent.slice(2), 16) : parseInt(ent.slice(1), 10);
19789
- if (!isNaN(code) && code >= 0 && code <= 1114111) return String.fromCodePoint(code);
19790
- } catch {
19791
- }
19792
- return m;
19793
- });
19794
- }
19795
- function tContentToText(raw) {
19796
- return decodeXmlEntities(
19797
- raw.replace(/<\/?(?:[A-Za-z0-9_]+:)?(?:tab|fwSpace|hwSpace|br|lineBreak)(?:\s[^>]*)?\/?>/g, " ").replace(/<[^>]*>/g, "")
19798
- );
19799
- }
19800
- var TAG_RE = /<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<\?[\s\S]*?\?>|<!(?:"[^"]*"|'[^']*'|[^>"'])*>|<\/([^\s>]+)\s*>|<([^\s/>!?]+)((?:"[^"]*"|'[^']*'|[^>"'])*?)(\/?)>/g;
19801
- var T_BARRIER = /* @__PURE__ */ new Set([
19802
- "tbl",
19803
- "ctrl",
19804
- "caption",
19805
- "pic",
19806
- "shape",
19807
- "drawingObject",
19808
- "drawText",
19809
- "shapeComment",
19810
- "memogroup",
19811
- "memo",
19812
- "hiddenComment",
19813
- "equation",
19814
- "parameters",
19815
- "subList",
19816
- "p"
19817
- ]);
19818
- var PARA_CONTAINER = /* @__PURE__ */ new Set([
19819
- "tc",
19820
- "ctrl",
19821
- "caption",
19822
- "drawText",
19823
- "pic",
19824
- "shape",
19825
- "drawingObject",
19826
- "memogroup",
19827
- "memo",
19828
- "hiddenComment",
19829
- "footNote",
19830
- "endNote",
19831
- "fn",
19832
- "en"
19833
- // 각주/미주 — 파서는 호스트 블록 footnoteText로만 흡수
19834
- ]);
19835
- var TABLE_BARRIER = /* @__PURE__ */ new Set([
19836
- "tbl",
19837
- "ctrl",
19838
- "caption",
19839
- "memogroup",
19840
- "memo",
19841
- "hiddenComment"
19842
- ]);
19843
- function localOf(qname) {
19844
- const i = qname.indexOf(":");
19845
- return i >= 0 ? qname.slice(i + 1) : qname;
19846
- }
19847
- function prefixOf(qname) {
19848
- const i = qname.indexOf(":");
19849
- return i >= 0 ? qname.slice(0, i) : "";
19850
- }
19851
- function scanSectionXml(xml, sectionIndex) {
19852
- const stack = [];
19853
- const bodyParagraphs = [];
19854
- const tables = [];
19855
- const headerTexts = [];
19856
- const footerTexts = [];
19857
- const paraStack = [];
19858
- const tableStack = [];
19859
- const rowStack = [];
19860
- const cellStack = [];
19861
- let pendingT = null;
19862
- const ctrlSubStack = [];
19863
- const classifyPara = () => {
19864
- let sawDrawText = false;
19865
- for (let i = stack.length - 1; i >= 0; i--) {
19866
- const l = stack[i].local;
19867
- if (l === "tc") return "cell";
19868
- if (l === "drawText") {
19869
- sawDrawText = true;
19870
- continue;
19871
- }
19872
- if (PARA_CONTAINER.has(l)) return "excluded";
19873
- }
19874
- return sawDrawText ? "draw" : "body";
19875
- };
19876
- const owningPara = () => {
19877
- if (paraStack.length === 0) return null;
19878
- for (let i = stack.length - 1; i >= 0; i--) {
19879
- const l = stack[i].local;
19880
- if (l === "p") return paraStack[paraStack.length - 1];
19881
- if (T_BARRIER.has(l)) return null;
19882
- }
19883
- return null;
19884
- };
19885
- const isTableTopLevel = () => {
19886
- for (let i = stack.length - 1; i >= 0; i--) {
19887
- if (TABLE_BARRIER.has(stack[i].local)) return false;
19500
+ const newCdOffset = offset;
19501
+ for (const e of entries) {
19502
+ const cd = copyBytes(original, e.cdStart, e.cdEnd);
19503
+ const cview = new DataView(cd.buffer, cd.byteOffset, cd.byteLength);
19504
+ cview.setUint32(42, newLocalOffset.get(e), true);
19505
+ const meta = newMeta.get(e);
19506
+ if (meta) {
19507
+ cview.setUint16(8, meta.flags, true);
19508
+ cview.setUint32(16, meta.crc, true);
19509
+ cview.setUint32(20, meta.compSize, true);
19510
+ cview.setUint32(24, meta.uncompSize, true);
19888
19511
  }
19889
- return true;
19890
- };
19891
- const currentCtrlSub = () => ctrlSubStack.length > 0 ? ctrlSubStack[ctrlSubStack.length - 1] : null;
19892
- TAG_RE.lastIndex = 0;
19893
- let m;
19894
- while ((m = TAG_RE.exec(xml)) !== null) {
19895
- const [full, closeName, openName, , selfClose] = m;
19896
- if (closeName === void 0 && openName === void 0) continue;
19897
- if (closeName !== void 0) {
19898
- const local2 = localOf(closeName);
19899
- if (local2 === "t" && pendingT) {
19900
- const { para, contentStart: contentStart2 } = pendingT;
19901
- para.tRanges.push({ contentStart: contentStart2, contentEnd: m.index });
19902
- para.text += tContentToText(xml.slice(contentStart2, m.index));
19903
- pendingT = null;
19904
- }
19905
- for (let i = stack.length - 1; i >= 0; i--) {
19906
- if (stack[i].local === local2) {
19907
- stack.length = i;
19908
- break;
19512
+ segments.push(cd);
19513
+ offset += cd.length;
19514
+ }
19515
+ const newCdSize = offset - newCdOffset;
19516
+ const eocd = copyBytes(original, eocdOffset);
19517
+ const eview = new DataView(eocd.buffer, eocd.byteOffset, eocd.byteLength);
19518
+ eview.setUint32(12, newCdSize, true);
19519
+ eview.setUint32(16, newCdOffset, true);
19520
+ segments.push(eocd);
19521
+ offset += eocd.length;
19522
+ const result = new Uint8Array(offset);
19523
+ let pos = 0;
19524
+ for (const seg of segments) {
19525
+ result.set(seg, pos);
19526
+ pos += seg.length;
19527
+ }
19528
+ return result;
19529
+ }
19530
+
19531
+ // src/form/filler-hwpx.ts
19532
+ async function fillHwpx(hwpxBuffer, values) {
19533
+ const u8 = new Uint8Array(hwpxBuffer);
19534
+ const zip = await JSZip4.loadAsync(hwpxBuffer);
19535
+ const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
19536
+ if (sectionPaths.length === 0) {
19537
+ throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
19538
+ }
19539
+ const normalizedValues = normalizeValues(values);
19540
+ const matchedLabels = /* @__PURE__ */ new Set();
19541
+ const filled = [];
19542
+ const failedKeys = /* @__PURE__ */ new Set();
19543
+ const succeededKeys = /* @__PURE__ */ new Set();
19544
+ const replacements = /* @__PURE__ */ new Map();
19545
+ const encoder = new TextEncoder();
19546
+ for (let si = 0; si < sectionPaths.length; si++) {
19547
+ const xml = await zip.file(sectionPaths[si]).async("text");
19548
+ const scan = scanSectionXml(xml, si);
19549
+ const ledger = /* @__PURE__ */ new Map();
19550
+ const led = (p) => {
19551
+ let l = ledger.get(p);
19552
+ if (!l) ledger.set(p, l = { ranges: [], filledIdx: [], matchKeys: [] });
19553
+ return l;
19554
+ };
19555
+ const matchText = (p) => paraTText(p, xml) ?? p.text;
19556
+ const cellLabelText = (cell) => cell.paragraphs.filter((p) => !p.inTextbox).map((p) => matchText(p)).join("");
19557
+ const allTables = [];
19558
+ const collectTables = (tables, depth) => {
19559
+ if (depth > 16) return;
19560
+ for (const t of tables) {
19561
+ allTables.push(t);
19562
+ for (const row of t.rows) {
19563
+ for (const cell of row) collectTables(cell.tables, depth + 1);
19909
19564
  }
19910
19565
  }
19911
- if (local2 === "p") {
19912
- const para = paraStack.pop();
19913
- if (para && para.kind === "excluded") {
19914
- const sub = currentCtrlSub();
19915
- if (sub && para.text.trim()) sub.texts.push(para.text);
19566
+ };
19567
+ collectTables(scan.tables, 0);
19568
+ collectTables(scan.orphanTables, 0);
19569
+ const patternApplied = /* @__PURE__ */ new Set();
19570
+ for (const table of allTables) {
19571
+ for (const row of table.rows) {
19572
+ for (const cell of row) {
19573
+ for (const para of cell.paragraphs) {
19574
+ const text = matchText(para);
19575
+ const result = fillInCellPatterns(text, normalizedValues, matchedLabels);
19576
+ if (!result) continue;
19577
+ const l = led(para);
19578
+ if (l.fullText !== void 0) continue;
19579
+ const newT = result.text;
19580
+ let s = 0;
19581
+ while (s < text.length && s < newT.length && text[s] === newT[s]) s++;
19582
+ let eo = text.length;
19583
+ let en = newT.length;
19584
+ while (eo > s && en > s && text[eo - 1] === newT[en - 1]) {
19585
+ eo--;
19586
+ en--;
19587
+ }
19588
+ l.ranges.push({ start: s, end: eo, replacement: newT.slice(s, en) });
19589
+ patternApplied.add(cell);
19590
+ for (const m of result.matches) {
19591
+ l.filledIdx.push(filled.length);
19592
+ l.matchKeys.push(m.key);
19593
+ filled.push({ label: m.label, value: m.value, row: -1, col: -1 });
19594
+ }
19595
+ }
19916
19596
  }
19917
- } else if (local2 === "tc") {
19918
- const cell = cellStack.pop();
19919
- const row = rowStack[rowStack.length - 1];
19920
- if (cell && row) row.push(cell);
19921
- } else if (local2 === "tr") {
19922
- const row = rowStack[rowStack.length - 1];
19923
- const table = tableStack[tableStack.length - 1];
19924
- if (row && table && row.length > 0) table.rows.push(row);
19925
- if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
19926
- } else if (local2 === "tbl") {
19927
- const table = tableStack.pop();
19928
- rowStack.pop();
19929
- if (table) {
19930
- finalizeTable(table);
19931
- if (!table.topLevel) {
19932
- const cell = cellStack[cellStack.length - 1];
19933
- if (cell) cell.tables.push(table);
19597
+ }
19598
+ }
19599
+ for (const table of allTables) {
19600
+ for (let rowIdx = 0; rowIdx < table.rows.length; rowIdx++) {
19601
+ const cells = table.rows[rowIdx];
19602
+ for (let colIdx = 0; colIdx < cells.length - 1; colIdx++) {
19603
+ const labelText = cellLabelText(cells[colIdx]);
19604
+ if (!isLabelCell(labelText)) continue;
19605
+ const valueCell = cells[colIdx + 1];
19606
+ if (isKeywordLabel(cellLabelText(valueCell))) continue;
19607
+ const normalizedCellLabel = normalizeLabel(labelText);
19608
+ if (!normalizedCellLabel) continue;
19609
+ const matchKey = findMatchingKey(normalizedCellLabel, normalizedValues);
19610
+ if (matchKey === void 0) continue;
19611
+ const newValue = normalizedValues.get(matchKey);
19612
+ if (patternApplied.has(valueCell)) {
19613
+ const target = valueCell.paragraphs.find((p) => p.tRanges.length > 0) ?? valueCell.paragraphs[0];
19614
+ if (!target) continue;
19615
+ const l = led(target);
19616
+ if (l.fullText === void 0) {
19617
+ l.ranges.push({ start: 0, end: 0, replacement: newValue + " " });
19618
+ l.filledIdx.push(filled.length);
19619
+ l.matchKeys.push(matchKey);
19620
+ }
19621
+ } else {
19622
+ const paras = valueCell.paragraphs;
19623
+ if (paras.length === 0) continue;
19624
+ const l0 = led(paras[0]);
19625
+ l0.fullText = newValue;
19626
+ l0.ranges = [];
19627
+ l0.filledIdx.push(filled.length);
19628
+ l0.matchKeys.push(matchKey);
19629
+ for (let k = 1; k < paras.length; k++) {
19630
+ const lk = led(paras[k]);
19631
+ lk.fullText = "";
19632
+ lk.ranges = [];
19633
+ }
19934
19634
  }
19635
+ matchedLabels.add(matchKey);
19636
+ filled.push({
19637
+ label: labelText.trim().replace(/[::]\s*$/, ""),
19638
+ value: newValue,
19639
+ row: rowIdx,
19640
+ col: colIdx
19641
+ });
19935
19642
  }
19936
- } else if (local2 === "header" || local2 === "footer") {
19937
- const sub = ctrlSubStack[ctrlSubStack.length - 1];
19938
- if (sub) {
19939
- ctrlSubStack.pop();
19940
- const joined = sub.texts.join("\n").trim();
19941
- if (joined) (sub.kind === "header" ? headerTexts : footerTexts).push(joined);
19643
+ }
19644
+ if (table.rows.length >= 2) {
19645
+ const headerCells = table.rows[0];
19646
+ const allLabels = headerCells.length > 0 && headerCells.every((cell) => {
19647
+ const t = cellLabelText(cell).trim();
19648
+ return t.length > 0 && t.length <= 20 && isLabelCell(t);
19649
+ });
19650
+ if (allLabels) {
19651
+ for (let rowIdx = 1; rowIdx < table.rows.length; rowIdx++) {
19652
+ const dataCells = table.rows[rowIdx];
19653
+ for (let colIdx = 0; colIdx < Math.min(headerCells.length, dataCells.length); colIdx++) {
19654
+ const headerLabel = normalizeLabel(cellLabelText(headerCells[colIdx]));
19655
+ const matchKey = findMatchingKey(headerLabel, normalizedValues);
19656
+ if (matchKey === void 0) continue;
19657
+ if (matchedLabels.has(matchKey)) continue;
19658
+ const newValue = normalizedValues.get(matchKey);
19659
+ const paras = dataCells[colIdx].paragraphs;
19660
+ if (paras.length === 0) continue;
19661
+ const l0 = led(paras[0]);
19662
+ l0.fullText = newValue;
19663
+ l0.ranges = [];
19664
+ l0.filledIdx.push(filled.length);
19665
+ l0.matchKeys.push(matchKey);
19666
+ for (let k = 1; k < paras.length; k++) {
19667
+ const lk = led(paras[k]);
19668
+ lk.fullText = "";
19669
+ lk.ranges = [];
19670
+ }
19671
+ matchedLabels.add(matchKey);
19672
+ filled.push({
19673
+ label: cellLabelText(headerCells[colIdx]).trim(),
19674
+ value: newValue,
19675
+ row: rowIdx,
19676
+ col: colIdx
19677
+ });
19678
+ }
19679
+ }
19942
19680
  }
19943
19681
  }
19944
- continue;
19945
19682
  }
19946
- const qname = openName;
19947
- const local = localOf(qname);
19948
- const attrsRaw = m[3] || "";
19949
- const isSelfClose = selfClose === "/";
19950
- const contentStart = m.index + full.length;
19951
- if (isSelfClose) {
19952
- if (local === "t") {
19953
- const para = owningPara();
19954
- if (para) para.tRanges.push({ contentStart: m.index, contentEnd: m.index + full.length, selfClosing: true, prefix: prefixOf(qname) });
19955
- } else if (local === "tab" || local === "fwSpace" || local === "hwSpace" || local === "br" || local === "lineBreak") {
19956
- if (!pendingT) {
19957
- const para = owningPara();
19958
- if (para) para.text += " ";
19959
- }
19960
- } else if (local === "run" || local === "r") {
19961
- const para = owningPara();
19962
- if (para && !para.selfCloseRun) para.selfCloseRun = { start: m.index, end: m.index + full.length };
19963
- } else if (local === "cellAddr") {
19964
- const cell = cellStack[cellStack.length - 1];
19965
- if (cell && insideCurrentTable(stack, tableStack)) {
19966
- const ca = parseInt(getAttr2(attrsRaw, "colAddr") || "", 10);
19967
- const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
19968
- if (!isNaN(ca)) cell.colAddr = ca;
19969
- if (!isNaN(ra)) cell.rowAddr = ra;
19683
+ for (const para of [...scan.bodyParagraphs, ...scan.excludedParagraphs]) {
19684
+ const existing = ledger.get(para);
19685
+ if (existing?.fullText !== void 0) continue;
19686
+ const text = matchText(para);
19687
+ for (const seg of scanInlineSegments(text)) {
19688
+ const matchKey = findMatchingKey(normalizeLabel(seg.label), normalizedValues);
19689
+ if (matchKey === void 0) continue;
19690
+ const newValue = normalizedValues.get(matchKey);
19691
+ const replacement = seg.valueStart === seg.valueEnd ? padInsertion(text, seg.valueStart, newValue) : newValue;
19692
+ const l = led(para);
19693
+ l.ranges.push({ start: seg.valueStart, end: seg.valueEnd, replacement });
19694
+ matchedLabels.add(matchKey);
19695
+ l.filledIdx.push(filled.length);
19696
+ l.matchKeys.push(matchKey);
19697
+ filled.push({ label: seg.label.trim(), value: newValue, row: -1, col: -1 });
19698
+ }
19699
+ }
19700
+ const splices = [];
19701
+ for (const [para, l] of ledger) {
19702
+ let paraSplices = null;
19703
+ if (l.fullText !== void 0) {
19704
+ paraSplices = buildParagraphSplices(para, l.fullText, xml);
19705
+ } else if (l.ranges.length > 0) {
19706
+ const sorted = [...l.ranges].sort((a, b) => a.start - b.start || a.end - b.end);
19707
+ const merged = [];
19708
+ for (const r of sorted) {
19709
+ const prev = merged[merged.length - 1];
19710
+ if (prev && r.start < prev.end) continue;
19711
+ merged.push(r);
19970
19712
  }
19971
- } else if (local === "cellSpan") {
19972
- const cell = cellStack[cellStack.length - 1];
19973
- if (cell && insideCurrentTable(stack, tableStack)) {
19974
- const cs = parseInt(getAttr2(attrsRaw, "colSpan") || "1", 10);
19975
- const rs = parseInt(getAttr2(attrsRaw, "rowSpan") || "1", 10);
19976
- cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs;
19977
- cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs;
19713
+ if (paraTText(para, xml) !== null) {
19714
+ const precise = [];
19715
+ let ok = true;
19716
+ for (const r of merged) {
19717
+ const sp = buildRangeSplices(para, xml, r.start, r.end, r.replacement);
19718
+ if (!sp) {
19719
+ ok = false;
19720
+ break;
19721
+ }
19722
+ precise.push(...sp);
19723
+ }
19724
+ paraSplices = ok ? precise : null;
19725
+ } else if (paraTextPureT(para, xml)) {
19726
+ let text = para.text;
19727
+ for (let k = merged.length - 1; k >= 0; k--) {
19728
+ const r = merged[k];
19729
+ text = text.slice(0, r.start) + r.replacement + text.slice(r.end);
19730
+ }
19731
+ paraSplices = buildParagraphSplices(para, text, xml);
19732
+ } else {
19733
+ paraSplices = null;
19978
19734
  }
19979
19735
  }
19736
+ if (paraSplices === null) {
19737
+ for (const idx of l.filledIdx) filled[idx] = null;
19738
+ for (const k of l.matchKeys) failedKeys.add(k);
19739
+ continue;
19740
+ }
19741
+ for (const k of l.matchKeys) succeededKeys.add(k);
19742
+ splices.push(...paraSplices);
19743
+ }
19744
+ if (splices.length > 0) {
19745
+ replacements.set(sectionPaths[si], encoder.encode(applySplices(xml, splices)));
19746
+ }
19747
+ }
19748
+ for (const k of failedKeys) {
19749
+ if (!succeededKeys.has(k)) matchedLabels.delete(k);
19750
+ }
19751
+ const cleanFilled = filled.filter((f) => f !== null);
19752
+ const unmatched = resolveUnmatched(normalizedValues, matchedLabels, values);
19753
+ const out = replacements.size > 0 ? patchZipEntries(u8, replacements) : new Uint8Array(u8);
19754
+ return {
19755
+ buffer: out.buffer.slice(out.byteOffset, out.byteOffset + out.byteLength),
19756
+ filled: cleanFilled,
19757
+ unmatched
19758
+ };
19759
+ }
19760
+
19761
+ // src/hwpx/generator.ts
19762
+ import JSZip5 from "jszip";
19763
+ var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
19764
+ var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
19765
+ var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
19766
+ var NS_OPF = "http://www.idpf.org/2007/opf/";
19767
+ var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
19768
+ var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
19769
+ var CHAR_NORMAL = 0;
19770
+ var CHAR_BOLD = 1;
19771
+ var CHAR_ITALIC = 2;
19772
+ var CHAR_BOLD_ITALIC = 3;
19773
+ var CHAR_CODE = 4;
19774
+ var CHAR_H1 = 5;
19775
+ var CHAR_H2 = 6;
19776
+ var CHAR_H3 = 7;
19777
+ var CHAR_H4 = 8;
19778
+ var CHAR_TABLE_HEADER = 9;
19779
+ var CHAR_QUOTE = 10;
19780
+ var PARA_NORMAL = 0;
19781
+ var PARA_H1 = 1;
19782
+ var PARA_H2 = 2;
19783
+ var PARA_H3 = 3;
19784
+ var PARA_H4 = 4;
19785
+ var PARA_CODE = 5;
19786
+ var PARA_QUOTE = 6;
19787
+ var PARA_LIST = 7;
19788
+ var DEFAULT_TEXT_COLOR = "#000000";
19789
+ function resolveTheme(theme) {
19790
+ return {
19791
+ h1: theme?.headingColors?.[1] ?? DEFAULT_TEXT_COLOR,
19792
+ h2: theme?.headingColors?.[2] ?? DEFAULT_TEXT_COLOR,
19793
+ h3: theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
19794
+ h4: theme?.headingColors?.[4] ?? theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
19795
+ body: theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
19796
+ quote: theme?.quoteColor ?? DEFAULT_TEXT_COLOR,
19797
+ /** quoteColor가 명시되었는지 — blockquote charPr 분기에 사용 (baseline 호환) */
19798
+ hasQuoteOption: theme?.quoteColor !== void 0,
19799
+ tableHeader: theme?.tableHeaderColor ?? theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
19800
+ tableHeaderBold: !!theme?.tableHeaderBold
19801
+ };
19802
+ }
19803
+ async function markdownToHwpx(markdown, options) {
19804
+ const theme = resolveTheme(options?.theme);
19805
+ const blocks = parseMarkdownToBlocks(markdown);
19806
+ const sectionXml = blocksToSectionXml(blocks, theme);
19807
+ const zip = new JSZip5();
19808
+ zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
19809
+ zip.file("META-INF/container.xml", generateContainerXml());
19810
+ zip.file("Contents/content.hpf", generateManifest());
19811
+ zip.file("Contents/header.xml", generateHeaderXml(theme));
19812
+ zip.file("Contents/section0.xml", sectionXml);
19813
+ zip.file("Preview/PrvText.txt", buildPrvText(blocks));
19814
+ return await zip.generateAsync({ type: "arraybuffer" });
19815
+ }
19816
+ function buildPrvText(blocks) {
19817
+ const lines = [];
19818
+ let bytes = 0;
19819
+ for (const b of blocks) {
19820
+ const text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
19821
+ if (!text) continue;
19822
+ lines.push(text);
19823
+ bytes += text.length * 3;
19824
+ if (bytes > 1024) break;
19825
+ }
19826
+ return lines.join("\n").slice(0, 1024);
19827
+ }
19828
+ function parseMarkdownToBlocks(md2) {
19829
+ const lines = md2.split("\n");
19830
+ const blocks = [];
19831
+ let i = 0;
19832
+ while (i < lines.length) {
19833
+ const line = lines[i];
19834
+ if (!line.trim()) {
19835
+ i++;
19836
+ continue;
19837
+ }
19838
+ const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
19839
+ if (fenceMatch) {
19840
+ const fence = fenceMatch[1];
19841
+ const lang = fenceMatch[2].trim();
19842
+ const codeLines = [];
19843
+ i++;
19844
+ while (i < lines.length && !lines[i].startsWith(fence)) {
19845
+ codeLines.push(lines[i]);
19846
+ i++;
19847
+ }
19848
+ if (i < lines.length) i++;
19849
+ blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
19980
19850
  continue;
19981
19851
  }
19982
- if (local === "t") {
19983
- const para = owningPara();
19984
- if (para) pendingT = { para, contentStart };
19985
- stack.push({ local, qname, contentStart });
19852
+ if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
19853
+ blocks.push({ type: "hr" });
19854
+ i++;
19986
19855
  continue;
19987
19856
  }
19988
- stack.push({ local, qname, contentStart });
19989
- if (local === "p") {
19990
- const para = {
19991
- sectionIndex,
19992
- kind: "excluded",
19993
- // 분류는 push 직후 스택 기준 (자기 자신 제외)
19994
- start: m.index,
19995
- tRanges: [],
19996
- text: ""
19997
- };
19998
- stack.pop();
19999
- para.kind = classifyPara();
20000
- stack.push({ local, qname, contentStart });
20001
- paraStack.push(para);
20002
- if (para.kind === "body" || para.kind === "draw") bodyParagraphs.push(para);
20003
- else if (para.kind === "cell") {
20004
- const cell = cellStack[cellStack.length - 1];
20005
- if (cell) cell.paragraphs.push(para);
20006
- }
20007
- } else if (local === "run" || local === "r") {
20008
- const para = owningPara();
20009
- if (para && para.runPrefix === void 0) para.runPrefix = prefixOf(qname);
20010
- } else if (local === "tbl") {
20011
- const table = {
20012
- sectionIndex,
20013
- start: m.index,
20014
- topLevel: false,
20015
- rows: [],
20016
- cellByAnchor: /* @__PURE__ */ new Map()
20017
- };
20018
- stack.pop();
20019
- table.topLevel = isTableTopLevel();
20020
- stack.push({ local, qname, contentStart });
20021
- tableStack.push(table);
20022
- rowStack.push([]);
20023
- if (table.topLevel) tables.push(table);
20024
- } else if (local === "tr") {
20025
- if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
20026
- } else if (local === "tc") {
20027
- cellStack.push({ colSpan: 1, rowSpan: 1, paragraphs: [], tables: [] });
20028
- } else if (local === "cellAddr" || local === "cellSpan") {
20029
- const cell = cellStack[cellStack.length - 1];
20030
- if (cell && insideCurrentTable(stack, tableStack)) {
20031
- if (local === "cellAddr") {
20032
- const ca = parseInt(getAttr2(attrsRaw, "colAddr") || "", 10);
20033
- const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
20034
- if (!isNaN(ca)) cell.colAddr = ca;
20035
- if (!isNaN(ra)) cell.rowAddr = ra;
20036
- } else {
20037
- const cs = parseInt(getAttr2(attrsRaw, "colSpan") || "1", 10);
20038
- const rs = parseInt(getAttr2(attrsRaw, "rowSpan") || "1", 10);
20039
- cell.colSpan = isNaN(cs) || cs < 1 ? 1 : cs;
20040
- cell.rowSpan = isNaN(rs) || rs < 1 ? 1 : rs;
19857
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
19858
+ if (headingMatch) {
19859
+ blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
19860
+ i++;
19861
+ continue;
19862
+ }
19863
+ if (line.trimStart().startsWith("|")) {
19864
+ const tableRows = [];
19865
+ while (i < lines.length && lines[i].trimStart().startsWith("|")) {
19866
+ const row = lines[i];
19867
+ if (/^[\s|:\-]+$/.test(row)) {
19868
+ i++;
19869
+ continue;
20041
19870
  }
19871
+ const cells = row.split("|").slice(1, -1).map((c) => c.trim());
19872
+ if (cells.length > 0) tableRows.push(cells);
19873
+ i++;
20042
19874
  }
20043
- } else if (local === "header" || local === "footer") {
20044
- if (stack.some((f) => f.local === "ctrl")) {
20045
- ctrlSubStack.push({ kind: local, texts: [] });
19875
+ if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
19876
+ continue;
19877
+ }
19878
+ if (line.trimStart().startsWith("> ")) {
19879
+ const quoteLines = [];
19880
+ while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
19881
+ quoteLines.push(lines[i].replace(/^>\s?/, ""));
19882
+ i++;
20046
19883
  }
20047
- } else if (local === "tab" || local === "fwSpace" || local === "hwSpace" || local === "br" || local === "lineBreak") {
20048
- const para = owningPara();
20049
- if (para) para.text += " ";
19884
+ for (const ql of quoteLines) {
19885
+ blocks.push({ type: "blockquote", text: ql.trim() || "" });
19886
+ }
19887
+ continue;
19888
+ }
19889
+ const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
19890
+ if (listMatch) {
19891
+ const indent = Math.floor(listMatch[1].length / 2);
19892
+ const ordered = /\d/.test(listMatch[2]);
19893
+ blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
19894
+ i++;
19895
+ continue;
20050
19896
  }
19897
+ blocks.push({ type: "paragraph", text: line.trim() });
19898
+ i++;
20051
19899
  }
20052
- for (const para of bodyParagraphs) fillRunInsertPos(para, xml);
20053
- const fillTableInsertPos = (table, depth = 0) => {
20054
- if (depth > 16) return;
20055
- for (const row of table.rows) {
20056
- for (const cell of row) {
20057
- for (const para of cell.paragraphs) fillRunInsertPos(para, xml);
20058
- for (const nested of cell.tables) fillTableInsertPos(nested, depth + 1);
20059
- }
19900
+ return blocks;
19901
+ }
19902
+ function parseInlineMarkdown(text) {
19903
+ text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
19904
+ text = text.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_, t, u) => t || u);
19905
+ text = text.replace(/~~([^~]+)~~/g, "$1");
19906
+ const spans = [];
19907
+ const regex = /(`[^`]+`|\*{3}[^*]+\*{3}|\*{2}[^*]+\*{2}|\*[^*]+\*|_{2}[^_]+_{2}|_[^_]+_)/g;
19908
+ let lastIdx = 0;
19909
+ for (const match of text.matchAll(regex)) {
19910
+ const idx = match.index;
19911
+ if (idx > lastIdx) {
19912
+ spans.push({ text: text.slice(lastIdx, idx), bold: false, italic: false, code: false });
20060
19913
  }
20061
- };
20062
- for (const table of tables) fillTableInsertPos(table);
20063
- return { sectionIndex, xml, bodyParagraphs, tables, headerTexts, footerTexts };
19914
+ const raw = match[0];
19915
+ if (raw.startsWith("`")) {
19916
+ spans.push({ text: raw.slice(1, -1), bold: false, italic: false, code: true });
19917
+ } else if (raw.startsWith("***") || raw.startsWith("___")) {
19918
+ spans.push({ text: raw.slice(3, -3), bold: true, italic: true, code: false });
19919
+ } else if (raw.startsWith("**") || raw.startsWith("__")) {
19920
+ spans.push({ text: raw.slice(2, -2), bold: true, italic: false, code: false });
19921
+ } else {
19922
+ spans.push({ text: raw.slice(1, -1), bold: false, italic: true, code: false });
19923
+ }
19924
+ lastIdx = idx + raw.length;
19925
+ }
19926
+ if (lastIdx < text.length) {
19927
+ spans.push({ text: text.slice(lastIdx), bold: false, italic: false, code: false });
19928
+ }
19929
+ if (spans.length === 0) {
19930
+ spans.push({ text, bold: false, italic: false, code: false });
19931
+ }
19932
+ return spans;
20064
19933
  }
20065
- function getAttr2(attrsRaw, name) {
20066
- const re = new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`);
20067
- const m = attrsRaw.match(re);
20068
- return m ? m[1] ?? m[2] : void 0;
19934
+ function spanToCharPrId(span) {
19935
+ if (span.code) return CHAR_CODE;
19936
+ if (span.bold && span.italic) return CHAR_BOLD_ITALIC;
19937
+ if (span.bold) return CHAR_BOLD;
19938
+ if (span.italic) return CHAR_ITALIC;
19939
+ return CHAR_NORMAL;
19940
+ }
19941
+ function escapeXml(text) {
19942
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
19943
+ }
19944
+ function generateRuns(text, defaultCharPr = CHAR_NORMAL) {
19945
+ const spans = parseInlineMarkdown(text);
19946
+ return spans.map((span) => {
19947
+ const charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
19948
+ return `<hp:run charPrIDRef="${charId}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`;
19949
+ }).join("");
19950
+ }
19951
+ function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL) {
19952
+ if (paraPrId === PARA_CODE) {
19953
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
19954
+ }
19955
+ const runs = generateRuns(text, charPrId);
19956
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
19957
+ }
19958
+ function headingParaPrId(level) {
19959
+ if (level === 1) return PARA_H1;
19960
+ if (level === 2) return PARA_H2;
19961
+ if (level === 3) return PARA_H3;
19962
+ return PARA_H4;
19963
+ }
19964
+ function headingCharPrId(level) {
19965
+ if (level === 1) return CHAR_H1;
19966
+ if (level === 2) return CHAR_H2;
19967
+ if (level === 3) return CHAR_H3;
19968
+ return CHAR_H4;
19969
+ }
19970
+ function generateContainerXml() {
19971
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
19972
+ <ocf:container xmlns:ocf="${NS_OCF}" xmlns:hpf="${NS_HPF}">
19973
+ <ocf:rootfiles>
19974
+ <ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/>
19975
+ </ocf:rootfiles>
19976
+ </ocf:container>`;
19977
+ }
19978
+ function generateManifest() {
19979
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
19980
+ <opf:package xmlns:opf="${NS_OPF}" xmlns:hpf="${NS_HPF}" xmlns:hh="${NS_HEAD}">
19981
+ <opf:manifest>
19982
+ <opf:item id="header" href="Contents/header.xml" media-type="application/xml"/>
19983
+ <opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/>
19984
+ </opf:manifest>
19985
+ <opf:spine>
19986
+ <opf:itemref idref="header" linear="no"/>
19987
+ <opf:itemref idref="section0" linear="yes"/>
19988
+ </opf:spine>
19989
+ </opf:package>`;
19990
+ }
19991
+ function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR) {
19992
+ const boldAttr = bold ? ` bold="1"` : "";
19993
+ const italicAttr = italic ? ` italic="1"` : "";
19994
+ const effFont = bold ? 2 : fontId;
19995
+ return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="0"${boldAttr}${italicAttr}>
19996
+ <hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${effFont}" japanese="${effFont}" other="${effFont}" symbol="${effFont}" user="${effFont}"/>
19997
+ <hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
19998
+ <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
19999
+ <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
20000
+ <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
20001
+ </hh:charPr>`;
20002
+ }
20003
+ function paraPr(id, opts = {}) {
20004
+ const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0 } = opts;
20005
+ return ` <hh:paraPr id="${id}" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="1" suppressLineNumbers="0" checked="0" textDir="AUTO">
20006
+ <hh:align horizontal="${align}" vertical="BASELINE"/>
20007
+ <hh:heading type="NONE" idRef="0" level="0"/>
20008
+ <hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="BREAK_WORD" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
20009
+ <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
20010
+ <hh:margin indent="${indent}" left="0" right="0" prev="${spaceBefore}" next="${spaceAfter}"/>
20011
+ <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
20012
+ <hh:border borderFillIDRef="0" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
20013
+ </hh:paraPr>`;
20014
+ }
20015
+ function generateHeaderXml(theme) {
20016
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20017
+ <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" version="1.4" secCnt="1">
20018
+ <hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/>
20019
+ <hh:refList>
20020
+ <hh:fontfaces itemCnt="7">
20021
+ <hh:fontface lang="HANGUL" fontCnt="3">
20022
+ <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
20023
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20024
+ </hh:font>
20025
+ <hh:font id="1" face="\uD568\uCD08\uB86C\uB3CB\uC6C0" type="TTF" isEmbedded="0">
20026
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20027
+ </hh:font>
20028
+ <hh:font id="2" face="HY\uACAC\uACE0\uB515" type="TTF" isEmbedded="0">
20029
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20030
+ </hh:font>
20031
+ </hh:fontface>
20032
+ <hh:fontface lang="LATIN" fontCnt="3">
20033
+ <hh:font id="0" face="Times New Roman" type="TTF" isEmbedded="0">
20034
+ <hh:typeInfo familyType="FCAT_OLDSTYLE" weight="5" proportion="4" contrast="2" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="4"/>
20035
+ </hh:font>
20036
+ <hh:font id="1" face="Consolas" type="TTF" isEmbedded="0">
20037
+ <hh:typeInfo familyType="FCAT_MODERN" weight="5" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
20038
+ </hh:font>
20039
+ <hh:font id="2" face="Arial Black" type="TTF" isEmbedded="0">
20040
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
20041
+ </hh:font>
20042
+ </hh:fontface>
20043
+ <hh:fontface lang="HANJA" fontCnt="1">
20044
+ <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
20045
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20046
+ </hh:font>
20047
+ </hh:fontface>
20048
+ <hh:fontface lang="JAPANESE" fontCnt="1">
20049
+ <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
20050
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20051
+ </hh:font>
20052
+ </hh:fontface>
20053
+ <hh:fontface lang="OTHER" fontCnt="1">
20054
+ <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
20055
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20056
+ </hh:font>
20057
+ </hh:fontface>
20058
+ <hh:fontface lang="SYMBOL" fontCnt="1">
20059
+ <hh:font id="0" face="Symbol" type="TTF" isEmbedded="0">
20060
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20061
+ </hh:font>
20062
+ </hh:fontface>
20063
+ <hh:fontface lang="USER" fontCnt="1">
20064
+ <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
20065
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20066
+ </hh:font>
20067
+ </hh:fontface>
20068
+ </hh:fontfaces>
20069
+ <hh:borderFills itemCnt="2">
20070
+ <hh:borderFill id="0" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
20071
+ <hh:slash type="NONE" Crooked="0" isCounter="0"/>
20072
+ <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
20073
+ <hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/>
20074
+ <hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/>
20075
+ <hh:topBorder type="NONE" width="0.1 mm" color="#000000"/>
20076
+ <hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/>
20077
+ <hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>
20078
+ <hh:fillInfo/>
20079
+ </hh:borderFill>
20080
+ <hh:borderFill id="1" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
20081
+ <hh:slash type="NONE" Crooked="0" isCounter="0"/>
20082
+ <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
20083
+ <hh:leftBorder type="SOLID" width="0.12 mm" color="#000000"/>
20084
+ <hh:rightBorder type="SOLID" width="0.12 mm" color="#000000"/>
20085
+ <hh:topBorder type="SOLID" width="0.12 mm" color="#000000"/>
20086
+ <hh:bottomBorder type="SOLID" width="0.12 mm" color="#000000"/>
20087
+ <hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>
20088
+ <hh:fillInfo/>
20089
+ </hh:borderFill>
20090
+ </hh:borderFills>
20091
+ <hh:charProperties itemCnt="11">
20092
+ ${charPr(0, 1e3, false, false, 0, theme.body)}
20093
+ ${charPr(1, 1e3, true, false, 0, theme.body)}
20094
+ ${charPr(2, 1e3, false, true, 0, theme.body)}
20095
+ ${charPr(3, 1e3, true, true, 0, theme.body)}
20096
+ ${charPr(4, 900, false, false, 1)}
20097
+ ${charPr(5, 1800, true, false, 1, theme.h1)}
20098
+ ${charPr(6, 1400, true, false, 1, theme.h2)}
20099
+ ${charPr(7, 1200, true, false, 1, theme.h3)}
20100
+ ${charPr(8, 1100, true, false, 1, theme.h4)}
20101
+ ${charPr(CHAR_TABLE_HEADER, 1e3, theme.tableHeaderBold, false, 0, theme.tableHeader)}
20102
+ ${charPr(CHAR_QUOTE, 1e3, false, true, 0, theme.quote)}
20103
+ </hh:charProperties>
20104
+ <hh:tabProperties itemCnt="0"/>
20105
+ <hh:numberings itemCnt="0"/>
20106
+ <hh:bullets itemCnt="0"/>
20107
+ <hh:paraProperties itemCnt="8">
20108
+ ${paraPr(0)}
20109
+ ${paraPr(1, { align: "LEFT", spaceBefore: 800, spaceAfter: 200, lineSpacing: 180 })}
20110
+ ${paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: 170 })}
20111
+ ${paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: 160 })}
20112
+ ${paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: 160 })}
20113
+ ${paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400 })}
20114
+ ${paraPr(6, { align: "LEFT", lineSpacing: 150, indent: 600 })}
20115
+ ${paraPr(7, { align: "LEFT", lineSpacing: 160, indent: 600 })}
20116
+ </hh:paraProperties>
20117
+ <hh:styles itemCnt="1">
20118
+ <hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langIDRef="1042" lockForm="0"/>
20119
+ </hh:styles>
20120
+ </hh:refList>
20121
+ <hh:compatibleDocument targetProgram="HWP2018"/>
20122
+ </hh:head>`;
20069
20123
  }
20070
- function insideCurrentTable(stack, tableStack) {
20071
- if (tableStack.length === 0) return false;
20072
- for (let i = stack.length - 1; i >= 0; i--) {
20073
- const l = stack[i].local;
20074
- if (l === "tc") return true;
20075
- if (l === "tbl") return false;
20076
- }
20077
- return false;
20124
+ function generateSecPr() {
20125
+ 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>`;
20078
20126
  }
20079
- function fillRunInsertPos(para, xml) {
20080
- if (para.tRanges.length > 0) return;
20081
- const pEnd = findElementEnd(xml, para.start);
20082
- if (pEnd < 0) return;
20083
- const slice = xml.slice(para.start, pEnd);
20084
- const runOpen = slice.match(/<((?:[A-Za-z0-9_]+:)?run)(?:\s(?:"[^"]*"|'[^']*'|[^>"'])*?)?(\/?)>/);
20085
- if (!runOpen || runOpen.index === void 0) return;
20086
- if (runOpen[2] === "/") return;
20087
- const qname = runOpen[1];
20088
- const closeIdx = slice.indexOf(`</${qname}>`, runOpen.index);
20089
- if (closeIdx < 0) return;
20090
- para.runInsertPos = para.start + closeIdx;
20091
- para.runPrefix = prefixOf(qname);
20127
+ var TABLE_ID_BASE = 1e3;
20128
+ var tableIdCounter = TABLE_ID_BASE;
20129
+ function nextTableId() {
20130
+ return ++tableIdCounter;
20092
20131
  }
20093
- function findElementEnd(xml, start) {
20094
- const open = xml.slice(start).match(/^<([^\s/>!?]+)/);
20095
- if (!open) return -1;
20096
- const qname = open[1];
20097
- const re = new RegExp(`<${qname}(?=[\\s/>])(?:"[^"]*"|'[^']*'|[^>"'])*?(/?)>|</${qname}\\s*>`, "g");
20098
- re.lastIndex = start;
20099
- let depth = 0;
20100
- let mm;
20101
- while ((mm = re.exec(xml)) !== null) {
20102
- if (mm[0].startsWith("</")) {
20103
- depth--;
20104
- if (depth === 0) return mm.index + mm[0].length;
20105
- } else if (mm[1] !== "/") {
20106
- depth++;
20107
- }
20108
- }
20109
- return -1;
20132
+ function generateTable(rows, theme) {
20133
+ const rowCnt = rows.length;
20134
+ const colCnt = Math.max(...rows.map((r) => r.length), 1);
20135
+ const cellW = Math.floor(44e3 / colCnt);
20136
+ const cellH = 1500;
20137
+ const tblW = cellW * colCnt;
20138
+ const tblH = cellH * rowCnt;
20139
+ const tblId = nextTableId();
20140
+ const useHeaderStyle = theme.tableHeader !== theme.body || theme.tableHeaderBold;
20141
+ const trElements = rows.map((row, rowIdx) => {
20142
+ const cells = row.length < colCnt ? [...row, ...Array(colCnt - row.length).fill("")] : row;
20143
+ const isHeaderRow = rowIdx === 0;
20144
+ const headerCharPr = isHeaderRow && useHeaderStyle ? CHAR_TABLE_HEADER : CHAR_NORMAL;
20145
+ const tdElements = cells.map((cell, colIdx) => {
20146
+ const runs = generateRuns(cell, headerCharPr);
20147
+ const p = `<hp:p paraPrIDRef="0" styleIDRef="0">${runs}</hp:p>`;
20148
+ 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>`;
20149
+ }).join("");
20150
+ return `<hp:tr>${tdElements}</hp:tr>`;
20151
+ }).join("");
20152
+ 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;
20153
+ 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>`;
20154
+ return `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${tbl}</hp:run></hp:p>`;
20110
20155
  }
20111
- function finalizeTable(table) {
20112
- const hasAddr = table.rows.some((row) => row.some((c) => c.colAddr !== void 0 && c.rowAddr !== void 0));
20113
- if (hasAddr) {
20114
- for (const row of table.rows) {
20115
- for (const cell of row) {
20116
- if (cell.rowAddr !== void 0 && cell.colAddr !== void 0) {
20117
- table.cellByAnchor.set(`${cell.rowAddr},${cell.colAddr}`, cell);
20118
- }
20156
+ function blocksToSectionXml(blocks, theme) {
20157
+ const paraXmls = [];
20158
+ let isFirst = true;
20159
+ const orderedCounters = {};
20160
+ let prevWasOrdered = false;
20161
+ for (const block of blocks) {
20162
+ let xml = "";
20163
+ if (block.type !== "list_item" || !block.ordered) {
20164
+ if (prevWasOrdered) {
20165
+ for (const k of Object.keys(orderedCounters)) delete orderedCounters[+k];
20119
20166
  }
20167
+ prevWasOrdered = false;
20120
20168
  }
20121
- return;
20122
- }
20123
- const numRows = table.rows.length;
20124
- const occupied = Array.from({ length: numRows }, () => []);
20125
- for (let rowIdx = 0; rowIdx < numRows; rowIdx++) {
20126
- let colIdx = 0;
20127
- for (const cell of table.rows[rowIdx]) {
20128
- while (occupied[rowIdx][colIdx]) colIdx++;
20129
- cell.rowAddr = rowIdx;
20130
- cell.colAddr = colIdx;
20131
- table.cellByAnchor.set(`${rowIdx},${colIdx}`, cell);
20132
- for (let r = rowIdx; r < Math.min(rowIdx + cell.rowSpan, numRows); r++) {
20133
- for (let c = colIdx; c < colIdx + cell.colSpan; c++) {
20134
- occupied[r][c] = true;
20169
+ switch (block.type) {
20170
+ case "heading": {
20171
+ const pId = headingParaPrId(block.level || 1);
20172
+ const cId = headingCharPrId(block.level || 1);
20173
+ xml = generateParagraph(block.text || "", pId, cId);
20174
+ break;
20175
+ }
20176
+ case "paragraph":
20177
+ xml = generateParagraph(block.text || "");
20178
+ break;
20179
+ case "code_block": {
20180
+ const codeLines = (block.text || "").split("\n");
20181
+ xml = codeLines.map((line) => generateParagraph(line || " ", PARA_CODE)).join("\n ");
20182
+ break;
20183
+ }
20184
+ case "blockquote":
20185
+ xml = generateParagraph(
20186
+ block.text || "",
20187
+ PARA_QUOTE,
20188
+ theme.hasQuoteOption ? CHAR_QUOTE : CHAR_NORMAL
20189
+ );
20190
+ break;
20191
+ case "list_item": {
20192
+ const indent = block.indent || 0;
20193
+ let marker;
20194
+ if (block.ordered) {
20195
+ orderedCounters[indent] = (orderedCounters[indent] || 0) + 1;
20196
+ for (const k of Object.keys(orderedCounters)) {
20197
+ if (+k > indent) delete orderedCounters[+k];
20198
+ }
20199
+ marker = `${orderedCounters[indent]}. `;
20200
+ prevWasOrdered = true;
20201
+ } else {
20202
+ marker = "\xB7 ";
20203
+ if (prevWasOrdered) {
20204
+ for (const k of Object.keys(orderedCounters)) delete orderedCounters[+k];
20205
+ }
20206
+ prevWasOrdered = false;
20135
20207
  }
20208
+ const indentPrefix = " ".repeat(indent);
20209
+ xml = generateParagraph(indentPrefix + marker + (block.text || ""), PARA_LIST);
20210
+ break;
20136
20211
  }
20137
- colIdx += cell.colSpan;
20138
- }
20139
- }
20140
- }
20141
- function buildParagraphSplices(para, newText, xml) {
20142
- const escaped = escapeXmlText(newText);
20143
- if (para.tRanges.length > 0) {
20144
- const splices = [];
20145
- const first = para.tRanges[0];
20146
- if (first.selfClosing) {
20147
- const prefix = first.prefix ? first.prefix + ":" : "";
20148
- splices.push({ start: first.contentStart, end: first.contentEnd, replacement: `<${prefix}t>${escaped}</${prefix}t>` });
20149
- } else {
20150
- splices.push({ start: first.contentStart, end: first.contentEnd, replacement: escaped });
20212
+ case "hr":
20213
+ 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>`;
20214
+ break;
20215
+ case "table":
20216
+ if (block.rows) {
20217
+ if (isFirst) {
20218
+ const secRun = `<hp:run charPrIDRef="0">${generateSecPr()}<hp:t></hp:t></hp:run>`;
20219
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
20220
+ isFirst = false;
20221
+ }
20222
+ xml = generateTable(block.rows, theme);
20223
+ }
20224
+ break;
20151
20225
  }
20152
- for (let i = 1; i < para.tRanges.length; i++) {
20153
- const r = para.tRanges[i];
20154
- if (!r.selfClosing && r.contentStart < r.contentEnd) {
20155
- splices.push({ start: r.contentStart, end: r.contentEnd, replacement: "" });
20156
- }
20226
+ if (!xml) continue;
20227
+ if (isFirst && block.type !== "table") {
20228
+ xml = xml.replace(
20229
+ /<hp:run charPrIDRef="(\d+)">/,
20230
+ `<hp:run charPrIDRef="$1">${generateSecPr()}`
20231
+ );
20232
+ isFirst = false;
20157
20233
  }
20158
- return splices;
20159
- }
20160
- if (para.runInsertPos !== void 0) {
20161
- if (!newText) return [];
20162
- const prefix = para.runPrefix ? para.runPrefix + ":" : "";
20163
- return [{ start: para.runInsertPos, end: para.runInsertPos, replacement: `<${prefix}t>${escaped}</${prefix}t>` }];
20234
+ paraXmls.push(xml);
20164
20235
  }
20165
- if (para.selfCloseRun && xml) {
20166
- if (!newText) return [];
20167
- const { start, end } = para.selfCloseRun;
20168
- const tag = xml.slice(start, end);
20169
- const qm = tag.match(/^<([^\s/>]+)/);
20170
- if (!qm || !tag.endsWith("/>")) return null;
20171
- const qname = qm[1];
20172
- const colon = qname.indexOf(":");
20173
- const prefix = colon >= 0 ? qname.slice(0, colon) + ":" : "";
20174
- const opened = tag.slice(0, tag.length - 2).trimEnd() + ">";
20175
- return [{ start, end, replacement: `${opened}<${prefix}t>${escaped}</${prefix}t></${qname}>` }];
20236
+ if (paraXmls.length === 0) {
20237
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${generateSecPr()}<hp:t></hp:t></hp:run></hp:p>`);
20176
20238
  }
20177
- return newText ? null : [];
20239
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20240
+ <hs:sec xmlns:hs="${NS_SECTION}" xmlns:hp="${NS_PARA}">
20241
+ ${paraXmls.join("\n ")}
20242
+ </hs:sec>`;
20178
20243
  }
20179
- function applySplices(xml, splices) {
20180
- const sorted = [...splices].sort((a, b) => a.start - b.start);
20181
- for (let i = 1; i < sorted.length; i++) {
20182
- if (sorted[i].start < sorted[i - 1].end) {
20183
- throw new Error("\uC18C\uC2A4\uB9F5 splice \uBC94\uC704 \uACB9\uCE68 \u2014 \uB0B4\uBD80 \uC624\uB958");
20184
- }
20244
+
20245
+ // src/diff/text-diff.ts
20246
+ function similarity(a, b) {
20247
+ if (a === b) return 1;
20248
+ if (!a || !b) return 0;
20249
+ const maxLen = Math.max(a.length, b.length);
20250
+ if (maxLen === 0) return 1;
20251
+ return 1 - levenshtein(a, b) / maxLen;
20252
+ }
20253
+ function normalizedSimilarity(a, b) {
20254
+ return similarity(normalize(a), normalize(b));
20255
+ }
20256
+ function normalize(s) {
20257
+ return s.replace(/\s+/g, " ").trim();
20258
+ }
20259
+ var MAX_LEVENSHTEIN_LEN = 1e4;
20260
+ function levenshtein(a, b) {
20261
+ if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
20262
+ const sampleLen = Math.min(500, a.length, b.length);
20263
+ let diffs = 0;
20264
+ for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
20265
+ const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
20266
+ return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
20185
20267
  }
20186
- let result = xml;
20187
- for (let i = sorted.length - 1; i >= 0; i--) {
20188
- const s = sorted[i];
20189
- result = result.slice(0, s.start) + s.replacement + result.slice(s.end);
20268
+ if (a.length > b.length) [a, b] = [b, a];
20269
+ const m = a.length;
20270
+ const n = b.length;
20271
+ let prev = Array.from({ length: m + 1 }, (_, i) => i);
20272
+ let curr = new Array(m + 1);
20273
+ for (let j = 1; j <= n; j++) {
20274
+ curr[0] = j;
20275
+ for (let i = 1; i <= m; i++) {
20276
+ if (a[i - 1] === b[j - 1]) {
20277
+ curr[i] = prev[i - 1];
20278
+ } else {
20279
+ curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
20280
+ }
20281
+ }
20282
+ ;
20283
+ [prev, curr] = [curr, prev];
20190
20284
  }
20191
- return result;
20285
+ return prev[m];
20192
20286
  }
20193
20287
 
20194
- // src/roundtrip/zip-patch.ts
20195
- import { deflateRawSync } from "zlib";
20196
- var EOCD_SIG = 101010256;
20197
- var CD_SIG = 33639248;
20198
- var LOCAL_SIG = 67324752;
20199
- var ZIP64_EOCD_LOC_SIG = 117853008;
20200
- function copyBytes(buf, start, end) {
20201
- return new Uint8Array(buf.subarray(start, end));
20288
+ // src/diff/compare.ts
20289
+ var SIMILARITY_THRESHOLD = 0.4;
20290
+ async function compare(bufferA, bufferB, options) {
20291
+ const [resultA, resultB] = await Promise.all([
20292
+ parse(bufferA, options),
20293
+ parse(bufferB, options)
20294
+ ]);
20295
+ if (!resultA.success) throw new Error(`\uBB38\uC11CA \uD30C\uC2F1 \uC2E4\uD328: ${resultA.error}`);
20296
+ if (!resultB.success) throw new Error(`\uBB38\uC11CB \uD30C\uC2F1 \uC2E4\uD328: ${resultB.error}`);
20297
+ return diffBlocks(resultA.blocks, resultB.blocks);
20202
20298
  }
20203
- function parseCentralDirectory(buf) {
20204
- const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
20205
- const minEocd = Math.max(0, buf.length - 22 - 65535);
20206
- let eocdOffset = -1;
20207
- for (let i = buf.length - 22; i >= minEocd; i--) {
20208
- if (view.getUint32(i, true) === EOCD_SIG && i + 22 + view.getUint16(i + 20, true) === buf.length) {
20209
- eocdOffset = i;
20210
- break;
20299
+ function diffBlocks(blocksA, blocksB) {
20300
+ const aligned = alignBlocks(blocksA, blocksB);
20301
+ const stats = { added: 0, removed: 0, modified: 0, unchanged: 0 };
20302
+ const diffs = [];
20303
+ for (const [a, b] of aligned) {
20304
+ if (a && b) {
20305
+ const sim = blockSimilarity(a, b);
20306
+ if (sim >= 0.99) {
20307
+ diffs.push({ type: "unchanged", before: a, after: b, similarity: 1 });
20308
+ stats.unchanged++;
20309
+ } else {
20310
+ const diff = { type: "modified", before: a, after: b, similarity: sim };
20311
+ if (a.type === "table" && b.type === "table" && a.table && b.table) {
20312
+ diff.cellDiffs = diffTableCells(a.table, b.table);
20313
+ }
20314
+ diffs.push(diff);
20315
+ stats.modified++;
20316
+ }
20317
+ } else if (a) {
20318
+ diffs.push({ type: "removed", before: a });
20319
+ stats.removed++;
20320
+ } else if (b) {
20321
+ diffs.push({ type: "added", after: b });
20322
+ stats.added++;
20211
20323
  }
20212
20324
  }
20213
- if (eocdOffset < 0) {
20214
- for (let i = buf.length - 22; i >= minEocd; i--) {
20215
- if (view.getUint32(i, true) !== EOCD_SIG) continue;
20216
- if (i + 22 + view.getUint16(i + 20, true) > buf.length) continue;
20217
- const cand = view.getUint32(i + 16, true);
20218
- if (cand < buf.length - 4 && view.getUint32(cand, true) === CD_SIG) {
20219
- eocdOffset = i;
20220
- break;
20325
+ return { stats, diffs };
20326
+ }
20327
+ function alignBlocks(a, b) {
20328
+ const m = a.length, n = b.length;
20329
+ if (m * n > 1e7) return fallbackAlign(a, b);
20330
+ const simCache = /* @__PURE__ */ new Map();
20331
+ const getSim = (i2, j2) => {
20332
+ const key = `${i2},${j2}`;
20333
+ let v = simCache.get(key);
20334
+ if (v === void 0) {
20335
+ v = blockSimilarity(a[i2], b[j2]);
20336
+ simCache.set(key, v);
20337
+ }
20338
+ return v;
20339
+ };
20340
+ const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
20341
+ for (let i2 = 1; i2 <= m; i2++) {
20342
+ for (let j2 = 1; j2 <= n; j2++) {
20343
+ if (getSim(i2 - 1, j2 - 1) >= SIMILARITY_THRESHOLD) {
20344
+ dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
20345
+ } else {
20346
+ dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
20221
20347
  }
20222
20348
  }
20223
20349
  }
20224
- if (eocdOffset < 0) throw new KordocError("ZIP EOCD\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
20225
- const totalEntries = view.getUint16(eocdOffset + 10, true);
20226
- const cdSize = view.getUint32(eocdOffset + 12, true);
20227
- const cdOffset = view.getUint32(eocdOffset + 16, true);
20228
- if (cdOffset === 4294967295 || totalEntries === 65535) throw new KordocError("ZIP64\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
20229
- if (eocdOffset >= 20 && view.getUint32(eocdOffset - 20, true) === ZIP64_EOCD_LOC_SIG) {
20230
- throw new KordocError("ZIP64\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
20231
- }
20232
- const decoder = new TextDecoder("utf-8");
20233
- const entries = [];
20234
- let pos = cdOffset;
20235
- for (let i = 0; i < totalEntries; i++) {
20236
- if (view.getUint32(pos, true) !== CD_SIG) throw new KordocError("ZIP Central Directory \uC190\uC0C1");
20237
- const flags = view.getUint16(pos + 8, true);
20238
- const method = view.getUint16(pos + 10, true);
20239
- const crc = view.getUint32(pos + 16, true);
20240
- const compSize = view.getUint32(pos + 20, true);
20241
- const uncompSize = view.getUint32(pos + 24, true);
20242
- const nameLen = view.getUint16(pos + 28, true);
20243
- const extraLen = view.getUint16(pos + 30, true);
20244
- const commentLen = view.getUint16(pos + 32, true);
20245
- const localOffset = view.getUint32(pos + 42, true);
20246
- if (compSize === 4294967295 || uncompSize === 4294967295 || localOffset === 4294967295) {
20247
- throw new KordocError("ZIP64\uB294 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
20350
+ const pairs = [];
20351
+ let i = m, j = n;
20352
+ while (i > 0 && j > 0) {
20353
+ if (getSim(i - 1, j - 1) >= SIMILARITY_THRESHOLD && dp[i][j] === dp[i - 1][j - 1] + 1) {
20354
+ pairs.push([i - 1, j - 1]);
20355
+ i--;
20356
+ j--;
20357
+ } else if (dp[i - 1][j] >= dp[i][j - 1]) {
20358
+ i--;
20359
+ } else {
20360
+ j--;
20248
20361
  }
20249
- const name = decoder.decode(buf.subarray(pos + 46, pos + 46 + nameLen));
20250
- const cdEnd = pos + 46 + nameLen + extraLen + commentLen;
20251
- entries.push({ cdStart: pos, cdEnd, name, flags, method, crc, compSize, uncompSize, localOffset });
20252
- pos = cdEnd;
20253
20362
  }
20254
- return { entries, cdOffset, cdSize, eocdOffset };
20255
- }
20256
- var CRC_TABLE = (() => {
20257
- const table = new Uint32Array(256);
20258
- for (let n = 0; n < 256; n++) {
20259
- let c = n;
20260
- for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
20261
- table[n] = c >>> 0;
20363
+ pairs.reverse();
20364
+ const result = [];
20365
+ let ai = 0, bi = 0;
20366
+ for (const [pi, pj] of pairs) {
20367
+ while (ai < pi) result.push([a[ai++], null]);
20368
+ while (bi < pj) result.push([null, b[bi++]]);
20369
+ result.push([a[ai++], b[bi++]]);
20262
20370
  }
20263
- return table;
20264
- })();
20265
- function crc32(data) {
20266
- let crc = 4294967295;
20267
- for (let i = 0; i < data.length; i++) {
20268
- crc = CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
20371
+ while (ai < m) result.push([a[ai++], null]);
20372
+ while (bi < n) result.push([null, b[bi++]]);
20373
+ return result;
20374
+ }
20375
+ function fallbackAlign(a, b) {
20376
+ const result = [];
20377
+ const len = Math.max(a.length, b.length);
20378
+ for (let i = 0; i < len; i++) {
20379
+ result.push([a[i] || null, b[i] || null]);
20269
20380
  }
20270
- return (crc ^ 4294967295) >>> 0;
20381
+ return result;
20271
20382
  }
20272
- function patchZipEntries(original, replacements) {
20273
- const { entries, cdOffset, eocdOffset } = parseCentralDirectory(original);
20274
- const view = new DataView(original.buffer, original.byteOffset, original.byteLength);
20275
- for (const name of replacements.keys()) {
20276
- if (!entries.some((e) => e.name === name)) throw new KordocError(`ZIP\uC5D0 \uC5C6\uB294 \uC5D4\uD2B8\uB9AC: ${name}`);
20383
+ function blockSimilarity(a, b) {
20384
+ if (a.type !== b.type) return 0;
20385
+ if (a.text !== void 0 && b.text !== void 0) {
20386
+ return normalizedSimilarity(a.text || "", b.text || "");
20277
20387
  }
20278
- const byLocal = [...entries].sort((a, b) => a.localOffset - b.localOffset);
20279
- const segments = [];
20280
- const newLocalOffset = /* @__PURE__ */ new Map();
20281
- const newMeta = /* @__PURE__ */ new Map();
20282
- let offset = 0;
20283
- for (let i = 0; i < byLocal.length; i++) {
20284
- const e = byLocal[i];
20285
- const segEnd = i + 1 < byLocal.length ? byLocal[i + 1].localOffset : cdOffset;
20286
- newLocalOffset.set(e, offset);
20287
- const newData = replacements.get(e.name);
20288
- if (newData === void 0) {
20289
- const seg = original.subarray(e.localOffset, segEnd);
20290
- segments.push(seg);
20291
- offset += seg.length;
20292
- continue;
20293
- }
20294
- if (view.getUint32(e.localOffset, true) !== LOCAL_SIG) throw new KordocError("ZIP \uB85C\uCEEC \uD5E4\uB354 \uC2DC\uADF8\uB2C8\uCC98 \uBD88\uC77C\uCE58");
20295
- const nameLen = view.getUint16(e.localOffset + 26, true);
20296
- const extraLen = view.getUint16(e.localOffset + 28, true);
20297
- const headerLen = 30 + nameLen + extraLen;
20298
- const header = copyBytes(original, e.localOffset, e.localOffset + headerLen);
20299
- const hview = new DataView(header.buffer, header.byteOffset, header.byteLength);
20300
- const method = e.method;
20301
- const compData = method === 0 ? newData : new Uint8Array(deflateRawSync(newData));
20302
- const crc = crc32(newData);
20303
- const flags = e.flags & ~8;
20304
- hview.setUint16(6, flags, true);
20305
- hview.setUint32(14, crc, true);
20306
- hview.setUint32(18, compData.length, true);
20307
- hview.setUint32(22, newData.length, true);
20308
- segments.push(header, compData);
20309
- offset += headerLen + compData.length;
20310
- newMeta.set(e, { crc, compSize: compData.length, uncompSize: newData.length, flags });
20388
+ if (a.type === "table" && a.table && b.table) {
20389
+ return tableSimilarity(a.table, b.table);
20311
20390
  }
20312
- const newCdOffset = offset;
20313
- for (const e of entries) {
20314
- const cd = copyBytes(original, e.cdStart, e.cdEnd);
20315
- const cview = new DataView(cd.buffer, cd.byteOffset, cd.byteLength);
20316
- cview.setUint32(42, newLocalOffset.get(e), true);
20317
- const meta = newMeta.get(e);
20318
- if (meta) {
20319
- cview.setUint16(8, meta.flags, true);
20320
- cview.setUint32(16, meta.crc, true);
20321
- cview.setUint32(20, meta.compSize, true);
20322
- cview.setUint32(24, meta.uncompSize, true);
20391
+ if (a.type === b.type) return 1;
20392
+ return 0;
20393
+ }
20394
+ function tableSimilarity(a, b) {
20395
+ const dimSim = 1 - Math.abs(a.rows * a.cols - b.rows * b.cols) / Math.max(a.rows * a.cols, b.rows * b.cols, 1);
20396
+ const textsA = a.cells.flat().map((c) => c.text).join(" ");
20397
+ const textsB = b.cells.flat().map((c) => c.text).join(" ");
20398
+ const contentSim = normalizedSimilarity(textsA, textsB);
20399
+ return dimSim * 0.3 + contentSim * 0.7;
20400
+ }
20401
+ function diffTableCells(a, b) {
20402
+ const maxRows = Math.max(a.rows, b.rows);
20403
+ const maxCols = Math.max(a.cols, b.cols);
20404
+ const result = [];
20405
+ for (let r = 0; r < maxRows; r++) {
20406
+ const row = [];
20407
+ for (let c = 0; c < maxCols; c++) {
20408
+ const cellA = r < a.rows && c < a.cols ? a.cells[r][c].text : void 0;
20409
+ const cellB = r < b.rows && c < b.cols ? b.cells[r][c].text : void 0;
20410
+ let type;
20411
+ if (cellA === void 0) type = "added";
20412
+ else if (cellB === void 0) type = "removed";
20413
+ else if (cellA === cellB) type = "unchanged";
20414
+ else type = "modified";
20415
+ row.push({ type, before: cellA, after: cellB });
20323
20416
  }
20324
- segments.push(cd);
20325
- offset += cd.length;
20326
- }
20327
- const newCdSize = offset - newCdOffset;
20328
- const eocd = copyBytes(original, eocdOffset);
20329
- const eview = new DataView(eocd.buffer, eocd.byteOffset, eocd.byteLength);
20330
- eview.setUint32(12, newCdSize, true);
20331
- eview.setUint32(16, newCdOffset, true);
20332
- segments.push(eocd);
20333
- offset += eocd.length;
20334
- const result = new Uint8Array(offset);
20335
- let pos = 0;
20336
- for (const seg of segments) {
20337
- result.set(seg, pos);
20338
- pos += seg.length;
20417
+ result.push(row);
20339
20418
  }
20340
20419
  return result;
20341
20420
  }
20342
20421
 
20422
+ // src/roundtrip/patcher.ts
20423
+ import JSZip6 from "jszip";
20424
+
20343
20425
  // src/roundtrip/markdown-units.ts
20344
20426
  function splitMarkdownUnits(md2) {
20345
20427
  const lines = md2.split("\n");
@@ -20855,6 +20937,41 @@ function applyCellEdit(table, scanTable, gridR, gridC, newLines, ctx, before, af
20855
20937
  return 1;
20856
20938
  }
20857
20939
 
20940
+ // src/roundtrip/hwpx-entries.ts
20941
+ async function resolveSectionEntryNames(zip) {
20942
+ for (const mp of ["Contents/content.hpf", "content.hpf"]) {
20943
+ const f = zip.file(mp);
20944
+ if (!f) continue;
20945
+ const xml = await f.async("text");
20946
+ const paths = sectionPathsFromManifest(xml).filter((p) => zip.file(p) !== null);
20947
+ if (paths.length > 0) return paths;
20948
+ }
20949
+ return Object.keys(zip.files).filter((n) => /[Ss]ection\d+\.xml$/.test(n)).sort();
20950
+ }
20951
+ function sectionPathsFromManifest(xml) {
20952
+ const isSectionId = (id) => /^s/i.test(id) || id.toLowerCase().includes("section");
20953
+ const attr = (tag, name) => {
20954
+ const m = tag.match(new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`));
20955
+ return m ? m[1] ?? m[2] : "";
20956
+ };
20957
+ const idToHref = /* @__PURE__ */ new Map();
20958
+ for (const m of xml.matchAll(/<opf:item(\s(?:"[^"]*"|'[^']*'|[^>"'])*?)\/?>/g)) {
20959
+ const id = attr(m[1], "id");
20960
+ let href = attr(m[1], "href");
20961
+ const mediaType = attr(m[1], "media-type");
20962
+ if (!isSectionId(id) && !mediaType.includes("xml")) continue;
20963
+ if (!href.startsWith("/") && !href.startsWith("Contents/") && isSectionId(id)) href = "Contents/" + href;
20964
+ if (id) idToHref.set(id, href);
20965
+ }
20966
+ const ordered = [];
20967
+ for (const m of xml.matchAll(/<opf:itemref(\s(?:"[^"]*"|'[^']*'|[^>"'])*?)\/?>/g)) {
20968
+ const href = idToHref.get(attr(m[1], "idref"));
20969
+ if (href) ordered.push(href);
20970
+ }
20971
+ if (ordered.length > 0) return ordered;
20972
+ return Array.from(idToHref.entries()).filter(([id]) => isSectionId(id)).sort((a, b) => a[0].localeCompare(b[0])).map(([, href]) => href);
20973
+ }
20974
+
20858
20975
  // src/roundtrip/patcher.ts
20859
20976
  async function patchHwpx(original, editedMarkdown, options) {
20860
20977
  const skipped = [];
@@ -21213,39 +21330,6 @@ function unitToBlock(u) {
21213
21330
  function u8ToArrayBuffer(u8) {
21214
21331
  return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength);
21215
21332
  }
21216
- async function resolveSectionEntryNames(zip) {
21217
- for (const mp of ["Contents/content.hpf", "content.hpf"]) {
21218
- const f = zip.file(mp);
21219
- if (!f) continue;
21220
- const xml = await f.async("text");
21221
- const paths = sectionPathsFromManifest(xml).filter((p) => zip.file(p) !== null);
21222
- if (paths.length > 0) return paths;
21223
- }
21224
- return Object.keys(zip.files).filter((n) => /[Ss]ection\d+\.xml$/.test(n)).sort();
21225
- }
21226
- function sectionPathsFromManifest(xml) {
21227
- const isSectionId = (id) => /^s/i.test(id) || id.toLowerCase().includes("section");
21228
- const attr = (tag, name) => {
21229
- const m = tag.match(new RegExp(`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`));
21230
- return m ? m[1] ?? m[2] : "";
21231
- };
21232
- const idToHref = /* @__PURE__ */ new Map();
21233
- for (const m of xml.matchAll(/<opf:item(\s(?:"[^"]*"|'[^']*'|[^>"'])*?)\/?>/g)) {
21234
- const id = attr(m[1], "id");
21235
- let href = attr(m[1], "href");
21236
- const mediaType = attr(m[1], "media-type");
21237
- if (!isSectionId(id) && !mediaType.includes("xml")) continue;
21238
- if (!href.startsWith("/") && !href.startsWith("Contents/") && isSectionId(id)) href = "Contents/" + href;
21239
- if (id) idToHref.set(id, href);
21240
- }
21241
- const ordered = [];
21242
- for (const m of xml.matchAll(/<opf:itemref(\s(?:"[^"]*"|'[^']*'|[^>"'])*?)\/?>/g)) {
21243
- const href = idToHref.get(attr(m[1], "idref"));
21244
- if (href) ordered.push(href);
21245
- }
21246
- if (ordered.length > 0) return ordered;
21247
- return Array.from(idToHref.entries()).filter(([id]) => isSectionId(id)).sort((a, b) => a[0].localeCompare(b[0])).map(([, href]) => href);
21248
- }
21249
21333
 
21250
21334
  // src/roundtrip/hwp5-patch.ts
21251
21335
  import { deflateRawSync as deflateRawSync2 } from "zlib";
@@ -22079,6 +22163,350 @@ function stageParaPatch(scan, para, newPlain, skip) {
22079
22163
  return 1;
22080
22164
  }
22081
22165
 
22166
+ // src/roundtrip/session.ts
22167
+ import JSZip7 from "jszip";
22168
+ async function buildState(bytes) {
22169
+ const parsed = await parseHwpxDocument(u8ToArrayBuffer2(bytes));
22170
+ const zip = await JSZip7.loadAsync(bytes);
22171
+ const sectionPaths = await resolveSectionEntryNames(zip);
22172
+ if (sectionPaths.length === 0) {
22173
+ throw new Error("HWPX \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
22174
+ }
22175
+ const scans = [];
22176
+ for (let i = 0; i < sectionPaths.length; i++) {
22177
+ const xml = await zip.file(sectionPaths[i]).async("text");
22178
+ scans.push(scanSectionXml(xml, i));
22179
+ }
22180
+ const paraMap = resolveParagraphMappings(parsed.blocks, scans);
22181
+ const scanTables = scans.flatMap((s) => s.tables.filter((t) => t.rows.length > 0));
22182
+ const tableOrdinals = buildTableOrdinals(parsed.blocks);
22183
+ const fragmentBlocks = /* @__PURE__ */ new Set();
22184
+ const unitBlocks = /* @__PURE__ */ new Set();
22185
+ for (const u of buildOrigUnits(parsed.blocks)) {
22186
+ unitBlocks.add(u.blockIdx);
22187
+ if (u.fragment) fragmentBlocks.add(u.blockIdx);
22188
+ }
22189
+ for (let i = 0; i < parsed.blocks.length; i++) {
22190
+ const b = parsed.blocks[i];
22191
+ if ((b.type === "paragraph" || b.type === "heading") && b.text && !unitBlocks.has(i)) {
22192
+ fragmentBlocks.add(i);
22193
+ }
22194
+ }
22195
+ return {
22196
+ bytes,
22197
+ blocks: parsed.blocks,
22198
+ markdown: parsed.markdown,
22199
+ sectionPaths,
22200
+ scans,
22201
+ paraMap,
22202
+ scanTables,
22203
+ tableOrdinals,
22204
+ fragmentBlocks
22205
+ };
22206
+ }
22207
+ function u8ToArrayBuffer2(u8) {
22208
+ return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength);
22209
+ }
22210
+ function irCellLines(text) {
22211
+ return stripCellTokens(sanitizeText(text)).split("\n").map((s) => s.trim()).filter(Boolean);
22212
+ }
22213
+ var HwpxSession = class _HwpxSession {
22214
+ state;
22215
+ constructor(state) {
22216
+ this.state = state;
22217
+ }
22218
+ /** HWPX 바이트로 세션을 연다 (입력은 복사되어 외부 변이와 격리) */
22219
+ static async open(input) {
22220
+ const bytes = input instanceof Uint8Array ? new Uint8Array(input) : new Uint8Array(input.slice(0));
22221
+ return new _HwpxSession(await buildState(bytes));
22222
+ }
22223
+ /** 현재 문서의 IR 블록 — patchBlocks 후 갱신되므로 호출마다 다시 읽을 것 */
22224
+ get blocks() {
22225
+ return this.state.blocks;
22226
+ }
22227
+ /** 현재 문서의 마크다운 */
22228
+ get markdown() {
22229
+ return this.state.markdown;
22230
+ }
22231
+ /** 현재 문서 바이트 (복사본) */
22232
+ get bytes() {
22233
+ return new Uint8Array(this.state.bytes);
22234
+ }
22235
+ /** 블록 → 원본 위치 참조. 매핑 실패 시 undefined */
22236
+ sourceRef(blockIndex) {
22237
+ const st = this.state;
22238
+ const block = st.blocks[blockIndex];
22239
+ if (!block) return void 0;
22240
+ if (block.type === "paragraph" || block.type === "heading") {
22241
+ const para = st.paraMap.get(blockIndex)?.para;
22242
+ if (!para) return void 0;
22243
+ return { kind: "paragraph", sectionIndex: para.sectionIndex, xmlStart: para.start };
22244
+ }
22245
+ if (block.type === "table" && block.table) {
22246
+ if (st.tableOrdinals.size !== st.scanTables.length) return void 0;
22247
+ const ordinal = st.tableOrdinals.get(blockIndex);
22248
+ const t = ordinal !== void 0 ? st.scanTables[ordinal] : void 0;
22249
+ if (!t) return void 0;
22250
+ return { kind: "table", sectionIndex: t.sectionIndex, xmlStart: t.start };
22251
+ }
22252
+ return void 0;
22253
+ }
22254
+ /** 블록 편집 가능성 사전 판정 — patcher graceful-skip 게이트의 사전 버전 */
22255
+ capability(blockIndex) {
22256
+ const st = this.state;
22257
+ const block = st.blocks[blockIndex];
22258
+ if (!block) return { capability: "locked", reason: "\uBE14\uB85D \uC778\uB371\uC2A4 \uBC94\uC704 \uBC16" };
22259
+ if (block.type === "paragraph" || block.type === "heading") {
22260
+ if (st.fragmentBlocks.has(blockIndex)) {
22261
+ 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)" };
22262
+ }
22263
+ if (block.text && block.text.includes("\n")) {
22264
+ 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)" };
22265
+ }
22266
+ if (!st.paraMap.get(blockIndex)?.para) {
22267
+ 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)" };
22268
+ }
22269
+ return { capability: "text" };
22270
+ }
22271
+ if (block.type === "table" && block.table) {
22272
+ if (st.tableOrdinals.size !== st.scanTables.length) {
22273
+ return { capability: "locked", reason: "\uD45C \uAC1C\uC218 \uBD88\uC77C\uCE58 \u2014 \uC18C\uC2A4\uB9F5 \uC2E0\uB8B0 \uBD88\uAC00" };
22274
+ }
22275
+ const ordinal = st.tableOrdinals.get(blockIndex);
22276
+ const scanTable = ordinal !== void 0 ? st.scanTables[ordinal] : void 0;
22277
+ if (!scanTable) {
22278
+ return { capability: "locked", reason: "\uD45C \uC18C\uC2A4\uB9F5 \uB9E4\uD551 \uC2E4\uD328" };
22279
+ }
22280
+ const table = block.table;
22281
+ const cells = [];
22282
+ let anyEditable = false;
22283
+ for (let r = 0; r < table.rows; r++) {
22284
+ const row = [];
22285
+ for (let c = 0; c < table.cols; c++) {
22286
+ const info = cellStaticCheck(table, scanTable, r, c);
22287
+ if (info.editable) anyEditable = true;
22288
+ row.push(info);
22289
+ }
22290
+ cells.push(row);
22291
+ }
22292
+ if (!anyEditable) return { capability: "locked", reason: "\uD3B8\uC9D1 \uAC00\uB2A5\uD55C \uC140 \uC5C6\uC74C", cells };
22293
+ return { capability: "cell-text", cells };
22294
+ }
22295
+ return { capability: "locked", reason: `${block.type} \uBE14\uB85D \uD3B8\uC9D1\uC740 \uBBF8\uC9C0\uC6D0 (v1)` };
22296
+ }
22297
+ /** 전 블록의 편집 가능성 */
22298
+ capabilities() {
22299
+ return this.state.blocks.map((_, i) => this.capability(i));
22300
+ }
22301
+ /**
22302
+ * 블록 단위 증분 패치 — 적용 후 세션 상태가 새 바이트로 갱신된다.
22303
+ *
22304
+ * - 호출은 내부적으로 직렬화된다 (동시 호출 시 도착 순서대로 누적 적용)
22305
+ * - 무변경 편집(현재 텍스트와 동일)은 조용히 건너뜀 (applied/skipped 모두 제외)
22306
+ * - 변경이 하나도 적용되지 않으면 반환 data는 현재 문서와 바이트 동일
22307
+ * - changes는 "패치 전 → 후" 문서 diff — modified 수가 기대 편집 수와
22308
+ * 일치하는지 확인 용도. patchHwpx의 verification(잔차 검증)과 의미가 다르다.
22309
+ */
22310
+ async patchBlocks(edits, options) {
22311
+ const run = this.opQueue.then(() => this.patchBlocksInner(edits, options));
22312
+ this.opQueue = run.then(() => void 0, () => void 0);
22313
+ return run;
22314
+ }
22315
+ opQueue = Promise.resolve();
22316
+ async patchBlocksInner(edits, options) {
22317
+ const st = this.state;
22318
+ const skipped = [];
22319
+ let applied = 0;
22320
+ const sectionSplices = st.scans.map(() => []);
22321
+ const cellCtx = { scans: st.scans, sectionSplices, skipped };
22322
+ const seenParas = /* @__PURE__ */ new Set();
22323
+ const seenCells = /* @__PURE__ */ new Set();
22324
+ for (const edit of edits) {
22325
+ const i = edit.blockIndex;
22326
+ const block = st.blocks[i];
22327
+ if (!block) {
22328
+ skipped.push({ reason: `\uBE14\uB85D \uC778\uB371\uC2A4 \uBC94\uC704 \uBC16: ${i}` });
22329
+ continue;
22330
+ }
22331
+ if (block.type === "table" && block.table) {
22332
+ if (!edit.cells?.length) {
22333
+ skipped.push({ reason: "\uD45C \uBE14\uB85D\uC5D0\uB294 cells \uD3B8\uC9D1\uB9CC \uC9C0\uC6D0", before: summarize(block.table.caption ?? "(\uD45C)") });
22334
+ continue;
22335
+ }
22336
+ if (st.tableOrdinals.size !== st.scanTables.length) {
22337
+ skipped.push({ reason: "\uD45C \uAC1C\uC218 \uBD88\uC77C\uCE58 \u2014 \uC18C\uC2A4\uB9F5 \uC2E0\uB8B0 \uBD88\uAC00" });
22338
+ continue;
22339
+ }
22340
+ const ordinal = st.tableOrdinals.get(i);
22341
+ const scanTable = ordinal !== void 0 ? st.scanTables[ordinal] : void 0;
22342
+ if (!scanTable) {
22343
+ skipped.push({ reason: "\uD45C \uC18C\uC2A4\uB9F5 \uB9E4\uD551 \uC2E4\uD328" });
22344
+ continue;
22345
+ }
22346
+ for (const cellEdit of edit.cells) {
22347
+ const key = `${i}:${cellEdit.row},${cellEdit.col}`;
22348
+ if (seenCells.has(key)) {
22349
+ 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) });
22350
+ continue;
22351
+ }
22352
+ const irCell = block.table.cells[cellEdit.row]?.[cellEdit.col];
22353
+ if (!irCell) {
22354
+ skipped.push({ reason: `\uC140 \uC88C\uD45C \uBC94\uC704 \uBC16: ${cellEdit.row},${cellEdit.col}`, after: summarize(cellEdit.text) });
22355
+ continue;
22356
+ }
22357
+ if (extractCellTokens(irCell.text) !== extractCellTokens(cellEdit.text)) {
22358
+ skipped.push({ reason: "\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0", before: summarize(irCell.text), after: summarize(cellEdit.text) });
22359
+ continue;
22360
+ }
22361
+ const newLines = stripCellTokens(cellEdit.text).split("\n").map((s) => s.trim()).filter(Boolean);
22362
+ const origLines = irCellLines(irCell.text);
22363
+ if (newLines.join("\n") === origLines.join("\n")) continue;
22364
+ const n = applyCellEdit(
22365
+ block.table,
22366
+ scanTable,
22367
+ cellEdit.row,
22368
+ cellEdit.col,
22369
+ newLines,
22370
+ cellCtx,
22371
+ irCell.text,
22372
+ cellEdit.text,
22373
+ origLines.length
22374
+ );
22375
+ if (n > 0) seenCells.add(key);
22376
+ applied += n;
22377
+ }
22378
+ continue;
22379
+ }
22380
+ if ((block.type === "paragraph" || block.type === "heading") && edit.newText !== void 0) {
22381
+ if (seenParas.has(i)) {
22382
+ 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) });
22383
+ continue;
22384
+ }
22385
+ const n = this.patchParagraphPlain(i, block, edit.newText, sectionSplices, skipped);
22386
+ if (n > 0) seenParas.add(i);
22387
+ applied += n;
22388
+ continue;
22389
+ }
22390
+ skipped.push({
22391
+ reason: `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBE14\uB85D \uC720\uD615(${block.type}) \uB610\uB294 \uD3B8\uC9D1 \uD615\uC2DD`,
22392
+ before: summarize(block.text ?? "")
22393
+ });
22394
+ }
22395
+ const replacements = /* @__PURE__ */ new Map();
22396
+ const encoder = new TextEncoder();
22397
+ try {
22398
+ for (let s = 0; s < st.scans.length; s++) {
22399
+ if (sectionSplices[s].length === 0) continue;
22400
+ replacements.set(st.sectionPaths[s], encoder.encode(applySplices(st.scans[s].xml, sectionSplices[s])));
22401
+ }
22402
+ } catch (err) {
22403
+ return { success: false, applied: 0, skipped, error: `\uC18C\uC2A4\uB9F5 splice \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}` };
22404
+ }
22405
+ if (replacements.size === 0) {
22406
+ let changes2;
22407
+ if (options?.verify !== false) {
22408
+ const units = splitMarkdownUnits(st.markdown);
22409
+ changes2 = diffUnitLists(units, units);
22410
+ }
22411
+ return { success: true, data: new Uint8Array(st.bytes), applied, skipped, changes: changes2 };
22412
+ }
22413
+ let data;
22414
+ try {
22415
+ data = patchZipEntries(st.bytes, replacements);
22416
+ } catch (err) {
22417
+ return { success: false, applied: 0, skipped, error: `ZIP \uC7AC\uC870\uB9BD \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}` };
22418
+ }
22419
+ const beforeMarkdown = st.markdown;
22420
+ let newState;
22421
+ try {
22422
+ newState = await buildState(data);
22423
+ } catch (err) {
22424
+ 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)}` };
22425
+ }
22426
+ this.state = newState;
22427
+ let changes;
22428
+ if (options?.verify !== false) {
22429
+ changes = diffUnitLists(splitMarkdownUnits(beforeMarkdown), splitMarkdownUnits(newState.markdown));
22430
+ }
22431
+ return { success: true, data: new Uint8Array(data), applied, skipped, changes };
22432
+ }
22433
+ /** 문단/헤딩 평문 편집 — patcher.patchParagraphUnit의 평문 입력 버전 */
22434
+ patchParagraphPlain(blockIndex, block, newTextRaw, sectionSplices, skipped) {
22435
+ const skip = (reason) => {
22436
+ skipped.push({ reason, before: summarize(block.text ?? ""), after: summarize(newTextRaw) });
22437
+ return 0;
22438
+ };
22439
+ const st = this.state;
22440
+ if (newTextRaw === (block.text ?? "")) return 0;
22441
+ if (st.fragmentBlocks.has(blockIndex)) {
22442
+ 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)");
22443
+ }
22444
+ if (block.text && block.text.includes("\n")) {
22445
+ 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)");
22446
+ }
22447
+ const mapping = st.paraMap.get(blockIndex);
22448
+ if (!mapping?.para) {
22449
+ 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)");
22450
+ }
22451
+ let newPlain = newTextRaw.split("\n").map((l) => l.trim()).filter(Boolean).join(" ");
22452
+ if (mapping.prefixStripped) {
22453
+ const origPrefix = block.text.split(" ", 1)[0];
22454
+ const sp = newPlain.indexOf(" ");
22455
+ const newFirst = sp > 0 ? newPlain.slice(0, sp) : newPlain;
22456
+ if (newFirst === origPrefix || /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u.test(newFirst)) {
22457
+ newPlain = sp > 0 ? newPlain.slice(sp + 1) : "";
22458
+ } else {
22459
+ 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) });
22460
+ }
22461
+ }
22462
+ if (newPlain === "") {
22463
+ return skip("\uBE14\uB85D \uBE44\uC6B0\uAE30/\uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 (v1) \u2014 \uC6D0\uBCF8 \uC720\uC9C0");
22464
+ }
22465
+ if (sanitizeText(newPlain) !== newPlain) {
22466
+ 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");
22467
+ }
22468
+ const splices = buildParagraphSplices(mapping.para, newPlain, st.scans[mapping.para.sectionIndex]?.xml);
22469
+ if (splices === null) return skip("\uBB38\uB2E8\uC5D0 \uD14D\uC2A4\uD2B8 \uB178\uB4DC\uB97C \uB9CC\uB4E4 \uC218 \uC5C6\uC74C");
22470
+ sectionSplices[mapping.para.sectionIndex].push(...splices);
22471
+ return 1;
22472
+ }
22473
+ };
22474
+ function cellStaticCheck(table, scanTable, r, c) {
22475
+ const irCell = table.cells[r]?.[c];
22476
+ if (!irCell) return { editable: false, reason: "\uC140 \uC88C\uD45C \uBC94\uC704 \uBC16" };
22477
+ const cell = scanTable.cellByAnchor.get(`${r},${c}`);
22478
+ if (!cell) return { editable: false, reason: "\uBCD1\uD569 \uC601\uC5ED\uC758 \uBE48 \uCE78\uC774\uAC70\uB098 \uC88C\uD45C \uBD88\uC77C\uCE58" };
22479
+ const scanJoined = cell.paragraphs.map((p) => p.text).filter((t) => normForMatch(t)).join("\n");
22480
+ if (normForMatch(scanJoined) !== normForMatch(stripCellTokens(irCell.text))) {
22481
+ if (normForMatch(irCell.text) !== "" || normForMatch(scanJoined) !== "") {
22482
+ const flatBlocks = (irCell.blocks ?? []).filter((b) => b.type === "paragraph" || b.type === "heading");
22483
+ const flatJoined = flatBlocks.map((b) => b.text ?? "").join("\n");
22484
+ if (normForMatch(scanJoined) !== normForMatch(flatJoined)) {
22485
+ 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" };
22486
+ }
22487
+ }
22488
+ }
22489
+ const nonEmpty = cell.paragraphs.filter((p) => normForMatch(p.text) !== "");
22490
+ if (nonEmpty.length === 0) {
22491
+ if (cell.paragraphs.length === 0) {
22492
+ return { editable: false, reason: "\uBE48 \uC140\uC5D0 \uBB38\uB2E8\uC774 \uC5C6\uC5B4 \uD14D\uC2A4\uD2B8 \uC0BD\uC785 \uBD88\uAC00" };
22493
+ }
22494
+ return { editable: true };
22495
+ }
22496
+ const lines = irCellLines(irCell.text);
22497
+ if (lines.length !== nonEmpty.length) {
22498
+ 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" };
22499
+ }
22500
+ return { editable: true };
22501
+ }
22502
+ async function openHwpxDocument(input) {
22503
+ return HwpxSession.open(input);
22504
+ }
22505
+ async function patchHwpxBlocks(original, edits, options) {
22506
+ const session = await HwpxSession.open(original);
22507
+ return session.patchBlocks(edits, options);
22508
+ }
22509
+
22082
22510
  // src/print/renderer.ts
22083
22511
  import { existsSync } from "fs";
22084
22512
  import MarkdownIt from "markdown-it";
@@ -22319,7 +22747,7 @@ async function parseHwp(buffer, options) {
22319
22747
  async function parsePdf(buffer, options) {
22320
22748
  let parsePdfDocument;
22321
22749
  try {
22322
- const mod = await import("./parser-3N6FZSKU.js");
22750
+ const mod = await import("./parser-4L7UFVEP.js");
22323
22751
  parsePdfDocument = mod.parsePdfDocument;
22324
22752
  } catch {
22325
22753
  return {
@@ -22414,13 +22842,22 @@ export {
22414
22842
  extractHwp5MetadataOnly,
22415
22843
  isLabelCell,
22416
22844
  extractFormFields,
22845
+ inferFieldType,
22846
+ extractFormSchema,
22417
22847
  fillFormFields,
22848
+ scanSectionXml,
22849
+ buildParagraphSplices,
22850
+ buildRangeSplices,
22851
+ applySplices,
22418
22852
  fillHwpx,
22419
22853
  markdownToHwpx,
22420
22854
  compare,
22421
22855
  diffBlocks,
22422
22856
  patchHwpx,
22423
22857
  patchHwp,
22858
+ HwpxSession,
22859
+ openHwpxDocument,
22860
+ patchHwpxBlocks,
22424
22861
  renderHtml,
22425
22862
  markdownToPdf,
22426
22863
  blocksToPdf,
@@ -22435,4 +22872,4 @@ export {
22435
22872
  parseHwpml,
22436
22873
  fillForm
22437
22874
  };
22438
- //# sourceMappingURL=chunk-X7VQVMXQ.js.map
22875
+ //# sourceMappingURL=chunk-VMUP5WPI.js.map