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
|
@@ -10,14 +10,15 @@ import {
|
|
|
10
10
|
convertTableToText,
|
|
11
11
|
dedupeRunningHeaders,
|
|
12
12
|
flattenLayoutTables,
|
|
13
|
+
inlineImagesIntoMarkdown,
|
|
13
14
|
mapPuaText
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-UEIYETCQ.js";
|
|
15
16
|
import {
|
|
16
17
|
detectFormat,
|
|
17
18
|
detectOle2Format,
|
|
18
19
|
detectZipFormat,
|
|
19
20
|
parseLenientCfb
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-MEPHGCPQ.js";
|
|
21
22
|
import {
|
|
22
23
|
parsePageRange
|
|
23
24
|
} from "./chunk-MOL7MDBG.js";
|
|
@@ -32,14 +33,14 @@ import {
|
|
|
32
33
|
paraTextPureT,
|
|
33
34
|
patchZipEntries,
|
|
34
35
|
scanSectionXml
|
|
35
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-D35VBACN.js";
|
|
36
37
|
import {
|
|
37
38
|
SPACE_EM_FIXED,
|
|
38
39
|
charWidthEm1000,
|
|
39
40
|
fitRatioForFewerLines,
|
|
40
41
|
measureTextWidth,
|
|
41
42
|
simulateWrap
|
|
42
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-2DZQF6YJ.js";
|
|
43
44
|
import {
|
|
44
45
|
MAX_DECOMPRESS_SIZE,
|
|
45
46
|
MAX_XML_DEPTH,
|
|
@@ -50,7 +51,7 @@ import {
|
|
|
50
51
|
createXmlParser,
|
|
51
52
|
extractTextFromNode,
|
|
52
53
|
findChildByLocalName
|
|
53
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-BNU5QGIZ.js";
|
|
54
55
|
import {
|
|
55
56
|
KordocError,
|
|
56
57
|
classifyError,
|
|
@@ -61,7 +62,7 @@ import {
|
|
|
61
62
|
sanitizeHref,
|
|
62
63
|
stripDtd,
|
|
63
64
|
toArrayBuffer
|
|
64
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-HMUEU7D7.js";
|
|
65
66
|
|
|
66
67
|
// src/index.ts
|
|
67
68
|
import { readFile } from "fs/promises";
|
|
@@ -2199,641 +2200,820 @@ function gongmunDepthFromIndent(para2, left, ctx) {
|
|
|
2199
2200
|
return depth >= 1 && depth < 8 ? depth : null;
|
|
2200
2201
|
}
|
|
2201
2202
|
|
|
2202
|
-
// src/
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2203
|
+
// src/hwp5/record.ts
|
|
2204
|
+
import { inflateRawSync, inflateSync } from "zlib";
|
|
2205
|
+
var TAG_PARA_HEADER = 66;
|
|
2206
|
+
var TAG_PARA_TEXT = 67;
|
|
2207
|
+
var TAG_CHAR_SHAPE = 68;
|
|
2208
|
+
var TAG_CTRL_HEADER = 71;
|
|
2209
|
+
var TAG_LIST_HEADER = 72;
|
|
2210
|
+
var TAG_TABLE = 77;
|
|
2211
|
+
var TAG_SHAPE_COMPONENT = 76;
|
|
2212
|
+
var TAG_SHAPE_COMPONENT_PICTURE = 85;
|
|
2213
|
+
var TAG_SHAPE_COMPONENT_CONTAINER = 86;
|
|
2214
|
+
var TAG_EQEDIT = 88;
|
|
2215
|
+
var TAG_BIN_DATA = 18;
|
|
2216
|
+
var TAG_DOC_CHAR_SHAPE = 21;
|
|
2217
|
+
var TAG_NUMBERING = 23;
|
|
2218
|
+
var TAG_BULLET = 24;
|
|
2219
|
+
var TAG_DOC_PARA_SHAPE = 25;
|
|
2220
|
+
var TAG_DOC_STYLE = 26;
|
|
2221
|
+
var CHAR_LINE = 0;
|
|
2222
|
+
var CHAR_SECTION_BREAK = 10;
|
|
2223
|
+
var CHAR_PARA = 13;
|
|
2224
|
+
var CHAR_TAB = 9;
|
|
2225
|
+
var CHAR_HYPHEN = 30;
|
|
2226
|
+
var CHAR_NBSP = 31;
|
|
2227
|
+
var CHAR_FIXED_NBSP = 24;
|
|
2228
|
+
var CHAR_FIXED_WIDTH = 25;
|
|
2229
|
+
var FLAG_COMPRESSED = 1 << 0;
|
|
2230
|
+
var FLAG_ENCRYPTED = 1 << 1;
|
|
2231
|
+
var FLAG_DISTRIBUTION = 1 << 2;
|
|
2232
|
+
var FLAG_DRM = 1 << 4;
|
|
2233
|
+
var MAX_RECORDS = 5e5;
|
|
2234
|
+
function readRecords(data) {
|
|
2235
|
+
const records = [];
|
|
2236
|
+
let offset = 0;
|
|
2237
|
+
while (offset + 4 <= data.length && records.length < MAX_RECORDS) {
|
|
2238
|
+
const header = data.readUInt32LE(offset);
|
|
2239
|
+
offset += 4;
|
|
2240
|
+
const tagId = header & 1023;
|
|
2241
|
+
const level = header >> 10 & 1023;
|
|
2242
|
+
let size = header >> 20 & 4095;
|
|
2243
|
+
if (size === 4095) {
|
|
2244
|
+
if (offset + 4 > data.length) break;
|
|
2245
|
+
size = data.readUInt32LE(offset);
|
|
2246
|
+
offset += 4;
|
|
2247
|
+
}
|
|
2248
|
+
if (offset + size > data.length) break;
|
|
2249
|
+
records.push({ tagId, level, size, data: data.subarray(offset, offset + size) });
|
|
2250
|
+
offset += size;
|
|
2225
2251
|
}
|
|
2252
|
+
return records;
|
|
2226
2253
|
}
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
if (
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
if (mime.includes("emf")) return "emf";
|
|
2235
|
-
if (mime.includes("svg")) return "svg";
|
|
2236
|
-
return "bin";
|
|
2237
|
-
}
|
|
2238
|
-
function collectImageBlocks(blocks, out, ownerCell, depth = 0) {
|
|
2239
|
-
if (depth > MAX_XML_DEPTH) return;
|
|
2240
|
-
for (const block of blocks) {
|
|
2241
|
-
if (block.type === "image") {
|
|
2242
|
-
out.push({ block, ownerCell });
|
|
2243
|
-
} else if (block.type === "table" && block.table) {
|
|
2244
|
-
for (const row of block.table.cells) {
|
|
2245
|
-
for (const cell2 of row) {
|
|
2246
|
-
if (cell2.blocks?.length) collectImageBlocks(cell2.blocks, out, cell2, depth + 1);
|
|
2247
|
-
}
|
|
2248
|
-
}
|
|
2254
|
+
var MAX_DECOMPRESS_SIZE2 = 100 * 1024 * 1024;
|
|
2255
|
+
function decompressStream(data) {
|
|
2256
|
+
const opts = { maxOutputLength: MAX_DECOMPRESS_SIZE2 };
|
|
2257
|
+
if (data.length >= 2 && data[0] === 120) {
|
|
2258
|
+
try {
|
|
2259
|
+
return inflateSync(data, opts);
|
|
2260
|
+
} catch {
|
|
2249
2261
|
}
|
|
2250
2262
|
}
|
|
2263
|
+
return inflateRawSync(data, opts);
|
|
2251
2264
|
}
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2265
|
+
function parseFileHeader(data) {
|
|
2266
|
+
if (data.length < 40) throw new KordocError("FileHeader\uAC00 \uB108\uBB34 \uC9E7\uC2B5\uB2C8\uB2E4 (\uCD5C\uC18C 40\uBC14\uC774\uD2B8)");
|
|
2267
|
+
const sig = data.subarray(0, 32).toString("utf8").replace(/\0+$/, "");
|
|
2268
|
+
return {
|
|
2269
|
+
signature: sig,
|
|
2270
|
+
versionMajor: data[35],
|
|
2271
|
+
flags: data.readUInt32LE(36)
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2274
|
+
function readHwpString(data, offset) {
|
|
2275
|
+
if (offset + 2 > data.length) return { value: "", next: data.length };
|
|
2276
|
+
const len = data.readUInt16LE(offset);
|
|
2277
|
+
const start = offset + 2;
|
|
2278
|
+
const end = start + len * 2;
|
|
2279
|
+
if (len === 0 || end > data.length) return { value: "", next: start };
|
|
2280
|
+
return { value: data.subarray(start, end).toString("utf16le"), next: end };
|
|
2281
|
+
}
|
|
2282
|
+
function parseDocInfo(records) {
|
|
2283
|
+
const charShapes = [];
|
|
2284
|
+
const paraShapes = [];
|
|
2285
|
+
const styles = [];
|
|
2286
|
+
const binData = [];
|
|
2287
|
+
const numberings = [];
|
|
2288
|
+
const bullets = [];
|
|
2289
|
+
for (const rec of records) {
|
|
2290
|
+
if (rec.tagId === TAG_DOC_PARA_SHAPE && rec.data.length >= 4) {
|
|
2291
|
+
const attr1 = rec.data.readUInt32LE(0);
|
|
2292
|
+
const headType = attr1 >>> 23 & 3;
|
|
2293
|
+
const paraLevel = attr1 >>> 25 & 7;
|
|
2294
|
+
const numberingId = rec.data.length >= 32 ? rec.data.readUInt16LE(30) : 0;
|
|
2295
|
+
paraShapes.push({ headType, paraLevel, numberingId });
|
|
2296
|
+
}
|
|
2297
|
+
if (rec.tagId === TAG_BIN_DATA && rec.data.length >= 2) {
|
|
2298
|
+
const attr = rec.data.readUInt16LE(0);
|
|
2299
|
+
const typeBits = attr & 15;
|
|
2300
|
+
if (typeBits === 0) {
|
|
2301
|
+
binData.push({ kind: "link", storageId: 0, extension: "" });
|
|
2302
|
+
} else {
|
|
2303
|
+
const storageId = rec.data.length >= 4 ? rec.data.readUInt16LE(2) : 0;
|
|
2304
|
+
const { value: extension } = readHwpString(rec.data, 4);
|
|
2305
|
+
binData.push({ kind: typeBits === 2 ? "storage" : "embed", storageId, extension });
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
if (rec.tagId === TAG_NUMBERING && rec.data.length >= 14) {
|
|
2309
|
+
const levelFormats = [];
|
|
2310
|
+
const numberFormats = [];
|
|
2311
|
+
const startNumbers = [1, 1, 1, 1, 1, 1, 1];
|
|
2312
|
+
let offset = 0;
|
|
2313
|
+
for (let level = 0; level < 7; level++) {
|
|
2314
|
+
if (offset + 12 > rec.data.length) {
|
|
2315
|
+
levelFormats.push("");
|
|
2316
|
+
numberFormats.push(0);
|
|
2317
|
+
continue;
|
|
2279
2318
|
}
|
|
2319
|
+
const attr = rec.data.readUInt32LE(offset);
|
|
2320
|
+
numberFormats.push(attr >>> 5 & 15);
|
|
2321
|
+
offset += 12;
|
|
2322
|
+
const { value, next } = readHwpString(rec.data, offset);
|
|
2323
|
+
levelFormats.push(value);
|
|
2324
|
+
offset = next;
|
|
2280
2325
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
imageIndex++;
|
|
2293
|
-
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
2294
|
-
img = { filename, data, mimeType };
|
|
2295
|
-
images.push(img);
|
|
2296
|
-
break;
|
|
2297
|
-
} catch (err) {
|
|
2298
|
-
if (err instanceof KordocError) throw err;
|
|
2326
|
+
let baseStart = 1;
|
|
2327
|
+
if (offset + 2 <= rec.data.length) {
|
|
2328
|
+
baseStart = rec.data.readUInt16LE(offset) || 1;
|
|
2329
|
+
offset += 2;
|
|
2330
|
+
}
|
|
2331
|
+
for (let level = 0; level < 7; level++) {
|
|
2332
|
+
if (offset + 4 <= rec.data.length) {
|
|
2333
|
+
startNumbers[level] = rec.data.readUInt32LE(offset) || 1;
|
|
2334
|
+
offset += 4;
|
|
2335
|
+
} else {
|
|
2336
|
+
startNumbers[level] = baseStart;
|
|
2299
2337
|
}
|
|
2300
2338
|
}
|
|
2301
|
-
|
|
2302
|
-
resolved.set(ref, img);
|
|
2339
|
+
numberings.push({ levelFormats, numberFormats, startNumbers });
|
|
2303
2340
|
}
|
|
2304
|
-
if (
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
2308
|
-
continue;
|
|
2341
|
+
if (rec.tagId === TAG_BULLET && rec.data.length >= 14) {
|
|
2342
|
+
const code = rec.data.readUInt16LE(12);
|
|
2343
|
+
bullets.push({ char: code > 0 ? String.fromCharCode(code) : "\u2022" });
|
|
2309
2344
|
}
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
}
|
|
2316
|
-
|
|
2317
|
-
// src/hwpx/metadata.ts
|
|
2318
|
-
import JSZip from "jszip";
|
|
2319
|
-
|
|
2320
|
-
// src/hwpx/zip-sections.ts
|
|
2321
|
-
import { inflateRawSync } from "zlib";
|
|
2322
|
-
function extractFromBrokenZip(buffer) {
|
|
2323
|
-
const data = new Uint8Array(buffer);
|
|
2324
|
-
const view = new DataView(buffer);
|
|
2325
|
-
let pos = 0;
|
|
2326
|
-
const blocks = [];
|
|
2327
|
-
const warnings = [
|
|
2328
|
-
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
2329
|
-
];
|
|
2330
|
-
let totalDecompressed = 0;
|
|
2331
|
-
let entryCount = 0;
|
|
2332
|
-
let sectionNum = 0;
|
|
2333
|
-
const shared = createSectionShared();
|
|
2334
|
-
while (pos < data.length - 30) {
|
|
2335
|
-
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
2336
|
-
pos++;
|
|
2337
|
-
while (pos < data.length - 30) {
|
|
2338
|
-
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
2339
|
-
pos++;
|
|
2345
|
+
if (rec.tagId === TAG_DOC_CHAR_SHAPE && rec.data.length >= 18) {
|
|
2346
|
+
if (rec.data.length >= 50) {
|
|
2347
|
+
const fontSize = rec.data.readUInt32LE(42);
|
|
2348
|
+
const attrFlags = rec.data.readUInt32LE(46);
|
|
2349
|
+
charShapes.push({ fontSize, attrFlags });
|
|
2350
|
+
} else {
|
|
2351
|
+
charShapes.push({ fontSize: 0, attrFlags: 0 });
|
|
2340
2352
|
}
|
|
2341
|
-
continue;
|
|
2342
|
-
}
|
|
2343
|
-
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
2344
|
-
const method = view.getUint16(pos + 8, true);
|
|
2345
|
-
const compSize = view.getUint32(pos + 18, true);
|
|
2346
|
-
const nameLen = view.getUint16(pos + 26, true);
|
|
2347
|
-
const extraLen = view.getUint16(pos + 28, true);
|
|
2348
|
-
if (nameLen > 1024 || extraLen > 65535) {
|
|
2349
|
-
pos += 30 + nameLen + extraLen;
|
|
2350
|
-
continue;
|
|
2351
|
-
}
|
|
2352
|
-
const fileStart = pos + 30 + nameLen + extraLen;
|
|
2353
|
-
if (fileStart + compSize > data.length) break;
|
|
2354
|
-
if (compSize === 0 && method !== 0) {
|
|
2355
|
-
pos = fileStart;
|
|
2356
|
-
continue;
|
|
2357
|
-
}
|
|
2358
|
-
const nameBytes = data.slice(pos + 30, pos + 30 + nameLen);
|
|
2359
|
-
const name = new TextDecoder().decode(nameBytes);
|
|
2360
|
-
if (isPathTraversal(name)) {
|
|
2361
|
-
pos = fileStart + compSize;
|
|
2362
|
-
continue;
|
|
2363
2353
|
}
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2354
|
+
if (rec.tagId === TAG_DOC_STYLE && rec.data.length >= 8) {
|
|
2355
|
+
try {
|
|
2356
|
+
let offset = 0;
|
|
2357
|
+
const nameLen = rec.data.readUInt16LE(offset);
|
|
2358
|
+
offset += 2;
|
|
2359
|
+
const nameBytes = nameLen * 2;
|
|
2360
|
+
const name = nameBytes > 0 && offset + nameBytes <= rec.data.length ? rec.data.subarray(offset, offset + nameBytes).toString("utf16le") : "";
|
|
2361
|
+
offset += nameBytes;
|
|
2362
|
+
let nameKo = "";
|
|
2363
|
+
if (offset + 2 <= rec.data.length) {
|
|
2364
|
+
const nameKoLen = rec.data.readUInt16LE(offset);
|
|
2365
|
+
offset += 2;
|
|
2366
|
+
const nameKoBytes = nameKoLen * 2;
|
|
2367
|
+
if (nameKoBytes > 0 && offset + nameKoBytes <= rec.data.length) {
|
|
2368
|
+
nameKo = rec.data.subarray(offset, offset + nameKoBytes).toString("utf16le");
|
|
2369
|
+
}
|
|
2370
|
+
offset += nameKoBytes;
|
|
2371
|
+
}
|
|
2372
|
+
const type = offset < rec.data.length ? rec.data.readUInt8(offset) : 0;
|
|
2373
|
+
offset += 1;
|
|
2374
|
+
offset += 1;
|
|
2375
|
+
offset += 2;
|
|
2376
|
+
const paraShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
2377
|
+
offset += 2;
|
|
2378
|
+
const charShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
2379
|
+
styles.push({ name, nameKo, charShapeId, paraShapeId, type });
|
|
2380
|
+
} catch {
|
|
2376
2381
|
}
|
|
2377
|
-
totalDecompressed += content.length * 2;
|
|
2378
|
-
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
2379
|
-
sectionNum++;
|
|
2380
|
-
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
2381
|
-
} catch {
|
|
2382
|
-
continue;
|
|
2383
2382
|
}
|
|
2384
2383
|
}
|
|
2385
|
-
|
|
2386
|
-
applyPageText(blocks, shared);
|
|
2387
|
-
const markdown = blocksToMarkdown(blocks);
|
|
2388
|
-
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
2384
|
+
return { charShapes, paraShapes, styles, binData, numberings, bullets };
|
|
2389
2385
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
if (!file) return null;
|
|
2393
|
-
try {
|
|
2394
|
-
const xml = await file.async("text");
|
|
2395
|
-
if (!/<opf:meta\b[^>]*name="generator"[^>]*content="kordoc"/.test(xml)) return null;
|
|
2396
|
-
return xml.match(/<opf:meta\b[^>]*name="kordoc-layout"[^>]*content="([^"]*)"/)?.[1] ?? null;
|
|
2397
|
-
} catch {
|
|
2398
|
-
return null;
|
|
2399
|
-
}
|
|
2386
|
+
function createParaTextState() {
|
|
2387
|
+
return { text: "", ctrlIdx: 0, fieldStack: [], fieldRanges: [] };
|
|
2400
2388
|
}
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
for (const mp of manifestPaths) {
|
|
2404
|
-
const mpLower = mp.toLowerCase();
|
|
2405
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
2406
|
-
if (!file) continue;
|
|
2407
|
-
const xml = await file.async("text");
|
|
2408
|
-
const paths = parseSectionPathsFromManifest(xml);
|
|
2409
|
-
if (paths.length > 0) return paths;
|
|
2410
|
-
}
|
|
2411
|
-
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
2412
|
-
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
2389
|
+
function isExtendedOnlyCtrlChar(ch) {
|
|
2390
|
+
return ch >= 1 && ch <= 3 || ch >= 11 && ch <= 12 || ch >= 14 && ch <= 18 || ch >= 21 && ch <= 23;
|
|
2413
2391
|
}
|
|
2414
|
-
function
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
const
|
|
2418
|
-
const
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
const
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2392
|
+
function appendParaText(state, data, resolveControl) {
|
|
2393
|
+
let result = "";
|
|
2394
|
+
let i = 0;
|
|
2395
|
+
const base = state.text.length;
|
|
2396
|
+
const resolveAt = (byteOffset, extended) => {
|
|
2397
|
+
const ctrlId = data.readUInt32LE(byteOffset);
|
|
2398
|
+
const idx = extended ? state.ctrlIdx : -1;
|
|
2399
|
+
const replacement = resolveControl?.(idx, ctrlId);
|
|
2400
|
+
if (replacement) result += replacement;
|
|
2401
|
+
if (extended) state.ctrlIdx++;
|
|
2402
|
+
};
|
|
2403
|
+
while (i + 1 < data.length) {
|
|
2404
|
+
const ch = data.readUInt16LE(i);
|
|
2405
|
+
i += 2;
|
|
2406
|
+
switch (ch) {
|
|
2407
|
+
// ── char 타입 (2바이트만, 확장 데이터 없음) ──
|
|
2408
|
+
case CHAR_LINE:
|
|
2409
|
+
result += "\n";
|
|
2410
|
+
break;
|
|
2411
|
+
case CHAR_SECTION_BREAK: {
|
|
2412
|
+
if (i + 16 <= data.length && data.readUInt16LE(i) === 11) {
|
|
2413
|
+
resolveAt(i + 2, true);
|
|
2414
|
+
i += 16;
|
|
2415
|
+
break;
|
|
2416
|
+
}
|
|
2417
|
+
result += "\n";
|
|
2418
|
+
break;
|
|
2419
|
+
}
|
|
2420
|
+
case CHAR_PARA:
|
|
2421
|
+
break;
|
|
2422
|
+
// 문단 끝
|
|
2423
|
+
case CHAR_HYPHEN:
|
|
2424
|
+
result += "-";
|
|
2425
|
+
break;
|
|
2426
|
+
case CHAR_NBSP:
|
|
2427
|
+
result += " ";
|
|
2428
|
+
break;
|
|
2429
|
+
case CHAR_FIXED_NBSP:
|
|
2430
|
+
result += "\xA0";
|
|
2431
|
+
break;
|
|
2432
|
+
// 진짜 NBSP
|
|
2433
|
+
case CHAR_FIXED_WIDTH:
|
|
2434
|
+
result += " ";
|
|
2435
|
+
break;
|
|
2436
|
+
// 고정폭 공백
|
|
2437
|
+
// ── inline 타입 (2바이트 + 14바이트 확장) ──
|
|
2438
|
+
case CHAR_TAB:
|
|
2439
|
+
result += " ";
|
|
2440
|
+
if (i + 14 <= data.length) i += 14;
|
|
2441
|
+
break;
|
|
2442
|
+
default:
|
|
2443
|
+
if (ch >= 1 && ch <= 31) {
|
|
2444
|
+
const isExtended = isExtendedOnlyCtrlChar(ch);
|
|
2445
|
+
const isInline = ch >= 4 && ch <= 9 || ch >= 19 && ch <= 20;
|
|
2446
|
+
if ((isExtended || isInline) && i + 14 <= data.length) {
|
|
2447
|
+
if (ch === 3) {
|
|
2448
|
+
state.fieldStack.push({ start: base + result.length, ctrlIdx: state.ctrlIdx });
|
|
2449
|
+
} else if (ch === 4) {
|
|
2450
|
+
const open = state.fieldStack.pop();
|
|
2451
|
+
if (open) {
|
|
2452
|
+
state.fieldRanges.push({ start: open.start, end: base + result.length, ctrlIdx: open.ctrlIdx });
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
resolveAt(i, isExtended);
|
|
2456
|
+
i += 14;
|
|
2457
|
+
}
|
|
2458
|
+
} else if (ch >= 32) {
|
|
2459
|
+
if (ch >= 55296 && ch <= 56319 && i + 1 < data.length) {
|
|
2460
|
+
const lo = data.readUInt16LE(i);
|
|
2461
|
+
if (lo >= 56320 && lo <= 57343) {
|
|
2462
|
+
i += 2;
|
|
2463
|
+
const codePoint = (ch - 55296 << 10) + (lo - 56320) + 65536;
|
|
2464
|
+
result += String.fromCodePoint(codePoint);
|
|
2465
|
+
break;
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
result += String.fromCharCode(ch);
|
|
2469
|
+
}
|
|
2470
|
+
break;
|
|
2431
2471
|
}
|
|
2432
|
-
if (ordered.length > 0) return ordered;
|
|
2433
2472
|
}
|
|
2434
|
-
|
|
2473
|
+
state.text += result;
|
|
2474
|
+
}
|
|
2475
|
+
function extractEquationText(data) {
|
|
2476
|
+
if (data.length < 6) return null;
|
|
2477
|
+
const scriptLength = data.readUInt16LE(4);
|
|
2478
|
+
const scriptStart = 6;
|
|
2479
|
+
const scriptEnd = scriptStart + scriptLength * 2;
|
|
2480
|
+
if (scriptLength <= 0 || scriptEnd > data.length) return null;
|
|
2481
|
+
const equation = data.subarray(scriptStart, scriptEnd).toString("utf16le").replace(/\0+/g, "").trim();
|
|
2482
|
+
return equation || null;
|
|
2435
2483
|
}
|
|
2436
2484
|
|
|
2437
|
-
// src/
|
|
2438
|
-
|
|
2485
|
+
// src/hwp5/images.ts
|
|
2486
|
+
function detectImageMime(data) {
|
|
2487
|
+
if (data.length < 4) return null;
|
|
2488
|
+
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return "image/png";
|
|
2489
|
+
if (data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
|
|
2490
|
+
if (data[0] === 71 && data[1] === 73 && data[2] === 70) return "image/gif";
|
|
2491
|
+
if (data[0] === 66 && data[1] === 77) return "image/bmp";
|
|
2492
|
+
if (data[0] === 215 && data[1] === 205 && data[2] === 198 && data[3] === 154) return "image/wmf";
|
|
2493
|
+
if (data[0] === 1 && data[1] === 0 && data[2] === 0 && data[3] === 0) return "image/emf";
|
|
2494
|
+
return null;
|
|
2495
|
+
}
|
|
2496
|
+
function normalizeBinPayload(data) {
|
|
2497
|
+
if (detectImageMime(data)) return data;
|
|
2439
2498
|
try {
|
|
2440
|
-
const
|
|
2441
|
-
|
|
2442
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
2443
|
-
if (!file) continue;
|
|
2444
|
-
const xml = await file.async("text");
|
|
2445
|
-
if (decompressed) {
|
|
2446
|
-
decompressed.total += xml.length * 2;
|
|
2447
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2448
|
-
}
|
|
2449
|
-
parseDublinCoreMetadata(xml, metadata);
|
|
2450
|
-
if (metadata.title || metadata.author) return;
|
|
2451
|
-
}
|
|
2499
|
+
const inflated = decompressStream(data);
|
|
2500
|
+
if (inflated.length > 0) return inflated;
|
|
2452
2501
|
} catch {
|
|
2453
2502
|
}
|
|
2503
|
+
return data;
|
|
2454
2504
|
}
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
const
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
if (text) return text;
|
|
2505
|
+
var BIN_ENTRY_RE = /(?:^|\/)BIN([0-9A-Fa-f]{4,8})(?:\.[^./\\]*)?$/;
|
|
2506
|
+
function collectImageBlocks(blocks, out) {
|
|
2507
|
+
for (const b of blocks) {
|
|
2508
|
+
if (b.type === "image") out.push(b);
|
|
2509
|
+
if (b.table) {
|
|
2510
|
+
for (const row of b.table.cells) {
|
|
2511
|
+
for (const cell2 of row) {
|
|
2512
|
+
if (cell2.blocks) collectImageBlocks(cell2.blocks, out);
|
|
2513
|
+
}
|
|
2465
2514
|
}
|
|
2466
2515
|
}
|
|
2467
|
-
|
|
2468
|
-
};
|
|
2469
|
-
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
2470
|
-
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
2471
|
-
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
2472
|
-
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
2473
|
-
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
2474
|
-
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
2475
|
-
if (keywords && !metadata.keywords) {
|
|
2476
|
-
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
2516
|
+
if (b.children) collectImageBlocks(b.children, out);
|
|
2477
2517
|
}
|
|
2478
2518
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2519
|
+
function forEachTableCell(blocks, fn) {
|
|
2520
|
+
for (const b of blocks) {
|
|
2521
|
+
if (b.table) {
|
|
2522
|
+
for (const row of b.table.cells) {
|
|
2523
|
+
for (const cell2 of row) {
|
|
2524
|
+
fn(cell2);
|
|
2525
|
+
if (cell2.blocks) forEachTableCell(cell2.blocks, fn);
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
if (b.children) forEachTableCell(b.children, fn);
|
|
2485
2530
|
}
|
|
2486
|
-
const metadata = {};
|
|
2487
|
-
await extractHwpxMetadata(zip, metadata);
|
|
2488
|
-
const sectionPaths = await resolveSectionPaths(zip);
|
|
2489
|
-
metadata.pageCount = sectionPaths.length;
|
|
2490
|
-
return metadata;
|
|
2491
2531
|
}
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
const
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2532
|
+
var CELL_IMAGE_SENTINEL_RE = /!\[image\]\(hwp5bin:(\d+)\)/g;
|
|
2533
|
+
function resolveCellImageSentinels(blocks, renamed) {
|
|
2534
|
+
forEachTableCell(blocks, (cell2) => {
|
|
2535
|
+
if (!cell2.text.includes("hwp5bin:")) return;
|
|
2536
|
+
cell2.text = cell2.text.replace(CELL_IMAGE_SENTINEL_RE, (_m, idStr) => {
|
|
2537
|
+
const filename = renamed.get(Number(idStr));
|
|
2538
|
+
return filename ? `` : "[\uC774\uBBF8\uC9C0]";
|
|
2539
|
+
});
|
|
2540
|
+
});
|
|
2541
|
+
}
|
|
2542
|
+
function resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced) {
|
|
2543
|
+
const imageBlocks = [];
|
|
2544
|
+
collectImageBlocks(blocks, imageBlocks);
|
|
2545
|
+
const images = [];
|
|
2546
|
+
const renamed = /* @__PURE__ */ new Map();
|
|
2547
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
2548
|
+
let imageIndex = 0;
|
|
2549
|
+
for (const block of imageBlocks) {
|
|
2550
|
+
if (!block.text) continue;
|
|
2551
|
+
const storageId = parseInt(block.text, 10);
|
|
2552
|
+
if (isNaN(storageId)) continue;
|
|
2553
|
+
let img = resolved.get(storageId);
|
|
2554
|
+
if (img === void 0) {
|
|
2555
|
+
const bin = binDataMap.get(storageId);
|
|
2556
|
+
if (!bin) {
|
|
2557
|
+
warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
|
|
2558
|
+
resolved.set(storageId, null);
|
|
2559
|
+
} else {
|
|
2560
|
+
const mime = detectImageMime(bin.data);
|
|
2561
|
+
if (!mime) {
|
|
2562
|
+
warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
|
|
2563
|
+
resolved.set(storageId, null);
|
|
2564
|
+
} else {
|
|
2565
|
+
imageIndex++;
|
|
2566
|
+
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
2567
|
+
img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
|
|
2568
|
+
resolved.set(storageId, img);
|
|
2569
|
+
images.push({ filename: img.filename, data: img.data, mimeType: img.mime });
|
|
2570
|
+
renamed.set(storageId, img.filename);
|
|
2514
2571
|
}
|
|
2515
2572
|
}
|
|
2516
|
-
|
|
2573
|
+
img = resolved.get(storageId);
|
|
2574
|
+
}
|
|
2575
|
+
if (!img) {
|
|
2576
|
+
const bin = binDataMap.get(storageId);
|
|
2577
|
+
block.type = "paragraph";
|
|
2578
|
+
block.text = bin ? `[\uC774\uBBF8\uC9C0: ${bin.name}]` : `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
|
|
2579
|
+
continue;
|
|
2517
2580
|
}
|
|
2581
|
+
block.text = img.filename;
|
|
2582
|
+
block.imageData = { data: img.data, mimeType: img.mime, filename: binDataMap.get(storageId).name };
|
|
2518
2583
|
}
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
const shared = createSectionShared();
|
|
2531
|
-
shared.kordocLayout = await readKordocLayout(zip);
|
|
2532
|
-
let parsedSections = 0;
|
|
2533
|
-
for (let si = 0; si < sectionPaths.length; si++) {
|
|
2534
|
-
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
2535
|
-
const file = zip.file(sectionPaths[si]);
|
|
2536
|
-
if (!file) continue;
|
|
2537
|
-
try {
|
|
2538
|
-
const xml = await file.async("text");
|
|
2539
|
-
decompressed.total += xml.length * 2;
|
|
2540
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2541
|
-
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
2542
|
-
parsedSections++;
|
|
2543
|
-
options?.onProgress?.(parsedSections, totalTarget);
|
|
2544
|
-
} catch (secErr) {
|
|
2545
|
-
if (secErr instanceof KordocError) throw secErr;
|
|
2546
|
-
warnings.push({ page: si + 1, message: `\uC139\uC158 ${si + 1} \uD30C\uC2F1 \uC2E4\uD328: ${secErr instanceof Error ? secErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "PARTIAL_PARSE" });
|
|
2584
|
+
if (sweepUnreferenced) {
|
|
2585
|
+
for (const [storageId, bin] of [...binDataMap.entries()].sort((a, b) => a[0] - b[0])) {
|
|
2586
|
+
if (resolved.has(storageId)) continue;
|
|
2587
|
+
const mime = detectImageMime(bin.data);
|
|
2588
|
+
if (!mime) continue;
|
|
2589
|
+
imageIndex++;
|
|
2590
|
+
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
2591
|
+
const filename = `image_${String(imageIndex).padStart(3, "0")}.${ext}`;
|
|
2592
|
+
const data = new Uint8Array(bin.data);
|
|
2593
|
+
images.push({ filename, data, mimeType: mime });
|
|
2594
|
+
blocks.push({ type: "image", text: filename, imageData: { data, mimeType: mime, filename: bin.name } });
|
|
2547
2595
|
}
|
|
2548
2596
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
detectHwpxHeadings(blocks, styleMap);
|
|
2552
|
-
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
2553
|
-
const markdown = blocksToMarkdown(blocks);
|
|
2554
|
-
return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0, images: images.length > 0 ? images : void 0 };
|
|
2597
|
+
resolveCellImageSentinels(blocks, renamed);
|
|
2598
|
+
return images;
|
|
2555
2599
|
}
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
var TAG_SHAPE_COMPONENT_PICTURE = 85;
|
|
2567
|
-
var TAG_SHAPE_COMPONENT_CONTAINER = 86;
|
|
2568
|
-
var TAG_EQEDIT = 88;
|
|
2569
|
-
var TAG_BIN_DATA = 18;
|
|
2570
|
-
var TAG_DOC_CHAR_SHAPE = 21;
|
|
2571
|
-
var TAG_NUMBERING = 23;
|
|
2572
|
-
var TAG_BULLET = 24;
|
|
2573
|
-
var TAG_DOC_PARA_SHAPE = 25;
|
|
2574
|
-
var TAG_DOC_STYLE = 26;
|
|
2575
|
-
var CHAR_LINE = 0;
|
|
2576
|
-
var CHAR_SECTION_BREAK = 10;
|
|
2577
|
-
var CHAR_PARA = 13;
|
|
2578
|
-
var CHAR_TAB = 9;
|
|
2579
|
-
var CHAR_HYPHEN = 30;
|
|
2580
|
-
var CHAR_NBSP = 31;
|
|
2581
|
-
var CHAR_FIXED_NBSP = 24;
|
|
2582
|
-
var CHAR_FIXED_WIDTH = 25;
|
|
2583
|
-
var FLAG_COMPRESSED = 1 << 0;
|
|
2584
|
-
var FLAG_ENCRYPTED = 1 << 1;
|
|
2585
|
-
var FLAG_DISTRIBUTION = 1 << 2;
|
|
2586
|
-
var FLAG_DRM = 1 << 4;
|
|
2587
|
-
var MAX_RECORDS = 5e5;
|
|
2588
|
-
function readRecords(data) {
|
|
2589
|
-
const records = [];
|
|
2590
|
-
let offset = 0;
|
|
2591
|
-
while (offset + 4 <= data.length && records.length < MAX_RECORDS) {
|
|
2592
|
-
const header = data.readUInt32LE(offset);
|
|
2593
|
-
offset += 4;
|
|
2594
|
-
const tagId = header & 1023;
|
|
2595
|
-
const level = header >> 10 & 1023;
|
|
2596
|
-
let size = header >> 20 & 4095;
|
|
2597
|
-
if (size === 4095) {
|
|
2598
|
-
if (offset + 4 > data.length) break;
|
|
2599
|
-
size = data.readUInt32LE(offset);
|
|
2600
|
-
offset += 4;
|
|
2600
|
+
function extractHwp5Images(fileIndex, blocks, warnings, sweepUnreferenced) {
|
|
2601
|
+
const binDataMap = /* @__PURE__ */ new Map();
|
|
2602
|
+
if (fileIndex) {
|
|
2603
|
+
for (const entry of fileIndex) {
|
|
2604
|
+
if (!entry?.name || !entry.content) continue;
|
|
2605
|
+
const match = entry.name.match(BIN_ENTRY_RE);
|
|
2606
|
+
if (!match) continue;
|
|
2607
|
+
const idx = parseInt(match[1], 16);
|
|
2608
|
+
const data = normalizeBinPayload(Buffer.from(entry.content));
|
|
2609
|
+
binDataMap.set(idx, { data, name: entry.name });
|
|
2601
2610
|
}
|
|
2602
|
-
if (offset + size > data.length) break;
|
|
2603
|
-
records.push({ tagId, level, size, data: data.subarray(offset, offset + size) });
|
|
2604
|
-
offset += size;
|
|
2605
2611
|
}
|
|
2606
|
-
|
|
2612
|
+
if (binDataMap.size === 0) {
|
|
2613
|
+
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
2614
|
+
return [];
|
|
2615
|
+
}
|
|
2616
|
+
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
2607
2617
|
}
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
const
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2618
|
+
function extractHwp5ImagesLenient(lcfb, blocks, warnings, sweepUnreferenced) {
|
|
2619
|
+
const binDataMap = /* @__PURE__ */ new Map();
|
|
2620
|
+
const binRe = /^BIN([0-9A-Fa-f]{4,8})(?:\.|$)/;
|
|
2621
|
+
for (const e of lcfb.entries()) {
|
|
2622
|
+
const match = e.name.match(binRe);
|
|
2623
|
+
if (!match) continue;
|
|
2624
|
+
const idx = parseInt(match[1], 16);
|
|
2625
|
+
const raw = lcfb.findStream(e.name);
|
|
2626
|
+
if (!raw) continue;
|
|
2627
|
+
binDataMap.set(idx, { data: normalizeBinPayload(raw), name: e.name });
|
|
2628
|
+
}
|
|
2629
|
+
if (binDataMap.size === 0) {
|
|
2630
|
+
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
2631
|
+
return [];
|
|
2616
2632
|
}
|
|
2617
|
-
return
|
|
2633
|
+
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
2618
2634
|
}
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2635
|
+
|
|
2636
|
+
// src/hwpx/images.ts
|
|
2637
|
+
function imageExtToMime(ext) {
|
|
2638
|
+
switch (ext.toLowerCase()) {
|
|
2639
|
+
case "jpg":
|
|
2640
|
+
case "jpeg":
|
|
2641
|
+
return "image/jpeg";
|
|
2642
|
+
case "png":
|
|
2643
|
+
return "image/png";
|
|
2644
|
+
case "gif":
|
|
2645
|
+
return "image/gif";
|
|
2646
|
+
case "bmp":
|
|
2647
|
+
return "image/bmp";
|
|
2648
|
+
case "tif":
|
|
2649
|
+
case "tiff":
|
|
2650
|
+
return "image/tiff";
|
|
2651
|
+
case "wmf":
|
|
2652
|
+
return "image/wmf";
|
|
2653
|
+
case "emf":
|
|
2654
|
+
return "image/emf";
|
|
2655
|
+
case "svg":
|
|
2656
|
+
return "image/svg+xml";
|
|
2657
|
+
default:
|
|
2658
|
+
return "application/octet-stream";
|
|
2659
|
+
}
|
|
2627
2660
|
}
|
|
2628
|
-
function
|
|
2629
|
-
if (
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
if (
|
|
2634
|
-
|
|
2661
|
+
function mimeToExt(mime) {
|
|
2662
|
+
if (mime.includes("jpeg")) return "jpg";
|
|
2663
|
+
if (mime.includes("png")) return "png";
|
|
2664
|
+
if (mime.includes("gif")) return "gif";
|
|
2665
|
+
if (mime.includes("bmp")) return "bmp";
|
|
2666
|
+
if (mime.includes("tiff")) return "tif";
|
|
2667
|
+
if (mime.includes("wmf")) return "wmf";
|
|
2668
|
+
if (mime.includes("emf")) return "emf";
|
|
2669
|
+
if (mime.includes("svg")) return "svg";
|
|
2670
|
+
return "bin";
|
|
2635
2671
|
}
|
|
2636
|
-
function
|
|
2637
|
-
|
|
2638
|
-
const
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
const headType = attr1 >>> 23 & 3;
|
|
2647
|
-
const paraLevel = attr1 >>> 25 & 7;
|
|
2648
|
-
const numberingId = rec.data.length >= 32 ? rec.data.readUInt16LE(30) : 0;
|
|
2649
|
-
paraShapes.push({ headType, paraLevel, numberingId });
|
|
2650
|
-
}
|
|
2651
|
-
if (rec.tagId === TAG_BIN_DATA && rec.data.length >= 2) {
|
|
2652
|
-
const attr = rec.data.readUInt16LE(0);
|
|
2653
|
-
const typeBits = attr & 15;
|
|
2654
|
-
if (typeBits === 0) {
|
|
2655
|
-
binData.push({ kind: "link", storageId: 0, extension: "" });
|
|
2656
|
-
} else {
|
|
2657
|
-
const storageId = rec.data.length >= 4 ? rec.data.readUInt16LE(2) : 0;
|
|
2658
|
-
const { value: extension } = readHwpString(rec.data, 4);
|
|
2659
|
-
binData.push({ kind: typeBits === 2 ? "storage" : "embed", storageId, extension });
|
|
2672
|
+
function collectImageBlocks2(blocks, out, ownerCell, depth = 0) {
|
|
2673
|
+
if (depth > MAX_XML_DEPTH) return;
|
|
2674
|
+
for (const block of blocks) {
|
|
2675
|
+
if (block.type === "image") {
|
|
2676
|
+
out.push({ block, ownerCell });
|
|
2677
|
+
} else if (block.type === "table" && block.table) {
|
|
2678
|
+
for (const row of block.table.cells) {
|
|
2679
|
+
for (const cell2 of row) {
|
|
2680
|
+
if (cell2.blocks?.length) collectImageBlocks2(cell2.blocks, out, cell2, depth + 1);
|
|
2681
|
+
}
|
|
2660
2682
|
}
|
|
2661
2683
|
}
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
async function extractImagesFromZip(zip, blocks, decompressed, warnings, sweepUnreferenced) {
|
|
2687
|
+
const images = [];
|
|
2688
|
+
let imageIndex = 0;
|
|
2689
|
+
const usedPaths = /* @__PURE__ */ new Set();
|
|
2690
|
+
const imageBlocks = [];
|
|
2691
|
+
collectImageBlocks2(blocks, imageBlocks);
|
|
2692
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
2693
|
+
for (const { block, ownerCell } of imageBlocks) {
|
|
2694
|
+
if (block.type !== "image" || !block.text) continue;
|
|
2695
|
+
const ref = block.text;
|
|
2696
|
+
let img = resolved.get(ref);
|
|
2697
|
+
if (img === void 0) {
|
|
2698
|
+
img = null;
|
|
2699
|
+
const candidates = [
|
|
2700
|
+
`BinData/${ref}`,
|
|
2701
|
+
`Contents/BinData/${ref}`,
|
|
2702
|
+
ref
|
|
2703
|
+
// 절대 경로일 수도 있음
|
|
2704
|
+
];
|
|
2705
|
+
let resolvedPath = null;
|
|
2706
|
+
if (!ref.includes(".")) {
|
|
2707
|
+
const prefixes = [`BinData/${ref}`, `Contents/BinData/${ref}`];
|
|
2708
|
+
for (const prefix of prefixes) {
|
|
2709
|
+
const match = zip.file(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.[a-zA-Z0-9]+$`));
|
|
2710
|
+
if (match.length > 0) {
|
|
2711
|
+
resolvedPath = match[0].name;
|
|
2712
|
+
break;
|
|
2713
|
+
}
|
|
2672
2714
|
}
|
|
2673
|
-
const attr = rec.data.readUInt32LE(offset);
|
|
2674
|
-
numberFormats.push(attr >>> 5 & 15);
|
|
2675
|
-
offset += 12;
|
|
2676
|
-
const { value, next } = readHwpString(rec.data, offset);
|
|
2677
|
-
levelFormats.push(value);
|
|
2678
|
-
offset = next;
|
|
2679
2715
|
}
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2716
|
+
const allCandidates = resolvedPath ? [resolvedPath, ...candidates] : candidates;
|
|
2717
|
+
for (const path of allCandidates) {
|
|
2718
|
+
if (isPathTraversal(path)) continue;
|
|
2719
|
+
const file = zip.file(path);
|
|
2720
|
+
if (!file) continue;
|
|
2721
|
+
try {
|
|
2722
|
+
const data = await file.async("uint8array");
|
|
2723
|
+
decompressed.total += data.length;
|
|
2724
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2725
|
+
const ext = path.includes(".") ? path.split(".").pop() || "png" : "png";
|
|
2726
|
+
const mimeType = imageExtToMime(ext);
|
|
2727
|
+
imageIndex++;
|
|
2728
|
+
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
2729
|
+
img = { filename, data, mimeType };
|
|
2730
|
+
images.push(img);
|
|
2731
|
+
usedPaths.add(path);
|
|
2732
|
+
break;
|
|
2733
|
+
} catch (err) {
|
|
2734
|
+
if (err instanceof KordocError) throw err;
|
|
2735
|
+
}
|
|
2684
2736
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2737
|
+
if (!img) warnings?.push({ page: block.pageNumber, message: `\uC774\uBBF8\uC9C0 \uD30C\uC77C \uC5C6\uC74C: ${ref}`, code: "SKIPPED_IMAGE" });
|
|
2738
|
+
resolved.set(ref, img);
|
|
2739
|
+
}
|
|
2740
|
+
if (!img) {
|
|
2741
|
+
block.type = "paragraph";
|
|
2742
|
+
block.text = `[\uC774\uBBF8\uC9C0: ${ref}]`;
|
|
2743
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
2744
|
+
continue;
|
|
2745
|
+
}
|
|
2746
|
+
block.text = img.filename;
|
|
2747
|
+
block.imageData = { data: img.data, mimeType: img.mimeType, filename: ref };
|
|
2748
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, ``);
|
|
2749
|
+
}
|
|
2750
|
+
if (sweepUnreferenced) {
|
|
2751
|
+
const binEntries = zip.file(/(?:^|\/)BinData\//i);
|
|
2752
|
+
for (const file of binEntries) {
|
|
2753
|
+
if (file.dir || usedPaths.has(file.name) || isPathTraversal(file.name)) continue;
|
|
2754
|
+
try {
|
|
2755
|
+
const data = await file.async("uint8array");
|
|
2756
|
+
decompressed.total += data.length;
|
|
2757
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2758
|
+
const ext = file.name.includes(".") ? file.name.split(".").pop() || "" : "";
|
|
2759
|
+
let mimeType = imageExtToMime(ext);
|
|
2760
|
+
if (mimeType === "application/octet-stream") {
|
|
2761
|
+
const sniffed = detectImageMime(data);
|
|
2762
|
+
if (!sniffed) continue;
|
|
2763
|
+
mimeType = sniffed;
|
|
2691
2764
|
}
|
|
2765
|
+
imageIndex++;
|
|
2766
|
+
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
2767
|
+
const img = { filename, data, mimeType };
|
|
2768
|
+
images.push(img);
|
|
2769
|
+
blocks.push({ type: "image", text: filename, imageData: { data, mimeType, filename: file.name } });
|
|
2770
|
+
} catch (err) {
|
|
2771
|
+
if (err instanceof KordocError) throw err;
|
|
2772
|
+
warnings?.push({ message: `\uC774\uBBF8\uC9C0 \uCD94\uCD9C \uC2E4\uD328: ${file.name}`, code: "SKIPPED_IMAGE" });
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
return images;
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
// src/hwpx/metadata.ts
|
|
2780
|
+
import JSZip from "jszip";
|
|
2781
|
+
|
|
2782
|
+
// src/hwpx/zip-sections.ts
|
|
2783
|
+
import { inflateRawSync as inflateRawSync2 } from "zlib";
|
|
2784
|
+
function extractFromBrokenZip(buffer) {
|
|
2785
|
+
const data = new Uint8Array(buffer);
|
|
2786
|
+
const view = new DataView(buffer);
|
|
2787
|
+
let pos = 0;
|
|
2788
|
+
const blocks = [];
|
|
2789
|
+
const warnings = [
|
|
2790
|
+
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
2791
|
+
];
|
|
2792
|
+
let totalDecompressed = 0;
|
|
2793
|
+
let entryCount = 0;
|
|
2794
|
+
let sectionNum = 0;
|
|
2795
|
+
const shared = createSectionShared();
|
|
2796
|
+
while (pos < data.length - 30) {
|
|
2797
|
+
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
2798
|
+
pos++;
|
|
2799
|
+
while (pos < data.length - 30) {
|
|
2800
|
+
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
2801
|
+
pos++;
|
|
2802
|
+
}
|
|
2803
|
+
continue;
|
|
2804
|
+
}
|
|
2805
|
+
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
2806
|
+
const method = view.getUint16(pos + 8, true);
|
|
2807
|
+
const compSize = view.getUint32(pos + 18, true);
|
|
2808
|
+
const nameLen = view.getUint16(pos + 26, true);
|
|
2809
|
+
const extraLen = view.getUint16(pos + 28, true);
|
|
2810
|
+
if (nameLen > 1024 || extraLen > 65535) {
|
|
2811
|
+
pos += 30 + nameLen + extraLen;
|
|
2812
|
+
continue;
|
|
2813
|
+
}
|
|
2814
|
+
const fileStart = pos + 30 + nameLen + extraLen;
|
|
2815
|
+
if (fileStart + compSize > data.length) break;
|
|
2816
|
+
if (compSize === 0 && method !== 0) {
|
|
2817
|
+
pos = fileStart;
|
|
2818
|
+
continue;
|
|
2819
|
+
}
|
|
2820
|
+
const nameBytes = data.slice(pos + 30, pos + 30 + nameLen);
|
|
2821
|
+
const name = new TextDecoder().decode(nameBytes);
|
|
2822
|
+
if (isPathTraversal(name)) {
|
|
2823
|
+
pos = fileStart + compSize;
|
|
2824
|
+
continue;
|
|
2825
|
+
}
|
|
2826
|
+
const fileData = data.slice(fileStart, fileStart + compSize);
|
|
2827
|
+
pos = fileStart + compSize;
|
|
2828
|
+
if (!name.toLowerCase().includes("section") || !name.endsWith(".xml")) continue;
|
|
2829
|
+
try {
|
|
2830
|
+
let content;
|
|
2831
|
+
if (method === 0) {
|
|
2832
|
+
content = new TextDecoder().decode(fileData);
|
|
2833
|
+
} else if (method === 8) {
|
|
2834
|
+
const decompressed = inflateRawSync2(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
2835
|
+
content = new TextDecoder().decode(decompressed);
|
|
2836
|
+
} else {
|
|
2837
|
+
continue;
|
|
2692
2838
|
}
|
|
2693
|
-
|
|
2839
|
+
totalDecompressed += content.length * 2;
|
|
2840
|
+
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
2841
|
+
sectionNum++;
|
|
2842
|
+
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
2843
|
+
} catch {
|
|
2844
|
+
continue;
|
|
2694
2845
|
}
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2846
|
+
}
|
|
2847
|
+
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");
|
|
2848
|
+
applyPageText(blocks, shared);
|
|
2849
|
+
const markdown = blocksToMarkdown(blocks);
|
|
2850
|
+
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
2851
|
+
}
|
|
2852
|
+
async function readKordocLayout(zip) {
|
|
2853
|
+
const file = zip.file("Contents/content.hpf") ?? zip.file("content.hpf");
|
|
2854
|
+
if (!file) return null;
|
|
2855
|
+
try {
|
|
2856
|
+
const xml = await file.async("text");
|
|
2857
|
+
if (!/<opf:meta\b[^>]*name="generator"[^>]*content="kordoc"/.test(xml)) return null;
|
|
2858
|
+
return xml.match(/<opf:meta\b[^>]*name="kordoc-layout"[^>]*content="([^"]*)"/)?.[1] ?? null;
|
|
2859
|
+
} catch {
|
|
2860
|
+
return null;
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
async function resolveSectionPaths(zip) {
|
|
2864
|
+
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
2865
|
+
for (const mp of manifestPaths) {
|
|
2866
|
+
const mpLower = mp.toLowerCase();
|
|
2867
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
2868
|
+
if (!file) continue;
|
|
2869
|
+
const xml = await file.async("text");
|
|
2870
|
+
const paths = parseSectionPathsFromManifest(xml);
|
|
2871
|
+
if (paths.length > 0) return paths;
|
|
2872
|
+
}
|
|
2873
|
+
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
2874
|
+
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
2875
|
+
}
|
|
2876
|
+
function parseSectionPathsFromManifest(xml) {
|
|
2877
|
+
const parser = createXmlParser();
|
|
2878
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
2879
|
+
const items = doc.getElementsByTagName("opf:item");
|
|
2880
|
+
const spine = doc.getElementsByTagName("opf:itemref");
|
|
2881
|
+
const idToHref = /* @__PURE__ */ new Map();
|
|
2882
|
+
for (let i = 0; i < items.length; i++) {
|
|
2883
|
+
const item = items[i];
|
|
2884
|
+
const id = item.getAttribute("id") || "";
|
|
2885
|
+
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
2886
|
+
if (id && href) idToHref.set(id, href);
|
|
2887
|
+
}
|
|
2888
|
+
if (spine.length > 0) {
|
|
2889
|
+
const ordered = [];
|
|
2890
|
+
for (let i = 0; i < spine.length; i++) {
|
|
2891
|
+
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
2892
|
+
if (href) ordered.push(href);
|
|
2698
2893
|
}
|
|
2699
|
-
if (
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2894
|
+
if (ordered.length > 0) return ordered;
|
|
2895
|
+
}
|
|
2896
|
+
return Array.from(idToHref.values()).sort(compareSectionPaths);
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
// src/hwpx/metadata.ts
|
|
2900
|
+
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
2901
|
+
try {
|
|
2902
|
+
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
2903
|
+
for (const mp of metaPaths) {
|
|
2904
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
2905
|
+
if (!file) continue;
|
|
2906
|
+
const xml = await file.async("text");
|
|
2907
|
+
if (decompressed) {
|
|
2908
|
+
decompressed.total += xml.length * 2;
|
|
2909
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2706
2910
|
}
|
|
2911
|
+
parseDublinCoreMetadata(xml, metadata);
|
|
2912
|
+
if (metadata.title || metadata.author) return;
|
|
2707
2913
|
}
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
if (nameKoBytes > 0 && offset + nameKoBytes <= rec.data.length) {
|
|
2722
|
-
nameKo = rec.data.subarray(offset, offset + nameKoBytes).toString("utf16le");
|
|
2723
|
-
}
|
|
2724
|
-
offset += nameKoBytes;
|
|
2725
|
-
}
|
|
2726
|
-
const type = offset < rec.data.length ? rec.data.readUInt8(offset) : 0;
|
|
2727
|
-
offset += 1;
|
|
2728
|
-
offset += 1;
|
|
2729
|
-
offset += 2;
|
|
2730
|
-
const paraShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
2731
|
-
offset += 2;
|
|
2732
|
-
const charShapeId = offset + 2 <= rec.data.length ? rec.data.readUInt16LE(offset) : 0;
|
|
2733
|
-
styles.push({ name, nameKo, charShapeId, paraShapeId, type });
|
|
2734
|
-
} catch {
|
|
2914
|
+
} catch {
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
function parseDublinCoreMetadata(xml, metadata) {
|
|
2918
|
+
const parser = createXmlParser();
|
|
2919
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
2920
|
+
if (!doc.documentElement) return;
|
|
2921
|
+
const getText = (tagNames) => {
|
|
2922
|
+
for (const tag of tagNames) {
|
|
2923
|
+
const els = doc.getElementsByTagName(tag);
|
|
2924
|
+
if (els.length > 0) {
|
|
2925
|
+
const text = els[0].textContent?.trim();
|
|
2926
|
+
if (text) return text;
|
|
2735
2927
|
}
|
|
2736
2928
|
}
|
|
2929
|
+
return void 0;
|
|
2930
|
+
};
|
|
2931
|
+
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
2932
|
+
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
2933
|
+
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
2934
|
+
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
2935
|
+
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
2936
|
+
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
2937
|
+
if (keywords && !metadata.keywords) {
|
|
2938
|
+
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
2737
2939
|
}
|
|
2738
|
-
return { charShapes, paraShapes, styles, binData, numberings, bullets };
|
|
2739
2940
|
}
|
|
2740
|
-
function
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2941
|
+
async function extractHwpxMetadataOnly(buffer) {
|
|
2942
|
+
let zip;
|
|
2943
|
+
try {
|
|
2944
|
+
zip = await JSZip.loadAsync(buffer);
|
|
2945
|
+
} catch {
|
|
2946
|
+
throw new KordocError("HWPX ZIP\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
2947
|
+
}
|
|
2948
|
+
const metadata = {};
|
|
2949
|
+
await extractHwpxMetadata(zip, metadata);
|
|
2950
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
2951
|
+
metadata.pageCount = sectionPaths.length;
|
|
2952
|
+
return metadata;
|
|
2745
2953
|
}
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
if (
|
|
2767
|
-
|
|
2768
|
-
i += 16;
|
|
2769
|
-
break;
|
|
2954
|
+
|
|
2955
|
+
// src/hwpx/parser.ts
|
|
2956
|
+
async function parseHwpxDocument(buffer, options) {
|
|
2957
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
2958
|
+
let zip;
|
|
2959
|
+
try {
|
|
2960
|
+
zip = await JSZip2.loadAsync(buffer);
|
|
2961
|
+
} catch {
|
|
2962
|
+
return extractFromBrokenZip(buffer);
|
|
2963
|
+
}
|
|
2964
|
+
const actualEntryCount = Object.keys(zip.files).length;
|
|
2965
|
+
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
2966
|
+
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2967
|
+
}
|
|
2968
|
+
const manifestFile = zip.file("META-INF/manifest.xml");
|
|
2969
|
+
if (manifestFile) {
|
|
2970
|
+
const manifestXml = await manifestFile.async("text");
|
|
2971
|
+
if (isEncryptedHwpx(manifestXml)) {
|
|
2972
|
+
if (isComFallbackAvailable() && options?.filePath) {
|
|
2973
|
+
const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
|
|
2974
|
+
if (pages.some((p) => p && p.trim().length > 0)) {
|
|
2975
|
+
return comResultToParseResult(pages, pageCount, warnings2);
|
|
2770
2976
|
}
|
|
2771
|
-
result += "\n";
|
|
2772
|
-
break;
|
|
2773
2977
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
if (open) {
|
|
2806
|
-
state.fieldRanges.push({ start: open.start, end: base + result.length, ctrlIdx: open.ctrlIdx });
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
resolveAt(i, isExtended);
|
|
2810
|
-
i += 14;
|
|
2811
|
-
}
|
|
2812
|
-
} else if (ch >= 32) {
|
|
2813
|
-
if (ch >= 55296 && ch <= 56319 && i + 1 < data.length) {
|
|
2814
|
-
const lo = data.readUInt16LE(i);
|
|
2815
|
-
if (lo >= 56320 && lo <= 57343) {
|
|
2816
|
-
i += 2;
|
|
2817
|
-
const codePoint = (ch - 55296 << 10) + (lo - 56320) + 65536;
|
|
2818
|
-
result += String.fromCodePoint(codePoint);
|
|
2819
|
-
break;
|
|
2820
|
-
}
|
|
2821
|
-
}
|
|
2822
|
-
result += String.fromCharCode(ch);
|
|
2823
|
-
}
|
|
2824
|
-
break;
|
|
2978
|
+
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.");
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
const decompressed = { total: 0 };
|
|
2982
|
+
const metadata = {};
|
|
2983
|
+
await extractHwpxMetadata(zip, metadata, decompressed);
|
|
2984
|
+
const styleMap = await extractHwpxStyles(zip, decompressed);
|
|
2985
|
+
const warnings = [];
|
|
2986
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
2987
|
+
if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
2988
|
+
metadata.pageCount = sectionPaths.length;
|
|
2989
|
+
const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null;
|
|
2990
|
+
const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length;
|
|
2991
|
+
const blocks = [];
|
|
2992
|
+
const shared = createSectionShared();
|
|
2993
|
+
shared.kordocLayout = await readKordocLayout(zip);
|
|
2994
|
+
let parsedSections = 0;
|
|
2995
|
+
for (let si = 0; si < sectionPaths.length; si++) {
|
|
2996
|
+
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
2997
|
+
const file = zip.file(sectionPaths[si]);
|
|
2998
|
+
if (!file) continue;
|
|
2999
|
+
try {
|
|
3000
|
+
const xml = await file.async("text");
|
|
3001
|
+
decompressed.total += xml.length * 2;
|
|
3002
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3003
|
+
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
3004
|
+
parsedSections++;
|
|
3005
|
+
options?.onProgress?.(parsedSections, totalTarget);
|
|
3006
|
+
} catch (secErr) {
|
|
3007
|
+
if (secErr instanceof KordocError) throw secErr;
|
|
3008
|
+
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" });
|
|
2825
3009
|
}
|
|
2826
3010
|
}
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
const
|
|
2832
|
-
|
|
2833
|
-
const scriptEnd = scriptStart + scriptLength * 2;
|
|
2834
|
-
if (scriptLength <= 0 || scriptEnd > data.length) return null;
|
|
2835
|
-
const equation = data.subarray(scriptStart, scriptEnd).toString("utf16le").replace(/\0+/g, "").trim();
|
|
2836
|
-
return equation || null;
|
|
3011
|
+
applyPageText(blocks, shared);
|
|
3012
|
+
const images = await extractImagesFromZip(zip, blocks, decompressed, warnings, !pageFilter);
|
|
3013
|
+
detectHwpxHeadings(blocks, styleMap);
|
|
3014
|
+
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
3015
|
+
const markdown = blocksToMarkdown(blocks);
|
|
3016
|
+
return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0, images: images.length > 0 ? images : void 0 };
|
|
2837
3017
|
}
|
|
2838
3018
|
|
|
2839
3019
|
// src/hwp5/numbering.ts
|
|
@@ -3014,282 +3194,6 @@ function expandNumberingFormat(formatStr, counters, numbering) {
|
|
|
3014
3194
|
return result;
|
|
3015
3195
|
}
|
|
3016
3196
|
|
|
3017
|
-
// src/hwp5/images.ts
|
|
3018
|
-
function detectImageMime(data) {
|
|
3019
|
-
if (data.length < 4) return null;
|
|
3020
|
-
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return "image/png";
|
|
3021
|
-
if (data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg";
|
|
3022
|
-
if (data[0] === 71 && data[1] === 73 && data[2] === 70) return "image/gif";
|
|
3023
|
-
if (data[0] === 66 && data[1] === 77) return "image/bmp";
|
|
3024
|
-
if (data[0] === 215 && data[1] === 205 && data[2] === 198 && data[3] === 154) return "image/wmf";
|
|
3025
|
-
if (data[0] === 1 && data[1] === 0 && data[2] === 0 && data[3] === 0) return "image/emf";
|
|
3026
|
-
return null;
|
|
3027
|
-
}
|
|
3028
|
-
function normalizeBinPayload(data) {
|
|
3029
|
-
if (detectImageMime(data)) return data;
|
|
3030
|
-
try {
|
|
3031
|
-
const inflated = decompressStream(data);
|
|
3032
|
-
if (inflated.length > 0) return inflated;
|
|
3033
|
-
} catch {
|
|
3034
|
-
}
|
|
3035
|
-
return data;
|
|
3036
|
-
}
|
|
3037
|
-
var BIN_ENTRY_RE = /(?:^|\/)BIN([0-9A-Fa-f]{4,8})(?:\.[^./\\]*)?$/;
|
|
3038
|
-
function collectImageBlocks2(blocks, out) {
|
|
3039
|
-
for (const b of blocks) {
|
|
3040
|
-
if (b.type === "image") out.push(b);
|
|
3041
|
-
if (b.table) {
|
|
3042
|
-
for (const row of b.table.cells) {
|
|
3043
|
-
for (const cell2 of row) {
|
|
3044
|
-
if (cell2.blocks) collectImageBlocks2(cell2.blocks, out);
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
}
|
|
3048
|
-
if (b.children) collectImageBlocks2(b.children, out);
|
|
3049
|
-
}
|
|
3050
|
-
}
|
|
3051
|
-
function forEachTableCell(blocks, fn) {
|
|
3052
|
-
for (const b of blocks) {
|
|
3053
|
-
if (b.table) {
|
|
3054
|
-
for (const row of b.table.cells) {
|
|
3055
|
-
for (const cell2 of row) {
|
|
3056
|
-
fn(cell2);
|
|
3057
|
-
if (cell2.blocks) forEachTableCell(cell2.blocks, fn);
|
|
3058
|
-
}
|
|
3059
|
-
}
|
|
3060
|
-
}
|
|
3061
|
-
if (b.children) forEachTableCell(b.children, fn);
|
|
3062
|
-
}
|
|
3063
|
-
}
|
|
3064
|
-
var CELL_IMAGE_SENTINEL_RE = /!\[image\]\(hwp5bin:(\d+)\)/g;
|
|
3065
|
-
function resolveCellImageSentinels(blocks, renamed) {
|
|
3066
|
-
forEachTableCell(blocks, (cell2) => {
|
|
3067
|
-
if (!cell2.text.includes("hwp5bin:")) return;
|
|
3068
|
-
cell2.text = cell2.text.replace(CELL_IMAGE_SENTINEL_RE, (_m, idStr) => {
|
|
3069
|
-
const filename = renamed.get(Number(idStr));
|
|
3070
|
-
return filename ? `` : "[\uC774\uBBF8\uC9C0]";
|
|
3071
|
-
});
|
|
3072
|
-
});
|
|
3073
|
-
}
|
|
3074
|
-
function resolveImageBlocks(binDataMap, blocks, warnings) {
|
|
3075
|
-
const imageBlocks = [];
|
|
3076
|
-
collectImageBlocks2(blocks, imageBlocks);
|
|
3077
|
-
if (imageBlocks.length === 0) return [];
|
|
3078
|
-
const images = [];
|
|
3079
|
-
const renamed = /* @__PURE__ */ new Map();
|
|
3080
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
3081
|
-
let imageIndex = 0;
|
|
3082
|
-
for (const block of imageBlocks) {
|
|
3083
|
-
if (!block.text) continue;
|
|
3084
|
-
const storageId = parseInt(block.text, 10);
|
|
3085
|
-
if (isNaN(storageId)) continue;
|
|
3086
|
-
let img = resolved.get(storageId);
|
|
3087
|
-
if (img === void 0) {
|
|
3088
|
-
const bin = binDataMap.get(storageId);
|
|
3089
|
-
if (!bin) {
|
|
3090
|
-
warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
|
|
3091
|
-
resolved.set(storageId, null);
|
|
3092
|
-
} else {
|
|
3093
|
-
const mime = detectImageMime(bin.data);
|
|
3094
|
-
if (!mime) {
|
|
3095
|
-
warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
|
|
3096
|
-
resolved.set(storageId, null);
|
|
3097
|
-
} else {
|
|
3098
|
-
imageIndex++;
|
|
3099
|
-
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
3100
|
-
img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
|
|
3101
|
-
resolved.set(storageId, img);
|
|
3102
|
-
images.push({ filename: img.filename, data: img.data, mimeType: img.mime });
|
|
3103
|
-
renamed.set(storageId, img.filename);
|
|
3104
|
-
}
|
|
3105
|
-
}
|
|
3106
|
-
img = resolved.get(storageId);
|
|
3107
|
-
}
|
|
3108
|
-
if (!img) {
|
|
3109
|
-
const bin = binDataMap.get(storageId);
|
|
3110
|
-
block.type = "paragraph";
|
|
3111
|
-
block.text = bin ? `[\uC774\uBBF8\uC9C0: ${bin.name}]` : `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
|
|
3112
|
-
continue;
|
|
3113
|
-
}
|
|
3114
|
-
block.text = img.filename;
|
|
3115
|
-
block.imageData = { data: img.data, mimeType: img.mime, filename: binDataMap.get(storageId).name };
|
|
3116
|
-
}
|
|
3117
|
-
resolveCellImageSentinels(blocks, renamed);
|
|
3118
|
-
return images;
|
|
3119
|
-
}
|
|
3120
|
-
function extractHwp5Images(fileIndex, blocks, warnings) {
|
|
3121
|
-
const binDataMap = /* @__PURE__ */ new Map();
|
|
3122
|
-
if (fileIndex) {
|
|
3123
|
-
for (const entry of fileIndex) {
|
|
3124
|
-
if (!entry?.name || !entry.content) continue;
|
|
3125
|
-
const match = entry.name.match(BIN_ENTRY_RE);
|
|
3126
|
-
if (!match) continue;
|
|
3127
|
-
const idx = parseInt(match[1], 16);
|
|
3128
|
-
const data = normalizeBinPayload(Buffer.from(entry.content));
|
|
3129
|
-
binDataMap.set(idx, { data, name: entry.name });
|
|
3130
|
-
}
|
|
3131
|
-
}
|
|
3132
|
-
if (binDataMap.size === 0) {
|
|
3133
|
-
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3134
|
-
return [];
|
|
3135
|
-
}
|
|
3136
|
-
return resolveImageBlocks(binDataMap, blocks, warnings);
|
|
3137
|
-
}
|
|
3138
|
-
function extractHwp5ImagesLenient(lcfb, blocks, warnings) {
|
|
3139
|
-
const binDataMap = /* @__PURE__ */ new Map();
|
|
3140
|
-
const binRe = /^BIN([0-9A-Fa-f]{4,8})(?:\.|$)/;
|
|
3141
|
-
for (const e of lcfb.entries()) {
|
|
3142
|
-
const match = e.name.match(binRe);
|
|
3143
|
-
if (!match) continue;
|
|
3144
|
-
const idx = parseInt(match[1], 16);
|
|
3145
|
-
const raw = lcfb.findStream(e.name);
|
|
3146
|
-
if (!raw) continue;
|
|
3147
|
-
binDataMap.set(idx, { data: normalizeBinPayload(raw), name: e.name });
|
|
3148
|
-
}
|
|
3149
|
-
if (binDataMap.size === 0) {
|
|
3150
|
-
resolveCellImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3151
|
-
return [];
|
|
3152
|
-
}
|
|
3153
|
-
return resolveImageBlocks(binDataMap, blocks, warnings);
|
|
3154
|
-
}
|
|
3155
|
-
|
|
3156
|
-
// src/image/transcode.ts
|
|
3157
|
-
import { deflateSync } from "zlib";
|
|
3158
|
-
var CRC_TABLE = (() => {
|
|
3159
|
-
const table2 = new Uint32Array(256);
|
|
3160
|
-
for (let n = 0; n < 256; n++) {
|
|
3161
|
-
let c = n;
|
|
3162
|
-
for (let k = 0; k < 8; k++) {
|
|
3163
|
-
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
3164
|
-
}
|
|
3165
|
-
table2[n] = c >>> 0;
|
|
3166
|
-
}
|
|
3167
|
-
return table2;
|
|
3168
|
-
})();
|
|
3169
|
-
function crc32(bytes) {
|
|
3170
|
-
let c = 4294967295;
|
|
3171
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
3172
|
-
c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
|
|
3173
|
-
}
|
|
3174
|
-
return (c ^ 4294967295) >>> 0;
|
|
3175
|
-
}
|
|
3176
|
-
var BI_RGB = 0;
|
|
3177
|
-
var MAX_DIM = 32767;
|
|
3178
|
-
var MAX_PIXELS = 36e6;
|
|
3179
|
-
function bmpToPng(bmp) {
|
|
3180
|
-
if (bmp.length < 54) return null;
|
|
3181
|
-
if (bmp[0] !== 66 || bmp[1] !== 77) return null;
|
|
3182
|
-
const dv = new DataView(bmp.buffer, bmp.byteOffset, bmp.byteLength);
|
|
3183
|
-
const dataOffset = dv.getUint32(10, true);
|
|
3184
|
-
const headerSize = dv.getUint32(14, true);
|
|
3185
|
-
if (headerSize < 40) return null;
|
|
3186
|
-
const width = dv.getInt32(18, true);
|
|
3187
|
-
const rawHeight = dv.getInt32(22, true);
|
|
3188
|
-
const bitCount = dv.getUint16(28, true);
|
|
3189
|
-
const compression = dv.getUint32(30, true);
|
|
3190
|
-
if (compression !== BI_RGB) return null;
|
|
3191
|
-
if (bitCount !== 24 && bitCount !== 32) return null;
|
|
3192
|
-
if (width <= 0 || rawHeight === 0) return null;
|
|
3193
|
-
if (width > MAX_DIM || Math.abs(rawHeight) > MAX_DIM) return null;
|
|
3194
|
-
const topDown = rawHeight < 0;
|
|
3195
|
-
const height = Math.abs(rawHeight);
|
|
3196
|
-
if (width * height > MAX_PIXELS) return null;
|
|
3197
|
-
const bytesPerPixel = bitCount >> 3;
|
|
3198
|
-
const rowStride = width * bytesPerPixel + 3 & ~3;
|
|
3199
|
-
if (dataOffset + rowStride * height > bmp.length) return null;
|
|
3200
|
-
const rgba = new Uint8Array(width * height * 4);
|
|
3201
|
-
let anyAlpha = 0;
|
|
3202
|
-
for (let y = 0; y < height; y++) {
|
|
3203
|
-
const srcRow = topDown ? y : height - 1 - y;
|
|
3204
|
-
let src = dataOffset + srcRow * rowStride;
|
|
3205
|
-
let dst = y * width * 4;
|
|
3206
|
-
for (let x = 0; x < width; x++) {
|
|
3207
|
-
rgba[dst] = bmp[src + 2];
|
|
3208
|
-
rgba[dst + 1] = bmp[src + 1];
|
|
3209
|
-
rgba[dst + 2] = bmp[src];
|
|
3210
|
-
const a = bitCount === 32 ? bmp[src + 3] : 255;
|
|
3211
|
-
rgba[dst + 3] = a;
|
|
3212
|
-
anyAlpha |= a;
|
|
3213
|
-
src += bytesPerPixel;
|
|
3214
|
-
dst += 4;
|
|
3215
|
-
}
|
|
3216
|
-
}
|
|
3217
|
-
if (bitCount === 32 && anyAlpha === 0) {
|
|
3218
|
-
for (let i = 3; i < rgba.length; i += 4) rgba[i] = 255;
|
|
3219
|
-
}
|
|
3220
|
-
return encodePng(width, height, rgba);
|
|
3221
|
-
}
|
|
3222
|
-
var PNG_SIGNATURE = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10);
|
|
3223
|
-
function chunk(type, data) {
|
|
3224
|
-
const body = new Uint8Array(4 + data.length);
|
|
3225
|
-
body[0] = type.charCodeAt(0);
|
|
3226
|
-
body[1] = type.charCodeAt(1);
|
|
3227
|
-
body[2] = type.charCodeAt(2);
|
|
3228
|
-
body[3] = type.charCodeAt(3);
|
|
3229
|
-
body.set(data, 4);
|
|
3230
|
-
const out = new Uint8Array(8 + data.length + 4);
|
|
3231
|
-
const dv = new DataView(out.buffer);
|
|
3232
|
-
dv.setUint32(0, data.length, false);
|
|
3233
|
-
out.set(body, 4);
|
|
3234
|
-
dv.setUint32(8 + data.length, crc32(body), false);
|
|
3235
|
-
return out;
|
|
3236
|
-
}
|
|
3237
|
-
function encodePng(width, height, rgba) {
|
|
3238
|
-
const ihdr = new Uint8Array(13);
|
|
3239
|
-
const iv = new DataView(ihdr.buffer);
|
|
3240
|
-
iv.setUint32(0, width, false);
|
|
3241
|
-
iv.setUint32(4, height, false);
|
|
3242
|
-
ihdr[8] = 8;
|
|
3243
|
-
ihdr[9] = 6;
|
|
3244
|
-
const stride = width * 4;
|
|
3245
|
-
const raw = new Uint8Array((stride + 1) * height);
|
|
3246
|
-
for (let y = 0; y < height; y++) {
|
|
3247
|
-
const rowStart = y * (stride + 1);
|
|
3248
|
-
raw[rowStart] = 0;
|
|
3249
|
-
raw.set(rgba.subarray(y * stride, y * stride + stride), rowStart + 1);
|
|
3250
|
-
}
|
|
3251
|
-
const idat = deflateSync(raw);
|
|
3252
|
-
const ihdrChunk = chunk("IHDR", ihdr);
|
|
3253
|
-
const idatChunk = chunk("IDAT", idat);
|
|
3254
|
-
const iendChunk = chunk("IEND", new Uint8Array(0));
|
|
3255
|
-
const out = new Uint8Array(PNG_SIGNATURE.length + ihdrChunk.length + idatChunk.length + iendChunk.length);
|
|
3256
|
-
let o = 0;
|
|
3257
|
-
out.set(PNG_SIGNATURE, o);
|
|
3258
|
-
o += PNG_SIGNATURE.length;
|
|
3259
|
-
out.set(ihdrChunk, o);
|
|
3260
|
-
o += ihdrChunk.length;
|
|
3261
|
-
out.set(idatChunk, o);
|
|
3262
|
-
o += idatChunk.length;
|
|
3263
|
-
out.set(iendChunk, o);
|
|
3264
|
-
return out;
|
|
3265
|
-
}
|
|
3266
|
-
function escapeRegExp(s) {
|
|
3267
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3268
|
-
}
|
|
3269
|
-
function inlineImagesIntoMarkdown(markdown, images, opts) {
|
|
3270
|
-
const compress = opts?.compress !== false;
|
|
3271
|
-
let out = markdown;
|
|
3272
|
-
for (const img of images) {
|
|
3273
|
-
let bytes = img.data;
|
|
3274
|
-
let mime = img.mimeType;
|
|
3275
|
-
if (compress && mime === "image/bmp") {
|
|
3276
|
-
const png = bmpToPng(img.data);
|
|
3277
|
-
if (png) {
|
|
3278
|
-
bytes = png;
|
|
3279
|
-
mime = "image/png";
|
|
3280
|
-
}
|
|
3281
|
-
}
|
|
3282
|
-
const base64 = Buffer.from(bytes).toString("base64");
|
|
3283
|
-
const dataUri = `data:${mime};base64,${base64}`;
|
|
3284
|
-
const name = escapeRegExp(img.filename);
|
|
3285
|
-
const mdRe = new RegExp(`!\\[image\\]\\((?:images/)?${name}\\)`, "g");
|
|
3286
|
-
out = out.replace(mdRe, () => ``);
|
|
3287
|
-
const imgTagRe = new RegExp(`(<img\\b[^>]*\\bsrc=")(?:images/)?${name}(")`, "g");
|
|
3288
|
-
out = out.replace(imgTagRe, (_m, pre, post) => `${pre}${dataUri}${post}`);
|
|
3289
|
-
}
|
|
3290
|
-
return out;
|
|
3291
|
-
}
|
|
3292
|
-
|
|
3293
3197
|
// src/hwp5/aes.ts
|
|
3294
3198
|
var S_BOX = new Uint8Array([
|
|
3295
3199
|
99,
|
|
@@ -4337,7 +4241,7 @@ function parseHwp5Document(buffer, options) {
|
|
|
4337
4241
|
}
|
|
4338
4242
|
}
|
|
4339
4243
|
const blocks = [...doc.headerBlocks, ...bodyBlocks, ...doc.footerBlocks];
|
|
4340
|
-
const images = cfb ? extractHwp5Images(cfb.FileIndex, blocks, warnings) : extractHwp5ImagesLenient(lenientCfb, blocks, warnings);
|
|
4244
|
+
const images = cfb ? extractHwp5Images(cfb.FileIndex, blocks, warnings, !pageFilter) : extractHwp5ImagesLenient(lenientCfb, blocks, warnings, !pageFilter);
|
|
4341
4245
|
let flatBlocks = flattenLayoutTables(blocks);
|
|
4342
4246
|
if (options?.dedupeRunningHeaders) {
|
|
4343
4247
|
const deduped = dedupeRunningHeaders(flatBlocks);
|
|
@@ -18812,6 +18716,102 @@ function extractRun(r) {
|
|
|
18812
18716
|
}
|
|
18813
18717
|
return { text, bold, italic };
|
|
18814
18718
|
}
|
|
18719
|
+
function fieldUrlFromInstr(instr) {
|
|
18720
|
+
if (!/HYPERLINK\b/i.test(instr)) return null;
|
|
18721
|
+
const urlM = instr.match(/HYPERLINK\s+"([^"]*)"/i);
|
|
18722
|
+
const anchorM = instr.match(/\\l\s+"([^"]*)"/i);
|
|
18723
|
+
if (urlM && urlM[1]) {
|
|
18724
|
+
return anchorM && anchorM[1] ? `${urlM[1]}#${anchorM[1]}` : urlM[1];
|
|
18725
|
+
}
|
|
18726
|
+
if (anchorM && anchorM[1]) return `#${anchorM[1]}`;
|
|
18727
|
+
return null;
|
|
18728
|
+
}
|
|
18729
|
+
function makeLink(text, rawHref) {
|
|
18730
|
+
if (!text) return "";
|
|
18731
|
+
if (!rawHref) return text;
|
|
18732
|
+
const href = sanitizeHref(rawHref);
|
|
18733
|
+
return href ? `[${text}](${href})` : text;
|
|
18734
|
+
}
|
|
18735
|
+
function collectInline(p, footnotes, rels) {
|
|
18736
|
+
const parts = [];
|
|
18737
|
+
let bold = false;
|
|
18738
|
+
let italic = false;
|
|
18739
|
+
let footnoteText;
|
|
18740
|
+
let fieldActive = false;
|
|
18741
|
+
let fieldStage = "instr";
|
|
18742
|
+
let fieldInstr = "";
|
|
18743
|
+
let fieldDisplay = "";
|
|
18744
|
+
const flushField = () => {
|
|
18745
|
+
if (!fieldActive) return;
|
|
18746
|
+
if (fieldDisplay) {
|
|
18747
|
+
const url = fieldUrlFromInstr(fieldInstr);
|
|
18748
|
+
parts.push(url ? makeLink(fieldDisplay, url) : fieldDisplay);
|
|
18749
|
+
}
|
|
18750
|
+
fieldActive = false;
|
|
18751
|
+
fieldStage = "instr";
|
|
18752
|
+
fieldInstr = "";
|
|
18753
|
+
fieldDisplay = "";
|
|
18754
|
+
};
|
|
18755
|
+
for (const el of effectiveChildElements(p)) {
|
|
18756
|
+
if (matchesLocal(el, "hyperlink")) {
|
|
18757
|
+
const rId = getAttr(el, "id");
|
|
18758
|
+
const anchor = getAttr(el, "anchor");
|
|
18759
|
+
const t = findElements(el, "r").map((r) => extractRun(r).text).join("");
|
|
18760
|
+
if (!t) continue;
|
|
18761
|
+
let raw = null;
|
|
18762
|
+
if (rId && rels.has(rId)) raw = rels.get(rId);
|
|
18763
|
+
else if (anchor) raw = `#${anchor}`;
|
|
18764
|
+
if (fieldActive && fieldStage === "display") fieldDisplay += t;
|
|
18765
|
+
else parts.push(makeLink(t, raw));
|
|
18766
|
+
continue;
|
|
18767
|
+
}
|
|
18768
|
+
if (matchesLocal(el, "fldSimple")) {
|
|
18769
|
+
const t = findElements(el, "r").map((r) => extractRun(r).text).join("");
|
|
18770
|
+
if (!t) continue;
|
|
18771
|
+
const url = fieldUrlFromInstr(getAttr(el, "instr") ?? "");
|
|
18772
|
+
parts.push(url ? makeLink(t, url) : t);
|
|
18773
|
+
continue;
|
|
18774
|
+
}
|
|
18775
|
+
if (matchesLocal(el, "r")) {
|
|
18776
|
+
for (const fc of getChildElements(el, "fldChar")) {
|
|
18777
|
+
const ty = getAttr(fc, "fldCharType");
|
|
18778
|
+
if (ty === "begin") {
|
|
18779
|
+
flushField();
|
|
18780
|
+
fieldActive = true;
|
|
18781
|
+
fieldStage = "instr";
|
|
18782
|
+
} else if (ty === "separate") {
|
|
18783
|
+
if (fieldActive) fieldStage = "display";
|
|
18784
|
+
} else if (ty === "end") flushField();
|
|
18785
|
+
}
|
|
18786
|
+
if (fieldActive && fieldStage === "instr") {
|
|
18787
|
+
for (const it of getChildElements(el, "instrText")) fieldInstr += it.textContent ?? "";
|
|
18788
|
+
}
|
|
18789
|
+
const rr = extractRun(el);
|
|
18790
|
+
if (rr.bold) bold = true;
|
|
18791
|
+
if (rr.italic) italic = true;
|
|
18792
|
+
const fnRefEls = getChildElements(el, "footnoteReference");
|
|
18793
|
+
if (fnRefEls.length > 0) {
|
|
18794
|
+
const fnId = getAttr(fnRefEls[0], "id");
|
|
18795
|
+
if (fnId && footnotes.has(fnId)) footnoteText = footnotes.get(fnId);
|
|
18796
|
+
}
|
|
18797
|
+
if (rr.text) {
|
|
18798
|
+
if (fieldActive && fieldStage === "display") fieldDisplay += rr.text;
|
|
18799
|
+
else if (fieldActive && fieldStage === "instr") {
|
|
18800
|
+
} else parts.push(rr.text);
|
|
18801
|
+
}
|
|
18802
|
+
continue;
|
|
18803
|
+
}
|
|
18804
|
+
}
|
|
18805
|
+
flushField();
|
|
18806
|
+
for (const om of collectOmmlRoots(p)) {
|
|
18807
|
+
const latex = ommlElementToLatex(om);
|
|
18808
|
+
if (!latex) continue;
|
|
18809
|
+
if (isDisplayMath(om)) parts.push(" $$" + latex + "$$ ");
|
|
18810
|
+
else parts.push(" $" + latex + "$ ");
|
|
18811
|
+
}
|
|
18812
|
+
const text = parts.join("").replace(/[ \t]{2,}/g, " ").trim();
|
|
18813
|
+
return { text, bold, italic, footnoteText };
|
|
18814
|
+
}
|
|
18815
18815
|
function parseParagraph2(p, styles, numbering, footnotes, rels) {
|
|
18816
18816
|
const pPrEls = getChildElements(p, "pPr");
|
|
18817
18817
|
let styleId = "";
|
|
@@ -18828,54 +18828,7 @@ function parseParagraph2(p, styles, numbering, footnotes, rels) {
|
|
|
18828
18828
|
ilvl = ilvlEls.length > 0 ? parseInt(getAttr(ilvlEls[0], "val") ?? "0", 10) : 0;
|
|
18829
18829
|
}
|
|
18830
18830
|
}
|
|
18831
|
-
const
|
|
18832
|
-
let hasBold = false;
|
|
18833
|
-
let hasItalic = false;
|
|
18834
|
-
let href;
|
|
18835
|
-
let footnoteText;
|
|
18836
|
-
const hyperlinks = getChildElements(p, "hyperlink");
|
|
18837
|
-
const hyperlinkTexts = /* @__PURE__ */ new Set();
|
|
18838
|
-
for (const hl of hyperlinks) {
|
|
18839
|
-
const rId = getAttr(hl, "id");
|
|
18840
|
-
const hlText = [];
|
|
18841
|
-
const runs2 = findElements(hl, "r");
|
|
18842
|
-
for (const r of runs2) {
|
|
18843
|
-
const result = extractRun(r);
|
|
18844
|
-
hlText.push(result.text);
|
|
18845
|
-
}
|
|
18846
|
-
const text2 = hlText.join("");
|
|
18847
|
-
if (text2) {
|
|
18848
|
-
hyperlinkTexts.add(text2);
|
|
18849
|
-
if (rId && rels.has(rId)) {
|
|
18850
|
-
href = rels.get(rId);
|
|
18851
|
-
parts.push(text2);
|
|
18852
|
-
} else {
|
|
18853
|
-
parts.push(text2);
|
|
18854
|
-
}
|
|
18855
|
-
}
|
|
18856
|
-
}
|
|
18857
|
-
const runs = getChildElements(p, "r");
|
|
18858
|
-
for (const r of runs) {
|
|
18859
|
-
if (r.parentNode && r.parentNode.localName === "hyperlink") continue;
|
|
18860
|
-
const result = extractRun(r);
|
|
18861
|
-
if (result.bold) hasBold = true;
|
|
18862
|
-
if (result.italic) hasItalic = true;
|
|
18863
|
-
const fnRefEls = getChildElements(r, "footnoteReference");
|
|
18864
|
-
if (fnRefEls.length > 0) {
|
|
18865
|
-
const fnId = getAttr(fnRefEls[0], "id");
|
|
18866
|
-
if (fnId && footnotes.has(fnId)) {
|
|
18867
|
-
footnoteText = footnotes.get(fnId);
|
|
18868
|
-
}
|
|
18869
|
-
}
|
|
18870
|
-
if (result.text) parts.push(result.text);
|
|
18871
|
-
}
|
|
18872
|
-
for (const om of collectOmmlRoots(p)) {
|
|
18873
|
-
const latex = ommlElementToLatex(om);
|
|
18874
|
-
if (!latex) continue;
|
|
18875
|
-
if (isDisplayMath(om)) parts.push(" $$" + latex + "$$ ");
|
|
18876
|
-
else parts.push(" $" + latex + "$ ");
|
|
18877
|
-
}
|
|
18878
|
-
const text = parts.join("").replace(/[ \t]{2,}/g, " ").trim();
|
|
18831
|
+
const { text, bold: hasBold, italic: hasItalic, footnoteText } = collectInline(p, footnotes, rels);
|
|
18879
18832
|
if (!text) return null;
|
|
18880
18833
|
const style = styles.get(styleId);
|
|
18881
18834
|
if (style?.outlineLevel !== void 0 && style.outlineLevel >= 0 && style.outlineLevel <= 5) {
|
|
@@ -18895,7 +18848,6 @@ function parseParagraph2(p, styles, numbering, footnotes, rels) {
|
|
|
18895
18848
|
if (hasBold || hasItalic) {
|
|
18896
18849
|
block.style = { bold: hasBold || void 0, italic: hasItalic || void 0 };
|
|
18897
18850
|
}
|
|
18898
|
-
if (href) block.href = href;
|
|
18899
18851
|
if (footnoteText) block.footnoteText = footnoteText;
|
|
18900
18852
|
return block;
|
|
18901
18853
|
}
|
|
@@ -18996,46 +18948,81 @@ function collectCellText(tc2, styles, numbering, footnotes, rels, depth) {
|
|
|
18996
18948
|
}
|
|
18997
18949
|
return parts;
|
|
18998
18950
|
}
|
|
18999
|
-
async function
|
|
19000
|
-
const
|
|
18951
|
+
async function buildImageMap(zip, rels, doc, warnings) {
|
|
18952
|
+
const imageMap = /* @__PURE__ */ new Map();
|
|
19001
18953
|
const images = [];
|
|
19002
|
-
const drawingElements = findElements(doc.documentElement, "drawing");
|
|
19003
18954
|
let imgIdx = 0;
|
|
19004
|
-
|
|
19005
|
-
|
|
19006
|
-
|
|
19007
|
-
|
|
19008
|
-
|
|
19009
|
-
|
|
19010
|
-
|
|
19011
|
-
|
|
19012
|
-
|
|
19013
|
-
|
|
19014
|
-
|
|
19015
|
-
|
|
19016
|
-
|
|
19017
|
-
|
|
19018
|
-
|
|
19019
|
-
|
|
19020
|
-
|
|
19021
|
-
|
|
19022
|
-
|
|
19023
|
-
|
|
19024
|
-
|
|
19025
|
-
|
|
19026
|
-
|
|
19027
|
-
|
|
19028
|
-
|
|
19029
|
-
|
|
19030
|
-
|
|
19031
|
-
|
|
19032
|
-
|
|
19033
|
-
|
|
19034
|
-
|
|
19035
|
-
|
|
18955
|
+
const imageRefs = [];
|
|
18956
|
+
for (const blip of findElements(doc.documentElement, "blip")) {
|
|
18957
|
+
const embedId = getAttr(blip, "embed");
|
|
18958
|
+
if (embedId) imageRefs.push(embedId);
|
|
18959
|
+
}
|
|
18960
|
+
for (const imagedata of collectNonFallbackImagedata(doc.documentElement)) {
|
|
18961
|
+
const embedId = getAttr(imagedata, "id");
|
|
18962
|
+
if (embedId) imageRefs.push(embedId);
|
|
18963
|
+
}
|
|
18964
|
+
for (const embedId of imageRefs) {
|
|
18965
|
+
if (imageMap.has(embedId)) continue;
|
|
18966
|
+
const target = rels.get(embedId);
|
|
18967
|
+
if (!target) continue;
|
|
18968
|
+
const imgPath = target.startsWith("/") ? target.slice(1) : target.startsWith("word/") ? target : `word/${target}`;
|
|
18969
|
+
const imgFile = zip.file(imgPath);
|
|
18970
|
+
if (!imgFile) continue;
|
|
18971
|
+
try {
|
|
18972
|
+
const data = await imgFile.async("uint8array");
|
|
18973
|
+
imgIdx++;
|
|
18974
|
+
const ext = imgPath.split(".").pop()?.toLowerCase() ?? "png";
|
|
18975
|
+
const mimeMap = {
|
|
18976
|
+
png: "image/png",
|
|
18977
|
+
jpg: "image/jpeg",
|
|
18978
|
+
jpeg: "image/jpeg",
|
|
18979
|
+
gif: "image/gif",
|
|
18980
|
+
bmp: "image/bmp",
|
|
18981
|
+
wmf: "image/wmf",
|
|
18982
|
+
emf: "image/emf"
|
|
18983
|
+
};
|
|
18984
|
+
const filename = `image_${String(imgIdx).padStart(3, "0")}.${ext}`;
|
|
18985
|
+
images.push({ filename, data, mimeType: mimeMap[ext] ?? "image/png" });
|
|
18986
|
+
imageMap.set(embedId, filename);
|
|
18987
|
+
} catch (err) {
|
|
18988
|
+
warnings.push({
|
|
18989
|
+
code: "SKIPPED_IMAGE",
|
|
18990
|
+
message: `DOCX \uC774\uBBF8\uC9C0 \uCD94\uCD9C \uC2E4\uD328 (${imgPath}): ${err instanceof Error ? err.message : String(err)}`
|
|
18991
|
+
});
|
|
19036
18992
|
}
|
|
19037
18993
|
}
|
|
19038
|
-
return {
|
|
18994
|
+
return { imageMap, images };
|
|
18995
|
+
}
|
|
18996
|
+
function collectParagraphBlips(node, out = [], depth = 0) {
|
|
18997
|
+
if (depth > 40) return out;
|
|
18998
|
+
for (const el of effectiveChildElements(node)) {
|
|
18999
|
+
if (matchesLocal(el, "txbxContent") || matchesLocal(el, "Fallback")) continue;
|
|
19000
|
+
if (matchesLocal(el, "blip") || matchesLocal(el, "imagedata")) out.push(el);
|
|
19001
|
+
else collectParagraphBlips(el, out, depth + 1);
|
|
19002
|
+
}
|
|
19003
|
+
return out;
|
|
19004
|
+
}
|
|
19005
|
+
function collectNonFallbackImagedata(node, out = [], depth = 0) {
|
|
19006
|
+
if (depth > 60) return out;
|
|
19007
|
+
const children = node.childNodes;
|
|
19008
|
+
for (let i = 0; i < children.length; i++) {
|
|
19009
|
+
if (children[i].nodeType !== 1) continue;
|
|
19010
|
+
const el = children[i];
|
|
19011
|
+
if (matchesLocal(el, "Fallback")) continue;
|
|
19012
|
+
if (matchesLocal(el, "imagedata")) out.push(el);
|
|
19013
|
+
else collectNonFallbackImagedata(el, out, depth + 1);
|
|
19014
|
+
}
|
|
19015
|
+
return out;
|
|
19016
|
+
}
|
|
19017
|
+
function emitParagraphImages(p, imageMap, linked, out) {
|
|
19018
|
+
for (const blip of collectParagraphBlips(p)) {
|
|
19019
|
+
const embedId = getAttr(blip, "embed") ?? getAttr(blip, "id");
|
|
19020
|
+
if (!embedId) continue;
|
|
19021
|
+
const filename = imageMap.get(embedId);
|
|
19022
|
+
if (!filename) continue;
|
|
19023
|
+
linked.add(embedId);
|
|
19024
|
+
out.push({ type: "image", text: filename });
|
|
19025
|
+
}
|
|
19039
19026
|
}
|
|
19040
19027
|
async function parseDocxDocument(buffer, options) {
|
|
19041
19028
|
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE4);
|
|
@@ -19092,6 +19079,8 @@ async function parseDocxDocument(buffer, options) {
|
|
|
19092
19079
|
if (body.length === 0) {
|
|
19093
19080
|
throw new KordocError("DOCX \uBCF8\uBB38(w:body)\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
19094
19081
|
}
|
|
19082
|
+
const { imageMap, images } = await buildImageMap(zip, rels, doc, warnings);
|
|
19083
|
+
const linkedImages = /* @__PURE__ */ new Set();
|
|
19095
19084
|
const blocks = [];
|
|
19096
19085
|
const bodyEl = body[0];
|
|
19097
19086
|
const topLevel = effectiveChildElements(bodyEl);
|
|
@@ -19100,16 +19089,20 @@ async function parseDocxDocument(buffer, options) {
|
|
|
19100
19089
|
if (localName2 === "p") {
|
|
19101
19090
|
const block = parseParagraph2(el, styles, numbering, footnotes, rels);
|
|
19102
19091
|
if (block) blocks.push(block);
|
|
19092
|
+
if (imageMap.size > 0) emitParagraphImages(el, imageMap, linkedImages, blocks);
|
|
19103
19093
|
for (const tp of collectTextboxParagraphs(el)) {
|
|
19104
19094
|
const tb = parseParagraph2(tp, styles, numbering, footnotes, rels);
|
|
19105
19095
|
if (tb) blocks.push(tb);
|
|
19096
|
+
if (imageMap.size > 0) emitParagraphImages(tp, imageMap, linkedImages, blocks);
|
|
19106
19097
|
}
|
|
19107
19098
|
} else if (localName2 === "tbl") {
|
|
19108
19099
|
const block = parseTable(el, styles, numbering, footnotes, rels);
|
|
19109
19100
|
if (block) blocks.push(block);
|
|
19110
19101
|
}
|
|
19111
19102
|
}
|
|
19112
|
-
const
|
|
19103
|
+
for (const [embedId, filename] of imageMap) {
|
|
19104
|
+
if (!linkedImages.has(embedId)) blocks.push({ type: "image", text: filename });
|
|
19105
|
+
}
|
|
19113
19106
|
const metadata = {};
|
|
19114
19107
|
const coreFile = zip.file("docProps/core.xml");
|
|
19115
19108
|
if (coreFile) {
|
|
@@ -25102,16 +25095,16 @@ function buildOrigUnits(blocks) {
|
|
|
25102
25095
|
for (let i = 0; i < blocks.length; i++) {
|
|
25103
25096
|
const block = blocks[i];
|
|
25104
25097
|
let consume = 1;
|
|
25105
|
-
let
|
|
25098
|
+
let chunk;
|
|
25106
25099
|
if (block.type === "paragraph" && block.text && /^\[별표\s*\d+/.test(sanitizeText(block.text))) {
|
|
25107
25100
|
const next = blocks[i + 1];
|
|
25108
25101
|
if (next?.type === "paragraph" && next.text && /관련\)?$/.test(next.text)) consume = 2;
|
|
25109
|
-
|
|
25102
|
+
chunk = blocksToMarkdown(blocks.slice(i, i + consume));
|
|
25110
25103
|
} else {
|
|
25111
|
-
|
|
25104
|
+
chunk = blocksToMarkdown([block]);
|
|
25112
25105
|
}
|
|
25113
|
-
if (
|
|
25114
|
-
const subUnits = splitMarkdownUnits(
|
|
25106
|
+
if (chunk) {
|
|
25107
|
+
const subUnits = splitMarkdownUnits(chunk);
|
|
25115
25108
|
const isFragment = consume === 2 || (block.type === "paragraph" || block.type === "heading") && subUnits.length > 1;
|
|
25116
25109
|
for (let s = 0; s < subUnits.length; s++) {
|
|
25117
25110
|
const u = { ...subUnits[s], blockIdx: i, fragment: isFragment || void 0 };
|
|
@@ -27103,7 +27096,7 @@ async function parseHwp(buffer, options) {
|
|
|
27103
27096
|
async function parsePdf(buffer, options) {
|
|
27104
27097
|
let parsePdfDocument;
|
|
27105
27098
|
try {
|
|
27106
|
-
const mod = await import("./parser-
|
|
27099
|
+
const mod = await import("./parser-TCTCSBYZ.js");
|
|
27107
27100
|
parsePdfDocument = mod.parsePdfDocument;
|
|
27108
27101
|
} catch {
|
|
27109
27102
|
return {
|
|
@@ -27114,8 +27107,8 @@ async function parsePdf(buffer, options) {
|
|
|
27114
27107
|
};
|
|
27115
27108
|
}
|
|
27116
27109
|
try {
|
|
27117
|
-
const { markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary } = await parsePdfDocument(buffer, options);
|
|
27118
|
-
return { success: true, fileType: "pdf", markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary };
|
|
27110
|
+
const { markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary, images } = await parsePdfDocument(buffer, options);
|
|
27111
|
+
return { success: true, fileType: "pdf", markdown, blocks, metadata, outline, warnings, isImageBased, pageQuality, qualitySummary, images };
|
|
27119
27112
|
} catch (err) {
|
|
27120
27113
|
const isImageBased = err instanceof Error && "isImageBased" in err ? true : void 0;
|
|
27121
27114
|
return { success: false, fileType: "pdf", error: err instanceof Error ? err.message : "PDF \uD30C\uC2F1 \uC2E4\uD328", code: classifyError(err), isImageBased };
|
|
@@ -27236,4 +27229,4 @@ export {
|
|
|
27236
27229
|
parseHwpml,
|
|
27237
27230
|
fillForm
|
|
27238
27231
|
};
|
|
27239
|
-
//# sourceMappingURL=chunk-
|
|
27232
|
+
//# sourceMappingURL=chunk-7S3M4N4E.js.map
|