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