kordoc 4.0.7 → 4.0.8
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 +823 -806
- package/dist/{-6HWEFXVV.js → -LE4RLXVA.js} +9 -9
- package/dist/{chunk-BKWHX3RC.js → chunk-2DZQF6YJ.js} +3 -3
- package/dist/chunk-2DZQF6YJ.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-U25XGCNH.cjs → chunk-6XAAYAWL.cjs} +142 -3
- package/dist/chunk-6XAAYAWL.cjs.map +1 -0
- package/dist/{chunk-TJJTLM76.js → chunk-7S3M4N4E.js} +666 -742
- package/dist/chunk-7S3M4N4E.js.map +1 -0
- package/dist/{chunk-Q7EN4EUJ.js → chunk-BNU5QGIZ.js} +2 -2
- package/dist/chunk-BNU5QGIZ.js.map +1 -0
- package/dist/{chunk-NGSHDBQR.js → chunk-D35VBACN.js} +3 -3
- package/dist/chunk-D35VBACN.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-AIQ3ISQU.js → chunk-GE43BE46.js} +1 -1
- package/dist/chunk-GE43BE46.js.map +1 -0
- package/dist/chunk-GS7T56RP.cjs.map +1 -1
- package/dist/{chunk-CNM75ZSX.js → chunk-HMUEU7D7.js} +2 -2
- package/dist/chunk-HMUEU7D7.js.map +1 -0
- package/dist/{chunk-CFQLK3LM.js → chunk-MEPHGCPQ.js} +1 -1
- package/dist/chunk-MEPHGCPQ.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-5OKHAJ62.js → chunk-OQPILS7B.js} +142 -3
- package/dist/chunk-OQPILS7B.js.map +1 -0
- package/dist/{chunk-ONXVBURQ.js → chunk-RROH5WHO.js} +2 -2
- package/dist/chunk-RROH5WHO.js.map +1 -0
- package/dist/{chunk-2SN3CKME.js → chunk-UEIYETCQ.js} +142 -3
- package/dist/chunk-UEIYETCQ.js.map +1 -0
- package/dist/cli.js +18 -18
- package/dist/cli.js.map +1 -1
- package/dist/{detect-NTR2FUEG.js → detect-RI2MQ33K.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 +1018 -1094
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +780 -856
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +14 -14
- 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-ITMU2WN5.cjs} +214 -38
- package/dist/parser-ITMU2WN5.cjs.map +1 -0
- package/dist/{parser-UGB3NIVH.js → parser-KMMUS73Q.js} +183 -7
- package/dist/parser-KMMUS73Q.js.map +1 -0
- package/dist/{parser-5EHWYJMC.js → parser-TCTCSBYZ.js} +185 -9
- package/dist/parser-TCTCSBYZ.js.map +1 -0
- package/dist/{profile-io-CAKRPQRD.js → profile-io-SLN4P76T.js} +3 -3
- package/dist/{provider-H4WWSPOV.js → provider-4ZJKV3DC.js} +1 -1
- package/dist/provider-4ZJKV3DC.js.map +1 -0
- package/dist/{provider-7H4CPZYS.js → provider-AKROB7WQ.js} +1 -1
- package/dist/provider-AKROB7WQ.js.map +1 -0
- package/dist/{provider-5K7O3Z2O.cjs → provider-G4C2V2PD.cjs} +1 -1
- package/dist/provider-G4C2V2PD.cjs.map +1 -0
- package/dist/render-25BT623I.js +10 -0
- package/dist/seal-R4TETA4C.js +10 -0
- package/dist/{setup-QSJ2INNS.js → setup-57FB3LSP.js} +1 -1
- package/dist/setup-57FB3LSP.js.map +1 -0
- package/dist/{watch-RMX427YR.js → watch-OHQWSVPE.js} +9 -9
- package/dist/watch-OHQWSVPE.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 → -LE4RLXVA.js.map} +0 -0
- /package/dist/{detect-NTR2FUEG.js.map → detect-RI2MQ33K.js.map} +0 -0
- /package/dist/{page-range-CIRRJPXJ.js.map → page-range-737B4EZW.js.map} +0 -0
- /package/dist/{page-range-OF5I4PQY.js.map → page-range-LNMXJU6W.js.map} +0 -0
- /package/dist/{profile-io-CAKRPQRD.js.map → profile-io-SLN4P76T.js.map} +0 -0
- /package/dist/{render-P6663O4I.js.map → render-25BT623I.js.map} +0 -0
- /package/dist/{seal-7PPTXKKV.js.map → seal-R4TETA4C.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
convertTableToText,
|
|
14
14
|
dedupeRunningHeaders,
|
|
15
15
|
flattenLayoutTables,
|
|
16
|
+
inlineImagesIntoMarkdown,
|
|
16
17
|
isPathTraversal,
|
|
17
18
|
mapPuaText,
|
|
18
19
|
normalizeSectionHref,
|
|
@@ -20,10 +21,10 @@ import {
|
|
|
20
21
|
sanitizeHref,
|
|
21
22
|
stripDtd,
|
|
22
23
|
toArrayBuffer
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-OQPILS7B.js";
|
|
24
25
|
import {
|
|
25
26
|
parsePageRange
|
|
26
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-GE43BE46.js";
|
|
27
28
|
|
|
28
29
|
// src/index.ts
|
|
29
30
|
import { readFile } from "fs/promises";
|
|
@@ -2711,628 +2712,807 @@ function gongmunDepthFromIndent(para2, left, ctx) {
|
|
|
2711
2712
|
return depth >= 1 && depth < 8 ? depth : null;
|
|
2712
2713
|
}
|
|
2713
2714
|
|
|
2714
|
-
// src/
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2715
|
+
// src/hwp5/record.ts
|
|
2716
|
+
import { inflateRawSync, inflateSync } from "zlib";
|
|
2717
|
+
var TAG_PARA_HEADER = 66;
|
|
2718
|
+
var TAG_PARA_TEXT = 67;
|
|
2719
|
+
var TAG_CHAR_SHAPE = 68;
|
|
2720
|
+
var TAG_CTRL_HEADER = 71;
|
|
2721
|
+
var TAG_LIST_HEADER = 72;
|
|
2722
|
+
var TAG_TABLE = 77;
|
|
2723
|
+
var TAG_SHAPE_COMPONENT = 76;
|
|
2724
|
+
var TAG_SHAPE_COMPONENT_PICTURE = 85;
|
|
2725
|
+
var TAG_SHAPE_COMPONENT_CONTAINER = 86;
|
|
2726
|
+
var TAG_EQEDIT = 88;
|
|
2727
|
+
var TAG_BIN_DATA = 18;
|
|
2728
|
+
var TAG_DOC_CHAR_SHAPE = 21;
|
|
2729
|
+
var TAG_NUMBERING = 23;
|
|
2730
|
+
var TAG_BULLET = 24;
|
|
2731
|
+
var TAG_DOC_PARA_SHAPE = 25;
|
|
2732
|
+
var TAG_DOC_STYLE = 26;
|
|
2733
|
+
var CHAR_LINE = 0;
|
|
2734
|
+
var CHAR_SECTION_BREAK = 10;
|
|
2735
|
+
var CHAR_PARA = 13;
|
|
2736
|
+
var CHAR_TAB = 9;
|
|
2737
|
+
var CHAR_HYPHEN = 30;
|
|
2738
|
+
var CHAR_NBSP = 31;
|
|
2739
|
+
var CHAR_FIXED_NBSP = 24;
|
|
2740
|
+
var CHAR_FIXED_WIDTH = 25;
|
|
2741
|
+
var FLAG_COMPRESSED = 1 << 0;
|
|
2742
|
+
var FLAG_ENCRYPTED = 1 << 1;
|
|
2743
|
+
var FLAG_DISTRIBUTION = 1 << 2;
|
|
2744
|
+
var FLAG_DRM = 1 << 4;
|
|
2745
|
+
var MAX_RECORDS = 5e5;
|
|
2746
|
+
function readRecords(data) {
|
|
2747
|
+
const records = [];
|
|
2748
|
+
let offset = 0;
|
|
2749
|
+
while (offset + 4 <= data.length && records.length < MAX_RECORDS) {
|
|
2750
|
+
const header = data.readUInt32LE(offset);
|
|
2751
|
+
offset += 4;
|
|
2752
|
+
const tagId = header & 1023;
|
|
2753
|
+
const level = header >> 10 & 1023;
|
|
2754
|
+
let size = header >> 20 & 4095;
|
|
2755
|
+
if (size === 4095) {
|
|
2756
|
+
if (offset + 4 > data.length) break;
|
|
2757
|
+
size = data.readUInt32LE(offset);
|
|
2758
|
+
offset += 4;
|
|
2759
|
+
}
|
|
2760
|
+
if (offset + size > data.length) break;
|
|
2761
|
+
records.push({ tagId, level, size, data: data.subarray(offset, offset + size) });
|
|
2762
|
+
offset += size;
|
|
2737
2763
|
}
|
|
2764
|
+
return records;
|
|
2738
2765
|
}
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
if (
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
if (mime.includes("emf")) return "emf";
|
|
2747
|
-
if (mime.includes("svg")) return "svg";
|
|
2748
|
-
return "bin";
|
|
2749
|
-
}
|
|
2750
|
-
function collectImageBlocks(blocks, out, ownerCell, depth = 0) {
|
|
2751
|
-
if (depth > MAX_XML_DEPTH) return;
|
|
2752
|
-
for (const block of blocks) {
|
|
2753
|
-
if (block.type === "image") {
|
|
2754
|
-
out.push({ block, ownerCell });
|
|
2755
|
-
} else if (block.type === "table" && block.table) {
|
|
2756
|
-
for (const row of block.table.cells) {
|
|
2757
|
-
for (const cell2 of row) {
|
|
2758
|
-
if (cell2.blocks?.length) collectImageBlocks(cell2.blocks, out, cell2, depth + 1);
|
|
2759
|
-
}
|
|
2760
|
-
}
|
|
2766
|
+
var MAX_DECOMPRESS_SIZE2 = 100 * 1024 * 1024;
|
|
2767
|
+
function decompressStream(data) {
|
|
2768
|
+
const opts = { maxOutputLength: MAX_DECOMPRESS_SIZE2 };
|
|
2769
|
+
if (data.length >= 2 && data[0] === 120) {
|
|
2770
|
+
try {
|
|
2771
|
+
return inflateSync(data, opts);
|
|
2772
|
+
} catch {
|
|
2761
2773
|
}
|
|
2762
2774
|
}
|
|
2775
|
+
return inflateRawSync(data, opts);
|
|
2763
2776
|
}
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2777
|
+
function parseFileHeader(data) {
|
|
2778
|
+
if (data.length < 40) throw new KordocError("FileHeader\uAC00 \uB108\uBB34 \uC9E7\uC2B5\uB2C8\uB2E4 (\uCD5C\uC18C 40\uBC14\uC774\uD2B8)");
|
|
2779
|
+
const sig = data.subarray(0, 32).toString("utf8").replace(/\0+$/, "");
|
|
2780
|
+
return {
|
|
2781
|
+
signature: sig,
|
|
2782
|
+
versionMajor: data[35],
|
|
2783
|
+
flags: data.readUInt32LE(36)
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
2786
|
+
function readHwpString(data, offset) {
|
|
2787
|
+
if (offset + 2 > data.length) return { value: "", next: data.length };
|
|
2788
|
+
const len = data.readUInt16LE(offset);
|
|
2789
|
+
const start = offset + 2;
|
|
2790
|
+
const end = start + len * 2;
|
|
2791
|
+
if (len === 0 || end > data.length) return { value: "", next: start };
|
|
2792
|
+
return { value: data.subarray(start, end).toString("utf16le"), next: end };
|
|
2793
|
+
}
|
|
2794
|
+
function parseDocInfo(records) {
|
|
2795
|
+
const charShapes = [];
|
|
2796
|
+
const paraShapes = [];
|
|
2797
|
+
const styles = [];
|
|
2798
|
+
const binData = [];
|
|
2799
|
+
const numberings = [];
|
|
2800
|
+
const bullets = [];
|
|
2801
|
+
for (const rec of records) {
|
|
2802
|
+
if (rec.tagId === TAG_DOC_PARA_SHAPE && rec.data.length >= 4) {
|
|
2803
|
+
const attr1 = rec.data.readUInt32LE(0);
|
|
2804
|
+
const headType = attr1 >>> 23 & 3;
|
|
2805
|
+
const paraLevel = attr1 >>> 25 & 7;
|
|
2806
|
+
const numberingId = rec.data.length >= 32 ? rec.data.readUInt16LE(30) : 0;
|
|
2807
|
+
paraShapes.push({ headType, paraLevel, numberingId });
|
|
2808
|
+
}
|
|
2809
|
+
if (rec.tagId === TAG_BIN_DATA && rec.data.length >= 2) {
|
|
2810
|
+
const attr = rec.data.readUInt16LE(0);
|
|
2811
|
+
const typeBits = attr & 15;
|
|
2812
|
+
if (typeBits === 0) {
|
|
2813
|
+
binData.push({ kind: "link", storageId: 0, extension: "" });
|
|
2814
|
+
} else {
|
|
2815
|
+
const storageId = rec.data.length >= 4 ? rec.data.readUInt16LE(2) : 0;
|
|
2816
|
+
const { value: extension } = readHwpString(rec.data, 4);
|
|
2817
|
+
binData.push({ kind: typeBits === 2 ? "storage" : "embed", storageId, extension });
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
if (rec.tagId === TAG_NUMBERING && rec.data.length >= 14) {
|
|
2821
|
+
const levelFormats = [];
|
|
2822
|
+
const numberFormats = [];
|
|
2823
|
+
const startNumbers = [1, 1, 1, 1, 1, 1, 1];
|
|
2824
|
+
let offset = 0;
|
|
2825
|
+
for (let level = 0; level < 7; level++) {
|
|
2826
|
+
if (offset + 12 > rec.data.length) {
|
|
2827
|
+
levelFormats.push("");
|
|
2828
|
+
numberFormats.push(0);
|
|
2829
|
+
continue;
|
|
2791
2830
|
}
|
|
2831
|
+
const attr = rec.data.readUInt32LE(offset);
|
|
2832
|
+
numberFormats.push(attr >>> 5 & 15);
|
|
2833
|
+
offset += 12;
|
|
2834
|
+
const { value, next } = readHwpString(rec.data, offset);
|
|
2835
|
+
levelFormats.push(value);
|
|
2836
|
+
offset = next;
|
|
2792
2837
|
}
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
imageIndex++;
|
|
2805
|
-
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
2806
|
-
img = { filename, data, mimeType };
|
|
2807
|
-
images.push(img);
|
|
2808
|
-
break;
|
|
2809
|
-
} catch (err) {
|
|
2810
|
-
if (err instanceof KordocError) throw err;
|
|
2838
|
+
let baseStart = 1;
|
|
2839
|
+
if (offset + 2 <= rec.data.length) {
|
|
2840
|
+
baseStart = rec.data.readUInt16LE(offset) || 1;
|
|
2841
|
+
offset += 2;
|
|
2842
|
+
}
|
|
2843
|
+
for (let level = 0; level < 7; level++) {
|
|
2844
|
+
if (offset + 4 <= rec.data.length) {
|
|
2845
|
+
startNumbers[level] = rec.data.readUInt32LE(offset) || 1;
|
|
2846
|
+
offset += 4;
|
|
2847
|
+
} else {
|
|
2848
|
+
startNumbers[level] = baseStart;
|
|
2811
2849
|
}
|
|
2812
2850
|
}
|
|
2813
|
-
|
|
2814
|
-
resolved.set(ref, img);
|
|
2851
|
+
numberings.push({ levelFormats, numberFormats, startNumbers });
|
|
2815
2852
|
}
|
|
2816
|
-
if (
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
2820
|
-
continue;
|
|
2853
|
+
if (rec.tagId === TAG_BULLET && rec.data.length >= 14) {
|
|
2854
|
+
const code = rec.data.readUInt16LE(12);
|
|
2855
|
+
bullets.push({ char: code > 0 ? String.fromCharCode(code) : "\u2022" });
|
|
2821
2856
|
}
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
}
|
|
2828
|
-
|
|
2829
|
-
// src/hwpx/metadata.ts
|
|
2830
|
-
import JSZip2 from "jszip";
|
|
2831
|
-
|
|
2832
|
-
// src/hwpx/zip-sections.ts
|
|
2833
|
-
import { inflateRawSync } from "zlib";
|
|
2834
|
-
function extractFromBrokenZip(buffer) {
|
|
2835
|
-
const data = new Uint8Array(buffer);
|
|
2836
|
-
const view = new DataView(buffer);
|
|
2837
|
-
let pos = 0;
|
|
2838
|
-
const blocks = [];
|
|
2839
|
-
const warnings = [
|
|
2840
|
-
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
2841
|
-
];
|
|
2842
|
-
let totalDecompressed = 0;
|
|
2843
|
-
let entryCount = 0;
|
|
2844
|
-
let sectionNum = 0;
|
|
2845
|
-
const shared = createSectionShared();
|
|
2846
|
-
while (pos < data.length - 30) {
|
|
2847
|
-
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
2848
|
-
pos++;
|
|
2849
|
-
while (pos < data.length - 30) {
|
|
2850
|
-
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
2851
|
-
pos++;
|
|
2857
|
+
if (rec.tagId === TAG_DOC_CHAR_SHAPE && rec.data.length >= 18) {
|
|
2858
|
+
if (rec.data.length >= 50) {
|
|
2859
|
+
const fontSize = rec.data.readUInt32LE(42);
|
|
2860
|
+
const attrFlags = rec.data.readUInt32LE(46);
|
|
2861
|
+
charShapes.push({ fontSize, attrFlags });
|
|
2862
|
+
} else {
|
|
2863
|
+
charShapes.push({ fontSize: 0, attrFlags: 0 });
|
|
2852
2864
|
}
|
|
2853
|
-
continue;
|
|
2854
|
-
}
|
|
2855
|
-
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
2856
|
-
const method = view.getUint16(pos + 8, true);
|
|
2857
|
-
const compSize = view.getUint32(pos + 18, true);
|
|
2858
|
-
const nameLen = view.getUint16(pos + 26, true);
|
|
2859
|
-
const extraLen = view.getUint16(pos + 28, true);
|
|
2860
|
-
if (nameLen > 1024 || extraLen > 65535) {
|
|
2861
|
-
pos += 30 + nameLen + extraLen;
|
|
2862
|
-
continue;
|
|
2863
2865
|
}
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2866
|
+
if (rec.tagId === TAG_DOC_STYLE && rec.data.length >= 8) {
|
|
2867
|
+
try {
|
|
2868
|
+
let offset = 0;
|
|
2869
|
+
const nameLen = rec.data.readUInt16LE(offset);
|
|
2870
|
+
offset += 2;
|
|
2871
|
+
const nameBytes = nameLen * 2;
|
|
2872
|
+
const name = nameBytes > 0 && offset + nameBytes <= rec.data.length ? rec.data.subarray(offset, offset + nameBytes).toString("utf16le") : "";
|
|
2873
|
+
offset += nameBytes;
|
|
2874
|
+
let nameKo = "";
|
|
2875
|
+
if (offset + 2 <= rec.data.length) {
|
|
2876
|
+
const nameKoLen = rec.data.readUInt16LE(offset);
|
|
2877
|
+
offset += 2;
|
|
2878
|
+
const nameKoBytes = nameKoLen * 2;
|
|
2879
|
+
if (nameKoBytes > 0 && offset + nameKoBytes <= rec.data.length) {
|
|
2880
|
+
nameKo = rec.data.subarray(offset, offset + nameKoBytes).toString("utf16le");
|
|
2881
|
+
}
|
|
2882
|
+
offset += nameKoBytes;
|
|
2883
|
+
}
|
|
2884
|
+
const type = offset < rec.data.length ? rec.data.readUInt8(offset) : 0;
|
|
2885
|
+
offset += 1;
|
|
2886
|
+
offset += 1;
|
|
2887
|
+
offset += 2;
|
|
2888
|
+
const paraShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
2889
|
+
offset += 2;
|
|
2890
|
+
const charShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
2891
|
+
styles.push({ name, nameKo, charShapeId, paraShapeId, type });
|
|
2892
|
+
} catch {
|
|
2893
|
+
}
|
|
2875
2894
|
}
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2895
|
+
}
|
|
2896
|
+
return { charShapes, paraShapes, styles, binData, numberings, bullets };
|
|
2897
|
+
}
|
|
2898
|
+
function createParaTextState() {
|
|
2899
|
+
return { text: "", ctrlIdx: 0, fieldStack: [], fieldRanges: [] };
|
|
2900
|
+
}
|
|
2901
|
+
function isExtendedOnlyCtrlChar(ch) {
|
|
2902
|
+
return ch >= 1 && ch <= 3 || ch >= 11 && ch <= 12 || ch >= 14 && ch <= 18 || ch >= 21 && ch <= 23;
|
|
2903
|
+
}
|
|
2904
|
+
function appendParaText(state, data, resolveControl) {
|
|
2905
|
+
let result = "";
|
|
2906
|
+
let i = 0;
|
|
2907
|
+
const base = state.text.length;
|
|
2908
|
+
const resolveAt = (byteOffset, extended) => {
|
|
2909
|
+
const ctrlId = data.readUInt32LE(byteOffset);
|
|
2910
|
+
const idx = extended ? state.ctrlIdx : -1;
|
|
2911
|
+
const replacement = resolveControl?.(idx, ctrlId);
|
|
2912
|
+
if (replacement) result += replacement;
|
|
2913
|
+
if (extended) state.ctrlIdx++;
|
|
2914
|
+
};
|
|
2915
|
+
while (i + 1 < data.length) {
|
|
2916
|
+
const ch = data.readUInt16LE(i);
|
|
2917
|
+
i += 2;
|
|
2918
|
+
switch (ch) {
|
|
2919
|
+
// ── char 타입 (2바이트만, 확장 데이터 없음) ──
|
|
2920
|
+
case CHAR_LINE:
|
|
2921
|
+
result += "\n";
|
|
2922
|
+
break;
|
|
2923
|
+
case CHAR_SECTION_BREAK: {
|
|
2924
|
+
if (i + 16 <= data.length && data.readUInt16LE(i) === 11) {
|
|
2925
|
+
resolveAt(i + 2, true);
|
|
2926
|
+
i += 16;
|
|
2927
|
+
break;
|
|
2928
|
+
}
|
|
2929
|
+
result += "\n";
|
|
2930
|
+
break;
|
|
2888
2931
|
}
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2932
|
+
case CHAR_PARA:
|
|
2933
|
+
break;
|
|
2934
|
+
// 문단 끝
|
|
2935
|
+
case CHAR_HYPHEN:
|
|
2936
|
+
result += "-";
|
|
2937
|
+
break;
|
|
2938
|
+
case CHAR_NBSP:
|
|
2939
|
+
result += " ";
|
|
2940
|
+
break;
|
|
2941
|
+
case CHAR_FIXED_NBSP:
|
|
2942
|
+
result += "\xA0";
|
|
2943
|
+
break;
|
|
2944
|
+
// 진짜 NBSP
|
|
2945
|
+
case CHAR_FIXED_WIDTH:
|
|
2946
|
+
result += " ";
|
|
2947
|
+
break;
|
|
2948
|
+
// 고정폭 공백
|
|
2949
|
+
// ── inline 타입 (2바이트 + 14바이트 확장) ──
|
|
2950
|
+
case CHAR_TAB:
|
|
2951
|
+
result += " ";
|
|
2952
|
+
if (i + 14 <= data.length) i += 14;
|
|
2953
|
+
break;
|
|
2954
|
+
default:
|
|
2955
|
+
if (ch >= 1 && ch <= 31) {
|
|
2956
|
+
const isExtended = isExtendedOnlyCtrlChar(ch);
|
|
2957
|
+
const isInline = ch >= 4 && ch <= 9 || ch >= 19 && ch <= 20;
|
|
2958
|
+
if ((isExtended || isInline) && i + 14 <= data.length) {
|
|
2959
|
+
if (ch === 3) {
|
|
2960
|
+
state.fieldStack.push({ start: base + result.length, ctrlIdx: state.ctrlIdx });
|
|
2961
|
+
} else if (ch === 4) {
|
|
2962
|
+
const open = state.fieldStack.pop();
|
|
2963
|
+
if (open) {
|
|
2964
|
+
state.fieldRanges.push({ start: open.start, end: base + result.length, ctrlIdx: open.ctrlIdx });
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
resolveAt(i, isExtended);
|
|
2968
|
+
i += 14;
|
|
2969
|
+
}
|
|
2970
|
+
} else if (ch >= 32) {
|
|
2971
|
+
if (ch >= 55296 && ch <= 56319 && i + 1 < data.length) {
|
|
2972
|
+
const lo = data.readUInt16LE(i);
|
|
2973
|
+
if (lo >= 56320 && lo <= 57343) {
|
|
2974
|
+
i += 2;
|
|
2975
|
+
const codePoint = (ch - 55296 << 10) + (lo - 56320) + 65536;
|
|
2976
|
+
result += String.fromCodePoint(codePoint);
|
|
2977
|
+
break;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
result += String.fromCharCode(ch);
|
|
2981
|
+
}
|
|
2982
|
+
break;
|
|
2895
2983
|
}
|
|
2896
2984
|
}
|
|
2897
|
-
|
|
2898
|
-
applyPageText(blocks, shared);
|
|
2899
|
-
const markdown = blocksToMarkdown(blocks);
|
|
2900
|
-
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
2985
|
+
state.text += result;
|
|
2901
2986
|
}
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
return null;
|
|
2911
|
-
}
|
|
2987
|
+
function extractEquationText(data) {
|
|
2988
|
+
if (data.length < 6) return null;
|
|
2989
|
+
const scriptLength = data.readUInt16LE(4);
|
|
2990
|
+
const scriptStart = 6;
|
|
2991
|
+
const scriptEnd = scriptStart + scriptLength * 2;
|
|
2992
|
+
if (scriptLength <= 0 || scriptEnd > data.length) return null;
|
|
2993
|
+
const equation = data.subarray(scriptStart, scriptEnd).toString("utf16le").replace(/\0+/g, "").trim();
|
|
2994
|
+
return equation || null;
|
|
2912
2995
|
}
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
2996
|
+
|
|
2997
|
+
// src/hwp5/images.ts
|
|
2998
|
+
function detectImageMime(data) {
|
|
2999
|
+
if (data.length < 4) return null;
|
|
3000
|
+
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return "image/png";
|
|
3001
|
+
if (data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
|
|
3002
|
+
if (data[0] === 71 && data[1] === 73 && data[2] === 70) return "image/gif";
|
|
3003
|
+
if (data[0] === 66 && data[1] === 77) return "image/bmp";
|
|
3004
|
+
if (data[0] === 215 && data[1] === 205 && data[2] === 198 && data[3] === 154) return "image/wmf";
|
|
3005
|
+
if (data[0] === 1 && data[1] === 0 && data[2] === 0 && data[3] === 0) return "image/emf";
|
|
3006
|
+
return null;
|
|
2925
3007
|
}
|
|
2926
|
-
function
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
for (let i = 0; i < items.length; i++) {
|
|
2933
|
-
const item = items[i];
|
|
2934
|
-
const id = item.getAttribute("id") || "";
|
|
2935
|
-
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
2936
|
-
if (id && href) idToHref.set(id, href);
|
|
2937
|
-
}
|
|
2938
|
-
if (spine.length > 0) {
|
|
2939
|
-
const ordered = [];
|
|
2940
|
-
for (let i = 0; i < spine.length; i++) {
|
|
2941
|
-
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
2942
|
-
if (href) ordered.push(href);
|
|
2943
|
-
}
|
|
2944
|
-
if (ordered.length > 0) return ordered;
|
|
3008
|
+
function normalizeBinPayload(data) {
|
|
3009
|
+
if (detectImageMime(data)) return data;
|
|
3010
|
+
try {
|
|
3011
|
+
const inflated = decompressStream(data);
|
|
3012
|
+
if (inflated.length > 0) return inflated;
|
|
3013
|
+
} catch {
|
|
2945
3014
|
}
|
|
2946
|
-
return
|
|
3015
|
+
return data;
|
|
2947
3016
|
}
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
if (decompressed) {
|
|
2958
|
-
decompressed.total += xml.length * 2;
|
|
2959
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3017
|
+
var BIN_ENTRY_RE = /(?:^|\/)BIN([0-9A-Fa-f]{4,8})(?:\.[^./\\]*)?$/;
|
|
3018
|
+
function collectImageBlocks(blocks, out) {
|
|
3019
|
+
for (const b of blocks) {
|
|
3020
|
+
if (b.type === "image") out.push(b);
|
|
3021
|
+
if (b.table) {
|
|
3022
|
+
for (const row of b.table.cells) {
|
|
3023
|
+
for (const cell2 of row) {
|
|
3024
|
+
if (cell2.blocks) collectImageBlocks(cell2.blocks, out);
|
|
3025
|
+
}
|
|
2960
3026
|
}
|
|
2961
|
-
parseDublinCoreMetadata(xml, metadata);
|
|
2962
|
-
if (metadata.title || metadata.author) return;
|
|
2963
3027
|
}
|
|
2964
|
-
|
|
3028
|
+
if (b.children) collectImageBlocks(b.children, out);
|
|
2965
3029
|
}
|
|
2966
3030
|
}
|
|
2967
|
-
function
|
|
2968
|
-
const
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
const text = els[0].textContent?.trim();
|
|
2976
|
-
if (text) return text;
|
|
3031
|
+
function forEachTableCell(blocks, fn) {
|
|
3032
|
+
for (const b of blocks) {
|
|
3033
|
+
if (b.table) {
|
|
3034
|
+
for (const row of b.table.cells) {
|
|
3035
|
+
for (const cell2 of row) {
|
|
3036
|
+
fn(cell2);
|
|
3037
|
+
if (cell2.blocks) forEachTableCell(cell2.blocks, fn);
|
|
3038
|
+
}
|
|
2977
3039
|
}
|
|
2978
3040
|
}
|
|
2979
|
-
|
|
2980
|
-
};
|
|
2981
|
-
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
2982
|
-
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
2983
|
-
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
2984
|
-
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
2985
|
-
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
2986
|
-
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
2987
|
-
if (keywords && !metadata.keywords) {
|
|
2988
|
-
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
3041
|
+
if (b.children) forEachTableCell(b.children, fn);
|
|
2989
3042
|
}
|
|
2990
3043
|
}
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
const
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3044
|
+
var CELL_IMAGE_SENTINEL_RE = /!\[image\]\(hwp5bin:(\d+)\)/g;
|
|
3045
|
+
function resolveCellImageSentinels(blocks, renamed) {
|
|
3046
|
+
forEachTableCell(blocks, (cell2) => {
|
|
3047
|
+
if (!cell2.text.includes("hwp5bin:")) return;
|
|
3048
|
+
cell2.text = cell2.text.replace(CELL_IMAGE_SENTINEL_RE, (_m, idStr) => {
|
|
3049
|
+
const filename = renamed.get(Number(idStr));
|
|
3050
|
+
return filename ? `` : "[\uC774\uBBF8\uC9C0]";
|
|
3051
|
+
});
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
3054
|
+
function resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced) {
|
|
3055
|
+
const imageBlocks = [];
|
|
3056
|
+
collectImageBlocks(blocks, imageBlocks);
|
|
3057
|
+
const images = [];
|
|
3058
|
+
const renamed = /* @__PURE__ */ new Map();
|
|
3059
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
3060
|
+
let imageIndex = 0;
|
|
3061
|
+
for (const block of imageBlocks) {
|
|
3062
|
+
if (!block.text) continue;
|
|
3063
|
+
const storageId = parseInt(block.text, 10);
|
|
3064
|
+
if (isNaN(storageId)) continue;
|
|
3065
|
+
let img = resolved.get(storageId);
|
|
3066
|
+
if (img === void 0) {
|
|
3067
|
+
const bin = binDataMap.get(storageId);
|
|
3068
|
+
if (!bin) {
|
|
3069
|
+
warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
|
|
3070
|
+
resolved.set(storageId, null);
|
|
3071
|
+
} else {
|
|
3072
|
+
const mime = detectImageMime(bin.data);
|
|
3073
|
+
if (!mime) {
|
|
3074
|
+
warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
|
|
3075
|
+
resolved.set(storageId, null);
|
|
3076
|
+
} else {
|
|
3077
|
+
imageIndex++;
|
|
3078
|
+
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
3079
|
+
img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
|
|
3080
|
+
resolved.set(storageId, img);
|
|
3081
|
+
images.push({ filename: img.filename, data: img.data, mimeType: img.mime });
|
|
3082
|
+
renamed.set(storageId, img.filename);
|
|
3013
3083
|
}
|
|
3014
3084
|
}
|
|
3015
|
-
|
|
3085
|
+
img = resolved.get(storageId);
|
|
3016
3086
|
}
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
const warnings = [];
|
|
3023
|
-
const sectionPaths = await resolveSectionPaths(zip);
|
|
3024
|
-
if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
3025
|
-
metadata.pageCount = sectionPaths.length;
|
|
3026
|
-
const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null;
|
|
3027
|
-
const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length;
|
|
3028
|
-
const blocks = [];
|
|
3029
|
-
const shared = createSectionShared();
|
|
3030
|
-
shared.kordocLayout = await readKordocLayout(zip);
|
|
3031
|
-
let parsedSections = 0;
|
|
3032
|
-
for (let si = 0; si < sectionPaths.length; si++) {
|
|
3033
|
-
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
3034
|
-
const file = zip.file(sectionPaths[si]);
|
|
3035
|
-
if (!file) continue;
|
|
3036
|
-
try {
|
|
3037
|
-
const xml = await file.async("text");
|
|
3038
|
-
decompressed.total += xml.length * 2;
|
|
3039
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3040
|
-
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
3041
|
-
parsedSections++;
|
|
3042
|
-
options?.onProgress?.(parsedSections, totalTarget);
|
|
3043
|
-
} catch (secErr) {
|
|
3044
|
-
if (secErr instanceof KordocError) throw secErr;
|
|
3045
|
-
warnings.push({ page: si + 1, message: `\uC139\uC158 ${si + 1} \uD30C\uC2F1 \uC2E4\uD328: ${secErr instanceof Error ? secErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "PARTIAL_PARSE" });
|
|
3087
|
+
if (!img) {
|
|
3088
|
+
const bin = binDataMap.get(storageId);
|
|
3089
|
+
block.type = "paragraph";
|
|
3090
|
+
block.text = bin ? `[\uC774\uBBF8\uC9C0: ${bin.name}]` : `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
|
|
3091
|
+
continue;
|
|
3046
3092
|
}
|
|
3093
|
+
block.text = img.filename;
|
|
3094
|
+
block.imageData = { data: img.data, mimeType: img.mime, filename: binDataMap.get(storageId).name };
|
|
3047
3095
|
}
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
var TAG_PARA_TEXT = 67;
|
|
3060
|
-
var TAG_CHAR_SHAPE = 68;
|
|
3061
|
-
var TAG_CTRL_HEADER = 71;
|
|
3062
|
-
var TAG_LIST_HEADER = 72;
|
|
3063
|
-
var TAG_TABLE = 77;
|
|
3064
|
-
var TAG_SHAPE_COMPONENT = 76;
|
|
3065
|
-
var TAG_SHAPE_COMPONENT_PICTURE = 85;
|
|
3066
|
-
var TAG_SHAPE_COMPONENT_CONTAINER = 86;
|
|
3067
|
-
var TAG_EQEDIT = 88;
|
|
3068
|
-
var TAG_BIN_DATA = 18;
|
|
3069
|
-
var TAG_DOC_CHAR_SHAPE = 21;
|
|
3070
|
-
var TAG_NUMBERING = 23;
|
|
3071
|
-
var TAG_BULLET = 24;
|
|
3072
|
-
var TAG_DOC_PARA_SHAPE = 25;
|
|
3073
|
-
var TAG_DOC_STYLE = 26;
|
|
3074
|
-
var CHAR_LINE = 0;
|
|
3075
|
-
var CHAR_SECTION_BREAK = 10;
|
|
3076
|
-
var CHAR_PARA = 13;
|
|
3077
|
-
var CHAR_TAB = 9;
|
|
3078
|
-
var CHAR_HYPHEN = 30;
|
|
3079
|
-
var CHAR_NBSP = 31;
|
|
3080
|
-
var CHAR_FIXED_NBSP = 24;
|
|
3081
|
-
var CHAR_FIXED_WIDTH = 25;
|
|
3082
|
-
var FLAG_COMPRESSED = 1 << 0;
|
|
3083
|
-
var FLAG_ENCRYPTED = 1 << 1;
|
|
3084
|
-
var FLAG_DISTRIBUTION = 1 << 2;
|
|
3085
|
-
var FLAG_DRM = 1 << 4;
|
|
3086
|
-
var MAX_RECORDS = 5e5;
|
|
3087
|
-
function readRecords(data) {
|
|
3088
|
-
const records = [];
|
|
3089
|
-
let offset = 0;
|
|
3090
|
-
while (offset + 4 <= data.length && records.length < MAX_RECORDS) {
|
|
3091
|
-
const header = data.readUInt32LE(offset);
|
|
3092
|
-
offset += 4;
|
|
3093
|
-
const tagId = header & 1023;
|
|
3094
|
-
const level = header >> 10 & 1023;
|
|
3095
|
-
let size = header >> 20 & 4095;
|
|
3096
|
-
if (size === 4095) {
|
|
3097
|
-
if (offset + 4 > data.length) break;
|
|
3098
|
-
size = data.readUInt32LE(offset);
|
|
3099
|
-
offset += 4;
|
|
3096
|
+
if (sweepUnreferenced) {
|
|
3097
|
+
for (const [storageId, bin] of [...binDataMap.entries()].sort((a, b) => a[0] - b[0])) {
|
|
3098
|
+
if (resolved.has(storageId)) continue;
|
|
3099
|
+
const mime = detectImageMime(bin.data);
|
|
3100
|
+
if (!mime) continue;
|
|
3101
|
+
imageIndex++;
|
|
3102
|
+
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
3103
|
+
const filename = `image_${String(imageIndex).padStart(3, "0")}.${ext}`;
|
|
3104
|
+
const data = new Uint8Array(bin.data);
|
|
3105
|
+
images.push({ filename, data, mimeType: mime });
|
|
3106
|
+
blocks.push({ type: "image", text: filename, imageData: { data, mimeType: mime, filename: bin.name } });
|
|
3100
3107
|
}
|
|
3101
|
-
if (offset + size > data.length) break;
|
|
3102
|
-
records.push({ tagId, level, size, data: data.subarray(offset, offset + size) });
|
|
3103
|
-
offset += size;
|
|
3104
3108
|
}
|
|
3105
|
-
|
|
3109
|
+
resolveCellImageSentinels(blocks, renamed);
|
|
3110
|
+
return images;
|
|
3106
3111
|
}
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3112
|
+
function extractHwp5Images(fileIndex, blocks, warnings, sweepUnreferenced) {
|
|
3113
|
+
const binDataMap = /* @__PURE__ */ new Map();
|
|
3114
|
+
if (fileIndex) {
|
|
3115
|
+
for (const entry of fileIndex) {
|
|
3116
|
+
if (!entry?.name || !entry.content) continue;
|
|
3117
|
+
const match = entry.name.match(BIN_ENTRY_RE);
|
|
3118
|
+
if (!match) continue;
|
|
3119
|
+
const idx = parseInt(match[1], 16);
|
|
3120
|
+
const data = normalizeBinPayload(Buffer.from(entry.content));
|
|
3121
|
+
binDataMap.set(idx, { data, name: entry.name });
|
|
3114
3122
|
}
|
|
3115
3123
|
}
|
|
3116
|
-
|
|
3124
|
+
if (binDataMap.size === 0) {
|
|
3125
|
+
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3126
|
+
return [];
|
|
3127
|
+
}
|
|
3128
|
+
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
3117
3129
|
}
|
|
3118
|
-
function
|
|
3119
|
-
|
|
3120
|
-
const
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3130
|
+
function extractHwp5ImagesLenient(lcfb, blocks, warnings, sweepUnreferenced) {
|
|
3131
|
+
const binDataMap = /* @__PURE__ */ new Map();
|
|
3132
|
+
const binRe = /^BIN([0-9A-Fa-f]{4,8})(?:\.|$)/;
|
|
3133
|
+
for (const e of lcfb.entries()) {
|
|
3134
|
+
const match = e.name.match(binRe);
|
|
3135
|
+
if (!match) continue;
|
|
3136
|
+
const idx = parseInt(match[1], 16);
|
|
3137
|
+
const raw = lcfb.findStream(e.name);
|
|
3138
|
+
if (!raw) continue;
|
|
3139
|
+
binDataMap.set(idx, { data: normalizeBinPayload(raw), name: e.name });
|
|
3140
|
+
}
|
|
3141
|
+
if (binDataMap.size === 0) {
|
|
3142
|
+
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3143
|
+
return [];
|
|
3144
|
+
}
|
|
3145
|
+
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
3126
3146
|
}
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3147
|
+
|
|
3148
|
+
// src/hwpx/images.ts
|
|
3149
|
+
function imageExtToMime(ext) {
|
|
3150
|
+
switch (ext.toLowerCase()) {
|
|
3151
|
+
case "jpg":
|
|
3152
|
+
case "jpeg":
|
|
3153
|
+
return "image/jpeg";
|
|
3154
|
+
case "png":
|
|
3155
|
+
return "image/png";
|
|
3156
|
+
case "gif":
|
|
3157
|
+
return "image/gif";
|
|
3158
|
+
case "bmp":
|
|
3159
|
+
return "image/bmp";
|
|
3160
|
+
case "tif":
|
|
3161
|
+
case "tiff":
|
|
3162
|
+
return "image/tiff";
|
|
3163
|
+
case "wmf":
|
|
3164
|
+
return "image/wmf";
|
|
3165
|
+
case "emf":
|
|
3166
|
+
return "image/emf";
|
|
3167
|
+
case "svg":
|
|
3168
|
+
return "image/svg+xml";
|
|
3169
|
+
default:
|
|
3170
|
+
return "application/octet-stream";
|
|
3171
|
+
}
|
|
3134
3172
|
}
|
|
3135
|
-
function
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
const
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
const storageId = rec.data.length >= 4 ? rec.data.readUInt16LE(2) : 0;
|
|
3157
|
-
const { value: extension } = readHwpString(rec.data, 4);
|
|
3158
|
-
binData.push({ kind: typeBits === 2 ? "storage" : "embed", storageId, extension });
|
|
3173
|
+
function mimeToExt(mime) {
|
|
3174
|
+
if (mime.includes("jpeg")) return "jpg";
|
|
3175
|
+
if (mime.includes("png")) return "png";
|
|
3176
|
+
if (mime.includes("gif")) return "gif";
|
|
3177
|
+
if (mime.includes("bmp")) return "bmp";
|
|
3178
|
+
if (mime.includes("tiff")) return "tif";
|
|
3179
|
+
if (mime.includes("wmf")) return "wmf";
|
|
3180
|
+
if (mime.includes("emf")) return "emf";
|
|
3181
|
+
if (mime.includes("svg")) return "svg";
|
|
3182
|
+
return "bin";
|
|
3183
|
+
}
|
|
3184
|
+
function collectImageBlocks2(blocks, out, ownerCell, depth = 0) {
|
|
3185
|
+
if (depth > MAX_XML_DEPTH) return;
|
|
3186
|
+
for (const block of blocks) {
|
|
3187
|
+
if (block.type === "image") {
|
|
3188
|
+
out.push({ block, ownerCell });
|
|
3189
|
+
} else if (block.type === "table" && block.table) {
|
|
3190
|
+
for (const row of block.table.cells) {
|
|
3191
|
+
for (const cell2 of row) {
|
|
3192
|
+
if (cell2.blocks?.length) collectImageBlocks2(cell2.blocks, out, cell2, depth + 1);
|
|
3193
|
+
}
|
|
3159
3194
|
}
|
|
3160
3195
|
}
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
async function extractImagesFromZip(zip, blocks, decompressed, warnings, sweepUnreferenced) {
|
|
3199
|
+
const images = [];
|
|
3200
|
+
let imageIndex = 0;
|
|
3201
|
+
const usedPaths = /* @__PURE__ */ new Set();
|
|
3202
|
+
const imageBlocks = [];
|
|
3203
|
+
collectImageBlocks2(blocks, imageBlocks);
|
|
3204
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
3205
|
+
for (const { block, ownerCell } of imageBlocks) {
|
|
3206
|
+
if (block.type !== "image" || !block.text) continue;
|
|
3207
|
+
const ref = block.text;
|
|
3208
|
+
let img = resolved.get(ref);
|
|
3209
|
+
if (img === void 0) {
|
|
3210
|
+
img = null;
|
|
3211
|
+
const candidates = [
|
|
3212
|
+
`BinData/${ref}`,
|
|
3213
|
+
`Contents/BinData/${ref}`,
|
|
3214
|
+
ref
|
|
3215
|
+
// 절대 경로일 수도 있음
|
|
3216
|
+
];
|
|
3217
|
+
let resolvedPath = null;
|
|
3218
|
+
if (!ref.includes(".")) {
|
|
3219
|
+
const prefixes = [`BinData/${ref}`, `Contents/BinData/${ref}`];
|
|
3220
|
+
for (const prefix of prefixes) {
|
|
3221
|
+
const match = zip.file(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.[a-zA-Z0-9]+$`));
|
|
3222
|
+
if (match.length > 0) {
|
|
3223
|
+
resolvedPath = match[0].name;
|
|
3224
|
+
break;
|
|
3225
|
+
}
|
|
3171
3226
|
}
|
|
3172
|
-
const attr = rec.data.readUInt32LE(offset);
|
|
3173
|
-
numberFormats.push(attr >>> 5 & 15);
|
|
3174
|
-
offset += 12;
|
|
3175
|
-
const { value, next } = readHwpString(rec.data, offset);
|
|
3176
|
-
levelFormats.push(value);
|
|
3177
|
-
offset = next;
|
|
3178
3227
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3228
|
+
const allCandidates = resolvedPath ? [resolvedPath, ...candidates] : candidates;
|
|
3229
|
+
for (const path of allCandidates) {
|
|
3230
|
+
if (isPathTraversal(path)) continue;
|
|
3231
|
+
const file = zip.file(path);
|
|
3232
|
+
if (!file) continue;
|
|
3233
|
+
try {
|
|
3234
|
+
const data = await file.async("uint8array");
|
|
3235
|
+
decompressed.total += data.length;
|
|
3236
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3237
|
+
const ext = path.includes(".") ? path.split(".").pop() || "png" : "png";
|
|
3238
|
+
const mimeType = imageExtToMime(ext);
|
|
3239
|
+
imageIndex++;
|
|
3240
|
+
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
3241
|
+
img = { filename, data, mimeType };
|
|
3242
|
+
images.push(img);
|
|
3243
|
+
usedPaths.add(path);
|
|
3244
|
+
break;
|
|
3245
|
+
} catch (err) {
|
|
3246
|
+
if (err instanceof KordocError) throw err;
|
|
3247
|
+
}
|
|
3183
3248
|
}
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3249
|
+
if (!img) warnings?.push({ page: block.pageNumber, message: `\uC774\uBBF8\uC9C0 \uD30C\uC77C \uC5C6\uC74C: ${ref}`, code: "SKIPPED_IMAGE" });
|
|
3250
|
+
resolved.set(ref, img);
|
|
3251
|
+
}
|
|
3252
|
+
if (!img) {
|
|
3253
|
+
block.type = "paragraph";
|
|
3254
|
+
block.text = `[\uC774\uBBF8\uC9C0: ${ref}]`;
|
|
3255
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
3256
|
+
continue;
|
|
3257
|
+
}
|
|
3258
|
+
block.text = img.filename;
|
|
3259
|
+
block.imageData = { data: img.data, mimeType: img.mimeType, filename: ref };
|
|
3260
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, ``);
|
|
3261
|
+
}
|
|
3262
|
+
if (sweepUnreferenced) {
|
|
3263
|
+
const binEntries = zip.file(/(?:^|\/)BinData\//i);
|
|
3264
|
+
for (const file of binEntries) {
|
|
3265
|
+
if (file.dir || usedPaths.has(file.name) || isPathTraversal(file.name)) continue;
|
|
3266
|
+
try {
|
|
3267
|
+
const data = await file.async("uint8array");
|
|
3268
|
+
decompressed.total += data.length;
|
|
3269
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3270
|
+
const ext = file.name.includes(".") ? file.name.split(".").pop() || "" : "";
|
|
3271
|
+
let mimeType = imageExtToMime(ext);
|
|
3272
|
+
if (mimeType === "application/octet-stream") {
|
|
3273
|
+
const sniffed = detectImageMime(data);
|
|
3274
|
+
if (!sniffed) continue;
|
|
3275
|
+
mimeType = sniffed;
|
|
3190
3276
|
}
|
|
3277
|
+
imageIndex++;
|
|
3278
|
+
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
3279
|
+
const img = { filename, data, mimeType };
|
|
3280
|
+
images.push(img);
|
|
3281
|
+
blocks.push({ type: "image", text: filename, imageData: { data, mimeType, filename: file.name } });
|
|
3282
|
+
} catch (err) {
|
|
3283
|
+
if (err instanceof KordocError) throw err;
|
|
3284
|
+
warnings?.push({ message: `\uC774\uBBF8\uC9C0 \uCD94\uCD9C \uC2E4\uD328: ${file.name}`, code: "SKIPPED_IMAGE" });
|
|
3191
3285
|
}
|
|
3192
|
-
numberings.push({ levelFormats, numberFormats, startNumbers });
|
|
3193
|
-
}
|
|
3194
|
-
if (rec.tagId === TAG_BULLET && rec.data.length >= 14) {
|
|
3195
|
-
const code = rec.data.readUInt16LE(12);
|
|
3196
|
-
bullets.push({ char: code > 0 ? String.fromCharCode(code) : "\u2022" });
|
|
3197
3286
|
}
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3287
|
+
}
|
|
3288
|
+
return images;
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
// src/hwpx/metadata.ts
|
|
3292
|
+
import JSZip2 from "jszip";
|
|
3293
|
+
|
|
3294
|
+
// src/hwpx/zip-sections.ts
|
|
3295
|
+
import { inflateRawSync as inflateRawSync2 } from "zlib";
|
|
3296
|
+
function extractFromBrokenZip(buffer) {
|
|
3297
|
+
const data = new Uint8Array(buffer);
|
|
3298
|
+
const view = new DataView(buffer);
|
|
3299
|
+
let pos = 0;
|
|
3300
|
+
const blocks = [];
|
|
3301
|
+
const warnings = [
|
|
3302
|
+
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
3303
|
+
];
|
|
3304
|
+
let totalDecompressed = 0;
|
|
3305
|
+
let entryCount = 0;
|
|
3306
|
+
let sectionNum = 0;
|
|
3307
|
+
const shared = createSectionShared();
|
|
3308
|
+
while (pos < data.length - 30) {
|
|
3309
|
+
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
3310
|
+
pos++;
|
|
3311
|
+
while (pos < data.length - 30) {
|
|
3312
|
+
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
3313
|
+
pos++;
|
|
3205
3314
|
}
|
|
3315
|
+
continue;
|
|
3206
3316
|
}
|
|
3207
|
-
if (
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3317
|
+
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
3318
|
+
const method = view.getUint16(pos + 8, true);
|
|
3319
|
+
const compSize = view.getUint32(pos + 18, true);
|
|
3320
|
+
const nameLen = view.getUint16(pos + 26, true);
|
|
3321
|
+
const extraLen = view.getUint16(pos + 28, true);
|
|
3322
|
+
if (nameLen > 1024 || extraLen > 65535) {
|
|
3323
|
+
pos += 30 + nameLen + extraLen;
|
|
3324
|
+
continue;
|
|
3325
|
+
}
|
|
3326
|
+
const fileStart = pos + 30 + nameLen + extraLen;
|
|
3327
|
+
if (fileStart + compSize > data.length) break;
|
|
3328
|
+
if (compSize === 0 && method !== 0) {
|
|
3329
|
+
pos = fileStart;
|
|
3330
|
+
continue;
|
|
3331
|
+
}
|
|
3332
|
+
const nameBytes = data.slice(pos + 30, pos + 30 + nameLen);
|
|
3333
|
+
const name = new TextDecoder().decode(nameBytes);
|
|
3334
|
+
if (isPathTraversal(name)) {
|
|
3335
|
+
pos = fileStart + compSize;
|
|
3336
|
+
continue;
|
|
3337
|
+
}
|
|
3338
|
+
const fileData = data.slice(fileStart, fileStart + compSize);
|
|
3339
|
+
pos = fileStart + compSize;
|
|
3340
|
+
if (!name.toLowerCase().includes("section") || !name.endsWith(".xml")) continue;
|
|
3341
|
+
try {
|
|
3342
|
+
let content;
|
|
3343
|
+
if (method === 0) {
|
|
3344
|
+
content = new TextDecoder().decode(fileData);
|
|
3345
|
+
} else if (method === 8) {
|
|
3346
|
+
const decompressed = inflateRawSync2(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
3347
|
+
content = new TextDecoder().decode(decompressed);
|
|
3348
|
+
} else {
|
|
3349
|
+
continue;
|
|
3234
3350
|
}
|
|
3351
|
+
totalDecompressed += content.length * 2;
|
|
3352
|
+
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
3353
|
+
sectionNum++;
|
|
3354
|
+
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
3355
|
+
} catch {
|
|
3356
|
+
continue;
|
|
3235
3357
|
}
|
|
3236
3358
|
}
|
|
3237
|
-
|
|
3359
|
+
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");
|
|
3360
|
+
applyPageText(blocks, shared);
|
|
3361
|
+
const markdown = blocksToMarkdown(blocks);
|
|
3362
|
+
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
3238
3363
|
}
|
|
3239
|
-
function
|
|
3240
|
-
|
|
3364
|
+
async function readKordocLayout(zip) {
|
|
3365
|
+
const file = zip.file("Contents/content.hpf") ?? zip.file("content.hpf");
|
|
3366
|
+
if (!file) return null;
|
|
3367
|
+
try {
|
|
3368
|
+
const xml = await file.async("text");
|
|
3369
|
+
if (!/<opf:meta\b[^>]*name="generator"[^>]*content="kordoc"/.test(xml)) return null;
|
|
3370
|
+
return xml.match(/<opf:meta\b[^>]*name="kordoc-layout"[^>]*content="([^"]*)"/)?.[1] ?? null;
|
|
3371
|
+
} catch {
|
|
3372
|
+
return null;
|
|
3373
|
+
}
|
|
3241
3374
|
}
|
|
3242
|
-
function
|
|
3243
|
-
|
|
3375
|
+
async function resolveSectionPaths(zip) {
|
|
3376
|
+
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
3377
|
+
for (const mp of manifestPaths) {
|
|
3378
|
+
const mpLower = mp.toLowerCase();
|
|
3379
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
3380
|
+
if (!file) continue;
|
|
3381
|
+
const xml = await file.async("text");
|
|
3382
|
+
const paths = parseSectionPathsFromManifest(xml);
|
|
3383
|
+
if (paths.length > 0) return paths;
|
|
3384
|
+
}
|
|
3385
|
+
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
3386
|
+
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
3244
3387
|
}
|
|
3245
|
-
function
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
const
|
|
3249
|
-
const
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
const
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3388
|
+
function parseSectionPathsFromManifest(xml) {
|
|
3389
|
+
const parser = createXmlParser();
|
|
3390
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
3391
|
+
const items = doc.getElementsByTagName("opf:item");
|
|
3392
|
+
const spine = doc.getElementsByTagName("opf:itemref");
|
|
3393
|
+
const idToHref = /* @__PURE__ */ new Map();
|
|
3394
|
+
for (let i = 0; i < items.length; i++) {
|
|
3395
|
+
const item = items[i];
|
|
3396
|
+
const id = item.getAttribute("id") || "";
|
|
3397
|
+
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
3398
|
+
if (id && href) idToHref.set(id, href);
|
|
3399
|
+
}
|
|
3400
|
+
if (spine.length > 0) {
|
|
3401
|
+
const ordered = [];
|
|
3402
|
+
for (let i = 0; i < spine.length; i++) {
|
|
3403
|
+
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
3404
|
+
if (href) ordered.push(href);
|
|
3405
|
+
}
|
|
3406
|
+
if (ordered.length > 0) return ordered;
|
|
3407
|
+
}
|
|
3408
|
+
return Array.from(idToHref.values()).sort(compareSectionPaths);
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
// src/hwpx/metadata.ts
|
|
3412
|
+
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
3413
|
+
try {
|
|
3414
|
+
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
3415
|
+
for (const mp of metaPaths) {
|
|
3416
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
3417
|
+
if (!file) continue;
|
|
3418
|
+
const xml = await file.async("text");
|
|
3419
|
+
if (decompressed) {
|
|
3420
|
+
decompressed.total += xml.length * 2;
|
|
3421
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3272
3422
|
}
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
// 문단 끝
|
|
3276
|
-
case CHAR_HYPHEN:
|
|
3277
|
-
result += "-";
|
|
3278
|
-
break;
|
|
3279
|
-
case CHAR_NBSP:
|
|
3280
|
-
result += " ";
|
|
3281
|
-
break;
|
|
3282
|
-
case CHAR_FIXED_NBSP:
|
|
3283
|
-
result += "\xA0";
|
|
3284
|
-
break;
|
|
3285
|
-
// 진짜 NBSP
|
|
3286
|
-
case CHAR_FIXED_WIDTH:
|
|
3287
|
-
result += " ";
|
|
3288
|
-
break;
|
|
3289
|
-
// 고정폭 공백
|
|
3290
|
-
// ── inline 타입 (2바이트 + 14바이트 확장) ──
|
|
3291
|
-
case CHAR_TAB:
|
|
3292
|
-
result += " ";
|
|
3293
|
-
if (i + 14 <= data.length) i += 14;
|
|
3294
|
-
break;
|
|
3295
|
-
default:
|
|
3296
|
-
if (ch >= 1 && ch <= 31) {
|
|
3297
|
-
const isExtended = isExtendedOnlyCtrlChar(ch);
|
|
3298
|
-
const isInline = ch >= 4 && ch <= 9 || ch >= 19 && ch <= 20;
|
|
3299
|
-
if ((isExtended || isInline) && i + 14 <= data.length) {
|
|
3300
|
-
if (ch === 3) {
|
|
3301
|
-
state.fieldStack.push({ start: base + result.length, ctrlIdx: state.ctrlIdx });
|
|
3302
|
-
} else if (ch === 4) {
|
|
3303
|
-
const open = state.fieldStack.pop();
|
|
3304
|
-
if (open) {
|
|
3305
|
-
state.fieldRanges.push({ start: open.start, end: base + result.length, ctrlIdx: open.ctrlIdx });
|
|
3306
|
-
}
|
|
3307
|
-
}
|
|
3308
|
-
resolveAt(i, isExtended);
|
|
3309
|
-
i += 14;
|
|
3310
|
-
}
|
|
3311
|
-
} else if (ch >= 32) {
|
|
3312
|
-
if (ch >= 55296 && ch <= 56319 && i + 1 < data.length) {
|
|
3313
|
-
const lo = data.readUInt16LE(i);
|
|
3314
|
-
if (lo >= 56320 && lo <= 57343) {
|
|
3315
|
-
i += 2;
|
|
3316
|
-
const codePoint = (ch - 55296 << 10) + (lo - 56320) + 65536;
|
|
3317
|
-
result += String.fromCodePoint(codePoint);
|
|
3318
|
-
break;
|
|
3319
|
-
}
|
|
3320
|
-
}
|
|
3321
|
-
result += String.fromCharCode(ch);
|
|
3322
|
-
}
|
|
3323
|
-
break;
|
|
3423
|
+
parseDublinCoreMetadata(xml, metadata);
|
|
3424
|
+
if (metadata.title || metadata.author) return;
|
|
3324
3425
|
}
|
|
3426
|
+
} catch {
|
|
3325
3427
|
}
|
|
3326
|
-
state.text += result;
|
|
3327
3428
|
}
|
|
3328
|
-
function
|
|
3329
|
-
|
|
3330
|
-
const
|
|
3331
|
-
|
|
3332
|
-
const
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3429
|
+
function parseDublinCoreMetadata(xml, metadata) {
|
|
3430
|
+
const parser = createXmlParser();
|
|
3431
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
3432
|
+
if (!doc.documentElement) return;
|
|
3433
|
+
const getText = (tagNames) => {
|
|
3434
|
+
for (const tag of tagNames) {
|
|
3435
|
+
const els = doc.getElementsByTagName(tag);
|
|
3436
|
+
if (els.length > 0) {
|
|
3437
|
+
const text = els[0].textContent?.trim();
|
|
3438
|
+
if (text) return text;
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
return void 0;
|
|
3442
|
+
};
|
|
3443
|
+
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
3444
|
+
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
3445
|
+
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
3446
|
+
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
3447
|
+
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
3448
|
+
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
3449
|
+
if (keywords && !metadata.keywords) {
|
|
3450
|
+
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
// src/hwpx/parser.ts
|
|
3455
|
+
async function parseHwpxDocument(buffer, options) {
|
|
3456
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
3457
|
+
let zip;
|
|
3458
|
+
try {
|
|
3459
|
+
zip = await JSZip3.loadAsync(buffer);
|
|
3460
|
+
} catch {
|
|
3461
|
+
return extractFromBrokenZip(buffer);
|
|
3462
|
+
}
|
|
3463
|
+
const actualEntryCount = Object.keys(zip.files).length;
|
|
3464
|
+
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
3465
|
+
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3466
|
+
}
|
|
3467
|
+
const manifestFile = zip.file("META-INF/manifest.xml");
|
|
3468
|
+
if (manifestFile) {
|
|
3469
|
+
const manifestXml = await manifestFile.async("text");
|
|
3470
|
+
if (isEncryptedHwpx(manifestXml)) {
|
|
3471
|
+
if (isComFallbackAvailable() && options?.filePath) {
|
|
3472
|
+
const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
|
|
3473
|
+
if (pages.some((p) => p && p.trim().length > 0)) {
|
|
3474
|
+
return comResultToParseResult(pages, pageCount, warnings2);
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
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.");
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
const decompressed = { total: 0 };
|
|
3481
|
+
const metadata = {};
|
|
3482
|
+
await extractHwpxMetadata(zip, metadata, decompressed);
|
|
3483
|
+
const styleMap = await extractHwpxStyles(zip, decompressed);
|
|
3484
|
+
const warnings = [];
|
|
3485
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
3486
|
+
if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
3487
|
+
metadata.pageCount = sectionPaths.length;
|
|
3488
|
+
const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null;
|
|
3489
|
+
const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length;
|
|
3490
|
+
const blocks = [];
|
|
3491
|
+
const shared = createSectionShared();
|
|
3492
|
+
shared.kordocLayout = await readKordocLayout(zip);
|
|
3493
|
+
let parsedSections = 0;
|
|
3494
|
+
for (let si = 0; si < sectionPaths.length; si++) {
|
|
3495
|
+
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
3496
|
+
const file = zip.file(sectionPaths[si]);
|
|
3497
|
+
if (!file) continue;
|
|
3498
|
+
try {
|
|
3499
|
+
const xml = await file.async("text");
|
|
3500
|
+
decompressed.total += xml.length * 2;
|
|
3501
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3502
|
+
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
3503
|
+
parsedSections++;
|
|
3504
|
+
options?.onProgress?.(parsedSections, totalTarget);
|
|
3505
|
+
} catch (secErr) {
|
|
3506
|
+
if (secErr instanceof KordocError) throw secErr;
|
|
3507
|
+
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" });
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
applyPageText(blocks, shared);
|
|
3511
|
+
const images = await extractImagesFromZip(zip, blocks, decompressed, warnings, !pageFilter);
|
|
3512
|
+
detectHwpxHeadings(blocks, styleMap);
|
|
3513
|
+
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
3514
|
+
const markdown = blocksToMarkdown(blocks);
|
|
3515
|
+
return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0, images: images.length > 0 ? images : void 0 };
|
|
3336
3516
|
}
|
|
3337
3517
|
|
|
3338
3518
|
// src/hwp5/numbering.ts
|
|
@@ -3513,282 +3693,6 @@ function expandNumberingFormat(formatStr, counters, numbering) {
|
|
|
3513
3693
|
return result;
|
|
3514
3694
|
}
|
|
3515
3695
|
|
|
3516
|
-
// src/hwp5/images.ts
|
|
3517
|
-
function detectImageMime(data) {
|
|
3518
|
-
if (data.length < 4) return null;
|
|
3519
|
-
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return "image/png";
|
|
3520
|
-
if (data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
|
|
3521
|
-
if (data[0] === 71 && data[1] === 73 && data[2] === 70) return "image/gif";
|
|
3522
|
-
if (data[0] === 66 && data[1] === 77) return "image/bmp";
|
|
3523
|
-
if (data[0] === 215 && data[1] === 205 && data[2] === 198 && data[3] === 154) return "image/wmf";
|
|
3524
|
-
if (data[0] === 1 && data[1] === 0 && data[2] === 0 && data[3] === 0) return "image/emf";
|
|
3525
|
-
return null;
|
|
3526
|
-
}
|
|
3527
|
-
function normalizeBinPayload(data) {
|
|
3528
|
-
if (detectImageMime(data)) return data;
|
|
3529
|
-
try {
|
|
3530
|
-
const inflated = decompressStream(data);
|
|
3531
|
-
if (inflated.length > 0) return inflated;
|
|
3532
|
-
} catch {
|
|
3533
|
-
}
|
|
3534
|
-
return data;
|
|
3535
|
-
}
|
|
3536
|
-
var BIN_ENTRY_RE = /(?:^|\/)BIN([0-9A-Fa-f]{4,8})(?:\.[^./\\]*)?$/;
|
|
3537
|
-
function collectImageBlocks2(blocks, out) {
|
|
3538
|
-
for (const b of blocks) {
|
|
3539
|
-
if (b.type === "image") out.push(b);
|
|
3540
|
-
if (b.table) {
|
|
3541
|
-
for (const row of b.table.cells) {
|
|
3542
|
-
for (const cell2 of row) {
|
|
3543
|
-
if (cell2.blocks) collectImageBlocks2(cell2.blocks, out);
|
|
3544
|
-
}
|
|
3545
|
-
}
|
|
3546
|
-
}
|
|
3547
|
-
if (b.children) collectImageBlocks2(b.children, out);
|
|
3548
|
-
}
|
|
3549
|
-
}
|
|
3550
|
-
function forEachTableCell(blocks, fn) {
|
|
3551
|
-
for (const b of blocks) {
|
|
3552
|
-
if (b.table) {
|
|
3553
|
-
for (const row of b.table.cells) {
|
|
3554
|
-
for (const cell2 of row) {
|
|
3555
|
-
fn(cell2);
|
|
3556
|
-
if (cell2.blocks) forEachTableCell(cell2.blocks, fn);
|
|
3557
|
-
}
|
|
3558
|
-
}
|
|
3559
|
-
}
|
|
3560
|
-
if (b.children) forEachTableCell(b.children, fn);
|
|
3561
|
-
}
|
|
3562
|
-
}
|
|
3563
|
-
var CELL_IMAGE_SENTINEL_RE = /!\[image\]\(hwp5bin:(\d+)\)/g;
|
|
3564
|
-
function resolveCellImageSentinels(blocks, renamed) {
|
|
3565
|
-
forEachTableCell(blocks, (cell2) => {
|
|
3566
|
-
if (!cell2.text.includes("hwp5bin:")) return;
|
|
3567
|
-
cell2.text = cell2.text.replace(CELL_IMAGE_SENTINEL_RE, (_m, idStr) => {
|
|
3568
|
-
const filename = renamed.get(Number(idStr));
|
|
3569
|
-
return filename ? `` : "[\uC774\uBBF8\uC9C0]";
|
|
3570
|
-
});
|
|
3571
|
-
});
|
|
3572
|
-
}
|
|
3573
|
-
function resolveImageBlocks(binDataMap, blocks, warnings) {
|
|
3574
|
-
const imageBlocks = [];
|
|
3575
|
-
collectImageBlocks2(blocks, imageBlocks);
|
|
3576
|
-
if (imageBlocks.length === 0) return [];
|
|
3577
|
-
const images = [];
|
|
3578
|
-
const renamed = /* @__PURE__ */ new Map();
|
|
3579
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
3580
|
-
let imageIndex = 0;
|
|
3581
|
-
for (const block of imageBlocks) {
|
|
3582
|
-
if (!block.text) continue;
|
|
3583
|
-
const storageId = parseInt(block.text, 10);
|
|
3584
|
-
if (isNaN(storageId)) continue;
|
|
3585
|
-
let img = resolved.get(storageId);
|
|
3586
|
-
if (img === void 0) {
|
|
3587
|
-
const bin = binDataMap.get(storageId);
|
|
3588
|
-
if (!bin) {
|
|
3589
|
-
warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
|
|
3590
|
-
resolved.set(storageId, null);
|
|
3591
|
-
} else {
|
|
3592
|
-
const mime = detectImageMime(bin.data);
|
|
3593
|
-
if (!mime) {
|
|
3594
|
-
warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
|
|
3595
|
-
resolved.set(storageId, null);
|
|
3596
|
-
} else {
|
|
3597
|
-
imageIndex++;
|
|
3598
|
-
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
3599
|
-
img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
|
|
3600
|
-
resolved.set(storageId, img);
|
|
3601
|
-
images.push({ filename: img.filename, data: img.data, mimeType: img.mime });
|
|
3602
|
-
renamed.set(storageId, img.filename);
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
img = resolved.get(storageId);
|
|
3606
|
-
}
|
|
3607
|
-
if (!img) {
|
|
3608
|
-
const bin = binDataMap.get(storageId);
|
|
3609
|
-
block.type = "paragraph";
|
|
3610
|
-
block.text = bin ? `[\uC774\uBBF8\uC9C0: ${bin.name}]` : `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
|
|
3611
|
-
continue;
|
|
3612
|
-
}
|
|
3613
|
-
block.text = img.filename;
|
|
3614
|
-
block.imageData = { data: img.data, mimeType: img.mime, filename: binDataMap.get(storageId).name };
|
|
3615
|
-
}
|
|
3616
|
-
resolveCellImageSentinels(blocks, renamed);
|
|
3617
|
-
return images;
|
|
3618
|
-
}
|
|
3619
|
-
function extractHwp5Images(fileIndex, blocks, warnings) {
|
|
3620
|
-
const binDataMap = /* @__PURE__ */ new Map();
|
|
3621
|
-
if (fileIndex) {
|
|
3622
|
-
for (const entry of fileIndex) {
|
|
3623
|
-
if (!entry?.name || !entry.content) continue;
|
|
3624
|
-
const match = entry.name.match(BIN_ENTRY_RE);
|
|
3625
|
-
if (!match) continue;
|
|
3626
|
-
const idx = parseInt(match[1], 16);
|
|
3627
|
-
const data = normalizeBinPayload(Buffer.from(entry.content));
|
|
3628
|
-
binDataMap.set(idx, { data, name: entry.name });
|
|
3629
|
-
}
|
|
3630
|
-
}
|
|
3631
|
-
if (binDataMap.size === 0) {
|
|
3632
|
-
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3633
|
-
return [];
|
|
3634
|
-
}
|
|
3635
|
-
return resolveImageBlocks(binDataMap, blocks, warnings);
|
|
3636
|
-
}
|
|
3637
|
-
function extractHwp5ImagesLenient(lcfb, blocks, warnings) {
|
|
3638
|
-
const binDataMap = /* @__PURE__ */ new Map();
|
|
3639
|
-
const binRe = /^BIN([0-9A-Fa-f]{4,8})(?:\.|$)/;
|
|
3640
|
-
for (const e of lcfb.entries()) {
|
|
3641
|
-
const match = e.name.match(binRe);
|
|
3642
|
-
if (!match) continue;
|
|
3643
|
-
const idx = parseInt(match[1], 16);
|
|
3644
|
-
const raw = lcfb.findStream(e.name);
|
|
3645
|
-
if (!raw) continue;
|
|
3646
|
-
binDataMap.set(idx, { data: normalizeBinPayload(raw), name: e.name });
|
|
3647
|
-
}
|
|
3648
|
-
if (binDataMap.size === 0) {
|
|
3649
|
-
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3650
|
-
return [];
|
|
3651
|
-
}
|
|
3652
|
-
return resolveImageBlocks(binDataMap, blocks, warnings);
|
|
3653
|
-
}
|
|
3654
|
-
|
|
3655
|
-
// src/image/transcode.ts
|
|
3656
|
-
import { deflateSync } from "zlib";
|
|
3657
|
-
var CRC_TABLE = (() => {
|
|
3658
|
-
const table2 = new Uint32Array(256);
|
|
3659
|
-
for (let n = 0; n < 256; n++) {
|
|
3660
|
-
let c = n;
|
|
3661
|
-
for (let k = 0; k < 8; k++) {
|
|
3662
|
-
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
3663
|
-
}
|
|
3664
|
-
table2[n] = c >>> 0;
|
|
3665
|
-
}
|
|
3666
|
-
return table2;
|
|
3667
|
-
})();
|
|
3668
|
-
function crc32(bytes) {
|
|
3669
|
-
let c = 4294967295;
|
|
3670
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
3671
|
-
c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
|
|
3672
|
-
}
|
|
3673
|
-
return (c ^ 4294967295) >>> 0;
|
|
3674
|
-
}
|
|
3675
|
-
var BI_RGB = 0;
|
|
3676
|
-
var MAX_DIM = 32767;
|
|
3677
|
-
var MAX_PIXELS = 36e6;
|
|
3678
|
-
function bmpToPng(bmp) {
|
|
3679
|
-
if (bmp.length < 54) return null;
|
|
3680
|
-
if (bmp[0] !== 66 || bmp[1] !== 77) return null;
|
|
3681
|
-
const dv = new DataView(bmp.buffer, bmp.byteOffset, bmp.byteLength);
|
|
3682
|
-
const dataOffset = dv.getUint32(10, true);
|
|
3683
|
-
const headerSize = dv.getUint32(14, true);
|
|
3684
|
-
if (headerSize < 40) return null;
|
|
3685
|
-
const width = dv.getInt32(18, true);
|
|
3686
|
-
const rawHeight = dv.getInt32(22, true);
|
|
3687
|
-
const bitCount = dv.getUint16(28, true);
|
|
3688
|
-
const compression = dv.getUint32(30, true);
|
|
3689
|
-
if (compression !== BI_RGB) return null;
|
|
3690
|
-
if (bitCount !== 24 && bitCount !== 32) return null;
|
|
3691
|
-
if (width <= 0 || rawHeight === 0) return null;
|
|
3692
|
-
if (width > MAX_DIM || Math.abs(rawHeight) > MAX_DIM) return null;
|
|
3693
|
-
const topDown = rawHeight < 0;
|
|
3694
|
-
const height = Math.abs(rawHeight);
|
|
3695
|
-
if (width * height > MAX_PIXELS) return null;
|
|
3696
|
-
const bytesPerPixel = bitCount >> 3;
|
|
3697
|
-
const rowStride = width * bytesPerPixel + 3 & ~3;
|
|
3698
|
-
if (dataOffset + rowStride * height > bmp.length) return null;
|
|
3699
|
-
const rgba = new Uint8Array(width * height * 4);
|
|
3700
|
-
let anyAlpha = 0;
|
|
3701
|
-
for (let y = 0; y < height; y++) {
|
|
3702
|
-
const srcRow = topDown ? y : height - 1 - y;
|
|
3703
|
-
let src = dataOffset + srcRow * rowStride;
|
|
3704
|
-
let dst = y * width * 4;
|
|
3705
|
-
for (let x = 0; x < width; x++) {
|
|
3706
|
-
rgba[dst] = bmp[src + 2];
|
|
3707
|
-
rgba[dst + 1] = bmp[src + 1];
|
|
3708
|
-
rgba[dst + 2] = bmp[src];
|
|
3709
|
-
const a = bitCount === 32 ? bmp[src + 3] : 255;
|
|
3710
|
-
rgba[dst + 3] = a;
|
|
3711
|
-
anyAlpha |= a;
|
|
3712
|
-
src += bytesPerPixel;
|
|
3713
|
-
dst += 4;
|
|
3714
|
-
}
|
|
3715
|
-
}
|
|
3716
|
-
if (bitCount === 32 && anyAlpha === 0) {
|
|
3717
|
-
for (let i = 3; i < rgba.length; i += 4) rgba[i] = 255;
|
|
3718
|
-
}
|
|
3719
|
-
return encodePng(width, height, rgba);
|
|
3720
|
-
}
|
|
3721
|
-
var PNG_SIGNATURE = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10);
|
|
3722
|
-
function chunk(type, data) {
|
|
3723
|
-
const body = new Uint8Array(4 + data.length);
|
|
3724
|
-
body[0] = type.charCodeAt(0);
|
|
3725
|
-
body[1] = type.charCodeAt(1);
|
|
3726
|
-
body[2] = type.charCodeAt(2);
|
|
3727
|
-
body[3] = type.charCodeAt(3);
|
|
3728
|
-
body.set(data, 4);
|
|
3729
|
-
const out = new Uint8Array(8 + data.length + 4);
|
|
3730
|
-
const dv = new DataView(out.buffer);
|
|
3731
|
-
dv.setUint32(0, data.length, false);
|
|
3732
|
-
out.set(body, 4);
|
|
3733
|
-
dv.setUint32(8 + data.length, crc32(body), false);
|
|
3734
|
-
return out;
|
|
3735
|
-
}
|
|
3736
|
-
function encodePng(width, height, rgba) {
|
|
3737
|
-
const ihdr = new Uint8Array(13);
|
|
3738
|
-
const iv = new DataView(ihdr.buffer);
|
|
3739
|
-
iv.setUint32(0, width, false);
|
|
3740
|
-
iv.setUint32(4, height, false);
|
|
3741
|
-
ihdr[8] = 8;
|
|
3742
|
-
ihdr[9] = 6;
|
|
3743
|
-
const stride = width * 4;
|
|
3744
|
-
const raw = new Uint8Array((stride + 1) * height);
|
|
3745
|
-
for (let y = 0; y < height; y++) {
|
|
3746
|
-
const rowStart = y * (stride + 1);
|
|
3747
|
-
raw[rowStart] = 0;
|
|
3748
|
-
raw.set(rgba.subarray(y * stride, y * stride + stride), rowStart + 1);
|
|
3749
|
-
}
|
|
3750
|
-
const idat = deflateSync(raw);
|
|
3751
|
-
const ihdrChunk = chunk("IHDR", ihdr);
|
|
3752
|
-
const idatChunk = chunk("IDAT", idat);
|
|
3753
|
-
const iendChunk = chunk("IEND", new Uint8Array(0));
|
|
3754
|
-
const out = new Uint8Array(PNG_SIGNATURE.length + ihdrChunk.length + idatChunk.length + iendChunk.length);
|
|
3755
|
-
let o = 0;
|
|
3756
|
-
out.set(PNG_SIGNATURE, o);
|
|
3757
|
-
o += PNG_SIGNATURE.length;
|
|
3758
|
-
out.set(ihdrChunk, o);
|
|
3759
|
-
o += ihdrChunk.length;
|
|
3760
|
-
out.set(idatChunk, o);
|
|
3761
|
-
o += idatChunk.length;
|
|
3762
|
-
out.set(iendChunk, o);
|
|
3763
|
-
return out;
|
|
3764
|
-
}
|
|
3765
|
-
function escapeRegExp(s) {
|
|
3766
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3767
|
-
}
|
|
3768
|
-
function inlineImagesIntoMarkdown(markdown, images, opts) {
|
|
3769
|
-
const compress = opts?.compress !== false;
|
|
3770
|
-
let out = markdown;
|
|
3771
|
-
for (const img of images) {
|
|
3772
|
-
let bytes = img.data;
|
|
3773
|
-
let mime = img.mimeType;
|
|
3774
|
-
if (compress && mime === "image/bmp") {
|
|
3775
|
-
const png = bmpToPng(img.data);
|
|
3776
|
-
if (png) {
|
|
3777
|
-
bytes = png;
|
|
3778
|
-
mime = "image/png";
|
|
3779
|
-
}
|
|
3780
|
-
}
|
|
3781
|
-
const base64 = Buffer.from(bytes).toString("base64");
|
|
3782
|
-
const dataUri = `data:${mime};base64,${base64}`;
|
|
3783
|
-
const name = escapeRegExp(img.filename);
|
|
3784
|
-
const mdRe = new RegExp(`!\\[image\\]\\((?:images/)?${name}\\)`, "g");
|
|
3785
|
-
out = out.replace(mdRe, () => ``);
|
|
3786
|
-
const imgTagRe = new RegExp(`(<img\\b[^>]*\\bsrc=")(?:images/)?${name}(")`, "g");
|
|
3787
|
-
out = out.replace(imgTagRe, (_m, pre, post) => `${pre}${dataUri}${post}`);
|
|
3788
|
-
}
|
|
3789
|
-
return out;
|
|
3790
|
-
}
|
|
3791
|
-
|
|
3792
3696
|
// src/hwp5/aes.ts
|
|
3793
3697
|
var S_BOX = new Uint8Array([
|
|
3794
3698
|
99,
|
|
@@ -4836,7 +4740,7 @@ function parseHwp5Document(buffer, options) {
|
|
|
4836
4740
|
}
|
|
4837
4741
|
}
|
|
4838
4742
|
const blocks = [...doc.headerBlocks, ...bodyBlocks, ...doc.footerBlocks];
|
|
4839
|
-
const images = cfb ? extractHwp5Images(cfb.FileIndex, blocks, warnings) : extractHwp5ImagesLenient(lenientCfb, blocks, warnings);
|
|
4743
|
+
const images = cfb ? extractHwp5Images(cfb.FileIndex, blocks, warnings, !pageFilter) : extractHwp5ImagesLenient(lenientCfb, blocks, warnings, !pageFilter);
|
|
4840
4744
|
let flatBlocks = flattenLayoutTables(blocks);
|
|
4841
4745
|
if (options?.dedupeRunningHeaders) {
|
|
4842
4746
|
const deduped = dedupeRunningHeaders(flatBlocks);
|
|
@@ -18057,7 +17961,7 @@ async function parseXlsxDocument(buffer, options) {
|
|
|
18057
17961
|
}
|
|
18058
17962
|
let pageFilter = null;
|
|
18059
17963
|
if (options?.pages) {
|
|
18060
|
-
const { parsePageRange: parsePageRange2 } = await import("./page-range-
|
|
17964
|
+
const { parsePageRange: parsePageRange2 } = await import("./page-range-LNMXJU6W.js");
|
|
18061
17965
|
pageFilter = parsePageRange2(options.pages, sheets.length);
|
|
18062
17966
|
}
|
|
18063
17967
|
const blocks = [];
|
|
@@ -18695,7 +18599,7 @@ async function parseXlsDocument(buffer, options) {
|
|
|
18695
18599
|
const totalSheets = Math.min(globals.sheets.length, MAX_SHEETS2);
|
|
18696
18600
|
let pageFilter = null;
|
|
18697
18601
|
if (options?.pages) {
|
|
18698
|
-
const { parsePageRange: parsePageRange2 } = await import("./page-range-
|
|
18602
|
+
const { parsePageRange: parsePageRange2 } = await import("./page-range-LNMXJU6W.js");
|
|
18699
18603
|
pageFilter = parsePageRange2(options.pages, totalSheets);
|
|
18700
18604
|
}
|
|
18701
18605
|
const allBlocks = [];
|
|
@@ -19533,9 +19437,17 @@ async function buildImageMap(zip, rels, doc, warnings) {
|
|
|
19533
19437
|
const imageMap = /* @__PURE__ */ new Map();
|
|
19534
19438
|
const images = [];
|
|
19535
19439
|
let imgIdx = 0;
|
|
19440
|
+
const imageRefs = [];
|
|
19536
19441
|
for (const blip of findElements(doc.documentElement, "blip")) {
|
|
19537
19442
|
const embedId = getAttr(blip, "embed");
|
|
19538
|
-
if (
|
|
19443
|
+
if (embedId) imageRefs.push(embedId);
|
|
19444
|
+
}
|
|
19445
|
+
for (const imagedata of collectNonFallbackImagedata(doc.documentElement)) {
|
|
19446
|
+
const embedId = getAttr(imagedata, "id");
|
|
19447
|
+
if (embedId) imageRefs.push(embedId);
|
|
19448
|
+
}
|
|
19449
|
+
for (const embedId of imageRefs) {
|
|
19450
|
+
if (imageMap.has(embedId)) continue;
|
|
19539
19451
|
const target = rels.get(embedId);
|
|
19540
19452
|
if (!target) continue;
|
|
19541
19453
|
const imgPath = target.startsWith("/") ? target.slice(1) : target.startsWith("word/") ? target : `word/${target}`;
|
|
@@ -19570,14 +19482,26 @@ function collectParagraphBlips(node, out = [], depth = 0) {
|
|
|
19570
19482
|
if (depth > 40) return out;
|
|
19571
19483
|
for (const el of effectiveChildElements(node)) {
|
|
19572
19484
|
if (matchesLocal(el, "txbxContent") || matchesLocal(el, "Fallback")) continue;
|
|
19573
|
-
if (matchesLocal(el, "blip")) out.push(el);
|
|
19485
|
+
if (matchesLocal(el, "blip") || matchesLocal(el, "imagedata")) out.push(el);
|
|
19574
19486
|
else collectParagraphBlips(el, out, depth + 1);
|
|
19575
19487
|
}
|
|
19576
19488
|
return out;
|
|
19577
19489
|
}
|
|
19490
|
+
function collectNonFallbackImagedata(node, out = [], depth = 0) {
|
|
19491
|
+
if (depth > 60) return out;
|
|
19492
|
+
const children = node.childNodes;
|
|
19493
|
+
for (let i = 0; i < children.length; i++) {
|
|
19494
|
+
if (children[i].nodeType !== 1) continue;
|
|
19495
|
+
const el = children[i];
|
|
19496
|
+
if (matchesLocal(el, "Fallback")) continue;
|
|
19497
|
+
if (matchesLocal(el, "imagedata")) out.push(el);
|
|
19498
|
+
else collectNonFallbackImagedata(el, out, depth + 1);
|
|
19499
|
+
}
|
|
19500
|
+
return out;
|
|
19501
|
+
}
|
|
19578
19502
|
function emitParagraphImages(p, imageMap, linked, out) {
|
|
19579
19503
|
for (const blip of collectParagraphBlips(p)) {
|
|
19580
|
-
const embedId = getAttr(blip, "embed");
|
|
19504
|
+
const embedId = getAttr(blip, "embed") ?? getAttr(blip, "id");
|
|
19581
19505
|
if (!embedId) continue;
|
|
19582
19506
|
const filename = imageMap.get(embedId);
|
|
19583
19507
|
if (!filename) continue;
|
|
@@ -21270,7 +21194,7 @@ function parseCentralDirectory(buf) {
|
|
|
21270
21194
|
}
|
|
21271
21195
|
return { entries, cdOffset, cdSize, eocdOffset };
|
|
21272
21196
|
}
|
|
21273
|
-
var
|
|
21197
|
+
var CRC_TABLE = (() => {
|
|
21274
21198
|
const table2 = new Uint32Array(256);
|
|
21275
21199
|
for (let n = 0; n < 256; n++) {
|
|
21276
21200
|
let c = n;
|
|
@@ -21279,10 +21203,10 @@ var CRC_TABLE2 = (() => {
|
|
|
21279
21203
|
}
|
|
21280
21204
|
return table2;
|
|
21281
21205
|
})();
|
|
21282
|
-
function
|
|
21206
|
+
function crc32(data) {
|
|
21283
21207
|
let crc = 4294967295;
|
|
21284
21208
|
for (let i = 0; i < data.length; i++) {
|
|
21285
|
-
crc =
|
|
21209
|
+
crc = CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
|
|
21286
21210
|
}
|
|
21287
21211
|
return (crc ^ 4294967295) >>> 0;
|
|
21288
21212
|
}
|
|
@@ -21321,7 +21245,7 @@ function patchZipEntries(original, replacements, additions) {
|
|
|
21321
21245
|
const hview = new DataView(header.buffer, header.byteOffset, header.byteLength);
|
|
21322
21246
|
const method = e.method;
|
|
21323
21247
|
const compData = method === 0 ? newData : new Uint8Array(deflateRawSync(newData));
|
|
21324
|
-
const crc =
|
|
21248
|
+
const crc = crc32(newData);
|
|
21325
21249
|
const flags = e.flags & ~8;
|
|
21326
21250
|
hview.setUint16(6, flags, true);
|
|
21327
21251
|
hview.setUint32(14, crc, true);
|
|
@@ -21339,7 +21263,7 @@ function patchZipEntries(original, replacements, additions) {
|
|
|
21339
21263
|
const deflated = new Uint8Array(deflateRawSync(data));
|
|
21340
21264
|
const method = deflated.length < data.length ? 8 : 0;
|
|
21341
21265
|
const compData = method === 8 ? deflated : data;
|
|
21342
|
-
const crc =
|
|
21266
|
+
const crc = crc32(data);
|
|
21343
21267
|
const header = new Uint8Array(30 + nameBytes.length);
|
|
21344
21268
|
const hv = new DataView(header.buffer);
|
|
21345
21269
|
hv.setUint32(0, LOCAL_SIG, true);
|
|
@@ -26881,16 +26805,16 @@ function buildOrigUnits(blocks) {
|
|
|
26881
26805
|
for (let i = 0; i < blocks.length; i++) {
|
|
26882
26806
|
const block = blocks[i];
|
|
26883
26807
|
let consume = 1;
|
|
26884
|
-
let
|
|
26808
|
+
let chunk;
|
|
26885
26809
|
if (block.type === "paragraph" && block.text && /^\[별표\s*\d+/.test(sanitizeText(block.text))) {
|
|
26886
26810
|
const next = blocks[i + 1];
|
|
26887
26811
|
if (next?.type === "paragraph" && next.text && /관련\)?$/.test(next.text)) consume = 2;
|
|
26888
|
-
|
|
26812
|
+
chunk = blocksToMarkdown(blocks.slice(i, i + consume));
|
|
26889
26813
|
} else {
|
|
26890
|
-
|
|
26814
|
+
chunk = blocksToMarkdown([block]);
|
|
26891
26815
|
}
|
|
26892
|
-
if (
|
|
26893
|
-
const subUnits = splitMarkdownUnits(
|
|
26816
|
+
if (chunk) {
|
|
26817
|
+
const subUnits = splitMarkdownUnits(chunk);
|
|
26894
26818
|
const isFragment = consume === 2 || (block.type === "paragraph" || block.type === "heading") && subUnits.length > 1;
|
|
26895
26819
|
for (let s = 0; s < subUnits.length; s++) {
|
|
26896
26820
|
const u = { ...subUnits[s], blockIdx: i, fragment: isFragment || void 0 };
|
|
@@ -29883,7 +29807,7 @@ async function parseHwp(buffer, options) {
|
|
|
29883
29807
|
async function parsePdf(buffer, options) {
|
|
29884
29808
|
let parsePdfDocument;
|
|
29885
29809
|
try {
|
|
29886
|
-
const mod = await import("./parser-
|
|
29810
|
+
const mod = await import("./parser-KMMUS73Q.js");
|
|
29887
29811
|
parsePdfDocument = mod.parsePdfDocument;
|
|
29888
29812
|
} catch {
|
|
29889
29813
|
return {
|
|
@@ -29894,8 +29818,8 @@ async function parsePdf(buffer, options) {
|
|
|
29894
29818
|
};
|
|
29895
29819
|
}
|
|
29896
29820
|
try {
|
|
29897
|
-
const { markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary } = await parsePdfDocument(buffer, options);
|
|
29898
|
-
return { success: true, fileType: "pdf", markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary };
|
|
29821
|
+
const { markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary, images } = await parsePdfDocument(buffer, options);
|
|
29822
|
+
return { success: true, fileType: "pdf", markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary, images };
|
|
29899
29823
|
} catch (err) {
|
|
29900
29824
|
const isImageBased = err instanceof Error && "isImageBased" in err ? true : void 0;
|
|
29901
29825
|
return { success: false, fileType: "pdf", error: err instanceof Error ? err.message : "PDF \uD30C\uC2F1 \uC2E4\uD328", code: classifyError(err), isImageBased };
|