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
|
@@ -3,24 +3,17 @@ import {
|
|
|
3
3
|
HEADING_RATIO_H1,
|
|
4
4
|
HEADING_RATIO_H2,
|
|
5
5
|
HEADING_RATIO_H3,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
blocksToMarkdown,
|
|
9
|
-
buildTable,
|
|
10
|
-
convertTableToText,
|
|
11
|
-
dedupeRunningHeaders,
|
|
12
|
-
flattenLayoutTables,
|
|
13
|
-
mapPuaText
|
|
14
|
-
} from "./chunk-2SN3CKME.js";
|
|
6
|
+
inlineImagesIntoMarkdown
|
|
7
|
+
} from "./chunk-UOO7LSDG.js";
|
|
15
8
|
import {
|
|
16
9
|
detectFormat,
|
|
17
10
|
detectOle2Format,
|
|
18
11
|
detectZipFormat,
|
|
19
12
|
parseLenientCfb
|
|
20
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-YOO6ET6M.js";
|
|
21
14
|
import {
|
|
22
15
|
parsePageRange
|
|
23
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-MOL7MDBG.js";
|
|
24
17
|
import {
|
|
25
18
|
allLinesegRemovalSplices,
|
|
26
19
|
applySplices,
|
|
@@ -32,25 +25,36 @@ import {
|
|
|
32
25
|
paraTextPureT,
|
|
33
26
|
patchZipEntries,
|
|
34
27
|
scanSectionXml
|
|
35
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-AX2R5Q2N.js";
|
|
29
|
+
import {
|
|
30
|
+
MAX_COLS,
|
|
31
|
+
MAX_ROWS,
|
|
32
|
+
blocksToMarkdown,
|
|
33
|
+
buildTable,
|
|
34
|
+
convertTableToText,
|
|
35
|
+
dedupeRunningHeaders,
|
|
36
|
+
flattenLayoutTables,
|
|
37
|
+
mapPuaText
|
|
38
|
+
} from "./chunk-SSTK6IKK.js";
|
|
36
39
|
import {
|
|
37
40
|
SPACE_EM_FIXED,
|
|
38
41
|
charWidthEm1000,
|
|
39
42
|
fitRatioForFewerLines,
|
|
40
43
|
measureTextWidth,
|
|
41
44
|
simulateWrap
|
|
42
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-37HJHCPA.js";
|
|
43
46
|
import {
|
|
44
47
|
MAX_DECOMPRESS_SIZE,
|
|
45
48
|
MAX_XML_DEPTH,
|
|
46
49
|
MAX_ZIP_ENTRIES,
|
|
50
|
+
ZipBombError,
|
|
47
51
|
applyPageText,
|
|
48
52
|
clampSpan,
|
|
49
53
|
createSectionShared,
|
|
50
54
|
createXmlParser,
|
|
51
55
|
extractTextFromNode,
|
|
52
56
|
findChildByLocalName
|
|
53
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-GQVSHGG4.js";
|
|
54
58
|
import {
|
|
55
59
|
KordocError,
|
|
56
60
|
classifyError,
|
|
@@ -58,10 +62,11 @@ import {
|
|
|
58
62
|
isPathTraversal,
|
|
59
63
|
normalizeSectionHref,
|
|
60
64
|
precheckZipSize,
|
|
65
|
+
sanitizeError,
|
|
61
66
|
sanitizeHref,
|
|
62
67
|
stripDtd,
|
|
63
68
|
toArrayBuffer
|
|
64
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-O5VS7RIR.js";
|
|
65
70
|
|
|
66
71
|
// src/index.ts
|
|
67
72
|
import { readFile } from "fs/promises";
|
|
@@ -1276,6 +1281,7 @@ function toRoman(n) {
|
|
|
1276
1281
|
return out;
|
|
1277
1282
|
}
|
|
1278
1283
|
function formatHeadNumber(n, numFormat) {
|
|
1284
|
+
if (n === 0 && numFormat === "DIGIT") return "0";
|
|
1279
1285
|
if (n <= 0) n = 1;
|
|
1280
1286
|
switch (numFormat) {
|
|
1281
1287
|
case "DIGIT":
|
|
@@ -1333,25 +1339,25 @@ function resolveParaHeading(paraEl, ctx) {
|
|
|
1333
1339
|
if (!numDef) return headingLevel ? { headingLevel } : null;
|
|
1334
1340
|
let counters = ctx.shared.numState.get(numId);
|
|
1335
1341
|
if (!counters) {
|
|
1336
|
-
counters = new Array(11).fill(
|
|
1342
|
+
counters = new Array(11).fill(-1);
|
|
1337
1343
|
ctx.shared.numState.set(numId, counters);
|
|
1338
1344
|
}
|
|
1339
1345
|
const head = numDef.heads.get(level);
|
|
1340
|
-
counters[level] = counters[level]
|
|
1341
|
-
for (let l = level + 1; l <= 10; l++) counters[l] =
|
|
1346
|
+
counters[level] = counters[level] < 0 ? head?.start ?? 1 : counters[level] + 1;
|
|
1347
|
+
for (let l = level + 1; l <= 10; l++) counters[l] = -1;
|
|
1342
1348
|
const fmtText = head ? head.text.trim() : `^${level}.`;
|
|
1343
1349
|
const prefix = fmtText.replace(/\^(10|[1-9])/g, (_, d) => {
|
|
1344
1350
|
const lv = parseInt(d, 10);
|
|
1345
1351
|
const refHead = numDef.heads.get(lv);
|
|
1346
|
-
const n = counters[lv]
|
|
1352
|
+
const n = counters[lv] >= 0 ? counters[lv] : refHead?.start ?? 1;
|
|
1347
1353
|
return formatHeadNumber(n, refHead?.numFormat || "DIGIT");
|
|
1348
1354
|
});
|
|
1349
1355
|
return { prefix: prefix || void 0, headingLevel };
|
|
1350
1356
|
}
|
|
1351
1357
|
|
|
1352
1358
|
// src/hwpx/table-build.ts
|
|
1353
|
-
function buildTableWithCellMeta(state) {
|
|
1354
|
-
const table2 = buildTable(state.rows);
|
|
1359
|
+
function buildTableWithCellMeta(state, keepAnchoredEmptyCols) {
|
|
1360
|
+
const table2 = buildTable(state.rows, { keepAnchoredEmptyCols });
|
|
1355
1361
|
if (state.caption) table2.caption = state.caption;
|
|
1356
1362
|
const anchors = [];
|
|
1357
1363
|
{
|
|
@@ -1415,7 +1421,7 @@ function completeTable(newTable, tableStack, blocks, ctx) {
|
|
|
1415
1421
|
if (newTable.caption) blocks.push({ type: "paragraph", text: newTable.caption, pageNumber: ctx.sectionNum });
|
|
1416
1422
|
return parentTable;
|
|
1417
1423
|
}
|
|
1418
|
-
const ir = buildTableWithCellMeta(newTable);
|
|
1424
|
+
const ir = buildTableWithCellMeta(newTable, ctx.shared.keepTrailingEmptyCols);
|
|
1419
1425
|
const block = { type: "table", table: ir, pageNumber: ctx.sectionNum };
|
|
1420
1426
|
if (parentTable?.cell) {
|
|
1421
1427
|
const cell2 = parentTable.cell;
|
|
@@ -1449,7 +1455,8 @@ function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
|
|
|
1449
1455
|
walkSection(doc.documentElement, blocks, null, [], ctx);
|
|
1450
1456
|
return blocks;
|
|
1451
1457
|
}
|
|
1452
|
-
function extractImageRef(el) {
|
|
1458
|
+
function extractImageRef(el, depth = 0) {
|
|
1459
|
+
if (depth > MAX_XML_DEPTH) return null;
|
|
1453
1460
|
const children = el.childNodes;
|
|
1454
1461
|
if (!children) return null;
|
|
1455
1462
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -1460,7 +1467,7 @@ function extractImageRef(el) {
|
|
|
1460
1467
|
const ref = child.getAttribute("binaryItemIDRef") || child.getAttribute("href") || "";
|
|
1461
1468
|
if (ref) return ref;
|
|
1462
1469
|
}
|
|
1463
|
-
const nested = extractImageRef(child);
|
|
1470
|
+
const nested = extractImageRef(child, depth + 1);
|
|
1464
1471
|
if (nested) return nested;
|
|
1465
1472
|
}
|
|
1466
1473
|
const directRef = el.getAttribute("binaryItemIDRef") || "";
|
|
@@ -1567,7 +1574,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
|
|
|
1567
1574
|
;
|
|
1568
1575
|
(cell2.blocks ??= []).push(cellBlock);
|
|
1569
1576
|
} else if (!tableCtx) {
|
|
1570
|
-
if (/^─{10,}$/.test(text)) {
|
|
1577
|
+
if (ctx.shared.kordocLayout && /^─{10,}$/.test(text)) {
|
|
1571
1578
|
blocks.push({ type: "separator", pageNumber: ctx.sectionNum });
|
|
1572
1579
|
tableCtx = walkParagraphChildren(el, blocks, tableCtx, tableStack, ctx, depth + 1);
|
|
1573
1580
|
break;
|
|
@@ -1843,8 +1850,8 @@ function extractDrawTextBlocks(drawTextNode, blocks, ctx) {
|
|
|
1843
1850
|
} else {
|
|
1844
1851
|
const info = extractParagraphInfo(child, ctx.styleMap, ctx);
|
|
1845
1852
|
let text = info.text.trim();
|
|
1853
|
+
const ph = resolveParaHeading(child, ctx);
|
|
1846
1854
|
if (text) {
|
|
1847
|
-
const ph = resolveParaHeading(child, ctx);
|
|
1848
1855
|
if (ph?.prefix) text = ph.prefix + " " + text;
|
|
1849
1856
|
const block = { type: "paragraph", text, style: info.style ?? void 0, pageNumber: ctx.sectionNum };
|
|
1850
1857
|
if (info.href) block.href = info.href;
|
|
@@ -1957,7 +1964,8 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
1957
1964
|
}
|
|
1958
1965
|
}
|
|
1959
1966
|
};
|
|
1960
|
-
const walk = (node) => {
|
|
1967
|
+
const walk = (node, depth = 0) => {
|
|
1968
|
+
if (depth > MAX_XML_DEPTH) return;
|
|
1961
1969
|
const children = node.childNodes;
|
|
1962
1970
|
if (!children) return;
|
|
1963
1971
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -1978,7 +1986,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
1978
1986
|
const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
|
|
1979
1987
|
switch (tag) {
|
|
1980
1988
|
case "t":
|
|
1981
|
-
walk(child);
|
|
1989
|
+
walk(child, depth + 1);
|
|
1982
1990
|
break;
|
|
1983
1991
|
// 자식 순회 (tab 등 하위 요소 처리)
|
|
1984
1992
|
case "tab": {
|
|
@@ -2011,7 +2019,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2011
2019
|
const safe = sanitizeHref(url);
|
|
2012
2020
|
if (safe) href = safe;
|
|
2013
2021
|
}
|
|
2014
|
-
walk(child);
|
|
2022
|
+
walk(child, depth + 1);
|
|
2015
2023
|
break;
|
|
2016
2024
|
}
|
|
2017
2025
|
// 각주/미주
|
|
@@ -2082,11 +2090,11 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2082
2090
|
case "r": {
|
|
2083
2091
|
const runCharPr = child.getAttribute("charPrIDRef");
|
|
2084
2092
|
if (runCharPr && !charPrId) charPrId = runCharPr;
|
|
2085
|
-
walk(child);
|
|
2093
|
+
walk(child, depth + 1);
|
|
2086
2094
|
break;
|
|
2087
2095
|
}
|
|
2088
2096
|
default:
|
|
2089
|
-
walk(child);
|
|
2097
|
+
walk(child, depth + 1);
|
|
2090
2098
|
break;
|
|
2091
2099
|
}
|
|
2092
2100
|
}
|
|
@@ -2097,7 +2105,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2097
2105
|
let cleanText = text.replace(/[ \t]+/g, " ").trim();
|
|
2098
2106
|
if (/^그림입니다\.?\s*원본\s*그림의\s*(이름|크기)/.test(cleanText)) cleanText = "";
|
|
2099
2107
|
cleanText = cleanText.replace(/그림입니다\.?\s*원본\s*그림의\s*(이름|크기)[^\n]*(\n[^\n]*원본\s*그림의\s*(이름|크기)[^\n]*)*/g, "").trim();
|
|
2100
|
-
cleanText = cleanText.replace(
|
|
2108
|
+
cleanText = cleanText.replace(/^(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|선|직선|곡선|화살표|오각형|육각형|팔각형|별|십자|구름|마름모|도넛|평행사변형|사다리꼴|개체|그리기\s?개체|묶음\s?개체|글상자|표|그림|OLE\s?개체)\s?입니다\.?$/gm, "").trim();
|
|
2101
2109
|
let style;
|
|
2102
2110
|
if (styleMap && charPrId) {
|
|
2103
2111
|
const charProp = styleMap.charProperties.get(charPrId);
|
|
@@ -2199,641 +2207,818 @@ function gongmunDepthFromIndent(para2, left, ctx) {
|
|
|
2199
2207
|
return depth >= 1 && depth < 8 ? depth : null;
|
|
2200
2208
|
}
|
|
2201
2209
|
|
|
2202
|
-
// src/
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2210
|
+
// src/hwp5/record.ts
|
|
2211
|
+
import { inflateRawSync, inflateSync } from "zlib";
|
|
2212
|
+
var TAG_PARA_HEADER = 66;
|
|
2213
|
+
var TAG_PARA_TEXT = 67;
|
|
2214
|
+
var TAG_CHAR_SHAPE = 68;
|
|
2215
|
+
var TAG_CTRL_HEADER = 71;
|
|
2216
|
+
var TAG_LIST_HEADER = 72;
|
|
2217
|
+
var TAG_TABLE = 77;
|
|
2218
|
+
var TAG_SHAPE_COMPONENT = 76;
|
|
2219
|
+
var TAG_SHAPE_COMPONENT_PICTURE = 85;
|
|
2220
|
+
var TAG_SHAPE_COMPONENT_CONTAINER = 86;
|
|
2221
|
+
var TAG_EQEDIT = 88;
|
|
2222
|
+
var TAG_BIN_DATA = 18;
|
|
2223
|
+
var TAG_DOC_CHAR_SHAPE = 21;
|
|
2224
|
+
var TAG_NUMBERING = 23;
|
|
2225
|
+
var TAG_BULLET = 24;
|
|
2226
|
+
var TAG_DOC_PARA_SHAPE = 25;
|
|
2227
|
+
var TAG_DOC_STYLE = 26;
|
|
2228
|
+
var CHAR_LINE = 0;
|
|
2229
|
+
var CHAR_SECTION_BREAK = 10;
|
|
2230
|
+
var CHAR_PARA = 13;
|
|
2231
|
+
var CHAR_TAB = 9;
|
|
2232
|
+
var CHAR_HYPHEN = 24;
|
|
2233
|
+
var CHAR_NBSP = 30;
|
|
2234
|
+
var CHAR_FIXED_WIDTH = 31;
|
|
2235
|
+
var FLAG_COMPRESSED = 1 << 0;
|
|
2236
|
+
var FLAG_ENCRYPTED = 1 << 1;
|
|
2237
|
+
var FLAG_DISTRIBUTION = 1 << 2;
|
|
2238
|
+
var FLAG_DRM = 1 << 4;
|
|
2239
|
+
var MAX_RECORDS = 5e5;
|
|
2240
|
+
function readRecords(data) {
|
|
2241
|
+
const records = [];
|
|
2242
|
+
let offset = 0;
|
|
2243
|
+
while (offset + 4 <= data.length && records.length < MAX_RECORDS) {
|
|
2244
|
+
const header = data.readUInt32LE(offset);
|
|
2245
|
+
offset += 4;
|
|
2246
|
+
const tagId = header & 1023;
|
|
2247
|
+
const level = header >> 10 & 1023;
|
|
2248
|
+
let size = header >> 20 & 4095;
|
|
2249
|
+
if (size === 4095) {
|
|
2250
|
+
if (offset + 4 > data.length) break;
|
|
2251
|
+
size = data.readUInt32LE(offset);
|
|
2252
|
+
offset += 4;
|
|
2253
|
+
}
|
|
2254
|
+
if (offset + size > data.length) break;
|
|
2255
|
+
records.push({ tagId, level, size, data: data.subarray(offset, offset + size) });
|
|
2256
|
+
offset += size;
|
|
2225
2257
|
}
|
|
2258
|
+
return records;
|
|
2226
2259
|
}
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
if (
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
if (mime.includes("emf")) return "emf";
|
|
2235
|
-
if (mime.includes("svg")) return "svg";
|
|
2236
|
-
return "bin";
|
|
2237
|
-
}
|
|
2238
|
-
function collectImageBlocks(blocks, out, ownerCell, depth = 0) {
|
|
2239
|
-
if (depth > MAX_XML_DEPTH) return;
|
|
2240
|
-
for (const block of blocks) {
|
|
2241
|
-
if (block.type === "image") {
|
|
2242
|
-
out.push({ block, ownerCell });
|
|
2243
|
-
} else if (block.type === "table" && block.table) {
|
|
2244
|
-
for (const row of block.table.cells) {
|
|
2245
|
-
for (const cell2 of row) {
|
|
2246
|
-
if (cell2.blocks?.length) collectImageBlocks(cell2.blocks, out, cell2, depth + 1);
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2260
|
+
var MAX_DECOMPRESS_SIZE2 = 100 * 1024 * 1024;
|
|
2261
|
+
function decompressStream(data) {
|
|
2262
|
+
const opts = { maxOutputLength: MAX_DECOMPRESS_SIZE2 };
|
|
2263
|
+
if (data.length >= 2 && data[0] === 120) {
|
|
2264
|
+
try {
|
|
2265
|
+
return inflateSync(data, opts);
|
|
2266
|
+
} catch {
|
|
2249
2267
|
}
|
|
2250
2268
|
}
|
|
2269
|
+
return inflateRawSync(data, opts);
|
|
2251
2270
|
}
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2271
|
+
function parseFileHeader(data) {
|
|
2272
|
+
if (data.length < 40) throw new KordocError("FileHeader\uAC00 \uB108\uBB34 \uC9E7\uC2B5\uB2C8\uB2E4 (\uCD5C\uC18C 40\uBC14\uC774\uD2B8)");
|
|
2273
|
+
const sig = data.subarray(0, 32).toString("utf8").replace(/\0+$/, "");
|
|
2274
|
+
return {
|
|
2275
|
+
signature: sig,
|
|
2276
|
+
versionMajor: data[35],
|
|
2277
|
+
flags: data.readUInt32LE(36)
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
function readHwpString(data, offset) {
|
|
2281
|
+
if (offset + 2 > data.length) return { value: "", next: data.length };
|
|
2282
|
+
const len = data.readUInt16LE(offset);
|
|
2283
|
+
const start = offset + 2;
|
|
2284
|
+
const end = start + len * 2;
|
|
2285
|
+
if (len === 0 || end > data.length) return { value: "", next: start };
|
|
2286
|
+
return { value: data.subarray(start, end).toString("utf16le"), next: end };
|
|
2287
|
+
}
|
|
2288
|
+
function parseDocInfo(records) {
|
|
2289
|
+
const charShapes = [];
|
|
2290
|
+
const paraShapes = [];
|
|
2291
|
+
const styles = [];
|
|
2292
|
+
const binData = [];
|
|
2293
|
+
const numberings = [];
|
|
2294
|
+
const bullets = [];
|
|
2295
|
+
for (const rec of records) {
|
|
2296
|
+
if (rec.tagId === TAG_DOC_PARA_SHAPE && rec.data.length >= 4) {
|
|
2297
|
+
const attr1 = rec.data.readUInt32LE(0);
|
|
2298
|
+
const headType = attr1 >>> 23 & 3;
|
|
2299
|
+
const paraLevel = attr1 >>> 25 & 7;
|
|
2300
|
+
const numberingId = rec.data.length >= 32 ? rec.data.readUInt16LE(30) : 0;
|
|
2301
|
+
paraShapes.push({ headType, paraLevel, numberingId });
|
|
2302
|
+
}
|
|
2303
|
+
if (rec.tagId === TAG_BIN_DATA && rec.data.length >= 2) {
|
|
2304
|
+
const attr = rec.data.readUInt16LE(0);
|
|
2305
|
+
const typeBits = attr & 15;
|
|
2306
|
+
if (typeBits === 0) {
|
|
2307
|
+
binData.push({ kind: "link", storageId: 0, extension: "" });
|
|
2308
|
+
} else {
|
|
2309
|
+
const storageId = rec.data.length >= 4 ? rec.data.readUInt16LE(2) : 0;
|
|
2310
|
+
const { value: extension } = readHwpString(rec.data, 4);
|
|
2311
|
+
binData.push({ kind: typeBits === 2 ? "storage" : "embed", storageId, extension });
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
if (rec.tagId === TAG_NUMBERING && rec.data.length >= 14) {
|
|
2315
|
+
const levelFormats = [];
|
|
2316
|
+
const numberFormats = [];
|
|
2317
|
+
const startNumbers = [1, 1, 1, 1, 1, 1, 1];
|
|
2318
|
+
let offset = 0;
|
|
2319
|
+
for (let level = 0; level < 7; level++) {
|
|
2320
|
+
if (offset + 12 > rec.data.length) {
|
|
2321
|
+
levelFormats.push("");
|
|
2322
|
+
numberFormats.push(0);
|
|
2323
|
+
continue;
|
|
2279
2324
|
}
|
|
2325
|
+
const attr = rec.data.readUInt32LE(offset);
|
|
2326
|
+
numberFormats.push(attr >>> 5 & 15);
|
|
2327
|
+
offset += 12;
|
|
2328
|
+
const { value, next } = readHwpString(rec.data, offset);
|
|
2329
|
+
levelFormats.push(value);
|
|
2330
|
+
offset = next;
|
|
2280
2331
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
imageIndex++;
|
|
2293
|
-
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
2294
|
-
img = { filename, data, mimeType };
|
|
2295
|
-
images.push(img);
|
|
2296
|
-
break;
|
|
2297
|
-
} catch (err) {
|
|
2298
|
-
if (err instanceof KordocError) throw err;
|
|
2332
|
+
let baseStart = 1;
|
|
2333
|
+
if (offset + 2 <= rec.data.length) {
|
|
2334
|
+
baseStart = rec.data.readUInt16LE(offset) || 1;
|
|
2335
|
+
offset += 2;
|
|
2336
|
+
}
|
|
2337
|
+
for (let level = 0; level < 7; level++) {
|
|
2338
|
+
if (offset + 4 <= rec.data.length) {
|
|
2339
|
+
startNumbers[level] = rec.data.readUInt32LE(offset) || 1;
|
|
2340
|
+
offset += 4;
|
|
2341
|
+
} else {
|
|
2342
|
+
startNumbers[level] = baseStart;
|
|
2299
2343
|
}
|
|
2300
2344
|
}
|
|
2301
|
-
|
|
2302
|
-
resolved.set(ref, img);
|
|
2345
|
+
numberings.push({ levelFormats, numberFormats, startNumbers });
|
|
2303
2346
|
}
|
|
2304
|
-
if (
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
2308
|
-
continue;
|
|
2347
|
+
if (rec.tagId === TAG_BULLET && rec.data.length >= 14) {
|
|
2348
|
+
const code = rec.data.readUInt16LE(12);
|
|
2349
|
+
bullets.push({ char: code > 0 ? String.fromCharCode(code) : "\u2022" });
|
|
2309
2350
|
}
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
}
|
|
2316
|
-
|
|
2317
|
-
// src/hwpx/metadata.ts
|
|
2318
|
-
import JSZip from "jszip";
|
|
2319
|
-
|
|
2320
|
-
// src/hwpx/zip-sections.ts
|
|
2321
|
-
import { inflateRawSync } from "zlib";
|
|
2322
|
-
function extractFromBrokenZip(buffer) {
|
|
2323
|
-
const data = new Uint8Array(buffer);
|
|
2324
|
-
const view = new DataView(buffer);
|
|
2325
|
-
let pos = 0;
|
|
2326
|
-
const blocks = [];
|
|
2327
|
-
const warnings = [
|
|
2328
|
-
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
2329
|
-
];
|
|
2330
|
-
let totalDecompressed = 0;
|
|
2331
|
-
let entryCount = 0;
|
|
2332
|
-
let sectionNum = 0;
|
|
2333
|
-
const shared = createSectionShared();
|
|
2334
|
-
while (pos < data.length - 30) {
|
|
2335
|
-
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
2336
|
-
pos++;
|
|
2337
|
-
while (pos < data.length - 30) {
|
|
2338
|
-
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
2339
|
-
pos++;
|
|
2351
|
+
if (rec.tagId === TAG_DOC_CHAR_SHAPE && rec.data.length >= 18) {
|
|
2352
|
+
if (rec.data.length >= 50) {
|
|
2353
|
+
const fontSize = rec.data.readUInt32LE(42);
|
|
2354
|
+
const attrFlags = rec.data.readUInt32LE(46);
|
|
2355
|
+
charShapes.push({ fontSize, attrFlags });
|
|
2356
|
+
} else {
|
|
2357
|
+
charShapes.push({ fontSize: 0, attrFlags: 0 });
|
|
2340
2358
|
}
|
|
2341
|
-
continue;
|
|
2342
|
-
}
|
|
2343
|
-
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
2344
|
-
const method = view.getUint16(pos + 8, true);
|
|
2345
|
-
const compSize = view.getUint32(pos + 18, true);
|
|
2346
|
-
const nameLen = view.getUint16(pos + 26, true);
|
|
2347
|
-
const extraLen = view.getUint16(pos + 28, true);
|
|
2348
|
-
if (nameLen > 1024 || extraLen > 65535) {
|
|
2349
|
-
pos += 30 + nameLen + extraLen;
|
|
2350
|
-
continue;
|
|
2351
|
-
}
|
|
2352
|
-
const fileStart = pos + 30 + nameLen + extraLen;
|
|
2353
|
-
if (fileStart + compSize > data.length) break;
|
|
2354
|
-
if (compSize === 0 && method !== 0) {
|
|
2355
|
-
pos = fileStart;
|
|
2356
|
-
continue;
|
|
2357
|
-
}
|
|
2358
|
-
const nameBytes = data.slice(pos + 30, pos + 30 + nameLen);
|
|
2359
|
-
const name = new TextDecoder().decode(nameBytes);
|
|
2360
|
-
if (isPathTraversal(name)) {
|
|
2361
|
-
pos = fileStart + compSize;
|
|
2362
|
-
continue;
|
|
2363
2359
|
}
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2360
|
+
if (rec.tagId === TAG_DOC_STYLE && rec.data.length >= 8) {
|
|
2361
|
+
try {
|
|
2362
|
+
let offset = 0;
|
|
2363
|
+
const nameLen = rec.data.readUInt16LE(offset);
|
|
2364
|
+
offset += 2;
|
|
2365
|
+
const nameBytes = nameLen * 2;
|
|
2366
|
+
const name = nameBytes > 0 && offset + nameBytes <= rec.data.length ? rec.data.subarray(offset, offset + nameBytes).toString("utf16le") : "";
|
|
2367
|
+
offset += nameBytes;
|
|
2368
|
+
let nameKo = "";
|
|
2369
|
+
if (offset + 2 <= rec.data.length) {
|
|
2370
|
+
const nameKoLen = rec.data.readUInt16LE(offset);
|
|
2371
|
+
offset += 2;
|
|
2372
|
+
const nameKoBytes = nameKoLen * 2;
|
|
2373
|
+
if (nameKoBytes > 0 && offset + nameKoBytes <= rec.data.length) {
|
|
2374
|
+
nameKo = rec.data.subarray(offset, offset + nameKoBytes).toString("utf16le");
|
|
2375
|
+
}
|
|
2376
|
+
offset += nameKoBytes;
|
|
2377
|
+
}
|
|
2378
|
+
const type = offset < rec.data.length ? rec.data.readUInt8(offset) : 0;
|
|
2379
|
+
offset += 1;
|
|
2380
|
+
offset += 1;
|
|
2381
|
+
offset += 2;
|
|
2382
|
+
const paraShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
2383
|
+
offset += 2;
|
|
2384
|
+
const charShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
2385
|
+
styles.push({ name, nameKo, charShapeId, paraShapeId, type });
|
|
2386
|
+
} catch {
|
|
2376
2387
|
}
|
|
2377
|
-
totalDecompressed += content.length * 2;
|
|
2378
|
-
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
2379
|
-
sectionNum++;
|
|
2380
|
-
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
2381
|
-
} catch {
|
|
2382
|
-
continue;
|
|
2383
2388
|
}
|
|
2384
2389
|
}
|
|
2385
|
-
|
|
2386
|
-
applyPageText(blocks, shared);
|
|
2387
|
-
const markdown = blocksToMarkdown(blocks);
|
|
2388
|
-
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
2390
|
+
return { charShapes, paraShapes, styles, binData, numberings, bullets };
|
|
2389
2391
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
if (!file) return null;
|
|
2393
|
-
try {
|
|
2394
|
-
const xml = await file.async("text");
|
|
2395
|
-
if (!/<opf:meta\b[^>]*name="generator"[^>]*content="kordoc"/.test(xml)) return null;
|
|
2396
|
-
return xml.match(/<opf:meta\b[^>]*name="kordoc-layout"[^>]*content="([^"]*)"/)?.[1] ?? null;
|
|
2397
|
-
} catch {
|
|
2398
|
-
return null;
|
|
2399
|
-
}
|
|
2392
|
+
function createParaTextState() {
|
|
2393
|
+
return { text: "", ctrlIdx: 0, fieldStack: [], fieldRanges: [] };
|
|
2400
2394
|
}
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
for (const mp of manifestPaths) {
|
|
2404
|
-
const mpLower = mp.toLowerCase();
|
|
2405
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
2406
|
-
if (!file) continue;
|
|
2407
|
-
const xml = await file.async("text");
|
|
2408
|
-
const paths = parseSectionPathsFromManifest(xml);
|
|
2409
|
-
if (paths.length > 0) return paths;
|
|
2410
|
-
}
|
|
2411
|
-
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
2412
|
-
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
2395
|
+
function isExtendedOnlyCtrlChar(ch) {
|
|
2396
|
+
return ch >= 1 && ch <= 3 || ch >= 11 && ch <= 12 || ch >= 14 && ch <= 18 || ch >= 21 && ch <= 23;
|
|
2413
2397
|
}
|
|
2414
|
-
function
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
const
|
|
2418
|
-
const
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
const
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2398
|
+
function appendParaText(state, data, resolveControl) {
|
|
2399
|
+
let result = "";
|
|
2400
|
+
let i = 0;
|
|
2401
|
+
const base = state.text.length;
|
|
2402
|
+
const resolveAt = (byteOffset, extended) => {
|
|
2403
|
+
const ctrlId = data.readUInt32LE(byteOffset);
|
|
2404
|
+
const idx = extended ? state.ctrlIdx : -1;
|
|
2405
|
+
const replacement = resolveControl?.(idx, ctrlId);
|
|
2406
|
+
if (replacement) result += replacement;
|
|
2407
|
+
if (extended) state.ctrlIdx++;
|
|
2408
|
+
};
|
|
2409
|
+
while (i + 1 < data.length) {
|
|
2410
|
+
const ch = data.readUInt16LE(i);
|
|
2411
|
+
i += 2;
|
|
2412
|
+
switch (ch) {
|
|
2413
|
+
// ── char 타입 (2바이트만, 확장 데이터 없음) ──
|
|
2414
|
+
case CHAR_LINE:
|
|
2415
|
+
result += "\n";
|
|
2416
|
+
break;
|
|
2417
|
+
case CHAR_SECTION_BREAK: {
|
|
2418
|
+
if (i + 16 <= data.length && data.readUInt16LE(i) === 11) {
|
|
2419
|
+
resolveAt(i + 2, true);
|
|
2420
|
+
i += 16;
|
|
2421
|
+
break;
|
|
2422
|
+
}
|
|
2423
|
+
result += "\n";
|
|
2424
|
+
break;
|
|
2425
|
+
}
|
|
2426
|
+
case CHAR_PARA:
|
|
2427
|
+
break;
|
|
2428
|
+
// 문단 끝
|
|
2429
|
+
case CHAR_HYPHEN:
|
|
2430
|
+
result += "-";
|
|
2431
|
+
break;
|
|
2432
|
+
case CHAR_NBSP:
|
|
2433
|
+
result += "\xA0";
|
|
2434
|
+
break;
|
|
2435
|
+
// 진짜 NBSP
|
|
2436
|
+
case CHAR_FIXED_WIDTH:
|
|
2437
|
+
result += " ";
|
|
2438
|
+
break;
|
|
2439
|
+
// 고정폭 공백
|
|
2440
|
+
// ── inline 타입 (2바이트 + 14바이트 확장) ──
|
|
2441
|
+
case CHAR_TAB:
|
|
2442
|
+
result += " ";
|
|
2443
|
+
if (i + 14 <= data.length) i += 14;
|
|
2444
|
+
break;
|
|
2445
|
+
default:
|
|
2446
|
+
if (ch >= 1 && ch <= 31) {
|
|
2447
|
+
const isExtended = isExtendedOnlyCtrlChar(ch);
|
|
2448
|
+
const isInline = ch >= 4 && ch <= 9 || ch >= 19 && ch <= 20;
|
|
2449
|
+
if ((isExtended || isInline) && i + 14 <= data.length) {
|
|
2450
|
+
if (ch === 3) {
|
|
2451
|
+
state.fieldStack.push({ start: base + result.length, ctrlIdx: state.ctrlIdx });
|
|
2452
|
+
} else if (ch === 4) {
|
|
2453
|
+
const open = state.fieldStack.pop();
|
|
2454
|
+
if (open) {
|
|
2455
|
+
state.fieldRanges.push({ start: open.start, end: base + result.length, ctrlIdx: open.ctrlIdx });
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
resolveAt(i, isExtended);
|
|
2459
|
+
i += 14;
|
|
2460
|
+
}
|
|
2461
|
+
} else if (ch >= 32) {
|
|
2462
|
+
if (ch >= 55296 && ch <= 56319 && i + 1 < data.length) {
|
|
2463
|
+
const lo = data.readUInt16LE(i);
|
|
2464
|
+
if (lo >= 56320 && lo <= 57343) {
|
|
2465
|
+
i += 2;
|
|
2466
|
+
const codePoint = (ch - 55296 << 10) + (lo - 56320) + 65536;
|
|
2467
|
+
result += String.fromCodePoint(codePoint);
|
|
2468
|
+
break;
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
result += String.fromCharCode(ch);
|
|
2472
|
+
}
|
|
2473
|
+
break;
|
|
2431
2474
|
}
|
|
2432
|
-
if (ordered.length > 0) return ordered;
|
|
2433
2475
|
}
|
|
2434
|
-
|
|
2476
|
+
state.text += result;
|
|
2477
|
+
}
|
|
2478
|
+
function extractEquationText(data) {
|
|
2479
|
+
if (data.length < 6) return null;
|
|
2480
|
+
const scriptLength = data.readUInt16LE(4);
|
|
2481
|
+
const scriptStart = 6;
|
|
2482
|
+
const scriptEnd = scriptStart + scriptLength * 2;
|
|
2483
|
+
if (scriptLength <= 0 || scriptEnd > data.length) return null;
|
|
2484
|
+
const equation = data.subarray(scriptStart, scriptEnd).toString("utf16le").replace(/\0+/g, "").trim();
|
|
2485
|
+
return equation || null;
|
|
2435
2486
|
}
|
|
2436
2487
|
|
|
2437
|
-
// src/
|
|
2438
|
-
|
|
2488
|
+
// src/hwp5/images.ts
|
|
2489
|
+
function detectImageMime(data) {
|
|
2490
|
+
if (data.length < 4) return null;
|
|
2491
|
+
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return "image/png";
|
|
2492
|
+
if (data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
|
|
2493
|
+
if (data[0] === 71 && data[1] === 73 && data[2] === 70) return "image/gif";
|
|
2494
|
+
if (data[0] === 66 && data[1] === 77) return "image/bmp";
|
|
2495
|
+
if (data[0] === 215 && data[1] === 205 && data[2] === 198 && data[3] === 154) return "image/wmf";
|
|
2496
|
+
if (data[0] === 1 && data[1] === 0 && data[2] === 0 && data[3] === 0) return "image/emf";
|
|
2497
|
+
return null;
|
|
2498
|
+
}
|
|
2499
|
+
function normalizeBinPayload(data) {
|
|
2500
|
+
if (detectImageMime(data)) return data;
|
|
2439
2501
|
try {
|
|
2440
|
-
const
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2502
|
+
const inflated = decompressStream(data);
|
|
2503
|
+
if (inflated.length > 0) return inflated;
|
|
2504
|
+
} catch {
|
|
2505
|
+
}
|
|
2506
|
+
return data;
|
|
2507
|
+
}
|
|
2508
|
+
var BIN_ENTRY_RE = /(?:^|\/)BIN([0-9A-Fa-f]{4,8})(?:\.[^./\\]*)?$/;
|
|
2509
|
+
function collectImageBlocks(blocks, out) {
|
|
2510
|
+
for (const b of blocks) {
|
|
2511
|
+
if (b.type === "image") out.push(b);
|
|
2512
|
+
if (b.table) {
|
|
2513
|
+
for (const row of b.table.cells) {
|
|
2514
|
+
for (const cell2 of row) {
|
|
2515
|
+
if (cell2.blocks) collectImageBlocks(cell2.blocks, out);
|
|
2516
|
+
}
|
|
2448
2517
|
}
|
|
2449
|
-
parseDublinCoreMetadata(xml, metadata);
|
|
2450
|
-
if (metadata.title || metadata.author) return;
|
|
2451
2518
|
}
|
|
2452
|
-
|
|
2519
|
+
if (b.children) collectImageBlocks(b.children, out);
|
|
2453
2520
|
}
|
|
2454
2521
|
}
|
|
2455
|
-
function
|
|
2456
|
-
const
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
const text = els[0].textContent?.trim();
|
|
2464
|
-
if (text) return text;
|
|
2522
|
+
function forEachTableCell(blocks, fn) {
|
|
2523
|
+
for (const b of blocks) {
|
|
2524
|
+
if (b.table) {
|
|
2525
|
+
for (const row of b.table.cells) {
|
|
2526
|
+
for (const cell2 of row) {
|
|
2527
|
+
fn(cell2);
|
|
2528
|
+
if (cell2.blocks) forEachTableCell(cell2.blocks, fn);
|
|
2529
|
+
}
|
|
2465
2530
|
}
|
|
2466
2531
|
}
|
|
2467
|
-
|
|
2468
|
-
};
|
|
2469
|
-
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
2470
|
-
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
2471
|
-
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
2472
|
-
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
2473
|
-
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
2474
|
-
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
2475
|
-
if (keywords && !metadata.keywords) {
|
|
2476
|
-
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
2532
|
+
if (b.children) forEachTableCell(b.children, fn);
|
|
2477
2533
|
}
|
|
2478
2534
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
const sectionPaths = await resolveSectionPaths(zip);
|
|
2489
|
-
metadata.pageCount = sectionPaths.length;
|
|
2490
|
-
return metadata;
|
|
2535
|
+
var CELL_IMAGE_SENTINEL_RE = /!\[image\]\(hwp5bin:(\d+)\)/g;
|
|
2536
|
+
function resolveCellImageSentinels(blocks, renamed) {
|
|
2537
|
+
forEachTableCell(blocks, (cell2) => {
|
|
2538
|
+
if (!cell2.text.includes("hwp5bin:")) return;
|
|
2539
|
+
cell2.text = cell2.text.replace(CELL_IMAGE_SENTINEL_RE, (_m, idStr) => {
|
|
2540
|
+
const filename = renamed.get(Number(idStr));
|
|
2541
|
+
return filename ? `` : "[\uC774\uBBF8\uC9C0]";
|
|
2542
|
+
});
|
|
2543
|
+
});
|
|
2491
2544
|
}
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2545
|
+
function resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced) {
|
|
2546
|
+
const imageBlocks = [];
|
|
2547
|
+
collectImageBlocks(blocks, imageBlocks);
|
|
2548
|
+
const images = [];
|
|
2549
|
+
const renamed = /* @__PURE__ */ new Map();
|
|
2550
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
2551
|
+
let imageIndex = 0;
|
|
2552
|
+
for (const block of imageBlocks) {
|
|
2553
|
+
if (!block.text) continue;
|
|
2554
|
+
const storageId = parseInt(block.text, 10);
|
|
2555
|
+
if (isNaN(storageId)) continue;
|
|
2556
|
+
let img = resolved.get(storageId);
|
|
2557
|
+
if (img === void 0) {
|
|
2558
|
+
const bin = binDataMap.get(storageId);
|
|
2559
|
+
if (!bin) {
|
|
2560
|
+
warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
|
|
2561
|
+
resolved.set(storageId, null);
|
|
2562
|
+
} else {
|
|
2563
|
+
const mime = detectImageMime(bin.data);
|
|
2564
|
+
if (!mime) {
|
|
2565
|
+
warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
|
|
2566
|
+
resolved.set(storageId, null);
|
|
2567
|
+
} else {
|
|
2568
|
+
imageIndex++;
|
|
2569
|
+
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
2570
|
+
img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
|
|
2571
|
+
resolved.set(storageId, img);
|
|
2572
|
+
images.push({ filename: img.filename, data: img.data, mimeType: img.mime });
|
|
2573
|
+
renamed.set(storageId, img.filename);
|
|
2514
2574
|
}
|
|
2515
2575
|
}
|
|
2516
|
-
|
|
2576
|
+
img = resolved.get(storageId);
|
|
2577
|
+
}
|
|
2578
|
+
if (!img) {
|
|
2579
|
+
const bin = binDataMap.get(storageId);
|
|
2580
|
+
block.type = "paragraph";
|
|
2581
|
+
block.text = bin ? `[\uC774\uBBF8\uC9C0: ${bin.name}]` : `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
|
|
2582
|
+
continue;
|
|
2517
2583
|
}
|
|
2584
|
+
block.text = img.filename;
|
|
2585
|
+
block.imageData = { data: img.data, mimeType: img.mime, filename: binDataMap.get(storageId).name };
|
|
2518
2586
|
}
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
const shared = createSectionShared();
|
|
2531
|
-
shared.kordocLayout = await readKordocLayout(zip);
|
|
2532
|
-
let parsedSections = 0;
|
|
2533
|
-
for (let si = 0; si < sectionPaths.length; si++) {
|
|
2534
|
-
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
2535
|
-
const file = zip.file(sectionPaths[si]);
|
|
2536
|
-
if (!file) continue;
|
|
2537
|
-
try {
|
|
2538
|
-
const xml = await file.async("text");
|
|
2539
|
-
decompressed.total += xml.length * 2;
|
|
2540
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2541
|
-
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
2542
|
-
parsedSections++;
|
|
2543
|
-
options?.onProgress?.(parsedSections, totalTarget);
|
|
2544
|
-
} catch (secErr) {
|
|
2545
|
-
if (secErr instanceof KordocError) throw secErr;
|
|
2546
|
-
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" });
|
|
2587
|
+
if (sweepUnreferenced) {
|
|
2588
|
+
for (const [storageId, bin] of [...binDataMap.entries()].sort((a, b) => a[0] - b[0])) {
|
|
2589
|
+
if (resolved.has(storageId)) continue;
|
|
2590
|
+
const mime = detectImageMime(bin.data);
|
|
2591
|
+
if (!mime) continue;
|
|
2592
|
+
imageIndex++;
|
|
2593
|
+
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
2594
|
+
const filename = `image_${String(imageIndex).padStart(3, "0")}.${ext}`;
|
|
2595
|
+
const data = new Uint8Array(bin.data);
|
|
2596
|
+
images.push({ filename, data, mimeType: mime });
|
|
2597
|
+
blocks.push({ type: "image", text: filename, imageData: { data, mimeType: mime, filename: bin.name } });
|
|
2547
2598
|
}
|
|
2548
2599
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
detectHwpxHeadings(blocks, styleMap);
|
|
2552
|
-
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
2553
|
-
const markdown = blocksToMarkdown(blocks);
|
|
2554
|
-
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 };
|
|
2600
|
+
resolveCellImageSentinels(blocks, renamed);
|
|
2601
|
+
return images;
|
|
2555
2602
|
}
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
var TAG_SHAPE_COMPONENT_PICTURE = 85;
|
|
2567
|
-
var TAG_SHAPE_COMPONENT_CONTAINER = 86;
|
|
2568
|
-
var TAG_EQEDIT = 88;
|
|
2569
|
-
var TAG_BIN_DATA = 18;
|
|
2570
|
-
var TAG_DOC_CHAR_SHAPE = 21;
|
|
2571
|
-
var TAG_NUMBERING = 23;
|
|
2572
|
-
var TAG_BULLET = 24;
|
|
2573
|
-
var TAG_DOC_PARA_SHAPE = 25;
|
|
2574
|
-
var TAG_DOC_STYLE = 26;
|
|
2575
|
-
var CHAR_LINE = 0;
|
|
2576
|
-
var CHAR_SECTION_BREAK = 10;
|
|
2577
|
-
var CHAR_PARA = 13;
|
|
2578
|
-
var CHAR_TAB = 9;
|
|
2579
|
-
var CHAR_HYPHEN = 30;
|
|
2580
|
-
var CHAR_NBSP = 31;
|
|
2581
|
-
var CHAR_FIXED_NBSP = 24;
|
|
2582
|
-
var CHAR_FIXED_WIDTH = 25;
|
|
2583
|
-
var FLAG_COMPRESSED = 1 << 0;
|
|
2584
|
-
var FLAG_ENCRYPTED = 1 << 1;
|
|
2585
|
-
var FLAG_DISTRIBUTION = 1 << 2;
|
|
2586
|
-
var FLAG_DRM = 1 << 4;
|
|
2587
|
-
var MAX_RECORDS = 5e5;
|
|
2588
|
-
function readRecords(data) {
|
|
2589
|
-
const records = [];
|
|
2590
|
-
let offset = 0;
|
|
2591
|
-
while (offset + 4 <= data.length && records.length < MAX_RECORDS) {
|
|
2592
|
-
const header = data.readUInt32LE(offset);
|
|
2593
|
-
offset += 4;
|
|
2594
|
-
const tagId = header & 1023;
|
|
2595
|
-
const level = header >> 10 & 1023;
|
|
2596
|
-
let size = header >> 20 & 4095;
|
|
2597
|
-
if (size === 4095) {
|
|
2598
|
-
if (offset + 4 > data.length) break;
|
|
2599
|
-
size = data.readUInt32LE(offset);
|
|
2600
|
-
offset += 4;
|
|
2603
|
+
function extractHwp5Images(fileIndex, blocks, warnings, sweepUnreferenced) {
|
|
2604
|
+
const binDataMap = /* @__PURE__ */ new Map();
|
|
2605
|
+
if (fileIndex) {
|
|
2606
|
+
for (const entry of fileIndex) {
|
|
2607
|
+
if (!entry?.name || !entry.content) continue;
|
|
2608
|
+
const match = entry.name.match(BIN_ENTRY_RE);
|
|
2609
|
+
if (!match) continue;
|
|
2610
|
+
const idx = parseInt(match[1], 16);
|
|
2611
|
+
const data = normalizeBinPayload(Buffer.from(entry.content));
|
|
2612
|
+
binDataMap.set(idx, { data, name: entry.name });
|
|
2601
2613
|
}
|
|
2602
|
-
if (offset + size > data.length) break;
|
|
2603
|
-
records.push({ tagId, level, size, data: data.subarray(offset, offset + size) });
|
|
2604
|
-
offset += size;
|
|
2605
2614
|
}
|
|
2606
|
-
|
|
2615
|
+
if (binDataMap.size === 0) {
|
|
2616
|
+
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
2617
|
+
return [];
|
|
2618
|
+
}
|
|
2619
|
+
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
2607
2620
|
}
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
const
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2621
|
+
function extractHwp5ImagesLenient(lcfb, blocks, warnings, sweepUnreferenced) {
|
|
2622
|
+
const binDataMap = /* @__PURE__ */ new Map();
|
|
2623
|
+
const binRe = /^BIN([0-9A-Fa-f]{4,8})(?:\.|$)/;
|
|
2624
|
+
for (const e of lcfb.entries()) {
|
|
2625
|
+
const match = e.name.match(binRe);
|
|
2626
|
+
if (!match) continue;
|
|
2627
|
+
const idx = parseInt(match[1], 16);
|
|
2628
|
+
const raw = lcfb.findStream(e.name);
|
|
2629
|
+
if (!raw) continue;
|
|
2630
|
+
binDataMap.set(idx, { data: normalizeBinPayload(raw), name: e.name });
|
|
2616
2631
|
}
|
|
2617
|
-
|
|
2632
|
+
if (binDataMap.size === 0) {
|
|
2633
|
+
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
2634
|
+
return [];
|
|
2635
|
+
}
|
|
2636
|
+
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
2618
2637
|
}
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2638
|
+
|
|
2639
|
+
// src/hwpx/images.ts
|
|
2640
|
+
function imageExtToMime(ext) {
|
|
2641
|
+
switch (ext.toLowerCase()) {
|
|
2642
|
+
case "jpg":
|
|
2643
|
+
case "jpeg":
|
|
2644
|
+
return "image/jpeg";
|
|
2645
|
+
case "png":
|
|
2646
|
+
return "image/png";
|
|
2647
|
+
case "gif":
|
|
2648
|
+
return "image/gif";
|
|
2649
|
+
case "bmp":
|
|
2650
|
+
return "image/bmp";
|
|
2651
|
+
case "tif":
|
|
2652
|
+
case "tiff":
|
|
2653
|
+
return "image/tiff";
|
|
2654
|
+
case "wmf":
|
|
2655
|
+
return "image/wmf";
|
|
2656
|
+
case "emf":
|
|
2657
|
+
return "image/emf";
|
|
2658
|
+
case "svg":
|
|
2659
|
+
return "image/svg+xml";
|
|
2660
|
+
default:
|
|
2661
|
+
return "application/octet-stream";
|
|
2662
|
+
}
|
|
2627
2663
|
}
|
|
2628
|
-
function
|
|
2629
|
-
if (
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
if (
|
|
2634
|
-
|
|
2664
|
+
function mimeToExt(mime) {
|
|
2665
|
+
if (mime.includes("jpeg")) return "jpg";
|
|
2666
|
+
if (mime.includes("png")) return "png";
|
|
2667
|
+
if (mime.includes("gif")) return "gif";
|
|
2668
|
+
if (mime.includes("bmp")) return "bmp";
|
|
2669
|
+
if (mime.includes("tiff")) return "tif";
|
|
2670
|
+
if (mime.includes("wmf")) return "wmf";
|
|
2671
|
+
if (mime.includes("emf")) return "emf";
|
|
2672
|
+
if (mime.includes("svg")) return "svg";
|
|
2673
|
+
return "bin";
|
|
2635
2674
|
}
|
|
2636
|
-
function
|
|
2637
|
-
|
|
2638
|
-
const
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
const headType = attr1 >>> 23 & 3;
|
|
2647
|
-
const paraLevel = attr1 >>> 25 & 7;
|
|
2648
|
-
const numberingId = rec.data.length >= 32 ? rec.data.readUInt16LE(30) : 0;
|
|
2649
|
-
paraShapes.push({ headType, paraLevel, numberingId });
|
|
2650
|
-
}
|
|
2651
|
-
if (rec.tagId === TAG_BIN_DATA && rec.data.length >= 2) {
|
|
2652
|
-
const attr = rec.data.readUInt16LE(0);
|
|
2653
|
-
const typeBits = attr & 15;
|
|
2654
|
-
if (typeBits === 0) {
|
|
2655
|
-
binData.push({ kind: "link", storageId: 0, extension: "" });
|
|
2656
|
-
} else {
|
|
2657
|
-
const storageId = rec.data.length >= 4 ? rec.data.readUInt16LE(2) : 0;
|
|
2658
|
-
const { value: extension } = readHwpString(rec.data, 4);
|
|
2659
|
-
binData.push({ kind: typeBits === 2 ? "storage" : "embed", storageId, extension });
|
|
2675
|
+
function collectImageBlocks2(blocks, out, ownerCell, depth = 0) {
|
|
2676
|
+
if (depth > MAX_XML_DEPTH) return;
|
|
2677
|
+
for (const block of blocks) {
|
|
2678
|
+
if (block.type === "image") {
|
|
2679
|
+
out.push({ block, ownerCell });
|
|
2680
|
+
} else if (block.type === "table" && block.table) {
|
|
2681
|
+
for (const row of block.table.cells) {
|
|
2682
|
+
for (const cell2 of row) {
|
|
2683
|
+
if (cell2.blocks?.length) collectImageBlocks2(cell2.blocks, out, cell2, depth + 1);
|
|
2684
|
+
}
|
|
2660
2685
|
}
|
|
2661
2686
|
}
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
async function extractImagesFromZip(zip, blocks, decompressed, warnings, sweepUnreferenced) {
|
|
2690
|
+
const images = [];
|
|
2691
|
+
let imageIndex = 0;
|
|
2692
|
+
const usedPaths = /* @__PURE__ */ new Set();
|
|
2693
|
+
const imageBlocks = [];
|
|
2694
|
+
collectImageBlocks2(blocks, imageBlocks);
|
|
2695
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
2696
|
+
for (const { block, ownerCell } of imageBlocks) {
|
|
2697
|
+
if (block.type !== "image" || !block.text) continue;
|
|
2698
|
+
const ref = block.text;
|
|
2699
|
+
let img = resolved.get(ref);
|
|
2700
|
+
if (img === void 0) {
|
|
2701
|
+
img = null;
|
|
2702
|
+
const candidates = [
|
|
2703
|
+
`BinData/${ref}`,
|
|
2704
|
+
`Contents/BinData/${ref}`,
|
|
2705
|
+
ref
|
|
2706
|
+
// 절대 경로일 수도 있음
|
|
2707
|
+
];
|
|
2708
|
+
let resolvedPath = null;
|
|
2709
|
+
if (!ref.includes(".")) {
|
|
2710
|
+
const prefixes = [`BinData/${ref}`, `Contents/BinData/${ref}`];
|
|
2711
|
+
for (const prefix of prefixes) {
|
|
2712
|
+
const match = zip.file(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.[a-zA-Z0-9]+$`));
|
|
2713
|
+
if (match.length > 0) {
|
|
2714
|
+
resolvedPath = match[0].name;
|
|
2715
|
+
break;
|
|
2716
|
+
}
|
|
2672
2717
|
}
|
|
2673
|
-
const attr = rec.data.readUInt32LE(offset);
|
|
2674
|
-
numberFormats.push(attr >>> 5 & 15);
|
|
2675
|
-
offset += 12;
|
|
2676
|
-
const { value, next } = readHwpString(rec.data, offset);
|
|
2677
|
-
levelFormats.push(value);
|
|
2678
|
-
offset = next;
|
|
2679
2718
|
}
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2719
|
+
const allCandidates = resolvedPath ? [resolvedPath, ...candidates] : candidates;
|
|
2720
|
+
for (const path of allCandidates) {
|
|
2721
|
+
if (isPathTraversal(path)) continue;
|
|
2722
|
+
const file = zip.file(path);
|
|
2723
|
+
if (!file) continue;
|
|
2724
|
+
try {
|
|
2725
|
+
const data = await file.async("uint8array");
|
|
2726
|
+
decompressed.total += data.length;
|
|
2727
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2728
|
+
const ext = path.includes(".") ? path.split(".").pop() || "png" : "png";
|
|
2729
|
+
const mimeType = imageExtToMime(ext);
|
|
2730
|
+
imageIndex++;
|
|
2731
|
+
const filename2 = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
2732
|
+
img = { filename: filename2, data, mimeType };
|
|
2733
|
+
images.push(img);
|
|
2734
|
+
usedPaths.add(path);
|
|
2735
|
+
break;
|
|
2736
|
+
} catch (err) {
|
|
2737
|
+
if (err instanceof KordocError) throw err;
|
|
2738
|
+
}
|
|
2684
2739
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2740
|
+
if (!img) warnings?.push({ page: block.pageNumber, message: `\uC774\uBBF8\uC9C0 \uD30C\uC77C \uC5C6\uC74C: ${ref}`, code: "SKIPPED_IMAGE" });
|
|
2741
|
+
resolved.set(ref, img);
|
|
2742
|
+
}
|
|
2743
|
+
if (!img) {
|
|
2744
|
+
block.type = "paragraph";
|
|
2745
|
+
block.text = `[\uC774\uBBF8\uC9C0: ${ref}]`;
|
|
2746
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, () => `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
2747
|
+
continue;
|
|
2748
|
+
}
|
|
2749
|
+
const filename = img.filename;
|
|
2750
|
+
block.text = filename;
|
|
2751
|
+
block.imageData = { data: img.data, mimeType: img.mimeType, filename: ref };
|
|
2752
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, () => ``);
|
|
2753
|
+
}
|
|
2754
|
+
if (sweepUnreferenced) {
|
|
2755
|
+
const binEntries = zip.file(/(?:^|\/)BinData\//i);
|
|
2756
|
+
for (const file of binEntries) {
|
|
2757
|
+
if (file.dir || usedPaths.has(file.name) || isPathTraversal(file.name)) continue;
|
|
2758
|
+
try {
|
|
2759
|
+
const data = await file.async("uint8array");
|
|
2760
|
+
decompressed.total += data.length;
|
|
2761
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2762
|
+
const ext = file.name.includes(".") ? file.name.split(".").pop() || "" : "";
|
|
2763
|
+
let mimeType = imageExtToMime(ext);
|
|
2764
|
+
if (mimeType === "application/octet-stream") {
|
|
2765
|
+
const sniffed = detectImageMime(data);
|
|
2766
|
+
if (!sniffed) continue;
|
|
2767
|
+
mimeType = sniffed;
|
|
2691
2768
|
}
|
|
2769
|
+
imageIndex++;
|
|
2770
|
+
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
2771
|
+
const img = { filename, data, mimeType };
|
|
2772
|
+
images.push(img);
|
|
2773
|
+
blocks.push({ type: "image", text: filename, imageData: { data, mimeType, filename: file.name } });
|
|
2774
|
+
} catch (err) {
|
|
2775
|
+
if (err instanceof KordocError) throw err;
|
|
2776
|
+
warnings?.push({ message: `\uC774\uBBF8\uC9C0 \uCD94\uCD9C \uC2E4\uD328: ${file.name}`, code: "SKIPPED_IMAGE" });
|
|
2692
2777
|
}
|
|
2693
|
-
numberings.push({ levelFormats, numberFormats, startNumbers });
|
|
2694
2778
|
}
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2779
|
+
}
|
|
2780
|
+
return images;
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
// src/hwpx/metadata.ts
|
|
2784
|
+
import JSZip from "jszip";
|
|
2785
|
+
|
|
2786
|
+
// src/hwpx/zip-sections.ts
|
|
2787
|
+
import { inflateRawSync as inflateRawSync2 } from "zlib";
|
|
2788
|
+
function extractFromBrokenZip(buffer) {
|
|
2789
|
+
const data = new Uint8Array(buffer);
|
|
2790
|
+
const view = new DataView(buffer);
|
|
2791
|
+
let pos = 0;
|
|
2792
|
+
const blocks = [];
|
|
2793
|
+
const warnings = [
|
|
2794
|
+
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
2795
|
+
];
|
|
2796
|
+
let totalDecompressed = 0;
|
|
2797
|
+
let entryCount = 0;
|
|
2798
|
+
let sectionNum = 0;
|
|
2799
|
+
const shared = createSectionShared();
|
|
2800
|
+
while (pos < data.length - 30) {
|
|
2801
|
+
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
2802
|
+
pos++;
|
|
2803
|
+
while (pos < data.length - 30) {
|
|
2804
|
+
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
2805
|
+
pos++;
|
|
2806
|
+
}
|
|
2807
|
+
continue;
|
|
2698
2808
|
}
|
|
2699
|
-
if (
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2809
|
+
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
2810
|
+
const method = view.getUint16(pos + 8, true);
|
|
2811
|
+
const compSize = view.getUint32(pos + 18, true);
|
|
2812
|
+
const nameLen = view.getUint16(pos + 26, true);
|
|
2813
|
+
const extraLen = view.getUint16(pos + 28, true);
|
|
2814
|
+
if (nameLen > 1024 || extraLen > 65535) {
|
|
2815
|
+
pos += 30 + nameLen + extraLen;
|
|
2816
|
+
continue;
|
|
2817
|
+
}
|
|
2818
|
+
const fileStart = pos + 30 + nameLen + extraLen;
|
|
2819
|
+
if (fileStart + compSize > data.length) break;
|
|
2820
|
+
if (compSize === 0 && method !== 0) {
|
|
2821
|
+
pos = fileStart;
|
|
2822
|
+
continue;
|
|
2823
|
+
}
|
|
2824
|
+
const nameBytes = data.slice(pos + 30, pos + 30 + nameLen);
|
|
2825
|
+
const name = new TextDecoder().decode(nameBytes);
|
|
2826
|
+
if (isPathTraversal(name)) {
|
|
2827
|
+
pos = fileStart + compSize;
|
|
2828
|
+
continue;
|
|
2829
|
+
}
|
|
2830
|
+
const fileData = data.slice(fileStart, fileStart + compSize);
|
|
2831
|
+
pos = fileStart + compSize;
|
|
2832
|
+
if (!name.toLowerCase().includes("section") || !name.endsWith(".xml")) continue;
|
|
2833
|
+
try {
|
|
2834
|
+
let content;
|
|
2835
|
+
if (method === 0) {
|
|
2836
|
+
content = new TextDecoder().decode(fileData);
|
|
2837
|
+
} else if (method === 8) {
|
|
2838
|
+
const decompressed = inflateRawSync2(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
2839
|
+
content = new TextDecoder().decode(decompressed);
|
|
2704
2840
|
} else {
|
|
2705
|
-
|
|
2841
|
+
continue;
|
|
2706
2842
|
}
|
|
2843
|
+
totalDecompressed += content.length * 2;
|
|
2844
|
+
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
2845
|
+
sectionNum++;
|
|
2846
|
+
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
2847
|
+
} catch {
|
|
2848
|
+
continue;
|
|
2707
2849
|
}
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2850
|
+
}
|
|
2851
|
+
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");
|
|
2852
|
+
applyPageText(blocks, shared);
|
|
2853
|
+
const markdown = blocksToMarkdown(blocks);
|
|
2854
|
+
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
2855
|
+
}
|
|
2856
|
+
async function readKordocLayout(zip) {
|
|
2857
|
+
const file = zip.file("Contents/content.hpf") ?? zip.file("content.hpf");
|
|
2858
|
+
if (!file) return null;
|
|
2859
|
+
try {
|
|
2860
|
+
const xml = await file.async("text");
|
|
2861
|
+
if (!/<opf:meta\b[^>]*name="generator"[^>]*content="kordoc"/.test(xml)) return null;
|
|
2862
|
+
return xml.match(/<opf:meta\b[^>]*name="kordoc-layout"[^>]*content="([^"]*)"/)?.[1] ?? null;
|
|
2863
|
+
} catch {
|
|
2864
|
+
return null;
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
async function resolveSectionPaths(zip) {
|
|
2868
|
+
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
2869
|
+
for (const mp of manifestPaths) {
|
|
2870
|
+
const mpLower = mp.toLowerCase();
|
|
2871
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
2872
|
+
if (!file) continue;
|
|
2873
|
+
const xml = await file.async("text");
|
|
2874
|
+
const paths = parseSectionPathsFromManifest(xml);
|
|
2875
|
+
if (paths.length > 0) return paths;
|
|
2876
|
+
}
|
|
2877
|
+
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
2878
|
+
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
2879
|
+
}
|
|
2880
|
+
function parseSectionPathsFromManifest(xml) {
|
|
2881
|
+
const parser = createXmlParser();
|
|
2882
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
2883
|
+
const items = doc.getElementsByTagName("opf:item");
|
|
2884
|
+
const spine = doc.getElementsByTagName("opf:itemref");
|
|
2885
|
+
const idToHref = /* @__PURE__ */ new Map();
|
|
2886
|
+
for (let i = 0; i < items.length; i++) {
|
|
2887
|
+
const item = items[i];
|
|
2888
|
+
const id = item.getAttribute("id") || "";
|
|
2889
|
+
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
2890
|
+
if (id && href) idToHref.set(id, href);
|
|
2891
|
+
}
|
|
2892
|
+
if (spine.length > 0) {
|
|
2893
|
+
const ordered = [];
|
|
2894
|
+
for (let i = 0; i < spine.length; i++) {
|
|
2895
|
+
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
2896
|
+
if (href) ordered.push(href);
|
|
2897
|
+
}
|
|
2898
|
+
if (ordered.length > 0) return ordered;
|
|
2899
|
+
}
|
|
2900
|
+
return Array.from(idToHref.values()).sort(compareSectionPaths);
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
// src/hwpx/metadata.ts
|
|
2904
|
+
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
2905
|
+
try {
|
|
2906
|
+
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
2907
|
+
for (const mp of metaPaths) {
|
|
2908
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
2909
|
+
if (!file) continue;
|
|
2910
|
+
const xml = await file.async("text");
|
|
2911
|
+
if (decompressed) {
|
|
2912
|
+
decompressed.total += xml.length * 2;
|
|
2913
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2735
2914
|
}
|
|
2915
|
+
parseDublinCoreMetadata(xml, metadata);
|
|
2916
|
+
if (metadata.title || metadata.author) return;
|
|
2736
2917
|
}
|
|
2918
|
+
} catch {
|
|
2737
2919
|
}
|
|
2738
|
-
return { charShapes, paraShapes, styles, binData, numberings, bullets };
|
|
2739
2920
|
}
|
|
2740
|
-
function
|
|
2741
|
-
|
|
2921
|
+
function parseDublinCoreMetadata(xml, metadata) {
|
|
2922
|
+
const parser = createXmlParser();
|
|
2923
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
2924
|
+
if (!doc.documentElement) return;
|
|
2925
|
+
const getText = (tagNames) => {
|
|
2926
|
+
for (const tag of tagNames) {
|
|
2927
|
+
const els = doc.getElementsByTagName(tag);
|
|
2928
|
+
if (els.length > 0) {
|
|
2929
|
+
const text = els[0].textContent?.trim();
|
|
2930
|
+
if (text) return text;
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
return void 0;
|
|
2934
|
+
};
|
|
2935
|
+
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
2936
|
+
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
2937
|
+
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
2938
|
+
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
2939
|
+
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
2940
|
+
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
2941
|
+
if (keywords && !metadata.keywords) {
|
|
2942
|
+
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
2943
|
+
}
|
|
2742
2944
|
}
|
|
2743
|
-
function
|
|
2744
|
-
|
|
2945
|
+
async function extractHwpxMetadataOnly(buffer) {
|
|
2946
|
+
let zip;
|
|
2947
|
+
try {
|
|
2948
|
+
zip = await JSZip.loadAsync(buffer);
|
|
2949
|
+
} catch {
|
|
2950
|
+
throw new KordocError("HWPX ZIP\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
2951
|
+
}
|
|
2952
|
+
const metadata = {};
|
|
2953
|
+
await extractHwpxMetadata(zip, metadata);
|
|
2954
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
2955
|
+
metadata.pageCount = sectionPaths.length;
|
|
2956
|
+
return metadata;
|
|
2745
2957
|
}
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
if (
|
|
2767
|
-
|
|
2768
|
-
i += 16;
|
|
2769
|
-
break;
|
|
2770
|
-
}
|
|
2771
|
-
result += "\n";
|
|
2772
|
-
break;
|
|
2773
|
-
}
|
|
2774
|
-
case CHAR_PARA:
|
|
2775
|
-
break;
|
|
2776
|
-
// 문단 끝
|
|
2777
|
-
case CHAR_HYPHEN:
|
|
2778
|
-
result += "-";
|
|
2779
|
-
break;
|
|
2780
|
-
case CHAR_NBSP:
|
|
2781
|
-
result += " ";
|
|
2782
|
-
break;
|
|
2783
|
-
case CHAR_FIXED_NBSP:
|
|
2784
|
-
result += "\xA0";
|
|
2785
|
-
break;
|
|
2786
|
-
// 진짜 NBSP
|
|
2787
|
-
case CHAR_FIXED_WIDTH:
|
|
2788
|
-
result += " ";
|
|
2789
|
-
break;
|
|
2790
|
-
// 고정폭 공백
|
|
2791
|
-
// ── inline 타입 (2바이트 + 14바이트 확장) ──
|
|
2792
|
-
case CHAR_TAB:
|
|
2793
|
-
result += " ";
|
|
2794
|
-
if (i + 14 <= data.length) i += 14;
|
|
2795
|
-
break;
|
|
2796
|
-
default:
|
|
2797
|
-
if (ch >= 1 && ch <= 31) {
|
|
2798
|
-
const isExtended = isExtendedOnlyCtrlChar(ch);
|
|
2799
|
-
const isInline = ch >= 4 && ch <= 9 || ch >= 19 && ch <= 20;
|
|
2800
|
-
if ((isExtended || isInline) && i + 14 <= data.length) {
|
|
2801
|
-
if (ch === 3) {
|
|
2802
|
-
state.fieldStack.push({ start: base + result.length, ctrlIdx: state.ctrlIdx });
|
|
2803
|
-
} else if (ch === 4) {
|
|
2804
|
-
const open = state.fieldStack.pop();
|
|
2805
|
-
if (open) {
|
|
2806
|
-
state.fieldRanges.push({ start: open.start, end: base + result.length, ctrlIdx: open.ctrlIdx });
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
resolveAt(i, isExtended);
|
|
2810
|
-
i += 14;
|
|
2811
|
-
}
|
|
2812
|
-
} else if (ch >= 32) {
|
|
2813
|
-
if (ch >= 55296 && ch <= 56319 && i + 1 < data.length) {
|
|
2814
|
-
const lo = data.readUInt16LE(i);
|
|
2815
|
-
if (lo >= 56320 && lo <= 57343) {
|
|
2816
|
-
i += 2;
|
|
2817
|
-
const codePoint = (ch - 55296 << 10) + (lo - 56320) + 65536;
|
|
2818
|
-
result += String.fromCodePoint(codePoint);
|
|
2819
|
-
break;
|
|
2820
|
-
}
|
|
2821
|
-
}
|
|
2822
|
-
result += String.fromCharCode(ch);
|
|
2958
|
+
|
|
2959
|
+
// src/hwpx/parser.ts
|
|
2960
|
+
async function parseHwpxDocument(buffer, options) {
|
|
2961
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
2962
|
+
let zip;
|
|
2963
|
+
try {
|
|
2964
|
+
zip = await JSZip2.loadAsync(buffer);
|
|
2965
|
+
} catch {
|
|
2966
|
+
return extractFromBrokenZip(buffer);
|
|
2967
|
+
}
|
|
2968
|
+
const actualEntryCount = Object.keys(zip.files).length;
|
|
2969
|
+
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
2970
|
+
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2971
|
+
}
|
|
2972
|
+
const manifestFile = zip.file("META-INF/manifest.xml");
|
|
2973
|
+
if (manifestFile) {
|
|
2974
|
+
const manifestXml = await manifestFile.async("text");
|
|
2975
|
+
if (isEncryptedHwpx(manifestXml)) {
|
|
2976
|
+
if (isComFallbackAvailable() && options?.filePath) {
|
|
2977
|
+
const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
|
|
2978
|
+
if (pages.some((p) => p && p.trim().length > 0)) {
|
|
2979
|
+
return comResultToParseResult(pages, pageCount, warnings2);
|
|
2823
2980
|
}
|
|
2824
|
-
|
|
2981
|
+
}
|
|
2982
|
+
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.");
|
|
2825
2983
|
}
|
|
2826
2984
|
}
|
|
2827
|
-
|
|
2828
|
-
}
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
const
|
|
2832
|
-
const
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
const
|
|
2836
|
-
|
|
2985
|
+
const decompressed = { total: 0 };
|
|
2986
|
+
const metadata = {};
|
|
2987
|
+
await extractHwpxMetadata(zip, metadata, decompressed);
|
|
2988
|
+
const styleMap = await extractHwpxStyles(zip, decompressed);
|
|
2989
|
+
const warnings = [];
|
|
2990
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
2991
|
+
if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
2992
|
+
metadata.pageCount = sectionPaths.length;
|
|
2993
|
+
const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null;
|
|
2994
|
+
const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length;
|
|
2995
|
+
const blocks = [];
|
|
2996
|
+
const shared = createSectionShared();
|
|
2997
|
+
shared.kordocLayout = await readKordocLayout(zip);
|
|
2998
|
+
shared.keepTrailingEmptyCols = options?.keepTrailingEmptyCols;
|
|
2999
|
+
let parsedSections = 0;
|
|
3000
|
+
for (let si = 0; si < sectionPaths.length; si++) {
|
|
3001
|
+
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
3002
|
+
const file = zip.file(sectionPaths[si]);
|
|
3003
|
+
if (!file) continue;
|
|
3004
|
+
try {
|
|
3005
|
+
const xml = await file.async("text");
|
|
3006
|
+
decompressed.total += xml.length * 2;
|
|
3007
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new ZipBombError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3008
|
+
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
3009
|
+
parsedSections++;
|
|
3010
|
+
options?.onProgress?.(parsedSections, totalTarget);
|
|
3011
|
+
} catch (secErr) {
|
|
3012
|
+
if (secErr instanceof ZipBombError) throw secErr;
|
|
3013
|
+
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" });
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
applyPageText(blocks, shared);
|
|
3017
|
+
const images = await extractImagesFromZip(zip, blocks, decompressed, warnings, !pageFilter);
|
|
3018
|
+
detectHwpxHeadings(blocks, styleMap);
|
|
3019
|
+
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
3020
|
+
const markdown = blocksToMarkdown(blocks);
|
|
3021
|
+
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 };
|
|
2837
3022
|
}
|
|
2838
3023
|
|
|
2839
3024
|
// src/hwp5/numbering.ts
|
|
@@ -2911,383 +3096,107 @@ function shapeFormatToNumFmt(code) {
|
|
|
2911
3096
|
return "digit";
|
|
2912
3097
|
}
|
|
2913
3098
|
}
|
|
2914
|
-
var CIRCLED_DIGITS = "\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469\u246A\u246B\u246C\u246D\u246E\u246F\u2470\u2471\u2472\u2473";
|
|
2915
|
-
var GANADA = "\uAC00\uB098\uB2E4\uB77C\uB9C8\uBC14\uC0AC\uC544\uC790\uCC28\uCE74\uD0C0\uD30C\uD558";
|
|
2916
|
-
var CIRCLED_GANADA = "\u326E\u326F\u3270\u3271\u3272\u3273\u3274\u3275\u3276\u3277\u3278\u3279\u327A\u327B";
|
|
2917
|
-
var JAMO = "\u3131\u3134\u3137\u3139\u3141\u3142\u3145\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
|
|
2918
|
-
var CIRCLED_JAMO = "\u3260\u3261\u3262\u3263\u3264\u3265\u3266\u3267\u3268\u3269\u326A\u326B\u326C\u326D";
|
|
2919
|
-
function fromTable(n, table2) {
|
|
2920
|
-
return n >= 1 && n <= table2.length ? table2[n - 1] : String(n);
|
|
2921
|
-
}
|
|
2922
|
-
function formatRoman(n, upper) {
|
|
2923
|
-
if (n <= 0 || n > 3999) return String(n);
|
|
2924
|
-
const values = [1e3, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
|
|
2925
|
-
const symbols = upper ? ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"] : ["m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i"];
|
|
2926
|
-
let result = "";
|
|
2927
|
-
let num2 = n;
|
|
2928
|
-
for (let i = 0; i < values.length; i++) {
|
|
2929
|
-
while (num2 >= values[i]) {
|
|
2930
|
-
result += symbols[i];
|
|
2931
|
-
num2 -= values[i];
|
|
2932
|
-
}
|
|
2933
|
-
}
|
|
2934
|
-
return result;
|
|
2935
|
-
}
|
|
2936
|
-
function formatLatin(n, upper) {
|
|
2937
|
-
if (n <= 0) return "";
|
|
2938
|
-
let result = "";
|
|
2939
|
-
let num2 = n;
|
|
2940
|
-
while (num2 > 0) {
|
|
2941
|
-
num2--;
|
|
2942
|
-
result = String.fromCharCode((upper ? 65 : 97) + num2 % 26) + result;
|
|
2943
|
-
num2 = Math.floor(num2 / 26);
|
|
2944
|
-
}
|
|
2945
|
-
return result;
|
|
2946
|
-
}
|
|
2947
|
-
function formatEastAsianNumber(n, digits, units, zero) {
|
|
2948
|
-
if (n === 0) return zero;
|
|
2949
|
-
if (n < 0 || n > 99999) return String(n);
|
|
2950
|
-
let result = "";
|
|
2951
|
-
let num2 = n;
|
|
2952
|
-
let unit = 0;
|
|
2953
|
-
while (num2 > 0) {
|
|
2954
|
-
const d = num2 % 10;
|
|
2955
|
-
if (d > 0) {
|
|
2956
|
-
const digitStr = d === 1 && unit > 0 ? "" : digits[d];
|
|
2957
|
-
result = digitStr + units[unit] + result;
|
|
2958
|
-
}
|
|
2959
|
-
num2 = Math.floor(num2 / 10);
|
|
2960
|
-
unit++;
|
|
2961
|
-
}
|
|
2962
|
-
return result;
|
|
2963
|
-
}
|
|
2964
|
-
var HANGUL_DIGITS = ["", "\uC77C", "\uC774", "\uC0BC", "\uC0AC", "\uC624", "\uC721", "\uCE60", "\uD314", "\uAD6C"];
|
|
2965
|
-
var HANGUL_UNITS = ["", "\uC2ED", "\uBC31", "\uCC9C", "\uB9CC"];
|
|
2966
|
-
var HANJA_DIGITS = ["", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D"];
|
|
2967
|
-
var HANJA_UNITS = ["", "\u5341", "\u767E", "\u5343", "\u842C"];
|
|
2968
|
-
function formatNumber(n, fmt) {
|
|
2969
|
-
switch (fmt) {
|
|
2970
|
-
case "circled":
|
|
2971
|
-
return fromTable(n, CIRCLED_DIGITS);
|
|
2972
|
-
case "romanUpper":
|
|
2973
|
-
return formatRoman(n, true);
|
|
2974
|
-
case "romanLower":
|
|
2975
|
-
return formatRoman(n, false);
|
|
2976
|
-
case "latinUpper":
|
|
2977
|
-
return formatLatin(n, true) || String(n);
|
|
2978
|
-
case "latinLower":
|
|
2979
|
-
return formatLatin(n, false) || String(n);
|
|
2980
|
-
case "ganada":
|
|
2981
|
-
return fromTable(n, GANADA);
|
|
2982
|
-
case "circledGanada":
|
|
2983
|
-
return fromTable(n, CIRCLED_GANADA);
|
|
2984
|
-
case "jamo":
|
|
2985
|
-
return fromTable(n, JAMO);
|
|
2986
|
-
case "circledJamo":
|
|
2987
|
-
return fromTable(n, CIRCLED_JAMO);
|
|
2988
|
-
case "hangulNum":
|
|
2989
|
-
return formatEastAsianNumber(n, HANGUL_DIGITS, HANGUL_UNITS, "\uC601");
|
|
2990
|
-
case "hanjaNum":
|
|
2991
|
-
return formatEastAsianNumber(n, HANJA_DIGITS, HANJA_UNITS, "\u96F6");
|
|
2992
|
-
default:
|
|
2993
|
-
return String(n);
|
|
2994
|
-
}
|
|
2995
|
-
}
|
|
2996
|
-
function expandNumberingFormat(formatStr, counters, numbering) {
|
|
2997
|
-
let result = "";
|
|
2998
|
-
let i = 0;
|
|
2999
|
-
while (i < formatStr.length) {
|
|
3000
|
-
const ch = formatStr[i];
|
|
3001
|
-
if (ch === "^" && i + 1 < formatStr.length && formatStr[i + 1] >= "1" && formatStr[i + 1] <= "7") {
|
|
3002
|
-
const levelRef = formatStr.charCodeAt(i + 1) - 48;
|
|
3003
|
-
const idx = levelRef - 1;
|
|
3004
|
-
const counterVal = counters[idx] ?? 0;
|
|
3005
|
-
const start = numbering.startNumbers[idx] ?? 1;
|
|
3006
|
-
const num2 = counterVal > 0 ? start - 1 + counterVal : start;
|
|
3007
|
-
result += formatNumber(num2, headFormatToNumFmt(numbering.numberFormats[idx] ?? 0));
|
|
3008
|
-
i += 2;
|
|
3009
|
-
continue;
|
|
3010
|
-
}
|
|
3011
|
-
result += ch;
|
|
3012
|
-
i++;
|
|
3013
|
-
}
|
|
3014
|
-
return result;
|
|
3015
|
-
}
|
|
3016
|
-
|
|
3017
|
-
// src/hwp5/images.ts
|
|
3018
|
-
function detectImageMime(data) {
|
|
3019
|
-
if (data.length < 4) return null;
|
|
3020
|
-
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return "image/png";
|
|
3021
|
-
if (data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
|
|
3022
|
-
if (data[0] === 71 && data[1] === 73 && data[2] === 70) return "image/gif";
|
|
3023
|
-
if (data[0] === 66 && data[1] === 77) return "image/bmp";
|
|
3024
|
-
if (data[0] === 215 && data[1] === 205 && data[2] === 198 && data[3] === 154) return "image/wmf";
|
|
3025
|
-
if (data[0] === 1 && data[1] === 0 && data[2] === 0 && data[3] === 0) return "image/emf";
|
|
3026
|
-
return null;
|
|
3027
|
-
}
|
|
3028
|
-
function normalizeBinPayload(data) {
|
|
3029
|
-
if (detectImageMime(data)) return data;
|
|
3030
|
-
try {
|
|
3031
|
-
const inflated = decompressStream(data);
|
|
3032
|
-
if (inflated.length > 0) return inflated;
|
|
3033
|
-
} catch {
|
|
3034
|
-
}
|
|
3035
|
-
return data;
|
|
3036
|
-
}
|
|
3037
|
-
var BIN_ENTRY_RE = /(?:^|\/)BIN([0-9A-Fa-f]{4,8})(?:\.[^./\\]*)?$/;
|
|
3038
|
-
function collectImageBlocks2(blocks, out) {
|
|
3039
|
-
for (const b of blocks) {
|
|
3040
|
-
if (b.type === "image") out.push(b);
|
|
3041
|
-
if (b.table) {
|
|
3042
|
-
for (const row of b.table.cells) {
|
|
3043
|
-
for (const cell2 of row) {
|
|
3044
|
-
if (cell2.blocks) collectImageBlocks2(cell2.blocks, out);
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
}
|
|
3048
|
-
if (b.children) collectImageBlocks2(b.children, out);
|
|
3049
|
-
}
|
|
3050
|
-
}
|
|
3051
|
-
function forEachTableCell(blocks, fn) {
|
|
3052
|
-
for (const b of blocks) {
|
|
3053
|
-
if (b.table) {
|
|
3054
|
-
for (const row of b.table.cells) {
|
|
3055
|
-
for (const cell2 of row) {
|
|
3056
|
-
fn(cell2);
|
|
3057
|
-
if (cell2.blocks) forEachTableCell(cell2.blocks, fn);
|
|
3058
|
-
}
|
|
3059
|
-
}
|
|
3060
|
-
}
|
|
3061
|
-
if (b.children) forEachTableCell(b.children, fn);
|
|
3062
|
-
}
|
|
3063
|
-
}
|
|
3064
|
-
var CELL_IMAGE_SENTINEL_RE = /!\[image\]\(hwp5bin:(\d+)\)/g;
|
|
3065
|
-
function resolveCellImageSentinels(blocks, renamed) {
|
|
3066
|
-
forEachTableCell(blocks, (cell2) => {
|
|
3067
|
-
if (!cell2.text.includes("hwp5bin:")) return;
|
|
3068
|
-
cell2.text = cell2.text.replace(CELL_IMAGE_SENTINEL_RE, (_m, idStr) => {
|
|
3069
|
-
const filename = renamed.get(Number(idStr));
|
|
3070
|
-
return filename ? `` : "[\uC774\uBBF8\uC9C0]";
|
|
3071
|
-
});
|
|
3072
|
-
});
|
|
3073
|
-
}
|
|
3074
|
-
function resolveImageBlocks(binDataMap, blocks, warnings) {
|
|
3075
|
-
const imageBlocks = [];
|
|
3076
|
-
collectImageBlocks2(blocks, imageBlocks);
|
|
3077
|
-
if (imageBlocks.length === 0) return [];
|
|
3078
|
-
const images = [];
|
|
3079
|
-
const renamed = /* @__PURE__ */ new Map();
|
|
3080
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
3081
|
-
let imageIndex = 0;
|
|
3082
|
-
for (const block of imageBlocks) {
|
|
3083
|
-
if (!block.text) continue;
|
|
3084
|
-
const storageId = parseInt(block.text, 10);
|
|
3085
|
-
if (isNaN(storageId)) continue;
|
|
3086
|
-
let img = resolved.get(storageId);
|
|
3087
|
-
if (img === void 0) {
|
|
3088
|
-
const bin = binDataMap.get(storageId);
|
|
3089
|
-
if (!bin) {
|
|
3090
|
-
warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
|
|
3091
|
-
resolved.set(storageId, null);
|
|
3092
|
-
} else {
|
|
3093
|
-
const mime = detectImageMime(bin.data);
|
|
3094
|
-
if (!mime) {
|
|
3095
|
-
warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
|
|
3096
|
-
resolved.set(storageId, null);
|
|
3097
|
-
} else {
|
|
3098
|
-
imageIndex++;
|
|
3099
|
-
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
3100
|
-
img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
|
|
3101
|
-
resolved.set(storageId, img);
|
|
3102
|
-
images.push({ filename: img.filename, data: img.data, mimeType: img.mime });
|
|
3103
|
-
renamed.set(storageId, img.filename);
|
|
3104
|
-
}
|
|
3105
|
-
}
|
|
3106
|
-
img = resolved.get(storageId);
|
|
3107
|
-
}
|
|
3108
|
-
if (!img) {
|
|
3109
|
-
const bin = binDataMap.get(storageId);
|
|
3110
|
-
block.type = "paragraph";
|
|
3111
|
-
block.text = bin ? `[\uC774\uBBF8\uC9C0: ${bin.name}]` : `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
|
|
3112
|
-
continue;
|
|
3113
|
-
}
|
|
3114
|
-
block.text = img.filename;
|
|
3115
|
-
block.imageData = { data: img.data, mimeType: img.mime, filename: binDataMap.get(storageId).name };
|
|
3116
|
-
}
|
|
3117
|
-
resolveCellImageSentinels(blocks, renamed);
|
|
3118
|
-
return images;
|
|
3119
|
-
}
|
|
3120
|
-
function extractHwp5Images(fileIndex, blocks, warnings) {
|
|
3121
|
-
const binDataMap = /* @__PURE__ */ new Map();
|
|
3122
|
-
if (fileIndex) {
|
|
3123
|
-
for (const entry of fileIndex) {
|
|
3124
|
-
if (!entry?.name || !entry.content) continue;
|
|
3125
|
-
const match = entry.name.match(BIN_ENTRY_RE);
|
|
3126
|
-
if (!match) continue;
|
|
3127
|
-
const idx = parseInt(match[1], 16);
|
|
3128
|
-
const data = normalizeBinPayload(Buffer.from(entry.content));
|
|
3129
|
-
binDataMap.set(idx, { data, name: entry.name });
|
|
3099
|
+
var CIRCLED_DIGITS = "\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469\u246A\u246B\u246C\u246D\u246E\u246F\u2470\u2471\u2472\u2473";
|
|
3100
|
+
var GANADA = "\uAC00\uB098\uB2E4\uB77C\uB9C8\uBC14\uC0AC\uC544\uC790\uCC28\uCE74\uD0C0\uD30C\uD558";
|
|
3101
|
+
var CIRCLED_GANADA = "\u326E\u326F\u3270\u3271\u3272\u3273\u3274\u3275\u3276\u3277\u3278\u3279\u327A\u327B";
|
|
3102
|
+
var JAMO = "\u3131\u3134\u3137\u3139\u3141\u3142\u3145\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
|
|
3103
|
+
var CIRCLED_JAMO = "\u3260\u3261\u3262\u3263\u3264\u3265\u3266\u3267\u3268\u3269\u326A\u326B\u326C\u326D";
|
|
3104
|
+
function fromTable(n, table2) {
|
|
3105
|
+
return n >= 1 && n <= table2.length ? table2[n - 1] : String(n);
|
|
3106
|
+
}
|
|
3107
|
+
function formatRoman(n, upper) {
|
|
3108
|
+
if (n <= 0 || n > 3999) return String(n);
|
|
3109
|
+
const values = [1e3, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
|
|
3110
|
+
const symbols = upper ? ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"] : ["m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i"];
|
|
3111
|
+
let result = "";
|
|
3112
|
+
let num2 = n;
|
|
3113
|
+
for (let i = 0; i < values.length; i++) {
|
|
3114
|
+
while (num2 >= values[i]) {
|
|
3115
|
+
result += symbols[i];
|
|
3116
|
+
num2 -= values[i];
|
|
3130
3117
|
}
|
|
3131
3118
|
}
|
|
3132
|
-
|
|
3133
|
-
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3134
|
-
return [];
|
|
3135
|
-
}
|
|
3136
|
-
return resolveImageBlocks(binDataMap, blocks, warnings);
|
|
3119
|
+
return result;
|
|
3137
3120
|
}
|
|
3138
|
-
function
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
if (!raw) continue;
|
|
3147
|
-
binDataMap.set(idx, { data: normalizeBinPayload(raw), name: e.name });
|
|
3148
|
-
}
|
|
3149
|
-
if (binDataMap.size === 0) {
|
|
3150
|
-
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3151
|
-
return [];
|
|
3121
|
+
function formatLatin(n, upper) {
|
|
3122
|
+
if (n <= 0) return "";
|
|
3123
|
+
let result = "";
|
|
3124
|
+
let num2 = n;
|
|
3125
|
+
while (num2 > 0) {
|
|
3126
|
+
num2--;
|
|
3127
|
+
result = String.fromCharCode((upper ? 65 : 97) + num2 % 26) + result;
|
|
3128
|
+
num2 = Math.floor(num2 / 26);
|
|
3152
3129
|
}
|
|
3153
|
-
return
|
|
3130
|
+
return result;
|
|
3154
3131
|
}
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3132
|
+
function formatEastAsianNumber(n, digits, units, zero) {
|
|
3133
|
+
if (n === 0) return zero;
|
|
3134
|
+
if (n < 0 || n > 99999) return String(n);
|
|
3135
|
+
let result = "";
|
|
3136
|
+
let num2 = n;
|
|
3137
|
+
let unit = 0;
|
|
3138
|
+
while (num2 > 0) {
|
|
3139
|
+
const d = num2 % 10;
|
|
3140
|
+
if (d > 0) {
|
|
3141
|
+
const digitStr = d === 1 && unit > 0 ? "" : digits[d];
|
|
3142
|
+
result = digitStr + units[unit] + result;
|
|
3164
3143
|
}
|
|
3165
|
-
|
|
3144
|
+
num2 = Math.floor(num2 / 10);
|
|
3145
|
+
unit++;
|
|
3166
3146
|
}
|
|
3167
|
-
return
|
|
3168
|
-
})();
|
|
3169
|
-
function crc32(bytes) {
|
|
3170
|
-
let c = 4294967295;
|
|
3171
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
3172
|
-
c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
|
|
3173
|
-
}
|
|
3174
|
-
return (c ^ 4294967295) >>> 0;
|
|
3175
|
-
}
|
|
3176
|
-
var BI_RGB = 0;
|
|
3177
|
-
var MAX_DIM = 32767;
|
|
3178
|
-
var MAX_PIXELS = 36e6;
|
|
3179
|
-
function bmpToPng(bmp) {
|
|
3180
|
-
if (bmp.length < 54) return null;
|
|
3181
|
-
if (bmp[0] !== 66 || bmp[1] !== 77) return null;
|
|
3182
|
-
const dv = new DataView(bmp.buffer, bmp.byteOffset, bmp.byteLength);
|
|
3183
|
-
const dataOffset = dv.getUint32(10, true);
|
|
3184
|
-
const headerSize = dv.getUint32(14, true);
|
|
3185
|
-
if (headerSize < 40) return null;
|
|
3186
|
-
const width = dv.getInt32(18, true);
|
|
3187
|
-
const rawHeight = dv.getInt32(22, true);
|
|
3188
|
-
const bitCount = dv.getUint16(28, true);
|
|
3189
|
-
const compression = dv.getUint32(30, true);
|
|
3190
|
-
if (compression !== BI_RGB) return null;
|
|
3191
|
-
if (bitCount !== 24 && bitCount !== 32) return null;
|
|
3192
|
-
if (width <= 0 || rawHeight === 0) return null;
|
|
3193
|
-
if (width > MAX_DIM || Math.abs(rawHeight) > MAX_DIM) return null;
|
|
3194
|
-
const topDown = rawHeight < 0;
|
|
3195
|
-
const height = Math.abs(rawHeight);
|
|
3196
|
-
if (width * height > MAX_PIXELS) return null;
|
|
3197
|
-
const bytesPerPixel = bitCount >> 3;
|
|
3198
|
-
const rowStride = width * bytesPerPixel + 3 & ~3;
|
|
3199
|
-
if (dataOffset + rowStride * height > bmp.length) return null;
|
|
3200
|
-
const rgba = new Uint8Array(width * height * 4);
|
|
3201
|
-
let anyAlpha = 0;
|
|
3202
|
-
for (let y = 0; y < height; y++) {
|
|
3203
|
-
const srcRow = topDown ? y : height - 1 - y;
|
|
3204
|
-
let src = dataOffset + srcRow * rowStride;
|
|
3205
|
-
let dst = y * width * 4;
|
|
3206
|
-
for (let x = 0; x < width; x++) {
|
|
3207
|
-
rgba[dst] = bmp[src + 2];
|
|
3208
|
-
rgba[dst + 1] = bmp[src + 1];
|
|
3209
|
-
rgba[dst + 2] = bmp[src];
|
|
3210
|
-
const a = bitCount === 32 ? bmp[src + 3] : 255;
|
|
3211
|
-
rgba[dst + 3] = a;
|
|
3212
|
-
anyAlpha |= a;
|
|
3213
|
-
src += bytesPerPixel;
|
|
3214
|
-
dst += 4;
|
|
3215
|
-
}
|
|
3216
|
-
}
|
|
3217
|
-
if (bitCount === 32 && anyAlpha === 0) {
|
|
3218
|
-
for (let i = 3; i < rgba.length; i += 4) rgba[i] = 255;
|
|
3219
|
-
}
|
|
3220
|
-
return encodePng(width, height, rgba);
|
|
3221
|
-
}
|
|
3222
|
-
var PNG_SIGNATURE = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10);
|
|
3223
|
-
function chunk(type, data) {
|
|
3224
|
-
const body = new Uint8Array(4 + data.length);
|
|
3225
|
-
body[0] = type.charCodeAt(0);
|
|
3226
|
-
body[1] = type.charCodeAt(1);
|
|
3227
|
-
body[2] = type.charCodeAt(2);
|
|
3228
|
-
body[3] = type.charCodeAt(3);
|
|
3229
|
-
body.set(data, 4);
|
|
3230
|
-
const out = new Uint8Array(8 + data.length + 4);
|
|
3231
|
-
const dv = new DataView(out.buffer);
|
|
3232
|
-
dv.setUint32(0, data.length, false);
|
|
3233
|
-
out.set(body, 4);
|
|
3234
|
-
dv.setUint32(8 + data.length, crc32(body), false);
|
|
3235
|
-
return out;
|
|
3147
|
+
return result;
|
|
3236
3148
|
}
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3149
|
+
var HANGUL_DIGITS = ["", "\uC77C", "\uC774", "\uC0BC", "\uC0AC", "\uC624", "\uC721", "\uCE60", "\uD314", "\uAD6C"];
|
|
3150
|
+
var HANGUL_UNITS = ["", "\uC2ED", "\uBC31", "\uCC9C", "\uB9CC"];
|
|
3151
|
+
var HANJA_DIGITS = ["", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u4E03", "\u516B", "\u4E5D"];
|
|
3152
|
+
var HANJA_UNITS = ["", "\u5341", "\u767E", "\u5343", "\u842C"];
|
|
3153
|
+
function formatNumber(n, fmt) {
|
|
3154
|
+
switch (fmt) {
|
|
3155
|
+
case "circled":
|
|
3156
|
+
return fromTable(n, CIRCLED_DIGITS);
|
|
3157
|
+
case "romanUpper":
|
|
3158
|
+
return formatRoman(n, true);
|
|
3159
|
+
case "romanLower":
|
|
3160
|
+
return formatRoman(n, false);
|
|
3161
|
+
case "latinUpper":
|
|
3162
|
+
return formatLatin(n, true) || String(n);
|
|
3163
|
+
case "latinLower":
|
|
3164
|
+
return formatLatin(n, false) || String(n);
|
|
3165
|
+
case "ganada":
|
|
3166
|
+
return fromTable(n, GANADA);
|
|
3167
|
+
case "circledGanada":
|
|
3168
|
+
return fromTable(n, CIRCLED_GANADA);
|
|
3169
|
+
case "jamo":
|
|
3170
|
+
return fromTable(n, JAMO);
|
|
3171
|
+
case "circledJamo":
|
|
3172
|
+
return fromTable(n, CIRCLED_JAMO);
|
|
3173
|
+
case "hangulNum":
|
|
3174
|
+
return formatEastAsianNumber(n, HANGUL_DIGITS, HANGUL_UNITS, "\uC601");
|
|
3175
|
+
case "hanjaNum":
|
|
3176
|
+
return formatEastAsianNumber(n, HANJA_DIGITS, HANJA_UNITS, "\u96F6");
|
|
3177
|
+
default:
|
|
3178
|
+
return String(n);
|
|
3179
|
+
}
|
|
3265
3180
|
}
|
|
3266
|
-
function
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
const
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
}
|
|
3181
|
+
function expandNumberingFormat(formatStr, counters, numbering) {
|
|
3182
|
+
let result = "";
|
|
3183
|
+
let i = 0;
|
|
3184
|
+
while (i < formatStr.length) {
|
|
3185
|
+
const ch = formatStr[i];
|
|
3186
|
+
if (ch === "^" && i + 1 < formatStr.length && formatStr[i + 1] >= "1" && formatStr[i + 1] <= "7") {
|
|
3187
|
+
const levelRef = formatStr.charCodeAt(i + 1) - 48;
|
|
3188
|
+
const idx = levelRef - 1;
|
|
3189
|
+
const counterVal = counters[idx] ?? 0;
|
|
3190
|
+
const start = numbering.startNumbers[idx] ?? 1;
|
|
3191
|
+
const num2 = counterVal > 0 ? start - 1 + counterVal : start;
|
|
3192
|
+
result += formatNumber(num2, headFormatToNumFmt(numbering.numberFormats[idx] ?? 0));
|
|
3193
|
+
i += 2;
|
|
3194
|
+
continue;
|
|
3281
3195
|
}
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
const name = escapeRegExp(img.filename);
|
|
3285
|
-
const mdRe = new RegExp(`!\\[image\\]\\((?:images/)?${name}\\)`, "g");
|
|
3286
|
-
out = out.replace(mdRe, () => ``);
|
|
3287
|
-
const imgTagRe = new RegExp(`(<img\\b[^>]*\\bsrc=")(?:images/)?${name}(")`, "g");
|
|
3288
|
-
out = out.replace(imgTagRe, (_m, pre, post) => `${pre}${dataUri}${post}`);
|
|
3196
|
+
result += ch;
|
|
3197
|
+
i++;
|
|
3289
3198
|
}
|
|
3290
|
-
return
|
|
3199
|
+
return result;
|
|
3291
3200
|
}
|
|
3292
3201
|
|
|
3293
3202
|
// src/hwp5/aes.ts
|
|
@@ -4317,6 +4226,7 @@ function parseHwp5Document(buffer, options) {
|
|
|
4317
4226
|
const totalTarget = pageFilter ? pageFilter.size : sections.length;
|
|
4318
4227
|
const bodyBlocks = [];
|
|
4319
4228
|
const doc = createHwp5DocState();
|
|
4229
|
+
doc.keepTrailingEmptyCols = options?.keepTrailingEmptyCols;
|
|
4320
4230
|
let totalDecompressed = 0;
|
|
4321
4231
|
let parsedSections = 0;
|
|
4322
4232
|
for (let si = 0; si < sections.length; si++) {
|
|
@@ -4337,7 +4247,7 @@ function parseHwp5Document(buffer, options) {
|
|
|
4337
4247
|
}
|
|
4338
4248
|
}
|
|
4339
4249
|
const blocks = [...doc.headerBlocks, ...bodyBlocks, ...doc.footerBlocks];
|
|
4340
|
-
const images = cfb ? extractHwp5Images(cfb.FileIndex, blocks, warnings) : extractHwp5ImagesLenient(lenientCfb, blocks, warnings);
|
|
4250
|
+
const images = cfb ? extractHwp5Images(cfb.FileIndex, blocks, warnings, !pageFilter) : extractHwp5ImagesLenient(lenientCfb, blocks, warnings, !pageFilter);
|
|
4341
4251
|
let flatBlocks = flattenLayoutTables(blocks);
|
|
4342
4252
|
if (options?.dedupeRunningHeaders) {
|
|
4343
4253
|
const deduped = dedupeRunningHeaders(flatBlocks);
|
|
@@ -4932,7 +4842,7 @@ function parseTableControl(ctrl, records, ctx) {
|
|
|
4932
4842
|
return table3;
|
|
4933
4843
|
}
|
|
4934
4844
|
const cellRows = arrangeCells(rows, cols, cells);
|
|
4935
|
-
const table2 = buildTable(cellRows);
|
|
4845
|
+
const table2 = buildTable(cellRows, { keepAnchoredEmptyCols: ctx.doc.keepTrailingEmptyCols });
|
|
4936
4846
|
if (caption && table2.rows > 0) table2.caption = caption;
|
|
4937
4847
|
return table2.rows > 0 ? table2 : null;
|
|
4938
4848
|
}
|
|
@@ -17126,6 +17036,7 @@ function readHeader(reader) {
|
|
|
17126
17036
|
}
|
|
17127
17037
|
|
|
17128
17038
|
// src/hwp3/parser.ts
|
|
17039
|
+
var MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
|
|
17129
17040
|
var PARA_SHAPE_SIZE = 187;
|
|
17130
17041
|
var LINE_INFO_SIZE = 14;
|
|
17131
17042
|
var INLINE_CHAR_SHAPE_SIZE = 31;
|
|
@@ -17161,8 +17072,11 @@ function parseHwp3Document(buffer, _options) {
|
|
|
17161
17072
|
const warnings = [];
|
|
17162
17073
|
if (header.compressed !== 0) {
|
|
17163
17074
|
try {
|
|
17164
|
-
body = inflateRawSync3(tail);
|
|
17075
|
+
body = inflateRawSync3(tail, { maxOutputLength: MAX_DECOMPRESS_SIZE3 });
|
|
17165
17076
|
} catch (err) {
|
|
17077
|
+
if (err?.code === "ERR_BUFFER_TOO_LARGE") {
|
|
17078
|
+
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`);
|
|
17079
|
+
}
|
|
17166
17080
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
17167
17081
|
throw new Error(`HWP3 \uC555\uCD95 \uD574\uC81C \uC2E4\uD328: ${msg2}`);
|
|
17168
17082
|
}
|
|
@@ -17359,7 +17273,7 @@ function isDistributionSentinel(markdown) {
|
|
|
17359
17273
|
import JSZip3 from "jszip";
|
|
17360
17274
|
import { DOMParser } from "@xmldom/xmldom";
|
|
17361
17275
|
var MAX_SHEETS = 100;
|
|
17362
|
-
var
|
|
17276
|
+
var MAX_DECOMPRESS_SIZE4 = 100 * 1024 * 1024;
|
|
17363
17277
|
var MAX_ROWS2 = 1e4;
|
|
17364
17278
|
var MAX_COLS2 = 200;
|
|
17365
17279
|
function cleanNumericValue(raw) {
|
|
@@ -17399,16 +17313,87 @@ function getTextContent(el) {
|
|
|
17399
17313
|
function parseXml(text) {
|
|
17400
17314
|
return new DOMParser().parseFromString(stripDtd(text), "text/xml");
|
|
17401
17315
|
}
|
|
17316
|
+
function collectRichText(root) {
|
|
17317
|
+
let out = "";
|
|
17318
|
+
const walk = (node) => {
|
|
17319
|
+
const children = node.childNodes;
|
|
17320
|
+
for (let i = 0; i < children.length; i++) {
|
|
17321
|
+
if (children[i].nodeType !== 1) continue;
|
|
17322
|
+
const el = children[i];
|
|
17323
|
+
const local = el.localName || el.tagName?.replace(/^[^:]+:/, "") || "";
|
|
17324
|
+
if (local === "rPh") continue;
|
|
17325
|
+
if (local === "t") out += el.textContent ?? "";
|
|
17326
|
+
else walk(el);
|
|
17327
|
+
}
|
|
17328
|
+
};
|
|
17329
|
+
walk(root);
|
|
17330
|
+
return out;
|
|
17331
|
+
}
|
|
17402
17332
|
function parseSharedStrings(xml) {
|
|
17403
17333
|
const doc = parseXml(xml);
|
|
17404
17334
|
const strings = [];
|
|
17405
17335
|
const siList = getElements(doc.documentElement, "si");
|
|
17406
17336
|
for (const si of siList) {
|
|
17407
|
-
|
|
17408
|
-
strings.push(tElements.map((t) => t.textContent ?? "").join(""));
|
|
17337
|
+
strings.push(collectRichText(si));
|
|
17409
17338
|
}
|
|
17410
17339
|
return strings;
|
|
17411
17340
|
}
|
|
17341
|
+
var BUILTIN_DATE_FMT = /* @__PURE__ */ new Map([
|
|
17342
|
+
[14, "date"],
|
|
17343
|
+
[15, "date"],
|
|
17344
|
+
[16, "date"],
|
|
17345
|
+
[17, "date"],
|
|
17346
|
+
[18, "datetime"],
|
|
17347
|
+
[19, "datetime"],
|
|
17348
|
+
[20, "datetime"],
|
|
17349
|
+
[21, "datetime"],
|
|
17350
|
+
[22, "datetime"],
|
|
17351
|
+
[45, "datetime"],
|
|
17352
|
+
[46, "datetime"],
|
|
17353
|
+
[47, "datetime"]
|
|
17354
|
+
]);
|
|
17355
|
+
function classifyDateFormat(code) {
|
|
17356
|
+
const stripped = code.replace(/"[^"]*"/g, "").replace(/\[[^\]]*\]/g, "").replace(/\\./g, "");
|
|
17357
|
+
if (!/[ymdh]/i.test(stripped)) return null;
|
|
17358
|
+
return /[hs]/i.test(stripped) ? "datetime" : "date";
|
|
17359
|
+
}
|
|
17360
|
+
function dateKindOfFmt(fmtId, customFormats) {
|
|
17361
|
+
const builtin = BUILTIN_DATE_FMT.get(fmtId);
|
|
17362
|
+
if (builtin) return builtin;
|
|
17363
|
+
const code = customFormats.get(fmtId);
|
|
17364
|
+
return code !== void 0 ? classifyDateFormat(code) : null;
|
|
17365
|
+
}
|
|
17366
|
+
function dateSerialToIso(serial, date1904, kind) {
|
|
17367
|
+
if (!isFinite(serial) || serial < 0) return null;
|
|
17368
|
+
let days = serial;
|
|
17369
|
+
if (date1904) days += 1462;
|
|
17370
|
+
else if (days < 60) days += 1;
|
|
17371
|
+
const ms = Math.round((days - 25569) * 864e5);
|
|
17372
|
+
const d = new Date(ms);
|
|
17373
|
+
if (isNaN(d.getTime()) || d.getUTCFullYear() > 9999) return null;
|
|
17374
|
+
const iso = d.toISOString();
|
|
17375
|
+
return kind === "datetime" ? iso.slice(0, 19) : iso.slice(0, 10);
|
|
17376
|
+
}
|
|
17377
|
+
function parseStyleDateXfs(xml) {
|
|
17378
|
+
const doc = parseXml(xml);
|
|
17379
|
+
const customFormats = /* @__PURE__ */ new Map();
|
|
17380
|
+
for (const el of getElements(doc.documentElement, "numFmt")) {
|
|
17381
|
+
const id = parseInt(el.getAttribute("numFmtId") ?? "", 10);
|
|
17382
|
+
if (isNaN(id)) continue;
|
|
17383
|
+
customFormats.set(id, el.getAttribute("formatCode") ?? "");
|
|
17384
|
+
}
|
|
17385
|
+
const dateXfs = /* @__PURE__ */ new Map();
|
|
17386
|
+
const cellXfsEls = getElements(doc.documentElement, "cellXfs");
|
|
17387
|
+
if (cellXfsEls.length === 0) return dateXfs;
|
|
17388
|
+
const xfs = getElements(cellXfsEls[0], "xf");
|
|
17389
|
+
for (let i = 0; i < xfs.length; i++) {
|
|
17390
|
+
const fmtId = parseInt(xfs[i].getAttribute("numFmtId") ?? "", 10);
|
|
17391
|
+
if (isNaN(fmtId)) continue;
|
|
17392
|
+
const kind = dateKindOfFmt(fmtId, customFormats);
|
|
17393
|
+
if (kind) dateXfs.set(i, kind);
|
|
17394
|
+
}
|
|
17395
|
+
return dateXfs;
|
|
17396
|
+
}
|
|
17412
17397
|
function parseWorkbook(xml) {
|
|
17413
17398
|
const doc = parseXml(xml);
|
|
17414
17399
|
const sheets = [];
|
|
@@ -17420,7 +17405,9 @@ function parseWorkbook(xml) {
|
|
|
17420
17405
|
rId: el.getAttribute("r:id") ?? ""
|
|
17421
17406
|
});
|
|
17422
17407
|
}
|
|
17423
|
-
|
|
17408
|
+
const prEls = getElements(doc.documentElement, "workbookPr");
|
|
17409
|
+
const d1904 = prEls.length > 0 ? prEls[0].getAttribute("date1904") : null;
|
|
17410
|
+
return { sheets, date1904: d1904 === "1" || d1904 === "true" };
|
|
17424
17411
|
}
|
|
17425
17412
|
function parseRels(xml) {
|
|
17426
17413
|
const doc = parseXml(xml);
|
|
@@ -17433,21 +17420,25 @@ function parseRels(xml) {
|
|
|
17433
17420
|
}
|
|
17434
17421
|
return map;
|
|
17435
17422
|
}
|
|
17436
|
-
function parseWorksheet(xml, sharedStrings) {
|
|
17423
|
+
function parseWorksheet(xml, sharedStrings, dateXfs, date1904) {
|
|
17437
17424
|
const doc = parseXml(xml);
|
|
17438
17425
|
const grid = [];
|
|
17439
17426
|
let maxRow = 0;
|
|
17440
17427
|
let maxCol = 0;
|
|
17441
17428
|
const rows = getElements(doc.documentElement, "row");
|
|
17429
|
+
let prevRow = -1;
|
|
17442
17430
|
for (const rowEl of rows) {
|
|
17443
|
-
const
|
|
17431
|
+
const rAttr = rowEl.getAttribute("r");
|
|
17432
|
+
const rowNum = rAttr !== null ? parseInt(rAttr, 10) - 1 : prevRow + 1;
|
|
17444
17433
|
if (rowNum < 0 || rowNum >= MAX_ROWS2) continue;
|
|
17434
|
+
if (Number.isFinite(rowNum)) prevRow = rowNum;
|
|
17445
17435
|
const cells = getElements(rowEl, "c");
|
|
17436
|
+
let prevCol = -1;
|
|
17446
17437
|
for (const cellEl of cells) {
|
|
17447
17438
|
const ref = cellEl.getAttribute("r");
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17439
|
+
const pos = ref !== null ? parseCellRef(ref) : { col: prevCol + 1, row: rowNum };
|
|
17440
|
+
if (!pos || !Number.isFinite(pos.row) || pos.row < 0 || pos.row >= MAX_ROWS2 || pos.col >= MAX_COLS2) continue;
|
|
17441
|
+
prevCol = pos.col;
|
|
17451
17442
|
const type = cellEl.getAttribute("t");
|
|
17452
17443
|
const vElements = getElements(cellEl, "v");
|
|
17453
17444
|
const fElements = getElements(cellEl, "f");
|
|
@@ -17461,12 +17452,19 @@ function parseWorksheet(xml, sharedStrings) {
|
|
|
17461
17452
|
value = raw === "1" ? "TRUE" : "FALSE";
|
|
17462
17453
|
} else {
|
|
17463
17454
|
value = cleanNumericValue(raw);
|
|
17455
|
+
if (type === null || type === "n") {
|
|
17456
|
+
const sAttr = cellEl.getAttribute("s");
|
|
17457
|
+
const kind = sAttr !== null ? dateXfs.get(parseInt(sAttr, 10)) : void 0;
|
|
17458
|
+
if (kind) {
|
|
17459
|
+
const iso = dateSerialToIso(parseFloat(raw), date1904, kind);
|
|
17460
|
+
if (iso) value = iso;
|
|
17461
|
+
}
|
|
17462
|
+
}
|
|
17464
17463
|
}
|
|
17465
17464
|
} else if (type === "inlineStr") {
|
|
17466
17465
|
const isEl = getElements(cellEl, "is");
|
|
17467
17466
|
if (isEl.length > 0) {
|
|
17468
|
-
|
|
17469
|
-
value = tElements.map((t) => t.textContent ?? "").join("");
|
|
17467
|
+
value = collectRichText(isEl[0]);
|
|
17470
17468
|
}
|
|
17471
17469
|
}
|
|
17472
17470
|
if (!value && fElements.length > 0) {
|
|
@@ -17485,7 +17483,14 @@ function parseWorksheet(xml, sharedStrings) {
|
|
|
17485
17483
|
const ref = el.getAttribute("ref");
|
|
17486
17484
|
if (!ref) continue;
|
|
17487
17485
|
const m = parseMergeRef(ref);
|
|
17488
|
-
if (m)
|
|
17486
|
+
if (m) {
|
|
17487
|
+
merges.push({
|
|
17488
|
+
startCol: Math.min(m.startCol, MAX_COLS2 - 1),
|
|
17489
|
+
startRow: Math.min(m.startRow, MAX_ROWS2 - 1),
|
|
17490
|
+
endCol: Math.min(m.endCol, MAX_COLS2 - 1),
|
|
17491
|
+
endRow: Math.min(m.endRow, MAX_ROWS2 - 1)
|
|
17492
|
+
});
|
|
17493
|
+
}
|
|
17489
17494
|
}
|
|
17490
17495
|
return { grid, merges, maxRow, maxCol };
|
|
17491
17496
|
}
|
|
@@ -17549,7 +17554,7 @@ function sheetToBlocks(sheetName, grid, merges, maxRow, maxCol, sheetIndex) {
|
|
|
17549
17554
|
return blocks;
|
|
17550
17555
|
}
|
|
17551
17556
|
async function parseXlsxDocument(buffer, options) {
|
|
17552
|
-
precheckZipSize(buffer,
|
|
17557
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE4);
|
|
17553
17558
|
const zip = await JSZip3.loadAsync(buffer);
|
|
17554
17559
|
const warnings = [];
|
|
17555
17560
|
const workbookFile = zip.file("xl/workbook.xml");
|
|
@@ -17561,10 +17566,18 @@ async function parseXlsxDocument(buffer, options) {
|
|
|
17561
17566
|
if (ssFile) {
|
|
17562
17567
|
sharedStrings = parseSharedStrings(await ssFile.async("text"));
|
|
17563
17568
|
}
|
|
17564
|
-
const sheets = parseWorkbook(await workbookFile.async("text"));
|
|
17569
|
+
const { sheets, date1904 } = parseWorkbook(await workbookFile.async("text"));
|
|
17565
17570
|
if (sheets.length === 0) {
|
|
17566
17571
|
throw new KordocError("XLSX \uD30C\uC77C\uC5D0 \uC2DC\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
17567
17572
|
}
|
|
17573
|
+
let dateXfs = /* @__PURE__ */ new Map();
|
|
17574
|
+
const stylesFile = zip.file("xl/styles.xml");
|
|
17575
|
+
if (stylesFile) {
|
|
17576
|
+
try {
|
|
17577
|
+
dateXfs = parseStyleDateXfs(await stylesFile.async("text"));
|
|
17578
|
+
} catch {
|
|
17579
|
+
}
|
|
17580
|
+
}
|
|
17568
17581
|
let relsMap = /* @__PURE__ */ new Map();
|
|
17569
17582
|
const relsFile = zip.file("xl/_rels/workbook.xml.rels");
|
|
17570
17583
|
if (relsFile) {
|
|
@@ -17572,7 +17585,7 @@ async function parseXlsxDocument(buffer, options) {
|
|
|
17572
17585
|
}
|
|
17573
17586
|
let pageFilter = null;
|
|
17574
17587
|
if (options?.pages) {
|
|
17575
|
-
const { parsePageRange: parsePageRange2 } = await import("./page-range-
|
|
17588
|
+
const { parsePageRange: parsePageRange2 } = await import("./page-range-737B4EZW.js");
|
|
17576
17589
|
pageFilter = parsePageRange2(options.pages, sheets.length);
|
|
17577
17590
|
}
|
|
17578
17591
|
const blocks = [];
|
|
@@ -17602,7 +17615,7 @@ async function parseXlsxDocument(buffer, options) {
|
|
|
17602
17615
|
}
|
|
17603
17616
|
try {
|
|
17604
17617
|
const sheetXml = await sheetFile.async("text");
|
|
17605
|
-
const { grid, merges, maxRow, maxCol } = parseWorksheet(sheetXml, sharedStrings);
|
|
17618
|
+
const { grid, merges, maxRow, maxCol } = parseWorksheet(sheetXml, sharedStrings, dateXfs, date1904);
|
|
17606
17619
|
const sheetBlocks = sheetToBlocks(sheet.name, grid, merges, maxRow, maxCol, i);
|
|
17607
17620
|
blocks.push(...sheetBlocks);
|
|
17608
17621
|
} catch (err) {
|
|
@@ -17646,7 +17659,10 @@ var OP_CONTINUE = 60;
|
|
|
17646
17659
|
var OP_BOUNDSHEET8 = 133;
|
|
17647
17660
|
var OP_SST = 252;
|
|
17648
17661
|
var OP_CODEPAGE = 66;
|
|
17662
|
+
var OP_DATE1904 = 34;
|
|
17649
17663
|
var OP_FILEPASS = 47;
|
|
17664
|
+
var OP_FORMAT = 1054;
|
|
17665
|
+
var OP_XF = 224;
|
|
17650
17666
|
var OP_NUMBER = 515;
|
|
17651
17667
|
var OP_RK = 638;
|
|
17652
17668
|
var OP_MULRK = 189;
|
|
@@ -17658,6 +17674,8 @@ var OP_BOOLERR = 517;
|
|
|
17658
17674
|
var OP_BLANK = 513;
|
|
17659
17675
|
var OP_MULBLANK = 190;
|
|
17660
17676
|
var OP_MERGECELLS = 229;
|
|
17677
|
+
var OP_SHRFMLA = 1212;
|
|
17678
|
+
var OP_ARRAY = 545;
|
|
17661
17679
|
var DT_GLOBALS = 5;
|
|
17662
17680
|
var DT_WORKSHEET = 16;
|
|
17663
17681
|
var MAX_RECORDS2 = 1e6;
|
|
@@ -17753,7 +17771,7 @@ function decodeUtf16Le(buf) {
|
|
|
17753
17771
|
}
|
|
17754
17772
|
|
|
17755
17773
|
// src/xls/sst.ts
|
|
17756
|
-
function parseString(buf, offset, segments) {
|
|
17774
|
+
function parseString(buf, offset, segments, segCursor) {
|
|
17757
17775
|
if (offset + 3 > buf.length) return null;
|
|
17758
17776
|
const cch = buf.readUInt16LE(offset);
|
|
17759
17777
|
let flags = buf.readUInt8(offset + 2);
|
|
@@ -17776,7 +17794,15 @@ function parseString(buf, offset, segments) {
|
|
|
17776
17794
|
const charBytes = [];
|
|
17777
17795
|
let charsRead = 0;
|
|
17778
17796
|
while (charsRead < cch) {
|
|
17779
|
-
|
|
17797
|
+
while (segCursor.idx < segments.length && segments[segCursor.idx] < off) segCursor.idx++;
|
|
17798
|
+
if (segCursor.idx < segments.length && segments[segCursor.idx] === off) {
|
|
17799
|
+
if (off >= buf.length) return null;
|
|
17800
|
+
flags = buf.readUInt8(off);
|
|
17801
|
+
highByte = (flags & 1) !== 0;
|
|
17802
|
+
off += 1;
|
|
17803
|
+
segCursor.idx++;
|
|
17804
|
+
}
|
|
17805
|
+
const nextBoundary = segCursor.idx < segments.length ? segments[segCursor.idx] : buf.length;
|
|
17780
17806
|
const remainChars = cch - charsRead;
|
|
17781
17807
|
const bytesPerChar = highByte ? 2 : 1;
|
|
17782
17808
|
const bytesAvail = nextBoundary - off;
|
|
@@ -17787,12 +17813,10 @@ function parseString(buf, offset, segments) {
|
|
|
17787
17813
|
charBytes.push(highByte ? slice : padToUtf16(slice));
|
|
17788
17814
|
off += bytesToRead;
|
|
17789
17815
|
charsRead += charsInThisRun;
|
|
17790
|
-
}
|
|
17791
|
-
|
|
17792
|
-
|
|
17793
|
-
|
|
17794
|
-
highByte = (flags & 1) !== 0;
|
|
17795
|
-
off += 1;
|
|
17816
|
+
} else if (nextBoundary < buf.length) {
|
|
17817
|
+
off = nextBoundary;
|
|
17818
|
+
} else {
|
|
17819
|
+
return null;
|
|
17796
17820
|
}
|
|
17797
17821
|
}
|
|
17798
17822
|
const text = decodeUtf16Le(Buffer.concat(charBytes));
|
|
@@ -17817,8 +17841,9 @@ function decodeSST(records) {
|
|
|
17817
17841
|
const cstUnique = combined.readUInt32LE(4);
|
|
17818
17842
|
const strings = [];
|
|
17819
17843
|
let off = 8;
|
|
17844
|
+
const segCursor = { idx: 0 };
|
|
17820
17845
|
for (let i = 0; i < cstUnique && off < combined.length; i++) {
|
|
17821
|
-
const r = parseString(combined, off, segments);
|
|
17846
|
+
const r = parseString(combined, off, segments, segCursor);
|
|
17822
17847
|
if (!r) break;
|
|
17823
17848
|
strings.push(r.text);
|
|
17824
17849
|
off += r.consumed;
|
|
@@ -17893,7 +17918,7 @@ function decodeFormulaStringRecord(data) {
|
|
|
17893
17918
|
return decodeUtf16Le(padded);
|
|
17894
17919
|
}
|
|
17895
17920
|
}
|
|
17896
|
-
function extractSheetCells(records, bofIndex, sst) {
|
|
17921
|
+
function extractSheetCells(records, bofIndex, sst, convertNum) {
|
|
17897
17922
|
const cells = [];
|
|
17898
17923
|
const merges = [];
|
|
17899
17924
|
const bofOffset = records[bofIndex].offset;
|
|
@@ -17911,14 +17936,16 @@ function extractSheetCells(records, bofIndex, sst) {
|
|
|
17911
17936
|
case OP_NUMBER: {
|
|
17912
17937
|
const h = readCellHeader(rec.data);
|
|
17913
17938
|
if (h && rec.data.length >= 14) {
|
|
17914
|
-
|
|
17939
|
+
const n = rec.data.readDoubleLE(6);
|
|
17940
|
+
cells.push({ row: h.row, col: h.col, value: convertNum ? convertNum(n, h.ixfe) : n });
|
|
17915
17941
|
}
|
|
17916
17942
|
break;
|
|
17917
17943
|
}
|
|
17918
17944
|
case OP_RK: {
|
|
17919
17945
|
const h = readCellHeader(rec.data);
|
|
17920
17946
|
if (h && rec.data.length >= 10) {
|
|
17921
|
-
|
|
17947
|
+
const n = decodeRk(rec.data.readInt32LE(6));
|
|
17948
|
+
cells.push({ row: h.row, col: h.col, value: convertNum ? convertNum(n, h.ixfe) : n });
|
|
17922
17949
|
}
|
|
17923
17950
|
break;
|
|
17924
17951
|
}
|
|
@@ -17926,7 +17953,7 @@ function extractSheetCells(records, bofIndex, sst) {
|
|
|
17926
17953
|
const m = decodeMulRk(rec.data);
|
|
17927
17954
|
if (m) {
|
|
17928
17955
|
for (const c of m.cells) {
|
|
17929
|
-
cells.push({ row: m.row, col: c.col, value: c.value });
|
|
17956
|
+
cells.push({ row: m.row, col: c.col, value: convertNum ? convertNum(c.value, c.ixfe) : c.value });
|
|
17930
17957
|
}
|
|
17931
17958
|
}
|
|
17932
17959
|
break;
|
|
@@ -17951,19 +17978,26 @@ function extractSheetCells(records, bofIndex, sst) {
|
|
|
17951
17978
|
if (h && rec.data.length >= 14) {
|
|
17952
17979
|
const result = decodeFormulaResult(rec.data.subarray(6, 14));
|
|
17953
17980
|
if (result.kind === "stringRef") {
|
|
17954
|
-
|
|
17981
|
+
let j = i + 1;
|
|
17982
|
+
while (j < records.length && (records[j].opcode === OP_SHRFMLA || records[j].opcode === OP_ARRAY)) j++;
|
|
17983
|
+
const next = records[j];
|
|
17955
17984
|
if (next && next.opcode === OP_STRING) {
|
|
17956
17985
|
cells.push({
|
|
17957
17986
|
row: h.row,
|
|
17958
17987
|
col: h.col,
|
|
17959
17988
|
value: decodeFormulaStringRecord(next.data)
|
|
17960
17989
|
});
|
|
17961
|
-
i
|
|
17990
|
+
i = j;
|
|
17962
17991
|
} else {
|
|
17963
17992
|
cells.push({ row: h.row, col: h.col, value: "" });
|
|
17964
17993
|
}
|
|
17965
17994
|
} else {
|
|
17966
|
-
|
|
17995
|
+
const v = result.value;
|
|
17996
|
+
cells.push({
|
|
17997
|
+
row: h.row,
|
|
17998
|
+
col: h.col,
|
|
17999
|
+
value: convertNum && typeof v === "number" ? convertNum(v, h.ixfe) : v
|
|
18000
|
+
});
|
|
17967
18001
|
}
|
|
17968
18002
|
}
|
|
17969
18003
|
break;
|
|
@@ -18013,7 +18047,7 @@ function extractSheetCells(records, bofIndex, sst) {
|
|
|
18013
18047
|
|
|
18014
18048
|
// src/xls/parser.ts
|
|
18015
18049
|
var MAX_SHEETS2 = 100;
|
|
18016
|
-
var MAX_ROWS3 =
|
|
18050
|
+
var MAX_ROWS3 = 65536;
|
|
18017
18051
|
var MAX_COLS3 = 1e3;
|
|
18018
18052
|
function decodeBoundSheet(data) {
|
|
18019
18053
|
if (data.length < 8) return null;
|
|
@@ -18036,10 +18070,33 @@ function decodeBoundSheet(data) {
|
|
|
18036
18070
|
}
|
|
18037
18071
|
return { name, lbPlyPos, dt };
|
|
18038
18072
|
}
|
|
18073
|
+
function decodeFormatRecord(data) {
|
|
18074
|
+
if (data.length < 5) return null;
|
|
18075
|
+
const ifmt = data.readUInt16LE(0);
|
|
18076
|
+
const cch = data.readUInt16LE(2);
|
|
18077
|
+
const flags = data.readUInt8(4);
|
|
18078
|
+
const highByte = (flags & 1) !== 0;
|
|
18079
|
+
const start = 5;
|
|
18080
|
+
let code;
|
|
18081
|
+
if (highByte) {
|
|
18082
|
+
const end = Math.min(start + cch * 2, data.length);
|
|
18083
|
+
code = decodeUtf16Le(data.subarray(start, end));
|
|
18084
|
+
} else {
|
|
18085
|
+
const end = Math.min(start + cch, data.length);
|
|
18086
|
+
const slice = data.subarray(start, end);
|
|
18087
|
+
const padded = Buffer.alloc(slice.length * 2);
|
|
18088
|
+
for (let i = 0; i < slice.length; i++) padded[i * 2] = slice[i];
|
|
18089
|
+
code = decodeUtf16Le(padded);
|
|
18090
|
+
}
|
|
18091
|
+
return { ifmt, code };
|
|
18092
|
+
}
|
|
18039
18093
|
function processGlobals(records) {
|
|
18040
18094
|
const sheets = [];
|
|
18041
18095
|
let codePage = 1200;
|
|
18042
18096
|
let encrypted = false;
|
|
18097
|
+
let date1904 = false;
|
|
18098
|
+
const customFormats = /* @__PURE__ */ new Map();
|
|
18099
|
+
const xfFmtIds = [];
|
|
18043
18100
|
const firstBof = records[0];
|
|
18044
18101
|
if (!firstBof || firstBof.opcode !== OP_BOF) {
|
|
18045
18102
|
throw new KordocError("XLS: \uCCAB \uB808\uCF54\uB4DC\uAC00 BOF\uAC00 \uC544\uB2D8");
|
|
@@ -18062,21 +18119,41 @@ function processGlobals(records) {
|
|
|
18062
18119
|
codePage = r.data.readUInt16LE(0);
|
|
18063
18120
|
} else if (r.opcode === OP_FILEPASS) {
|
|
18064
18121
|
encrypted = true;
|
|
18122
|
+
} else if (r.opcode === OP_DATE1904 && r.data.length >= 2) {
|
|
18123
|
+
date1904 = r.data.readUInt16LE(0) === 1;
|
|
18124
|
+
} else if (r.opcode === OP_FORMAT) {
|
|
18125
|
+
const f = decodeFormatRecord(r.data);
|
|
18126
|
+
if (f) customFormats.set(f.ifmt, f.code);
|
|
18127
|
+
} else if (r.opcode === OP_XF && r.data.length >= 4) {
|
|
18128
|
+
xfFmtIds.push(r.data.readUInt16LE(2));
|
|
18065
18129
|
}
|
|
18066
18130
|
i++;
|
|
18067
18131
|
}
|
|
18132
|
+
const dateXfs = /* @__PURE__ */ new Map();
|
|
18133
|
+
for (let k = 0; k < xfFmtIds.length; k++) {
|
|
18134
|
+
const kind = dateKindOfFmt(xfFmtIds[k], customFormats);
|
|
18135
|
+
if (kind) dateXfs.set(k, kind);
|
|
18136
|
+
}
|
|
18068
18137
|
const globalsRecords = records.slice(0, i);
|
|
18069
18138
|
const sst = decodeSST(globalsRecords);
|
|
18070
|
-
return { sheets, sst, codePage, encrypted, endIndex: i };
|
|
18139
|
+
return { sheets, sst, codePage, encrypted, dateXfs, date1904, endIndex: i };
|
|
18071
18140
|
}
|
|
18072
18141
|
function findSheetBofIndex(records, lbPlyPos) {
|
|
18073
18142
|
const exact = records.findIndex(
|
|
18074
18143
|
(r) => r.opcode === OP_BOF && r.offset === lbPlyPos
|
|
18075
18144
|
);
|
|
18076
18145
|
if (exact >= 0) return exact;
|
|
18077
|
-
|
|
18078
|
-
|
|
18079
|
-
|
|
18146
|
+
let best = -1;
|
|
18147
|
+
let bestOffset = Infinity;
|
|
18148
|
+
for (let idx = 1; idx < records.length; idx++) {
|
|
18149
|
+
const r = records[idx];
|
|
18150
|
+
if (r.opcode !== OP_BOF) continue;
|
|
18151
|
+
if (r.offset >= lbPlyPos && r.offset < bestOffset) {
|
|
18152
|
+
best = idx;
|
|
18153
|
+
bestOffset = r.offset;
|
|
18154
|
+
}
|
|
18155
|
+
}
|
|
18156
|
+
return best;
|
|
18080
18157
|
}
|
|
18081
18158
|
function cellValueToText(v) {
|
|
18082
18159
|
if (v === null || v === void 0) return "";
|
|
@@ -18207,10 +18284,18 @@ async function parseXlsDocument(buffer, options) {
|
|
|
18207
18284
|
]
|
|
18208
18285
|
};
|
|
18209
18286
|
}
|
|
18287
|
+
const convertNum = globals.dateXfs.size > 0 ? (n, ixfe) => {
|
|
18288
|
+
const kind = globals.dateXfs.get(ixfe);
|
|
18289
|
+
if (kind) {
|
|
18290
|
+
const iso = dateSerialToIso(n, globals.date1904, kind);
|
|
18291
|
+
if (iso) return iso;
|
|
18292
|
+
}
|
|
18293
|
+
return n;
|
|
18294
|
+
} : void 0;
|
|
18210
18295
|
const totalSheets = Math.min(globals.sheets.length, MAX_SHEETS2);
|
|
18211
18296
|
let pageFilter = null;
|
|
18212
18297
|
if (options?.pages) {
|
|
18213
|
-
const { parsePageRange: parsePageRange2 } = await import("./page-range-
|
|
18298
|
+
const { parsePageRange: parsePageRange2 } = await import("./page-range-737B4EZW.js");
|
|
18214
18299
|
pageFilter = parsePageRange2(options.pages, totalSheets);
|
|
18215
18300
|
}
|
|
18216
18301
|
const allBlocks = [];
|
|
@@ -18233,7 +18318,7 @@ async function parseXlsDocument(buffer, options) {
|
|
|
18233
18318
|
continue;
|
|
18234
18319
|
}
|
|
18235
18320
|
try {
|
|
18236
|
-
const { sheet } = extractSheetCells(records, bofIdx, globals.sst);
|
|
18321
|
+
const { sheet } = extractSheetCells(records, bofIdx, globals.sst, convertNum);
|
|
18237
18322
|
const blocks = sheetToBlocks2(meta.name, sheet, i);
|
|
18238
18323
|
allBlocks.push(...blocks);
|
|
18239
18324
|
} catch (e) {
|
|
@@ -18361,6 +18446,9 @@ var NARY_MAP = {
|
|
|
18361
18446
|
"\u2A02": "\\bigotimes",
|
|
18362
18447
|
"\u2A00": "\\bigodot"
|
|
18363
18448
|
};
|
|
18449
|
+
function onOffVal(v) {
|
|
18450
|
+
return v !== "0" && v !== "false" && v !== "off";
|
|
18451
|
+
}
|
|
18364
18452
|
function mapDelim(ch, isLeft) {
|
|
18365
18453
|
const l = {
|
|
18366
18454
|
"(": "(",
|
|
@@ -18387,10 +18475,23 @@ function mapDelim(ch, isLeft) {
|
|
|
18387
18475
|
const map = isLeft ? l : r;
|
|
18388
18476
|
return map[ch] ?? ch;
|
|
18389
18477
|
}
|
|
18478
|
+
function isBalancedWrap(s) {
|
|
18479
|
+
let depth = 0;
|
|
18480
|
+
for (let i = 0; i < s.length; i++) {
|
|
18481
|
+
const ch = s[i];
|
|
18482
|
+
if (ch === "{" && s[i - 1] !== "\\") depth++;
|
|
18483
|
+
else if (ch === "}" && s[i - 1] !== "\\") {
|
|
18484
|
+
depth--;
|
|
18485
|
+
if (depth === 0) return i === s.length - 1;
|
|
18486
|
+
if (depth < 0) return false;
|
|
18487
|
+
}
|
|
18488
|
+
}
|
|
18489
|
+
return false;
|
|
18490
|
+
}
|
|
18390
18491
|
function grp(body) {
|
|
18391
18492
|
const s = body.trim();
|
|
18392
18493
|
if (s.length === 0) return "{}";
|
|
18393
|
-
if (s.startsWith("{") && s.endsWith("}")) return s;
|
|
18494
|
+
if (s.startsWith("{") && s.endsWith("}") && isBalancedWrap(s)) return s;
|
|
18394
18495
|
return "{" + s + "}";
|
|
18395
18496
|
}
|
|
18396
18497
|
function childrenToLatex(parent) {
|
|
@@ -18484,8 +18585,8 @@ function nodeToLatex(el) {
|
|
|
18484
18585
|
}
|
|
18485
18586
|
const sh = firstKid(naryPr, "subHide");
|
|
18486
18587
|
const ph = firstKid(naryPr, "supHide");
|
|
18487
|
-
if (sh) subHide = (sh.getAttribute("m:val") ?? sh.getAttribute("val"))
|
|
18488
|
-
if (ph) supHide = (ph.getAttribute("m:val") ?? ph.getAttribute("val"))
|
|
18588
|
+
if (sh) subHide = onOffVal(sh.getAttribute("m:val") ?? sh.getAttribute("val"));
|
|
18589
|
+
if (ph) supHide = onOffVal(ph.getAttribute("m:val") ?? ph.getAttribute("val"));
|
|
18489
18590
|
const ll = firstKid(naryPr, "limLoc");
|
|
18490
18591
|
if (ll) limLoc = ll.getAttribute("m:val") ?? ll.getAttribute("val") ?? "";
|
|
18491
18592
|
}
|
|
@@ -18632,7 +18733,7 @@ function isDisplayMath(el) {
|
|
|
18632
18733
|
}
|
|
18633
18734
|
|
|
18634
18735
|
// src/docx/parser.ts
|
|
18635
|
-
var
|
|
18736
|
+
var MAX_DECOMPRESS_SIZE5 = 100 * 1024 * 1024;
|
|
18636
18737
|
function matchesLocal(el, localName2) {
|
|
18637
18738
|
return el.localName === localName2 || (el.tagName?.endsWith(`:${localName2}`) ?? false);
|
|
18638
18739
|
}
|
|
@@ -18650,6 +18751,8 @@ function effectiveChildElements(parent) {
|
|
|
18650
18751
|
result.push(...effectiveChildElements(c));
|
|
18651
18752
|
}
|
|
18652
18753
|
}
|
|
18754
|
+
} else if (matchesLocal(el, "ins") || matchesLocal(el, "smartTag")) {
|
|
18755
|
+
result.push(...effectiveChildElements(el));
|
|
18653
18756
|
} else {
|
|
18654
18757
|
result.push(el);
|
|
18655
18758
|
}
|
|
@@ -18714,6 +18817,21 @@ function parseStyles(xml) {
|
|
|
18714
18817
|
}
|
|
18715
18818
|
styles.set(styleId, { name, basedOn, outlineLevel });
|
|
18716
18819
|
}
|
|
18820
|
+
for (const [styleId, info] of styles) {
|
|
18821
|
+
if (info.outlineLevel !== void 0) continue;
|
|
18822
|
+
const seen = /* @__PURE__ */ new Set([styleId]);
|
|
18823
|
+
let cur = info.basedOn;
|
|
18824
|
+
while (cur && !seen.has(cur)) {
|
|
18825
|
+
seen.add(cur);
|
|
18826
|
+
const parent = styles.get(cur);
|
|
18827
|
+
if (!parent) break;
|
|
18828
|
+
if (parent.outlineLevel !== void 0) {
|
|
18829
|
+
info.outlineLevel = parent.outlineLevel;
|
|
18830
|
+
break;
|
|
18831
|
+
}
|
|
18832
|
+
cur = parent.basedOn;
|
|
18833
|
+
}
|
|
18834
|
+
}
|
|
18717
18835
|
return styles;
|
|
18718
18836
|
}
|
|
18719
18837
|
function parseNumbering(xml) {
|
|
@@ -18801,8 +18919,12 @@ function collectOmmlRoots(p) {
|
|
|
18801
18919
|
return out;
|
|
18802
18920
|
}
|
|
18803
18921
|
function extractRun(r) {
|
|
18804
|
-
|
|
18805
|
-
const
|
|
18922
|
+
let text = "";
|
|
18923
|
+
for (const el of effectiveChildElements(r)) {
|
|
18924
|
+
if (matchesLocal(el, "t")) text += el.textContent ?? "";
|
|
18925
|
+
else if (matchesLocal(el, "br") || matchesLocal(el, "cr")) text += "\n";
|
|
18926
|
+
else if (matchesLocal(el, "tab")) text += " ";
|
|
18927
|
+
}
|
|
18806
18928
|
let bold = false;
|
|
18807
18929
|
let italic = false;
|
|
18808
18930
|
const rPrEls = getChildElements(r, "rPr");
|
|
@@ -18957,7 +19079,7 @@ function collectTextboxParagraphs(node, inTxbx = false, out = [], depth = 0) {
|
|
|
18957
19079
|
}
|
|
18958
19080
|
return out;
|
|
18959
19081
|
}
|
|
18960
|
-
function parseTable(tbl2, styles, numbering, footnotes, rels) {
|
|
19082
|
+
function parseTable(tbl2, styles, numbering, footnotes, rels, keepEmptyCols) {
|
|
18961
19083
|
const trElements = getChildElements(tbl2, "tr");
|
|
18962
19084
|
if (trElements.length === 0) return null;
|
|
18963
19085
|
const rawRows = [];
|
|
@@ -19019,7 +19141,7 @@ ${cell2.text}` : cell2.text;
|
|
|
19019
19141
|
return { text: cell2.text, colSpan: cell2.colSpan, rowSpan, colAddr: cell2.col, rowAddr: r };
|
|
19020
19142
|
})
|
|
19021
19143
|
);
|
|
19022
|
-
const table2 = buildTable(cellRows);
|
|
19144
|
+
const table2 = buildTable(cellRows, { keepAnchoredEmptyCols: keepEmptyCols });
|
|
19023
19145
|
if (table2.rows === 0 || table2.cols === 0) return null;
|
|
19024
19146
|
return { type: "table", table: table2 };
|
|
19025
19147
|
}
|
|
@@ -19048,9 +19170,17 @@ async function buildImageMap(zip, rels, doc, warnings) {
|
|
|
19048
19170
|
const imageMap = /* @__PURE__ */ new Map();
|
|
19049
19171
|
const images = [];
|
|
19050
19172
|
let imgIdx = 0;
|
|
19173
|
+
const imageRefs = [];
|
|
19051
19174
|
for (const blip of findElements(doc.documentElement, "blip")) {
|
|
19052
19175
|
const embedId = getAttr(blip, "embed");
|
|
19053
|
-
if (
|
|
19176
|
+
if (embedId) imageRefs.push(embedId);
|
|
19177
|
+
}
|
|
19178
|
+
for (const imagedata of collectNonFallbackImagedata(doc.documentElement)) {
|
|
19179
|
+
const embedId = getAttr(imagedata, "id");
|
|
19180
|
+
if (embedId) imageRefs.push(embedId);
|
|
19181
|
+
}
|
|
19182
|
+
for (const embedId of imageRefs) {
|
|
19183
|
+
if (imageMap.has(embedId)) continue;
|
|
19054
19184
|
const target = rels.get(embedId);
|
|
19055
19185
|
if (!target) continue;
|
|
19056
19186
|
const imgPath = target.startsWith("/") ? target.slice(1) : target.startsWith("word/") ? target : `word/${target}`;
|
|
@@ -19085,14 +19215,26 @@ function collectParagraphBlips(node, out = [], depth = 0) {
|
|
|
19085
19215
|
if (depth > 40) return out;
|
|
19086
19216
|
for (const el of effectiveChildElements(node)) {
|
|
19087
19217
|
if (matchesLocal(el, "txbxContent") || matchesLocal(el, "Fallback")) continue;
|
|
19088
|
-
if (matchesLocal(el, "blip")) out.push(el);
|
|
19218
|
+
if (matchesLocal(el, "blip") || matchesLocal(el, "imagedata")) out.push(el);
|
|
19089
19219
|
else collectParagraphBlips(el, out, depth + 1);
|
|
19090
19220
|
}
|
|
19091
19221
|
return out;
|
|
19092
19222
|
}
|
|
19223
|
+
function collectNonFallbackImagedata(node, out = [], depth = 0) {
|
|
19224
|
+
if (depth > 60) return out;
|
|
19225
|
+
const children = node.childNodes;
|
|
19226
|
+
for (let i = 0; i < children.length; i++) {
|
|
19227
|
+
if (children[i].nodeType !== 1) continue;
|
|
19228
|
+
const el = children[i];
|
|
19229
|
+
if (matchesLocal(el, "Fallback")) continue;
|
|
19230
|
+
if (matchesLocal(el, "imagedata")) out.push(el);
|
|
19231
|
+
else collectNonFallbackImagedata(el, out, depth + 1);
|
|
19232
|
+
}
|
|
19233
|
+
return out;
|
|
19234
|
+
}
|
|
19093
19235
|
function emitParagraphImages(p, imageMap, linked, out) {
|
|
19094
19236
|
for (const blip of collectParagraphBlips(p)) {
|
|
19095
|
-
const embedId = getAttr(blip, "embed");
|
|
19237
|
+
const embedId = getAttr(blip, "embed") ?? getAttr(blip, "id");
|
|
19096
19238
|
if (!embedId) continue;
|
|
19097
19239
|
const filename = imageMap.get(embedId);
|
|
19098
19240
|
if (!filename) continue;
|
|
@@ -19101,7 +19243,7 @@ function emitParagraphImages(p, imageMap, linked, out) {
|
|
|
19101
19243
|
}
|
|
19102
19244
|
}
|
|
19103
19245
|
async function parseDocxDocument(buffer, options) {
|
|
19104
|
-
precheckZipSize(buffer,
|
|
19246
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE5);
|
|
19105
19247
|
const zip = await JSZip4.loadAsync(buffer);
|
|
19106
19248
|
const warnings = [];
|
|
19107
19249
|
const docFile = zip.file("word/document.xml");
|
|
@@ -19172,7 +19314,7 @@ async function parseDocxDocument(buffer, options) {
|
|
|
19172
19314
|
if (imageMap.size > 0) emitParagraphImages(tp, imageMap, linkedImages, blocks);
|
|
19173
19315
|
}
|
|
19174
19316
|
} else if (localName2 === "tbl") {
|
|
19175
|
-
const block = parseTable(el, styles, numbering, footnotes, rels);
|
|
19317
|
+
const block = parseTable(el, styles, numbering, footnotes, rels, options?.keepTrailingEmptyCols);
|
|
19176
19318
|
if (block) blocks.push(block);
|
|
19177
19319
|
}
|
|
19178
19320
|
}
|
|
@@ -19264,7 +19406,7 @@ function parseHwpmlDocument(buffer, options) {
|
|
|
19264
19406
|
if (localName(el) !== "SECTION") continue;
|
|
19265
19407
|
sectionIdx++;
|
|
19266
19408
|
if (pageFilter && !pageFilter.has(sectionIdx)) continue;
|
|
19267
|
-
parseSection2(el, blocks, paraShapeMap, sectionIdx, warnings);
|
|
19409
|
+
parseSection2(el, blocks, paraShapeMap, sectionIdx, warnings, options?.keepTrailingEmptyCols ?? false);
|
|
19268
19410
|
}
|
|
19269
19411
|
const outline = blocks.filter((b) => b.type === "heading" && b.text).map((b) => ({ level: b.level ?? 1, text: b.text, pageNumber: b.pageNumber }));
|
|
19270
19412
|
const markdown = blocksToMarkdown(blocks);
|
|
@@ -19300,10 +19442,10 @@ function buildParaShapeMap(root) {
|
|
|
19300
19442
|
}
|
|
19301
19443
|
return map;
|
|
19302
19444
|
}
|
|
19303
|
-
function parseSection2(section, blocks, paraShapeMap, sectionNum, warnings) {
|
|
19304
|
-
walkContent(section, blocks, paraShapeMap, sectionNum, warnings, false);
|
|
19445
|
+
function parseSection2(section, blocks, paraShapeMap, sectionNum, warnings, keep) {
|
|
19446
|
+
walkContent(section, blocks, paraShapeMap, sectionNum, warnings, false, keep);
|
|
19305
19447
|
}
|
|
19306
|
-
function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, depth = 0) {
|
|
19448
|
+
function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, keep, depth = 0) {
|
|
19307
19449
|
if (depth > MAX_XML_DEPTH2) return;
|
|
19308
19450
|
const children = node.childNodes;
|
|
19309
19451
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -19316,24 +19458,24 @@ function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderF
|
|
|
19316
19458
|
if (tag === "P") {
|
|
19317
19459
|
if (!inHeaderFooter) {
|
|
19318
19460
|
parseParagraph3(el, blocks, paraShapeMap, sectionNum);
|
|
19319
|
-
walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings);
|
|
19461
|
+
walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings, keep);
|
|
19320
19462
|
}
|
|
19321
19463
|
continue;
|
|
19322
19464
|
}
|
|
19323
19465
|
if (tag === "TABLE") {
|
|
19324
19466
|
if (!inHeaderFooter) {
|
|
19325
|
-
parseTable2(el, blocks, paraShapeMap, sectionNum, warnings);
|
|
19467
|
+
parseTable2(el, blocks, paraShapeMap, sectionNum, warnings, keep);
|
|
19326
19468
|
}
|
|
19327
19469
|
continue;
|
|
19328
19470
|
}
|
|
19329
19471
|
if (tag === "PARALIST" || tag === "SECTION" || tag === "COLDEF") {
|
|
19330
|
-
walkContent(el, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, depth + 1);
|
|
19472
|
+
walkContent(el, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, keep, depth + 1);
|
|
19331
19473
|
continue;
|
|
19332
19474
|
}
|
|
19333
|
-
walkContent(el, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, depth + 1);
|
|
19475
|
+
walkContent(el, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, keep, depth + 1);
|
|
19334
19476
|
}
|
|
19335
19477
|
}
|
|
19336
|
-
function walkTablesInP(node, blocks, paraShapeMap, sectionNum, warnings, depth = 0) {
|
|
19478
|
+
function walkTablesInP(node, blocks, paraShapeMap, sectionNum, warnings, keep, depth = 0) {
|
|
19337
19479
|
if (depth > MAX_XML_DEPTH2) return;
|
|
19338
19480
|
const children = node.childNodes;
|
|
19339
19481
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -19341,11 +19483,11 @@ function walkTablesInP(node, blocks, paraShapeMap, sectionNum, warnings, depth =
|
|
|
19341
19483
|
if (el.nodeType !== 1) continue;
|
|
19342
19484
|
const tag = localName(el);
|
|
19343
19485
|
if (tag === "TABLE") {
|
|
19344
|
-
parseTable2(el, blocks, paraShapeMap, sectionNum, warnings);
|
|
19486
|
+
parseTable2(el, blocks, paraShapeMap, sectionNum, warnings, keep);
|
|
19345
19487
|
continue;
|
|
19346
19488
|
}
|
|
19347
19489
|
if (tag === "FOOTNOTE" || tag === "ENDNOTE" || tag === "HEADER" || tag === "FOOTER") continue;
|
|
19348
|
-
walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings, depth + 1);
|
|
19490
|
+
walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings, keep, depth + 1);
|
|
19349
19491
|
}
|
|
19350
19492
|
}
|
|
19351
19493
|
function parseParagraph3(el, blocks, paraShapeMap, sectionNum) {
|
|
@@ -19381,7 +19523,7 @@ function collectCharText(node, parts, depth = 0) {
|
|
|
19381
19523
|
}
|
|
19382
19524
|
}
|
|
19383
19525
|
}
|
|
19384
|
-
function parseTable2(el, blocks, paraShapeMap, sectionNum, warnings) {
|
|
19526
|
+
function parseTable2(el, blocks, paraShapeMap, sectionNum, warnings, keep) {
|
|
19385
19527
|
const cells = [];
|
|
19386
19528
|
const rowCount = parseInt(el.getAttribute("RowCount") ?? "0", 10);
|
|
19387
19529
|
const colCount = parseInt(el.getAttribute("ColCount") ?? "0", 10);
|
|
@@ -19425,7 +19567,7 @@ function parseTable2(el, blocks, paraShapeMap, sectionNum, warnings) {
|
|
|
19425
19567
|
const cellRows = grid.map(
|
|
19426
19568
|
(row) => row.map((cell2) => cell2 ?? { text: "", colSpan: 1, rowSpan: 1 })
|
|
19427
19569
|
);
|
|
19428
|
-
const table2 = buildTable(cellRows);
|
|
19570
|
+
const table2 = buildTable(cellRows, { keepAnchoredEmptyCols: keep });
|
|
19429
19571
|
const caption = extractShapeCaption(el);
|
|
19430
19572
|
if (caption.text && caption.before) {
|
|
19431
19573
|
blocks.push({ type: "paragraph", text: caption.text, pageNumber: sectionNum });
|
|
@@ -19661,7 +19803,7 @@ function findMatchingKey(cellLabel, values) {
|
|
|
19661
19803
|
bestKey = key;
|
|
19662
19804
|
}
|
|
19663
19805
|
} else if (key.startsWith(cellLabel)) {
|
|
19664
|
-
if (cellLabel.length >= key.length * 0.
|
|
19806
|
+
if (cellLabel.length >= key.length * 0.75 && cellLabel.length > bestLen) {
|
|
19665
19807
|
bestLen = cellLabel.length;
|
|
19666
19808
|
bestKey = key;
|
|
19667
19809
|
}
|
|
@@ -19701,7 +19843,7 @@ function fillInCellPatterns(cellText, values, matchedLabels, blockedLabels) {
|
|
|
19701
19843
|
const matchKey = values.available(normalizedKw) ? normalizedKw : void 0;
|
|
19702
19844
|
if (matchKey === void 0) return match;
|
|
19703
19845
|
const val = values.peek(matchKey);
|
|
19704
|
-
const isTruthy = ["\u2611", "\u2713", "\u2714", "v", "V", "true", "1", "yes", "o", "O"].includes(val.trim())
|
|
19846
|
+
const isTruthy = ["\u2611", "\u2713", "\u2714", "v", "V", "true", "1", "yes", "o", "O"].includes(val.trim());
|
|
19705
19847
|
if (!isTruthy) return match;
|
|
19706
19848
|
values.consume(matchKey);
|
|
19707
19849
|
matchedLabels.add(matchKey);
|
|
@@ -19723,14 +19865,20 @@ function fillInCellPatterns(cellText, values, matchedLabels, blockedLabels) {
|
|
|
19723
19865
|
);
|
|
19724
19866
|
return matches.length > 0 ? { text, matches } : null;
|
|
19725
19867
|
}
|
|
19726
|
-
var INLINE_LABEL_RE = /([가-힣A-Za-z]{2,10})\s*[::]/g;
|
|
19868
|
+
var INLINE_LABEL_RE = /((?:[가-힣A-Za-z]{1,10} )?)([가-힣A-Za-z]{2,10})\s*[::]/g;
|
|
19727
19869
|
function scanInlineSegments(text) {
|
|
19728
19870
|
const labels = [];
|
|
19729
19871
|
INLINE_LABEL_RE.lastIndex = 0;
|
|
19730
19872
|
let m;
|
|
19731
19873
|
while ((m = INLINE_LABEL_RE.exec(text)) !== null) {
|
|
19732
19874
|
if (text[INLINE_LABEL_RE.lastIndex] === "/") continue;
|
|
19733
|
-
labels.push({
|
|
19875
|
+
labels.push({
|
|
19876
|
+
label: m[2],
|
|
19877
|
+
ext: m[1] ? m[1] + m[2] : void 0,
|
|
19878
|
+
extStart: m[1] ? m.index : void 0,
|
|
19879
|
+
start: m.index + m[1].length,
|
|
19880
|
+
end: INLINE_LABEL_RE.lastIndex
|
|
19881
|
+
});
|
|
19734
19882
|
}
|
|
19735
19883
|
const segments = [];
|
|
19736
19884
|
for (let i = 0; i < labels.length; i++) {
|
|
@@ -19748,21 +19896,44 @@ function scanInlineSegments(text) {
|
|
|
19748
19896
|
labelStart: cur.start,
|
|
19749
19897
|
valueStart: vs,
|
|
19750
19898
|
valueEnd: ve,
|
|
19751
|
-
value: text.slice(vs, ve)
|
|
19899
|
+
value: text.slice(vs, ve),
|
|
19900
|
+
...cur.ext !== void 0 ? { extLabel: cur.ext, extStart: cur.extStart } : {}
|
|
19752
19901
|
});
|
|
19753
19902
|
}
|
|
19754
19903
|
return segments;
|
|
19755
19904
|
}
|
|
19905
|
+
function matchInlineSegment(seg, values, blockedLabels) {
|
|
19906
|
+
const nlabel = normalizeLabel(seg.label);
|
|
19907
|
+
if (seg.extLabel !== void 0) {
|
|
19908
|
+
const nExt = normalizeLabel(seg.extLabel);
|
|
19909
|
+
if (nExt !== nlabel && !blockedLabels?.has(nExt) && values.has(nExt)) {
|
|
19910
|
+
return { key: nExt, label: seg.extLabel, viaExt: true };
|
|
19911
|
+
}
|
|
19912
|
+
}
|
|
19913
|
+
if (blockedLabels?.has(nlabel)) return void 0;
|
|
19914
|
+
const key = findMatchingKey(nlabel, values);
|
|
19915
|
+
return key !== void 0 ? { key, label: seg.label, viaExt: false } : void 0;
|
|
19916
|
+
}
|
|
19917
|
+
function clampSegmentEnd(text, seg, next, nextViaExt) {
|
|
19918
|
+
let ve = seg.valueEnd;
|
|
19919
|
+
if (nextViaExt && next?.extStart !== void 0 && next.extStart < ve) ve = next.extStart;
|
|
19920
|
+
while (ve > seg.valueStart && /\s/.test(text[ve - 1])) ve--;
|
|
19921
|
+
return ve;
|
|
19922
|
+
}
|
|
19756
19923
|
function padInsertion(text, pos, value) {
|
|
19757
19924
|
const lead = pos > 0 && !/\s/.test(text[pos - 1]) ? " " : "";
|
|
19758
19925
|
const trail = pos < text.length && !/\s/.test(text[pos]) ? " " : "";
|
|
19759
19926
|
return lead + value + trail;
|
|
19760
19927
|
}
|
|
19761
|
-
function normalizeValues(values) {
|
|
19928
|
+
function normalizeValues(values, warnings) {
|
|
19762
19929
|
const map = /* @__PURE__ */ new Map();
|
|
19763
19930
|
for (const [label, raw] of Object.entries(values)) {
|
|
19764
19931
|
const { value, format } = typeof raw === "object" && !Array.isArray(raw) ? raw : { value: raw, format: void 0 };
|
|
19765
|
-
|
|
19932
|
+
const key = normalizeLabel(label);
|
|
19933
|
+
if (map.has(key)) {
|
|
19934
|
+
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`);
|
|
19935
|
+
}
|
|
19936
|
+
map.set(key, Array.isArray(value) ? value.map((v) => formatFillValue(v, format)) : formatFillValue(value, format));
|
|
19766
19937
|
}
|
|
19767
19938
|
return map;
|
|
19768
19939
|
}
|
|
@@ -19951,10 +20122,10 @@ function extractFromTable(table2) {
|
|
|
19951
20122
|
}
|
|
19952
20123
|
if (fields.length === 0 && table2.rows >= 2 && table2.cols >= 2) {
|
|
19953
20124
|
const headerRow = table2.cells[0];
|
|
19954
|
-
const allLabels = headerRow
|
|
19955
|
-
const t = cell2
|
|
20125
|
+
const allLabels = headerRow?.every((cell2) => {
|
|
20126
|
+
const t = cell2?.text.trim() ?? "";
|
|
19956
20127
|
return t.length > 0 && t.length <= 20;
|
|
19957
|
-
});
|
|
20128
|
+
}) ?? false;
|
|
19958
20129
|
if (allLabels) {
|
|
19959
20130
|
for (let r = 1; r < table2.rows; r++) {
|
|
19960
20131
|
for (let c = 0; c < table2.cols; c++) {
|
|
@@ -20045,7 +20216,8 @@ function fillFormFields(blocks, values, blockedLabels) {
|
|
|
20045
20216
|
const cloned = structuredClone(blocks);
|
|
20046
20217
|
const filled = [];
|
|
20047
20218
|
const matchedLabels = /* @__PURE__ */ new Set();
|
|
20048
|
-
const
|
|
20219
|
+
const warnings = [];
|
|
20220
|
+
const normalizedValues = normalizeValues(values, warnings);
|
|
20049
20221
|
const cursor = new ValueCursor(normalizedValues);
|
|
20050
20222
|
const allTables = collectIRTables(cloned, 0);
|
|
20051
20223
|
const patternFilledCells = /* @__PURE__ */ new Set();
|
|
@@ -20074,7 +20246,7 @@ function fillFormFields(blocks, values, blockedLabels) {
|
|
|
20074
20246
|
if (newText !== block.text) block.text = newText;
|
|
20075
20247
|
}
|
|
20076
20248
|
const unmatched = resolveUnmatched(normalizedValues, matchedLabels, values);
|
|
20077
|
-
return { blocks: cloned, filled, unmatched };
|
|
20249
|
+
return { blocks: cloned, filled, unmatched, ...warnings.length > 0 ? { warnings } : {} };
|
|
20078
20250
|
}
|
|
20079
20251
|
function collectIRTables(blocks, depth) {
|
|
20080
20252
|
if (depth > 16) return [];
|
|
@@ -20108,13 +20280,31 @@ function coveredPositions(table2) {
|
|
|
20108
20280
|
}
|
|
20109
20281
|
return covered;
|
|
20110
20282
|
}
|
|
20283
|
+
function isHeaderDataTable(table2, covered) {
|
|
20284
|
+
if (table2.rows < 2) return false;
|
|
20285
|
+
const headerRow = table2.cells[0];
|
|
20286
|
+
if (!headerRow?.length) return false;
|
|
20287
|
+
const allLabels = headerRow.every((cell2) => {
|
|
20288
|
+
const t = cell2?.text.trim() ?? "";
|
|
20289
|
+
return t.length > 0 && t.length <= 20 && isLabelCell(t);
|
|
20290
|
+
});
|
|
20291
|
+
if (!allLabels) return false;
|
|
20292
|
+
for (let c = 0; c < table2.cols; c++) {
|
|
20293
|
+
if (covered.has(`1,${c}`)) continue;
|
|
20294
|
+
const cell2 = table2.cells[1]?.[c];
|
|
20295
|
+
if (!cell2) break;
|
|
20296
|
+
return !isLabelCell(cell2.text);
|
|
20297
|
+
}
|
|
20298
|
+
return true;
|
|
20299
|
+
}
|
|
20111
20300
|
function fillTable(table2, values, filled, matchedLabels, patternFilledCells, blockedLabels) {
|
|
20112
20301
|
if (table2.cols < 2) return;
|
|
20113
20302
|
const covered = coveredPositions(table2);
|
|
20114
|
-
|
|
20303
|
+
const skipHeaderRow = isHeaderDataTable(table2, covered);
|
|
20304
|
+
for (let r = skipHeaderRow ? 1 : 0; r < table2.rows; r++) {
|
|
20115
20305
|
for (let c = 0; c < table2.cols; c++) {
|
|
20116
20306
|
if (covered.has(`${r},${c}`)) continue;
|
|
20117
|
-
const labelCell = table2.cells[r][c];
|
|
20307
|
+
const labelCell = table2.cells[r]?.[c];
|
|
20118
20308
|
if (!labelCell) continue;
|
|
20119
20309
|
if (!isLabelCell(labelCell.text)) continue;
|
|
20120
20310
|
let vc = c + labelCell.colSpan;
|
|
@@ -20147,10 +20337,10 @@ function fillTable(table2, values, filled, matchedLabels, patternFilledCells, bl
|
|
|
20147
20337
|
}
|
|
20148
20338
|
if (table2.rows >= 2 && table2.cols >= 2) {
|
|
20149
20339
|
const headerRow = table2.cells[0];
|
|
20150
|
-
const allLabels = headerRow
|
|
20151
|
-
const t = cell2
|
|
20340
|
+
const allLabels = headerRow?.every((cell2) => {
|
|
20341
|
+
const t = cell2?.text.trim() ?? "";
|
|
20152
20342
|
return t.length > 0 && t.length <= 20 && isLabelCell(t);
|
|
20153
|
-
});
|
|
20343
|
+
}) ?? false;
|
|
20154
20344
|
if (!allLabels) return;
|
|
20155
20345
|
for (let r = 1; r < table2.rows; r++) {
|
|
20156
20346
|
for (let c = 0; c < table2.cols; c++) {
|
|
@@ -20165,7 +20355,11 @@ function fillTable(table2, values, filled, matchedLabels, patternFilledCells, bl
|
|
|
20165
20355
|
if (!values.isArray(matchKey) && matchedLabels.has(matchKey)) continue;
|
|
20166
20356
|
const newValue = values.consume(matchKey);
|
|
20167
20357
|
if (newValue === void 0) continue;
|
|
20168
|
-
valueCell
|
|
20358
|
+
if (patternFilledCells?.has(valueCell)) {
|
|
20359
|
+
valueCell.text = newValue + " " + valueCell.text;
|
|
20360
|
+
} else {
|
|
20361
|
+
valueCell.text = newValue;
|
|
20362
|
+
}
|
|
20169
20363
|
matchedLabels.add(matchKey);
|
|
20170
20364
|
filled.push({
|
|
20171
20365
|
label: headerCell.text.trim(),
|
|
@@ -20181,20 +20375,22 @@ function fillTable(table2, values, filled, matchedLabels, patternFilledCells, bl
|
|
|
20181
20375
|
function fillInlineFields(text, values, filled, matchedLabels, blockedLabels) {
|
|
20182
20376
|
const segments = scanInlineSegments(text);
|
|
20183
20377
|
if (segments.length === 0) return text;
|
|
20378
|
+
const matches = segments.map((seg) => matchInlineSegment(seg, values, blockedLabels));
|
|
20184
20379
|
let out = "";
|
|
20185
20380
|
let pos = 0;
|
|
20186
|
-
for (
|
|
20187
|
-
const
|
|
20188
|
-
|
|
20189
|
-
|
|
20190
|
-
|
|
20381
|
+
for (let i = 0; i < segments.length; i++) {
|
|
20382
|
+
const seg = segments[i];
|
|
20383
|
+
const matched = matches[i];
|
|
20384
|
+
if (matched === void 0) continue;
|
|
20385
|
+
const matchKey = matched.key;
|
|
20191
20386
|
const newValue = values.consume(matchKey);
|
|
20192
20387
|
if (newValue === void 0) continue;
|
|
20193
20388
|
matchedLabels.add(matchKey);
|
|
20194
|
-
filled.push({ label:
|
|
20389
|
+
filled.push({ label: matched.label.trim(), value: newValue, row: -1, col: -1, key: matchKey });
|
|
20390
|
+
const ve = clampSegmentEnd(text, seg, segments[i + 1], matches[i + 1]?.viaExt ?? false);
|
|
20195
20391
|
out += text.slice(pos, seg.valueStart);
|
|
20196
|
-
out += seg.valueStart ===
|
|
20197
|
-
pos =
|
|
20392
|
+
out += seg.valueStart === ve ? padInsertion(text, seg.valueStart, newValue) : newValue;
|
|
20393
|
+
pos = ve;
|
|
20198
20394
|
}
|
|
20199
20395
|
out += text.slice(pos);
|
|
20200
20396
|
return out;
|
|
@@ -20209,7 +20405,8 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
|
20209
20405
|
if (sectionPaths.length === 0) {
|
|
20210
20406
|
throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
20211
20407
|
}
|
|
20212
|
-
const
|
|
20408
|
+
const warnings = [];
|
|
20409
|
+
const normalizedValues = normalizeValues(values, warnings);
|
|
20213
20410
|
const cursor = new ValueCursor(normalizedValues);
|
|
20214
20411
|
const matchedLabels = /* @__PURE__ */ new Set();
|
|
20215
20412
|
const filled = [];
|
|
@@ -20271,7 +20468,17 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
|
20271
20468
|
}
|
|
20272
20469
|
}
|
|
20273
20470
|
for (const table2 of allTables) {
|
|
20274
|
-
|
|
20471
|
+
const skipHeaderRow = table2.rows.length >= 2 && (() => {
|
|
20472
|
+
const first = table2.rows[0];
|
|
20473
|
+
const allLabels = first.length > 0 && first.every((cell2) => {
|
|
20474
|
+
const t = cellLabelText(cell2).trim();
|
|
20475
|
+
return t.length > 0 && t.length <= 20 && isLabelCell(t);
|
|
20476
|
+
});
|
|
20477
|
+
if (!allLabels) return false;
|
|
20478
|
+
const d0 = table2.rows[1][0];
|
|
20479
|
+
return d0 === void 0 || !isLabelCell(cellLabelText(d0));
|
|
20480
|
+
})();
|
|
20481
|
+
for (let rowIdx = skipHeaderRow ? 1 : 0; rowIdx < table2.rows.length; rowIdx++) {
|
|
20275
20482
|
const cells = table2.rows[rowIdx];
|
|
20276
20483
|
for (let colIdx = 0; colIdx < cells.length - 1; colIdx++) {
|
|
20277
20484
|
const labelText = cellLabelText(cells[colIdx]);
|
|
@@ -20342,9 +20549,30 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
|
20342
20549
|
const matchKey = findMatchingKey(headerLabel, cursor);
|
|
20343
20550
|
if (matchKey === void 0) continue;
|
|
20344
20551
|
if (!cursor.isArray(matchKey) && matchedLabels.has(matchKey)) continue;
|
|
20552
|
+
const dataCell = dataCells[colIdx];
|
|
20553
|
+
if (patternApplied.has(dataCell)) {
|
|
20554
|
+
const target = dataCell.paragraphs.find((p) => p.tRanges.length > 0) ?? dataCell.paragraphs[0];
|
|
20555
|
+
if (!target) continue;
|
|
20556
|
+
const l = led(target);
|
|
20557
|
+
if (l.fullText !== void 0) continue;
|
|
20558
|
+
const newValue2 = cursor.consume(matchKey);
|
|
20559
|
+
if (newValue2 === void 0) continue;
|
|
20560
|
+
l.ranges.push({ start: 0, end: 0, replacement: newValue2 + " " });
|
|
20561
|
+
l.filledIdx.push(filled.length);
|
|
20562
|
+
l.matchKeys.push(matchKey);
|
|
20563
|
+
matchedLabels.add(matchKey);
|
|
20564
|
+
filled.push({
|
|
20565
|
+
label: cellLabelText(headerCells[colIdx]).trim(),
|
|
20566
|
+
value: newValue2,
|
|
20567
|
+
row: rowIdx,
|
|
20568
|
+
col: colIdx,
|
|
20569
|
+
key: matchKey
|
|
20570
|
+
});
|
|
20571
|
+
continue;
|
|
20572
|
+
}
|
|
20345
20573
|
const newValue = cursor.consume(matchKey);
|
|
20346
20574
|
if (newValue === void 0) continue;
|
|
20347
|
-
const paras =
|
|
20575
|
+
const paras = dataCell.paragraphs;
|
|
20348
20576
|
if (paras.length === 0) continue;
|
|
20349
20577
|
const l0 = led(paras[0]);
|
|
20350
20578
|
l0.fullText = newValue;
|
|
@@ -20373,20 +20601,23 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
|
20373
20601
|
const existing = ledger.get(para2);
|
|
20374
20602
|
if (existing?.fullText !== void 0) continue;
|
|
20375
20603
|
const text = matchText(para2);
|
|
20376
|
-
|
|
20377
|
-
|
|
20378
|
-
|
|
20379
|
-
const
|
|
20380
|
-
|
|
20604
|
+
const segments = scanInlineSegments(text);
|
|
20605
|
+
const matches = segments.map((seg) => matchInlineSegment(seg, cursor, blockedLabels));
|
|
20606
|
+
for (let i = 0; i < segments.length; i++) {
|
|
20607
|
+
const seg = segments[i];
|
|
20608
|
+
const matched = matches[i];
|
|
20609
|
+
if (matched === void 0) continue;
|
|
20610
|
+
const matchKey = matched.key;
|
|
20381
20611
|
const newValue = cursor.consume(matchKey);
|
|
20382
20612
|
if (newValue === void 0) continue;
|
|
20383
|
-
const
|
|
20613
|
+
const ve = clampSegmentEnd(text, seg, segments[i + 1], matches[i + 1]?.viaExt ?? false);
|
|
20614
|
+
const replacement = seg.valueStart === ve ? padInsertion(text, seg.valueStart, newValue) : newValue;
|
|
20384
20615
|
const l = led(para2);
|
|
20385
|
-
l.ranges.push({ start: seg.valueStart, end:
|
|
20616
|
+
l.ranges.push({ start: seg.valueStart, end: ve, replacement });
|
|
20386
20617
|
matchedLabels.add(matchKey);
|
|
20387
20618
|
l.filledIdx.push(filled.length);
|
|
20388
20619
|
l.matchKeys.push(matchKey);
|
|
20389
|
-
filled.push({ label:
|
|
20620
|
+
filled.push({ label: matched.label.trim(), value: newValue, row: -1, col: -1, key: matchKey });
|
|
20390
20621
|
}
|
|
20391
20622
|
}
|
|
20392
20623
|
const splices = [];
|
|
@@ -20439,7 +20670,7 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
|
20439
20670
|
}
|
|
20440
20671
|
}
|
|
20441
20672
|
for (const k of failedKeys) {
|
|
20442
|
-
if (!succeededKeys.has(k)) matchedLabels.delete(k);
|
|
20673
|
+
if (!succeededKeys.has(k) || cursor.isArray(k)) matchedLabels.delete(k);
|
|
20443
20674
|
}
|
|
20444
20675
|
const cleanFilled = filled.filter((f) => f !== null);
|
|
20445
20676
|
const unmatched = resolveUnmatched(normalizedValues, matchedLabels, values);
|
|
@@ -20447,7 +20678,8 @@ async function fillHwpx(hwpxBuffer, values, blockedLabels) {
|
|
|
20447
20678
|
return {
|
|
20448
20679
|
buffer: out.buffer.slice(out.byteOffset, out.byteOffset + out.byteLength),
|
|
20449
20680
|
filled: cleanFilled,
|
|
20450
|
-
unmatched
|
|
20681
|
+
unmatched,
|
|
20682
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
20451
20683
|
};
|
|
20452
20684
|
}
|
|
20453
20685
|
|
|
@@ -20724,27 +20956,39 @@ function parseMarkdownToBlocks(md2) {
|
|
|
20724
20956
|
if (/^<table[\s>]/i.test(line.trimStart())) {
|
|
20725
20957
|
const htmlLines = [];
|
|
20726
20958
|
let depth = 0;
|
|
20727
|
-
|
|
20728
|
-
|
|
20959
|
+
let closed = false;
|
|
20960
|
+
let j = i;
|
|
20961
|
+
while (j < lines.length) {
|
|
20962
|
+
const l = lines[j];
|
|
20729
20963
|
htmlLines.push(l);
|
|
20730
20964
|
depth += (l.match(/<table[\s>]/gi) ?? []).length;
|
|
20731
20965
|
depth -= (l.match(/<\/table>/gi) ?? []).length;
|
|
20732
|
-
|
|
20733
|
-
if (depth <= 0)
|
|
20966
|
+
j++;
|
|
20967
|
+
if (depth <= 0) {
|
|
20968
|
+
closed = true;
|
|
20969
|
+
break;
|
|
20970
|
+
}
|
|
20971
|
+
}
|
|
20972
|
+
if (closed) {
|
|
20973
|
+
blocks.push({ type: "html_table", text: htmlLines.join("\n") });
|
|
20974
|
+
i = j;
|
|
20975
|
+
continue;
|
|
20734
20976
|
}
|
|
20735
|
-
blocks.push({ type: "html_table", text: htmlLines.join("\n") });
|
|
20736
|
-
continue;
|
|
20737
20977
|
}
|
|
20738
20978
|
if (line.trimStart().startsWith("|")) {
|
|
20739
20979
|
const tableRows = [];
|
|
20980
|
+
let sepSeen = false;
|
|
20740
20981
|
while (i < lines.length && lines[i].trimStart().startsWith("|")) {
|
|
20741
20982
|
const row = lines[i];
|
|
20742
|
-
|
|
20743
|
-
|
|
20744
|
-
|
|
20745
|
-
|
|
20983
|
+
if (tableRows.length === 1 && !sepSeen) {
|
|
20984
|
+
const sepCells = row.trim().replace(/^\|/, "").replace(/\|$/, "").split("|");
|
|
20985
|
+
if (sepCells.every((c) => /^\s*:?-+:?\s*$/.test(c))) {
|
|
20986
|
+
sepSeen = true;
|
|
20987
|
+
i++;
|
|
20988
|
+
continue;
|
|
20989
|
+
}
|
|
20746
20990
|
}
|
|
20747
|
-
const cells = row.split(
|
|
20991
|
+
const cells = row.split(/(?<!\\)\|/).slice(1, -1).map((c) => c.trim().replace(/\\\|/g, "|"));
|
|
20748
20992
|
if (cells.length > 0) tableRows.push(cells);
|
|
20749
20993
|
i++;
|
|
20750
20994
|
}
|
|
@@ -21693,13 +21937,26 @@ function normalize(s) {
|
|
|
21693
21937
|
return s.replace(/\s+/g, " ").trim();
|
|
21694
21938
|
}
|
|
21695
21939
|
var MAX_LEVENSHTEIN_LEN = 1e4;
|
|
21940
|
+
function approxDistance(a, b) {
|
|
21941
|
+
const bigramCounts = (s) => {
|
|
21942
|
+
const m = /* @__PURE__ */ new Map();
|
|
21943
|
+
for (let i = 0; i < s.length - 1; i++) {
|
|
21944
|
+
const g = s.slice(i, i + 2);
|
|
21945
|
+
m.set(g, (m.get(g) ?? 0) + 1);
|
|
21946
|
+
}
|
|
21947
|
+
return m;
|
|
21948
|
+
};
|
|
21949
|
+
const ca = bigramCounts(a);
|
|
21950
|
+
const cb = bigramCounts(b);
|
|
21951
|
+
let inter = 0;
|
|
21952
|
+
for (const [g, n] of ca) inter += Math.min(n, cb.get(g) ?? 0);
|
|
21953
|
+
const total = Math.max(a.length - 1, 0) + Math.max(b.length - 1, 0);
|
|
21954
|
+
const dice = total > 0 ? 2 * inter / total : 1;
|
|
21955
|
+
return Math.round(Math.max(a.length, b.length) * (1 - dice));
|
|
21956
|
+
}
|
|
21696
21957
|
function levenshtein(a, b) {
|
|
21697
21958
|
if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
|
|
21698
|
-
|
|
21699
|
-
let diffs = 0;
|
|
21700
|
-
for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
|
|
21701
|
-
const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
|
|
21702
|
-
return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
|
|
21959
|
+
return approxDistance(a, b);
|
|
21703
21960
|
}
|
|
21704
21961
|
if (a.length > b.length) [a, b] = [b, a];
|
|
21705
21962
|
const m = a.length;
|
|
@@ -21851,9 +22108,16 @@ function bestSimInRange(arr, from, to, target) {
|
|
|
21851
22108
|
return best;
|
|
21852
22109
|
}
|
|
21853
22110
|
function escapeGfm(text) {
|
|
21854
|
-
|
|
22111
|
+
const NUL = String.fromCharCode(0);
|
|
22112
|
+
const spans = [];
|
|
22113
|
+
const masked = text.replace(/!\[[^\]]*\]\([^)\n]*\)|\]\((?:https?:|mailto:|tel:|#)[^)\n]*\)/gi, (m) => {
|
|
22114
|
+
spans.push(m);
|
|
22115
|
+
return NUL + (spans.length - 1) + NUL;
|
|
22116
|
+
});
|
|
22117
|
+
const escaped = masked.replace(/([~*_`])/g, "\\$1");
|
|
22118
|
+
return escaped.replace(new RegExp(NUL + "(\\d+)" + NUL, "g"), (_, n) => spans[Number(n)]);
|
|
21855
22119
|
}
|
|
21856
|
-
var HWP_SHAPE_ALT_TEXT_RE =
|
|
22120
|
+
var HWP_SHAPE_ALT_TEXT_RE = /^(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?$/gm;
|
|
21857
22121
|
function sanitizeText(text) {
|
|
21858
22122
|
let result = mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
|
|
21859
22123
|
if (result.length <= 30 && result.includes(" ")) {
|
|
@@ -21869,7 +22133,7 @@ function normForMatch(text) {
|
|
|
21869
22133
|
return sanitizeText(text).replace(/\s+/g, " ").trim();
|
|
21870
22134
|
}
|
|
21871
22135
|
function unescapeGfm(text) {
|
|
21872
|
-
return text.replace(/\\([~*])/g, "$1");
|
|
22136
|
+
return text.replace(/\\([~*_`])/g, "$1");
|
|
21873
22137
|
}
|
|
21874
22138
|
function summarize(text) {
|
|
21875
22139
|
const t = text.replace(/\s+/g, " ").trim();
|
|
@@ -21936,7 +22200,7 @@ function parseGfmTable(lines) {
|
|
|
21936
22200
|
return rows;
|
|
21937
22201
|
}
|
|
21938
22202
|
function unescapeGfmCell(text) {
|
|
21939
|
-
return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\([~*])/g, "$1");
|
|
22203
|
+
return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\([~*_`])/g, "$1");
|
|
21940
22204
|
}
|
|
21941
22205
|
function replicateCellInnerHtml(cell2) {
|
|
21942
22206
|
if (cell2.blocks?.length) {
|
|
@@ -22024,10 +22288,10 @@ function parseHtmlTable(raw) {
|
|
|
22024
22288
|
}
|
|
22025
22289
|
} else {
|
|
22026
22290
|
if (!isClose) {
|
|
22027
|
-
const cs = parseInt(attrs.match(/colspan\s*=\s*"(\d+)
|
|
22028
|
-
const rs = parseInt(attrs.match(/rowspan\s*=\s*"(\d+)
|
|
22291
|
+
const cs = parseInt(attrs.match(/colspan\s*=\s*["']?(\d+)/i)?.[1] || "1", 10);
|
|
22292
|
+
const rs = parseInt(attrs.match(/rowspan\s*=\s*["']?(\d+)/i)?.[1] || "1", 10);
|
|
22029
22293
|
cellStart = m.index + m[0].length;
|
|
22030
|
-
cellInfo = { colSpan: isNaN(cs) ? 1 : cs, rowSpan: isNaN(rs) ? 1 : rs };
|
|
22294
|
+
cellInfo = { colSpan: clampSpan(isNaN(cs) ? 1 : cs, MAX_COLS), rowSpan: clampSpan(isNaN(rs) ? 1 : rs, MAX_ROWS) };
|
|
22031
22295
|
} else if (cellStart >= 0 && cellInfo && currentRow) {
|
|
22032
22296
|
currentRow.push({ inner: raw.slice(cellStart, m.index), colSpan: cellInfo.colSpan, rowSpan: cellInfo.rowSpan });
|
|
22033
22297
|
cellStart = -1;
|
|
@@ -22671,8 +22935,8 @@ function layoutHtmlRows(rows) {
|
|
|
22671
22935
|
let c = 0;
|
|
22672
22936
|
for (const cell2 of rows[r].cells) {
|
|
22673
22937
|
while (occupied.has(`${r},${c}`)) c++;
|
|
22674
|
-
const colSpan =
|
|
22675
|
-
const rowSpan =
|
|
22938
|
+
const colSpan = clampSpan(cell2.colSpan, MAX_COLS);
|
|
22939
|
+
const rowSpan = clampSpan(cell2.rowSpan, MAX_ROWS);
|
|
22676
22940
|
placed.push({ r, c, colSpan, rowSpan, inner: cell2.inner, isHeader: rows[r].tag === "th" });
|
|
22677
22941
|
for (let dr = 0; dr < rowSpan; dr++) {
|
|
22678
22942
|
for (let dc = 0; dc < colSpan; dc++) occupied.add(`${r + dr},${c + dc}`);
|
|
@@ -23831,12 +24095,20 @@ function diffBlocks(blocksA, blocksB) {
|
|
|
23831
24095
|
function alignBlocks(a, b) {
|
|
23832
24096
|
const m = a.length, n = b.length;
|
|
23833
24097
|
if (m * n > 1e7) return fallbackAlign(a, b);
|
|
24098
|
+
const lenOf = (blk) => {
|
|
24099
|
+
const t = blk.text !== void 0 ? blk.text : blk.type === "table" && blk.table ? blk.table.cells.flat().map((c) => c?.text ?? "").join(" ") : "";
|
|
24100
|
+
return t.replace(/\s+/g, " ").trim().length;
|
|
24101
|
+
};
|
|
24102
|
+
const aLen = a.map(lenOf);
|
|
24103
|
+
const bLen = b.map(lenOf);
|
|
23834
24104
|
const simCache = /* @__PURE__ */ new Map();
|
|
23835
24105
|
const getSim = (i2, j2) => {
|
|
23836
24106
|
const key = `${i2},${j2}`;
|
|
23837
24107
|
let v = simCache.get(key);
|
|
23838
24108
|
if (v === void 0) {
|
|
23839
|
-
|
|
24109
|
+
const mx = Math.max(aLen[i2], bLen[j2]);
|
|
24110
|
+
const cut = a[i2].type === "table" || b[j2].type === "table" ? 6 / 7 : 1 - SIMILARITY_THRESHOLD;
|
|
24111
|
+
v = mx > 0 && (mx - Math.min(aLen[i2], bLen[j2])) / mx > cut ? 0 : blockSimilarity(a[i2], b[j2]);
|
|
23840
24112
|
simCache.set(key, v);
|
|
23841
24113
|
}
|
|
23842
24114
|
return v;
|
|
@@ -23897,8 +24169,8 @@ function blockSimilarity(a, b) {
|
|
|
23897
24169
|
}
|
|
23898
24170
|
function tableSimilarity(a, b) {
|
|
23899
24171
|
const dimSim = 1 - Math.abs(a.rows * a.cols - b.rows * b.cols) / Math.max(a.rows * a.cols, b.rows * b.cols, 1);
|
|
23900
|
-
const textsA = a.cells.flat().map((c) => c
|
|
23901
|
-
const textsB = b.cells.flat().map((c) => c
|
|
24172
|
+
const textsA = a.cells.flat().map((c) => c?.text ?? "").join(" ");
|
|
24173
|
+
const textsB = b.cells.flat().map((c) => c?.text ?? "").join(" ");
|
|
23902
24174
|
const contentSim = normalizedSimilarity(textsA, textsB);
|
|
23903
24175
|
return dimSim * 0.3 + contentSim * 0.7;
|
|
23904
24176
|
}
|
|
@@ -23909,8 +24181,8 @@ function diffTableCells(a, b) {
|
|
|
23909
24181
|
for (let r = 0; r < maxRows; r++) {
|
|
23910
24182
|
const row = [];
|
|
23911
24183
|
for (let c = 0; c < maxCols; c++) {
|
|
23912
|
-
const cellA = r < a.rows && c < a.cols ? a.cells[r][c]
|
|
23913
|
-
const cellB = r < b.rows && c < b.cols ? b.cells[r][c]
|
|
24184
|
+
const cellA = r < a.rows && c < a.cols ? a.cells[r]?.[c]?.text : void 0;
|
|
24185
|
+
const cellB = r < b.rows && c < b.cols ? b.cells[r]?.[c]?.text : void 0;
|
|
23914
24186
|
let type;
|
|
23915
24187
|
if (cellA === void 0) type = "added";
|
|
23916
24188
|
else if (cellB === void 0) type = "removed";
|
|
@@ -25171,16 +25443,16 @@ function buildOrigUnits(blocks) {
|
|
|
25171
25443
|
for (let i = 0; i < blocks.length; i++) {
|
|
25172
25444
|
const block = blocks[i];
|
|
25173
25445
|
let consume = 1;
|
|
25174
|
-
let
|
|
25446
|
+
let chunk;
|
|
25175
25447
|
if (block.type === "paragraph" && block.text && /^\[별표\s*\d+/.test(sanitizeText(block.text))) {
|
|
25176
25448
|
const next = blocks[i + 1];
|
|
25177
25449
|
if (next?.type === "paragraph" && next.text && /관련\)?$/.test(next.text)) consume = 2;
|
|
25178
|
-
|
|
25450
|
+
chunk = blocksToMarkdown(blocks.slice(i, i + consume));
|
|
25179
25451
|
} else {
|
|
25180
|
-
|
|
25452
|
+
chunk = blocksToMarkdown([block]);
|
|
25181
25453
|
}
|
|
25182
|
-
if (
|
|
25183
|
-
const subUnits = splitMarkdownUnits(
|
|
25454
|
+
if (chunk) {
|
|
25455
|
+
const subUnits = splitMarkdownUnits(chunk);
|
|
25184
25456
|
const isFragment = consume === 2 || (block.type === "paragraph" || block.type === "heading") && subUnits.length > 1;
|
|
25185
25457
|
for (let s = 0; s < subUnits.length; s++) {
|
|
25186
25458
|
const u = { ...subUnits[s], blockIdx: i, fragment: isFragment || void 0 };
|
|
@@ -25447,6 +25719,9 @@ var Surgeon = class {
|
|
|
25447
25719
|
miniFatSectors = [];
|
|
25448
25720
|
dirSectors = [];
|
|
25449
25721
|
entries = [];
|
|
25722
|
+
/** replace()가 FREESECT로 해제한 섹터 — finish()에서 재할당 안 된 것만 0으로 지움 (데이터 잔존 방지) */
|
|
25723
|
+
freedSectors = [];
|
|
25724
|
+
freedMiniSectors = [];
|
|
25450
25725
|
constructor(file) {
|
|
25451
25726
|
if (file.length < SECTOR || file.readUInt32LE(0) !== 3759263696) {
|
|
25452
25727
|
throw new OleSurgeonError("OLE \uC2DC\uADF8\uB2C8\uCC98\uAC00 \uC544\uB2D9\uB2C8\uB2E4");
|
|
@@ -25570,6 +25845,8 @@ var Surgeon = class {
|
|
|
25570
25845
|
if (this.fat[i] !== FREESECT) continue;
|
|
25571
25846
|
if (SECTOR + (i + 1) * SECTOR > this.buf.length) continue;
|
|
25572
25847
|
this.fat[i] = ENDOFCHAIN;
|
|
25848
|
+
const off = this.sectorOffset(i);
|
|
25849
|
+
this.buf.fill(0, off, off + SECTOR);
|
|
25573
25850
|
out.push(i);
|
|
25574
25851
|
}
|
|
25575
25852
|
while (out.length < n) {
|
|
@@ -25669,9 +25946,15 @@ var Surgeon = class {
|
|
|
25669
25946
|
const entry = this.findEntry(path);
|
|
25670
25947
|
if (entry.size > 0 && entry.start !== ENDOFCHAIN) {
|
|
25671
25948
|
if (entry.size < MINI_CUTOFF) {
|
|
25672
|
-
for (const s of this.miniChain(entry.start))
|
|
25949
|
+
for (const s of this.miniChain(entry.start)) {
|
|
25950
|
+
this.miniFat[s] = FREESECT;
|
|
25951
|
+
this.freedMiniSectors.push(s);
|
|
25952
|
+
}
|
|
25673
25953
|
} else {
|
|
25674
|
-
for (const s of this.chain(entry.start))
|
|
25954
|
+
for (const s of this.chain(entry.start)) {
|
|
25955
|
+
this.fat[s] = FREESECT;
|
|
25956
|
+
this.freedSectors.push(s);
|
|
25957
|
+
}
|
|
25675
25958
|
}
|
|
25676
25959
|
}
|
|
25677
25960
|
if (newData.length < MINI_CUTOFF) {
|
|
@@ -25700,9 +25983,27 @@ var Surgeon = class {
|
|
|
25700
25983
|
this.writeDirEntry(entry);
|
|
25701
25984
|
}
|
|
25702
25985
|
finish() {
|
|
25986
|
+
this.wipeFreedSectors();
|
|
25703
25987
|
this.flushFat();
|
|
25704
25988
|
return this.buf;
|
|
25705
25989
|
}
|
|
25990
|
+
/** 해제 후 재할당되지 않고 남은 FREESECT 섹터의 바이트를 0으로 채움 (데이터 remanence 제거) */
|
|
25991
|
+
wipeFreedSectors() {
|
|
25992
|
+
for (const s of this.freedSectors) {
|
|
25993
|
+
if (this.fat[s] !== FREESECT) continue;
|
|
25994
|
+
const off = this.sectorOffset(s);
|
|
25995
|
+
this.buf.fill(0, off, off + SECTOR);
|
|
25996
|
+
}
|
|
25997
|
+
if (this.freedMiniSectors.length > 0) {
|
|
25998
|
+
const root = this.rootEntry();
|
|
25999
|
+
const rootChain = root.start === ENDOFCHAIN || root.size === 0 ? [] : this.chain(root.start);
|
|
26000
|
+
for (const s of this.freedMiniSectors) {
|
|
26001
|
+
if (this.miniFat[s] !== FREESECT) continue;
|
|
26002
|
+
const off = this.miniOffset(s, rootChain);
|
|
26003
|
+
this.buf.fill(0, off, off + MINI_SECTOR);
|
|
26004
|
+
}
|
|
26005
|
+
}
|
|
26006
|
+
}
|
|
25706
26007
|
};
|
|
25707
26008
|
|
|
25708
26009
|
// src/roundtrip/hwp5-patch.ts
|
|
@@ -27133,7 +27434,7 @@ async function parseHwp3(buffer, options) {
|
|
|
27133
27434
|
const { markdown, blocks, metadata, outline, warnings } = parseHwp3Document(buffer, options);
|
|
27134
27435
|
return { success: true, fileType: "hwp3", markdown, blocks, metadata, outline, warnings };
|
|
27135
27436
|
} catch (err) {
|
|
27136
|
-
return { success: false, fileType: "hwp3", error: err
|
|
27437
|
+
return { success: false, fileType: "hwp3", error: sanitizeError(err), code: classifyError(err) };
|
|
27137
27438
|
}
|
|
27138
27439
|
}
|
|
27139
27440
|
async function parseHwpx(buffer, options) {
|
|
@@ -27141,7 +27442,7 @@ async function parseHwpx(buffer, options) {
|
|
|
27141
27442
|
const { markdown, blocks, metadata, outline, warnings, images } = await parseHwpxDocument(buffer, options);
|
|
27142
27443
|
return { success: true, fileType: "hwpx", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0 };
|
|
27143
27444
|
} catch (err) {
|
|
27144
|
-
return { success: false, fileType: "hwpx", error: err
|
|
27445
|
+
return { success: false, fileType: "hwpx", error: sanitizeError(err), code: classifyError(err) };
|
|
27145
27446
|
}
|
|
27146
27447
|
}
|
|
27147
27448
|
async function parseHwp(buffer, options) {
|
|
@@ -27166,13 +27467,13 @@ async function parseHwp(buffer, options) {
|
|
|
27166
27467
|
}
|
|
27167
27468
|
return { success: true, fileType: "hwp", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0 };
|
|
27168
27469
|
} catch (err) {
|
|
27169
|
-
return { success: false, fileType: "hwp", error: err
|
|
27470
|
+
return { success: false, fileType: "hwp", error: sanitizeError(err), code: classifyError(err) };
|
|
27170
27471
|
}
|
|
27171
27472
|
}
|
|
27172
27473
|
async function parsePdf(buffer, options) {
|
|
27173
27474
|
let parsePdfDocument;
|
|
27174
27475
|
try {
|
|
27175
|
-
const mod = await import("./parser-
|
|
27476
|
+
const mod = await import("./parser-PEOYBXBO.js");
|
|
27176
27477
|
parsePdfDocument = mod.parsePdfDocument;
|
|
27177
27478
|
} catch {
|
|
27178
27479
|
return {
|
|
@@ -27183,11 +27484,11 @@ async function parsePdf(buffer, options) {
|
|
|
27183
27484
|
};
|
|
27184
27485
|
}
|
|
27185
27486
|
try {
|
|
27186
|
-
const { markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary } = await parsePdfDocument(buffer, options);
|
|
27187
|
-
return { success: true, fileType: "pdf", markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary };
|
|
27487
|
+
const { markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary, images } = await parsePdfDocument(buffer, options);
|
|
27488
|
+
return { success: true, fileType: "pdf", markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary, images };
|
|
27188
27489
|
} catch (err) {
|
|
27189
27490
|
const isImageBased = err instanceof Error && "isImageBased" in err ? true : void 0;
|
|
27190
|
-
return { success: false, fileType: "pdf", error: err
|
|
27491
|
+
return { success: false, fileType: "pdf", error: sanitizeError(err), code: classifyError(err), isImageBased };
|
|
27191
27492
|
}
|
|
27192
27493
|
}
|
|
27193
27494
|
async function parseXlsx(buffer, options) {
|
|
@@ -27195,7 +27496,7 @@ async function parseXlsx(buffer, options) {
|
|
|
27195
27496
|
const { markdown, blocks, metadata, warnings } = await parseXlsxDocument(buffer, options);
|
|
27196
27497
|
return { success: true, fileType: "xlsx", markdown, blocks, metadata, warnings };
|
|
27197
27498
|
} catch (err) {
|
|
27198
|
-
return { success: false, fileType: "xlsx", error: err
|
|
27499
|
+
return { success: false, fileType: "xlsx", error: sanitizeError(err), code: classifyError(err) };
|
|
27199
27500
|
}
|
|
27200
27501
|
}
|
|
27201
27502
|
async function parseXls(buffer, options) {
|
|
@@ -27203,7 +27504,7 @@ async function parseXls(buffer, options) {
|
|
|
27203
27504
|
const { markdown, blocks, metadata, warnings } = await parseXlsDocument(buffer, options);
|
|
27204
27505
|
return { success: true, fileType: "xls", markdown, blocks, metadata, warnings };
|
|
27205
27506
|
} catch (err) {
|
|
27206
|
-
return { success: false, fileType: "xls", error: err
|
|
27507
|
+
return { success: false, fileType: "xls", error: sanitizeError(err), code: classifyError(err) };
|
|
27207
27508
|
}
|
|
27208
27509
|
}
|
|
27209
27510
|
async function parseDocx(buffer, options) {
|
|
@@ -27211,7 +27512,7 @@ async function parseDocx(buffer, options) {
|
|
|
27211
27512
|
const { markdown, blocks, metadata, outline, warnings, images } = await parseDocxDocument(buffer, options);
|
|
27212
27513
|
return { success: true, fileType: "docx", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0 };
|
|
27213
27514
|
} catch (err) {
|
|
27214
|
-
return { success: false, fileType: "docx", error: err
|
|
27515
|
+
return { success: false, fileType: "docx", error: sanitizeError(err), code: classifyError(err) };
|
|
27215
27516
|
}
|
|
27216
27517
|
}
|
|
27217
27518
|
async function parseHwpml(buffer, options) {
|
|
@@ -27219,7 +27520,7 @@ async function parseHwpml(buffer, options) {
|
|
|
27219
27520
|
const { markdown, blocks, metadata, outline, warnings } = parseHwpmlDocument(buffer, options);
|
|
27220
27521
|
return { success: true, fileType: "hwpml", markdown, blocks, metadata, outline, warnings };
|
|
27221
27522
|
} catch (err) {
|
|
27222
|
-
return { success: false, fileType: "hwpml", error: err
|
|
27523
|
+
return { success: false, fileType: "hwpml", error: sanitizeError(err), code: classifyError(err) };
|
|
27223
27524
|
}
|
|
27224
27525
|
}
|
|
27225
27526
|
async function fillForm(input, values, outputFormat = "markdown") {
|
|
@@ -27305,4 +27606,4 @@ export {
|
|
|
27305
27606
|
parseHwpml,
|
|
27306
27607
|
fillForm
|
|
27307
27608
|
};
|
|
27308
|
-
//# sourceMappingURL=chunk-
|
|
27609
|
+
//# sourceMappingURL=chunk-L2XQQTOY.js.map
|