kordoc 4.0.7 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +837 -806
- package/dist/{-6HWEFXVV.js → -AS4IABL2.js} +24 -11
- package/dist/{chunk-BKWHX3RC.js → chunk-37HJHCPA.js} +24 -15
- package/dist/chunk-37HJHCPA.js.map +1 -0
- package/dist/{chunk-U25XGCNH.cjs → chunk-37VKMUST.cjs} +174 -25
- package/dist/chunk-37VKMUST.cjs.map +1 -0
- package/dist/{chunk-5OKHAJ62.js → chunk-4KI23VHA.js} +162 -13
- package/dist/chunk-4KI23VHA.js.map +1 -0
- package/dist/{chunk-6GBLFQMA.js → chunk-5RPYBC2Q.js} +1 -1
- package/dist/chunk-5RPYBC2Q.js.map +1 -0
- package/dist/{chunk-NGSHDBQR.js → chunk-AX2R5Q2N.js} +7 -4
- package/dist/chunk-AX2R5Q2N.js.map +1 -0
- package/dist/{chunk-X34YWRL5.cjs → chunk-DCZVOIEO.cjs} +1 -1
- package/dist/chunk-DCZVOIEO.cjs.map +1 -0
- package/dist/chunk-DZIXKL7E.js +145 -0
- package/dist/chunk-DZIXKL7E.js.map +1 -0
- package/dist/chunk-F2ZU3IZG.js +82 -0
- package/dist/chunk-F2ZU3IZG.js.map +1 -0
- package/dist/{chunk-AIQ3ISQU.js → chunk-GE43BE46.js} +1 -1
- package/dist/chunk-GE43BE46.js.map +1 -0
- package/dist/{chunk-Q7EN4EUJ.js → chunk-GQVSHGG4.js} +12 -4
- package/dist/chunk-GQVSHGG4.js.map +1 -0
- package/dist/chunk-GS7T56RP.cjs.map +1 -1
- package/dist/{chunk-TJJTLM76.js → chunk-L2XQQTOY.js} +1422 -1121
- package/dist/chunk-L2XQQTOY.js.map +1 -0
- package/dist/{chunk-3TBUDJDE.js → chunk-MOL7MDBG.js} +1 -1
- package/dist/chunk-MOL7MDBG.js.map +1 -0
- package/dist/{chunk-CNM75ZSX.js → chunk-O5VS7RIR.js} +2 -2
- package/dist/chunk-O5VS7RIR.js.map +1 -0
- package/dist/{chunk-2SN3CKME.js → chunk-SSTK6IKK.js} +18 -21
- package/dist/chunk-SSTK6IKK.js.map +1 -0
- package/dist/chunk-UOO7LSDG.js +152 -0
- package/dist/chunk-UOO7LSDG.js.map +1 -0
- package/dist/{chunk-ONXVBURQ.js → chunk-WO52HVQJ.js} +2 -2
- package/dist/chunk-WO52HVQJ.js.map +1 -0
- package/dist/{chunk-CFQLK3LM.js → chunk-YOO6ET6M.js} +7 -3
- package/dist/chunk-YOO6ET6M.js.map +1 -0
- package/dist/chunks-WVMGF7JL.js +10 -0
- package/dist/cli.js +173 -38
- package/dist/cli.js.map +1 -1
- package/dist/{detect-NTR2FUEG.js → detect-YEZX2XCF.js} +2 -2
- package/dist/{formula-ZYUDQHD3.cjs → formula-5NKVS2LR.cjs} +1 -1
- package/dist/formula-5NKVS2LR.cjs.map +1 -0
- package/dist/{formula-KOQV353G.js → formula-JCNF43NE.js} +1 -1
- package/dist/formula-JCNF43NE.js.map +1 -0
- package/dist/{formula-MEDGYQXS.js → formula-RXVSQPXI.js} +1 -1
- package/dist/formula-RXVSQPXI.js.map +1 -0
- package/dist/index.cjs +1932 -1397
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -2
- package/dist/index.d.ts +106 -2
- package/dist/index.js +1626 -1091
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +275 -67
- package/dist/mcp.js.map +1 -1
- package/dist/page-range-737B4EZW.js +8 -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-E6U5TNAU.cjs → parser-N4A7UGDK.cjs} +314 -56
- package/dist/parser-N4A7UGDK.cjs.map +1 -0
- package/dist/{parser-5EHWYJMC.js → parser-PEOYBXBO.js} +287 -27
- package/dist/parser-PEOYBXBO.js.map +1 -0
- package/dist/{parser-UGB3NIVH.js → parser-XX4QTDFQ.js} +283 -25
- package/dist/parser-XX4QTDFQ.js.map +1 -0
- package/dist/{profile-io-CAKRPQRD.js → profile-io-PLDQFPJA.js} +3 -3
- package/dist/{provider-5K7O3Z2O.cjs → provider-4FAYHJ6N.cjs} +3 -3
- package/dist/provider-4FAYHJ6N.cjs.map +1 -0
- package/dist/{provider-H4WWSPOV.js → provider-C6IOGIS5.js} +3 -3
- package/dist/provider-C6IOGIS5.js.map +1 -0
- package/dist/{provider-7H4CPZYS.js → provider-UIBW2MIH.js} +3 -3
- package/dist/provider-UIBW2MIH.js.map +1 -0
- package/dist/rasterize-WWNQLKYW.js +40 -0
- package/dist/rasterize-WWNQLKYW.js.map +1 -0
- package/dist/redact-7ILEAUTS.js +12 -0
- package/dist/render-T7S6H6AN.js +10 -0
- package/dist/render-T7S6H6AN.js.map +1 -0
- package/dist/seal-7PTL6MXH.js +10 -0
- package/dist/seal-7PTL6MXH.js.map +1 -0
- package/dist/{setup-QSJ2INNS.js → setup-Q2PRE7UA.js} +5 -3
- package/dist/setup-Q2PRE7UA.js.map +1 -0
- package/dist/{watch-RMX427YR.js → watch-SFHXZALC.js} +78 -24
- package/dist/watch-SFHXZALC.js.map +1 -0
- package/package.json +97 -97
- package/dist/chunk-2SN3CKME.js.map +0 -1
- package/dist/chunk-3TBUDJDE.js.map +0 -1
- package/dist/chunk-5OKHAJ62.js.map +0 -1
- package/dist/chunk-6GBLFQMA.js.map +0 -1
- package/dist/chunk-AIQ3ISQU.js.map +0 -1
- package/dist/chunk-BKWHX3RC.js.map +0 -1
- package/dist/chunk-CFQLK3LM.js.map +0 -1
- package/dist/chunk-CNM75ZSX.js.map +0 -1
- package/dist/chunk-NGSHDBQR.js.map +0 -1
- package/dist/chunk-ONXVBURQ.js.map +0 -1
- package/dist/chunk-Q7EN4EUJ.js.map +0 -1
- package/dist/chunk-TJJTLM76.js.map +0 -1
- package/dist/chunk-U25XGCNH.cjs.map +0 -1
- package/dist/chunk-X34YWRL5.cjs.map +0 -1
- package/dist/formula-KOQV353G.js.map +0 -1
- package/dist/formula-MEDGYQXS.js.map +0 -1
- package/dist/formula-ZYUDQHD3.cjs.map +0 -1
- package/dist/page-range-CIRRJPXJ.js +0 -7
- package/dist/page-range-OF5I4PQY.js +0 -8
- package/dist/page-range-TPIRSA3J.cjs +0 -8
- package/dist/page-range-TPIRSA3J.cjs.map +0 -1
- package/dist/parser-5EHWYJMC.js.map +0 -1
- package/dist/parser-E6U5TNAU.cjs.map +0 -1
- package/dist/parser-UGB3NIVH.js.map +0 -1
- package/dist/provider-5K7O3Z2O.cjs.map +0 -1
- package/dist/provider-7H4CPZYS.js.map +0 -1
- package/dist/provider-H4WWSPOV.js.map +0 -1
- package/dist/render-P6663O4I.js +0 -10
- package/dist/seal-7PPTXKKV.js +0 -10
- package/dist/setup-QSJ2INNS.js.map +0 -1
- package/dist/watch-RMX427YR.js.map +0 -1
- /package/dist/{-6HWEFXVV.js.map → -AS4IABL2.js.map} +0 -0
- /package/dist/{detect-NTR2FUEG.js.map → chunks-WVMGF7JL.js.map} +0 -0
- /package/dist/{page-range-CIRRJPXJ.js.map → detect-YEZX2XCF.js.map} +0 -0
- /package/dist/{page-range-OF5I4PQY.js.map → page-range-737B4EZW.js.map} +0 -0
- /package/dist/{profile-io-CAKRPQRD.js.map → page-range-LNMXJU6W.js.map} +0 -0
- /package/dist/{render-P6663O4I.js.map → profile-io-PLDQFPJA.js.map} +0 -0
- /package/dist/{seal-7PPTXKKV.js.map → redact-7ILEAUTS.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -13,17 +13,19 @@ import {
|
|
|
13
13
|
convertTableToText,
|
|
14
14
|
dedupeRunningHeaders,
|
|
15
15
|
flattenLayoutTables,
|
|
16
|
+
inlineImagesIntoMarkdown,
|
|
16
17
|
isPathTraversal,
|
|
17
18
|
mapPuaText,
|
|
18
19
|
normalizeSectionHref,
|
|
19
20
|
precheckZipSize,
|
|
21
|
+
sanitizeError,
|
|
20
22
|
sanitizeHref,
|
|
21
23
|
stripDtd,
|
|
22
24
|
toArrayBuffer
|
|
23
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-4KI23VHA.js";
|
|
24
26
|
import {
|
|
25
27
|
parsePageRange
|
|
26
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-GE43BE46.js";
|
|
27
29
|
|
|
28
30
|
// src/index.ts
|
|
29
31
|
import { readFile } from "fs/promises";
|
|
@@ -47,8 +49,11 @@ function parseLenientCfb(data) {
|
|
|
47
49
|
const miniSectorSizeShift = data.readUInt16LE(32);
|
|
48
50
|
if (miniSectorSizeShift > 16) throw new Error("\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBBF8\uB2C8 \uC139\uD130 \uD06C\uAE30 \uC2DC\uD504\uD2B8: " + miniSectorSizeShift);
|
|
49
51
|
const miniSectorSize = 1 << miniSectorSizeShift;
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
52
|
+
const headerFatSectorCount = data.readUInt32LE(44);
|
|
53
|
+
if (headerFatSectorCount > 1e4) throw new Error("FAT \uC139\uD130 \uC218\uAC00 \uB108\uBB34 \uB9CE\uC2B5\uB2C8\uB2E4: " + headerFatSectorCount);
|
|
54
|
+
const maxRealSectors = Math.ceil(Math.max(0, data.length - 512) / sectorSize);
|
|
55
|
+
const maxFatSectors = Math.max(1, Math.ceil(maxRealSectors / (sectorSize / 4)));
|
|
56
|
+
const fatSectorCount = Math.min(headerFatSectorCount, maxFatSectors);
|
|
52
57
|
const firstDirSector = data.readUInt32LE(48);
|
|
53
58
|
const miniStreamCutoff = data.readUInt32LE(56);
|
|
54
59
|
const firstMiniFatSector = data.readUInt32LE(60);
|
|
@@ -75,6 +80,7 @@ function parseLenientCfb(data) {
|
|
|
75
80
|
if (visitedDifat.has(difatSector)) break;
|
|
76
81
|
visitedDifat.add(difatSector);
|
|
77
82
|
const buf = readSectorData(difatSector);
|
|
83
|
+
if (buf.length < sectorSize) break;
|
|
78
84
|
const entriesPerSector = sectorSize / 4 - 1;
|
|
79
85
|
for (let i = 0; i < entriesPerSector && fatSectors.length < fatSectorCount; i++) {
|
|
80
86
|
const sid = buf.readUInt32LE(i * 4);
|
|
@@ -393,6 +399,12 @@ function comResultToParseResult(pages, pageCount, warnings) {
|
|
|
393
399
|
import { DOMParser } from "@xmldom/xmldom";
|
|
394
400
|
var MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024;
|
|
395
401
|
var MAX_ZIP_ENTRIES = 500;
|
|
402
|
+
var ZipBombError = class extends KordocError {
|
|
403
|
+
constructor(message) {
|
|
404
|
+
super(message);
|
|
405
|
+
this.name = "ZipBombError";
|
|
406
|
+
}
|
|
407
|
+
};
|
|
396
408
|
function clampSpan(val, max) {
|
|
397
409
|
return Math.max(1, Math.min(val, max));
|
|
398
410
|
}
|
|
@@ -428,14 +440,15 @@ function findChildByLocalName(parent, name) {
|
|
|
428
440
|
}
|
|
429
441
|
return null;
|
|
430
442
|
}
|
|
431
|
-
function extractTextFromNode(node) {
|
|
443
|
+
function extractTextFromNode(node, depth = 0) {
|
|
432
444
|
let result = "";
|
|
445
|
+
if (depth > MAX_XML_DEPTH) return result;
|
|
433
446
|
const children = node.childNodes;
|
|
434
447
|
if (!children) return result;
|
|
435
448
|
for (let i = 0; i < children.length; i++) {
|
|
436
449
|
const child = children[i];
|
|
437
450
|
if (child.nodeType === 3) result += child.textContent || "";
|
|
438
|
-
else if (child.nodeType === 1) result += extractTextFromNode(child);
|
|
451
|
+
else if (child.nodeType === 1) result += extractTextFromNode(child, depth + 1);
|
|
439
452
|
}
|
|
440
453
|
return result.trim();
|
|
441
454
|
}
|
|
@@ -1319,7 +1332,7 @@ function simulateWrap(text, firstWidth, contWidth, height, ratioPct, mode = "kee
|
|
|
1319
1332
|
for (const ch of text.slice(from, to)) w += charW2(ch);
|
|
1320
1333
|
return w;
|
|
1321
1334
|
};
|
|
1322
|
-
const units = text.match(mode === "keep" ? / +|[^ ]+/
|
|
1335
|
+
const units = text.match(mode === "keep" ? / +|[^ ]+/gu : / +|[^ ]/gu) ?? [];
|
|
1323
1336
|
const starts = [0];
|
|
1324
1337
|
let lineW = 0;
|
|
1325
1338
|
let avail = firstWidth;
|
|
@@ -1788,6 +1801,7 @@ function toRoman(n) {
|
|
|
1788
1801
|
return out;
|
|
1789
1802
|
}
|
|
1790
1803
|
function formatHeadNumber(n, numFormat) {
|
|
1804
|
+
if (n === 0 && numFormat === "DIGIT") return "0";
|
|
1791
1805
|
if (n <= 0) n = 1;
|
|
1792
1806
|
switch (numFormat) {
|
|
1793
1807
|
case "DIGIT":
|
|
@@ -1845,25 +1859,25 @@ function resolveParaHeading(paraEl, ctx) {
|
|
|
1845
1859
|
if (!numDef) return headingLevel ? { headingLevel } : null;
|
|
1846
1860
|
let counters = ctx.shared.numState.get(numId);
|
|
1847
1861
|
if (!counters) {
|
|
1848
|
-
counters = new Array(11).fill(
|
|
1862
|
+
counters = new Array(11).fill(-1);
|
|
1849
1863
|
ctx.shared.numState.set(numId, counters);
|
|
1850
1864
|
}
|
|
1851
1865
|
const head = numDef.heads.get(level);
|
|
1852
|
-
counters[level] = counters[level]
|
|
1853
|
-
for (let l = level + 1; l <= 10; l++) counters[l] =
|
|
1866
|
+
counters[level] = counters[level] < 0 ? head?.start ?? 1 : counters[level] + 1;
|
|
1867
|
+
for (let l = level + 1; l <= 10; l++) counters[l] = -1;
|
|
1854
1868
|
const fmtText = head ? head.text.trim() : `^${level}.`;
|
|
1855
1869
|
const prefix = fmtText.replace(/\^(10|[1-9])/g, (_, d) => {
|
|
1856
1870
|
const lv = parseInt(d, 10);
|
|
1857
1871
|
const refHead = numDef.heads.get(lv);
|
|
1858
|
-
const n = counters[lv]
|
|
1872
|
+
const n = counters[lv] >= 0 ? counters[lv] : refHead?.start ?? 1;
|
|
1859
1873
|
return formatHeadNumber(n, refHead?.numFormat || "DIGIT");
|
|
1860
1874
|
});
|
|
1861
1875
|
return { prefix: prefix || void 0, headingLevel };
|
|
1862
1876
|
}
|
|
1863
1877
|
|
|
1864
1878
|
// src/hwpx/table-build.ts
|
|
1865
|
-
function buildTableWithCellMeta(state) {
|
|
1866
|
-
const table2 = buildTable(state.rows);
|
|
1879
|
+
function buildTableWithCellMeta(state, keepAnchoredEmptyCols) {
|
|
1880
|
+
const table2 = buildTable(state.rows, { keepAnchoredEmptyCols });
|
|
1867
1881
|
if (state.caption) table2.caption = state.caption;
|
|
1868
1882
|
const anchors = [];
|
|
1869
1883
|
{
|
|
@@ -1927,7 +1941,7 @@ function completeTable(newTable, tableStack, blocks, ctx) {
|
|
|
1927
1941
|
if (newTable.caption) blocks.push({ type: "paragraph", text: newTable.caption, pageNumber: ctx.sectionNum });
|
|
1928
1942
|
return parentTable;
|
|
1929
1943
|
}
|
|
1930
|
-
const ir = buildTableWithCellMeta(newTable);
|
|
1944
|
+
const ir = buildTableWithCellMeta(newTable, ctx.shared.keepTrailingEmptyCols);
|
|
1931
1945
|
const block = { type: "table", table: ir, pageNumber: ctx.sectionNum };
|
|
1932
1946
|
if (parentTable?.cell) {
|
|
1933
1947
|
const cell2 = parentTable.cell;
|
|
@@ -1961,7 +1975,8 @@ function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
|
|
|
1961
1975
|
walkSection(doc.documentElement, blocks, null, [], ctx);
|
|
1962
1976
|
return blocks;
|
|
1963
1977
|
}
|
|
1964
|
-
function extractImageRef(el) {
|
|
1978
|
+
function extractImageRef(el, depth = 0) {
|
|
1979
|
+
if (depth > MAX_XML_DEPTH) return null;
|
|
1965
1980
|
const children = el.childNodes;
|
|
1966
1981
|
if (!children) return null;
|
|
1967
1982
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -1972,7 +1987,7 @@ function extractImageRef(el) {
|
|
|
1972
1987
|
const ref = child.getAttribute("binaryItemIDRef") || child.getAttribute("href") || "";
|
|
1973
1988
|
if (ref) return ref;
|
|
1974
1989
|
}
|
|
1975
|
-
const nested = extractImageRef(child);
|
|
1990
|
+
const nested = extractImageRef(child, depth + 1);
|
|
1976
1991
|
if (nested) return nested;
|
|
1977
1992
|
}
|
|
1978
1993
|
const directRef = el.getAttribute("binaryItemIDRef") || "";
|
|
@@ -2079,7 +2094,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
|
|
|
2079
2094
|
;
|
|
2080
2095
|
(cell2.blocks ??= []).push(cellBlock);
|
|
2081
2096
|
} else if (!tableCtx) {
|
|
2082
|
-
if (/^─{10,}$/.test(text)) {
|
|
2097
|
+
if (ctx.shared.kordocLayout && /^─{10,}$/.test(text)) {
|
|
2083
2098
|
blocks.push({ type: "separator", pageNumber: ctx.sectionNum });
|
|
2084
2099
|
tableCtx = walkParagraphChildren(el, blocks, tableCtx, tableStack, ctx, depth + 1);
|
|
2085
2100
|
break;
|
|
@@ -2355,8 +2370,8 @@ function extractDrawTextBlocks(drawTextNode, blocks, ctx) {
|
|
|
2355
2370
|
} else {
|
|
2356
2371
|
const info = extractParagraphInfo(child, ctx.styleMap, ctx);
|
|
2357
2372
|
let text = info.text.trim();
|
|
2373
|
+
const ph = resolveParaHeading(child, ctx);
|
|
2358
2374
|
if (text) {
|
|
2359
|
-
const ph = resolveParaHeading(child, ctx);
|
|
2360
2375
|
if (ph?.prefix) text = ph.prefix + " " + text;
|
|
2361
2376
|
const block = { type: "paragraph", text, style: info.style ?? void 0, pageNumber: ctx.sectionNum };
|
|
2362
2377
|
if (info.href) block.href = info.href;
|
|
@@ -2469,7 +2484,8 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2469
2484
|
}
|
|
2470
2485
|
}
|
|
2471
2486
|
};
|
|
2472
|
-
const walk = (node) => {
|
|
2487
|
+
const walk = (node, depth = 0) => {
|
|
2488
|
+
if (depth > MAX_XML_DEPTH) return;
|
|
2473
2489
|
const children = node.childNodes;
|
|
2474
2490
|
if (!children) return;
|
|
2475
2491
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -2490,7 +2506,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2490
2506
|
const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
|
|
2491
2507
|
switch (tag) {
|
|
2492
2508
|
case "t":
|
|
2493
|
-
walk(child);
|
|
2509
|
+
walk(child, depth + 1);
|
|
2494
2510
|
break;
|
|
2495
2511
|
// 자식 순회 (tab 등 하위 요소 처리)
|
|
2496
2512
|
case "tab": {
|
|
@@ -2523,7 +2539,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2523
2539
|
const safe = sanitizeHref(url);
|
|
2524
2540
|
if (safe) href = safe;
|
|
2525
2541
|
}
|
|
2526
|
-
walk(child);
|
|
2542
|
+
walk(child, depth + 1);
|
|
2527
2543
|
break;
|
|
2528
2544
|
}
|
|
2529
2545
|
// 각주/미주
|
|
@@ -2594,11 +2610,11 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2594
2610
|
case "r": {
|
|
2595
2611
|
const runCharPr = child.getAttribute("charPrIDRef");
|
|
2596
2612
|
if (runCharPr && !charPrId) charPrId = runCharPr;
|
|
2597
|
-
walk(child);
|
|
2613
|
+
walk(child, depth + 1);
|
|
2598
2614
|
break;
|
|
2599
2615
|
}
|
|
2600
2616
|
default:
|
|
2601
|
-
walk(child);
|
|
2617
|
+
walk(child, depth + 1);
|
|
2602
2618
|
break;
|
|
2603
2619
|
}
|
|
2604
2620
|
}
|
|
@@ -2609,7 +2625,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2609
2625
|
let cleanText = text.replace(/[ \t]+/g, " ").trim();
|
|
2610
2626
|
if (/^그림입니다\.?\s*원본\s*그림의\s*(이름|크기)/.test(cleanText)) cleanText = "";
|
|
2611
2627
|
cleanText = cleanText.replace(/그림입니다\.?\s*원본\s*그림의\s*(이름|크기)[^\n]*(\n[^\n]*원본\s*그림의\s*(이름|크기)[^\n]*)*/g, "").trim();
|
|
2612
|
-
cleanText = cleanText.replace(
|
|
2628
|
+
cleanText = cleanText.replace(/^(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|선|직선|곡선|화살표|오각형|육각형|팔각형|별|십자|구름|마름모|도넛|평행사변형|사다리꼴|개체|그리기\s?개체|묶음\s?개체|글상자|표|그림|OLE\s?개체)\s?입니다\.?$/gm, "").trim();
|
|
2613
2629
|
let style;
|
|
2614
2630
|
if (styleMap && charPrId) {
|
|
2615
2631
|
const charProp = styleMap.charProperties.get(charPrId);
|
|
@@ -2711,628 +2727,805 @@ function gongmunDepthFromIndent(para2, left, ctx) {
|
|
|
2711
2727
|
return depth >= 1 && depth < 8 ? depth : null;
|
|
2712
2728
|
}
|
|
2713
2729
|
|
|
2714
|
-
// src/
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2730
|
+
// src/hwp5/record.ts
|
|
2731
|
+
import { inflateRawSync, inflateSync } from "zlib";
|
|
2732
|
+
var TAG_PARA_HEADER = 66;
|
|
2733
|
+
var TAG_PARA_TEXT = 67;
|
|
2734
|
+
var TAG_CHAR_SHAPE = 68;
|
|
2735
|
+
var TAG_CTRL_HEADER = 71;
|
|
2736
|
+
var TAG_LIST_HEADER = 72;
|
|
2737
|
+
var TAG_TABLE = 77;
|
|
2738
|
+
var TAG_SHAPE_COMPONENT = 76;
|
|
2739
|
+
var TAG_SHAPE_COMPONENT_PICTURE = 85;
|
|
2740
|
+
var TAG_SHAPE_COMPONENT_CONTAINER = 86;
|
|
2741
|
+
var TAG_EQEDIT = 88;
|
|
2742
|
+
var TAG_BIN_DATA = 18;
|
|
2743
|
+
var TAG_DOC_CHAR_SHAPE = 21;
|
|
2744
|
+
var TAG_NUMBERING = 23;
|
|
2745
|
+
var TAG_BULLET = 24;
|
|
2746
|
+
var TAG_DOC_PARA_SHAPE = 25;
|
|
2747
|
+
var TAG_DOC_STYLE = 26;
|
|
2748
|
+
var CHAR_LINE = 0;
|
|
2749
|
+
var CHAR_SECTION_BREAK = 10;
|
|
2750
|
+
var CHAR_PARA = 13;
|
|
2751
|
+
var CHAR_TAB = 9;
|
|
2752
|
+
var CHAR_HYPHEN = 24;
|
|
2753
|
+
var CHAR_NBSP = 30;
|
|
2754
|
+
var CHAR_FIXED_WIDTH = 31;
|
|
2755
|
+
var FLAG_COMPRESSED = 1 << 0;
|
|
2756
|
+
var FLAG_ENCRYPTED = 1 << 1;
|
|
2757
|
+
var FLAG_DISTRIBUTION = 1 << 2;
|
|
2758
|
+
var FLAG_DRM = 1 << 4;
|
|
2759
|
+
var MAX_RECORDS = 5e5;
|
|
2760
|
+
function readRecords(data) {
|
|
2761
|
+
const records = [];
|
|
2762
|
+
let offset = 0;
|
|
2763
|
+
while (offset + 4 <= data.length && records.length < MAX_RECORDS) {
|
|
2764
|
+
const header = data.readUInt32LE(offset);
|
|
2765
|
+
offset += 4;
|
|
2766
|
+
const tagId = header & 1023;
|
|
2767
|
+
const level = header >> 10 & 1023;
|
|
2768
|
+
let size = header >> 20 & 4095;
|
|
2769
|
+
if (size === 4095) {
|
|
2770
|
+
if (offset + 4 > data.length) break;
|
|
2771
|
+
size = data.readUInt32LE(offset);
|
|
2772
|
+
offset += 4;
|
|
2773
|
+
}
|
|
2774
|
+
if (offset + size > data.length) break;
|
|
2775
|
+
records.push({ tagId, level, size, data: data.subarray(offset, offset + size) });
|
|
2776
|
+
offset += size;
|
|
2737
2777
|
}
|
|
2778
|
+
return records;
|
|
2738
2779
|
}
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
if (
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
if (mime.includes("emf")) return "emf";
|
|
2747
|
-
if (mime.includes("svg")) return "svg";
|
|
2748
|
-
return "bin";
|
|
2749
|
-
}
|
|
2750
|
-
function collectImageBlocks(blocks, out, ownerCell, depth = 0) {
|
|
2751
|
-
if (depth > MAX_XML_DEPTH) return;
|
|
2752
|
-
for (const block of blocks) {
|
|
2753
|
-
if (block.type === "image") {
|
|
2754
|
-
out.push({ block, ownerCell });
|
|
2755
|
-
} else if (block.type === "table" && block.table) {
|
|
2756
|
-
for (const row of block.table.cells) {
|
|
2757
|
-
for (const cell2 of row) {
|
|
2758
|
-
if (cell2.blocks?.length) collectImageBlocks(cell2.blocks, out, cell2, depth + 1);
|
|
2759
|
-
}
|
|
2760
|
-
}
|
|
2780
|
+
var MAX_DECOMPRESS_SIZE2 = 100 * 1024 * 1024;
|
|
2781
|
+
function decompressStream(data) {
|
|
2782
|
+
const opts = { maxOutputLength: MAX_DECOMPRESS_SIZE2 };
|
|
2783
|
+
if (data.length >= 2 && data[0] === 120) {
|
|
2784
|
+
try {
|
|
2785
|
+
return inflateSync(data, opts);
|
|
2786
|
+
} catch {
|
|
2761
2787
|
}
|
|
2762
2788
|
}
|
|
2789
|
+
return inflateRawSync(data, opts);
|
|
2763
2790
|
}
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
+
function parseFileHeader(data) {
|
|
2792
|
+
if (data.length < 40) throw new KordocError("FileHeader\uAC00 \uB108\uBB34 \uC9E7\uC2B5\uB2C8\uB2E4 (\uCD5C\uC18C 40\uBC14\uC774\uD2B8)");
|
|
2793
|
+
const sig = data.subarray(0, 32).toString("utf8").replace(/\0+$/, "");
|
|
2794
|
+
return {
|
|
2795
|
+
signature: sig,
|
|
2796
|
+
versionMajor: data[35],
|
|
2797
|
+
flags: data.readUInt32LE(36)
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
function readHwpString(data, offset) {
|
|
2801
|
+
if (offset + 2 > data.length) return { value: "", next: data.length };
|
|
2802
|
+
const len = data.readUInt16LE(offset);
|
|
2803
|
+
const start = offset + 2;
|
|
2804
|
+
const end = start + len * 2;
|
|
2805
|
+
if (len === 0 || end > data.length) return { value: "", next: start };
|
|
2806
|
+
return { value: data.subarray(start, end).toString("utf16le"), next: end };
|
|
2807
|
+
}
|
|
2808
|
+
function parseDocInfo(records) {
|
|
2809
|
+
const charShapes = [];
|
|
2810
|
+
const paraShapes = [];
|
|
2811
|
+
const styles = [];
|
|
2812
|
+
const binData = [];
|
|
2813
|
+
const numberings = [];
|
|
2814
|
+
const bullets = [];
|
|
2815
|
+
for (const rec of records) {
|
|
2816
|
+
if (rec.tagId === TAG_DOC_PARA_SHAPE && rec.data.length >= 4) {
|
|
2817
|
+
const attr1 = rec.data.readUInt32LE(0);
|
|
2818
|
+
const headType = attr1 >>> 23 & 3;
|
|
2819
|
+
const paraLevel = attr1 >>> 25 & 7;
|
|
2820
|
+
const numberingId = rec.data.length >= 32 ? rec.data.readUInt16LE(30) : 0;
|
|
2821
|
+
paraShapes.push({ headType, paraLevel, numberingId });
|
|
2822
|
+
}
|
|
2823
|
+
if (rec.tagId === TAG_BIN_DATA && rec.data.length >= 2) {
|
|
2824
|
+
const attr = rec.data.readUInt16LE(0);
|
|
2825
|
+
const typeBits = attr & 15;
|
|
2826
|
+
if (typeBits === 0) {
|
|
2827
|
+
binData.push({ kind: "link", storageId: 0, extension: "" });
|
|
2828
|
+
} else {
|
|
2829
|
+
const storageId = rec.data.length >= 4 ? rec.data.readUInt16LE(2) : 0;
|
|
2830
|
+
const { value: extension } = readHwpString(rec.data, 4);
|
|
2831
|
+
binData.push({ kind: typeBits === 2 ? "storage" : "embed", storageId, extension });
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
if (rec.tagId === TAG_NUMBERING && rec.data.length >= 14) {
|
|
2835
|
+
const levelFormats = [];
|
|
2836
|
+
const numberFormats = [];
|
|
2837
|
+
const startNumbers = [1, 1, 1, 1, 1, 1, 1];
|
|
2838
|
+
let offset = 0;
|
|
2839
|
+
for (let level = 0; level < 7; level++) {
|
|
2840
|
+
if (offset + 12 > rec.data.length) {
|
|
2841
|
+
levelFormats.push("");
|
|
2842
|
+
numberFormats.push(0);
|
|
2843
|
+
continue;
|
|
2791
2844
|
}
|
|
2845
|
+
const attr = rec.data.readUInt32LE(offset);
|
|
2846
|
+
numberFormats.push(attr >>> 5 & 15);
|
|
2847
|
+
offset += 12;
|
|
2848
|
+
const { value, next } = readHwpString(rec.data, offset);
|
|
2849
|
+
levelFormats.push(value);
|
|
2850
|
+
offset = next;
|
|
2792
2851
|
}
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
imageIndex++;
|
|
2805
|
-
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
2806
|
-
img = { filename, data, mimeType };
|
|
2807
|
-
images.push(img);
|
|
2808
|
-
break;
|
|
2809
|
-
} catch (err) {
|
|
2810
|
-
if (err instanceof KordocError) throw err;
|
|
2852
|
+
let baseStart = 1;
|
|
2853
|
+
if (offset + 2 <= rec.data.length) {
|
|
2854
|
+
baseStart = rec.data.readUInt16LE(offset) || 1;
|
|
2855
|
+
offset += 2;
|
|
2856
|
+
}
|
|
2857
|
+
for (let level = 0; level < 7; level++) {
|
|
2858
|
+
if (offset + 4 <= rec.data.length) {
|
|
2859
|
+
startNumbers[level] = rec.data.readUInt32LE(offset) || 1;
|
|
2860
|
+
offset += 4;
|
|
2861
|
+
} else {
|
|
2862
|
+
startNumbers[level] = baseStart;
|
|
2811
2863
|
}
|
|
2812
2864
|
}
|
|
2813
|
-
|
|
2814
|
-
resolved.set(ref, img);
|
|
2865
|
+
numberings.push({ levelFormats, numberFormats, startNumbers });
|
|
2815
2866
|
}
|
|
2816
|
-
if (
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
2820
|
-
continue;
|
|
2867
|
+
if (rec.tagId === TAG_BULLET && rec.data.length >= 14) {
|
|
2868
|
+
const code = rec.data.readUInt16LE(12);
|
|
2869
|
+
bullets.push({ char: code > 0 ? String.fromCharCode(code) : "\u2022" });
|
|
2821
2870
|
}
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
}
|
|
2828
|
-
|
|
2829
|
-
// src/hwpx/metadata.ts
|
|
2830
|
-
import JSZip2 from "jszip";
|
|
2831
|
-
|
|
2832
|
-
// src/hwpx/zip-sections.ts
|
|
2833
|
-
import { inflateRawSync } from "zlib";
|
|
2834
|
-
function extractFromBrokenZip(buffer) {
|
|
2835
|
-
const data = new Uint8Array(buffer);
|
|
2836
|
-
const view = new DataView(buffer);
|
|
2837
|
-
let pos = 0;
|
|
2838
|
-
const blocks = [];
|
|
2839
|
-
const warnings = [
|
|
2840
|
-
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
2841
|
-
];
|
|
2842
|
-
let totalDecompressed = 0;
|
|
2843
|
-
let entryCount = 0;
|
|
2844
|
-
let sectionNum = 0;
|
|
2845
|
-
const shared = createSectionShared();
|
|
2846
|
-
while (pos < data.length - 30) {
|
|
2847
|
-
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
2848
|
-
pos++;
|
|
2849
|
-
while (pos < data.length - 30) {
|
|
2850
|
-
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
2851
|
-
pos++;
|
|
2871
|
+
if (rec.tagId === TAG_DOC_CHAR_SHAPE && rec.data.length >= 18) {
|
|
2872
|
+
if (rec.data.length >= 50) {
|
|
2873
|
+
const fontSize = rec.data.readUInt32LE(42);
|
|
2874
|
+
const attrFlags = rec.data.readUInt32LE(46);
|
|
2875
|
+
charShapes.push({ fontSize, attrFlags });
|
|
2876
|
+
} else {
|
|
2877
|
+
charShapes.push({ fontSize: 0, attrFlags: 0 });
|
|
2852
2878
|
}
|
|
2853
|
-
continue;
|
|
2854
2879
|
}
|
|
2855
|
-
if (
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
content = new TextDecoder().decode(fileData);
|
|
2883
|
-
} else if (method === 8) {
|
|
2884
|
-
const decompressed = inflateRawSync(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
2885
|
-
content = new TextDecoder().decode(decompressed);
|
|
2886
|
-
} else {
|
|
2887
|
-
continue;
|
|
2880
|
+
if (rec.tagId === TAG_DOC_STYLE && rec.data.length >= 8) {
|
|
2881
|
+
try {
|
|
2882
|
+
let offset = 0;
|
|
2883
|
+
const nameLen = rec.data.readUInt16LE(offset);
|
|
2884
|
+
offset += 2;
|
|
2885
|
+
const nameBytes = nameLen * 2;
|
|
2886
|
+
const name = nameBytes > 0 && offset + nameBytes <= rec.data.length ? rec.data.subarray(offset, offset + nameBytes).toString("utf16le") : "";
|
|
2887
|
+
offset += nameBytes;
|
|
2888
|
+
let nameKo = "";
|
|
2889
|
+
if (offset + 2 <= rec.data.length) {
|
|
2890
|
+
const nameKoLen = rec.data.readUInt16LE(offset);
|
|
2891
|
+
offset += 2;
|
|
2892
|
+
const nameKoBytes = nameKoLen * 2;
|
|
2893
|
+
if (nameKoBytes > 0 && offset + nameKoBytes <= rec.data.length) {
|
|
2894
|
+
nameKo = rec.data.subarray(offset, offset + nameKoBytes).toString("utf16le");
|
|
2895
|
+
}
|
|
2896
|
+
offset += nameKoBytes;
|
|
2897
|
+
}
|
|
2898
|
+
const type = offset < rec.data.length ? rec.data.readUInt8(offset) : 0;
|
|
2899
|
+
offset += 1;
|
|
2900
|
+
offset += 1;
|
|
2901
|
+
offset += 2;
|
|
2902
|
+
const paraShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
2903
|
+
offset += 2;
|
|
2904
|
+
const charShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
2905
|
+
styles.push({ name, nameKo, charShapeId, paraShapeId, type });
|
|
2906
|
+
} catch {
|
|
2888
2907
|
}
|
|
2889
|
-
totalDecompressed += content.length * 2;
|
|
2890
|
-
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
2891
|
-
sectionNum++;
|
|
2892
|
-
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
2893
|
-
} catch {
|
|
2894
|
-
continue;
|
|
2895
2908
|
}
|
|
2896
2909
|
}
|
|
2897
|
-
|
|
2898
|
-
applyPageText(blocks, shared);
|
|
2899
|
-
const markdown = blocksToMarkdown(blocks);
|
|
2900
|
-
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
2910
|
+
return { charShapes, paraShapes, styles, binData, numberings, bullets };
|
|
2901
2911
|
}
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
if (!file) return null;
|
|
2905
|
-
try {
|
|
2906
|
-
const xml = await file.async("text");
|
|
2907
|
-
if (!/<opf:meta\b[^>]*name="generator"[^>]*content="kordoc"/.test(xml)) return null;
|
|
2908
|
-
return xml.match(/<opf:meta\b[^>]*name="kordoc-layout"[^>]*content="([^"]*)"/)?.[1] ?? null;
|
|
2909
|
-
} catch {
|
|
2910
|
-
return null;
|
|
2911
|
-
}
|
|
2912
|
+
function createParaTextState() {
|
|
2913
|
+
return { text: "", ctrlIdx: 0, fieldStack: [], fieldRanges: [] };
|
|
2912
2914
|
}
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
for (const mp of manifestPaths) {
|
|
2916
|
-
const mpLower = mp.toLowerCase();
|
|
2917
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
2918
|
-
if (!file) continue;
|
|
2919
|
-
const xml = await file.async("text");
|
|
2920
|
-
const paths = parseSectionPathsFromManifest(xml);
|
|
2921
|
-
if (paths.length > 0) return paths;
|
|
2922
|
-
}
|
|
2923
|
-
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
2924
|
-
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
2915
|
+
function isExtendedOnlyCtrlChar(ch) {
|
|
2916
|
+
return ch >= 1 && ch <= 3 || ch >= 11 && ch <= 12 || ch >= 14 && ch <= 18 || ch >= 21 && ch <= 23;
|
|
2925
2917
|
}
|
|
2926
|
-
function
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
const
|
|
2930
|
-
const
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
const
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2918
|
+
function appendParaText(state, data, resolveControl) {
|
|
2919
|
+
let result = "";
|
|
2920
|
+
let i = 0;
|
|
2921
|
+
const base = state.text.length;
|
|
2922
|
+
const resolveAt = (byteOffset, extended) => {
|
|
2923
|
+
const ctrlId = data.readUInt32LE(byteOffset);
|
|
2924
|
+
const idx = extended ? state.ctrlIdx : -1;
|
|
2925
|
+
const replacement = resolveControl?.(idx, ctrlId);
|
|
2926
|
+
if (replacement) result += replacement;
|
|
2927
|
+
if (extended) state.ctrlIdx++;
|
|
2928
|
+
};
|
|
2929
|
+
while (i + 1 < data.length) {
|
|
2930
|
+
const ch = data.readUInt16LE(i);
|
|
2931
|
+
i += 2;
|
|
2932
|
+
switch (ch) {
|
|
2933
|
+
// ── char 타입 (2바이트만, 확장 데이터 없음) ──
|
|
2934
|
+
case CHAR_LINE:
|
|
2935
|
+
result += "\n";
|
|
2936
|
+
break;
|
|
2937
|
+
case CHAR_SECTION_BREAK: {
|
|
2938
|
+
if (i + 16 <= data.length && data.readUInt16LE(i) === 11) {
|
|
2939
|
+
resolveAt(i + 2, true);
|
|
2940
|
+
i += 16;
|
|
2941
|
+
break;
|
|
2942
|
+
}
|
|
2943
|
+
result += "\n";
|
|
2944
|
+
break;
|
|
2945
|
+
}
|
|
2946
|
+
case CHAR_PARA:
|
|
2947
|
+
break;
|
|
2948
|
+
// 문단 끝
|
|
2949
|
+
case CHAR_HYPHEN:
|
|
2950
|
+
result += "-";
|
|
2951
|
+
break;
|
|
2952
|
+
case CHAR_NBSP:
|
|
2953
|
+
result += "\xA0";
|
|
2954
|
+
break;
|
|
2955
|
+
// 진짜 NBSP
|
|
2956
|
+
case CHAR_FIXED_WIDTH:
|
|
2957
|
+
result += " ";
|
|
2958
|
+
break;
|
|
2959
|
+
// 고정폭 공백
|
|
2960
|
+
// ── inline 타입 (2바이트 + 14바이트 확장) ──
|
|
2961
|
+
case CHAR_TAB:
|
|
2962
|
+
result += " ";
|
|
2963
|
+
if (i + 14 <= data.length) i += 14;
|
|
2964
|
+
break;
|
|
2965
|
+
default:
|
|
2966
|
+
if (ch >= 1 && ch <= 31) {
|
|
2967
|
+
const isExtended = isExtendedOnlyCtrlChar(ch);
|
|
2968
|
+
const isInline = ch >= 4 && ch <= 9 || ch >= 19 && ch <= 20;
|
|
2969
|
+
if ((isExtended || isInline) && i + 14 <= data.length) {
|
|
2970
|
+
if (ch === 3) {
|
|
2971
|
+
state.fieldStack.push({ start: base + result.length, ctrlIdx: state.ctrlIdx });
|
|
2972
|
+
} else if (ch === 4) {
|
|
2973
|
+
const open = state.fieldStack.pop();
|
|
2974
|
+
if (open) {
|
|
2975
|
+
state.fieldRanges.push({ start: open.start, end: base + result.length, ctrlIdx: open.ctrlIdx });
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
resolveAt(i, isExtended);
|
|
2979
|
+
i += 14;
|
|
2980
|
+
}
|
|
2981
|
+
} else if (ch >= 32) {
|
|
2982
|
+
if (ch >= 55296 && ch <= 56319 && i + 1 < data.length) {
|
|
2983
|
+
const lo = data.readUInt16LE(i);
|
|
2984
|
+
if (lo >= 56320 && lo <= 57343) {
|
|
2985
|
+
i += 2;
|
|
2986
|
+
const codePoint = (ch - 55296 << 10) + (lo - 56320) + 65536;
|
|
2987
|
+
result += String.fromCodePoint(codePoint);
|
|
2988
|
+
break;
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
result += String.fromCharCode(ch);
|
|
2992
|
+
}
|
|
2993
|
+
break;
|
|
2943
2994
|
}
|
|
2944
|
-
if (ordered.length > 0) return ordered;
|
|
2945
2995
|
}
|
|
2946
|
-
|
|
2996
|
+
state.text += result;
|
|
2997
|
+
}
|
|
2998
|
+
function extractEquationText(data) {
|
|
2999
|
+
if (data.length < 6) return null;
|
|
3000
|
+
const scriptLength = data.readUInt16LE(4);
|
|
3001
|
+
const scriptStart = 6;
|
|
3002
|
+
const scriptEnd = scriptStart + scriptLength * 2;
|
|
3003
|
+
if (scriptLength <= 0 || scriptEnd > data.length) return null;
|
|
3004
|
+
const equation = data.subarray(scriptStart, scriptEnd).toString("utf16le").replace(/\0+/g, "").trim();
|
|
3005
|
+
return equation || null;
|
|
2947
3006
|
}
|
|
2948
3007
|
|
|
2949
|
-
// src/
|
|
2950
|
-
|
|
3008
|
+
// src/hwp5/images.ts
|
|
3009
|
+
function detectImageMime(data) {
|
|
3010
|
+
if (data.length < 4) return null;
|
|
3011
|
+
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return "image/png";
|
|
3012
|
+
if (data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
|
|
3013
|
+
if (data[0] === 71 && data[1] === 73 && data[2] === 70) return "image/gif";
|
|
3014
|
+
if (data[0] === 66 && data[1] === 77) return "image/bmp";
|
|
3015
|
+
if (data[0] === 215 && data[1] === 205 && data[2] === 198 && data[3] === 154) return "image/wmf";
|
|
3016
|
+
if (data[0] === 1 && data[1] === 0 && data[2] === 0 && data[3] === 0) return "image/emf";
|
|
3017
|
+
return null;
|
|
3018
|
+
}
|
|
3019
|
+
function normalizeBinPayload(data) {
|
|
3020
|
+
if (detectImageMime(data)) return data;
|
|
2951
3021
|
try {
|
|
2952
|
-
const
|
|
2953
|
-
|
|
2954
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
2955
|
-
if (!file) continue;
|
|
2956
|
-
const xml = await file.async("text");
|
|
2957
|
-
if (decompressed) {
|
|
2958
|
-
decompressed.total += xml.length * 2;
|
|
2959
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2960
|
-
}
|
|
2961
|
-
parseDublinCoreMetadata(xml, metadata);
|
|
2962
|
-
if (metadata.title || metadata.author) return;
|
|
2963
|
-
}
|
|
3022
|
+
const inflated = decompressStream(data);
|
|
3023
|
+
if (inflated.length > 0) return inflated;
|
|
2964
3024
|
} catch {
|
|
2965
3025
|
}
|
|
3026
|
+
return data;
|
|
2966
3027
|
}
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
const
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
if (text) return text;
|
|
3028
|
+
var BIN_ENTRY_RE = /(?:^|\/)BIN([0-9A-Fa-f]{4,8})(?:\.[^./\\]*)?$/;
|
|
3029
|
+
function collectImageBlocks(blocks, out) {
|
|
3030
|
+
for (const b of blocks) {
|
|
3031
|
+
if (b.type === "image") out.push(b);
|
|
3032
|
+
if (b.table) {
|
|
3033
|
+
for (const row of b.table.cells) {
|
|
3034
|
+
for (const cell2 of row) {
|
|
3035
|
+
if (cell2.blocks) collectImageBlocks(cell2.blocks, out);
|
|
3036
|
+
}
|
|
2977
3037
|
}
|
|
2978
3038
|
}
|
|
2979
|
-
|
|
2980
|
-
};
|
|
2981
|
-
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
2982
|
-
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
2983
|
-
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
2984
|
-
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
2985
|
-
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
2986
|
-
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
2987
|
-
if (keywords && !metadata.keywords) {
|
|
2988
|
-
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
3039
|
+
if (b.children) collectImageBlocks(b.children, out);
|
|
2989
3040
|
}
|
|
2990
3041
|
}
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
3003
|
-
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3042
|
+
function forEachTableCell(blocks, fn) {
|
|
3043
|
+
for (const b of blocks) {
|
|
3044
|
+
if (b.table) {
|
|
3045
|
+
for (const row of b.table.cells) {
|
|
3046
|
+
for (const cell2 of row) {
|
|
3047
|
+
fn(cell2);
|
|
3048
|
+
if (cell2.blocks) forEachTableCell(cell2.blocks, fn);
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
if (b.children) forEachTableCell(b.children, fn);
|
|
3004
3053
|
}
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3054
|
+
}
|
|
3055
|
+
var CELL_IMAGE_SENTINEL_RE = /!\[image\]\(hwp5bin:(\d+)\)/g;
|
|
3056
|
+
function resolveCellImageSentinels(blocks, renamed) {
|
|
3057
|
+
forEachTableCell(blocks, (cell2) => {
|
|
3058
|
+
if (!cell2.text.includes("hwp5bin:")) return;
|
|
3059
|
+
cell2.text = cell2.text.replace(CELL_IMAGE_SENTINEL_RE, (_m, idStr) => {
|
|
3060
|
+
const filename = renamed.get(Number(idStr));
|
|
3061
|
+
return filename ? `` : "[\uC774\uBBF8\uC9C0]";
|
|
3062
|
+
});
|
|
3063
|
+
});
|
|
3064
|
+
}
|
|
3065
|
+
function resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced) {
|
|
3066
|
+
const imageBlocks = [];
|
|
3067
|
+
collectImageBlocks(blocks, imageBlocks);
|
|
3068
|
+
const images = [];
|
|
3069
|
+
const renamed = /* @__PURE__ */ new Map();
|
|
3070
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
3071
|
+
let imageIndex = 0;
|
|
3072
|
+
for (const block of imageBlocks) {
|
|
3073
|
+
if (!block.text) continue;
|
|
3074
|
+
const storageId = parseInt(block.text, 10);
|
|
3075
|
+
if (isNaN(storageId)) continue;
|
|
3076
|
+
let img = resolved.get(storageId);
|
|
3077
|
+
if (img === void 0) {
|
|
3078
|
+
const bin = binDataMap.get(storageId);
|
|
3079
|
+
if (!bin) {
|
|
3080
|
+
warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
|
|
3081
|
+
resolved.set(storageId, null);
|
|
3082
|
+
} else {
|
|
3083
|
+
const mime = detectImageMime(bin.data);
|
|
3084
|
+
if (!mime) {
|
|
3085
|
+
warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
|
|
3086
|
+
resolved.set(storageId, null);
|
|
3087
|
+
} else {
|
|
3088
|
+
imageIndex++;
|
|
3089
|
+
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
3090
|
+
img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
|
|
3091
|
+
resolved.set(storageId, img);
|
|
3092
|
+
images.push({ filename: img.filename, data: img.data, mimeType: img.mime });
|
|
3093
|
+
renamed.set(storageId, img.filename);
|
|
3013
3094
|
}
|
|
3014
3095
|
}
|
|
3015
|
-
|
|
3096
|
+
img = resolved.get(storageId);
|
|
3097
|
+
}
|
|
3098
|
+
if (!img) {
|
|
3099
|
+
const bin = binDataMap.get(storageId);
|
|
3100
|
+
block.type = "paragraph";
|
|
3101
|
+
block.text = bin ? `[\uC774\uBBF8\uC9C0: ${bin.name}]` : `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
|
|
3102
|
+
continue;
|
|
3016
3103
|
}
|
|
3104
|
+
block.text = img.filename;
|
|
3105
|
+
block.imageData = { data: img.data, mimeType: img.mime, filename: binDataMap.get(storageId).name };
|
|
3017
3106
|
}
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
const shared = createSectionShared();
|
|
3030
|
-
shared.kordocLayout = await readKordocLayout(zip);
|
|
3031
|
-
let parsedSections = 0;
|
|
3032
|
-
for (let si = 0; si < sectionPaths.length; si++) {
|
|
3033
|
-
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
3034
|
-
const file = zip.file(sectionPaths[si]);
|
|
3035
|
-
if (!file) continue;
|
|
3036
|
-
try {
|
|
3037
|
-
const xml = await file.async("text");
|
|
3038
|
-
decompressed.total += xml.length * 2;
|
|
3039
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3040
|
-
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
3041
|
-
parsedSections++;
|
|
3042
|
-
options?.onProgress?.(parsedSections, totalTarget);
|
|
3043
|
-
} catch (secErr) {
|
|
3044
|
-
if (secErr instanceof KordocError) throw secErr;
|
|
3045
|
-
warnings.push({ page: si + 1, message: `\uC139\uC158 ${si + 1} \uD30C\uC2F1 \uC2E4\uD328: ${secErr instanceof Error ? secErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "PARTIAL_PARSE" });
|
|
3107
|
+
if (sweepUnreferenced) {
|
|
3108
|
+
for (const [storageId, bin] of [...binDataMap.entries()].sort((a, b) => a[0] - b[0])) {
|
|
3109
|
+
if (resolved.has(storageId)) continue;
|
|
3110
|
+
const mime = detectImageMime(bin.data);
|
|
3111
|
+
if (!mime) continue;
|
|
3112
|
+
imageIndex++;
|
|
3113
|
+
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
3114
|
+
const filename = `image_${String(imageIndex).padStart(3, "0")}.${ext}`;
|
|
3115
|
+
const data = new Uint8Array(bin.data);
|
|
3116
|
+
images.push({ filename, data, mimeType: mime });
|
|
3117
|
+
blocks.push({ type: "image", text: filename, imageData: { data, mimeType: mime, filename: bin.name } });
|
|
3046
3118
|
}
|
|
3047
3119
|
}
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
detectHwpxHeadings(blocks, styleMap);
|
|
3051
|
-
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
3052
|
-
const markdown = blocksToMarkdown(blocks);
|
|
3053
|
-
return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0, images: images.length > 0 ? images : void 0 };
|
|
3120
|
+
resolveCellImageSentinels(blocks, renamed);
|
|
3121
|
+
return images;
|
|
3054
3122
|
}
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
var TAG_SHAPE_COMPONENT_PICTURE = 85;
|
|
3066
|
-
var TAG_SHAPE_COMPONENT_CONTAINER = 86;
|
|
3067
|
-
var TAG_EQEDIT = 88;
|
|
3068
|
-
var TAG_BIN_DATA = 18;
|
|
3069
|
-
var TAG_DOC_CHAR_SHAPE = 21;
|
|
3070
|
-
var TAG_NUMBERING = 23;
|
|
3071
|
-
var TAG_BULLET = 24;
|
|
3072
|
-
var TAG_DOC_PARA_SHAPE = 25;
|
|
3073
|
-
var TAG_DOC_STYLE = 26;
|
|
3074
|
-
var CHAR_LINE = 0;
|
|
3075
|
-
var CHAR_SECTION_BREAK = 10;
|
|
3076
|
-
var CHAR_PARA = 13;
|
|
3077
|
-
var CHAR_TAB = 9;
|
|
3078
|
-
var CHAR_HYPHEN = 30;
|
|
3079
|
-
var CHAR_NBSP = 31;
|
|
3080
|
-
var CHAR_FIXED_NBSP = 24;
|
|
3081
|
-
var CHAR_FIXED_WIDTH = 25;
|
|
3082
|
-
var FLAG_COMPRESSED = 1 << 0;
|
|
3083
|
-
var FLAG_ENCRYPTED = 1 << 1;
|
|
3084
|
-
var FLAG_DISTRIBUTION = 1 << 2;
|
|
3085
|
-
var FLAG_DRM = 1 << 4;
|
|
3086
|
-
var MAX_RECORDS = 5e5;
|
|
3087
|
-
function readRecords(data) {
|
|
3088
|
-
const records = [];
|
|
3089
|
-
let offset = 0;
|
|
3090
|
-
while (offset + 4 <= data.length && records.length < MAX_RECORDS) {
|
|
3091
|
-
const header = data.readUInt32LE(offset);
|
|
3092
|
-
offset += 4;
|
|
3093
|
-
const tagId = header & 1023;
|
|
3094
|
-
const level = header >> 10 & 1023;
|
|
3095
|
-
let size = header >> 20 & 4095;
|
|
3096
|
-
if (size === 4095) {
|
|
3097
|
-
if (offset + 4 > data.length) break;
|
|
3098
|
-
size = data.readUInt32LE(offset);
|
|
3099
|
-
offset += 4;
|
|
3123
|
+
function extractHwp5Images(fileIndex, blocks, warnings, sweepUnreferenced) {
|
|
3124
|
+
const binDataMap = /* @__PURE__ */ new Map();
|
|
3125
|
+
if (fileIndex) {
|
|
3126
|
+
for (const entry of fileIndex) {
|
|
3127
|
+
if (!entry?.name || !entry.content) continue;
|
|
3128
|
+
const match = entry.name.match(BIN_ENTRY_RE);
|
|
3129
|
+
if (!match) continue;
|
|
3130
|
+
const idx = parseInt(match[1], 16);
|
|
3131
|
+
const data = normalizeBinPayload(Buffer.from(entry.content));
|
|
3132
|
+
binDataMap.set(idx, { data, name: entry.name });
|
|
3100
3133
|
}
|
|
3101
|
-
if (offset + size > data.length) break;
|
|
3102
|
-
records.push({ tagId, level, size, data: data.subarray(offset, offset + size) });
|
|
3103
|
-
offset += size;
|
|
3104
3134
|
}
|
|
3105
|
-
|
|
3135
|
+
if (binDataMap.size === 0) {
|
|
3136
|
+
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3137
|
+
return [];
|
|
3138
|
+
}
|
|
3139
|
+
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
3106
3140
|
}
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
const
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3141
|
+
function extractHwp5ImagesLenient(lcfb, blocks, warnings, sweepUnreferenced) {
|
|
3142
|
+
const binDataMap = /* @__PURE__ */ new Map();
|
|
3143
|
+
const binRe = /^BIN([0-9A-Fa-f]{4,8})(?:\.|$)/;
|
|
3144
|
+
for (const e of lcfb.entries()) {
|
|
3145
|
+
const match = e.name.match(binRe);
|
|
3146
|
+
if (!match) continue;
|
|
3147
|
+
const idx = parseInt(match[1], 16);
|
|
3148
|
+
const raw = lcfb.findStream(e.name);
|
|
3149
|
+
if (!raw) continue;
|
|
3150
|
+
binDataMap.set(idx, { data: normalizeBinPayload(raw), name: e.name });
|
|
3151
|
+
}
|
|
3152
|
+
if (binDataMap.size === 0) {
|
|
3153
|
+
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3154
|
+
return [];
|
|
3115
3155
|
}
|
|
3116
|
-
return
|
|
3156
|
+
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
3117
3157
|
}
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3158
|
+
|
|
3159
|
+
// src/hwpx/images.ts
|
|
3160
|
+
function imageExtToMime(ext) {
|
|
3161
|
+
switch (ext.toLowerCase()) {
|
|
3162
|
+
case "jpg":
|
|
3163
|
+
case "jpeg":
|
|
3164
|
+
return "image/jpeg";
|
|
3165
|
+
case "png":
|
|
3166
|
+
return "image/png";
|
|
3167
|
+
case "gif":
|
|
3168
|
+
return "image/gif";
|
|
3169
|
+
case "bmp":
|
|
3170
|
+
return "image/bmp";
|
|
3171
|
+
case "tif":
|
|
3172
|
+
case "tiff":
|
|
3173
|
+
return "image/tiff";
|
|
3174
|
+
case "wmf":
|
|
3175
|
+
return "image/wmf";
|
|
3176
|
+
case "emf":
|
|
3177
|
+
return "image/emf";
|
|
3178
|
+
case "svg":
|
|
3179
|
+
return "image/svg+xml";
|
|
3180
|
+
default:
|
|
3181
|
+
return "application/octet-stream";
|
|
3182
|
+
}
|
|
3126
3183
|
}
|
|
3127
|
-
function
|
|
3128
|
-
if (
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
if (
|
|
3133
|
-
|
|
3184
|
+
function mimeToExt(mime) {
|
|
3185
|
+
if (mime.includes("jpeg")) return "jpg";
|
|
3186
|
+
if (mime.includes("png")) return "png";
|
|
3187
|
+
if (mime.includes("gif")) return "gif";
|
|
3188
|
+
if (mime.includes("bmp")) return "bmp";
|
|
3189
|
+
if (mime.includes("tiff")) return "tif";
|
|
3190
|
+
if (mime.includes("wmf")) return "wmf";
|
|
3191
|
+
if (mime.includes("emf")) return "emf";
|
|
3192
|
+
if (mime.includes("svg")) return "svg";
|
|
3193
|
+
return "bin";
|
|
3134
3194
|
}
|
|
3135
|
-
function
|
|
3136
|
-
|
|
3137
|
-
const
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
const headType = attr1 >>> 23 & 3;
|
|
3146
|
-
const paraLevel = attr1 >>> 25 & 7;
|
|
3147
|
-
const numberingId = rec.data.length >= 32 ? rec.data.readUInt16LE(30) : 0;
|
|
3148
|
-
paraShapes.push({ headType, paraLevel, numberingId });
|
|
3149
|
-
}
|
|
3150
|
-
if (rec.tagId === TAG_BIN_DATA && rec.data.length >= 2) {
|
|
3151
|
-
const attr = rec.data.readUInt16LE(0);
|
|
3152
|
-
const typeBits = attr & 15;
|
|
3153
|
-
if (typeBits === 0) {
|
|
3154
|
-
binData.push({ kind: "link", storageId: 0, extension: "" });
|
|
3155
|
-
} else {
|
|
3156
|
-
const storageId = rec.data.length >= 4 ? rec.data.readUInt16LE(2) : 0;
|
|
3157
|
-
const { value: extension } = readHwpString(rec.data, 4);
|
|
3158
|
-
binData.push({ kind: typeBits === 2 ? "storage" : "embed", storageId, extension });
|
|
3195
|
+
function collectImageBlocks2(blocks, out, ownerCell, depth = 0) {
|
|
3196
|
+
if (depth > MAX_XML_DEPTH) return;
|
|
3197
|
+
for (const block of blocks) {
|
|
3198
|
+
if (block.type === "image") {
|
|
3199
|
+
out.push({ block, ownerCell });
|
|
3200
|
+
} else if (block.type === "table" && block.table) {
|
|
3201
|
+
for (const row of block.table.cells) {
|
|
3202
|
+
for (const cell2 of row) {
|
|
3203
|
+
if (cell2.blocks?.length) collectImageBlocks2(cell2.blocks, out, cell2, depth + 1);
|
|
3204
|
+
}
|
|
3159
3205
|
}
|
|
3160
3206
|
}
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
async function extractImagesFromZip(zip, blocks, decompressed, warnings, sweepUnreferenced) {
|
|
3210
|
+
const images = [];
|
|
3211
|
+
let imageIndex = 0;
|
|
3212
|
+
const usedPaths = /* @__PURE__ */ new Set();
|
|
3213
|
+
const imageBlocks = [];
|
|
3214
|
+
collectImageBlocks2(blocks, imageBlocks);
|
|
3215
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
3216
|
+
for (const { block, ownerCell } of imageBlocks) {
|
|
3217
|
+
if (block.type !== "image" || !block.text) continue;
|
|
3218
|
+
const ref = block.text;
|
|
3219
|
+
let img = resolved.get(ref);
|
|
3220
|
+
if (img === void 0) {
|
|
3221
|
+
img = null;
|
|
3222
|
+
const candidates = [
|
|
3223
|
+
`BinData/${ref}`,
|
|
3224
|
+
`Contents/BinData/${ref}`,
|
|
3225
|
+
ref
|
|
3226
|
+
// 절대 경로일 수도 있음
|
|
3227
|
+
];
|
|
3228
|
+
let resolvedPath = null;
|
|
3229
|
+
if (!ref.includes(".")) {
|
|
3230
|
+
const prefixes = [`BinData/${ref}`, `Contents/BinData/${ref}`];
|
|
3231
|
+
for (const prefix of prefixes) {
|
|
3232
|
+
const match = zip.file(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.[a-zA-Z0-9]+$`));
|
|
3233
|
+
if (match.length > 0) {
|
|
3234
|
+
resolvedPath = match[0].name;
|
|
3235
|
+
break;
|
|
3236
|
+
}
|
|
3171
3237
|
}
|
|
3172
|
-
const attr = rec.data.readUInt32LE(offset);
|
|
3173
|
-
numberFormats.push(attr >>> 5 & 15);
|
|
3174
|
-
offset += 12;
|
|
3175
|
-
const { value, next } = readHwpString(rec.data, offset);
|
|
3176
|
-
levelFormats.push(value);
|
|
3177
|
-
offset = next;
|
|
3178
|
-
}
|
|
3179
|
-
let baseStart = 1;
|
|
3180
|
-
if (offset + 2 <= rec.data.length) {
|
|
3181
|
-
baseStart = rec.data.readUInt16LE(offset) || 1;
|
|
3182
|
-
offset += 2;
|
|
3183
3238
|
}
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3239
|
+
const allCandidates = resolvedPath ? [resolvedPath, ...candidates] : candidates;
|
|
3240
|
+
for (const path of allCandidates) {
|
|
3241
|
+
if (isPathTraversal(path)) continue;
|
|
3242
|
+
const file = zip.file(path);
|
|
3243
|
+
if (!file) continue;
|
|
3244
|
+
try {
|
|
3245
|
+
const data = await file.async("uint8array");
|
|
3246
|
+
decompressed.total += data.length;
|
|
3247
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3248
|
+
const ext = path.includes(".") ? path.split(".").pop() || "png" : "png";
|
|
3249
|
+
const mimeType = imageExtToMime(ext);
|
|
3250
|
+
imageIndex++;
|
|
3251
|
+
const filename2 = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
3252
|
+
img = { filename: filename2, data, mimeType };
|
|
3253
|
+
images.push(img);
|
|
3254
|
+
usedPaths.add(path);
|
|
3255
|
+
break;
|
|
3256
|
+
} catch (err) {
|
|
3257
|
+
if (err instanceof KordocError) throw err;
|
|
3190
3258
|
}
|
|
3191
3259
|
}
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
if (rec.tagId === TAG_BULLET && rec.data.length >= 14) {
|
|
3195
|
-
const code = rec.data.readUInt16LE(12);
|
|
3196
|
-
bullets.push({ char: code > 0 ? String.fromCharCode(code) : "\u2022" });
|
|
3260
|
+
if (!img) warnings?.push({ page: block.pageNumber, message: `\uC774\uBBF8\uC9C0 \uD30C\uC77C \uC5C6\uC74C: ${ref}`, code: "SKIPPED_IMAGE" });
|
|
3261
|
+
resolved.set(ref, img);
|
|
3197
3262
|
}
|
|
3198
|
-
if (
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
} else {
|
|
3204
|
-
charShapes.push({ fontSize: 0, attrFlags: 0 });
|
|
3205
|
-
}
|
|
3263
|
+
if (!img) {
|
|
3264
|
+
block.type = "paragraph";
|
|
3265
|
+
block.text = `[\uC774\uBBF8\uC9C0: ${ref}]`;
|
|
3266
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, () => `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
3267
|
+
continue;
|
|
3206
3268
|
}
|
|
3207
|
-
|
|
3269
|
+
const filename = img.filename;
|
|
3270
|
+
block.text = filename;
|
|
3271
|
+
block.imageData = { data: img.data, mimeType: img.mimeType, filename: ref };
|
|
3272
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, () => ``);
|
|
3273
|
+
}
|
|
3274
|
+
if (sweepUnreferenced) {
|
|
3275
|
+
const binEntries = zip.file(/(?:^|\/)BinData\//i);
|
|
3276
|
+
for (const file of binEntries) {
|
|
3277
|
+
if (file.dir || usedPaths.has(file.name) || isPathTraversal(file.name)) continue;
|
|
3208
3278
|
try {
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
const
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
offset += 2;
|
|
3219
|
-
const nameKoBytes = nameKoLen * 2;
|
|
3220
|
-
if (nameKoBytes > 0 && offset + nameKoBytes <= rec.data.length) {
|
|
3221
|
-
nameKo = rec.data.subarray(offset, offset + nameKoBytes).toString("utf16le");
|
|
3222
|
-
}
|
|
3223
|
-
offset += nameKoBytes;
|
|
3279
|
+
const data = await file.async("uint8array");
|
|
3280
|
+
decompressed.total += data.length;
|
|
3281
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3282
|
+
const ext = file.name.includes(".") ? file.name.split(".").pop() || "" : "";
|
|
3283
|
+
let mimeType = imageExtToMime(ext);
|
|
3284
|
+
if (mimeType === "application/octet-stream") {
|
|
3285
|
+
const sniffed = detectImageMime(data);
|
|
3286
|
+
if (!sniffed) continue;
|
|
3287
|
+
mimeType = sniffed;
|
|
3224
3288
|
}
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
} catch {
|
|
3289
|
+
imageIndex++;
|
|
3290
|
+
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
3291
|
+
const img = { filename, data, mimeType };
|
|
3292
|
+
images.push(img);
|
|
3293
|
+
blocks.push({ type: "image", text: filename, imageData: { data, mimeType, filename: file.name } });
|
|
3294
|
+
} catch (err) {
|
|
3295
|
+
if (err instanceof KordocError) throw err;
|
|
3296
|
+
warnings?.push({ message: `\uC774\uBBF8\uC9C0 \uCD94\uCD9C \uC2E4\uD328: ${file.name}`, code: "SKIPPED_IMAGE" });
|
|
3234
3297
|
}
|
|
3235
3298
|
}
|
|
3236
3299
|
}
|
|
3237
|
-
return
|
|
3300
|
+
return images;
|
|
3238
3301
|
}
|
|
3239
|
-
|
|
3240
|
-
|
|
3302
|
+
|
|
3303
|
+
// src/hwpx/metadata.ts
|
|
3304
|
+
import JSZip2 from "jszip";
|
|
3305
|
+
|
|
3306
|
+
// src/hwpx/zip-sections.ts
|
|
3307
|
+
import { inflateRawSync as inflateRawSync2 } from "zlib";
|
|
3308
|
+
function extractFromBrokenZip(buffer) {
|
|
3309
|
+
const data = new Uint8Array(buffer);
|
|
3310
|
+
const view = new DataView(buffer);
|
|
3311
|
+
let pos = 0;
|
|
3312
|
+
const blocks = [];
|
|
3313
|
+
const warnings = [
|
|
3314
|
+
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
3315
|
+
];
|
|
3316
|
+
let totalDecompressed = 0;
|
|
3317
|
+
let entryCount = 0;
|
|
3318
|
+
let sectionNum = 0;
|
|
3319
|
+
const shared = createSectionShared();
|
|
3320
|
+
while (pos < data.length - 30) {
|
|
3321
|
+
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
3322
|
+
pos++;
|
|
3323
|
+
while (pos < data.length - 30) {
|
|
3324
|
+
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
3325
|
+
pos++;
|
|
3326
|
+
}
|
|
3327
|
+
continue;
|
|
3328
|
+
}
|
|
3329
|
+
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
3330
|
+
const method = view.getUint16(pos + 8, true);
|
|
3331
|
+
const compSize = view.getUint32(pos + 18, true);
|
|
3332
|
+
const nameLen = view.getUint16(pos + 26, true);
|
|
3333
|
+
const extraLen = view.getUint16(pos + 28, true);
|
|
3334
|
+
if (nameLen > 1024 || extraLen > 65535) {
|
|
3335
|
+
pos += 30 + nameLen + extraLen;
|
|
3336
|
+
continue;
|
|
3337
|
+
}
|
|
3338
|
+
const fileStart = pos + 30 + nameLen + extraLen;
|
|
3339
|
+
if (fileStart + compSize > data.length) break;
|
|
3340
|
+
if (compSize === 0 && method !== 0) {
|
|
3341
|
+
pos = fileStart;
|
|
3342
|
+
continue;
|
|
3343
|
+
}
|
|
3344
|
+
const nameBytes = data.slice(pos + 30, pos + 30 + nameLen);
|
|
3345
|
+
const name = new TextDecoder().decode(nameBytes);
|
|
3346
|
+
if (isPathTraversal(name)) {
|
|
3347
|
+
pos = fileStart + compSize;
|
|
3348
|
+
continue;
|
|
3349
|
+
}
|
|
3350
|
+
const fileData = data.slice(fileStart, fileStart + compSize);
|
|
3351
|
+
pos = fileStart + compSize;
|
|
3352
|
+
if (!name.toLowerCase().includes("section") || !name.endsWith(".xml")) continue;
|
|
3353
|
+
try {
|
|
3354
|
+
let content;
|
|
3355
|
+
if (method === 0) {
|
|
3356
|
+
content = new TextDecoder().decode(fileData);
|
|
3357
|
+
} else if (method === 8) {
|
|
3358
|
+
const decompressed = inflateRawSync2(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
3359
|
+
content = new TextDecoder().decode(decompressed);
|
|
3360
|
+
} else {
|
|
3361
|
+
continue;
|
|
3362
|
+
}
|
|
3363
|
+
totalDecompressed += content.length * 2;
|
|
3364
|
+
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
3365
|
+
sectionNum++;
|
|
3366
|
+
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
3367
|
+
} catch {
|
|
3368
|
+
continue;
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
if (blocks.length === 0) throw new KordocError("\uC190\uC0C1\uB41C HWPX\uC5D0\uC11C \uC139\uC158 \uB370\uC774\uD130\uB97C \uBCF5\uAD6C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
3372
|
+
applyPageText(blocks, shared);
|
|
3373
|
+
const markdown = blocksToMarkdown(blocks);
|
|
3374
|
+
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
3241
3375
|
}
|
|
3242
|
-
function
|
|
3243
|
-
|
|
3376
|
+
async function readKordocLayout(zip) {
|
|
3377
|
+
const file = zip.file("Contents/content.hpf") ?? zip.file("content.hpf");
|
|
3378
|
+
if (!file) return null;
|
|
3379
|
+
try {
|
|
3380
|
+
const xml = await file.async("text");
|
|
3381
|
+
if (!/<opf:meta\b[^>]*name="generator"[^>]*content="kordoc"/.test(xml)) return null;
|
|
3382
|
+
return xml.match(/<opf:meta\b[^>]*name="kordoc-layout"[^>]*content="([^"]*)"/)?.[1] ?? null;
|
|
3383
|
+
} catch {
|
|
3384
|
+
return null;
|
|
3385
|
+
}
|
|
3244
3386
|
}
|
|
3245
|
-
function
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
const
|
|
3252
|
-
const
|
|
3253
|
-
if (
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3387
|
+
async function resolveSectionPaths(zip) {
|
|
3388
|
+
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
3389
|
+
for (const mp of manifestPaths) {
|
|
3390
|
+
const mpLower = mp.toLowerCase();
|
|
3391
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
3392
|
+
if (!file) continue;
|
|
3393
|
+
const xml = await file.async("text");
|
|
3394
|
+
const paths = parseSectionPathsFromManifest(xml);
|
|
3395
|
+
if (paths.length > 0) return paths;
|
|
3396
|
+
}
|
|
3397
|
+
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
3398
|
+
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
3399
|
+
}
|
|
3400
|
+
function parseSectionPathsFromManifest(xml) {
|
|
3401
|
+
const parser = createXmlParser();
|
|
3402
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
3403
|
+
const items = doc.getElementsByTagName("opf:item");
|
|
3404
|
+
const spine = doc.getElementsByTagName("opf:itemref");
|
|
3405
|
+
const idToHref = /* @__PURE__ */ new Map();
|
|
3406
|
+
for (let i = 0; i < items.length; i++) {
|
|
3407
|
+
const item = items[i];
|
|
3408
|
+
const id = item.getAttribute("id") || "";
|
|
3409
|
+
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
3410
|
+
if (id && href) idToHref.set(id, href);
|
|
3411
|
+
}
|
|
3412
|
+
if (spine.length > 0) {
|
|
3413
|
+
const ordered = [];
|
|
3414
|
+
for (let i = 0; i < spine.length; i++) {
|
|
3415
|
+
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
3416
|
+
if (href) ordered.push(href);
|
|
3417
|
+
}
|
|
3418
|
+
if (ordered.length > 0) return ordered;
|
|
3419
|
+
}
|
|
3420
|
+
return Array.from(idToHref.values()).sort(compareSectionPaths);
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
// src/hwpx/metadata.ts
|
|
3424
|
+
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
3425
|
+
try {
|
|
3426
|
+
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
3427
|
+
for (const mp of metaPaths) {
|
|
3428
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
3429
|
+
if (!file) continue;
|
|
3430
|
+
const xml = await file.async("text");
|
|
3431
|
+
if (decompressed) {
|
|
3432
|
+
decompressed.total += xml.length * 2;
|
|
3433
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3272
3434
|
}
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3435
|
+
parseDublinCoreMetadata(xml, metadata);
|
|
3436
|
+
if (metadata.title || metadata.author) return;
|
|
3437
|
+
}
|
|
3438
|
+
} catch {
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
function parseDublinCoreMetadata(xml, metadata) {
|
|
3442
|
+
const parser = createXmlParser();
|
|
3443
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
3444
|
+
if (!doc.documentElement) return;
|
|
3445
|
+
const getText = (tagNames) => {
|
|
3446
|
+
for (const tag of tagNames) {
|
|
3447
|
+
const els = doc.getElementsByTagName(tag);
|
|
3448
|
+
if (els.length > 0) {
|
|
3449
|
+
const text = els[0].textContent?.trim();
|
|
3450
|
+
if (text) return text;
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
return void 0;
|
|
3454
|
+
};
|
|
3455
|
+
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
3456
|
+
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
3457
|
+
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
3458
|
+
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
3459
|
+
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
3460
|
+
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
3461
|
+
if (keywords && !metadata.keywords) {
|
|
3462
|
+
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
// src/hwpx/parser.ts
|
|
3467
|
+
async function parseHwpxDocument(buffer, options) {
|
|
3468
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
3469
|
+
let zip;
|
|
3470
|
+
try {
|
|
3471
|
+
zip = await JSZip3.loadAsync(buffer);
|
|
3472
|
+
} catch {
|
|
3473
|
+
return extractFromBrokenZip(buffer);
|
|
3474
|
+
}
|
|
3475
|
+
const actualEntryCount = Object.keys(zip.files).length;
|
|
3476
|
+
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
3477
|
+
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3478
|
+
}
|
|
3479
|
+
const manifestFile = zip.file("META-INF/manifest.xml");
|
|
3480
|
+
if (manifestFile) {
|
|
3481
|
+
const manifestXml = await manifestFile.async("text");
|
|
3482
|
+
if (isEncryptedHwpx(manifestXml)) {
|
|
3483
|
+
if (isComFallbackAvailable() && options?.filePath) {
|
|
3484
|
+
const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
|
|
3485
|
+
if (pages.some((p) => p && p.trim().length > 0)) {
|
|
3486
|
+
return comResultToParseResult(pages, pageCount, warnings2);
|
|
3322
3487
|
}
|
|
3323
|
-
|
|
3488
|
+
}
|
|
3489
|
+
throw new KordocError("DRM \uC554\uD638\uD654\uB41C HWPX \uD30C\uC77C\uC785\uB2C8\uB2E4. Windows + \uD55C\uCEF4 \uC624\uD53C\uC2A4 \uC124\uCE58 \uC2DC \uC790\uB3D9 \uCD94\uCD9C\uB429\uB2C8\uB2E4.");
|
|
3324
3490
|
}
|
|
3325
3491
|
}
|
|
3326
|
-
|
|
3327
|
-
}
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
const
|
|
3331
|
-
const
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
const
|
|
3335
|
-
|
|
3492
|
+
const decompressed = { total: 0 };
|
|
3493
|
+
const metadata = {};
|
|
3494
|
+
await extractHwpxMetadata(zip, metadata, decompressed);
|
|
3495
|
+
const styleMap = await extractHwpxStyles(zip, decompressed);
|
|
3496
|
+
const warnings = [];
|
|
3497
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
3498
|
+
if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
3499
|
+
metadata.pageCount = sectionPaths.length;
|
|
3500
|
+
const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null;
|
|
3501
|
+
const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length;
|
|
3502
|
+
const blocks = [];
|
|
3503
|
+
const shared = createSectionShared();
|
|
3504
|
+
shared.kordocLayout = await readKordocLayout(zip);
|
|
3505
|
+
shared.keepTrailingEmptyCols = options?.keepTrailingEmptyCols;
|
|
3506
|
+
let parsedSections = 0;
|
|
3507
|
+
for (let si = 0; si < sectionPaths.length; si++) {
|
|
3508
|
+
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
3509
|
+
const file = zip.file(sectionPaths[si]);
|
|
3510
|
+
if (!file) continue;
|
|
3511
|
+
try {
|
|
3512
|
+
const xml = await file.async("text");
|
|
3513
|
+
decompressed.total += xml.length * 2;
|
|
3514
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new ZipBombError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3515
|
+
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
3516
|
+
parsedSections++;
|
|
3517
|
+
options?.onProgress?.(parsedSections, totalTarget);
|
|
3518
|
+
} catch (secErr) {
|
|
3519
|
+
if (secErr instanceof ZipBombError) throw secErr;
|
|
3520
|
+
warnings.push({ page: si + 1, message: `\uC139\uC158 ${si + 1} \uD30C\uC2F1 \uC2E4\uD328: ${secErr instanceof Error ? secErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "PARTIAL_PARSE" });
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
applyPageText(blocks, shared);
|
|
3524
|
+
const images = await extractImagesFromZip(zip, blocks, decompressed, warnings, !pageFilter);
|
|
3525
|
+
detectHwpxHeadings(blocks, styleMap);
|
|
3526
|
+
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
3527
|
+
const markdown = blocksToMarkdown(blocks);
|
|
3528
|
+
return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0, images: images.length > 0 ? images : void 0 };
|
|
3336
3529
|
}
|
|
3337
3530
|
|
|
3338
3531
|
// src/hwp5/numbering.ts
|
|
@@ -3443,350 +3636,74 @@ function formatLatin(n, upper) {
|
|
|
3443
3636
|
}
|
|
3444
3637
|
return result;
|
|
3445
3638
|
}
|
|
3446
|
-
function formatEastAsianNumber(n, digits, units, zero) {
|
|
3447
|
-
if (n === 0) return zero;
|
|
3448
|
-
if (n < 0 || n > 99999) return String(n);
|
|
3449
|
-
let result = "";
|
|
3450
|
-
let num4 = n;
|
|
3451
|
-
let unit = 0;
|
|
3452
|
-
while (num4 > 0) {
|
|
3453
|
-
const d = num4 % 10;
|
|
3454
|
-
if (d > 0) {
|
|
3455
|
-
const digitStr = d === 1 && unit > 0 ? "" : digits[d];
|
|
3456
|
-
result = digitStr + units[unit] + result;
|
|
3457
|
-
}
|
|
3458
|
-
num4 = Math.floor(num4 / 10);
|
|
3459
|
-
unit++;
|
|
3460
|
-
}
|
|
3461
|
-
return result;
|
|
3462
|
-
}
|
|
3463
|
-
var HANGUL_DIGITS = ["", "\uC77C", "\uC774", "\uC0BC", "\uC0AC", "\uC624", "\uC721", "\uCE60", "\uD314", "\uAD6C"];
|
|
3464
|
-
var HANGUL_UNITS = ["", "\uC2ED", "\uBC31", "\uCC9C", "\uB9CC"];
|
|
3465
|
-
var HANJA_DIGITS = ["", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D"];
|
|
3466
|
-
var HANJA_UNITS = ["", "\u5341", "\u767E", "\u5343", "\u842C"];
|
|
3467
|
-
function formatNumber(n, fmt) {
|
|
3468
|
-
switch (fmt) {
|
|
3469
|
-
case "circled":
|
|
3470
|
-
return fromTable(n, CIRCLED_DIGITS);
|
|
3471
|
-
case "romanUpper":
|
|
3472
|
-
return formatRoman(n, true);
|
|
3473
|
-
case "romanLower":
|
|
3474
|
-
return formatRoman(n, false);
|
|
3475
|
-
case "latinUpper":
|
|
3476
|
-
return formatLatin(n, true) || String(n);
|
|
3477
|
-
case "latinLower":
|
|
3478
|
-
return formatLatin(n, false) || String(n);
|
|
3479
|
-
case "ganada":
|
|
3480
|
-
return fromTable(n, GANADA);
|
|
3481
|
-
case "circledGanada":
|
|
3482
|
-
return fromTable(n, CIRCLED_GANADA);
|
|
3483
|
-
case "jamo":
|
|
3484
|
-
return fromTable(n, JAMO);
|
|
3485
|
-
case "circledJamo":
|
|
3486
|
-
return fromTable(n, CIRCLED_JAMO);
|
|
3487
|
-
case "hangulNum":
|
|
3488
|
-
return formatEastAsianNumber(n, HANGUL_DIGITS, HANGUL_UNITS, "\uC601");
|
|
3489
|
-
case "hanjaNum":
|
|
3490
|
-
return formatEastAsianNumber(n, HANJA_DIGITS, HANJA_UNITS, "\u96F6");
|
|
3491
|
-
default:
|
|
3492
|
-
return String(n);
|
|
3493
|
-
}
|
|
3494
|
-
}
|
|
3495
|
-
function expandNumberingFormat(formatStr, counters, numbering) {
|
|
3496
|
-
let result = "";
|
|
3497
|
-
let i = 0;
|
|
3498
|
-
while (i < formatStr.length) {
|
|
3499
|
-
const ch = formatStr[i];
|
|
3500
|
-
if (ch === "^" && i + 1 < formatStr.length && formatStr[i + 1] >= "1" && formatStr[i + 1] <= "7") {
|
|
3501
|
-
const levelRef = formatStr.charCodeAt(i + 1) - 48;
|
|
3502
|
-
const idx = levelRef - 1;
|
|
3503
|
-
const counterVal = counters[idx] ?? 0;
|
|
3504
|
-
const start = numbering.startNumbers[idx] ?? 1;
|
|
3505
|
-
const num4 = counterVal > 0 ? start - 1 + counterVal : start;
|
|
3506
|
-
result += formatNumber(num4, headFormatToNumFmt(numbering.numberFormats[idx] ?? 0));
|
|
3507
|
-
i += 2;
|
|
3508
|
-
continue;
|
|
3509
|
-
}
|
|
3510
|
-
result += ch;
|
|
3511
|
-
i++;
|
|
3512
|
-
}
|
|
3513
|
-
return result;
|
|
3514
|
-
}
|
|
3515
|
-
|
|
3516
|
-
// src/hwp5/images.ts
|
|
3517
|
-
function detectImageMime(data) {
|
|
3518
|
-
if (data.length < 4) return null;
|
|
3519
|
-
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return "image/png";
|
|
3520
|
-
if (data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
|
|
3521
|
-
if (data[0] === 71 && data[1] === 73 && data[2] === 70) return "image/gif";
|
|
3522
|
-
if (data[0] === 66 && data[1] === 77) return "image/bmp";
|
|
3523
|
-
if (data[0] === 215 && data[1] === 205 && data[2] === 198 && data[3] === 154) return "image/wmf";
|
|
3524
|
-
if (data[0] === 1 && data[1] === 0 && data[2] === 0 && data[3] === 0) return "image/emf";
|
|
3525
|
-
return null;
|
|
3526
|
-
}
|
|
3527
|
-
function normalizeBinPayload(data) {
|
|
3528
|
-
if (detectImageMime(data)) return data;
|
|
3529
|
-
try {
|
|
3530
|
-
const inflated = decompressStream(data);
|
|
3531
|
-
if (inflated.length > 0) return inflated;
|
|
3532
|
-
} catch {
|
|
3533
|
-
}
|
|
3534
|
-
return data;
|
|
3535
|
-
}
|
|
3536
|
-
var BIN_ENTRY_RE = /(?:^|\/)BIN([0-9A-Fa-f]{4,8})(?:\.[^./\\]*)?$/;
|
|
3537
|
-
function collectImageBlocks2(blocks, out) {
|
|
3538
|
-
for (const b of blocks) {
|
|
3539
|
-
if (b.type === "image") out.push(b);
|
|
3540
|
-
if (b.table) {
|
|
3541
|
-
for (const row of b.table.cells) {
|
|
3542
|
-
for (const cell2 of row) {
|
|
3543
|
-
if (cell2.blocks) collectImageBlocks2(cell2.blocks, out);
|
|
3544
|
-
}
|
|
3545
|
-
}
|
|
3546
|
-
}
|
|
3547
|
-
if (b.children) collectImageBlocks2(b.children, out);
|
|
3548
|
-
}
|
|
3549
|
-
}
|
|
3550
|
-
function forEachTableCell(blocks, fn) {
|
|
3551
|
-
for (const b of blocks) {
|
|
3552
|
-
if (b.table) {
|
|
3553
|
-
for (const row of b.table.cells) {
|
|
3554
|
-
for (const cell2 of row) {
|
|
3555
|
-
fn(cell2);
|
|
3556
|
-
if (cell2.blocks) forEachTableCell(cell2.blocks, fn);
|
|
3557
|
-
}
|
|
3558
|
-
}
|
|
3559
|
-
}
|
|
3560
|
-
if (b.children) forEachTableCell(b.children, fn);
|
|
3561
|
-
}
|
|
3562
|
-
}
|
|
3563
|
-
var CELL_IMAGE_SENTINEL_RE = /!\[image\]\(hwp5bin:(\d+)\)/g;
|
|
3564
|
-
function resolveCellImageSentinels(blocks, renamed) {
|
|
3565
|
-
forEachTableCell(blocks, (cell2) => {
|
|
3566
|
-
if (!cell2.text.includes("hwp5bin:")) return;
|
|
3567
|
-
cell2.text = cell2.text.replace(CELL_IMAGE_SENTINEL_RE, (_m, idStr) => {
|
|
3568
|
-
const filename = renamed.get(Number(idStr));
|
|
3569
|
-
return filename ? `` : "[\uC774\uBBF8\uC9C0]";
|
|
3570
|
-
});
|
|
3571
|
-
});
|
|
3572
|
-
}
|
|
3573
|
-
function resolveImageBlocks(binDataMap, blocks, warnings) {
|
|
3574
|
-
const imageBlocks = [];
|
|
3575
|
-
collectImageBlocks2(blocks, imageBlocks);
|
|
3576
|
-
if (imageBlocks.length === 0) return [];
|
|
3577
|
-
const images = [];
|
|
3578
|
-
const renamed = /* @__PURE__ */ new Map();
|
|
3579
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
3580
|
-
let imageIndex = 0;
|
|
3581
|
-
for (const block of imageBlocks) {
|
|
3582
|
-
if (!block.text) continue;
|
|
3583
|
-
const storageId = parseInt(block.text, 10);
|
|
3584
|
-
if (isNaN(storageId)) continue;
|
|
3585
|
-
let img = resolved.get(storageId);
|
|
3586
|
-
if (img === void 0) {
|
|
3587
|
-
const bin = binDataMap.get(storageId);
|
|
3588
|
-
if (!bin) {
|
|
3589
|
-
warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
|
|
3590
|
-
resolved.set(storageId, null);
|
|
3591
|
-
} else {
|
|
3592
|
-
const mime = detectImageMime(bin.data);
|
|
3593
|
-
if (!mime) {
|
|
3594
|
-
warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
|
|
3595
|
-
resolved.set(storageId, null);
|
|
3596
|
-
} else {
|
|
3597
|
-
imageIndex++;
|
|
3598
|
-
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
3599
|
-
img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
|
|
3600
|
-
resolved.set(storageId, img);
|
|
3601
|
-
images.push({ filename: img.filename, data: img.data, mimeType: img.mime });
|
|
3602
|
-
renamed.set(storageId, img.filename);
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
img = resolved.get(storageId);
|
|
3606
|
-
}
|
|
3607
|
-
if (!img) {
|
|
3608
|
-
const bin = binDataMap.get(storageId);
|
|
3609
|
-
block.type = "paragraph";
|
|
3610
|
-
block.text = bin ? `[\uC774\uBBF8\uC9C0: ${bin.name}]` : `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
|
|
3611
|
-
continue;
|
|
3612
|
-
}
|
|
3613
|
-
block.text = img.filename;
|
|
3614
|
-
block.imageData = { data: img.data, mimeType: img.mime, filename: binDataMap.get(storageId).name };
|
|
3615
|
-
}
|
|
3616
|
-
resolveCellImageSentinels(blocks, renamed);
|
|
3617
|
-
return images;
|
|
3618
|
-
}
|
|
3619
|
-
function extractHwp5Images(fileIndex, blocks, warnings) {
|
|
3620
|
-
const binDataMap = /* @__PURE__ */ new Map();
|
|
3621
|
-
if (fileIndex) {
|
|
3622
|
-
for (const entry of fileIndex) {
|
|
3623
|
-
if (!entry?.name || !entry.content) continue;
|
|
3624
|
-
const match = entry.name.match(BIN_ENTRY_RE);
|
|
3625
|
-
if (!match) continue;
|
|
3626
|
-
const idx = parseInt(match[1], 16);
|
|
3627
|
-
const data = normalizeBinPayload(Buffer.from(entry.content));
|
|
3628
|
-
binDataMap.set(idx, { data, name: entry.name });
|
|
3629
|
-
}
|
|
3630
|
-
}
|
|
3631
|
-
if (binDataMap.size === 0) {
|
|
3632
|
-
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3633
|
-
return [];
|
|
3634
|
-
}
|
|
3635
|
-
return resolveImageBlocks(binDataMap, blocks, warnings);
|
|
3636
|
-
}
|
|
3637
|
-
function extractHwp5ImagesLenient(lcfb, blocks, warnings) {
|
|
3638
|
-
const binDataMap = /* @__PURE__ */ new Map();
|
|
3639
|
-
const binRe = /^BIN([0-9A-Fa-f]{4,8})(?:\.|$)/;
|
|
3640
|
-
for (const e of lcfb.entries()) {
|
|
3641
|
-
const match = e.name.match(binRe);
|
|
3642
|
-
if (!match) continue;
|
|
3643
|
-
const idx = parseInt(match[1], 16);
|
|
3644
|
-
const raw = lcfb.findStream(e.name);
|
|
3645
|
-
if (!raw) continue;
|
|
3646
|
-
binDataMap.set(idx, { data: normalizeBinPayload(raw), name: e.name });
|
|
3647
|
-
}
|
|
3648
|
-
if (binDataMap.size === 0) {
|
|
3649
|
-
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3650
|
-
return [];
|
|
3651
|
-
}
|
|
3652
|
-
return resolveImageBlocks(binDataMap, blocks, warnings);
|
|
3653
|
-
}
|
|
3654
|
-
|
|
3655
|
-
// src/image/transcode.ts
|
|
3656
|
-
import { deflateSync } from "zlib";
|
|
3657
|
-
var CRC_TABLE = (() => {
|
|
3658
|
-
const table2 = new Uint32Array(256);
|
|
3659
|
-
for (let n = 0; n < 256; n++) {
|
|
3660
|
-
let c = n;
|
|
3661
|
-
for (let k = 0; k < 8; k++) {
|
|
3662
|
-
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
3639
|
+
function formatEastAsianNumber(n, digits, units, zero) {
|
|
3640
|
+
if (n === 0) return zero;
|
|
3641
|
+
if (n < 0 || n > 99999) return String(n);
|
|
3642
|
+
let result = "";
|
|
3643
|
+
let num4 = n;
|
|
3644
|
+
let unit = 0;
|
|
3645
|
+
while (num4 > 0) {
|
|
3646
|
+
const d = num4 % 10;
|
|
3647
|
+
if (d > 0) {
|
|
3648
|
+
const digitStr = d === 1 && unit > 0 ? "" : digits[d];
|
|
3649
|
+
result = digitStr + units[unit] + result;
|
|
3663
3650
|
}
|
|
3664
|
-
|
|
3651
|
+
num4 = Math.floor(num4 / 10);
|
|
3652
|
+
unit++;
|
|
3665
3653
|
}
|
|
3666
|
-
return
|
|
3667
|
-
})();
|
|
3668
|
-
function crc32(bytes) {
|
|
3669
|
-
let c = 4294967295;
|
|
3670
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
3671
|
-
c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
|
|
3672
|
-
}
|
|
3673
|
-
return (c ^ 4294967295) >>> 0;
|
|
3674
|
-
}
|
|
3675
|
-
var BI_RGB = 0;
|
|
3676
|
-
var MAX_DIM = 32767;
|
|
3677
|
-
var MAX_PIXELS = 36e6;
|
|
3678
|
-
function bmpToPng(bmp) {
|
|
3679
|
-
if (bmp.length < 54) return null;
|
|
3680
|
-
if (bmp[0] !== 66 || bmp[1] !== 77) return null;
|
|
3681
|
-
const dv = new DataView(bmp.buffer, bmp.byteOffset, bmp.byteLength);
|
|
3682
|
-
const dataOffset = dv.getUint32(10, true);
|
|
3683
|
-
const headerSize = dv.getUint32(14, true);
|
|
3684
|
-
if (headerSize < 40) return null;
|
|
3685
|
-
const width = dv.getInt32(18, true);
|
|
3686
|
-
const rawHeight = dv.getInt32(22, true);
|
|
3687
|
-
const bitCount = dv.getUint16(28, true);
|
|
3688
|
-
const compression = dv.getUint32(30, true);
|
|
3689
|
-
if (compression !== BI_RGB) return null;
|
|
3690
|
-
if (bitCount !== 24 && bitCount !== 32) return null;
|
|
3691
|
-
if (width <= 0 || rawHeight === 0) return null;
|
|
3692
|
-
if (width > MAX_DIM || Math.abs(rawHeight) > MAX_DIM) return null;
|
|
3693
|
-
const topDown = rawHeight < 0;
|
|
3694
|
-
const height = Math.abs(rawHeight);
|
|
3695
|
-
if (width * height > MAX_PIXELS) return null;
|
|
3696
|
-
const bytesPerPixel = bitCount >> 3;
|
|
3697
|
-
const rowStride = width * bytesPerPixel + 3 & ~3;
|
|
3698
|
-
if (dataOffset + rowStride * height > bmp.length) return null;
|
|
3699
|
-
const rgba = new Uint8Array(width * height * 4);
|
|
3700
|
-
let anyAlpha = 0;
|
|
3701
|
-
for (let y = 0; y < height; y++) {
|
|
3702
|
-
const srcRow = topDown ? y : height - 1 - y;
|
|
3703
|
-
let src = dataOffset + srcRow * rowStride;
|
|
3704
|
-
let dst = y * width * 4;
|
|
3705
|
-
for (let x = 0; x < width; x++) {
|
|
3706
|
-
rgba[dst] = bmp[src + 2];
|
|
3707
|
-
rgba[dst + 1] = bmp[src + 1];
|
|
3708
|
-
rgba[dst + 2] = bmp[src];
|
|
3709
|
-
const a = bitCount === 32 ? bmp[src + 3] : 255;
|
|
3710
|
-
rgba[dst + 3] = a;
|
|
3711
|
-
anyAlpha |= a;
|
|
3712
|
-
src += bytesPerPixel;
|
|
3713
|
-
dst += 4;
|
|
3714
|
-
}
|
|
3715
|
-
}
|
|
3716
|
-
if (bitCount === 32 && anyAlpha === 0) {
|
|
3717
|
-
for (let i = 3; i < rgba.length; i += 4) rgba[i] = 255;
|
|
3718
|
-
}
|
|
3719
|
-
return encodePng(width, height, rgba);
|
|
3720
|
-
}
|
|
3721
|
-
var PNG_SIGNATURE = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10);
|
|
3722
|
-
function chunk(type, data) {
|
|
3723
|
-
const body = new Uint8Array(4 + data.length);
|
|
3724
|
-
body[0] = type.charCodeAt(0);
|
|
3725
|
-
body[1] = type.charCodeAt(1);
|
|
3726
|
-
body[2] = type.charCodeAt(2);
|
|
3727
|
-
body[3] = type.charCodeAt(3);
|
|
3728
|
-
body.set(data, 4);
|
|
3729
|
-
const out = new Uint8Array(8 + data.length + 4);
|
|
3730
|
-
const dv = new DataView(out.buffer);
|
|
3731
|
-
dv.setUint32(0, data.length, false);
|
|
3732
|
-
out.set(body, 4);
|
|
3733
|
-
dv.setUint32(8 + data.length, crc32(body), false);
|
|
3734
|
-
return out;
|
|
3654
|
+
return result;
|
|
3735
3655
|
}
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3656
|
+
var HANGUL_DIGITS = ["", "\uC77C", "\uC774", "\uC0BC", "\uC0AC", "\uC624", "\uC721", "\uCE60", "\uD314", "\uAD6C"];
|
|
3657
|
+
var HANGUL_UNITS = ["", "\uC2ED", "\uBC31", "\uCC9C", "\uB9CC"];
|
|
3658
|
+
var HANJA_DIGITS = ["", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D"];
|
|
3659
|
+
var HANJA_UNITS = ["", "\u5341", "\u767E", "\u5343", "\u842C"];
|
|
3660
|
+
function formatNumber(n, fmt) {
|
|
3661
|
+
switch (fmt) {
|
|
3662
|
+
case "circled":
|
|
3663
|
+
return fromTable(n, CIRCLED_DIGITS);
|
|
3664
|
+
case "romanUpper":
|
|
3665
|
+
return formatRoman(n, true);
|
|
3666
|
+
case "romanLower":
|
|
3667
|
+
return formatRoman(n, false);
|
|
3668
|
+
case "latinUpper":
|
|
3669
|
+
return formatLatin(n, true) || String(n);
|
|
3670
|
+
case "latinLower":
|
|
3671
|
+
return formatLatin(n, false) || String(n);
|
|
3672
|
+
case "ganada":
|
|
3673
|
+
return fromTable(n, GANADA);
|
|
3674
|
+
case "circledGanada":
|
|
3675
|
+
return fromTable(n, CIRCLED_GANADA);
|
|
3676
|
+
case "jamo":
|
|
3677
|
+
return fromTable(n, JAMO);
|
|
3678
|
+
case "circledJamo":
|
|
3679
|
+
return fromTable(n, CIRCLED_JAMO);
|
|
3680
|
+
case "hangulNum":
|
|
3681
|
+
return formatEastAsianNumber(n, HANGUL_DIGITS, HANGUL_UNITS, "\uC601");
|
|
3682
|
+
case "hanjaNum":
|
|
3683
|
+
return formatEastAsianNumber(n, HANJA_DIGITS, HANJA_UNITS, "\u96F6");
|
|
3684
|
+
default:
|
|
3685
|
+
return String(n);
|
|
3686
|
+
}
|
|
3764
3687
|
}
|
|
3765
|
-
function
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
const
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
const dataUri = `data:${mime};base64,${base64}`;
|
|
3783
|
-
const name = escapeRegExp(img.filename);
|
|
3784
|
-
const mdRe = new RegExp(`!\\[image\\]\\((?:images/)?${name}\\)`, "g");
|
|
3785
|
-
out = out.replace(mdRe, () => ``);
|
|
3786
|
-
const imgTagRe = new RegExp(`(<img\\b[^>]*\\bsrc=")(?:images/)?${name}(")`, "g");
|
|
3787
|
-
out = out.replace(imgTagRe, (_m, pre, post) => `${pre}${dataUri}${post}`);
|
|
3688
|
+
function expandNumberingFormat(formatStr, counters, numbering) {
|
|
3689
|
+
let result = "";
|
|
3690
|
+
let i = 0;
|
|
3691
|
+
while (i < formatStr.length) {
|
|
3692
|
+
const ch = formatStr[i];
|
|
3693
|
+
if (ch === "^" && i + 1 < formatStr.length && formatStr[i + 1] >= "1" && formatStr[i + 1] <= "7") {
|
|
3694
|
+
const levelRef = formatStr.charCodeAt(i + 1) - 48;
|
|
3695
|
+
const idx = levelRef - 1;
|
|
3696
|
+
const counterVal = counters[idx] ?? 0;
|
|
3697
|
+
const start = numbering.startNumbers[idx] ?? 1;
|
|
3698
|
+
const num4 = counterVal > 0 ? start - 1 + counterVal : start;
|
|
3699
|
+
result += formatNumber(num4, headFormatToNumFmt(numbering.numberFormats[idx] ?? 0));
|
|
3700
|
+
i += 2;
|
|
3701
|
+
continue;
|
|
3702
|
+
}
|
|
3703
|
+
result += ch;
|
|
3704
|
+
i++;
|
|
3788
3705
|
}
|
|
3789
|
-
return
|
|
3706
|
+
return result;
|
|
3790
3707
|
}
|
|
3791
3708
|
|
|
3792
3709
|
// src/hwp5/aes.ts
|
|
@@ -4816,6 +4733,7 @@ function parseHwp5Document(buffer, options) {
|
|
|
4816
4733
|
const totalTarget = pageFilter ? pageFilter.size : sections.length;
|
|
4817
4734
|
const bodyBlocks = [];
|
|
4818
4735
|
const doc = createHwp5DocState();
|
|
4736
|
+
doc.keepTrailingEmptyCols = options?.keepTrailingEmptyCols;
|
|
4819
4737
|
let totalDecompressed = 0;
|
|
4820
4738
|
let parsedSections = 0;
|
|
4821
4739
|
for (let si = 0; si < sections.length; si++) {
|
|
@@ -4836,7 +4754,7 @@ function parseHwp5Document(buffer, options) {
|
|
|
4836
4754
|
}
|
|
4837
4755
|
}
|
|
4838
4756
|
const blocks = [...doc.headerBlocks, ...bodyBlocks, ...doc.footerBlocks];
|
|
4839
|
-
const images = cfb ? extractHwp5Images(cfb.FileIndex, blocks, warnings) : extractHwp5ImagesLenient(lenientCfb, blocks, warnings);
|
|
4757
|
+
const images = cfb ? extractHwp5Images(cfb.FileIndex, blocks, warnings, !pageFilter) : extractHwp5ImagesLenient(lenientCfb, blocks, warnings, !pageFilter);
|
|
4840
4758
|
let flatBlocks = flattenLayoutTables(blocks);
|
|
4841
4759
|
if (options?.dedupeRunningHeaders) {
|
|
4842
4760
|
const deduped = dedupeRunningHeaders(flatBlocks);
|
|
@@ -5417,7 +5335,7 @@ function parseTableControl(ctrl, records, ctx) {
|
|
|
5417
5335
|
return table3;
|
|
5418
5336
|
}
|
|
5419
5337
|
const cellRows = arrangeCells(rows, cols, cells);
|
|
5420
|
-
const table2 = buildTable(cellRows);
|
|
5338
|
+
const table2 = buildTable(cellRows, { keepAnchoredEmptyCols: ctx.doc.keepTrailingEmptyCols });
|
|
5421
5339
|
if (caption && table2.rows > 0) table2.caption = caption;
|
|
5422
5340
|
return table2.rows > 0 ? table2 : null;
|
|
5423
5341
|
}
|
|
@@ -17611,6 +17529,7 @@ function readHeader(reader) {
|
|
|
17611
17529
|
}
|
|
17612
17530
|
|
|
17613
17531
|
// src/hwp3/parser.ts
|
|
17532
|
+
var MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
|
|
17614
17533
|
var PARA_SHAPE_SIZE = 187;
|
|
17615
17534
|
var LINE_INFO_SIZE = 14;
|
|
17616
17535
|
var INLINE_CHAR_SHAPE_SIZE = 31;
|
|
@@ -17646,8 +17565,11 @@ function parseHwp3Document(buffer, _options) {
|
|
|
17646
17565
|
const warnings = [];
|
|
17647
17566
|
if (header.compressed !== 0) {
|
|
17648
17567
|
try {
|
|
17649
|
-
body = inflateRawSync3(tail);
|
|
17568
|
+
body = inflateRawSync3(tail, { maxOutputLength: MAX_DECOMPRESS_SIZE3 });
|
|
17650
17569
|
} catch (err) {
|
|
17570
|
+
if (err?.code === "ERR_BUFFER_TOO_LARGE") {
|
|
17571
|
+
throw new Error(`HWP3 \uC555\uCD95 \uD574\uC81C \uACB0\uACFC\uAC00 \uCD5C\uB300 \uD5C8\uC6A9 \uD06C\uAE30(${MAX_DECOMPRESS_SIZE3 / 1024 / 1024}MB)\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4`);
|
|
17572
|
+
}
|
|
17651
17573
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
17652
17574
|
throw new Error(`HWP3 \uC555\uCD95 \uD574\uC81C \uC2E4\uD328: ${msg2}`);
|
|
17653
17575
|
}
|
|
@@ -17844,7 +17766,7 @@ function isDistributionSentinel(markdown) {
|
|
|
17844
17766
|
import JSZip4 from "jszip";
|
|
17845
17767
|
import { DOMParser as DOMParser2 } from "@xmldom/xmldom";
|
|
17846
17768
|
var MAX_SHEETS = 100;
|
|
17847
|
-
var
|
|
17769
|
+
var MAX_DECOMPRESS_SIZE4 = 100 * 1024 * 1024;
|
|
17848
17770
|
var MAX_ROWS2 = 1e4;
|
|
17849
17771
|
var MAX_COLS2 = 200;
|
|
17850
17772
|
function cleanNumericValue(raw) {
|
|
@@ -17884,16 +17806,87 @@ function getTextContent(el) {
|
|
|
17884
17806
|
function parseXml(text) {
|
|
17885
17807
|
return new DOMParser2().parseFromString(stripDtd(text), "text/xml");
|
|
17886
17808
|
}
|
|
17809
|
+
function collectRichText(root) {
|
|
17810
|
+
let out = "";
|
|
17811
|
+
const walk = (node) => {
|
|
17812
|
+
const children = node.childNodes;
|
|
17813
|
+
for (let i = 0; i < children.length; i++) {
|
|
17814
|
+
if (children[i].nodeType !== 1) continue;
|
|
17815
|
+
const el = children[i];
|
|
17816
|
+
const local = el.localName || el.tagName?.replace(/^[^:]+:/, "") || "";
|
|
17817
|
+
if (local === "rPh") continue;
|
|
17818
|
+
if (local === "t") out += el.textContent ?? "";
|
|
17819
|
+
else walk(el);
|
|
17820
|
+
}
|
|
17821
|
+
};
|
|
17822
|
+
walk(root);
|
|
17823
|
+
return out;
|
|
17824
|
+
}
|
|
17887
17825
|
function parseSharedStrings(xml) {
|
|
17888
17826
|
const doc = parseXml(xml);
|
|
17889
17827
|
const strings = [];
|
|
17890
17828
|
const siList = getElements(doc.documentElement, "si");
|
|
17891
17829
|
for (const si of siList) {
|
|
17892
|
-
|
|
17893
|
-
strings.push(tElements.map((t) => t.textContent ?? "").join(""));
|
|
17830
|
+
strings.push(collectRichText(si));
|
|
17894
17831
|
}
|
|
17895
17832
|
return strings;
|
|
17896
17833
|
}
|
|
17834
|
+
var BUILTIN_DATE_FMT = /* @__PURE__ */ new Map([
|
|
17835
|
+
[14, "date"],
|
|
17836
|
+
[15, "date"],
|
|
17837
|
+
[16, "date"],
|
|
17838
|
+
[17, "date"],
|
|
17839
|
+
[18, "datetime"],
|
|
17840
|
+
[19, "datetime"],
|
|
17841
|
+
[20, "datetime"],
|
|
17842
|
+
[21, "datetime"],
|
|
17843
|
+
[22, "datetime"],
|
|
17844
|
+
[45, "datetime"],
|
|
17845
|
+
[46, "datetime"],
|
|
17846
|
+
[47, "datetime"]
|
|
17847
|
+
]);
|
|
17848
|
+
function classifyDateFormat(code) {
|
|
17849
|
+
const stripped = code.replace(/"[^"]*"/g, "").replace(/\[[^\]]*\]/g, "").replace(/\\./g, "");
|
|
17850
|
+
if (!/[ymdh]/i.test(stripped)) return null;
|
|
17851
|
+
return /[hs]/i.test(stripped) ? "datetime" : "date";
|
|
17852
|
+
}
|
|
17853
|
+
function dateKindOfFmt(fmtId, customFormats) {
|
|
17854
|
+
const builtin = BUILTIN_DATE_FMT.get(fmtId);
|
|
17855
|
+
if (builtin) return builtin;
|
|
17856
|
+
const code = customFormats.get(fmtId);
|
|
17857
|
+
return code !== void 0 ? classifyDateFormat(code) : null;
|
|
17858
|
+
}
|
|
17859
|
+
function dateSerialToIso(serial, date1904, kind) {
|
|
17860
|
+
if (!isFinite(serial) || serial < 0) return null;
|
|
17861
|
+
let days = serial;
|
|
17862
|
+
if (date1904) days += 1462;
|
|
17863
|
+
else if (days < 60) days += 1;
|
|
17864
|
+
const ms = Math.round((days - 25569) * 864e5);
|
|
17865
|
+
const d = new Date(ms);
|
|
17866
|
+
if (isNaN(d.getTime()) || d.getUTCFullYear() > 9999) return null;
|
|
17867
|
+
const iso = d.toISOString();
|
|
17868
|
+
return kind === "datetime" ? iso.slice(0, 19) : iso.slice(0, 10);
|
|
17869
|
+
}
|
|
17870
|
+
function parseStyleDateXfs(xml) {
|
|
17871
|
+
const doc = parseXml(xml);
|
|
17872
|
+
const customFormats = /* @__PURE__ */ new Map();
|
|
17873
|
+
for (const el of getElements(doc.documentElement, "numFmt")) {
|
|
17874
|
+
const id = parseInt(el.getAttribute("numFmtId") ?? "", 10);
|
|
17875
|
+
if (isNaN(id)) continue;
|
|
17876
|
+
customFormats.set(id, el.getAttribute("formatCode") ?? "");
|
|
17877
|
+
}
|
|
17878
|
+
const dateXfs = /* @__PURE__ */ new Map();
|
|
17879
|
+
const cellXfsEls = getElements(doc.documentElement, "cellXfs");
|
|
17880
|
+
if (cellXfsEls.length === 0) return dateXfs;
|
|
17881
|
+
const xfs = getElements(cellXfsEls[0], "xf");
|
|
17882
|
+
for (let i = 0; i < xfs.length; i++) {
|
|
17883
|
+
const fmtId = parseInt(xfs[i].getAttribute("numFmtId") ?? "", 10);
|
|
17884
|
+
if (isNaN(fmtId)) continue;
|
|
17885
|
+
const kind = dateKindOfFmt(fmtId, customFormats);
|
|
17886
|
+
if (kind) dateXfs.set(i, kind);
|
|
17887
|
+
}
|
|
17888
|
+
return dateXfs;
|
|
17889
|
+
}
|
|
17897
17890
|
function parseWorkbook(xml) {
|
|
17898
17891
|
const doc = parseXml(xml);
|
|
17899
17892
|
const sheets = [];
|
|
@@ -17905,7 +17898,9 @@ function parseWorkbook(xml) {
|
|
|
17905
17898
|
rId: el.getAttribute("r:id") ?? ""
|
|
17906
17899
|
});
|
|
17907
17900
|
}
|
|
17908
|
-
|
|
17901
|
+
const prEls = getElements(doc.documentElement, "workbookPr");
|
|
17902
|
+
const d1904 = prEls.length > 0 ? prEls[0].getAttribute("date1904") : null;
|
|
17903
|
+
return { sheets, date1904: d1904 === "1" || d1904 === "true" };
|
|
17909
17904
|
}
|
|
17910
17905
|
function parseRels(xml) {
|
|
17911
17906
|
const doc = parseXml(xml);
|
|
@@ -17918,21 +17913,25 @@ function parseRels(xml) {
|
|
|
17918
17913
|
}
|
|
17919
17914
|
return map;
|
|
17920
17915
|
}
|
|
17921
|
-
function parseWorksheet(xml, sharedStrings) {
|
|
17916
|
+
function parseWorksheet(xml, sharedStrings, dateXfs, date1904) {
|
|
17922
17917
|
const doc = parseXml(xml);
|
|
17923
17918
|
const grid = [];
|
|
17924
17919
|
let maxRow = 0;
|
|
17925
17920
|
let maxCol = 0;
|
|
17926
17921
|
const rows = getElements(doc.documentElement, "row");
|
|
17922
|
+
let prevRow = -1;
|
|
17927
17923
|
for (const rowEl of rows) {
|
|
17928
|
-
const
|
|
17924
|
+
const rAttr = rowEl.getAttribute("r");
|
|
17925
|
+
const rowNum = rAttr !== null ? parseInt(rAttr, 10) - 1 : prevRow + 1;
|
|
17929
17926
|
if (rowNum < 0 || rowNum >= MAX_ROWS2) continue;
|
|
17927
|
+
if (Number.isFinite(rowNum)) prevRow = rowNum;
|
|
17930
17928
|
const cells = getElements(rowEl, "c");
|
|
17929
|
+
let prevCol = -1;
|
|
17931
17930
|
for (const cellEl of cells) {
|
|
17932
17931
|
const ref = cellEl.getAttribute("r");
|
|
17933
|
-
|
|
17934
|
-
|
|
17935
|
-
|
|
17932
|
+
const pos = ref !== null ? parseCellRef(ref) : { col: prevCol + 1, row: rowNum };
|
|
17933
|
+
if (!pos || !Number.isFinite(pos.row) || pos.row < 0 || pos.row >= MAX_ROWS2 || pos.col >= MAX_COLS2) continue;
|
|
17934
|
+
prevCol = pos.col;
|
|
17936
17935
|
const type = cellEl.getAttribute("t");
|
|
17937
17936
|
const vElements = getElements(cellEl, "v");
|
|
17938
17937
|
const fElements = getElements(cellEl, "f");
|
|
@@ -17946,12 +17945,19 @@ function parseWorksheet(xml, sharedStrings) {
|
|
|
17946
17945
|
value = raw === "1" ? "TRUE" : "FALSE";
|
|
17947
17946
|
} else {
|
|
17948
17947
|
value = cleanNumericValue(raw);
|
|
17948
|
+
if (type === null || type === "n") {
|
|
17949
|
+
const sAttr = cellEl.getAttribute("s");
|
|
17950
|
+
const kind = sAttr !== null ? dateXfs.get(parseInt(sAttr, 10)) : void 0;
|
|
17951
|
+
if (kind) {
|
|
17952
|
+
const iso = dateSerialToIso(parseFloat(raw), date1904, kind);
|
|
17953
|
+
if (iso) value = iso;
|
|
17954
|
+
}
|
|
17955
|
+
}
|
|
17949
17956
|
}
|
|
17950
17957
|
} else if (type === "inlineStr") {
|
|
17951
17958
|
const isEl = getElements(cellEl, "is");
|
|
17952
17959
|
if (isEl.length > 0) {
|
|
17953
|
-
|
|
17954
|
-
value = tElements.map((t) => t.textContent ?? "").join("");
|
|
17960
|
+
value = collectRichText(isEl[0]);
|
|
17955
17961
|
}
|
|
17956
17962
|
}
|
|
17957
17963
|
if (!value && fElements.length > 0) {
|
|
@@ -17970,7 +17976,14 @@ function parseWorksheet(xml, sharedStrings) {
|
|
|
17970
17976
|
const ref = el.getAttribute("ref");
|
|
17971
17977
|
if (!ref) continue;
|
|
17972
17978
|
const m = parseMergeRef(ref);
|
|
17973
|
-
if (m)
|
|
17979
|
+
if (m) {
|
|
17980
|
+
merges.push({
|
|
17981
|
+
startCol: Math.min(m.startCol, MAX_COLS2 - 1),
|
|
17982
|
+
startRow: Math.min(m.startRow, MAX_ROWS2 - 1),
|
|
17983
|
+
endCol: Math.min(m.endCol, MAX_COLS2 - 1),
|
|
17984
|
+
endRow: Math.min(m.endRow, MAX_ROWS2 - 1)
|
|
17985
|
+
});
|
|
17986
|
+
}
|
|
17974
17987
|
}
|
|
17975
17988
|
return { grid, merges, maxRow, maxCol };
|
|
17976
17989
|
}
|
|
@@ -18034,7 +18047,7 @@ function sheetToBlocks(sheetName, grid, merges, maxRow, maxCol, sheetIndex) {
|
|
|
18034
18047
|
return blocks;
|
|
18035
18048
|
}
|
|
18036
18049
|
async function parseXlsxDocument(buffer, options) {
|
|
18037
|
-
precheckZipSize(buffer,
|
|
18050
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE4);
|
|
18038
18051
|
const zip = await JSZip4.loadAsync(buffer);
|
|
18039
18052
|
const warnings = [];
|
|
18040
18053
|
const workbookFile = zip.file("xl/workbook.xml");
|
|
@@ -18046,10 +18059,18 @@ async function parseXlsxDocument(buffer, options) {
|
|
|
18046
18059
|
if (ssFile) {
|
|
18047
18060
|
sharedStrings = parseSharedStrings(await ssFile.async("text"));
|
|
18048
18061
|
}
|
|
18049
|
-
const sheets = parseWorkbook(await workbookFile.async("text"));
|
|
18062
|
+
const { sheets, date1904 } = parseWorkbook(await workbookFile.async("text"));
|
|
18050
18063
|
if (sheets.length === 0) {
|
|
18051
18064
|
throw new KordocError("XLSX \uD30C\uC77C\uC5D0 \uC2DC\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
18052
18065
|
}
|
|
18066
|
+
let dateXfs = /* @__PURE__ */ new Map();
|
|
18067
|
+
const stylesFile = zip.file("xl/styles.xml");
|
|
18068
|
+
if (stylesFile) {
|
|
18069
|
+
try {
|
|
18070
|
+
dateXfs = parseStyleDateXfs(await stylesFile.async("text"));
|
|
18071
|
+
} catch {
|
|
18072
|
+
}
|
|
18073
|
+
}
|
|
18053
18074
|
let relsMap = /* @__PURE__ */ new Map();
|
|
18054
18075
|
const relsFile = zip.file("xl/_rels/workbook.xml.rels");
|
|
18055
18076
|
if (relsFile) {
|
|
@@ -18057,7 +18078,7 @@ async function parseXlsxDocument(buffer, options) {
|
|
|
18057
18078
|
}
|
|
18058
18079
|
let pageFilter = null;
|
|
18059
18080
|
if (options?.pages) {
|
|
18060
|
-
const { parsePageRange: parsePageRange2 } = await import("./page-range-
|
|
18081
|
+
const { parsePageRange: parsePageRange2 } = await import("./page-range-LNMXJU6W.js");
|
|
18061
18082
|
pageFilter = parsePageRange2(options.pages, sheets.length);
|
|
18062
18083
|
}
|
|
18063
18084
|
const blocks = [];
|
|
@@ -18087,7 +18108,7 @@ async function parseXlsxDocument(buffer, options) {
|
|
|
18087
18108
|
}
|
|
18088
18109
|
try {
|
|
18089
18110
|
const sheetXml = await sheetFile.async("text");
|
|
18090
|
-
const { grid, merges, maxRow, maxCol } = parseWorksheet(sheetXml, sharedStrings);
|
|
18111
|
+
const { grid, merges, maxRow, maxCol } = parseWorksheet(sheetXml, sharedStrings, dateXfs, date1904);
|
|
18091
18112
|
const sheetBlocks = sheetToBlocks(sheet.name, grid, merges, maxRow, maxCol, i);
|
|
18092
18113
|
blocks.push(...sheetBlocks);
|
|
18093
18114
|
} catch (err) {
|
|
@@ -18131,7 +18152,10 @@ var OP_CONTINUE = 60;
|
|
|
18131
18152
|
var OP_BOUNDSHEET8 = 133;
|
|
18132
18153
|
var OP_SST = 252;
|
|
18133
18154
|
var OP_CODEPAGE = 66;
|
|
18155
|
+
var OP_DATE1904 = 34;
|
|
18134
18156
|
var OP_FILEPASS = 47;
|
|
18157
|
+
var OP_FORMAT = 1054;
|
|
18158
|
+
var OP_XF = 224;
|
|
18135
18159
|
var OP_NUMBER = 515;
|
|
18136
18160
|
var OP_RK = 638;
|
|
18137
18161
|
var OP_MULRK = 189;
|
|
@@ -18143,6 +18167,8 @@ var OP_BOOLERR = 517;
|
|
|
18143
18167
|
var OP_BLANK = 513;
|
|
18144
18168
|
var OP_MULBLANK = 190;
|
|
18145
18169
|
var OP_MERGECELLS = 229;
|
|
18170
|
+
var OP_SHRFMLA = 1212;
|
|
18171
|
+
var OP_ARRAY = 545;
|
|
18146
18172
|
var DT_GLOBALS = 5;
|
|
18147
18173
|
var DT_WORKSHEET = 16;
|
|
18148
18174
|
var MAX_RECORDS2 = 1e6;
|
|
@@ -18238,7 +18264,7 @@ function decodeUtf16Le(buf) {
|
|
|
18238
18264
|
}
|
|
18239
18265
|
|
|
18240
18266
|
// src/xls/sst.ts
|
|
18241
|
-
function parseString(buf, offset, segments) {
|
|
18267
|
+
function parseString(buf, offset, segments, segCursor) {
|
|
18242
18268
|
if (offset + 3 > buf.length) return null;
|
|
18243
18269
|
const cch = buf.readUInt16LE(offset);
|
|
18244
18270
|
let flags = buf.readUInt8(offset + 2);
|
|
@@ -18261,7 +18287,15 @@ function parseString(buf, offset, segments) {
|
|
|
18261
18287
|
const charBytes = [];
|
|
18262
18288
|
let charsRead = 0;
|
|
18263
18289
|
while (charsRead < cch) {
|
|
18264
|
-
|
|
18290
|
+
while (segCursor.idx < segments.length && segments[segCursor.idx] < off) segCursor.idx++;
|
|
18291
|
+
if (segCursor.idx < segments.length && segments[segCursor.idx] === off) {
|
|
18292
|
+
if (off >= buf.length) return null;
|
|
18293
|
+
flags = buf.readUInt8(off);
|
|
18294
|
+
highByte = (flags & 1) !== 0;
|
|
18295
|
+
off += 1;
|
|
18296
|
+
segCursor.idx++;
|
|
18297
|
+
}
|
|
18298
|
+
const nextBoundary = segCursor.idx < segments.length ? segments[segCursor.idx] : buf.length;
|
|
18265
18299
|
const remainChars = cch - charsRead;
|
|
18266
18300
|
const bytesPerChar = highByte ? 2 : 1;
|
|
18267
18301
|
const bytesAvail = nextBoundary - off;
|
|
@@ -18272,12 +18306,10 @@ function parseString(buf, offset, segments) {
|
|
|
18272
18306
|
charBytes.push(highByte ? slice : padToUtf16(slice));
|
|
18273
18307
|
off += bytesToRead;
|
|
18274
18308
|
charsRead += charsInThisRun;
|
|
18275
|
-
}
|
|
18276
|
-
|
|
18277
|
-
|
|
18278
|
-
|
|
18279
|
-
highByte = (flags & 1) !== 0;
|
|
18280
|
-
off += 1;
|
|
18309
|
+
} else if (nextBoundary < buf.length) {
|
|
18310
|
+
off = nextBoundary;
|
|
18311
|
+
} else {
|
|
18312
|
+
return null;
|
|
18281
18313
|
}
|
|
18282
18314
|
}
|
|
18283
18315
|
const text = decodeUtf16Le(Buffer.concat(charBytes));
|
|
@@ -18302,8 +18334,9 @@ function decodeSST(records) {
|
|
|
18302
18334
|
const cstUnique = combined.readUInt32LE(4);
|
|
18303
18335
|
const strings = [];
|
|
18304
18336
|
let off = 8;
|
|
18337
|
+
const segCursor = { idx: 0 };
|
|
18305
18338
|
for (let i = 0; i < cstUnique && off < combined.length; i++) {
|
|
18306
|
-
const r = parseString(combined, off, segments);
|
|
18339
|
+
const r = parseString(combined, off, segments, segCursor);
|
|
18307
18340
|
if (!r) break;
|
|
18308
18341
|
strings.push(r.text);
|
|
18309
18342
|
off += r.consumed;
|
|
@@ -18378,7 +18411,7 @@ function decodeFormulaStringRecord(data) {
|
|
|
18378
18411
|
return decodeUtf16Le(padded);
|
|
18379
18412
|
}
|
|
18380
18413
|
}
|
|
18381
|
-
function extractSheetCells(records, bofIndex, sst) {
|
|
18414
|
+
function extractSheetCells(records, bofIndex, sst, convertNum) {
|
|
18382
18415
|
const cells = [];
|
|
18383
18416
|
const merges = [];
|
|
18384
18417
|
const bofOffset = records[bofIndex].offset;
|
|
@@ -18396,14 +18429,16 @@ function extractSheetCells(records, bofIndex, sst) {
|
|
|
18396
18429
|
case OP_NUMBER: {
|
|
18397
18430
|
const h = readCellHeader(rec.data);
|
|
18398
18431
|
if (h && rec.data.length >= 14) {
|
|
18399
|
-
|
|
18432
|
+
const n = rec.data.readDoubleLE(6);
|
|
18433
|
+
cells.push({ row: h.row, col: h.col, value: convertNum ? convertNum(n, h.ixfe) : n });
|
|
18400
18434
|
}
|
|
18401
18435
|
break;
|
|
18402
18436
|
}
|
|
18403
18437
|
case OP_RK: {
|
|
18404
18438
|
const h = readCellHeader(rec.data);
|
|
18405
18439
|
if (h && rec.data.length >= 10) {
|
|
18406
|
-
|
|
18440
|
+
const n = decodeRk(rec.data.readInt32LE(6));
|
|
18441
|
+
cells.push({ row: h.row, col: h.col, value: convertNum ? convertNum(n, h.ixfe) : n });
|
|
18407
18442
|
}
|
|
18408
18443
|
break;
|
|
18409
18444
|
}
|
|
@@ -18411,7 +18446,7 @@ function extractSheetCells(records, bofIndex, sst) {
|
|
|
18411
18446
|
const m = decodeMulRk(rec.data);
|
|
18412
18447
|
if (m) {
|
|
18413
18448
|
for (const c of m.cells) {
|
|
18414
|
-
cells.push({ row: m.row, col: c.col, value: c.value });
|
|
18449
|
+
cells.push({ row: m.row, col: c.col, value: convertNum ? convertNum(c.value, c.ixfe) : c.value });
|
|
18415
18450
|
}
|
|
18416
18451
|
}
|
|
18417
18452
|
break;
|
|
@@ -18436,19 +18471,26 @@ function extractSheetCells(records, bofIndex, sst) {
|
|
|
18436
18471
|
if (h && rec.data.length >= 14) {
|
|
18437
18472
|
const result = decodeFormulaResult(rec.data.subarray(6, 14));
|
|
18438
18473
|
if (result.kind === "stringRef") {
|
|
18439
|
-
|
|
18474
|
+
let j = i + 1;
|
|
18475
|
+
while (j < records.length && (records[j].opcode === OP_SHRFMLA || records[j].opcode === OP_ARRAY)) j++;
|
|
18476
|
+
const next = records[j];
|
|
18440
18477
|
if (next && next.opcode === OP_STRING) {
|
|
18441
18478
|
cells.push({
|
|
18442
18479
|
row: h.row,
|
|
18443
18480
|
col: h.col,
|
|
18444
18481
|
value: decodeFormulaStringRecord(next.data)
|
|
18445
18482
|
});
|
|
18446
|
-
i
|
|
18483
|
+
i = j;
|
|
18447
18484
|
} else {
|
|
18448
18485
|
cells.push({ row: h.row, col: h.col, value: "" });
|
|
18449
18486
|
}
|
|
18450
18487
|
} else {
|
|
18451
|
-
|
|
18488
|
+
const v = result.value;
|
|
18489
|
+
cells.push({
|
|
18490
|
+
row: h.row,
|
|
18491
|
+
col: h.col,
|
|
18492
|
+
value: convertNum && typeof v === "number" ? convertNum(v, h.ixfe) : v
|
|
18493
|
+
});
|
|
18452
18494
|
}
|
|
18453
18495
|
}
|
|
18454
18496
|
break;
|
|
@@ -18498,7 +18540,7 @@ function extractSheetCells(records, bofIndex, sst) {
|
|
|
18498
18540
|
|
|
18499
18541
|
// src/xls/parser.ts
|
|
18500
18542
|
var MAX_SHEETS2 = 100;
|
|
18501
|
-
var MAX_ROWS3 =
|
|
18543
|
+
var MAX_ROWS3 = 65536;
|
|
18502
18544
|
var MAX_COLS3 = 1e3;
|
|
18503
18545
|
function decodeBoundSheet(data) {
|
|
18504
18546
|
if (data.length < 8) return null;
|
|
@@ -18521,10 +18563,33 @@ function decodeBoundSheet(data) {
|
|
|
18521
18563
|
}
|
|
18522
18564
|
return { name, lbPlyPos, dt };
|
|
18523
18565
|
}
|
|
18566
|
+
function decodeFormatRecord(data) {
|
|
18567
|
+
if (data.length < 5) return null;
|
|
18568
|
+
const ifmt = data.readUInt16LE(0);
|
|
18569
|
+
const cch = data.readUInt16LE(2);
|
|
18570
|
+
const flags = data.readUInt8(4);
|
|
18571
|
+
const highByte = (flags & 1) !== 0;
|
|
18572
|
+
const start = 5;
|
|
18573
|
+
let code;
|
|
18574
|
+
if (highByte) {
|
|
18575
|
+
const end = Math.min(start + cch * 2, data.length);
|
|
18576
|
+
code = decodeUtf16Le(data.subarray(start, end));
|
|
18577
|
+
} else {
|
|
18578
|
+
const end = Math.min(start + cch, data.length);
|
|
18579
|
+
const slice = data.subarray(start, end);
|
|
18580
|
+
const padded = Buffer.alloc(slice.length * 2);
|
|
18581
|
+
for (let i = 0; i < slice.length; i++) padded[i * 2] = slice[i];
|
|
18582
|
+
code = decodeUtf16Le(padded);
|
|
18583
|
+
}
|
|
18584
|
+
return { ifmt, code };
|
|
18585
|
+
}
|
|
18524
18586
|
function processGlobals(records) {
|
|
18525
18587
|
const sheets = [];
|
|
18526
18588
|
let codePage = 1200;
|
|
18527
18589
|
let encrypted = false;
|
|
18590
|
+
let date1904 = false;
|
|
18591
|
+
const customFormats = /* @__PURE__ */ new Map();
|
|
18592
|
+
const xfFmtIds = [];
|
|
18528
18593
|
const firstBof = records[0];
|
|
18529
18594
|
if (!firstBof || firstBof.opcode !== OP_BOF) {
|
|
18530
18595
|
throw new KordocError("XLS: \uCCAB \uB808\uCF54\uB4DC\uAC00 BOF\uAC00 \uC544\uB2D8");
|
|
@@ -18547,21 +18612,41 @@ function processGlobals(records) {
|
|
|
18547
18612
|
codePage = r.data.readUInt16LE(0);
|
|
18548
18613
|
} else if (r.opcode === OP_FILEPASS) {
|
|
18549
18614
|
encrypted = true;
|
|
18615
|
+
} else if (r.opcode === OP_DATE1904 && r.data.length >= 2) {
|
|
18616
|
+
date1904 = r.data.readUInt16LE(0) === 1;
|
|
18617
|
+
} else if (r.opcode === OP_FORMAT) {
|
|
18618
|
+
const f = decodeFormatRecord(r.data);
|
|
18619
|
+
if (f) customFormats.set(f.ifmt, f.code);
|
|
18620
|
+
} else if (r.opcode === OP_XF && r.data.length >= 4) {
|
|
18621
|
+
xfFmtIds.push(r.data.readUInt16LE(2));
|
|
18550
18622
|
}
|
|
18551
18623
|
i++;
|
|
18552
18624
|
}
|
|
18625
|
+
const dateXfs = /* @__PURE__ */ new Map();
|
|
18626
|
+
for (let k = 0; k < xfFmtIds.length; k++) {
|
|
18627
|
+
const kind = dateKindOfFmt(xfFmtIds[k], customFormats);
|
|
18628
|
+
if (kind) dateXfs.set(k, kind);
|
|
18629
|
+
}
|
|
18553
18630
|
const globalsRecords = records.slice(0, i);
|
|
18554
18631
|
const sst = decodeSST(globalsRecords);
|
|
18555
|
-
return { sheets, sst, codePage, encrypted, endIndex: i };
|
|
18632
|
+
return { sheets, sst, codePage, encrypted, dateXfs, date1904, endIndex: i };
|
|
18556
18633
|
}
|
|
18557
18634
|
function findSheetBofIndex(records, lbPlyPos) {
|
|
18558
18635
|
const exact = records.findIndex(
|
|
18559
18636
|
(r) => r.opcode === OP_BOF && r.offset === lbPlyPos
|
|
18560
18637
|
);
|
|
18561
18638
|
if (exact >= 0) return exact;
|
|
18562
|
-
|
|
18563
|
-
|
|
18564
|
-
|
|
18639
|
+
let best = -1;
|
|
18640
|
+
let bestOffset = Infinity;
|
|
18641
|
+
for (let idx = 1; idx < records.length; idx++) {
|
|
18642
|
+
const r = records[idx];
|
|
18643
|
+
if (r.opcode !== OP_BOF) continue;
|
|
18644
|
+
if (r.offset >= lbPlyPos && r.offset < bestOffset) {
|
|
18645
|
+
best = idx;
|
|
18646
|
+
bestOffset = r.offset;
|
|
18647
|
+
}
|
|
18648
|
+
}
|
|
18649
|
+
return best;
|
|
18565
18650
|
}
|
|
18566
18651
|
function cellValueToText(v) {
|
|
18567
18652
|
if (v === null || v === void 0) return "";
|
|
@@ -18692,10 +18777,18 @@ async function parseXlsDocument(buffer, options) {
|
|
|
18692
18777
|
]
|
|
18693
18778
|
};
|
|
18694
18779
|
}
|
|
18780
|
+
const convertNum = globals.dateXfs.size > 0 ? (n, ixfe) => {
|
|
18781
|
+
const kind = globals.dateXfs.get(ixfe);
|
|
18782
|
+
if (kind) {
|
|
18783
|
+
const iso = dateSerialToIso(n, globals.date1904, kind);
|
|
18784
|
+
if (iso) return iso;
|
|
18785
|
+
}
|
|
18786
|
+
return n;
|
|
18787
|
+
} : void 0;
|
|
18695
18788
|
const totalSheets = Math.min(globals.sheets.length, MAX_SHEETS2);
|
|
18696
18789
|
let pageFilter = null;
|
|
18697
18790
|
if (options?.pages) {
|
|
18698
|
-
const { parsePageRange: parsePageRange2 } = await import("./page-range-
|
|
18791
|
+
const { parsePageRange: parsePageRange2 } = await import("./page-range-LNMXJU6W.js");
|
|
18699
18792
|
pageFilter = parsePageRange2(options.pages, totalSheets);
|
|
18700
18793
|
}
|
|
18701
18794
|
const allBlocks = [];
|
|
@@ -18718,7 +18811,7 @@ async function parseXlsDocument(buffer, options) {
|
|
|
18718
18811
|
continue;
|
|
18719
18812
|
}
|
|
18720
18813
|
try {
|
|
18721
|
-
const { sheet } = extractSheetCells(records, bofIdx, globals.sst);
|
|
18814
|
+
const { sheet } = extractSheetCells(records, bofIdx, globals.sst, convertNum);
|
|
18722
18815
|
const blocks = sheetToBlocks2(meta.name, sheet, i);
|
|
18723
18816
|
allBlocks.push(...blocks);
|
|
18724
18817
|
} catch (e) {
|
|
@@ -18846,6 +18939,9 @@ var NARY_MAP = {
|
|
|
18846
18939
|
"\u2A02": "\\bigotimes",
|
|
18847
18940
|
"\u2A00": "\\bigodot"
|
|
18848
18941
|
};
|
|
18942
|
+
function onOffVal(v) {
|
|
18943
|
+
return v !== "0" && v !== "false" && v !== "off";
|
|
18944
|
+
}
|
|
18849
18945
|
function mapDelim(ch, isLeft) {
|
|
18850
18946
|
const l = {
|
|
18851
18947
|
"(": "(",
|
|
@@ -18872,10 +18968,23 @@ function mapDelim(ch, isLeft) {
|
|
|
18872
18968
|
const map = isLeft ? l : r;
|
|
18873
18969
|
return map[ch] ?? ch;
|
|
18874
18970
|
}
|
|
18971
|
+
function isBalancedWrap(s) {
|
|
18972
|
+
let depth = 0;
|
|
18973
|
+
for (let i = 0; i < s.length; i++) {
|
|
18974
|
+
const ch = s[i];
|
|
18975
|
+
if (ch === "{" && s[i - 1] !== "\\") depth++;
|
|
18976
|
+
else if (ch === "}" && s[i - 1] !== "\\") {
|
|
18977
|
+
depth--;
|
|
18978
|
+
if (depth === 0) return i === s.length - 1;
|
|
18979
|
+
if (depth < 0) return false;
|
|
18980
|
+
}
|
|
18981
|
+
}
|
|
18982
|
+
return false;
|
|
18983
|
+
}
|
|
18875
18984
|
function grp(body) {
|
|
18876
18985
|
const s = body.trim();
|
|
18877
18986
|
if (s.length === 0) return "{}";
|
|
18878
|
-
if (s.startsWith("{") && s.endsWith("}")) return s;
|
|
18987
|
+
if (s.startsWith("{") && s.endsWith("}") && isBalancedWrap(s)) return s;
|
|
18879
18988
|
return "{" + s + "}";
|
|
18880
18989
|
}
|
|
18881
18990
|
function childrenToLatex(parent) {
|
|
@@ -18969,8 +19078,8 @@ function nodeToLatex(el) {
|
|
|
18969
19078
|
}
|
|
18970
19079
|
const sh = firstKid(naryPr, "subHide");
|
|
18971
19080
|
const ph = firstKid(naryPr, "supHide");
|
|
18972
|
-
if (sh) subHide = (sh.getAttribute("m:val") ?? sh.getAttribute("val"))
|
|
18973
|
-
if (ph) supHide = (ph.getAttribute("m:val") ?? ph.getAttribute("val"))
|
|
19081
|
+
if (sh) subHide = onOffVal(sh.getAttribute("m:val") ?? sh.getAttribute("val"));
|
|
19082
|
+
if (ph) supHide = onOffVal(ph.getAttribute("m:val") ?? ph.getAttribute("val"));
|
|
18974
19083
|
const ll = firstKid(naryPr, "limLoc");
|
|
18975
19084
|
if (ll) limLoc = ll.getAttribute("m:val") ?? ll.getAttribute("val") ?? "";
|
|
18976
19085
|
}
|
|
@@ -19117,7 +19226,7 @@ function isDisplayMath(el) {
|
|
|
19117
19226
|
}
|
|
19118
19227
|
|
|
19119
19228
|
// src/docx/parser.ts
|
|
19120
|
-
var
|
|
19229
|
+
var MAX_DECOMPRESS_SIZE5 = 100 * 1024 * 1024;
|
|
19121
19230
|
function matchesLocal(el, localName2) {
|
|
19122
19231
|
return el.localName === localName2 || (el.tagName?.endsWith(`:${localName2}`) ?? false);
|
|
19123
19232
|
}
|
|
@@ -19135,6 +19244,8 @@ function effectiveChildElements(parent) {
|
|
|
19135
19244
|
result.push(...effectiveChildElements(c));
|
|
19136
19245
|
}
|
|
19137
19246
|
}
|
|
19247
|
+
} else if (matchesLocal(el, "ins") || matchesLocal(el, "smartTag")) {
|
|
19248
|
+
result.push(...effectiveChildElements(el));
|
|
19138
19249
|
} else {
|
|
19139
19250
|
result.push(el);
|
|
19140
19251
|
}
|
|
@@ -19199,6 +19310,21 @@ function parseStyles(xml) {
|
|
|
19199
19310
|
}
|
|
19200
19311
|
styles.set(styleId, { name, basedOn, outlineLevel });
|
|
19201
19312
|
}
|
|
19313
|
+
for (const [styleId, info] of styles) {
|
|
19314
|
+
if (info.outlineLevel !== void 0) continue;
|
|
19315
|
+
const seen = /* @__PURE__ */ new Set([styleId]);
|
|
19316
|
+
let cur = info.basedOn;
|
|
19317
|
+
while (cur && !seen.has(cur)) {
|
|
19318
|
+
seen.add(cur);
|
|
19319
|
+
const parent = styles.get(cur);
|
|
19320
|
+
if (!parent) break;
|
|
19321
|
+
if (parent.outlineLevel !== void 0) {
|
|
19322
|
+
info.outlineLevel = parent.outlineLevel;
|
|
19323
|
+
break;
|
|
19324
|
+
}
|
|
19325
|
+
cur = parent.basedOn;
|
|
19326
|
+
}
|
|
19327
|
+
}
|
|
19202
19328
|
return styles;
|
|
19203
19329
|
}
|
|
19204
19330
|
function parseNumbering(xml) {
|
|
@@ -19286,8 +19412,12 @@ function collectOmmlRoots(p) {
|
|
|
19286
19412
|
return out;
|
|
19287
19413
|
}
|
|
19288
19414
|
function extractRun(r) {
|
|
19289
|
-
|
|
19290
|
-
const
|
|
19415
|
+
let text = "";
|
|
19416
|
+
for (const el of effectiveChildElements(r)) {
|
|
19417
|
+
if (matchesLocal(el, "t")) text += el.textContent ?? "";
|
|
19418
|
+
else if (matchesLocal(el, "br") || matchesLocal(el, "cr")) text += "\n";
|
|
19419
|
+
else if (matchesLocal(el, "tab")) text += " ";
|
|
19420
|
+
}
|
|
19291
19421
|
let bold = false;
|
|
19292
19422
|
let italic = false;
|
|
19293
19423
|
const rPrEls = getChildElements(r, "rPr");
|
|
@@ -19442,7 +19572,7 @@ function collectTextboxParagraphs(node, inTxbx = false, out = [], depth = 0) {
|
|
|
19442
19572
|
}
|
|
19443
19573
|
return out;
|
|
19444
19574
|
}
|
|
19445
|
-
function parseTable(tbl2, styles, numbering, footnotes, rels) {
|
|
19575
|
+
function parseTable(tbl2, styles, numbering, footnotes, rels, keepEmptyCols) {
|
|
19446
19576
|
const trElements = getChildElements(tbl2, "tr");
|
|
19447
19577
|
if (trElements.length === 0) return null;
|
|
19448
19578
|
const rawRows = [];
|
|
@@ -19504,7 +19634,7 @@ ${cell2.text}` : cell2.text;
|
|
|
19504
19634
|
return { text: cell2.text, colSpan: cell2.colSpan, rowSpan, colAddr: cell2.col, rowAddr: r };
|
|
19505
19635
|
})
|
|
19506
19636
|
);
|
|
19507
|
-
const table2 = buildTable(cellRows);
|
|
19637
|
+
const table2 = buildTable(cellRows, { keepAnchoredEmptyCols: keepEmptyCols });
|
|
19508
19638
|
if (table2.rows === 0 || table2.cols === 0) return null;
|
|
19509
19639
|
return { type: "table", table: table2 };
|
|
19510
19640
|
}
|
|
@@ -19533,9 +19663,17 @@ async function buildImageMap(zip, rels, doc, warnings) {
|
|
|
19533
19663
|
const imageMap = /* @__PURE__ */ new Map();
|
|
19534
19664
|
const images = [];
|
|
19535
19665
|
let imgIdx = 0;
|
|
19666
|
+
const imageRefs = [];
|
|
19536
19667
|
for (const blip of findElements(doc.documentElement, "blip")) {
|
|
19537
19668
|
const embedId = getAttr(blip, "embed");
|
|
19538
|
-
if (
|
|
19669
|
+
if (embedId) imageRefs.push(embedId);
|
|
19670
|
+
}
|
|
19671
|
+
for (const imagedata of collectNonFallbackImagedata(doc.documentElement)) {
|
|
19672
|
+
const embedId = getAttr(imagedata, "id");
|
|
19673
|
+
if (embedId) imageRefs.push(embedId);
|
|
19674
|
+
}
|
|
19675
|
+
for (const embedId of imageRefs) {
|
|
19676
|
+
if (imageMap.has(embedId)) continue;
|
|
19539
19677
|
const target = rels.get(embedId);
|
|
19540
19678
|
if (!target) continue;
|
|
19541
19679
|
const imgPath = target.startsWith("/") ? target.slice(1) : target.startsWith("word/") ? target : `word/${target}`;
|
|
@@ -19570,14 +19708,26 @@ function collectParagraphBlips(node, out = [], depth = 0) {
|
|
|
19570
19708
|
if (depth > 40) return out;
|
|
19571
19709
|
for (const el of effectiveChildElements(node)) {
|
|
19572
19710
|
if (matchesLocal(el, "txbxContent") || matchesLocal(el, "Fallback")) continue;
|
|
19573
|
-
if (matchesLocal(el, "blip")) out.push(el);
|
|
19711
|
+
if (matchesLocal(el, "blip") || matchesLocal(el, "imagedata")) out.push(el);
|
|
19574
19712
|
else collectParagraphBlips(el, out, depth + 1);
|
|
19575
19713
|
}
|
|
19576
19714
|
return out;
|
|
19577
19715
|
}
|
|
19716
|
+
function collectNonFallbackImagedata(node, out = [], depth = 0) {
|
|
19717
|
+
if (depth > 60) return out;
|
|
19718
|
+
const children = node.childNodes;
|
|
19719
|
+
for (let i = 0; i < children.length; i++) {
|
|
19720
|
+
if (children[i].nodeType !== 1) continue;
|
|
19721
|
+
const el = children[i];
|
|
19722
|
+
if (matchesLocal(el, "Fallback")) continue;
|
|
19723
|
+
if (matchesLocal(el, "imagedata")) out.push(el);
|
|
19724
|
+
else collectNonFallbackImagedata(el, out, depth + 1);
|
|
19725
|
+
}
|
|
19726
|
+
return out;
|
|
19727
|
+
}
|
|
19578
19728
|
function emitParagraphImages(p, imageMap, linked, out) {
|
|
19579
19729
|
for (const blip of collectParagraphBlips(p)) {
|
|
19580
|
-
const embedId = getAttr(blip, "embed");
|
|
19730
|
+
const embedId = getAttr(blip, "embed") ?? getAttr(blip, "id");
|
|
19581
19731
|
if (!embedId) continue;
|
|
19582
19732
|
const filename = imageMap.get(embedId);
|
|
19583
19733
|
if (!filename) continue;
|
|
@@ -19586,7 +19736,7 @@ function emitParagraphImages(p, imageMap, linked, out) {
|
|
|
19586
19736
|
}
|
|
19587
19737
|
}
|
|
19588
19738
|
async function parseDocxDocument(buffer, options) {
|
|
19589
|
-
precheckZipSize(buffer,
|
|
19739
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE5);
|
|
19590
19740
|
const zip = await JSZip5.loadAsync(buffer);
|
|
19591
19741
|
const warnings = [];
|
|
19592
19742
|
const docFile = zip.file("word/document.xml");
|
|
@@ -19657,7 +19807,7 @@ async function parseDocxDocument(buffer, options) {
|
|
|
19657
19807
|
if (imageMap.size > 0) emitParagraphImages(tp, imageMap, linkedImages, blocks);
|
|
19658
19808
|
}
|
|
19659
19809
|
} else if (localName2 === "tbl") {
|
|
19660
|
-
const block = parseTable(el, styles, numbering, footnotes, rels);
|
|
19810
|
+
const block = parseTable(el, styles, numbering, footnotes, rels, options?.keepTrailingEmptyCols);
|
|
19661
19811
|
if (block) blocks.push(block);
|
|
19662
19812
|
}
|
|
19663
19813
|
}
|
|
@@ -19749,7 +19899,7 @@ function parseHwpmlDocument(buffer, options) {
|
|
|
19749
19899
|
if (localName(el) !== "SECTION") continue;
|
|
19750
19900
|
sectionIdx++;
|
|
19751
19901
|
if (pageFilter && !pageFilter.has(sectionIdx)) continue;
|
|
19752
|
-
parseSection2(el, blocks, paraShapeMap, sectionIdx, warnings);
|
|
19902
|
+
parseSection2(el, blocks, paraShapeMap, sectionIdx, warnings, options?.keepTrailingEmptyCols ?? false);
|
|
19753
19903
|
}
|
|
19754
19904
|
const outline = blocks.filter((b) => b.type === "heading" && b.text).map((b) => ({ level: b.level ?? 1, text: b.text, pageNumber: b.pageNumber }));
|
|
19755
19905
|
const markdown = blocksToMarkdown(blocks);
|
|
@@ -19785,10 +19935,10 @@ function buildParaShapeMap(root) {
|
|
|
19785
19935
|
}
|
|
19786
19936
|
return map;
|
|
19787
19937
|
}
|
|
19788
|
-
function parseSection2(section, blocks, paraShapeMap, sectionNum, warnings) {
|
|
19789
|
-
walkContent(section, blocks, paraShapeMap, sectionNum, warnings, false);
|
|
19938
|
+
function parseSection2(section, blocks, paraShapeMap, sectionNum, warnings, keep) {
|
|
19939
|
+
walkContent(section, blocks, paraShapeMap, sectionNum, warnings, false, keep);
|
|
19790
19940
|
}
|
|
19791
|
-
function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, depth = 0) {
|
|
19941
|
+
function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, keep, depth = 0) {
|
|
19792
19942
|
if (depth > MAX_XML_DEPTH2) return;
|
|
19793
19943
|
const children = node.childNodes;
|
|
19794
19944
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -19801,24 +19951,24 @@ function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderF
|
|
|
19801
19951
|
if (tag === "P") {
|
|
19802
19952
|
if (!inHeaderFooter) {
|
|
19803
19953
|
parseParagraph3(el, blocks, paraShapeMap, sectionNum);
|
|
19804
|
-
walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings);
|
|
19954
|
+
walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings, keep);
|
|
19805
19955
|
}
|
|
19806
19956
|
continue;
|
|
19807
19957
|
}
|
|
19808
19958
|
if (tag === "TABLE") {
|
|
19809
19959
|
if (!inHeaderFooter) {
|
|
19810
|
-
parseTable2(el, blocks, paraShapeMap, sectionNum, warnings);
|
|
19960
|
+
parseTable2(el, blocks, paraShapeMap, sectionNum, warnings, keep);
|
|
19811
19961
|
}
|
|
19812
19962
|
continue;
|
|
19813
19963
|
}
|
|
19814
19964
|
if (tag === "PARALIST" || tag === "SECTION" || tag === "COLDEF") {
|
|
19815
|
-
walkContent(el, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, depth + 1);
|
|
19965
|
+
walkContent(el, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, keep, depth + 1);
|
|
19816
19966
|
continue;
|
|
19817
19967
|
}
|
|
19818
|
-
walkContent(el, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, depth + 1);
|
|
19968
|
+
walkContent(el, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, keep, depth + 1);
|
|
19819
19969
|
}
|
|
19820
19970
|
}
|
|
19821
|
-
function walkTablesInP(node, blocks, paraShapeMap, sectionNum, warnings, depth = 0) {
|
|
19971
|
+
function walkTablesInP(node, blocks, paraShapeMap, sectionNum, warnings, keep, depth = 0) {
|
|
19822
19972
|
if (depth > MAX_XML_DEPTH2) return;
|
|
19823
19973
|
const children = node.childNodes;
|
|
19824
19974
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -19826,11 +19976,11 @@ function walkTablesInP(node, blocks, paraShapeMap, sectionNum, warnings, depth =
|
|
|
19826
19976
|
if (el.nodeType !== 1) continue;
|
|
19827
19977
|
const tag = localName(el);
|
|
19828
19978
|
if (tag === "TABLE") {
|
|
19829
|
-
parseTable2(el, blocks, paraShapeMap, sectionNum, warnings);
|
|
19979
|
+
parseTable2(el, blocks, paraShapeMap, sectionNum, warnings, keep);
|
|
19830
19980
|
continue;
|
|
19831
19981
|
}
|
|
19832
19982
|
if (tag === "FOOTNOTE" || tag === "ENDNOTE" || tag === "HEADER" || tag === "FOOTER") continue;
|
|
19833
|
-
walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings, depth + 1);
|
|
19983
|
+
walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings, keep, depth + 1);
|
|
19834
19984
|
}
|
|
19835
19985
|
}
|
|
19836
19986
|
function parseParagraph3(el, blocks, paraShapeMap, sectionNum) {
|
|
@@ -19866,7 +20016,7 @@ function collectCharText(node, parts, depth = 0) {
|
|
|
19866
20016
|
}
|
|
19867
20017
|
}
|
|
19868
20018
|
}
|
|
19869
|
-
function parseTable2(el, blocks, paraShapeMap, sectionNum, warnings) {
|
|
20019
|
+
function parseTable2(el, blocks, paraShapeMap, sectionNum, warnings, keep) {
|
|
19870
20020
|
const cells = [];
|
|
19871
20021
|
const rowCount = parseInt(el.getAttribute("RowCount") ?? "0", 10);
|
|
19872
20022
|
const colCount = parseInt(el.getAttribute("ColCount") ?? "0", 10);
|
|
@@ -19910,7 +20060,7 @@ function parseTable2(el, blocks, paraShapeMap, sectionNum, warnings) {
|
|
|
19910
20060
|
const cellRows = grid.map(
|
|
19911
20061
|
(row) => row.map((cell2) => cell2 ?? { text: "", colSpan: 1, rowSpan: 1 })
|
|
19912
20062
|
);
|
|
19913
|
-
const table2 = buildTable(cellRows);
|
|
20063
|
+
const table2 = buildTable(cellRows, { keepAnchoredEmptyCols: keep });
|
|
19914
20064
|
const caption = extractShapeCaption(el);
|
|
19915
20065
|
if (caption.text && caption.before) {
|
|
19916
20066
|
blocks.push({ type: "paragraph", text: caption.text, pageNumber: sectionNum });
|
|
@@ -20146,7 +20296,7 @@ function findMatchingKey(cellLabel, values) {
|
|
|
20146
20296
|
bestKey = key;
|
|
20147
20297
|
}
|
|
20148
20298
|
} else if (key.startsWith(cellLabel)) {
|
|
20149
|
-
if (cellLabel.length >= key.length * 0.
|
|
20299
|
+
if (cellLabel.length >= key.length * 0.75 && cellLabel.length > bestLen) {
|
|
20150
20300
|
bestLen = cellLabel.length;
|
|
20151
20301
|
bestKey = key;
|
|
20152
20302
|
}
|
|
@@ -20186,7 +20336,7 @@ function fillInCellPatterns(cellText, values, matchedLabels, blockedLabels) {
|
|
|
20186
20336
|
const matchKey = values.available(normalizedKw) ? normalizedKw : void 0;
|
|
20187
20337
|
if (matchKey === void 0) return match;
|
|
20188
20338
|
const val = values.peek(matchKey);
|
|
20189
|
-
const isTruthy = ["\u2611", "\u2713", "\u2714", "v", "V", "true", "1", "yes", "o", "O"].includes(val.trim())
|
|
20339
|
+
const isTruthy = ["\u2611", "\u2713", "\u2714", "v", "V", "true", "1", "yes", "o", "O"].includes(val.trim());
|
|
20190
20340
|
if (!isTruthy) return match;
|
|
20191
20341
|
values.consume(matchKey);
|
|
20192
20342
|
matchedLabels.add(matchKey);
|
|
@@ -20208,14 +20358,20 @@ function fillInCellPatterns(cellText, values, matchedLabels, blockedLabels) {
|
|
|
20208
20358
|
);
|
|
20209
20359
|
return matches.length > 0 ? { text, matches } : null;
|
|
20210
20360
|
}
|
|
20211
|
-
var INLINE_LABEL_RE = /([가-힣A-Za-z]{2,10})\s*[::]/g;
|
|
20361
|
+
var INLINE_LABEL_RE = /((?:[가-힣A-Za-z]{1,10} )?)([가-힣A-Za-z]{2,10})\s*[::]/g;
|
|
20212
20362
|
function scanInlineSegments(text) {
|
|
20213
20363
|
const labels = [];
|
|
20214
20364
|
INLINE_LABEL_RE.lastIndex = 0;
|
|
20215
20365
|
let m;
|
|
20216
20366
|
while ((m = INLINE_LABEL_RE.exec(text)) !== null) {
|
|
20217
20367
|
if (text[INLINE_LABEL_RE.lastIndex] === "/") continue;
|
|
20218
|
-
labels.push({
|
|
20368
|
+
labels.push({
|
|
20369
|
+
label: m[2],
|
|
20370
|
+
ext: m[1] ? m[1] + m[2] : void 0,
|
|
20371
|
+
extStart: m[1] ? m.index : void 0,
|
|
20372
|
+
start: m.index + m[1].length,
|
|
20373
|
+
end: INLINE_LABEL_RE.lastIndex
|
|
20374
|
+
});
|
|
20219
20375
|
}
|
|
20220
20376
|
const segments = [];
|
|
20221
20377
|
for (let i = 0; i < labels.length; i++) {
|
|
@@ -20233,21 +20389,44 @@ function scanInlineSegments(text) {
|
|
|
20233
20389
|
labelStart: cur.start,
|
|
20234
20390
|
valueStart: vs,
|
|
20235
20391
|
valueEnd: ve,
|
|
20236
|
-
value: text.slice(vs, ve)
|
|
20392
|
+
value: text.slice(vs, ve),
|
|
20393
|
+
...cur.ext !== void 0 ? { extLabel: cur.ext, extStart: cur.extStart } : {}
|
|
20237
20394
|
});
|
|
20238
20395
|
}
|
|
20239
20396
|
return segments;
|
|
20240
20397
|
}
|
|
20398
|
+
function matchInlineSegment(seg, values, blockedLabels) {
|
|
20399
|
+
const nlabel = normalizeLabel(seg.label);
|
|
20400
|
+
if (seg.extLabel !== void 0) {
|
|
20401
|
+
const nExt = normalizeLabel(seg.extLabel);
|
|
20402
|
+
if (nExt !== nlabel && !blockedLabels?.has(nExt) && values.has(nExt)) {
|
|
20403
|
+
return { key: nExt, label: seg.extLabel, viaExt: true };
|
|
20404
|
+
}
|
|
20405
|
+
}
|
|
20406
|
+
if (blockedLabels?.has(nlabel)) return void 0;
|
|
20407
|
+
const key = findMatchingKey(nlabel, values);
|
|
20408
|
+
return key !== void 0 ? { key, label: seg.label, viaExt: false } : void 0;
|
|
20409
|
+
}
|
|
20410
|
+
function clampSegmentEnd(text, seg, next, nextViaExt) {
|
|
20411
|
+
let ve = seg.valueEnd;
|
|
20412
|
+
if (nextViaExt && next?.extStart !== void 0 && next.extStart < ve) ve = next.extStart;
|
|
20413
|
+
while (ve > seg.valueStart && /\s/.test(text[ve - 1])) ve--;
|
|
20414
|
+
return ve;
|
|
20415
|
+
}
|
|
20241
20416
|
function padInsertion(text, pos, value) {
|
|
20242
20417
|
const lead = pos > 0 && !/\s/.test(text[pos - 1]) ? " " : "";
|
|
20243
20418
|
const trail = pos < text.length && !/\s/.test(text[pos]) ? " " : "";
|
|
20244
20419
|
return lead + value + trail;
|
|
20245
20420
|
}
|
|
20246
|
-
function normalizeValues(values) {
|
|
20421
|
+
function normalizeValues(values, warnings) {
|
|
20247
20422
|
const map = /* @__PURE__ */ new Map();
|
|
20248
20423
|
for (const [label, raw] of Object.entries(values)) {
|
|
20249
20424
|
const { value, format } = typeof raw === "object" && !Array.isArray(raw) ? raw : { value: raw, format: void 0 };
|
|
20250
|
-
|
|
20425
|
+
const key = normalizeLabel(label);
|
|
20426
|
+
if (map.has(key)) {
|
|
20427
|
+
warnings?.push(`\uC785\uB825 \uB77C\uBCA8 "${label}"\uC774 \uC815\uADDC\uD654 \uD0A4 "${key}"\uC5D0\uC11C \uB2E4\uB978 \uB77C\uBCA8\uACFC \uCDA9\uB3CC \u2014 \uB4A4 \uAC12\uC73C\uB85C \uB36E\uC5B4\uC500`);
|
|
20428
|
+
}
|
|
20429
|
+
map.set(key, Array.isArray(value) ? value.map((v) => formatFillValue(v, format)) : formatFillValue(value, format));
|
|
20251
20430
|
}
|
|
20252
20431
|
return map;
|
|
20253
20432
|
}
|
|
@@ -20436,10 +20615,10 @@ function extractFromTable(table2) {
|
|
|
20436
20615
|
}
|
|
20437
20616
|
if (fields.length === 0 && table2.rows >= 2 && table2.cols >= 2) {
|
|
20438
20617
|
const headerRow = table2.cells[0];
|
|
20439
|
-
const allLabels = headerRow
|
|
20440
|
-
const t = cell2
|
|
20618
|
+
const allLabels = headerRow?.every((cell2) => {
|
|
20619
|
+
const t = cell2?.text.trim() ?? "";
|
|
20441
20620
|
return t.length > 0 && t.length <= 20;
|
|
20442
|
-
});
|
|
20621
|
+
}) ?? false;
|
|
20443
20622
|
if (allLabels) {
|
|
20444
20623
|
for (let r = 1; r < table2.rows; r++) {
|
|
20445
20624
|
for (let c = 0; c < table2.cols; c++) {
|
|
@@ -20530,7 +20709,8 @@ function fillFormFields(blocks, values, blockedLabels) {
|
|
|
20530
20709
|
const cloned = structuredClone(blocks);
|
|
20531
20710
|
const filled = [];
|
|
20532
20711
|
const matchedLabels = /* @__PURE__ */ new Set();
|
|
20533
|
-
const
|
|
20712
|
+
const warnings = [];
|
|
20713
|
+
const normalizedValues = normalizeValues(values, warnings);
|
|
20534
20714
|
const cursor = new ValueCursor(normalizedValues);
|
|
20535
20715
|
const allTables = collectIRTables(cloned, 0);
|
|
20536
20716
|
const patternFilledCells = /* @__PURE__ */ new Set();
|
|
@@ -20559,7 +20739,7 @@ function fillFormFields(blocks, values, blockedLabels) {
|
|
|
20559
20739
|
if (newText !== block.text) block.text = newText;
|
|
20560
20740
|
}
|
|
20561
20741
|
const unmatched = resolveUnmatched(normalizedValues, matchedLabels, values);
|
|
20562
|
-
return { blocks: cloned, filled, unmatched };
|
|
20742
|
+
return { blocks: cloned, filled, unmatched, ...warnings.length > 0 ? { warnings } : {} };
|
|
20563
20743
|
}
|
|
20564
20744
|
function collectIRTables(blocks, depth) {
|
|
20565
20745
|
if (depth > 16) return [];
|
|
@@ -20593,13 +20773,31 @@ function coveredPositions(table2) {
|
|
|
20593
20773
|
}
|
|
20594
20774
|
return covered;
|
|
20595
20775
|
}
|
|
20776
|
+
function isHeaderDataTable(table2, covered) {
|
|
20777
|
+
if (table2.rows < 2) return false;
|
|
20778
|
+
const headerRow = table2.cells[0];
|
|
20779
|
+
if (!headerRow?.length) return false;
|
|
20780
|
+
const allLabels = headerRow.every((cell2) => {
|
|
20781
|
+
const t = cell2?.text.trim() ?? "";
|
|
20782
|
+
return t.length > 0 && t.length <= 20 && isLabelCell(t);
|
|
20783
|
+
});
|
|
20784
|
+
if (!allLabels) return false;
|
|
20785
|
+
for (let c = 0; c < table2.cols; c++) {
|
|
20786
|
+
if (covered.has(`1,${c}`)) continue;
|
|
20787
|
+
const cell2 = table2.cells[1]?.[c];
|
|
20788
|
+
if (!cell2) break;
|
|
20789
|
+
return !isLabelCell(cell2.text);
|
|
20790
|
+
}
|
|
20791
|
+
return true;
|
|
20792
|
+
}
|
|
20596
20793
|
function fillTable(table2, values, filled, matchedLabels, patternFilledCells, blockedLabels) {
|
|
20597
20794
|
if (table2.cols < 2) return;
|
|
20598
20795
|
const covered = coveredPositions(table2);
|
|
20599
|
-
|
|
20796
|
+
const skipHeaderRow = isHeaderDataTable(table2, covered);
|
|
20797
|
+
for (let r = skipHeaderRow ? 1 : 0; r < table2.rows; r++) {
|
|
20600
20798
|
for (let c = 0; c < table2.cols; c++) {
|
|
20601
20799
|
if (covered.has(`${r},${c}`)) continue;
|
|
20602
|
-
const labelCell = table2.cells[r][c];
|
|
20800
|
+
const labelCell = table2.cells[r]?.[c];
|
|
20603
20801
|
if (!labelCell) continue;
|
|
20604
20802
|
if (!isLabelCell(labelCell.text)) continue;
|
|
20605
20803
|
let vc = c + labelCell.colSpan;
|
|
@@ -20632,10 +20830,10 @@ function fillTable(table2, values, filled, matchedLabels, patternFilledCells, bl
|
|
|
20632
20830
|
}
|
|
20633
20831
|
if (table2.rows >= 2 && table2.cols >= 2) {
|
|
20634
20832
|
const headerRow = table2.cells[0];
|
|
20635
|
-
const allLabels = headerRow
|
|
20636
|
-
const t = cell2
|
|
20833
|
+
const allLabels = headerRow?.every((cell2) => {
|
|
20834
|
+
const t = cell2?.text.trim() ?? "";
|
|
20637
20835
|
return t.length > 0 && t.length <= 20 && isLabelCell(t);
|
|
20638
|
-
});
|
|
20836
|
+
}) ?? false;
|
|
20639
20837
|
if (!allLabels) return;
|
|
20640
20838
|
for (let r = 1; r < table2.rows; r++) {
|
|
20641
20839
|
for (let c = 0; c < table2.cols; c++) {
|
|
@@ -20650,7 +20848,11 @@ function fillTable(table2, values, filled, matchedLabels, patternFilledCells, bl
|
|
|
20650
20848
|
if (!values.isArray(matchKey) && matchedLabels.has(matchKey)) continue;
|
|
20651
20849
|
const newValue = values.consume(matchKey);
|
|
20652
20850
|
if (newValue === void 0) continue;
|
|
20653
|
-
valueCell
|
|
20851
|
+
if (patternFilledCells?.has(valueCell)) {
|
|
20852
|
+
valueCell.text = newValue + " " + valueCell.text;
|
|
20853
|
+
} else {
|
|
20854
|
+
valueCell.text = newValue;
|
|
20855
|
+
}
|
|
20654
20856
|
matchedLabels.add(matchKey);
|
|
20655
20857
|
filled.push({
|
|
20656
20858
|
label: headerCell.text.trim(),
|
|
@@ -20666,20 +20868,22 @@ function fillTable(table2, values, filled, matchedLabels, patternFilledCells, bl
|
|
|
20666
20868
|
function fillInlineFields(text, values, filled, matchedLabels, blockedLabels) {
|
|
20667
20869
|
const segments = scanInlineSegments(text);
|
|
20668
20870
|
if (segments.length === 0) return text;
|
|
20871
|
+
const matches = segments.map((seg) => matchInlineSegment(seg, values, blockedLabels));
|
|
20669
20872
|
let out = "";
|
|
20670
20873
|
let pos = 0;
|
|
20671
|
-
for (
|
|
20672
|
-
const
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20874
|
+
for (let i = 0; i < segments.length; i++) {
|
|
20875
|
+
const seg = segments[i];
|
|
20876
|
+
const matched = matches[i];
|
|
20877
|
+
if (matched === void 0) continue;
|
|
20878
|
+
const matchKey = matched.key;
|
|
20676
20879
|
const newValue = values.consume(matchKey);
|
|
20677
20880
|
if (newValue === void 0) continue;
|
|
20678
20881
|
matchedLabels.add(matchKey);
|
|
20679
|
-
filled.push({ label:
|
|
20882
|
+
filled.push({ label: matched.label.trim(), value: newValue, row: -1, col: -1, key: matchKey });
|
|
20883
|
+
const ve = clampSegmentEnd(text, seg, segments[i + 1], matches[i + 1]?.viaExt ?? false);
|
|
20680
20884
|
out += text.slice(pos, seg.valueStart);
|
|
20681
|
-
out += seg.valueStart ===
|
|
20682
|
-
pos =
|
|
20885
|
+
out += seg.valueStart === ve ? padInsertion(text, seg.valueStart, newValue) : newValue;
|
|
20886
|
+
pos = ve;
|
|
20683
20887
|
}
|
|
20684
20888
|
out += text.slice(pos);
|
|
20685
20889
|
return out;
|
|
@@ -20690,7 +20894,7 @@ import JSZip6 from "jszip";
|
|
|
20690
20894
|
|
|
20691
20895
|
// src/roundtrip/source-map.ts
|
|
20692
20896
|
function escapeXmlText(text) {
|
|
20693
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
20897
|
+
return text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
20694
20898
|
}
|
|
20695
20899
|
function decodeXmlEntities(text) {
|
|
20696
20900
|
return text.replace(/&(lt|gt|amp|quot|apos|#x?[0-9a-fA-F]+);/g, (m, ent) => {
|
|
@@ -21270,7 +21474,7 @@ function parseCentralDirectory(buf) {
|
|
|
21270
21474
|
}
|
|
21271
21475
|
return { entries, cdOffset, cdSize, eocdOffset };
|
|
21272
21476
|
}
|
|
21273
|
-
var
|
|
21477
|
+
var CRC_TABLE = (() => {
|
|
21274
21478
|
const table2 = new Uint32Array(256);
|
|
21275
21479
|
for (let n = 0; n < 256; n++) {
|
|
21276
21480
|
let c = n;
|
|
@@ -21279,10 +21483,10 @@ var CRC_TABLE2 = (() => {
|
|
|
21279
21483
|
}
|
|
21280
21484
|
return table2;
|
|
21281
21485
|
})();
|
|
21282
|
-
function
|
|
21486
|
+
function crc32(data) {
|
|
21283
21487
|
let crc = 4294967295;
|
|
21284
21488
|
for (let i = 0; i < data.length; i++) {
|
|
21285
|
-
crc =
|
|
21489
|
+
crc = CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
|
|
21286
21490
|
}
|
|
21287
21491
|
return (crc ^ 4294967295) >>> 0;
|
|
21288
21492
|
}
|
|
@@ -21320,8 +21524,11 @@ function patchZipEntries(original, replacements, additions) {
|
|
|
21320
21524
|
const header = copyBytes(original, e.localOffset, e.localOffset + headerLen);
|
|
21321
21525
|
const hview = new DataView(header.buffer, header.byteOffset, header.byteLength);
|
|
21322
21526
|
const method = e.method;
|
|
21527
|
+
if (method !== 0 && method !== 8) {
|
|
21528
|
+
throw new KordocError(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 ZIP \uC555\uCD95 \uBC29\uC2DD(method=${method}): ${e.name} \u2014 STORE(0)/DEFLATE(8)\uB9CC \uAD50\uCCB4 \uAC00\uB2A5`);
|
|
21529
|
+
}
|
|
21323
21530
|
const compData = method === 0 ? newData : new Uint8Array(deflateRawSync(newData));
|
|
21324
|
-
const crc =
|
|
21531
|
+
const crc = crc32(newData);
|
|
21325
21532
|
const flags = e.flags & ~8;
|
|
21326
21533
|
hview.setUint16(6, flags, true);
|
|
21327
21534
|
hview.setUint32(14, crc, true);
|
|
@@ -21339,7 +21546,7 @@ function patchZipEntries(original, replacements, additions) {
|
|
|
21339
21546
|
const deflated = new Uint8Array(deflateRawSync(data));
|
|
21340
21547
|
const method = deflated.length < data.length ? 8 : 0;
|
|
21341
21548
|
const compData = method === 8 ? deflated : data;
|
|
21342
|
-
const crc =
|
|
21549
|
+
const crc = crc32(data);
|
|
21343
21550
|
const header = new Uint8Array(30 + nameBytes.length);
|
|
21344
21551
|
const hv = new DataView(header.buffer);
|
|
21345
21552
|
hv.setUint32(0, LOCAL_SIG, true);
|
|
@@ -21421,7 +21628,8 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
|
21421
21628
|
if (sectionPaths.length === 0) {
|
|
21422
21629
|
throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
21423
21630
|
}
|
|
21424
|
-
const
|
|
21631
|
+
const warnings = [];
|
|
21632
|
+
const normalizedValues = normalizeValues(values, warnings);
|
|
21425
21633
|
const cursor = new ValueCursor(normalizedValues);
|
|
21426
21634
|
const matchedLabels = /* @__PURE__ */ new Set();
|
|
21427
21635
|
const filled = [];
|
|
@@ -21483,7 +21691,17 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
|
21483
21691
|
}
|
|
21484
21692
|
}
|
|
21485
21693
|
for (const table2 of allTables) {
|
|
21486
|
-
|
|
21694
|
+
const skipHeaderRow = table2.rows.length >= 2 && (() => {
|
|
21695
|
+
const first = table2.rows[0];
|
|
21696
|
+
const allLabels = first.length > 0 && first.every((cell2) => {
|
|
21697
|
+
const t = cellLabelText(cell2).trim();
|
|
21698
|
+
return t.length > 0 && t.length <= 20 && isLabelCell(t);
|
|
21699
|
+
});
|
|
21700
|
+
if (!allLabels) return false;
|
|
21701
|
+
const d0 = table2.rows[1][0];
|
|
21702
|
+
return d0 === void 0 || !isLabelCell(cellLabelText(d0));
|
|
21703
|
+
})();
|
|
21704
|
+
for (let rowIdx = skipHeaderRow ? 1 : 0; rowIdx < table2.rows.length; rowIdx++) {
|
|
21487
21705
|
const cells = table2.rows[rowIdx];
|
|
21488
21706
|
for (let colIdx = 0; colIdx < cells.length - 1; colIdx++) {
|
|
21489
21707
|
const labelText = cellLabelText(cells[colIdx]);
|
|
@@ -21554,9 +21772,30 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
|
21554
21772
|
const matchKey = findMatchingKey(headerLabel, cursor);
|
|
21555
21773
|
if (matchKey === void 0) continue;
|
|
21556
21774
|
if (!cursor.isArray(matchKey) && matchedLabels.has(matchKey)) continue;
|
|
21775
|
+
const dataCell = dataCells[colIdx];
|
|
21776
|
+
if (patternApplied.has(dataCell)) {
|
|
21777
|
+
const target = dataCell.paragraphs.find((p) => p.tRanges.length > 0) ?? dataCell.paragraphs[0];
|
|
21778
|
+
if (!target) continue;
|
|
21779
|
+
const l = led(target);
|
|
21780
|
+
if (l.fullText !== void 0) continue;
|
|
21781
|
+
const newValue2 = cursor.consume(matchKey);
|
|
21782
|
+
if (newValue2 === void 0) continue;
|
|
21783
|
+
l.ranges.push({ start: 0, end: 0, replacement: newValue2 + " " });
|
|
21784
|
+
l.filledIdx.push(filled.length);
|
|
21785
|
+
l.matchKeys.push(matchKey);
|
|
21786
|
+
matchedLabels.add(matchKey);
|
|
21787
|
+
filled.push({
|
|
21788
|
+
label: cellLabelText(headerCells[colIdx]).trim(),
|
|
21789
|
+
value: newValue2,
|
|
21790
|
+
row: rowIdx,
|
|
21791
|
+
col: colIdx,
|
|
21792
|
+
key: matchKey
|
|
21793
|
+
});
|
|
21794
|
+
continue;
|
|
21795
|
+
}
|
|
21557
21796
|
const newValue = cursor.consume(matchKey);
|
|
21558
21797
|
if (newValue === void 0) continue;
|
|
21559
|
-
const paras =
|
|
21798
|
+
const paras = dataCell.paragraphs;
|
|
21560
21799
|
if (paras.length === 0) continue;
|
|
21561
21800
|
const l0 = led(paras[0]);
|
|
21562
21801
|
l0.fullText = newValue;
|
|
@@ -21585,20 +21824,23 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
|
21585
21824
|
const existing = ledger.get(para2);
|
|
21586
21825
|
if (existing?.fullText !== void 0) continue;
|
|
21587
21826
|
const text = matchText(para2);
|
|
21588
|
-
|
|
21589
|
-
|
|
21590
|
-
|
|
21591
|
-
const
|
|
21592
|
-
|
|
21827
|
+
const segments = scanInlineSegments(text);
|
|
21828
|
+
const matches = segments.map((seg) => matchInlineSegment(seg, cursor, blockedLabels));
|
|
21829
|
+
for (let i = 0; i < segments.length; i++) {
|
|
21830
|
+
const seg = segments[i];
|
|
21831
|
+
const matched = matches[i];
|
|
21832
|
+
if (matched === void 0) continue;
|
|
21833
|
+
const matchKey = matched.key;
|
|
21593
21834
|
const newValue = cursor.consume(matchKey);
|
|
21594
21835
|
if (newValue === void 0) continue;
|
|
21595
|
-
const
|
|
21836
|
+
const ve = clampSegmentEnd(text, seg, segments[i + 1], matches[i + 1]?.viaExt ?? false);
|
|
21837
|
+
const replacement = seg.valueStart === ve ? padInsertion(text, seg.valueStart, newValue) : newValue;
|
|
21596
21838
|
const l = led(para2);
|
|
21597
|
-
l.ranges.push({ start: seg.valueStart, end:
|
|
21839
|
+
l.ranges.push({ start: seg.valueStart, end: ve, replacement });
|
|
21598
21840
|
matchedLabels.add(matchKey);
|
|
21599
21841
|
l.filledIdx.push(filled.length);
|
|
21600
21842
|
l.matchKeys.push(matchKey);
|
|
21601
|
-
filled.push({ label:
|
|
21843
|
+
filled.push({ label: matched.label.trim(), value: newValue, row: -1, col: -1, key: matchKey });
|
|
21602
21844
|
}
|
|
21603
21845
|
}
|
|
21604
21846
|
const splices = [];
|
|
@@ -21651,7 +21893,7 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
|
21651
21893
|
}
|
|
21652
21894
|
}
|
|
21653
21895
|
for (const k of failedKeys) {
|
|
21654
|
-
if (!succeededKeys.has(k)) matchedLabels.delete(k);
|
|
21896
|
+
if (!succeededKeys.has(k) || cursor.isArray(k)) matchedLabels.delete(k);
|
|
21655
21897
|
}
|
|
21656
21898
|
const cleanFilled = filled.filter((f) => f !== null);
|
|
21657
21899
|
const unmatched = resolveUnmatched(normalizedValues, matchedLabels, values);
|
|
@@ -21659,7 +21901,8 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
|
21659
21901
|
return {
|
|
21660
21902
|
buffer: out.buffer.slice(out.byteOffset, out.byteOffset + out.byteLength),
|
|
21661
21903
|
filled: cleanFilled,
|
|
21662
|
-
unmatched
|
|
21904
|
+
unmatched,
|
|
21905
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
21663
21906
|
};
|
|
21664
21907
|
}
|
|
21665
21908
|
|
|
@@ -21936,27 +22179,39 @@ function parseMarkdownToBlocks(md2) {
|
|
|
21936
22179
|
if (/^<table[\s>]/i.test(line.trimStart())) {
|
|
21937
22180
|
const htmlLines = [];
|
|
21938
22181
|
let depth = 0;
|
|
21939
|
-
|
|
21940
|
-
|
|
22182
|
+
let closed = false;
|
|
22183
|
+
let j = i;
|
|
22184
|
+
while (j < lines.length) {
|
|
22185
|
+
const l = lines[j];
|
|
21941
22186
|
htmlLines.push(l);
|
|
21942
22187
|
depth += (l.match(/<table[\s>]/gi) ?? []).length;
|
|
21943
22188
|
depth -= (l.match(/<\/table>/gi) ?? []).length;
|
|
21944
|
-
|
|
21945
|
-
if (depth <= 0)
|
|
22189
|
+
j++;
|
|
22190
|
+
if (depth <= 0) {
|
|
22191
|
+
closed = true;
|
|
22192
|
+
break;
|
|
22193
|
+
}
|
|
22194
|
+
}
|
|
22195
|
+
if (closed) {
|
|
22196
|
+
blocks.push({ type: "html_table", text: htmlLines.join("\n") });
|
|
22197
|
+
i = j;
|
|
22198
|
+
continue;
|
|
21946
22199
|
}
|
|
21947
|
-
blocks.push({ type: "html_table", text: htmlLines.join("\n") });
|
|
21948
|
-
continue;
|
|
21949
22200
|
}
|
|
21950
22201
|
if (line.trimStart().startsWith("|")) {
|
|
21951
22202
|
const tableRows = [];
|
|
22203
|
+
let sepSeen = false;
|
|
21952
22204
|
while (i < lines.length && lines[i].trimStart().startsWith("|")) {
|
|
21953
22205
|
const row = lines[i];
|
|
21954
|
-
|
|
21955
|
-
|
|
21956
|
-
|
|
21957
|
-
|
|
22206
|
+
if (tableRows.length === 1 && !sepSeen) {
|
|
22207
|
+
const sepCells = row.trim().replace(/^\|/, "").replace(/\|$/, "").split("|");
|
|
22208
|
+
if (sepCells.every((c) => /^\s*:?-+:?\s*$/.test(c))) {
|
|
22209
|
+
sepSeen = true;
|
|
22210
|
+
i++;
|
|
22211
|
+
continue;
|
|
22212
|
+
}
|
|
21958
22213
|
}
|
|
21959
|
-
const cells = row.split(
|
|
22214
|
+
const cells = row.split(/(?<!\\)\|/).slice(1, -1).map((c) => c.trim().replace(/\\\|/g, "|"));
|
|
21960
22215
|
if (cells.length > 0) tableRows.push(cells);
|
|
21961
22216
|
i++;
|
|
21962
22217
|
}
|
|
@@ -22905,13 +23160,26 @@ function normalize(s) {
|
|
|
22905
23160
|
return s.replace(/\s+/g, " ").trim();
|
|
22906
23161
|
}
|
|
22907
23162
|
var MAX_LEVENSHTEIN_LEN = 1e4;
|
|
23163
|
+
function approxDistance(a, b) {
|
|
23164
|
+
const bigramCounts = (s) => {
|
|
23165
|
+
const m = /* @__PURE__ */ new Map();
|
|
23166
|
+
for (let i = 0; i < s.length - 1; i++) {
|
|
23167
|
+
const g = s.slice(i, i + 2);
|
|
23168
|
+
m.set(g, (m.get(g) ?? 0) + 1);
|
|
23169
|
+
}
|
|
23170
|
+
return m;
|
|
23171
|
+
};
|
|
23172
|
+
const ca = bigramCounts(a);
|
|
23173
|
+
const cb = bigramCounts(b);
|
|
23174
|
+
let inter = 0;
|
|
23175
|
+
for (const [g, n] of ca) inter += Math.min(n, cb.get(g) ?? 0);
|
|
23176
|
+
const total = Math.max(a.length - 1, 0) + Math.max(b.length - 1, 0);
|
|
23177
|
+
const dice = total > 0 ? 2 * inter / total : 1;
|
|
23178
|
+
return Math.round(Math.max(a.length, b.length) * (1 - dice));
|
|
23179
|
+
}
|
|
22908
23180
|
function levenshtein(a, b) {
|
|
22909
23181
|
if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
|
|
22910
|
-
|
|
22911
|
-
let diffs = 0;
|
|
22912
|
-
for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
|
|
22913
|
-
const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
|
|
22914
|
-
return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
|
|
23182
|
+
return approxDistance(a, b);
|
|
22915
23183
|
}
|
|
22916
23184
|
if (a.length > b.length) [a, b] = [b, a];
|
|
22917
23185
|
const m = a.length;
|
|
@@ -23063,9 +23331,16 @@ function bestSimInRange(arr, from, to, target) {
|
|
|
23063
23331
|
return best;
|
|
23064
23332
|
}
|
|
23065
23333
|
function escapeGfm(text) {
|
|
23066
|
-
|
|
23334
|
+
const NUL = String.fromCharCode(0);
|
|
23335
|
+
const spans = [];
|
|
23336
|
+
const masked = text.replace(/!\[[^\]]*\]\([^)\n]*\)|\]\((?:https?:|mailto:|tel:|#)[^)\n]*\)/gi, (m) => {
|
|
23337
|
+
spans.push(m);
|
|
23338
|
+
return NUL + (spans.length - 1) + NUL;
|
|
23339
|
+
});
|
|
23340
|
+
const escaped = masked.replace(/([~*_`])/g, "\\$1");
|
|
23341
|
+
return escaped.replace(new RegExp(NUL + "(\\d+)" + NUL, "g"), (_, n) => spans[Number(n)]);
|
|
23067
23342
|
}
|
|
23068
|
-
var HWP_SHAPE_ALT_TEXT_RE =
|
|
23343
|
+
var HWP_SHAPE_ALT_TEXT_RE = /^(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?$/gm;
|
|
23069
23344
|
function sanitizeText(text) {
|
|
23070
23345
|
let result = mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
|
|
23071
23346
|
if (result.length <= 30 && result.includes(" ")) {
|
|
@@ -23081,7 +23356,7 @@ function normForMatch(text) {
|
|
|
23081
23356
|
return sanitizeText(text).replace(/\s+/g, " ").trim();
|
|
23082
23357
|
}
|
|
23083
23358
|
function unescapeGfm(text) {
|
|
23084
|
-
return text.replace(/\\([~*])/g, "$1");
|
|
23359
|
+
return text.replace(/\\([~*_`])/g, "$1");
|
|
23085
23360
|
}
|
|
23086
23361
|
function summarize(text) {
|
|
23087
23362
|
const t = text.replace(/\s+/g, " ").trim();
|
|
@@ -23148,7 +23423,7 @@ function parseGfmTable(lines) {
|
|
|
23148
23423
|
return rows;
|
|
23149
23424
|
}
|
|
23150
23425
|
function unescapeGfmCell(text) {
|
|
23151
|
-
return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\([~*])/g, "$1");
|
|
23426
|
+
return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\([~*_`])/g, "$1");
|
|
23152
23427
|
}
|
|
23153
23428
|
function replicateCellInnerHtml(cell2) {
|
|
23154
23429
|
if (cell2.blocks?.length) {
|
|
@@ -23236,10 +23511,10 @@ function parseHtmlTable(raw) {
|
|
|
23236
23511
|
}
|
|
23237
23512
|
} else {
|
|
23238
23513
|
if (!isClose) {
|
|
23239
|
-
const cs = parseInt(attrs.match(/colspan\s*=\s*"(\d+)
|
|
23240
|
-
const rs = parseInt(attrs.match(/rowspan\s*=\s*"(\d+)
|
|
23514
|
+
const cs = parseInt(attrs.match(/colspan\s*=\s*["']?(\d+)/i)?.[1] || "1", 10);
|
|
23515
|
+
const rs = parseInt(attrs.match(/rowspan\s*=\s*["']?(\d+)/i)?.[1] || "1", 10);
|
|
23241
23516
|
cellStart = m.index + m[0].length;
|
|
23242
|
-
cellInfo = { colSpan: isNaN(cs) ? 1 : cs, rowSpan: isNaN(rs) ? 1 : rs };
|
|
23517
|
+
cellInfo = { colSpan: clampSpan(isNaN(cs) ? 1 : cs, MAX_COLS), rowSpan: clampSpan(isNaN(rs) ? 1 : rs, MAX_ROWS) };
|
|
23243
23518
|
} else if (cellStart >= 0 && cellInfo && currentRow) {
|
|
23244
23519
|
currentRow.push({ inner: raw.slice(cellStart, m.index), colSpan: cellInfo.colSpan, rowSpan: cellInfo.rowSpan });
|
|
23245
23520
|
cellStart = -1;
|
|
@@ -23883,8 +24158,8 @@ function layoutHtmlRows(rows) {
|
|
|
23883
24158
|
let c = 0;
|
|
23884
24159
|
for (const cell2 of rows[r].cells) {
|
|
23885
24160
|
while (occupied.has(`${r},${c}`)) c++;
|
|
23886
|
-
const colSpan =
|
|
23887
|
-
const rowSpan =
|
|
24161
|
+
const colSpan = clampSpan(cell2.colSpan, MAX_COLS);
|
|
24162
|
+
const rowSpan = clampSpan(cell2.rowSpan, MAX_ROWS);
|
|
23888
24163
|
placed.push({ r, c, colSpan, rowSpan, inner: cell2.inner, isHeader: rows[r].tag === "th" });
|
|
23889
24164
|
for (let dr = 0; dr < rowSpan; dr++) {
|
|
23890
24165
|
for (let dc = 0; dc < colSpan; dc++) occupied.add(`${r + dr},${c + dc}`);
|
|
@@ -25043,12 +25318,20 @@ function diffBlocks(blocksA, blocksB) {
|
|
|
25043
25318
|
function alignBlocks(a, b) {
|
|
25044
25319
|
const m = a.length, n = b.length;
|
|
25045
25320
|
if (m * n > 1e7) return fallbackAlign(a, b);
|
|
25321
|
+
const lenOf = (blk) => {
|
|
25322
|
+
const t = blk.text !== void 0 ? blk.text : blk.type === "table" && blk.table ? blk.table.cells.flat().map((c) => c?.text ?? "").join(" ") : "";
|
|
25323
|
+
return t.replace(/\s+/g, " ").trim().length;
|
|
25324
|
+
};
|
|
25325
|
+
const aLen = a.map(lenOf);
|
|
25326
|
+
const bLen = b.map(lenOf);
|
|
25046
25327
|
const simCache = /* @__PURE__ */ new Map();
|
|
25047
25328
|
const getSim = (i2, j2) => {
|
|
25048
25329
|
const key = `${i2},${j2}`;
|
|
25049
25330
|
let v = simCache.get(key);
|
|
25050
25331
|
if (v === void 0) {
|
|
25051
|
-
|
|
25332
|
+
const mx = Math.max(aLen[i2], bLen[j2]);
|
|
25333
|
+
const cut = a[i2].type === "table" || b[j2].type === "table" ? 6 / 7 : 1 - SIMILARITY_THRESHOLD;
|
|
25334
|
+
v = mx > 0 && (mx - Math.min(aLen[i2], bLen[j2])) / mx > cut ? 0 : blockSimilarity(a[i2], b[j2]);
|
|
25052
25335
|
simCache.set(key, v);
|
|
25053
25336
|
}
|
|
25054
25337
|
return v;
|
|
@@ -25109,8 +25392,8 @@ function blockSimilarity(a, b) {
|
|
|
25109
25392
|
}
|
|
25110
25393
|
function tableSimilarity(a, b) {
|
|
25111
25394
|
const dimSim = 1 - Math.abs(a.rows * a.cols - b.rows * b.cols) / Math.max(a.rows * a.cols, b.rows * b.cols, 1);
|
|
25112
|
-
const textsA = a.cells.flat().map((c) => c
|
|
25113
|
-
const textsB = b.cells.flat().map((c) => c
|
|
25395
|
+
const textsA = a.cells.flat().map((c) => c?.text ?? "").join(" ");
|
|
25396
|
+
const textsB = b.cells.flat().map((c) => c?.text ?? "").join(" ");
|
|
25114
25397
|
const contentSim = normalizedSimilarity(textsA, textsB);
|
|
25115
25398
|
return dimSim * 0.3 + contentSim * 0.7;
|
|
25116
25399
|
}
|
|
@@ -25121,8 +25404,8 @@ function diffTableCells(a, b) {
|
|
|
25121
25404
|
for (let r = 0; r < maxRows; r++) {
|
|
25122
25405
|
const row = [];
|
|
25123
25406
|
for (let c = 0; c < maxCols; c++) {
|
|
25124
|
-
const cellA = r < a.rows && c < a.cols ? a.cells[r][c]
|
|
25125
|
-
const cellB = r < b.rows && c < b.cols ? b.cells[r][c]
|
|
25407
|
+
const cellA = r < a.rows && c < a.cols ? a.cells[r]?.[c]?.text : void 0;
|
|
25408
|
+
const cellB = r < b.rows && c < b.cols ? b.cells[r]?.[c]?.text : void 0;
|
|
25126
25409
|
let type;
|
|
25127
25410
|
if (cellA === void 0) type = "added";
|
|
25128
25411
|
else if (cellB === void 0) type = "removed";
|
|
@@ -26881,16 +27164,16 @@ function buildOrigUnits(blocks) {
|
|
|
26881
27164
|
for (let i = 0; i < blocks.length; i++) {
|
|
26882
27165
|
const block = blocks[i];
|
|
26883
27166
|
let consume = 1;
|
|
26884
|
-
let
|
|
27167
|
+
let chunk;
|
|
26885
27168
|
if (block.type === "paragraph" && block.text && /^\[별표\s*\d+/.test(sanitizeText(block.text))) {
|
|
26886
27169
|
const next = blocks[i + 1];
|
|
26887
27170
|
if (next?.type === "paragraph" && next.text && /관련\)?$/.test(next.text)) consume = 2;
|
|
26888
|
-
|
|
27171
|
+
chunk = blocksToMarkdown(blocks.slice(i, i + consume));
|
|
26889
27172
|
} else {
|
|
26890
|
-
|
|
27173
|
+
chunk = blocksToMarkdown([block]);
|
|
26891
27174
|
}
|
|
26892
|
-
if (
|
|
26893
|
-
const subUnits = splitMarkdownUnits(
|
|
27175
|
+
if (chunk) {
|
|
27176
|
+
const subUnits = splitMarkdownUnits(chunk);
|
|
26894
27177
|
const isFragment = consume === 2 || (block.type === "paragraph" || block.type === "heading") && subUnits.length > 1;
|
|
26895
27178
|
for (let s = 0; s < subUnits.length; s++) {
|
|
26896
27179
|
const u = { ...subUnits[s], blockIdx: i, fragment: isFragment || void 0 };
|
|
@@ -27157,6 +27440,9 @@ var Surgeon = class {
|
|
|
27157
27440
|
miniFatSectors = [];
|
|
27158
27441
|
dirSectors = [];
|
|
27159
27442
|
entries = [];
|
|
27443
|
+
/** replace()가 FREESECT로 해제한 섹터 — finish()에서 재할당 안 된 것만 0으로 지움 (데이터 잔존 방지) */
|
|
27444
|
+
freedSectors = [];
|
|
27445
|
+
freedMiniSectors = [];
|
|
27160
27446
|
constructor(file) {
|
|
27161
27447
|
if (file.length < SECTOR || file.readUInt32LE(0) !== 3759263696) {
|
|
27162
27448
|
throw new OleSurgeonError("OLE \uC2DC\uADF8\uB2C8\uCC98\uAC00 \uC544\uB2D9\uB2C8\uB2E4");
|
|
@@ -27280,6 +27566,8 @@ var Surgeon = class {
|
|
|
27280
27566
|
if (this.fat[i] !== FREESECT) continue;
|
|
27281
27567
|
if (SECTOR + (i + 1) * SECTOR > this.buf.length) continue;
|
|
27282
27568
|
this.fat[i] = ENDOFCHAIN;
|
|
27569
|
+
const off = this.sectorOffset(i);
|
|
27570
|
+
this.buf.fill(0, off, off + SECTOR);
|
|
27283
27571
|
out.push(i);
|
|
27284
27572
|
}
|
|
27285
27573
|
while (out.length < n) {
|
|
@@ -27379,9 +27667,15 @@ var Surgeon = class {
|
|
|
27379
27667
|
const entry = this.findEntry(path);
|
|
27380
27668
|
if (entry.size > 0 && entry.start !== ENDOFCHAIN) {
|
|
27381
27669
|
if (entry.size < MINI_CUTOFF) {
|
|
27382
|
-
for (const s of this.miniChain(entry.start))
|
|
27670
|
+
for (const s of this.miniChain(entry.start)) {
|
|
27671
|
+
this.miniFat[s] = FREESECT;
|
|
27672
|
+
this.freedMiniSectors.push(s);
|
|
27673
|
+
}
|
|
27383
27674
|
} else {
|
|
27384
|
-
for (const s of this.chain(entry.start))
|
|
27675
|
+
for (const s of this.chain(entry.start)) {
|
|
27676
|
+
this.fat[s] = FREESECT;
|
|
27677
|
+
this.freedSectors.push(s);
|
|
27678
|
+
}
|
|
27385
27679
|
}
|
|
27386
27680
|
}
|
|
27387
27681
|
if (newData.length < MINI_CUTOFF) {
|
|
@@ -27410,9 +27704,27 @@ var Surgeon = class {
|
|
|
27410
27704
|
this.writeDirEntry(entry);
|
|
27411
27705
|
}
|
|
27412
27706
|
finish() {
|
|
27707
|
+
this.wipeFreedSectors();
|
|
27413
27708
|
this.flushFat();
|
|
27414
27709
|
return this.buf;
|
|
27415
27710
|
}
|
|
27711
|
+
/** 해제 후 재할당되지 않고 남은 FREESECT 섹터의 바이트를 0으로 채움 (데이터 remanence 제거) */
|
|
27712
|
+
wipeFreedSectors() {
|
|
27713
|
+
for (const s of this.freedSectors) {
|
|
27714
|
+
if (this.fat[s] !== FREESECT) continue;
|
|
27715
|
+
const off = this.sectorOffset(s);
|
|
27716
|
+
this.buf.fill(0, off, off + SECTOR);
|
|
27717
|
+
}
|
|
27718
|
+
if (this.freedMiniSectors.length > 0) {
|
|
27719
|
+
const root = this.rootEntry();
|
|
27720
|
+
const rootChain = root.start === ENDOFCHAIN || root.size === 0 ? [] : this.chain(root.start);
|
|
27721
|
+
for (const s of this.freedMiniSectors) {
|
|
27722
|
+
if (this.miniFat[s] !== FREESECT) continue;
|
|
27723
|
+
const off = this.miniOffset(s, rootChain);
|
|
27724
|
+
this.buf.fill(0, off, off + MINI_SECTOR);
|
|
27725
|
+
}
|
|
27726
|
+
}
|
|
27727
|
+
}
|
|
27416
27728
|
};
|
|
27417
27729
|
|
|
27418
27730
|
// src/roundtrip/hwp5-patch.ts
|
|
@@ -28297,6 +28609,216 @@ async function validateHwpx(buffer) {
|
|
|
28297
28609
|
return { ok: issues.length === 0, issues, entryCount: names.length };
|
|
28298
28610
|
}
|
|
28299
28611
|
|
|
28612
|
+
// src/redact.ts
|
|
28613
|
+
var DEFAULT_REDACT_RULES = [
|
|
28614
|
+
"rrn",
|
|
28615
|
+
"phone",
|
|
28616
|
+
"email",
|
|
28617
|
+
"card",
|
|
28618
|
+
"account"
|
|
28619
|
+
];
|
|
28620
|
+
var RULE_PRIORITY = [
|
|
28621
|
+
"rrn",
|
|
28622
|
+
"email",
|
|
28623
|
+
"card",
|
|
28624
|
+
"phone",
|
|
28625
|
+
"driver",
|
|
28626
|
+
"account",
|
|
28627
|
+
"passport"
|
|
28628
|
+
];
|
|
28629
|
+
function luhnValid(digits) {
|
|
28630
|
+
let sum = 0;
|
|
28631
|
+
for (let i = 0; i < digits.length; i++) {
|
|
28632
|
+
let d = digits.charCodeAt(digits.length - 1 - i) - 48;
|
|
28633
|
+
if (i % 2 === 1) {
|
|
28634
|
+
d *= 2;
|
|
28635
|
+
if (d > 9) d -= 9;
|
|
28636
|
+
}
|
|
28637
|
+
sum += d;
|
|
28638
|
+
}
|
|
28639
|
+
return sum % 10 === 0;
|
|
28640
|
+
}
|
|
28641
|
+
function birthdateValid(front6) {
|
|
28642
|
+
const mm = Number(front6.slice(2, 4));
|
|
28643
|
+
const dd = Number(front6.slice(4, 6));
|
|
28644
|
+
return mm >= 1 && mm <= 12 && dd >= 1 && dd <= 31;
|
|
28645
|
+
}
|
|
28646
|
+
var RULES2 = {
|
|
28647
|
+
// 주민/외국인등록번호 — 뒷자리 첫 숫자 1-8 + 생년월일 유효성으로 오탐 축소.
|
|
28648
|
+
// 유니코드 대시 변형(‐ ‑ – —)은 rrn만 허용. 앞 6자리 유지, 뒤 7자리 전부 마스크.
|
|
28649
|
+
rrn: {
|
|
28650
|
+
pattern: /(?<!\d)(\d{6})([-‐‑–—])([1-8]\d{6})(?!\d)/g,
|
|
28651
|
+
validate: (m) => birthdateValid(m[1]),
|
|
28652
|
+
mask: (m, mc) => m[1] + m[2] + mc.repeat(7)
|
|
28653
|
+
},
|
|
28654
|
+
// 이메일 — 로컬파트 첫 글자만 남기고 마스크, 도메인 유지
|
|
28655
|
+
email: {
|
|
28656
|
+
pattern: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g,
|
|
28657
|
+
mask: (m, mc) => {
|
|
28658
|
+
const at = m[0].indexOf("@");
|
|
28659
|
+
return m[0][0] + mc.repeat(at - 1) + m[0].slice(at);
|
|
28660
|
+
}
|
|
28661
|
+
},
|
|
28662
|
+
// 카드번호 — 구분자 필수(무구분 16자리는 오탐 높아 제외), 동일 구분자 강제(\2),
|
|
28663
|
+
// Luhn 체크. 가운데 8자리 마스크.
|
|
28664
|
+
card: {
|
|
28665
|
+
pattern: /(?<!\d)(\d{4})([- ])(\d{4})\2(\d{4})\2(\d{4})(?!\d)/g,
|
|
28666
|
+
validate: (m) => luhnValid(m[1] + m[3] + m[4] + m[5]),
|
|
28667
|
+
mask: (m, mc) => m[1] + m[2] + mc.repeat(4) + m[2] + mc.repeat(4) + m[2] + m[5]
|
|
28668
|
+
},
|
|
28669
|
+
// 전화번호 — 휴대폰(01[016789])·서울(02)·지역(0[3-6]\d)·인터넷(070)은 구분자
|
|
28670
|
+
// -·.·공백 또는 무구분(동일 구분자 강제), 대표번호(15xx/16xx/18xx)는 구분자 필수.
|
|
28671
|
+
// 가운데 자리만 마스크 (대표번호는 뒤 4자리). 선행 [\d-] 금지 — 계좌 부분매치 방지.
|
|
28672
|
+
phone: {
|
|
28673
|
+
pattern: /(?<![\d-])(?:(01[016789]|070|02|0[3-6]\d)([-. ]?)(\d{3,4})\2(\d{4})|(1[568]\d{2})([-. ])(\d{4}))(?!\d)/g,
|
|
28674
|
+
mask: (m, mc) => m[1] !== void 0 ? m[1] + m[2] + mc.repeat(m[3].length) + m[2] + m[4] : m[5] + m[6] + mc.repeat(4)
|
|
28675
|
+
},
|
|
28676
|
+
// 운전면허 (기본 OFF) — 신형 12자리만 (지역명 2글자 선행 구버전은 스킵). 뒷 8자리 마스크.
|
|
28677
|
+
driver: {
|
|
28678
|
+
pattern: /(?<![\d-])(\d{2})-(\d{2})-\d{6}-\d{2}(?!-?\d)/g,
|
|
28679
|
+
mask: (m, mc) => m[1] + "-" + m[2] + "-" + mc.repeat(6) + "-" + mc.repeat(2)
|
|
28680
|
+
},
|
|
28681
|
+
// 계좌번호 — 3~4그룹 + 총 자릿수 10~16. rrn·card·phone과 겹치면 그쪽 우선.
|
|
28682
|
+
// 마지막 그룹 빼고 전부 마스크. 사업자등록번호(3-2-5, 10자리)도 걸린다 — 계약상 의도
|
|
28683
|
+
// (테스트로 명시). 날짜(2026-07-16)는 8자리라 총자릿수 검증에서 탈락.
|
|
28684
|
+
account: {
|
|
28685
|
+
pattern: /(?<!\d)(?<!\d-)\d{2,6}(?:-\d{2,6}){1,2}-\d{2,8}(?!-?\d)/g,
|
|
28686
|
+
validate: (m) => {
|
|
28687
|
+
const digits = m[0].replace(/-/g, "").length;
|
|
28688
|
+
return digits >= 10 && digits <= 16;
|
|
28689
|
+
},
|
|
28690
|
+
mask: (m, mc) => {
|
|
28691
|
+
const parts = m[0].split("-");
|
|
28692
|
+
return parts.map((p, i) => i === parts.length - 1 ? p : mc.repeat(p.length)).join("-");
|
|
28693
|
+
}
|
|
28694
|
+
},
|
|
28695
|
+
// 여권번호 (기본 OFF) — 단어 경계, 첫 글자만 남기고 전부 마스크
|
|
28696
|
+
passport: {
|
|
28697
|
+
pattern: /\b([MSRODG])\d{8}(?![0-9A-Za-z])/g,
|
|
28698
|
+
mask: (m, mc) => m[1] + mc.repeat(8)
|
|
28699
|
+
}
|
|
28700
|
+
};
|
|
28701
|
+
function redactText(text, options) {
|
|
28702
|
+
const maskChar = options?.maskChar ?? "\u25CF";
|
|
28703
|
+
if (maskChar.length !== 1 || /[0-9A-Za-z]/.test(maskChar)) {
|
|
28704
|
+
throw new Error(`maskChar\uB294 \uC601\uC22B\uC790\uAC00 \uC544\uB2CC 1\uAE00\uC790\uC5EC\uC57C \uD568: ${JSON.stringify(maskChar)}`);
|
|
28705
|
+
}
|
|
28706
|
+
const enabled = options?.rules ?? DEFAULT_REDACT_RULES;
|
|
28707
|
+
const hits = [];
|
|
28708
|
+
if (text === "" || enabled.length === 0) return { text, hits };
|
|
28709
|
+
const occupied = [];
|
|
28710
|
+
for (const rule of RULE_PRIORITY) {
|
|
28711
|
+
if (!enabled.includes(rule)) continue;
|
|
28712
|
+
const def = RULES2[rule];
|
|
28713
|
+
for (const m of text.matchAll(def.pattern)) {
|
|
28714
|
+
if (def.validate && !def.validate(m)) continue;
|
|
28715
|
+
const start = m.index;
|
|
28716
|
+
const end = start + m[0].length;
|
|
28717
|
+
if (occupied.some((o) => start < o.end && end > o.start)) continue;
|
|
28718
|
+
occupied.push({ start, end });
|
|
28719
|
+
hits.push({ rule, masked: def.mask(m, maskChar), index: start, length: m[0].length });
|
|
28720
|
+
}
|
|
28721
|
+
}
|
|
28722
|
+
hits.sort((a, b) => a.index - b.index);
|
|
28723
|
+
let out = "";
|
|
28724
|
+
let cursor = 0;
|
|
28725
|
+
for (const h of hits) {
|
|
28726
|
+
out += text.slice(cursor, h.index) + h.masked;
|
|
28727
|
+
cursor = h.index + h.length;
|
|
28728
|
+
}
|
|
28729
|
+
out += text.slice(cursor);
|
|
28730
|
+
return { text: out, hits };
|
|
28731
|
+
}
|
|
28732
|
+
function redactMarkdown(markdown, options) {
|
|
28733
|
+
const lines = markdown.split("\n");
|
|
28734
|
+
const hits = [];
|
|
28735
|
+
let offset = 0;
|
|
28736
|
+
const outLines = lines.map((line) => {
|
|
28737
|
+
if (line.includes("data:image/")) {
|
|
28738
|
+
offset += line.length + 1;
|
|
28739
|
+
return line;
|
|
28740
|
+
}
|
|
28741
|
+
const r = redactText(line, options);
|
|
28742
|
+
for (const h of r.hits) hits.push({ ...h, index: h.index + offset });
|
|
28743
|
+
offset += line.length + 1;
|
|
28744
|
+
return r.text;
|
|
28745
|
+
});
|
|
28746
|
+
return { text: outLines.join("\n"), hits };
|
|
28747
|
+
}
|
|
28748
|
+
|
|
28749
|
+
// src/chunks.ts
|
|
28750
|
+
var LIST_MARKER_RE = /^(?:[□■◇◆○◎●◦ㅇ•▪▸▶※-]|\d{1,3}[.)]|[가나다라마바사아자차카타파하][.)]|\([가나다라마바사아자차카타파하0-9]{1,3}\)|[①-⑳㉮-㉻㈎-㈛])\s/;
|
|
28751
|
+
function listDepthOf(block) {
|
|
28752
|
+
if (block.listDepth !== void 0) return block.listDepth;
|
|
28753
|
+
if (block.type === "list") return 0;
|
|
28754
|
+
if (block.type === "paragraph" && block.text && LIST_MARKER_RE.test(block.text.trim())) return 0;
|
|
28755
|
+
return void 0;
|
|
28756
|
+
}
|
|
28757
|
+
function sameBreadcrumb(a, b) {
|
|
28758
|
+
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
28759
|
+
}
|
|
28760
|
+
function blocksToChunks(blocks, options) {
|
|
28761
|
+
const granularity = options?.granularity ?? "section";
|
|
28762
|
+
const includeCells = options?.includeTableCells ?? false;
|
|
28763
|
+
const chunks = [];
|
|
28764
|
+
const headingStack = [];
|
|
28765
|
+
const listStack = [];
|
|
28766
|
+
const crumb = () => [...headingStack.map((h) => h.text), ...listStack.map((l) => l.text)];
|
|
28767
|
+
const push = (type, breadcrumb, text, blockRange, page, table2) => {
|
|
28768
|
+
const chunk = { id: "c" + String(chunks.length + 1).padStart(4, "0"), type, breadcrumb, text, blockRange };
|
|
28769
|
+
if (page !== void 0) chunk.page = page;
|
|
28770
|
+
if (table2) chunk.table = table2;
|
|
28771
|
+
chunks.push(chunk);
|
|
28772
|
+
};
|
|
28773
|
+
let run = null;
|
|
28774
|
+
const flushRun = () => {
|
|
28775
|
+
if (!run) return;
|
|
28776
|
+
push("text", run.breadcrumb, blocksToMarkdown(run.blocks), [run.start, run.end], run.page);
|
|
28777
|
+
run = null;
|
|
28778
|
+
};
|
|
28779
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
28780
|
+
const block = blocks[i];
|
|
28781
|
+
const md2 = blocksToMarkdown([block]);
|
|
28782
|
+
if (!md2) continue;
|
|
28783
|
+
if (block.type === "heading") {
|
|
28784
|
+
flushRun();
|
|
28785
|
+
const level = Math.min(block.level || 2, 6);
|
|
28786
|
+
while (headingStack.length && headingStack[headingStack.length - 1].level >= level) headingStack.pop();
|
|
28787
|
+
listStack.length = 0;
|
|
28788
|
+
push("heading", crumb(), md2, [i, i], block.pageNumber);
|
|
28789
|
+
headingStack.push({ level, text: (block.text ?? "").trim() });
|
|
28790
|
+
continue;
|
|
28791
|
+
}
|
|
28792
|
+
if (block.type === "table" && block.table) {
|
|
28793
|
+
flushRun();
|
|
28794
|
+
const summary = { rows: block.table.rows, cols: block.table.cols };
|
|
28795
|
+
if (includeCells) summary.cells = block.table.cells.map((row) => row.map((c) => c.text));
|
|
28796
|
+
push("table", crumb(), md2, [i, i], block.pageNumber, summary);
|
|
28797
|
+
continue;
|
|
28798
|
+
}
|
|
28799
|
+
const depth = listDepthOf(block);
|
|
28800
|
+
if (depth !== void 0) {
|
|
28801
|
+
while (listStack.length && listStack[listStack.length - 1].depth >= depth) listStack.pop();
|
|
28802
|
+
}
|
|
28803
|
+
const breadcrumb = crumb();
|
|
28804
|
+
if (depth !== void 0) listStack.push({ depth, text: (block.text ?? "").trim() });
|
|
28805
|
+
if (granularity === "block") {
|
|
28806
|
+
push("text", breadcrumb, md2, [i, i], block.pageNumber);
|
|
28807
|
+
continue;
|
|
28808
|
+
}
|
|
28809
|
+
if (run && sameBreadcrumb(run.breadcrumb, breadcrumb)) {
|
|
28810
|
+
run.blocks.push(block);
|
|
28811
|
+
run.end = i;
|
|
28812
|
+
if (run.page === void 0) run.page = block.pageNumber;
|
|
28813
|
+
} else {
|
|
28814
|
+
flushRun();
|
|
28815
|
+
run = { breadcrumb, blocks: [block], start: i, end: i, page: block.pageNumber };
|
|
28816
|
+
}
|
|
28817
|
+
}
|
|
28818
|
+
flushRun();
|
|
28819
|
+
return chunks;
|
|
28820
|
+
}
|
|
28821
|
+
|
|
28300
28822
|
// src/roundtrip/session.ts
|
|
28301
28823
|
import JSZip12 from "jszip";
|
|
28302
28824
|
async function buildState(bytes) {
|
|
@@ -29272,7 +29794,7 @@ function drawPara(p, ox, oy, areaW, ctx, depth, segPages) {
|
|
|
29272
29794
|
}
|
|
29273
29795
|
if (text.trim().length > 0) {
|
|
29274
29796
|
const attrs = [`x="${pt(cx)}"`, `y="${pt(y)}"`, `font-size="${pt(st.height)}"`];
|
|
29275
|
-
if (st.fontFamily) attrs.push(`font-family="${st.fontFamily}"`);
|
|
29797
|
+
if (st.fontFamily) attrs.push(`font-family="${escapeXml3(st.fontFamily)}"`);
|
|
29276
29798
|
if ([...text].length > 1 && sw > 50) {
|
|
29277
29799
|
attrs.push(`textLength="${pt(sw)}"`, `lengthAdjust="${plan.scale < 1 ? "spacingAndGlyphs" : "spacing"}"`);
|
|
29278
29800
|
}
|
|
@@ -29439,8 +29961,10 @@ function drawShape(o, x, y, ctx, depth) {
|
|
|
29439
29961
|
for (const p of elements2(sub)) if (ln2(p) === "p") drawPara(p, x, y, w, ctx, depth + 1);
|
|
29440
29962
|
}
|
|
29441
29963
|
}
|
|
29442
|
-
function cellContentExtent(cell2) {
|
|
29964
|
+
function cellContentExtent(cell2, memo) {
|
|
29443
29965
|
if (!cell2.sub) return 0;
|
|
29966
|
+
const hit = memo?.cell.get(cell2.el);
|
|
29967
|
+
if (hit !== void 0) return hit;
|
|
29444
29968
|
let ext = 0;
|
|
29445
29969
|
for (const p of elements2(cell2.sub)) {
|
|
29446
29970
|
if (ln2(p) !== "p") continue;
|
|
@@ -29449,7 +29973,7 @@ function cellContentExtent(cell2) {
|
|
|
29449
29973
|
const baseV = m.segs[0]?.vertpos ?? 0;
|
|
29450
29974
|
for (const o of m.objs) {
|
|
29451
29975
|
if (o.inline) {
|
|
29452
|
-
const h = o.tag === "tbl" ? Math.max(o.height, measureTableHeight(o.el)) : o.height;
|
|
29976
|
+
const h = o.tag === "tbl" ? Math.max(o.height, measureTableHeight(o.el, memo)) : o.height;
|
|
29453
29977
|
ext = Math.max(ext, baseV + h);
|
|
29454
29978
|
continue;
|
|
29455
29979
|
}
|
|
@@ -29461,6 +29985,7 @@ function cellContentExtent(cell2) {
|
|
|
29461
29985
|
ext = Math.max(ext, anchor + num3(om, "top") + num3(pos, "vertOffset") + o.height);
|
|
29462
29986
|
}
|
|
29463
29987
|
}
|
|
29988
|
+
memo?.cell.set(cell2.el, ext);
|
|
29464
29989
|
return ext;
|
|
29465
29990
|
}
|
|
29466
29991
|
function edgeLine(x1, y1, x2, y2, e) {
|
|
@@ -29483,8 +30008,9 @@ function collectCells(tbl2) {
|
|
|
29483
30008
|
if (!addr || !csz) continue;
|
|
29484
30009
|
cells.push({
|
|
29485
30010
|
el: tc2,
|
|
29486
|
-
|
|
29487
|
-
|
|
30011
|
+
// 음수 주소(uint32 역변환·손상 입력) 방어 — colX/rowY 인덱스 이탈로 NaN 좌표 방지
|
|
30012
|
+
ca: Math.max(0, num3(addr, "colAddr")),
|
|
30013
|
+
ra: Math.max(0, num3(addr, "rowAddr")),
|
|
29488
30014
|
cs: Math.max(1, num3(span, "colSpan", 1)),
|
|
29489
30015
|
rs: Math.max(1, num3(span, "rowSpan", 1)),
|
|
29490
30016
|
w: num3(csz, "width"),
|
|
@@ -29500,16 +30026,19 @@ function collectCells(tbl2) {
|
|
|
29500
30026
|
}
|
|
29501
30027
|
return cells;
|
|
29502
30028
|
}
|
|
29503
|
-
function measureTableHeight(tbl2) {
|
|
30029
|
+
function measureTableHeight(tbl2, memo) {
|
|
30030
|
+
const hit = memo?.table.get(tbl2);
|
|
30031
|
+
if (hit !== void 0) return hit;
|
|
29504
30032
|
const cells = collectCells(tbl2);
|
|
29505
30033
|
if (cells.length === 0 || cells.length > 4096) return 0;
|
|
29506
30034
|
const nRows = Math.max(...cells.map((c) => c.ra + c.rs));
|
|
29507
30035
|
const rowH = solveRowHeights(
|
|
29508
|
-
cells.map((c) => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c) : void 0 })),
|
|
30036
|
+
cells.map((c) => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c, memo) : void 0 })),
|
|
29509
30037
|
nRows
|
|
29510
30038
|
);
|
|
29511
30039
|
let sum = 0;
|
|
29512
30040
|
for (const h of rowH) sum += h;
|
|
30041
|
+
memo?.table.set(tbl2, sum);
|
|
29513
30042
|
return sum;
|
|
29514
30043
|
}
|
|
29515
30044
|
function drawTable(tbl2, tx, ty, ctx, depth) {
|
|
@@ -29526,7 +30055,7 @@ function drawTable(tbl2, tx, ty, ctx, depth) {
|
|
|
29526
30055
|
const colCons = cells.map((c) => ({ a: c.ca, b: c.ca + c.cs, size: c.w }));
|
|
29527
30056
|
const colX = solveBoundaries(colCons, nCols, num3(tblSz, "width") || void 0);
|
|
29528
30057
|
const rowH = solveRowHeights(
|
|
29529
|
-
cells.map((c) => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c) : void 0 })),
|
|
30058
|
+
cells.map((c) => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c, ctx.extentMemo) : void 0 })),
|
|
29530
30059
|
nRows
|
|
29531
30060
|
);
|
|
29532
30061
|
const rowY = [0];
|
|
@@ -29546,7 +30075,7 @@ function drawTable(tbl2, tx, ty, ctx, depth) {
|
|
|
29546
30075
|
const { c } = g;
|
|
29547
30076
|
if (!c.sub) continue;
|
|
29548
30077
|
const innerH = g.h - c.marginT - c.marginB;
|
|
29549
|
-
const extent = cellContentExtent(c);
|
|
30078
|
+
const extent = cellContentExtent(c, ctx.extentMemo);
|
|
29550
30079
|
const va = c.sub.getAttribute("vertAlign") ?? "TOP";
|
|
29551
30080
|
let yoff = 0;
|
|
29552
30081
|
if (va === "CENTER") yoff = Math.max(0, (innerH - extent) / 2);
|
|
@@ -29609,8 +30138,9 @@ function sniffMime(name, bytes) {
|
|
|
29609
30138
|
const lower = name.toLowerCase();
|
|
29610
30139
|
if (lower.endsWith(".png") || bytes.length > 4 && bytes[0] === 137 && bytes[1] === 80) return "image/png";
|
|
29611
30140
|
if (lower.endsWith(".bmp") || bytes.length > 2 && bytes[0] === 66 && bytes[1] === 77) return "image/bmp";
|
|
29612
|
-
if (lower.endsWith(".gif")) return "image/gif";
|
|
30141
|
+
if (lower.endsWith(".gif") || bytes.length > 3 && bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70) return "image/gif";
|
|
29613
30142
|
if (lower.endsWith(".svg")) return "image/svg+xml";
|
|
30143
|
+
if (bytes.length > 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
|
|
29614
30144
|
return "image/jpeg";
|
|
29615
30145
|
}
|
|
29616
30146
|
function readSectionGeom(root) {
|
|
@@ -29741,7 +30271,8 @@ async function renderHwpxToSvg(input, options) {
|
|
|
29741
30271
|
highlights: (options?.highlights ?? []).map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0),
|
|
29742
30272
|
warnings,
|
|
29743
30273
|
warned: /* @__PURE__ */ new Set(),
|
|
29744
|
-
stats: { texts: 0, images: 0, tables: 0 }
|
|
30274
|
+
stats: { texts: 0, images: 0, tables: 0 },
|
|
30275
|
+
extentMemo: { cell: /* @__PURE__ */ new WeakMap(), table: /* @__PURE__ */ new WeakMap() }
|
|
29745
30276
|
};
|
|
29746
30277
|
const rendered = [];
|
|
29747
30278
|
let noCacheSkipped = false;
|
|
@@ -29844,7 +30375,7 @@ async function parseHwp3(buffer, options) {
|
|
|
29844
30375
|
const { markdown, blocks, metadata, outline, warnings } = parseHwp3Document(buffer, options);
|
|
29845
30376
|
return { success: true, fileType: "hwp3", markdown, blocks, metadata, outline, warnings };
|
|
29846
30377
|
} catch (err) {
|
|
29847
|
-
return { success: false, fileType: "hwp3", error: err
|
|
30378
|
+
return { success: false, fileType: "hwp3", error: sanitizeError(err), code: classifyError(err) };
|
|
29848
30379
|
}
|
|
29849
30380
|
}
|
|
29850
30381
|
async function parseHwpx(buffer, options) {
|
|
@@ -29852,7 +30383,7 @@ async function parseHwpx(buffer, options) {
|
|
|
29852
30383
|
const { markdown, blocks, metadata, outline, warnings, images } = await parseHwpxDocument(buffer, options);
|
|
29853
30384
|
return { success: true, fileType: "hwpx", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0 };
|
|
29854
30385
|
} catch (err) {
|
|
29855
|
-
return { success: false, fileType: "hwpx", error: err
|
|
30386
|
+
return { success: false, fileType: "hwpx", error: sanitizeError(err), code: classifyError(err) };
|
|
29856
30387
|
}
|
|
29857
30388
|
}
|
|
29858
30389
|
async function parseHwp(buffer, options) {
|
|
@@ -29877,13 +30408,13 @@ async function parseHwp(buffer, options) {
|
|
|
29877
30408
|
}
|
|
29878
30409
|
return { success: true, fileType: "hwp", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0 };
|
|
29879
30410
|
} catch (err) {
|
|
29880
|
-
return { success: false, fileType: "hwp", error: err
|
|
30411
|
+
return { success: false, fileType: "hwp", error: sanitizeError(err), code: classifyError(err) };
|
|
29881
30412
|
}
|
|
29882
30413
|
}
|
|
29883
30414
|
async function parsePdf(buffer, options) {
|
|
29884
30415
|
let parsePdfDocument;
|
|
29885
30416
|
try {
|
|
29886
|
-
const mod = await import("./parser-
|
|
30417
|
+
const mod = await import("./parser-XX4QTDFQ.js");
|
|
29887
30418
|
parsePdfDocument = mod.parsePdfDocument;
|
|
29888
30419
|
} catch {
|
|
29889
30420
|
return {
|
|
@@ -29894,11 +30425,11 @@ async function parsePdf(buffer, options) {
|
|
|
29894
30425
|
};
|
|
29895
30426
|
}
|
|
29896
30427
|
try {
|
|
29897
|
-
const { markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary } = await parsePdfDocument(buffer, options);
|
|
29898
|
-
return { success: true, fileType: "pdf", markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary };
|
|
30428
|
+
const { markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary, images } = await parsePdfDocument(buffer, options);
|
|
30429
|
+
return { success: true, fileType: "pdf", markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary, images };
|
|
29899
30430
|
} catch (err) {
|
|
29900
30431
|
const isImageBased = err instanceof Error && "isImageBased" in err ? true : void 0;
|
|
29901
|
-
return { success: false, fileType: "pdf", error: err
|
|
30432
|
+
return { success: false, fileType: "pdf", error: sanitizeError(err), code: classifyError(err), isImageBased };
|
|
29902
30433
|
}
|
|
29903
30434
|
}
|
|
29904
30435
|
async function parseXlsx(buffer, options) {
|
|
@@ -29906,7 +30437,7 @@ async function parseXlsx(buffer, options) {
|
|
|
29906
30437
|
const { markdown, blocks, metadata, warnings } = await parseXlsxDocument(buffer, options);
|
|
29907
30438
|
return { success: true, fileType: "xlsx", markdown, blocks, metadata, warnings };
|
|
29908
30439
|
} catch (err) {
|
|
29909
|
-
return { success: false, fileType: "xlsx", error: err
|
|
30440
|
+
return { success: false, fileType: "xlsx", error: sanitizeError(err), code: classifyError(err) };
|
|
29910
30441
|
}
|
|
29911
30442
|
}
|
|
29912
30443
|
async function parseXls(buffer, options) {
|
|
@@ -29914,7 +30445,7 @@ async function parseXls(buffer, options) {
|
|
|
29914
30445
|
const { markdown, blocks, metadata, warnings } = await parseXlsDocument(buffer, options);
|
|
29915
30446
|
return { success: true, fileType: "xls", markdown, blocks, metadata, warnings };
|
|
29916
30447
|
} catch (err) {
|
|
29917
|
-
return { success: false, fileType: "xls", error: err
|
|
30448
|
+
return { success: false, fileType: "xls", error: sanitizeError(err), code: classifyError(err) };
|
|
29918
30449
|
}
|
|
29919
30450
|
}
|
|
29920
30451
|
async function parseDocx(buffer, options) {
|
|
@@ -29922,7 +30453,7 @@ async function parseDocx(buffer, options) {
|
|
|
29922
30453
|
const { markdown, blocks, metadata, outline, warnings, images } = await parseDocxDocument(buffer, options);
|
|
29923
30454
|
return { success: true, fileType: "docx", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0 };
|
|
29924
30455
|
} catch (err) {
|
|
29925
|
-
return { success: false, fileType: "docx", error: err
|
|
30456
|
+
return { success: false, fileType: "docx", error: sanitizeError(err), code: classifyError(err) };
|
|
29926
30457
|
}
|
|
29927
30458
|
}
|
|
29928
30459
|
async function parseHwpml(buffer, options) {
|
|
@@ -29930,7 +30461,7 @@ async function parseHwpml(buffer, options) {
|
|
|
29930
30461
|
const { markdown, blocks, metadata, outline, warnings } = parseHwpmlDocument(buffer, options);
|
|
29931
30462
|
return { success: true, fileType: "hwpml", markdown, blocks, metadata, outline, warnings };
|
|
29932
30463
|
} catch (err) {
|
|
29933
|
-
return { success: false, fileType: "hwpml", error: err
|
|
30464
|
+
return { success: false, fileType: "hwpml", error: sanitizeError(err), code: classifyError(err) };
|
|
29934
30465
|
}
|
|
29935
30466
|
}
|
|
29936
30467
|
async function fillForm(input, values, outputFormat = "markdown") {
|
|
@@ -29973,6 +30504,7 @@ async function fillForm(input, values, outputFormat = "markdown") {
|
|
|
29973
30504
|
return { output: markdown, format: "markdown", fill };
|
|
29974
30505
|
}
|
|
29975
30506
|
export {
|
|
30507
|
+
DEFAULT_REDACT_RULES,
|
|
29976
30508
|
HwpxSession,
|
|
29977
30509
|
PRESET_ALIAS,
|
|
29978
30510
|
SPACE_EM_FIXED,
|
|
@@ -29980,6 +30512,7 @@ export {
|
|
|
29980
30512
|
VERSION,
|
|
29981
30513
|
ValueCursor,
|
|
29982
30514
|
applySplices,
|
|
30515
|
+
blocksToChunks,
|
|
29983
30516
|
blocksToMarkdown,
|
|
29984
30517
|
blocksToPdf,
|
|
29985
30518
|
buildParagraphSplices,
|
|
@@ -30027,6 +30560,8 @@ export {
|
|
|
30027
30560
|
patchHwpx,
|
|
30028
30561
|
patchHwpxBlocks,
|
|
30029
30562
|
placeSealHwpx,
|
|
30563
|
+
redactMarkdown,
|
|
30564
|
+
redactText,
|
|
30030
30565
|
renderHtml,
|
|
30031
30566
|
renderHwpxToSvg,
|
|
30032
30567
|
scanSectionXml,
|