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.
- package/README.md +559 -536
- package/dist/{-5BWAV4ZY.js → -UCE73NNK.js} +24 -6
- package/dist/{chunk-MEPHGCPQ.js → chunk-5CJGKKMZ.js} +1 -1
- package/dist/chunk-5CJGKKMZ.js.map +1 -0
- package/dist/{chunk-MUOQXDZ4.cjs → chunk-DCZVOIEO.cjs} +1 -1
- package/dist/chunk-DCZVOIEO.cjs.map +1 -0
- package/dist/{chunk-NBJB6TJB.cjs → chunk-FJON4QNB.cjs} +2 -2
- package/dist/chunk-FJON4QNB.cjs.map +1 -0
- package/dist/{chunk-SBVRCJFH.js → chunk-GE43BE46.js} +1 -1
- package/dist/chunk-GS7T56RP.cjs +8 -0
- package/dist/chunk-GS7T56RP.cjs.map +1 -0
- package/dist/{chunk-O5P6EG5L.js → chunk-KR6DSGM7.js} +2 -2
- package/dist/chunk-KR6DSGM7.js.map +1 -0
- package/dist/chunk-MOL7MDBG.js +0 -0
- package/dist/{chunk-X7VQVMXQ.js → chunk-VMUP5WPI.js} +2043 -1606
- package/dist/chunk-VMUP5WPI.js.map +1 -0
- package/dist/{chunk-X3SCCO5Q.js → chunk-ZZNSI4GV.js} +2 -2
- package/dist/chunk-ZZNSI4GV.js.map +1 -0
- package/dist/cli.js +5 -5
- package/dist/cli.js.map +1 -1
- package/dist/{detect-RI2MQ33K.js → detect-PJZMUL2Z.js} +2 -2
- package/dist/{formula-XGG6ZP42.cjs → formula-5NKVS2LR.cjs} +4 -2
- package/dist/formula-5NKVS2LR.cjs.map +1 -0
- package/dist/formula-JCNF43NE.js +0 -0
- package/dist/{formula-3AQUUIRF.js → formula-RXVSQPXI.js} +1 -1
- package/dist/index.cjs +2190 -1750
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +263 -6
- package/dist/index.d.ts +263 -6
- package/dist/index.js +2045 -1608
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +5 -5
- package/dist/mcp.js.map +1 -1
- package/dist/page-range-737B4EZW.js +0 -0
- package/dist/page-range-LNMXJU6W.js +7 -0
- package/dist/page-range-P7SDW6LR.cjs +8 -0
- package/dist/page-range-P7SDW6LR.cjs.map +1 -0
- package/dist/{parser-3N6FZSKU.js → parser-4L7UFVEP.js} +3 -3
- package/dist/parser-4L7UFVEP.js.map +1 -0
- package/dist/{parser-LZH7ZELV.js → parser-QTJL6IQY.js} +5 -5
- package/dist/parser-QTJL6IQY.js.map +1 -0
- package/dist/{parser-5FZJVLQL.cjs → parser-XULVNDMN.cjs} +19 -18
- package/dist/parser-XULVNDMN.cjs.map +1 -0
- package/dist/{provider-SNONEZNW.cjs → provider-5K7O3Z2O.cjs} +4 -2
- package/dist/provider-5K7O3Z2O.cjs.map +1 -0
- package/dist/{provider-AKROB7WQ.js → provider-7H4CPZYS.js} +1 -1
- package/dist/provider-7H4CPZYS.js.map +1 -0
- package/dist/{provider-2SEHU2FM.js → provider-H4WWSPOV.js} +1 -1
- package/dist/provider-H4WWSPOV.js.map +1 -0
- package/dist/setup-57FB3LSP.js +0 -0
- package/dist/{watch-4FMRS7QU.js → watch-FPDSHB23.js} +4 -4
- package/dist/watch-FPDSHB23.js.map +1 -0
- package/package.json +98 -98
- package/dist/chunk-MEPHGCPQ.js.map +0 -1
- package/dist/chunk-MUOQXDZ4.cjs.map +0 -1
- package/dist/chunk-NBJB6TJB.cjs.map +0 -1
- package/dist/chunk-O5P6EG5L.js.map +0 -1
- package/dist/chunk-X3SCCO5Q.js.map +0 -1
- package/dist/chunk-X7VQVMXQ.js.map +0 -1
- package/dist/formula-XGG6ZP42.cjs.map +0 -1
- package/dist/page-range-3C7UGGEK.cjs +0 -7
- package/dist/page-range-3C7UGGEK.cjs.map +0 -1
- package/dist/page-range-H35FN3OQ.js +0 -7
- package/dist/parser-3N6FZSKU.js.map +0 -1
- package/dist/parser-5FZJVLQL.cjs.map +0 -1
- package/dist/parser-LZH7ZELV.js.map +0 -1
- package/dist/provider-2SEHU2FM.js.map +0 -1
- package/dist/provider-AKROB7WQ.js.map +0 -1
- package/dist/provider-SNONEZNW.cjs.map +0 -1
- package/dist/watch-4FMRS7QU.js.map +0 -1
- /package/dist/{-5BWAV4ZY.js.map → -UCE73NNK.js.map} +0 -0
- /package/dist/{chunk-SBVRCJFH.js.map → chunk-GE43BE46.js.map} +0 -0
- /package/dist/{detect-RI2MQ33K.js.map → detect-PJZMUL2Z.js.map} +0 -0
- /package/dist/{formula-3AQUUIRF.js.map → formula-RXVSQPXI.js.map} +0 -0
- /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-
|
|
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-
|
|
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,
|
|
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 ===
|
|
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,
|
|
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 ===
|
|
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,
|
|
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 ===
|
|
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
|
|
18163
|
-
if (
|
|
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 (
|
|
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
|
|
18577
|
-
|
|
18578
|
-
|
|
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
|
-
|
|
18589
|
-
|
|
18590
|
-
|
|
18591
|
-
|
|
18592
|
-
|
|
18593
|
-
|
|
18594
|
-
|
|
18595
|
-
|
|
18596
|
-
|
|
18597
|
-
|
|
18598
|
-
|
|
18599
|
-
|
|
18600
|
-
|
|
18601
|
-
|
|
18602
|
-
|
|
18603
|
-
|
|
18604
|
-
|
|
18605
|
-
|
|
18606
|
-
|
|
18607
|
-
|
|
18608
|
-
}
|
|
18609
|
-
return
|
|
18610
|
-
}
|
|
18611
|
-
function
|
|
18612
|
-
|
|
18613
|
-
|
|
18614
|
-
|
|
18615
|
-
|
|
18616
|
-
|
|
18617
|
-
return
|
|
18618
|
-
}
|
|
18619
|
-
function
|
|
18620
|
-
|
|
18621
|
-
const
|
|
18622
|
-
|
|
18623
|
-
|
|
18624
|
-
(
|
|
18625
|
-
|
|
18626
|
-
|
|
18627
|
-
|
|
18628
|
-
|
|
18629
|
-
|
|
18630
|
-
|
|
18631
|
-
|
|
18632
|
-
|
|
18633
|
-
|
|
18634
|
-
|
|
18635
|
-
|
|
18636
|
-
|
|
18637
|
-
|
|
18638
|
-
|
|
18639
|
-
|
|
18640
|
-
|
|
18641
|
-
|
|
18642
|
-
|
|
18643
|
-
|
|
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
|
|
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
|
-
|
|
18771
|
-
|
|
18772
|
-
|
|
18773
|
-
|
|
18774
|
-
|
|
18775
|
-
|
|
18776
|
-
|
|
18777
|
-
|
|
18778
|
-
|
|
18779
|
-
|
|
18780
|
-
|
|
18781
|
-
|
|
18782
|
-
|
|
18783
|
-
|
|
18784
|
-
|
|
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
|
-
|
|
18792
|
-
|
|
18793
|
-
|
|
18794
|
-
|
|
18795
|
-
|
|
18796
|
-
|
|
18797
|
-
|
|
18798
|
-
|
|
18799
|
-
|
|
18800
|
-
|
|
18801
|
-
|
|
18802
|
-
|
|
18803
|
-
|
|
18804
|
-
|
|
18805
|
-
|
|
18806
|
-
|
|
18807
|
-
|
|
18808
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
-
|
|
18828
|
-
const
|
|
18829
|
-
|
|
18830
|
-
|
|
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
|
-
|
|
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
|
|
18922
|
-
|
|
18923
|
-
|
|
18924
|
-
|
|
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
|
-
|
|
18937
|
-
|
|
18938
|
-
|
|
18939
|
-
|
|
18940
|
-
|
|
18941
|
-
|
|
18942
|
-
|
|
18943
|
-
|
|
18944
|
-
|
|
18945
|
-
|
|
18946
|
-
|
|
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
|
|
18949
|
-
|
|
18950
|
-
|
|
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
|
|
18957
|
-
const
|
|
18958
|
-
const
|
|
18959
|
-
|
|
18960
|
-
|
|
18961
|
-
|
|
18962
|
-
|
|
18963
|
-
|
|
18964
|
-
|
|
18965
|
-
|
|
18966
|
-
|
|
18967
|
-
|
|
18968
|
-
|
|
18969
|
-
|
|
18970
|
-
|
|
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
|
-
|
|
18975
|
-
|
|
18976
|
-
|
|
18977
|
-
|
|
18978
|
-
|
|
18979
|
-
|
|
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
|
-
|
|
18996
|
-
|
|
18997
|
-
|
|
18998
|
-
|
|
18999
|
-
|
|
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
|
-
|
|
19006
|
-
|
|
19007
|
-
|
|
19008
|
-
|
|
19009
|
-
|
|
19010
|
-
|
|
19011
|
-
|
|
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
|
-
|
|
19016
|
-
|
|
19017
|
-
|
|
19018
|
-
|
|
19019
|
-
|
|
19020
|
-
|
|
19021
|
-
|
|
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
|
-
|
|
19024
|
-
|
|
19025
|
-
|
|
19026
|
-
|
|
19027
|
-
|
|
19028
|
-
|
|
19029
|
-
|
|
19030
|
-
|
|
19031
|
-
|
|
19032
|
-
|
|
19033
|
-
|
|
19034
|
-
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
|
|
19038
|
-
|
|
19039
|
-
|
|
19040
|
-
|
|
19041
|
-
|
|
19042
|
-
|
|
19043
|
-
|
|
19044
|
-
|
|
19045
|
-
|
|
19046
|
-
|
|
19047
|
-
|
|
19048
|
-
|
|
19049
|
-
|
|
19050
|
-
|
|
19051
|
-
|
|
19052
|
-
if (
|
|
19053
|
-
|
|
19054
|
-
|
|
19055
|
-
if (
|
|
19056
|
-
const
|
|
19057
|
-
|
|
19058
|
-
|
|
19059
|
-
|
|
19060
|
-
|
|
19061
|
-
|
|
19062
|
-
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
|
|
19066
|
-
|
|
19067
|
-
|
|
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
|
-
|
|
19072
|
-
const
|
|
19073
|
-
|
|
19074
|
-
|
|
19075
|
-
|
|
19076
|
-
|
|
19077
|
-
|
|
19078
|
-
|
|
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
|
-
|
|
19148
|
-
const
|
|
19149
|
-
const
|
|
19150
|
-
|
|
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
|
|
19161
|
-
|
|
19162
|
-
let
|
|
19163
|
-
|
|
19164
|
-
|
|
19165
|
-
if (
|
|
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
|
|
19200
|
+
return false;
|
|
19171
19201
|
}
|
|
19172
|
-
function
|
|
19173
|
-
|
|
19174
|
-
const
|
|
19175
|
-
|
|
19176
|
-
|
|
19177
|
-
|
|
19178
|
-
|
|
19179
|
-
|
|
19180
|
-
|
|
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
|
-
|
|
19183
|
-
|
|
19184
|
-
|
|
19185
|
-
|
|
19186
|
-
|
|
19187
|
-
|
|
19188
|
-
|
|
19189
|
-
|
|
19190
|
-
|
|
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
|
-
|
|
19202
|
-
|
|
19203
|
-
|
|
19204
|
-
|
|
19205
|
-
|
|
19206
|
-
|
|
19207
|
-
|
|
19208
|
-
|
|
19209
|
-
|
|
19210
|
-
|
|
19211
|
-
|
|
19212
|
-
|
|
19213
|
-
|
|
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
|
-
|
|
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
|
|
19247
|
-
|
|
19248
|
-
|
|
19249
|
-
|
|
19250
|
-
|
|
19251
|
-
|
|
19252
|
-
|
|
19253
|
-
|
|
19254
|
-
|
|
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
|
-
|
|
19259
|
-
|
|
19260
|
-
|
|
19261
|
-
|
|
19262
|
-
|
|
19263
|
-
|
|
19264
|
-
|
|
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
|
-
|
|
19283
|
+
splices.push({ start: first.contentStart, end: first.contentEnd, replacement: escaped });
|
|
19267
19284
|
}
|
|
19268
|
-
|
|
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 (
|
|
19271
|
-
|
|
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 (
|
|
19274
|
-
|
|
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
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
|
19296
|
-
|
|
19297
|
-
|
|
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
|
-
|
|
19300
|
-
return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
|
|
19320
|
+
return text;
|
|
19301
19321
|
}
|
|
19302
|
-
function
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
|
|
19306
|
-
|
|
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
|
|
19309
|
-
if (
|
|
19310
|
-
|
|
19311
|
-
|
|
19312
|
-
|
|
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
|
|
19315
|
-
|
|
19316
|
-
|
|
19317
|
-
|
|
19318
|
-
|
|
19319
|
-
|
|
19320
|
-
|
|
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
|
-
|
|
19323
|
-
|
|
19324
|
-
|
|
19325
|
-
|
|
19326
|
-
|
|
19327
|
-
|
|
19328
|
-
|
|
19329
|
-
|
|
19330
|
-
|
|
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
|
|
19336
|
-
const
|
|
19337
|
-
const
|
|
19338
|
-
|
|
19339
|
-
|
|
19340
|
-
|
|
19341
|
-
|
|
19342
|
-
|
|
19343
|
-
|
|
19344
|
-
|
|
19345
|
-
|
|
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
|
-
|
|
19348
|
-
const
|
|
19349
|
-
|
|
19350
|
-
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
|
|
19354
|
-
|
|
19355
|
-
|
|
19356
|
-
|
|
19357
|
-
|
|
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
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
|
|
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
|
-
|
|
19736
|
-
|
|
19737
|
-
|
|
19738
|
-
|
|
19739
|
-
|
|
19740
|
-
|
|
19741
|
-
|
|
19742
|
-
|
|
19743
|
-
|
|
19744
|
-
|
|
19745
|
-
|
|
19746
|
-
|
|
19747
|
-
|
|
19748
|
-
|
|
19749
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19764
|
-
|
|
19765
|
-
|
|
19766
|
-
|
|
19767
|
-
|
|
19768
|
-
|
|
19769
|
-
|
|
19770
|
-
|
|
19771
|
-
|
|
19772
|
-
|
|
19773
|
-
|
|
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
|
-
|
|
19890
|
-
|
|
19891
|
-
|
|
19892
|
-
|
|
19893
|
-
|
|
19894
|
-
|
|
19895
|
-
|
|
19896
|
-
|
|
19897
|
-
|
|
19898
|
-
|
|
19899
|
-
|
|
19900
|
-
|
|
19901
|
-
|
|
19902
|
-
|
|
19903
|
-
|
|
19904
|
-
|
|
19905
|
-
|
|
19906
|
-
|
|
19907
|
-
|
|
19908
|
-
|
|
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
|
-
|
|
19912
|
-
|
|
19913
|
-
|
|
19914
|
-
|
|
19915
|
-
|
|
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
|
-
}
|
|
19918
|
-
|
|
19919
|
-
|
|
19920
|
-
|
|
19921
|
-
|
|
19922
|
-
|
|
19923
|
-
|
|
19924
|
-
|
|
19925
|
-
|
|
19926
|
-
|
|
19927
|
-
|
|
19928
|
-
|
|
19929
|
-
|
|
19930
|
-
|
|
19931
|
-
|
|
19932
|
-
|
|
19933
|
-
|
|
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
|
-
}
|
|
19937
|
-
|
|
19938
|
-
|
|
19939
|
-
|
|
19940
|
-
const
|
|
19941
|
-
|
|
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
|
|
19947
|
-
|
|
19948
|
-
|
|
19949
|
-
|
|
19950
|
-
|
|
19951
|
-
|
|
19952
|
-
|
|
19953
|
-
const
|
|
19954
|
-
|
|
19955
|
-
|
|
19956
|
-
|
|
19957
|
-
|
|
19958
|
-
|
|
19959
|
-
|
|
19960
|
-
|
|
19961
|
-
|
|
19962
|
-
|
|
19963
|
-
|
|
19964
|
-
|
|
19965
|
-
|
|
19966
|
-
|
|
19967
|
-
|
|
19968
|
-
|
|
19969
|
-
|
|
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
|
-
|
|
19972
|
-
|
|
19973
|
-
|
|
19974
|
-
const
|
|
19975
|
-
|
|
19976
|
-
|
|
19977
|
-
|
|
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 (
|
|
19983
|
-
|
|
19984
|
-
|
|
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
|
-
|
|
19989
|
-
if (
|
|
19990
|
-
|
|
19991
|
-
|
|
19992
|
-
|
|
19993
|
-
|
|
19994
|
-
|
|
19995
|
-
|
|
19996
|
-
|
|
19997
|
-
|
|
19998
|
-
|
|
19999
|
-
|
|
20000
|
-
|
|
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
|
-
|
|
20044
|
-
|
|
20045
|
-
|
|
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
|
-
|
|
20048
|
-
|
|
20049
|
-
|
|
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
|
-
|
|
20053
|
-
|
|
20054
|
-
|
|
20055
|
-
|
|
20056
|
-
|
|
20057
|
-
|
|
20058
|
-
|
|
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
|
-
|
|
20063
|
-
|
|
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
|
|
20066
|
-
|
|
20067
|
-
|
|
20068
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
|
20071
|
-
|
|
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
|
-
|
|
20080
|
-
|
|
20081
|
-
|
|
20082
|
-
|
|
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
|
|
20094
|
-
const
|
|
20095
|
-
|
|
20096
|
-
const
|
|
20097
|
-
const
|
|
20098
|
-
|
|
20099
|
-
|
|
20100
|
-
|
|
20101
|
-
|
|
20102
|
-
|
|
20103
|
-
|
|
20104
|
-
|
|
20105
|
-
|
|
20106
|
-
|
|
20107
|
-
|
|
20108
|
-
|
|
20109
|
-
|
|
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
|
|
20112
|
-
const
|
|
20113
|
-
|
|
20114
|
-
|
|
20115
|
-
|
|
20116
|
-
|
|
20117
|
-
|
|
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
|
-
|
|
20122
|
-
|
|
20123
|
-
|
|
20124
|
-
|
|
20125
|
-
|
|
20126
|
-
|
|
20127
|
-
|
|
20128
|
-
|
|
20129
|
-
|
|
20130
|
-
|
|
20131
|
-
|
|
20132
|
-
|
|
20133
|
-
|
|
20134
|
-
|
|
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
|
-
|
|
20138
|
-
|
|
20139
|
-
|
|
20140
|
-
|
|
20141
|
-
|
|
20142
|
-
|
|
20143
|
-
|
|
20144
|
-
|
|
20145
|
-
|
|
20146
|
-
|
|
20147
|
-
|
|
20148
|
-
|
|
20149
|
-
|
|
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
|
-
|
|
20153
|
-
|
|
20154
|
-
|
|
20155
|
-
|
|
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
|
-
|
|
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 (
|
|
20166
|
-
|
|
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
|
|
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
|
-
|
|
20180
|
-
|
|
20181
|
-
|
|
20182
|
-
|
|
20183
|
-
|
|
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
|
-
|
|
20187
|
-
|
|
20188
|
-
|
|
20189
|
-
|
|
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
|
|
20285
|
+
return prev[m];
|
|
20192
20286
|
}
|
|
20193
20287
|
|
|
20194
|
-
// src/
|
|
20195
|
-
|
|
20196
|
-
|
|
20197
|
-
|
|
20198
|
-
|
|
20199
|
-
|
|
20200
|
-
|
|
20201
|
-
|
|
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
|
|
20204
|
-
const
|
|
20205
|
-
const
|
|
20206
|
-
|
|
20207
|
-
for (
|
|
20208
|
-
if (
|
|
20209
|
-
|
|
20210
|
-
|
|
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
|
-
|
|
20214
|
-
|
|
20215
|
-
|
|
20216
|
-
|
|
20217
|
-
|
|
20218
|
-
|
|
20219
|
-
|
|
20220
|
-
|
|
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
|
-
|
|
20225
|
-
|
|
20226
|
-
|
|
20227
|
-
|
|
20228
|
-
|
|
20229
|
-
|
|
20230
|
-
|
|
20231
|
-
|
|
20232
|
-
|
|
20233
|
-
|
|
20234
|
-
|
|
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
|
-
|
|
20255
|
-
|
|
20256
|
-
|
|
20257
|
-
const
|
|
20258
|
-
|
|
20259
|
-
|
|
20260
|
-
|
|
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
|
-
|
|
20264
|
-
|
|
20265
|
-
|
|
20266
|
-
|
|
20267
|
-
|
|
20268
|
-
|
|
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
|
|
20381
|
+
return result;
|
|
20271
20382
|
}
|
|
20272
|
-
function
|
|
20273
|
-
|
|
20274
|
-
|
|
20275
|
-
|
|
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
|
-
|
|
20279
|
-
|
|
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
|
-
|
|
20313
|
-
|
|
20314
|
-
|
|
20315
|
-
|
|
20316
|
-
|
|
20317
|
-
|
|
20318
|
-
|
|
20319
|
-
|
|
20320
|
-
|
|
20321
|
-
|
|
20322
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
22875
|
+
//# sourceMappingURL=chunk-VMUP5WPI.js.map
|