kordoc 4.0.6 → 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/README.md +823 -806
- package/dist/{-NDU7YGSS.js → -LE4RLXVA.js} +8 -8
- package/dist/{chunk-5SOZXPQY.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-DQJTNXVS.cjs → chunk-6XAAYAWL.cjs} +142 -3
- package/dist/chunk-6XAAYAWL.cjs.map +1 -0
- package/dist/{chunk-HC2SEH6I.js → chunk-7S3M4N4E.js} +939 -946
- package/dist/chunk-7S3M4N4E.js.map +1 -0
- package/dist/{chunk-4C7SBZNW.js → chunk-BNU5QGIZ.js} +2 -2
- package/dist/chunk-BNU5QGIZ.js.map +1 -0
- package/dist/{chunk-63ASYC4N.js → chunk-D35VBACN.js} +3 -3
- package/dist/chunk-D35VBACN.js.map +1 -0
- package/dist/chunk-DCZVOIEO.cjs.map +1 -1
- package/dist/chunk-GS7T56RP.cjs.map +1 -1
- package/dist/{chunk-L3I4TIRP.js → chunk-HMUEU7D7.js} +2 -2
- package/dist/chunk-HMUEU7D7.js.map +1 -0
- package/dist/{chunk-5CJGKKMZ.js → chunk-MEPHGCPQ.js} +1 -1
- package/dist/chunk-MEPHGCPQ.js.map +1 -0
- package/dist/chunk-MOL7MDBG.js +0 -0
- package/dist/{chunk-HOGJ5ALY.js → chunk-OQPILS7B.js} +142 -3
- package/dist/chunk-OQPILS7B.js.map +1 -0
- package/dist/{chunk-KA4FEBS2.js → chunk-RROH5WHO.js} +2 -2
- package/dist/chunk-RROH5WHO.js.map +1 -0
- package/dist/{chunk-UVSOSFER.js → chunk-UEIYETCQ.js} +142 -3
- package/dist/chunk-UEIYETCQ.js.map +1 -0
- package/dist/cli.js +15 -15
- package/dist/cli.js.map +1 -1
- package/dist/{detect-PJZMUL2Z.js → detect-RI2MQ33K.js} +2 -2
- package/dist/formula-5NKVS2LR.cjs.map +1 -1
- package/dist/formula-JCNF43NE.js +0 -0
- package/dist/index.cjs +1248 -1255
- 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 +925 -932
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +13 -13
- package/dist/mcp.js.map +1 -1
- package/dist/page-range-737B4EZW.js +0 -0
- package/dist/page-range-P7SDW6LR.cjs.map +1 -1
- package/dist/{parser-VEY4B5CJ.cjs → parser-ITMU2WN5.cjs} +231 -37
- package/dist/parser-ITMU2WN5.cjs.map +1 -0
- package/dist/{parser-DMKHIV4E.js → parser-KMMUS73Q.js} +200 -6
- package/dist/parser-KMMUS73Q.js.map +1 -0
- package/dist/{parser-C636QIOB.js → parser-TCTCSBYZ.js} +202 -8
- package/dist/parser-TCTCSBYZ.js.map +1 -0
- package/dist/{profile-io-JYGCC6XQ.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-57FB3LSP.js +0 -0
- package/dist/{watch-ZQUFJBFR.js → watch-OHQWSVPE.js} +8 -8
- package/dist/watch-OHQWSVPE.js.map +1 -0
- package/package.json +97 -97
- package/dist/chunk-4C7SBZNW.js.map +0 -1
- package/dist/chunk-5CJGKKMZ.js.map +0 -1
- package/dist/chunk-5SOZXPQY.js.map +0 -1
- package/dist/chunk-63ASYC4N.js.map +0 -1
- package/dist/chunk-6GBLFQMA.js.map +0 -1
- package/dist/chunk-DQJTNXVS.cjs.map +0 -1
- package/dist/chunk-HC2SEH6I.js.map +0 -1
- package/dist/chunk-HOGJ5ALY.js.map +0 -1
- package/dist/chunk-KA4FEBS2.js.map +0 -1
- package/dist/chunk-L3I4TIRP.js.map +0 -1
- package/dist/chunk-UVSOSFER.js.map +0 -1
- package/dist/parser-C636QIOB.js.map +0 -1
- package/dist/parser-DMKHIV4E.js.map +0 -1
- package/dist/parser-VEY4B5CJ.cjs.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-ISOEOMTM.js +0 -10
- package/dist/seal-676ZIAZZ.js +0 -10
- package/dist/watch-ZQUFJBFR.js.map +0 -1
- /package/dist/{-NDU7YGSS.js.map → -LE4RLXVA.js.map} +0 -0
- /package/dist/{detect-PJZMUL2Z.js.map → detect-RI2MQ33K.js.map} +0 -0
- /package/dist/{profile-io-JYGCC6XQ.js.map → profile-io-SLN4P76T.js.map} +0 -0
- /package/dist/{render-ISOEOMTM.js.map → render-25BT623I.js.map} +0 -0
- /package/dist/{seal-676ZIAZZ.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,7 +21,7 @@ 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
27
|
} from "./chunk-GE43BE46.js";
|
|
@@ -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
2865
|
}
|
|
2855
|
-
if (
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
content = new TextDecoder().decode(fileData);
|
|
2883
|
-
} else if (method === 8) {
|
|
2884
|
-
const decompressed = inflateRawSync(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
2885
|
-
content = new TextDecoder().decode(decompressed);
|
|
2886
|
-
} else {
|
|
2887
|
-
continue;
|
|
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 {
|
|
2888
2893
|
}
|
|
2889
|
-
totalDecompressed += content.length * 2;
|
|
2890
|
-
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
2891
|
-
sectionNum++;
|
|
2892
|
-
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
2893
|
-
} catch {
|
|
2894
|
-
continue;
|
|
2895
2894
|
}
|
|
2896
2895
|
}
|
|
2897
|
-
|
|
2898
|
-
applyPageText(blocks, shared);
|
|
2899
|
-
const markdown = blocksToMarkdown(blocks);
|
|
2900
|
-
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
2896
|
+
return { charShapes, paraShapes, styles, binData, numberings, bullets };
|
|
2901
2897
|
}
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
if (!file) return null;
|
|
2905
|
-
try {
|
|
2906
|
-
const xml = await file.async("text");
|
|
2907
|
-
if (!/<opf:meta\b[^>]*name="generator"[^>]*content="kordoc"/.test(xml)) return null;
|
|
2908
|
-
return xml.match(/<opf:meta\b[^>]*name="kordoc-layout"[^>]*content="([^"]*)"/)?.[1] ?? null;
|
|
2909
|
-
} catch {
|
|
2910
|
-
return null;
|
|
2911
|
-
}
|
|
2898
|
+
function createParaTextState() {
|
|
2899
|
+
return { text: "", ctrlIdx: 0, fieldStack: [], fieldRanges: [] };
|
|
2912
2900
|
}
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
for (const mp of manifestPaths) {
|
|
2916
|
-
const mpLower = mp.toLowerCase();
|
|
2917
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
2918
|
-
if (!file) continue;
|
|
2919
|
-
const xml = await file.async("text");
|
|
2920
|
-
const paths = parseSectionPathsFromManifest(xml);
|
|
2921
|
-
if (paths.length > 0) return paths;
|
|
2922
|
-
}
|
|
2923
|
-
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
2924
|
-
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
2901
|
+
function isExtendedOnlyCtrlChar(ch) {
|
|
2902
|
+
return ch >= 1 && ch <= 3 || ch >= 11 && ch <= 12 || ch >= 14 && ch <= 18 || ch >= 21 && ch <= 23;
|
|
2925
2903
|
}
|
|
2926
|
-
function
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
const
|
|
2930
|
-
const
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
const
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
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;
|
|
2931
|
+
}
|
|
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;
|
|
2943
2983
|
}
|
|
2944
|
-
if (ordered.length > 0) return ordered;
|
|
2945
2984
|
}
|
|
2946
|
-
|
|
2985
|
+
state.text += result;
|
|
2986
|
+
}
|
|
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;
|
|
2947
2995
|
}
|
|
2948
2996
|
|
|
2949
|
-
// src/
|
|
2950
|
-
|
|
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;
|
|
3007
|
+
}
|
|
3008
|
+
function normalizeBinPayload(data) {
|
|
3009
|
+
if (detectImageMime(data)) return data;
|
|
2951
3010
|
try {
|
|
2952
|
-
const
|
|
2953
|
-
|
|
2954
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
2955
|
-
if (!file) continue;
|
|
2956
|
-
const xml = await file.async("text");
|
|
2957
|
-
if (decompressed) {
|
|
2958
|
-
decompressed.total += xml.length * 2;
|
|
2959
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2960
|
-
}
|
|
2961
|
-
parseDublinCoreMetadata(xml, metadata);
|
|
2962
|
-
if (metadata.title || metadata.author) return;
|
|
2963
|
-
}
|
|
3011
|
+
const inflated = decompressStream(data);
|
|
3012
|
+
if (inflated.length > 0) return inflated;
|
|
2964
3013
|
} catch {
|
|
2965
3014
|
}
|
|
3015
|
+
return data;
|
|
2966
3016
|
}
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
const
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
if (text) return text;
|
|
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
|
+
}
|
|
2977
3026
|
}
|
|
2978
3027
|
}
|
|
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);
|
|
3028
|
+
if (b.children) collectImageBlocks(b.children, out);
|
|
2989
3029
|
}
|
|
2990
3030
|
}
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
3003
|
-
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
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
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
if (b.children) forEachTableCell(b.children, fn);
|
|
3004
3042
|
}
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3043
|
+
}
|
|
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
|
}
|
|
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;
|
|
3092
|
+
}
|
|
3093
|
+
block.text = img.filename;
|
|
3094
|
+
block.imageData = { data: img.data, mimeType: img.mime, filename: binDataMap.get(storageId).name };
|
|
3017
3095
|
}
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
const shared = createSectionShared();
|
|
3030
|
-
shared.kordocLayout = await readKordocLayout(zip);
|
|
3031
|
-
let parsedSections = 0;
|
|
3032
|
-
for (let si = 0; si < sectionPaths.length; si++) {
|
|
3033
|
-
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
3034
|
-
const file = zip.file(sectionPaths[si]);
|
|
3035
|
-
if (!file) continue;
|
|
3036
|
-
try {
|
|
3037
|
-
const xml = await file.async("text");
|
|
3038
|
-
decompressed.total += xml.length * 2;
|
|
3039
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3040
|
-
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
3041
|
-
parsedSections++;
|
|
3042
|
-
options?.onProgress?.(parsedSections, totalTarget);
|
|
3043
|
-
} catch (secErr) {
|
|
3044
|
-
if (secErr instanceof KordocError) throw secErr;
|
|
3045
|
-
warnings.push({ page: si + 1, message: `\uC139\uC158 ${si + 1} \uD30C\uC2F1 \uC2E4\uD328: ${secErr instanceof Error ? secErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "PARTIAL_PARSE" });
|
|
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 } });
|
|
3046
3107
|
}
|
|
3047
3108
|
}
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
detectHwpxHeadings(blocks, styleMap);
|
|
3051
|
-
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
3052
|
-
const markdown = blocksToMarkdown(blocks);
|
|
3053
|
-
return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0, images: images.length > 0 ? images : void 0 };
|
|
3109
|
+
resolveCellImageSentinels(blocks, renamed);
|
|
3110
|
+
return images;
|
|
3054
3111
|
}
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
var TAG_SHAPE_COMPONENT_PICTURE = 85;
|
|
3066
|
-
var TAG_SHAPE_COMPONENT_CONTAINER = 86;
|
|
3067
|
-
var TAG_EQEDIT = 88;
|
|
3068
|
-
var TAG_BIN_DATA = 18;
|
|
3069
|
-
var TAG_DOC_CHAR_SHAPE = 21;
|
|
3070
|
-
var TAG_NUMBERING = 23;
|
|
3071
|
-
var TAG_BULLET = 24;
|
|
3072
|
-
var TAG_DOC_PARA_SHAPE = 25;
|
|
3073
|
-
var TAG_DOC_STYLE = 26;
|
|
3074
|
-
var CHAR_LINE = 0;
|
|
3075
|
-
var CHAR_SECTION_BREAK = 10;
|
|
3076
|
-
var CHAR_PARA = 13;
|
|
3077
|
-
var CHAR_TAB = 9;
|
|
3078
|
-
var CHAR_HYPHEN = 30;
|
|
3079
|
-
var CHAR_NBSP = 31;
|
|
3080
|
-
var CHAR_FIXED_NBSP = 24;
|
|
3081
|
-
var CHAR_FIXED_WIDTH = 25;
|
|
3082
|
-
var FLAG_COMPRESSED = 1 << 0;
|
|
3083
|
-
var FLAG_ENCRYPTED = 1 << 1;
|
|
3084
|
-
var FLAG_DISTRIBUTION = 1 << 2;
|
|
3085
|
-
var FLAG_DRM = 1 << 4;
|
|
3086
|
-
var MAX_RECORDS = 5e5;
|
|
3087
|
-
function readRecords(data) {
|
|
3088
|
-
const records = [];
|
|
3089
|
-
let offset = 0;
|
|
3090
|
-
while (offset + 4 <= data.length && records.length < MAX_RECORDS) {
|
|
3091
|
-
const header = data.readUInt32LE(offset);
|
|
3092
|
-
offset += 4;
|
|
3093
|
-
const tagId = header & 1023;
|
|
3094
|
-
const level = header >> 10 & 1023;
|
|
3095
|
-
let size = header >> 20 & 4095;
|
|
3096
|
-
if (size === 4095) {
|
|
3097
|
-
if (offset + 4 > data.length) break;
|
|
3098
|
-
size = data.readUInt32LE(offset);
|
|
3099
|
-
offset += 4;
|
|
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 });
|
|
3100
3122
|
}
|
|
3101
|
-
if (offset + size > data.length) break;
|
|
3102
|
-
records.push({ tagId, level, size, data: data.subarray(offset, offset + size) });
|
|
3103
|
-
offset += size;
|
|
3104
3123
|
}
|
|
3105
|
-
|
|
3124
|
+
if (binDataMap.size === 0) {
|
|
3125
|
+
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3126
|
+
return [];
|
|
3127
|
+
}
|
|
3128
|
+
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
3106
3129
|
}
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
const
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
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 [];
|
|
3115
3144
|
}
|
|
3116
|
-
return
|
|
3145
|
+
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
3117
3146
|
}
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
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
|
+
}
|
|
3126
3172
|
}
|
|
3127
|
-
function
|
|
3128
|
-
if (
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
if (
|
|
3133
|
-
|
|
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";
|
|
3134
3183
|
}
|
|
3135
|
-
function
|
|
3136
|
-
|
|
3137
|
-
const
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
const headType = attr1 >>> 23 & 3;
|
|
3146
|
-
const paraLevel = attr1 >>> 25 & 7;
|
|
3147
|
-
const numberingId = rec.data.length >= 32 ? rec.data.readUInt16LE(30) : 0;
|
|
3148
|
-
paraShapes.push({ headType, paraLevel, numberingId });
|
|
3149
|
-
}
|
|
3150
|
-
if (rec.tagId === TAG_BIN_DATA && rec.data.length >= 2) {
|
|
3151
|
-
const attr = rec.data.readUInt16LE(0);
|
|
3152
|
-
const typeBits = attr & 15;
|
|
3153
|
-
if (typeBits === 0) {
|
|
3154
|
-
binData.push({ kind: "link", storageId: 0, extension: "" });
|
|
3155
|
-
} else {
|
|
3156
|
-
const storageId = rec.data.length >= 4 ? rec.data.readUInt16LE(2) : 0;
|
|
3157
|
-
const { value: extension } = readHwpString(rec.data, 4);
|
|
3158
|
-
binData.push({ kind: typeBits === 2 ? "storage" : "embed", storageId, extension });
|
|
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
3286
|
}
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
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++;
|
|
3314
|
+
}
|
|
3315
|
+
continue;
|
|
3316
|
+
}
|
|
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;
|
|
3197
3337
|
}
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
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);
|
|
3203
3348
|
} else {
|
|
3204
|
-
|
|
3205
|
-
}
|
|
3206
|
-
}
|
|
3207
|
-
if (rec.tagId === TAG_DOC_STYLE && rec.data.length >= 8) {
|
|
3208
|
-
try {
|
|
3209
|
-
let offset = 0;
|
|
3210
|
-
const nameLen = rec.data.readUInt16LE(offset);
|
|
3211
|
-
offset += 2;
|
|
3212
|
-
const nameBytes = nameLen * 2;
|
|
3213
|
-
const name = nameBytes > 0 && offset + nameBytes <= rec.data.length ? rec.data.subarray(offset, offset + nameBytes).toString("utf16le") : "";
|
|
3214
|
-
offset += nameBytes;
|
|
3215
|
-
let nameKo = "";
|
|
3216
|
-
if (offset + 2 <= rec.data.length) {
|
|
3217
|
-
const nameKoLen = rec.data.readUInt16LE(offset);
|
|
3218
|
-
offset += 2;
|
|
3219
|
-
const nameKoBytes = nameKoLen * 2;
|
|
3220
|
-
if (nameKoBytes > 0 && offset + nameKoBytes <= rec.data.length) {
|
|
3221
|
-
nameKo = rec.data.subarray(offset, offset + nameKoBytes).toString("utf16le");
|
|
3222
|
-
}
|
|
3223
|
-
offset += nameKoBytes;
|
|
3224
|
-
}
|
|
3225
|
-
const type = offset < rec.data.length ? rec.data.readUInt8(offset) : 0;
|
|
3226
|
-
offset += 1;
|
|
3227
|
-
offset += 1;
|
|
3228
|
-
offset += 2;
|
|
3229
|
-
const paraShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
3230
|
-
offset += 2;
|
|
3231
|
-
const charShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
3232
|
-
styles.push({ name, nameKo, charShapeId, paraShapeId, type });
|
|
3233
|
-
} catch {
|
|
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
|
-
|
|
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)");
|
|
3422
|
+
}
|
|
3423
|
+
parseDublinCoreMetadata(xml, metadata);
|
|
3424
|
+
if (metadata.title || metadata.author) return;
|
|
3425
|
+
}
|
|
3426
|
+
} catch {
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
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;
|
|
3255
3442
|
};
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
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);
|
|
3269
3475
|
}
|
|
3270
|
-
result += "\n";
|
|
3271
|
-
break;
|
|
3272
3476
|
}
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
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;
|
|
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" });
|
|
3324
3508
|
}
|
|
3325
3509
|
}
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
const
|
|
3331
|
-
|
|
3332
|
-
const scriptEnd = scriptStart + scriptLength * 2;
|
|
3333
|
-
if (scriptLength <= 0 || scriptEnd > data.length) return null;
|
|
3334
|
-
const equation = data.subarray(scriptStart, scriptEnd).toString("utf16le").replace(/\0+/g, "").trim();
|
|
3335
|
-
return equation || null;
|
|
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);
|
|
@@ -19297,6 +19201,102 @@ function extractRun(r) {
|
|
|
19297
19201
|
}
|
|
19298
19202
|
return { text, bold, italic };
|
|
19299
19203
|
}
|
|
19204
|
+
function fieldUrlFromInstr(instr) {
|
|
19205
|
+
if (!/HYPERLINK\b/i.test(instr)) return null;
|
|
19206
|
+
const urlM = instr.match(/HYPERLINK\s+"([^"]*)"/i);
|
|
19207
|
+
const anchorM = instr.match(/\\l\s+"([^"]*)"/i);
|
|
19208
|
+
if (urlM && urlM[1]) {
|
|
19209
|
+
return anchorM && anchorM[1] ? `${urlM[1]}#${anchorM[1]}` : urlM[1];
|
|
19210
|
+
}
|
|
19211
|
+
if (anchorM && anchorM[1]) return `#${anchorM[1]}`;
|
|
19212
|
+
return null;
|
|
19213
|
+
}
|
|
19214
|
+
function makeLink(text, rawHref) {
|
|
19215
|
+
if (!text) return "";
|
|
19216
|
+
if (!rawHref) return text;
|
|
19217
|
+
const href = sanitizeHref(rawHref);
|
|
19218
|
+
return href ? `[${text}](${href})` : text;
|
|
19219
|
+
}
|
|
19220
|
+
function collectInline(p, footnotes, rels) {
|
|
19221
|
+
const parts = [];
|
|
19222
|
+
let bold = false;
|
|
19223
|
+
let italic = false;
|
|
19224
|
+
let footnoteText;
|
|
19225
|
+
let fieldActive = false;
|
|
19226
|
+
let fieldStage = "instr";
|
|
19227
|
+
let fieldInstr = "";
|
|
19228
|
+
let fieldDisplay = "";
|
|
19229
|
+
const flushField = () => {
|
|
19230
|
+
if (!fieldActive) return;
|
|
19231
|
+
if (fieldDisplay) {
|
|
19232
|
+
const url = fieldUrlFromInstr(fieldInstr);
|
|
19233
|
+
parts.push(url ? makeLink(fieldDisplay, url) : fieldDisplay);
|
|
19234
|
+
}
|
|
19235
|
+
fieldActive = false;
|
|
19236
|
+
fieldStage = "instr";
|
|
19237
|
+
fieldInstr = "";
|
|
19238
|
+
fieldDisplay = "";
|
|
19239
|
+
};
|
|
19240
|
+
for (const el of effectiveChildElements(p)) {
|
|
19241
|
+
if (matchesLocal(el, "hyperlink")) {
|
|
19242
|
+
const rId = getAttr(el, "id");
|
|
19243
|
+
const anchor = getAttr(el, "anchor");
|
|
19244
|
+
const t = findElements(el, "r").map((r) => extractRun(r).text).join("");
|
|
19245
|
+
if (!t) continue;
|
|
19246
|
+
let raw = null;
|
|
19247
|
+
if (rId && rels.has(rId)) raw = rels.get(rId);
|
|
19248
|
+
else if (anchor) raw = `#${anchor}`;
|
|
19249
|
+
if (fieldActive && fieldStage === "display") fieldDisplay += t;
|
|
19250
|
+
else parts.push(makeLink(t, raw));
|
|
19251
|
+
continue;
|
|
19252
|
+
}
|
|
19253
|
+
if (matchesLocal(el, "fldSimple")) {
|
|
19254
|
+
const t = findElements(el, "r").map((r) => extractRun(r).text).join("");
|
|
19255
|
+
if (!t) continue;
|
|
19256
|
+
const url = fieldUrlFromInstr(getAttr(el, "instr") ?? "");
|
|
19257
|
+
parts.push(url ? makeLink(t, url) : t);
|
|
19258
|
+
continue;
|
|
19259
|
+
}
|
|
19260
|
+
if (matchesLocal(el, "r")) {
|
|
19261
|
+
for (const fc of getChildElements(el, "fldChar")) {
|
|
19262
|
+
const ty = getAttr(fc, "fldCharType");
|
|
19263
|
+
if (ty === "begin") {
|
|
19264
|
+
flushField();
|
|
19265
|
+
fieldActive = true;
|
|
19266
|
+
fieldStage = "instr";
|
|
19267
|
+
} else if (ty === "separate") {
|
|
19268
|
+
if (fieldActive) fieldStage = "display";
|
|
19269
|
+
} else if (ty === "end") flushField();
|
|
19270
|
+
}
|
|
19271
|
+
if (fieldActive && fieldStage === "instr") {
|
|
19272
|
+
for (const it of getChildElements(el, "instrText")) fieldInstr += it.textContent ?? "";
|
|
19273
|
+
}
|
|
19274
|
+
const rr = extractRun(el);
|
|
19275
|
+
if (rr.bold) bold = true;
|
|
19276
|
+
if (rr.italic) italic = true;
|
|
19277
|
+
const fnRefEls = getChildElements(el, "footnoteReference");
|
|
19278
|
+
if (fnRefEls.length > 0) {
|
|
19279
|
+
const fnId = getAttr(fnRefEls[0], "id");
|
|
19280
|
+
if (fnId && footnotes.has(fnId)) footnoteText = footnotes.get(fnId);
|
|
19281
|
+
}
|
|
19282
|
+
if (rr.text) {
|
|
19283
|
+
if (fieldActive && fieldStage === "display") fieldDisplay += rr.text;
|
|
19284
|
+
else if (fieldActive && fieldStage === "instr") {
|
|
19285
|
+
} else parts.push(rr.text);
|
|
19286
|
+
}
|
|
19287
|
+
continue;
|
|
19288
|
+
}
|
|
19289
|
+
}
|
|
19290
|
+
flushField();
|
|
19291
|
+
for (const om of collectOmmlRoots(p)) {
|
|
19292
|
+
const latex = ommlElementToLatex(om);
|
|
19293
|
+
if (!latex) continue;
|
|
19294
|
+
if (isDisplayMath(om)) parts.push(" $$" + latex + "$$ ");
|
|
19295
|
+
else parts.push(" $" + latex + "$ ");
|
|
19296
|
+
}
|
|
19297
|
+
const text = parts.join("").replace(/[ \t]{2,}/g, " ").trim();
|
|
19298
|
+
return { text, bold, italic, footnoteText };
|
|
19299
|
+
}
|
|
19300
19300
|
function parseParagraph2(p, styles, numbering, footnotes, rels) {
|
|
19301
19301
|
const pPrEls = getChildElements(p, "pPr");
|
|
19302
19302
|
let styleId = "";
|
|
@@ -19313,54 +19313,7 @@ function parseParagraph2(p, styles, numbering, footnotes, rels) {
|
|
|
19313
19313
|
ilvl = ilvlEls.length > 0 ? parseInt(getAttr(ilvlEls[0], "val") ?? "0", 10) : 0;
|
|
19314
19314
|
}
|
|
19315
19315
|
}
|
|
19316
|
-
const
|
|
19317
|
-
let hasBold = false;
|
|
19318
|
-
let hasItalic = false;
|
|
19319
|
-
let href;
|
|
19320
|
-
let footnoteText;
|
|
19321
|
-
const hyperlinks = getChildElements(p, "hyperlink");
|
|
19322
|
-
const hyperlinkTexts = /* @__PURE__ */ new Set();
|
|
19323
|
-
for (const hl of hyperlinks) {
|
|
19324
|
-
const rId = getAttr(hl, "id");
|
|
19325
|
-
const hlText = [];
|
|
19326
|
-
const runs2 = findElements(hl, "r");
|
|
19327
|
-
for (const r of runs2) {
|
|
19328
|
-
const result = extractRun(r);
|
|
19329
|
-
hlText.push(result.text);
|
|
19330
|
-
}
|
|
19331
|
-
const text2 = hlText.join("");
|
|
19332
|
-
if (text2) {
|
|
19333
|
-
hyperlinkTexts.add(text2);
|
|
19334
|
-
if (rId && rels.has(rId)) {
|
|
19335
|
-
href = rels.get(rId);
|
|
19336
|
-
parts.push(text2);
|
|
19337
|
-
} else {
|
|
19338
|
-
parts.push(text2);
|
|
19339
|
-
}
|
|
19340
|
-
}
|
|
19341
|
-
}
|
|
19342
|
-
const runs = getChildElements(p, "r");
|
|
19343
|
-
for (const r of runs) {
|
|
19344
|
-
if (r.parentNode && r.parentNode.localName === "hyperlink") continue;
|
|
19345
|
-
const result = extractRun(r);
|
|
19346
|
-
if (result.bold) hasBold = true;
|
|
19347
|
-
if (result.italic) hasItalic = true;
|
|
19348
|
-
const fnRefEls = getChildElements(r, "footnoteReference");
|
|
19349
|
-
if (fnRefEls.length > 0) {
|
|
19350
|
-
const fnId = getAttr(fnRefEls[0], "id");
|
|
19351
|
-
if (fnId && footnotes.has(fnId)) {
|
|
19352
|
-
footnoteText = footnotes.get(fnId);
|
|
19353
|
-
}
|
|
19354
|
-
}
|
|
19355
|
-
if (result.text) parts.push(result.text);
|
|
19356
|
-
}
|
|
19357
|
-
for (const om of collectOmmlRoots(p)) {
|
|
19358
|
-
const latex = ommlElementToLatex(om);
|
|
19359
|
-
if (!latex) continue;
|
|
19360
|
-
if (isDisplayMath(om)) parts.push(" $$" + latex + "$$ ");
|
|
19361
|
-
else parts.push(" $" + latex + "$ ");
|
|
19362
|
-
}
|
|
19363
|
-
const text = parts.join("").replace(/[ \t]{2,}/g, " ").trim();
|
|
19316
|
+
const { text, bold: hasBold, italic: hasItalic, footnoteText } = collectInline(p, footnotes, rels);
|
|
19364
19317
|
if (!text) return null;
|
|
19365
19318
|
const style = styles.get(styleId);
|
|
19366
19319
|
if (style?.outlineLevel !== void 0 && style.outlineLevel >= 0 && style.outlineLevel <= 5) {
|
|
@@ -19380,7 +19333,6 @@ function parseParagraph2(p, styles, numbering, footnotes, rels) {
|
|
|
19380
19333
|
if (hasBold || hasItalic) {
|
|
19381
19334
|
block.style = { bold: hasBold || void 0, italic: hasItalic || void 0 };
|
|
19382
19335
|
}
|
|
19383
|
-
if (href) block.href = href;
|
|
19384
19336
|
if (footnoteText) block.footnoteText = footnoteText;
|
|
19385
19337
|
return block;
|
|
19386
19338
|
}
|
|
@@ -19481,46 +19433,81 @@ function collectCellText(tc2, styles, numbering, footnotes, rels, depth) {
|
|
|
19481
19433
|
}
|
|
19482
19434
|
return parts;
|
|
19483
19435
|
}
|
|
19484
|
-
async function
|
|
19485
|
-
const
|
|
19436
|
+
async function buildImageMap(zip, rels, doc, warnings) {
|
|
19437
|
+
const imageMap = /* @__PURE__ */ new Map();
|
|
19486
19438
|
const images = [];
|
|
19487
|
-
const drawingElements = findElements(doc.documentElement, "drawing");
|
|
19488
19439
|
let imgIdx = 0;
|
|
19489
|
-
|
|
19490
|
-
|
|
19491
|
-
|
|
19492
|
-
|
|
19493
|
-
|
|
19494
|
-
|
|
19495
|
-
|
|
19496
|
-
|
|
19497
|
-
|
|
19498
|
-
|
|
19499
|
-
|
|
19500
|
-
|
|
19501
|
-
|
|
19502
|
-
|
|
19503
|
-
|
|
19504
|
-
|
|
19505
|
-
|
|
19506
|
-
|
|
19507
|
-
|
|
19508
|
-
|
|
19509
|
-
|
|
19510
|
-
|
|
19511
|
-
|
|
19512
|
-
|
|
19513
|
-
|
|
19514
|
-
|
|
19515
|
-
|
|
19516
|
-
|
|
19517
|
-
|
|
19518
|
-
|
|
19519
|
-
|
|
19520
|
-
|
|
19440
|
+
const imageRefs = [];
|
|
19441
|
+
for (const blip of findElements(doc.documentElement, "blip")) {
|
|
19442
|
+
const embedId = getAttr(blip, "embed");
|
|
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;
|
|
19451
|
+
const target = rels.get(embedId);
|
|
19452
|
+
if (!target) continue;
|
|
19453
|
+
const imgPath = target.startsWith("/") ? target.slice(1) : target.startsWith("word/") ? target : `word/${target}`;
|
|
19454
|
+
const imgFile = zip.file(imgPath);
|
|
19455
|
+
if (!imgFile) continue;
|
|
19456
|
+
try {
|
|
19457
|
+
const data = await imgFile.async("uint8array");
|
|
19458
|
+
imgIdx++;
|
|
19459
|
+
const ext = imgPath.split(".").pop()?.toLowerCase() ?? "png";
|
|
19460
|
+
const mimeMap = {
|
|
19461
|
+
png: "image/png",
|
|
19462
|
+
jpg: "image/jpeg",
|
|
19463
|
+
jpeg: "image/jpeg",
|
|
19464
|
+
gif: "image/gif",
|
|
19465
|
+
bmp: "image/bmp",
|
|
19466
|
+
wmf: "image/wmf",
|
|
19467
|
+
emf: "image/emf"
|
|
19468
|
+
};
|
|
19469
|
+
const filename = `image_${String(imgIdx).padStart(3, "0")}.${ext}`;
|
|
19470
|
+
images.push({ filename, data, mimeType: mimeMap[ext] ?? "image/png" });
|
|
19471
|
+
imageMap.set(embedId, filename);
|
|
19472
|
+
} catch (err) {
|
|
19473
|
+
warnings.push({
|
|
19474
|
+
code: "SKIPPED_IMAGE",
|
|
19475
|
+
message: `DOCX \uC774\uBBF8\uC9C0 \uCD94\uCD9C \uC2E4\uD328 (${imgPath}): ${err instanceof Error ? err.message : String(err)}`
|
|
19476
|
+
});
|
|
19521
19477
|
}
|
|
19522
19478
|
}
|
|
19523
|
-
return {
|
|
19479
|
+
return { imageMap, images };
|
|
19480
|
+
}
|
|
19481
|
+
function collectParagraphBlips(node, out = [], depth = 0) {
|
|
19482
|
+
if (depth > 40) return out;
|
|
19483
|
+
for (const el of effectiveChildElements(node)) {
|
|
19484
|
+
if (matchesLocal(el, "txbxContent") || matchesLocal(el, "Fallback")) continue;
|
|
19485
|
+
if (matchesLocal(el, "blip") || matchesLocal(el, "imagedata")) out.push(el);
|
|
19486
|
+
else collectParagraphBlips(el, out, depth + 1);
|
|
19487
|
+
}
|
|
19488
|
+
return out;
|
|
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
|
+
}
|
|
19502
|
+
function emitParagraphImages(p, imageMap, linked, out) {
|
|
19503
|
+
for (const blip of collectParagraphBlips(p)) {
|
|
19504
|
+
const embedId = getAttr(blip, "embed") ?? getAttr(blip, "id");
|
|
19505
|
+
if (!embedId) continue;
|
|
19506
|
+
const filename = imageMap.get(embedId);
|
|
19507
|
+
if (!filename) continue;
|
|
19508
|
+
linked.add(embedId);
|
|
19509
|
+
out.push({ type: "image", text: filename });
|
|
19510
|
+
}
|
|
19524
19511
|
}
|
|
19525
19512
|
async function parseDocxDocument(buffer, options) {
|
|
19526
19513
|
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE4);
|
|
@@ -19577,6 +19564,8 @@ async function parseDocxDocument(buffer, options) {
|
|
|
19577
19564
|
if (body.length === 0) {
|
|
19578
19565
|
throw new KordocError("DOCX \uBCF8\uBB38(w:body)\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
19579
19566
|
}
|
|
19567
|
+
const { imageMap, images } = await buildImageMap(zip, rels, doc, warnings);
|
|
19568
|
+
const linkedImages = /* @__PURE__ */ new Set();
|
|
19580
19569
|
const blocks = [];
|
|
19581
19570
|
const bodyEl = body[0];
|
|
19582
19571
|
const topLevel = effectiveChildElements(bodyEl);
|
|
@@ -19585,16 +19574,20 @@ async function parseDocxDocument(buffer, options) {
|
|
|
19585
19574
|
if (localName2 === "p") {
|
|
19586
19575
|
const block = parseParagraph2(el, styles, numbering, footnotes, rels);
|
|
19587
19576
|
if (block) blocks.push(block);
|
|
19577
|
+
if (imageMap.size > 0) emitParagraphImages(el, imageMap, linkedImages, blocks);
|
|
19588
19578
|
for (const tp of collectTextboxParagraphs(el)) {
|
|
19589
19579
|
const tb = parseParagraph2(tp, styles, numbering, footnotes, rels);
|
|
19590
19580
|
if (tb) blocks.push(tb);
|
|
19581
|
+
if (imageMap.size > 0) emitParagraphImages(tp, imageMap, linkedImages, blocks);
|
|
19591
19582
|
}
|
|
19592
19583
|
} else if (localName2 === "tbl") {
|
|
19593
19584
|
const block = parseTable(el, styles, numbering, footnotes, rels);
|
|
19594
19585
|
if (block) blocks.push(block);
|
|
19595
19586
|
}
|
|
19596
19587
|
}
|
|
19597
|
-
const
|
|
19588
|
+
for (const [embedId, filename] of imageMap) {
|
|
19589
|
+
if (!linkedImages.has(embedId)) blocks.push({ type: "image", text: filename });
|
|
19590
|
+
}
|
|
19598
19591
|
const metadata = {};
|
|
19599
19592
|
const coreFile = zip.file("docProps/core.xml");
|
|
19600
19593
|
if (coreFile) {
|
|
@@ -21201,7 +21194,7 @@ function parseCentralDirectory(buf) {
|
|
|
21201
21194
|
}
|
|
21202
21195
|
return { entries, cdOffset, cdSize, eocdOffset };
|
|
21203
21196
|
}
|
|
21204
|
-
var
|
|
21197
|
+
var CRC_TABLE = (() => {
|
|
21205
21198
|
const table2 = new Uint32Array(256);
|
|
21206
21199
|
for (let n = 0; n < 256; n++) {
|
|
21207
21200
|
let c = n;
|
|
@@ -21210,10 +21203,10 @@ var CRC_TABLE2 = (() => {
|
|
|
21210
21203
|
}
|
|
21211
21204
|
return table2;
|
|
21212
21205
|
})();
|
|
21213
|
-
function
|
|
21206
|
+
function crc32(data) {
|
|
21214
21207
|
let crc = 4294967295;
|
|
21215
21208
|
for (let i = 0; i < data.length; i++) {
|
|
21216
|
-
crc =
|
|
21209
|
+
crc = CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
|
|
21217
21210
|
}
|
|
21218
21211
|
return (crc ^ 4294967295) >>> 0;
|
|
21219
21212
|
}
|
|
@@ -21252,7 +21245,7 @@ function patchZipEntries(original, replacements, additions) {
|
|
|
21252
21245
|
const hview = new DataView(header.buffer, header.byteOffset, header.byteLength);
|
|
21253
21246
|
const method = e.method;
|
|
21254
21247
|
const compData = method === 0 ? newData : new Uint8Array(deflateRawSync(newData));
|
|
21255
|
-
const crc =
|
|
21248
|
+
const crc = crc32(newData);
|
|
21256
21249
|
const flags = e.flags & ~8;
|
|
21257
21250
|
hview.setUint16(6, flags, true);
|
|
21258
21251
|
hview.setUint32(14, crc, true);
|
|
@@ -21270,7 +21263,7 @@ function patchZipEntries(original, replacements, additions) {
|
|
|
21270
21263
|
const deflated = new Uint8Array(deflateRawSync(data));
|
|
21271
21264
|
const method = deflated.length < data.length ? 8 : 0;
|
|
21272
21265
|
const compData = method === 8 ? deflated : data;
|
|
21273
|
-
const crc =
|
|
21266
|
+
const crc = crc32(data);
|
|
21274
21267
|
const header = new Uint8Array(30 + nameBytes.length);
|
|
21275
21268
|
const hv = new DataView(header.buffer);
|
|
21276
21269
|
hv.setUint32(0, LOCAL_SIG, true);
|
|
@@ -26812,16 +26805,16 @@ function buildOrigUnits(blocks) {
|
|
|
26812
26805
|
for (let i = 0; i < blocks.length; i++) {
|
|
26813
26806
|
const block = blocks[i];
|
|
26814
26807
|
let consume = 1;
|
|
26815
|
-
let
|
|
26808
|
+
let chunk;
|
|
26816
26809
|
if (block.type === "paragraph" && block.text && /^\[별표\s*\d+/.test(sanitizeText(block.text))) {
|
|
26817
26810
|
const next = blocks[i + 1];
|
|
26818
26811
|
if (next?.type === "paragraph" && next.text && /관련\)?$/.test(next.text)) consume = 2;
|
|
26819
|
-
|
|
26812
|
+
chunk = blocksToMarkdown(blocks.slice(i, i + consume));
|
|
26820
26813
|
} else {
|
|
26821
|
-
|
|
26814
|
+
chunk = blocksToMarkdown([block]);
|
|
26822
26815
|
}
|
|
26823
|
-
if (
|
|
26824
|
-
const subUnits = splitMarkdownUnits(
|
|
26816
|
+
if (chunk) {
|
|
26817
|
+
const subUnits = splitMarkdownUnits(chunk);
|
|
26825
26818
|
const isFragment = consume === 2 || (block.type === "paragraph" || block.type === "heading") && subUnits.length > 1;
|
|
26826
26819
|
for (let s = 0; s < subUnits.length; s++) {
|
|
26827
26820
|
const u = { ...subUnits[s], blockIdx: i, fragment: isFragment || void 0 };
|
|
@@ -29814,7 +29807,7 @@ async function parseHwp(buffer, options) {
|
|
|
29814
29807
|
async function parsePdf(buffer, options) {
|
|
29815
29808
|
let parsePdfDocument;
|
|
29816
29809
|
try {
|
|
29817
|
-
const mod = await import("./parser-
|
|
29810
|
+
const mod = await import("./parser-KMMUS73Q.js");
|
|
29818
29811
|
parsePdfDocument = mod.parsePdfDocument;
|
|
29819
29812
|
} catch {
|
|
29820
29813
|
return {
|
|
@@ -29825,8 +29818,8 @@ async function parsePdf(buffer, options) {
|
|
|
29825
29818
|
};
|
|
29826
29819
|
}
|
|
29827
29820
|
try {
|
|
29828
|
-
const { markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary } = await parsePdfDocument(buffer, options);
|
|
29829
|
-
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 };
|
|
29830
29823
|
} catch (err) {
|
|
29831
29824
|
const isImageBased = err instanceof Error && "isImageBased" in err ? true : void 0;
|
|
29832
29825
|
return { success: false, fileType: "pdf", error: err instanceof Error ? err.message : "PDF \uD30C\uC2F1 \uC2E4\uD328", code: classifyError(err), isImageBased };
|