kordoc 1.7.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -15
- package/dist/chunk-MOL7MDBG.js +35 -0
- package/dist/chunk-MOL7MDBG.js.map +1 -0
- package/dist/{chunk-NJ3R7LNR.js → chunk-QQ6PZADA.js} +1120 -230
- package/dist/chunk-QQ6PZADA.js.map +1 -0
- package/dist/chunk-UUKFY5P5.js +93 -0
- package/dist/chunk-UUKFY5P5.js.map +1 -0
- package/dist/cli.js +11 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1208 -191
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -5
- package/dist/index.d.ts +17 -5
- package/dist/index.js +1203 -190
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +10 -7
- package/dist/mcp.js.map +1 -1
- package/dist/page-range-737B4EZW.js +8 -0
- package/dist/page-range-737B4EZW.js.map +1 -0
- package/dist/provider-A4FHJSID.js +0 -0
- package/dist/utils-OTCR2KMY.js +22 -0
- package/dist/utils-OTCR2KMY.js.map +1 -0
- package/dist/{watch-AKTZTPVF.js → watch-JFDOENIO.js} +13 -5
- package/dist/watch-JFDOENIO.js.map +1 -0
- package/package.json +77 -75
- package/dist/chunk-NJ3R7LNR.js.map +0 -1
- package/dist/watch-AKTZTPVF.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,44 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
));
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
|
+
// src/page-range.ts
|
|
34
|
+
var page_range_exports = {};
|
|
35
|
+
__export(page_range_exports, {
|
|
36
|
+
parsePageRange: () => parsePageRange
|
|
37
|
+
});
|
|
38
|
+
function parsePageRange(spec, maxPages) {
|
|
39
|
+
const result = /* @__PURE__ */ new Set();
|
|
40
|
+
if (maxPages <= 0) return result;
|
|
41
|
+
if (Array.isArray(spec)) {
|
|
42
|
+
for (const n of spec) {
|
|
43
|
+
const page = Math.round(n);
|
|
44
|
+
if (page >= 1 && page <= maxPages) result.add(page);
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
if (typeof spec !== "string" || spec.trim() === "") return result;
|
|
49
|
+
const parts = spec.split(",");
|
|
50
|
+
for (const part of parts) {
|
|
51
|
+
const trimmed = part.trim();
|
|
52
|
+
if (!trimmed) continue;
|
|
53
|
+
const rangeMatch = trimmed.match(/^(\d+)\s*-\s*(\d+)$/);
|
|
54
|
+
if (rangeMatch) {
|
|
55
|
+
const start = Math.max(1, parseInt(rangeMatch[1], 10));
|
|
56
|
+
const end = Math.min(maxPages, parseInt(rangeMatch[2], 10));
|
|
57
|
+
for (let i = start; i <= end; i++) result.add(i);
|
|
58
|
+
} else {
|
|
59
|
+
const page = parseInt(trimmed, 10);
|
|
60
|
+
if (!isNaN(page) && page >= 1 && page <= maxPages) result.add(page);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
var init_page_range = __esm({
|
|
66
|
+
"src/page-range.ts"() {
|
|
67
|
+
"use strict";
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
33
71
|
// src/ocr/provider.ts
|
|
34
72
|
var provider_exports = {};
|
|
35
73
|
__export(provider_exports, {
|
|
@@ -79,28 +117,36 @@ __export(index_exports, {
|
|
|
79
117
|
blocksToMarkdown: () => blocksToMarkdown,
|
|
80
118
|
compare: () => compare,
|
|
81
119
|
detectFormat: () => detectFormat,
|
|
120
|
+
detectZipFormat: () => detectZipFormat,
|
|
82
121
|
diffBlocks: () => diffBlocks,
|
|
83
122
|
extractFormFields: () => extractFormFields,
|
|
84
123
|
isHwpxFile: () => isHwpxFile,
|
|
85
124
|
isOldHwpFile: () => isOldHwpFile,
|
|
86
125
|
isPdfFile: () => isPdfFile,
|
|
126
|
+
isZipFile: () => isZipFile,
|
|
87
127
|
markdownToHwpx: () => markdownToHwpx,
|
|
88
128
|
parse: () => parse,
|
|
129
|
+
parseDocx: () => parseDocx,
|
|
89
130
|
parseHwp: () => parseHwp,
|
|
90
131
|
parseHwpx: () => parseHwpx,
|
|
91
|
-
parsePdf: () => parsePdf
|
|
132
|
+
parsePdf: () => parsePdf,
|
|
133
|
+
parseXlsx: () => parseXlsx
|
|
92
134
|
});
|
|
93
135
|
module.exports = __toCommonJS(index_exports);
|
|
94
136
|
var import_promises = require("fs/promises");
|
|
95
137
|
|
|
96
138
|
// src/detect.ts
|
|
139
|
+
var import_jszip = __toESM(require("jszip"), 1);
|
|
97
140
|
function magicBytes(buffer) {
|
|
98
141
|
return new Uint8Array(buffer, 0, Math.min(4, buffer.byteLength));
|
|
99
142
|
}
|
|
100
|
-
function
|
|
143
|
+
function isZipFile(buffer) {
|
|
101
144
|
const b = magicBytes(buffer);
|
|
102
145
|
return b[0] === 80 && b[1] === 75 && b[2] === 3 && b[3] === 4;
|
|
103
146
|
}
|
|
147
|
+
function isHwpxFile(buffer) {
|
|
148
|
+
return isZipFile(buffer);
|
|
149
|
+
}
|
|
104
150
|
function isOldHwpFile(buffer) {
|
|
105
151
|
const b = magicBytes(buffer);
|
|
106
152
|
return b[0] === 208 && b[1] === 207 && b[2] === 17 && b[3] === 224;
|
|
@@ -111,14 +157,27 @@ function isPdfFile(buffer) {
|
|
|
111
157
|
}
|
|
112
158
|
function detectFormat(buffer) {
|
|
113
159
|
if (buffer.byteLength < 4) return "unknown";
|
|
114
|
-
if (
|
|
160
|
+
if (isZipFile(buffer)) return "hwpx";
|
|
115
161
|
if (isOldHwpFile(buffer)) return "hwp";
|
|
116
162
|
if (isPdfFile(buffer)) return "pdf";
|
|
117
163
|
return "unknown";
|
|
118
164
|
}
|
|
165
|
+
async function detectZipFormat(buffer) {
|
|
166
|
+
try {
|
|
167
|
+
const zip = await import_jszip.default.loadAsync(buffer);
|
|
168
|
+
if (zip.file("xl/workbook.xml")) return "xlsx";
|
|
169
|
+
if (zip.file("word/document.xml")) return "docx";
|
|
170
|
+
if (zip.file("Contents/content.hpf") || zip.file("mimetype")) return "hwpx";
|
|
171
|
+
const hasSection = Object.keys(zip.files).some((f) => f.startsWith("Contents/"));
|
|
172
|
+
if (hasSection) return "hwpx";
|
|
173
|
+
return "unknown";
|
|
174
|
+
} catch {
|
|
175
|
+
return "unknown";
|
|
176
|
+
}
|
|
177
|
+
}
|
|
119
178
|
|
|
120
179
|
// src/hwpx/parser.ts
|
|
121
|
-
var
|
|
180
|
+
var import_jszip2 = __toESM(require("jszip"), 1);
|
|
122
181
|
var import_zlib = require("zlib");
|
|
123
182
|
var import_xmldom = require("@xmldom/xmldom");
|
|
124
183
|
|
|
@@ -178,6 +237,16 @@ function buildTable(rows) {
|
|
|
178
237
|
cellIdx++;
|
|
179
238
|
}
|
|
180
239
|
}
|
|
240
|
+
let effectiveCols = maxCols;
|
|
241
|
+
while (effectiveCols > 0) {
|
|
242
|
+
const colEmpty = grid.every((row) => !row[effectiveCols - 1]?.text?.trim());
|
|
243
|
+
if (!colEmpty) break;
|
|
244
|
+
effectiveCols--;
|
|
245
|
+
}
|
|
246
|
+
if (effectiveCols < maxCols && effectiveCols > 0) {
|
|
247
|
+
const trimmed = grid.map((row) => row.slice(0, effectiveCols));
|
|
248
|
+
return { rows: numRows, cols: effectiveCols, cells: trimmed, hasHeader: numRows > 1 };
|
|
249
|
+
}
|
|
181
250
|
return { rows: numRows, cols: maxCols, cells: grid, hasHeader: numRows > 1 };
|
|
182
251
|
}
|
|
183
252
|
function convertTableToText(rows) {
|
|
@@ -185,13 +254,26 @@ function convertTableToText(rows) {
|
|
|
185
254
|
(row) => row.map((c) => c.text.trim().replace(/\n/g, " ")).filter(Boolean).join(" | ")
|
|
186
255
|
).filter(Boolean).join("\n");
|
|
187
256
|
}
|
|
257
|
+
var HWP_SHAPE_ALT_TEXT_RE = /(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?/g;
|
|
258
|
+
function sanitizeText(text) {
|
|
259
|
+
let result = text.replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
|
|
260
|
+
if (result.length <= 30 && result.includes(" ")) {
|
|
261
|
+
const tokens = result.split(" ");
|
|
262
|
+
const koreanSingleCharCount = tokens.filter((t) => t.length === 1 && /[\uAC00-\uD7AF\u3131-\u318E]/.test(t)).length;
|
|
263
|
+
if (tokens.length >= 3 && koreanSingleCharCount / tokens.length >= 0.7) {
|
|
264
|
+
result = tokens.join("");
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
188
269
|
function blocksToMarkdown(blocks) {
|
|
189
270
|
const lines = [];
|
|
190
271
|
for (let i = 0; i < blocks.length; i++) {
|
|
191
272
|
const block = blocks[i];
|
|
192
273
|
if (block.type === "heading" && block.text) {
|
|
193
274
|
const prefix = "#".repeat(Math.min(block.level || 2, 6));
|
|
194
|
-
|
|
275
|
+
const headingText = sanitizeText(block.text);
|
|
276
|
+
if (headingText) lines.push("", `${prefix} ${headingText}`, "");
|
|
195
277
|
continue;
|
|
196
278
|
}
|
|
197
279
|
if (block.type === "image" && block.text) {
|
|
@@ -203,9 +285,11 @@ function blocksToMarkdown(blocks) {
|
|
|
203
285
|
continue;
|
|
204
286
|
}
|
|
205
287
|
if (block.type === "list" && block.text) {
|
|
206
|
-
const
|
|
288
|
+
const listText = sanitizeText(block.text);
|
|
289
|
+
if (!listText) continue;
|
|
290
|
+
const alreadyNumbered = block.listType === "ordered" && /^\d+\.\s/.test(listText);
|
|
207
291
|
const prefix = alreadyNumbered ? "" : block.listType === "ordered" ? "1. " : "- ";
|
|
208
|
-
lines.push(`${prefix}${
|
|
292
|
+
lines.push(`${prefix}${listText}`);
|
|
209
293
|
if (block.children) {
|
|
210
294
|
for (const child of block.children) {
|
|
211
295
|
const childPrefix = child.listType === "ordered" ? "1." : "-";
|
|
@@ -215,7 +299,8 @@ function blocksToMarkdown(blocks) {
|
|
|
215
299
|
continue;
|
|
216
300
|
}
|
|
217
301
|
if (block.type === "paragraph" && block.text) {
|
|
218
|
-
let text = block.text;
|
|
302
|
+
let text = sanitizeText(block.text);
|
|
303
|
+
if (!text) continue;
|
|
219
304
|
if (/^\[별표\s*\d+/.test(text)) {
|
|
220
305
|
const nextBlock = blocks[i + 1];
|
|
221
306
|
if (nextBlock?.type === "paragraph" && nextBlock.text && /관련\)?$/.test(nextBlock.text)) {
|
|
@@ -252,7 +337,7 @@ function tableToMarkdown(table) {
|
|
|
252
337
|
if (table.rows === 0 || table.cols === 0) return "";
|
|
253
338
|
const { cells, rows: numRows, cols: numCols } = table;
|
|
254
339
|
if (numRows === 1 && numCols === 1) {
|
|
255
|
-
const content = cells[0][0].text;
|
|
340
|
+
const content = sanitizeText(cells[0][0].text);
|
|
256
341
|
return content.split(/\n/).map((line) => {
|
|
257
342
|
const trimmed = line.trim();
|
|
258
343
|
if (!trimmed) return "";
|
|
@@ -261,13 +346,19 @@ function tableToMarkdown(table) {
|
|
|
261
346
|
return trimmed;
|
|
262
347
|
}).filter(Boolean).join("\n");
|
|
263
348
|
}
|
|
349
|
+
if (numCols === 1 && numRows >= 2) {
|
|
350
|
+
return cells.map((row) => sanitizeText(row[0].text).replace(/\n/g, " ")).filter(Boolean).join("\n");
|
|
351
|
+
}
|
|
264
352
|
const display = Array.from({ length: numRows }, () => Array(numCols).fill(""));
|
|
265
353
|
const skip = /* @__PURE__ */ new Set();
|
|
266
354
|
for (let r = 0; r < numRows; r++) {
|
|
355
|
+
let cellIdx = 0;
|
|
267
356
|
for (let c = 0; c < numCols; c++) {
|
|
268
357
|
if (skip.has(`${r},${c}`)) continue;
|
|
269
|
-
const cell = cells[r][
|
|
270
|
-
|
|
358
|
+
const cell = cells[r]?.[cellIdx];
|
|
359
|
+
if (!cell) break;
|
|
360
|
+
cellIdx++;
|
|
361
|
+
display[r][c] = sanitizeText(cell.text).replace(/\n/g, "<br>");
|
|
271
362
|
for (let dr = 0; dr < cell.rowSpan; dr++) {
|
|
272
363
|
for (let dc = 0; dc < cell.colSpan; dc++) {
|
|
273
364
|
if (dr === 0 && dc === 0) continue;
|
|
@@ -276,12 +367,28 @@ function tableToMarkdown(table) {
|
|
|
276
367
|
}
|
|
277
368
|
}
|
|
278
369
|
}
|
|
370
|
+
c += cell.colSpan - 1;
|
|
279
371
|
}
|
|
280
372
|
}
|
|
281
373
|
const uniqueRows = [];
|
|
282
|
-
|
|
374
|
+
let pendingFirstCol = "";
|
|
375
|
+
for (let r = 0; r < display.length; r++) {
|
|
376
|
+
const row = display[r];
|
|
283
377
|
const isEmptyPlaceholder = row.every((cell) => cell === "");
|
|
284
|
-
if (
|
|
378
|
+
if (isEmptyPlaceholder) continue;
|
|
379
|
+
const hasSkippedCols = row.some((cell, c) => cell === "" && skip.has(`${r},${c}`));
|
|
380
|
+
const nonEmptyCols = row.filter((cell) => cell !== "");
|
|
381
|
+
if (!hasSkippedCols && nonEmptyCols.length === 1 && row[0] !== "" && row.slice(1).every((c) => c === "")) {
|
|
382
|
+
pendingFirstCol = row[0];
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (pendingFirstCol && row[0] === "") {
|
|
386
|
+
row[0] = pendingFirstCol;
|
|
387
|
+
pendingFirstCol = "";
|
|
388
|
+
} else {
|
|
389
|
+
pendingFirstCol = "";
|
|
390
|
+
}
|
|
391
|
+
uniqueRows.push(row);
|
|
285
392
|
}
|
|
286
393
|
if (uniqueRows.length === 0) return "";
|
|
287
394
|
const md = [];
|
|
@@ -293,8 +400,13 @@ function tableToMarkdown(table) {
|
|
|
293
400
|
return md.join("\n");
|
|
294
401
|
}
|
|
295
402
|
|
|
403
|
+
// src/types.ts
|
|
404
|
+
var HEADING_RATIO_H1 = 1.5;
|
|
405
|
+
var HEADING_RATIO_H2 = 1.3;
|
|
406
|
+
var HEADING_RATIO_H3 = 1.15;
|
|
407
|
+
|
|
296
408
|
// src/utils.ts
|
|
297
|
-
var VERSION = true ? "1.
|
|
409
|
+
var VERSION = true ? "1.8.0" : "0.0.0-dev";
|
|
298
410
|
function toArrayBuffer(buf) {
|
|
299
411
|
if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
|
|
300
412
|
return buf.buffer;
|
|
@@ -312,6 +424,50 @@ function isPathTraversal(name) {
|
|
|
312
424
|
const normalized = name.replace(/\\/g, "/");
|
|
313
425
|
return normalized.includes("..") || normalized.startsWith("/") || /^[A-Za-z]:/.test(normalized);
|
|
314
426
|
}
|
|
427
|
+
function precheckZipSize(buffer, maxUncompressedSize = 100 * 1024 * 1024, maxEntries = 500) {
|
|
428
|
+
try {
|
|
429
|
+
const data = new DataView(buffer);
|
|
430
|
+
const len = buffer.byteLength;
|
|
431
|
+
let eocdOffset = -1;
|
|
432
|
+
for (let i = len - 22; i >= Math.max(0, len - 65557); i--) {
|
|
433
|
+
if (data.getUint32(i, true) === 101010256) {
|
|
434
|
+
eocdOffset = i;
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
if (eocdOffset < 0) return { totalUncompressed: 0, entryCount: 0 };
|
|
439
|
+
const entryCount = data.getUint16(eocdOffset + 10, true);
|
|
440
|
+
if (entryCount > maxEntries) {
|
|
441
|
+
throw new KordocError(`ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC: ${entryCount} (\uCD5C\uB300 ${maxEntries})`);
|
|
442
|
+
}
|
|
443
|
+
const cdSize = data.getUint32(eocdOffset + 12, true);
|
|
444
|
+
const cdOffset = data.getUint32(eocdOffset + 16, true);
|
|
445
|
+
if (cdOffset + cdSize > len) return { totalUncompressed: 0, entryCount };
|
|
446
|
+
let totalUncompressed = 0;
|
|
447
|
+
let pos = cdOffset;
|
|
448
|
+
for (let i = 0; i < entryCount && pos + 46 <= cdOffset + cdSize; i++) {
|
|
449
|
+
if (data.getUint32(pos, true) !== 33639248) break;
|
|
450
|
+
totalUncompressed += data.getUint32(pos + 24, true);
|
|
451
|
+
const nameLen = data.getUint16(pos + 28, true);
|
|
452
|
+
const extraLen = data.getUint16(pos + 30, true);
|
|
453
|
+
const commentLen = data.getUint16(pos + 32, true);
|
|
454
|
+
pos += 46 + nameLen + extraLen + commentLen;
|
|
455
|
+
}
|
|
456
|
+
if (totalUncompressed > maxUncompressedSize) {
|
|
457
|
+
throw new KordocError(`ZIP \uBE44\uC555\uCD95 \uD06C\uAE30 \uCD08\uACFC: ${(totalUncompressed / 1024 / 1024).toFixed(1)}MB (\uCD5C\uB300 ${maxUncompressedSize / 1024 / 1024}MB)`);
|
|
458
|
+
}
|
|
459
|
+
return { totalUncompressed, entryCount };
|
|
460
|
+
} catch (err) {
|
|
461
|
+
if (err instanceof KordocError) throw err;
|
|
462
|
+
return { totalUncompressed: 0, entryCount: 0 };
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
var SAFE_HREF_RE2 = /^(?:https?:|mailto:|tel:|#)/i;
|
|
466
|
+
function sanitizeHref2(href) {
|
|
467
|
+
const trimmed = href.trim();
|
|
468
|
+
if (!trimmed || !SAFE_HREF_RE2.test(trimmed)) return null;
|
|
469
|
+
return trimmed;
|
|
470
|
+
}
|
|
315
471
|
function classifyError(err) {
|
|
316
472
|
if (!(err instanceof Error)) return "PARSE_ERROR";
|
|
317
473
|
const msg = err.message;
|
|
@@ -325,36 +481,8 @@ function classifyError(err) {
|
|
|
325
481
|
return "PARSE_ERROR";
|
|
326
482
|
}
|
|
327
483
|
|
|
328
|
-
// src/page-range.ts
|
|
329
|
-
function parsePageRange(spec, maxPages) {
|
|
330
|
-
const result = /* @__PURE__ */ new Set();
|
|
331
|
-
if (maxPages <= 0) return result;
|
|
332
|
-
if (Array.isArray(spec)) {
|
|
333
|
-
for (const n of spec) {
|
|
334
|
-
const page = Math.round(n);
|
|
335
|
-
if (page >= 1 && page <= maxPages) result.add(page);
|
|
336
|
-
}
|
|
337
|
-
return result;
|
|
338
|
-
}
|
|
339
|
-
if (typeof spec !== "string" || spec.trim() === "") return result;
|
|
340
|
-
const parts = spec.split(",");
|
|
341
|
-
for (const part of parts) {
|
|
342
|
-
const trimmed = part.trim();
|
|
343
|
-
if (!trimmed) continue;
|
|
344
|
-
const rangeMatch = trimmed.match(/^(\d+)\s*-\s*(\d+)$/);
|
|
345
|
-
if (rangeMatch) {
|
|
346
|
-
const start = Math.max(1, parseInt(rangeMatch[1], 10));
|
|
347
|
-
const end = Math.min(maxPages, parseInt(rangeMatch[2], 10));
|
|
348
|
-
for (let i = start; i <= end; i++) result.add(i);
|
|
349
|
-
} else {
|
|
350
|
-
const page = parseInt(trimmed, 10);
|
|
351
|
-
if (!isNaN(page) && page >= 1 && page <= maxPages) result.add(page);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return result;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
484
|
// src/hwpx/parser.ts
|
|
485
|
+
init_page_range();
|
|
358
486
|
var MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024;
|
|
359
487
|
var MAX_ZIP_ENTRIES = 500;
|
|
360
488
|
function clampSpan(val, max) {
|
|
@@ -446,16 +574,10 @@ function stripDtd(xml) {
|
|
|
446
574
|
return xml.replace(/<!DOCTYPE\s[^[>]*(\[[\s\S]*?\])?\s*>/gi, "");
|
|
447
575
|
}
|
|
448
576
|
async function parseHwpxDocument(buffer, options) {
|
|
449
|
-
|
|
450
|
-
if (precheck.totalUncompressed > MAX_DECOMPRESS_SIZE) {
|
|
451
|
-
throw new KordocError("ZIP \uBE44\uC555\uCD95 \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
452
|
-
}
|
|
453
|
-
if (precheck.entryCount > MAX_ZIP_ENTRIES) {
|
|
454
|
-
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
455
|
-
}
|
|
577
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
456
578
|
let zip;
|
|
457
579
|
try {
|
|
458
|
-
zip = await
|
|
580
|
+
zip = await import_jszip2.default.loadAsync(buffer);
|
|
459
581
|
} catch {
|
|
460
582
|
return extractFromBrokenZip(buffer);
|
|
461
583
|
}
|
|
@@ -615,46 +737,17 @@ function parseDublinCoreMetadata(xml, metadata) {
|
|
|
615
737
|
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
616
738
|
}
|
|
617
739
|
}
|
|
618
|
-
function precheckZipSize(buffer) {
|
|
619
|
-
try {
|
|
620
|
-
const data = new DataView(buffer);
|
|
621
|
-
const len = buffer.byteLength;
|
|
622
|
-
if (len < 22) return { totalUncompressed: 0, entryCount: 0 };
|
|
623
|
-
const searchStart = Math.max(0, len - 22 - 65535);
|
|
624
|
-
let eocdOffset = -1;
|
|
625
|
-
for (let i = len - 22; i >= searchStart; i--) {
|
|
626
|
-
if (data.getUint32(i, true) === 101010256) {
|
|
627
|
-
eocdOffset = i;
|
|
628
|
-
break;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
if (eocdOffset < 0) return { totalUncompressed: 0, entryCount: 0 };
|
|
632
|
-
const entryCount = data.getUint16(eocdOffset + 10, true);
|
|
633
|
-
const cdSize = data.getUint32(eocdOffset + 12, true);
|
|
634
|
-
const cdOffset = data.getUint32(eocdOffset + 16, true);
|
|
635
|
-
if (cdOffset + cdSize > len) return { totalUncompressed: 0, entryCount };
|
|
636
|
-
let totalUncompressed = 0;
|
|
637
|
-
let pos = cdOffset;
|
|
638
|
-
for (let i = 0; i < entryCount && pos + 46 <= cdOffset + cdSize; i++) {
|
|
639
|
-
if (data.getUint32(pos, true) !== 33639248) break;
|
|
640
|
-
totalUncompressed += data.getUint32(pos + 24, true);
|
|
641
|
-
const nameLen = data.getUint16(pos + 28, true);
|
|
642
|
-
const extraLen = data.getUint16(pos + 30, true);
|
|
643
|
-
const commentLen = data.getUint16(pos + 32, true);
|
|
644
|
-
pos += 46 + nameLen + extraLen + commentLen;
|
|
645
|
-
}
|
|
646
|
-
return { totalUncompressed, entryCount };
|
|
647
|
-
} catch {
|
|
648
|
-
return { totalUncompressed: 0, entryCount: 0 };
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
740
|
function extractFromBrokenZip(buffer) {
|
|
652
741
|
const data = new Uint8Array(buffer);
|
|
653
742
|
const view = new DataView(buffer);
|
|
654
743
|
let pos = 0;
|
|
655
744
|
const blocks = [];
|
|
745
|
+
const warnings = [
|
|
746
|
+
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
747
|
+
];
|
|
656
748
|
let totalDecompressed = 0;
|
|
657
749
|
let entryCount = 0;
|
|
750
|
+
let sectionNum = 0;
|
|
658
751
|
while (pos < data.length - 30) {
|
|
659
752
|
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
660
753
|
pos++;
|
|
@@ -700,14 +793,15 @@ function extractFromBrokenZip(buffer) {
|
|
|
700
793
|
}
|
|
701
794
|
totalDecompressed += content.length * 2;
|
|
702
795
|
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
703
|
-
|
|
796
|
+
sectionNum++;
|
|
797
|
+
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum));
|
|
704
798
|
} catch {
|
|
705
799
|
continue;
|
|
706
800
|
}
|
|
707
801
|
}
|
|
708
802
|
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");
|
|
709
803
|
const markdown = blocksToMarkdown(blocks);
|
|
710
|
-
return { markdown, blocks };
|
|
804
|
+
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
711
805
|
}
|
|
712
806
|
async function resolveSectionPaths(zip) {
|
|
713
807
|
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
@@ -771,9 +865,9 @@ function detectHwpxHeadings(blocks, styleMap) {
|
|
|
771
865
|
let level = 0;
|
|
772
866
|
if (baseFontSize > 0 && block.style?.fontSize) {
|
|
773
867
|
const ratio = block.style.fontSize / baseFontSize;
|
|
774
|
-
if (ratio >=
|
|
775
|
-
else if (ratio >=
|
|
776
|
-
else if (ratio >=
|
|
868
|
+
if (ratio >= HEADING_RATIO_H1) level = 1;
|
|
869
|
+
else if (ratio >= HEADING_RATIO_H2) level = 2;
|
|
870
|
+
else if (ratio >= HEADING_RATIO_H3) level = 3;
|
|
777
871
|
}
|
|
778
872
|
if (/^제\d+[조장절편]/.test(text) && text.length <= 50) {
|
|
779
873
|
if (level === 0) level = 3;
|
|
@@ -905,39 +999,47 @@ function walkParagraphChildren(node, blocks, tableCtx, tableStack, styleMap, war
|
|
|
905
999
|
if (depth > MAX_XML_DEPTH) return tableCtx;
|
|
906
1000
|
const children = node.childNodes;
|
|
907
1001
|
if (!children) return tableCtx;
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
if (
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
if (
|
|
918
|
-
if (tableStack.
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1002
|
+
const walkChildren = (parent, d) => {
|
|
1003
|
+
if (d > MAX_XML_DEPTH) return;
|
|
1004
|
+
const kids = parent.childNodes;
|
|
1005
|
+
if (!kids) return;
|
|
1006
|
+
for (let i = 0; i < kids.length; i++) {
|
|
1007
|
+
const el = kids[i];
|
|
1008
|
+
if (el.nodeType !== 1) continue;
|
|
1009
|
+
const tag = el.tagName || el.localName || "";
|
|
1010
|
+
const localTag = tag.replace(/^[^:]+:/, "");
|
|
1011
|
+
if (localTag === "tbl") {
|
|
1012
|
+
if (tableCtx) tableStack.push(tableCtx);
|
|
1013
|
+
const newTable = { rows: [], currentRow: [], cell: null };
|
|
1014
|
+
walkSection(el, blocks, newTable, tableStack, styleMap, warnings, sectionNum, d + 1);
|
|
1015
|
+
if (newTable.rows.length > 0) {
|
|
1016
|
+
if (tableStack.length > 0) {
|
|
1017
|
+
const parentTable = tableStack.pop();
|
|
1018
|
+
const nestedText = convertTableToText(newTable.rows);
|
|
1019
|
+
if (parentTable.cell) {
|
|
1020
|
+
parentTable.cell.text += (parentTable.cell.text ? "\n" : "") + nestedText;
|
|
1021
|
+
}
|
|
1022
|
+
tableCtx = parentTable;
|
|
1023
|
+
} else {
|
|
1024
|
+
blocks.push({ type: "table", table: buildTable(newTable.rows), pageNumber: sectionNum });
|
|
1025
|
+
tableCtx = null;
|
|
923
1026
|
}
|
|
924
|
-
tableCtx = parentTable;
|
|
925
1027
|
} else {
|
|
926
|
-
|
|
927
|
-
tableCtx = null;
|
|
1028
|
+
tableCtx = tableStack.length > 0 ? tableStack.pop() : null;
|
|
928
1029
|
}
|
|
929
|
-
} else {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
} else if (
|
|
937
|
-
|
|
1030
|
+
} else if (localTag === "pic" || localTag === "shape" || localTag === "drawingObject") {
|
|
1031
|
+
const imgRef = extractImageRef(el);
|
|
1032
|
+
if (imgRef) {
|
|
1033
|
+
blocks.push({ type: "image", text: imgRef, pageNumber: sectionNum });
|
|
1034
|
+
} else if (warnings && sectionNum) {
|
|
1035
|
+
warnings.push({ page: sectionNum, message: `\uC2A4\uD0B5\uB41C \uC694\uC18C: ${localTag}`, code: "SKIPPED_IMAGE" });
|
|
1036
|
+
}
|
|
1037
|
+
} else if (localTag === "r" || localTag === "run" || localTag === "ctrl") {
|
|
1038
|
+
walkChildren(el, d + 1);
|
|
938
1039
|
}
|
|
939
1040
|
}
|
|
940
|
-
}
|
|
1041
|
+
};
|
|
1042
|
+
walkChildren(node, depth);
|
|
941
1043
|
return tableCtx;
|
|
942
1044
|
}
|
|
943
1045
|
function extractParagraphInfo(para, styleMap) {
|
|
@@ -976,7 +1078,10 @@ function extractParagraphInfo(para, styleMap) {
|
|
|
976
1078
|
// 하이퍼링크
|
|
977
1079
|
case "hyperlink": {
|
|
978
1080
|
const url = child.getAttribute("url") || child.getAttribute("href") || "";
|
|
979
|
-
if (url)
|
|
1081
|
+
if (url) {
|
|
1082
|
+
const safe = sanitizeHref2(url);
|
|
1083
|
+
if (safe) href = safe;
|
|
1084
|
+
}
|
|
980
1085
|
walk(child);
|
|
981
1086
|
break;
|
|
982
1087
|
}
|
|
@@ -989,6 +1094,29 @@ function extractParagraphInfo(para, styleMap) {
|
|
|
989
1094
|
if (noteText) footnote = (footnote ? footnote + "; " : "") + noteText;
|
|
990
1095
|
break;
|
|
991
1096
|
}
|
|
1097
|
+
// 제어 요소 — 필드, 컨트롤, 매개변수 등 스킵
|
|
1098
|
+
case "ctrl":
|
|
1099
|
+
case "fieldBegin":
|
|
1100
|
+
case "fieldEnd":
|
|
1101
|
+
case "parameters":
|
|
1102
|
+
case "stringParam":
|
|
1103
|
+
case "integerParam":
|
|
1104
|
+
case "boolParam":
|
|
1105
|
+
case "floatParam":
|
|
1106
|
+
case "secPr":
|
|
1107
|
+
// 섹션 속성 (페이지 설정 등)
|
|
1108
|
+
case "colPr":
|
|
1109
|
+
// 다단 속성
|
|
1110
|
+
case "linesegarray":
|
|
1111
|
+
case "lineseg":
|
|
1112
|
+
// 레이아웃 정보
|
|
1113
|
+
// 도형/이미지 요소 — 대체텍스트("사각형입니다." 등) 누출 방지
|
|
1114
|
+
case "pic":
|
|
1115
|
+
case "shape":
|
|
1116
|
+
case "drawingObject":
|
|
1117
|
+
case "shapeComment":
|
|
1118
|
+
case "drawText":
|
|
1119
|
+
break;
|
|
992
1120
|
// run 요소에서 charPrIDRef 추출
|
|
993
1121
|
case "r": {
|
|
994
1122
|
const runCharPr = child.getAttribute("charPrIDRef");
|
|
@@ -1003,7 +1131,10 @@ function extractParagraphInfo(para, styleMap) {
|
|
|
1003
1131
|
}
|
|
1004
1132
|
};
|
|
1005
1133
|
walk(para);
|
|
1006
|
-
|
|
1134
|
+
let cleanText = text.replace(/[ \t]+/g, " ").trim();
|
|
1135
|
+
if (/^그림입니다\.?\s*원본\s*그림의\s*(이름|크기)/.test(cleanText)) cleanText = "";
|
|
1136
|
+
cleanText = cleanText.replace(/그림입니다\.?\s*원본\s*그림의\s*(이름|크기)[^\n]*(\n[^\n]*원본\s*그림의\s*(이름|크기)[^\n]*)*/g, "").trim();
|
|
1137
|
+
cleanText = cleanText.replace(/(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|선|직선|곡선|화살표|오각형|육각형|팔각형|별|십자|구름|마름모|도넛|평행사변형|사다리꼴|개체|그리기\s?개체|묶음\s?개체|글상자|수식|표|그림|OLE\s?개체)\s?입니다\.?/g, "").trim();
|
|
1007
1138
|
let style;
|
|
1008
1139
|
if (styleMap && charPrId) {
|
|
1009
1140
|
const charProp = styleMap.charProperties.get(charPrId);
|
|
@@ -1182,6 +1313,7 @@ function extractText(data) {
|
|
|
1182
1313
|
}
|
|
1183
1314
|
|
|
1184
1315
|
// src/hwp5/parser.ts
|
|
1316
|
+
init_page_range();
|
|
1185
1317
|
var import_module = require("module");
|
|
1186
1318
|
var import_meta = {};
|
|
1187
1319
|
var require2 = (0, import_module.createRequire)(import_meta.url);
|
|
@@ -1282,9 +1414,9 @@ function detectHwp5Headings(blocks, docInfo) {
|
|
|
1282
1414
|
if (/^\d+$/.test(text)) continue;
|
|
1283
1415
|
const ratio = block.style.fontSize / baseFontSize;
|
|
1284
1416
|
let level = 0;
|
|
1285
|
-
if (ratio >=
|
|
1286
|
-
else if (ratio >=
|
|
1287
|
-
else if (ratio >=
|
|
1417
|
+
if (ratio >= HEADING_RATIO_H1) level = 1;
|
|
1418
|
+
else if (ratio >= HEADING_RATIO_H2) level = 2;
|
|
1419
|
+
else if (ratio >= HEADING_RATIO_H3) level = 3;
|
|
1288
1420
|
if (/^제\d+[조장절편]/.test(text) && text.length <= 50) {
|
|
1289
1421
|
if (level === 0) level = 3;
|
|
1290
1422
|
}
|
|
@@ -1371,20 +1503,22 @@ function detectImageMime(data) {
|
|
|
1371
1503
|
}
|
|
1372
1504
|
function extractHwp5Images(cfb, blocks, compressed, warnings) {
|
|
1373
1505
|
const binDataMap = /* @__PURE__ */ new Map();
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
if (
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1506
|
+
const binDataRe = /\/BinData\/[Bb][Ii][Nn](\d{4})$/;
|
|
1507
|
+
if (cfb.FileIndex) {
|
|
1508
|
+
for (const entry of cfb.FileIndex) {
|
|
1509
|
+
if (!entry?.name || !entry.content) continue;
|
|
1510
|
+
const match = entry.name.match(binDataRe);
|
|
1511
|
+
if (!match) continue;
|
|
1512
|
+
const idx = parseInt(match[1], 10);
|
|
1513
|
+
let data = Buffer.from(entry.content);
|
|
1514
|
+
if (compressed) {
|
|
1515
|
+
try {
|
|
1516
|
+
data = decompressStream(data);
|
|
1517
|
+
} catch {
|
|
1518
|
+
}
|
|
1385
1519
|
}
|
|
1520
|
+
binDataMap.set(idx, { data, name: entry.name });
|
|
1386
1521
|
}
|
|
1387
|
-
binDataMap.set(idx, { data, name: entry.name || `BIN${idx}` });
|
|
1388
1522
|
}
|
|
1389
1523
|
if (binDataMap.size === 0) return [];
|
|
1390
1524
|
const images = [];
|
|
@@ -1531,6 +1665,16 @@ function parseTableBlock(records, startIdx) {
|
|
|
1531
1665
|
i++;
|
|
1532
1666
|
}
|
|
1533
1667
|
if (rows === 0 || cols === 0 || cells.length === 0) return { table: null, nextIdx: i };
|
|
1668
|
+
const hasAddr = cells.some((c) => c.colAddr !== void 0 && c.rowAddr !== void 0);
|
|
1669
|
+
if (hasAddr) {
|
|
1670
|
+
const cellRows2 = arrangeCells(rows, cols, cells);
|
|
1671
|
+
const irCells = cellRows2.map((row) => row.map((c) => ({
|
|
1672
|
+
text: c.text.trim(),
|
|
1673
|
+
colSpan: c.colSpan,
|
|
1674
|
+
rowSpan: c.rowSpan
|
|
1675
|
+
})));
|
|
1676
|
+
return { table: { rows, cols, cells: irCells, hasHeader: rows > 1 }, nextIdx: i };
|
|
1677
|
+
}
|
|
1534
1678
|
const cellRows = arrangeCells(rows, cols, cells);
|
|
1535
1679
|
return { table: buildTable(cellRows), nextIdx: i };
|
|
1536
1680
|
}
|
|
@@ -1600,6 +1744,9 @@ function arrangeCells(rows, cols, cells) {
|
|
|
1600
1744
|
return grid.map((row) => row.map((c) => c || { text: "", colSpan: 1, rowSpan: 1 }));
|
|
1601
1745
|
}
|
|
1602
1746
|
|
|
1747
|
+
// src/pdf/parser.ts
|
|
1748
|
+
init_page_range();
|
|
1749
|
+
|
|
1603
1750
|
// src/pdf/line-detector.ts
|
|
1604
1751
|
var import_pdf = require("pdfjs-dist/legacy/build/pdf.mjs");
|
|
1605
1752
|
var ORIENTATION_TOL = 2;
|
|
@@ -1794,7 +1941,36 @@ function buildTableGrids(horizontals, verticals) {
|
|
|
1794
1941
|
};
|
|
1795
1942
|
grids.push({ rowYs, colXs, bbox });
|
|
1796
1943
|
}
|
|
1797
|
-
return grids;
|
|
1944
|
+
return mergeAdjacentGrids(grids);
|
|
1945
|
+
}
|
|
1946
|
+
function mergeAdjacentGrids(grids) {
|
|
1947
|
+
if (grids.length <= 1) return grids;
|
|
1948
|
+
const sorted = [...grids].sort((a, b) => b.bbox.y2 - a.bbox.y2);
|
|
1949
|
+
const merged = [sorted[0]];
|
|
1950
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
1951
|
+
const prev = merged[merged.length - 1];
|
|
1952
|
+
const curr = sorted[i];
|
|
1953
|
+
if (prev.colXs.length === curr.colXs.length) {
|
|
1954
|
+
const colMatch = prev.colXs.every((x, ci) => Math.abs(x - curr.colXs[ci]) <= COORD_MERGE_TOL * 3);
|
|
1955
|
+
const verticalGap = prev.bbox.y1 - curr.bbox.y2;
|
|
1956
|
+
if (colMatch && verticalGap >= -COORD_MERGE_TOL && verticalGap <= 20) {
|
|
1957
|
+
const allRowYs = [.../* @__PURE__ */ new Set([...prev.rowYs, ...curr.rowYs])].sort((a, b) => b - a);
|
|
1958
|
+
merged[merged.length - 1] = {
|
|
1959
|
+
rowYs: allRowYs,
|
|
1960
|
+
colXs: prev.colXs,
|
|
1961
|
+
bbox: {
|
|
1962
|
+
x1: Math.min(prev.bbox.x1, curr.bbox.x1),
|
|
1963
|
+
y1: Math.min(prev.bbox.y1, curr.bbox.y1),
|
|
1964
|
+
x2: Math.max(prev.bbox.x2, curr.bbox.x2),
|
|
1965
|
+
y2: Math.max(prev.bbox.y2, curr.bbox.y2)
|
|
1966
|
+
}
|
|
1967
|
+
};
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
merged.push(curr);
|
|
1972
|
+
}
|
|
1973
|
+
return merged;
|
|
1798
1974
|
}
|
|
1799
1975
|
function clusterCoordinates(values) {
|
|
1800
1976
|
if (values.length === 0) return [];
|
|
@@ -1981,7 +2157,11 @@ function cellTextToString(items) {
|
|
|
1981
2157
|
for (let j = 1; j < s.length; j++) {
|
|
1982
2158
|
const gap = s[j].x - (s[j - 1].x + s[j - 1].w);
|
|
1983
2159
|
const avgFs = (s[j].fontSize + s[j - 1].fontSize) / 2;
|
|
1984
|
-
|
|
2160
|
+
const prevIsKorean = /[가-힣]$/.test(result);
|
|
2161
|
+
const currIsKorean = /^[가-힣]/.test(s[j].text);
|
|
2162
|
+
if (gap < avgFs * 0.15) {
|
|
2163
|
+
result += s[j].text;
|
|
2164
|
+
} else if (gap < avgFs * 0.35 && (prevIsKorean || currIsKorean)) {
|
|
1985
2165
|
result += s[j].text;
|
|
1986
2166
|
} else {
|
|
1987
2167
|
result += " " + s[j].text;
|
|
@@ -1996,6 +2176,12 @@ function cellTextToString(items) {
|
|
|
1996
2176
|
const curr = textLines[i];
|
|
1997
2177
|
if (/[가-힣]$/.test(prev) && /^[가-힣]+$/.test(curr) && curr.length <= 8 && !curr.includes(" ")) {
|
|
1998
2178
|
merged[merged.length - 1] = prev + curr;
|
|
2179
|
+
} else if (curr.trim().length <= 3 && /^[)\]%}]/.test(curr.trim())) {
|
|
2180
|
+
merged[merged.length - 1] = prev + curr.trim();
|
|
2181
|
+
} else if (/[,(]$/.test(prev.trim()) && curr.trim().length <= 15) {
|
|
2182
|
+
merged[merged.length - 1] = prev + curr.trim();
|
|
2183
|
+
} else if (/[\d,]$/.test(prev) && /^[\d,]+[)\]]?$/.test(curr.trim()) && curr.trim().length <= 10) {
|
|
2184
|
+
merged[merged.length - 1] = prev + curr.trim();
|
|
1999
2185
|
} else {
|
|
2000
2186
|
merged.push(curr);
|
|
2001
2187
|
}
|
|
@@ -2208,21 +2394,26 @@ async function loadPdfWithTimeout(buffer) {
|
|
|
2208
2394
|
disableFontFace: true,
|
|
2209
2395
|
isEvalSupported: false
|
|
2210
2396
|
});
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2397
|
+
let timer;
|
|
2398
|
+
try {
|
|
2399
|
+
return await Promise.race([
|
|
2400
|
+
loadingTask.promise,
|
|
2401
|
+
new Promise((_, reject) => {
|
|
2402
|
+
timer = setTimeout(() => {
|
|
2403
|
+
loadingTask.destroy();
|
|
2404
|
+
reject(new KordocError("PDF \uB85C\uB529 \uD0C0\uC784\uC544\uC6C3 (30\uCD08 \uCD08\uACFC)"));
|
|
2405
|
+
}, PDF_LOAD_TIMEOUT_MS);
|
|
2406
|
+
})
|
|
2407
|
+
]);
|
|
2408
|
+
} finally {
|
|
2409
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
2410
|
+
}
|
|
2220
2411
|
}
|
|
2221
2412
|
async function parsePdfDocument(buffer, options) {
|
|
2222
2413
|
const doc = await loadPdfWithTimeout(buffer);
|
|
2223
2414
|
try {
|
|
2224
2415
|
const pageCount = doc.numPages;
|
|
2225
|
-
if (pageCount === 0)
|
|
2416
|
+
if (pageCount === 0) throw new KordocError("PDF\uC5D0 \uD398\uC774\uC9C0\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
2226
2417
|
const metadata = { pageCount };
|
|
2227
2418
|
await extractPdfMetadata(doc, metadata);
|
|
2228
2419
|
const blocks = [];
|
|
@@ -2275,14 +2466,14 @@ async function parsePdfDocument(buffer, options) {
|
|
|
2275
2466
|
const ocrBlocks = await ocrPages2(doc, options.ocr, pageFilter, effectivePageCount);
|
|
2276
2467
|
if (ocrBlocks.length > 0) {
|
|
2277
2468
|
const ocrMarkdown = ocrBlocks.map((b) => b.text || "").filter(Boolean).join("\n\n");
|
|
2278
|
-
return {
|
|
2469
|
+
return { markdown: ocrMarkdown, blocks: ocrBlocks, metadata, warnings, isImageBased: true };
|
|
2279
2470
|
}
|
|
2280
2471
|
} catch {
|
|
2281
2472
|
}
|
|
2282
2473
|
}
|
|
2283
|
-
|
|
2474
|
+
throw Object.assign(new KordocError(`\uC774\uBBF8\uC9C0 \uAE30\uBC18 PDF (${pageCount}\uD398\uC774\uC9C0, ${totalChars}\uC790)`), { isImageBased: true });
|
|
2284
2475
|
}
|
|
2285
|
-
if (options?.removeHeaderFooter && parsedPageCount >= 3) {
|
|
2476
|
+
if (options?.removeHeaderFooter !== false && parsedPageCount >= 3) {
|
|
2286
2477
|
const removed = removeHeaderFooterBlocks(blocks, pageHeights, warnings);
|
|
2287
2478
|
for (let ri = removed.length - 1; ri >= 0; ri--) {
|
|
2288
2479
|
blocks.splice(removed[ri], 1);
|
|
@@ -2292,9 +2483,10 @@ async function parsePdfDocument(buffer, options) {
|
|
|
2292
2483
|
if (medianFontSize > 0) {
|
|
2293
2484
|
detectHeadings(blocks, medianFontSize);
|
|
2294
2485
|
}
|
|
2486
|
+
detectMarkerHeadings(blocks);
|
|
2295
2487
|
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
2296
2488
|
let markdown = cleanPdfText(blocksToMarkdown(blocks));
|
|
2297
|
-
return {
|
|
2489
|
+
return { markdown, blocks, metadata, outline: outline.length > 0 ? outline : void 0, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
2298
2490
|
} finally {
|
|
2299
2491
|
await doc.destroy().catch(() => {
|
|
2300
2492
|
});
|
|
@@ -2354,12 +2546,67 @@ function detectHeadings(blocks, medianFontSize) {
|
|
|
2354
2546
|
if (/^\d+$/.test(text)) continue;
|
|
2355
2547
|
const ratio = block.style.fontSize / medianFontSize;
|
|
2356
2548
|
let level = 0;
|
|
2357
|
-
if (ratio >=
|
|
2358
|
-
else if (ratio >=
|
|
2359
|
-
else if (ratio >=
|
|
2549
|
+
if (ratio >= HEADING_RATIO_H1) level = 1;
|
|
2550
|
+
else if (ratio >= HEADING_RATIO_H2) level = 2;
|
|
2551
|
+
else if (ratio >= HEADING_RATIO_H3) level = 3;
|
|
2360
2552
|
if (level > 0) {
|
|
2361
2553
|
block.type = "heading";
|
|
2362
2554
|
block.level = level;
|
|
2555
|
+
block.text = collapseEvenSpacing(text);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
function collapseEvenSpacing(text) {
|
|
2560
|
+
const tokens = text.split(" ");
|
|
2561
|
+
const singleCharCount = tokens.filter((t) => t.length === 1).length;
|
|
2562
|
+
if (tokens.length >= 3 && singleCharCount / tokens.length >= 0.7) {
|
|
2563
|
+
return tokens.join("");
|
|
2564
|
+
}
|
|
2565
|
+
return text;
|
|
2566
|
+
}
|
|
2567
|
+
function shouldDemoteTable(table) {
|
|
2568
|
+
const allCells = table.cells.flatMap((row) => row.map((c) => c.text.trim())).filter(Boolean);
|
|
2569
|
+
const allText = allCells.join(" ");
|
|
2570
|
+
if (allText.length > 200) return false;
|
|
2571
|
+
if (/[□■◆○●▶]/.test(allText) && table.rows <= 3) return true;
|
|
2572
|
+
const totalCells = table.rows * table.cols;
|
|
2573
|
+
const emptyCells = totalCells - allCells.length;
|
|
2574
|
+
if (table.rows <= 2 && emptyCells > totalCells * 0.5) return true;
|
|
2575
|
+
if (table.rows === 1 && !/\d{2,}/.test(allText)) return true;
|
|
2576
|
+
return false;
|
|
2577
|
+
}
|
|
2578
|
+
function demoteTableToText(table) {
|
|
2579
|
+
const lines = [];
|
|
2580
|
+
for (let r = 0; r < table.rows; r++) {
|
|
2581
|
+
const cells = table.cells[r].map((c) => c.text.trim()).filter(Boolean);
|
|
2582
|
+
if (cells.length === 0) continue;
|
|
2583
|
+
if (table.cols === 2 && cells.length === 2) {
|
|
2584
|
+
lines.push(`${cells[0]} : ${cells[1]}`);
|
|
2585
|
+
} else {
|
|
2586
|
+
lines.push(cells.join(" "));
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
return lines.join("\n");
|
|
2590
|
+
}
|
|
2591
|
+
function detectMarkerHeadings(blocks) {
|
|
2592
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
2593
|
+
const block = blocks[i];
|
|
2594
|
+
if (block.type !== "paragraph" || !block.text) continue;
|
|
2595
|
+
const text = block.text.trim();
|
|
2596
|
+
if (text.length < 50 && /^[□■◆◇▶]\s*[가-힣]/.test(text)) {
|
|
2597
|
+
block.type = "heading";
|
|
2598
|
+
block.level = 4;
|
|
2599
|
+
continue;
|
|
2600
|
+
}
|
|
2601
|
+
if (/^[가-힣]{2,6}$/.test(text) && block.style?.fontSize) {
|
|
2602
|
+
const prev = blocks[i - 1];
|
|
2603
|
+
const next = blocks[i + 1];
|
|
2604
|
+
const prevIsStructural = !prev || prev.type === "table" || prev.type === "heading" || prev.type === "separator";
|
|
2605
|
+
const nextIsStructural = !next || next.type === "table" || next.type === "heading" || next.type === "paragraph" && next.text && /^[□■◆○●]/.test(next.text.trim());
|
|
2606
|
+
if (prevIsStructural || nextIsStructural) {
|
|
2607
|
+
block.type = "heading";
|
|
2608
|
+
block.level = 3;
|
|
2609
|
+
}
|
|
2363
2610
|
}
|
|
2364
2611
|
}
|
|
2365
2612
|
}
|
|
@@ -2396,7 +2643,7 @@ function computeRegion(items) {
|
|
|
2396
2643
|
}
|
|
2397
2644
|
return { items, minX, minY, maxX, maxY };
|
|
2398
2645
|
}
|
|
2399
|
-
function findYSplit(items,
|
|
2646
|
+
function findYSplit(items, _region, gapThreshold) {
|
|
2400
2647
|
const sorted = [...items].sort((a, b) => b.y - a.y);
|
|
2401
2648
|
let bestGap = gapThreshold;
|
|
2402
2649
|
let bestSplit = null;
|
|
@@ -2411,7 +2658,7 @@ function findYSplit(items, region, gapThreshold) {
|
|
|
2411
2658
|
}
|
|
2412
2659
|
return bestSplit;
|
|
2413
2660
|
}
|
|
2414
|
-
function findXSplit(items,
|
|
2661
|
+
function findXSplit(items, _region, gapThreshold) {
|
|
2415
2662
|
const sorted = [...items].sort((a, b) => a.x - b.x);
|
|
2416
2663
|
let bestGap = gapThreshold;
|
|
2417
2664
|
let bestSplit = null;
|
|
@@ -2470,7 +2717,8 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals) {
|
|
|
2470
2717
|
);
|
|
2471
2718
|
for (const cell of cells) {
|
|
2472
2719
|
const cellItems = cellTextMap.get(cell) || [];
|
|
2473
|
-
|
|
2720
|
+
let text = cellTextToString(cellItems);
|
|
2721
|
+
text = text.replace(/^[\s]*[-–—]\s*\d+\s*[-–—][\s]*$/gm, "").trim();
|
|
2474
2722
|
irGrid[cell.row][cell.col] = {
|
|
2475
2723
|
text,
|
|
2476
2724
|
colSpan: cell.colSpan,
|
|
@@ -2485,18 +2733,21 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals) {
|
|
|
2485
2733
|
};
|
|
2486
2734
|
const hasContent = irGrid.some((row) => row.some((cell) => cell.text.trim() !== ""));
|
|
2487
2735
|
if (!hasContent) continue;
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2736
|
+
const tableBbox = {
|
|
2737
|
+
page: pageNum,
|
|
2738
|
+
x: grid.bbox.x1,
|
|
2739
|
+
y: grid.bbox.y1,
|
|
2740
|
+
width: grid.bbox.x2 - grid.bbox.x1,
|
|
2741
|
+
height: grid.bbox.y2 - grid.bbox.y1
|
|
2742
|
+
};
|
|
2743
|
+
if (shouldDemoteTable(irTable)) {
|
|
2744
|
+
const demoted = demoteTableToText(irTable);
|
|
2745
|
+
if (demoted) {
|
|
2746
|
+
blocks.push({ type: "paragraph", text: demoted, pageNumber: pageNum, bbox: tableBbox, style: dominantStyle(tableItems) });
|
|
2498
2747
|
}
|
|
2499
|
-
|
|
2748
|
+
continue;
|
|
2749
|
+
}
|
|
2750
|
+
blocks.push({ type: "table", table: irTable, pageNumber: pageNum, bbox: tableBbox });
|
|
2500
2751
|
}
|
|
2501
2752
|
const remaining = items.filter((i) => !usedItems.has(i));
|
|
2502
2753
|
if (remaining.length > 0) {
|
|
@@ -2508,9 +2759,29 @@ function extractBlocksWithGrids(items, pageNum, grids, horizontals, verticals) {
|
|
|
2508
2759
|
const by = b.bbox ? b.bbox.y + b.bbox.height : 0;
|
|
2509
2760
|
return by - ay;
|
|
2510
2761
|
});
|
|
2511
|
-
return allBlocks;
|
|
2762
|
+
return mergeAdjacentTableBlocks(allBlocks);
|
|
2512
2763
|
}
|
|
2513
|
-
return blocks;
|
|
2764
|
+
return mergeAdjacentTableBlocks(blocks);
|
|
2765
|
+
}
|
|
2766
|
+
function mergeAdjacentTableBlocks(blocks) {
|
|
2767
|
+
if (blocks.length <= 1) return blocks;
|
|
2768
|
+
const result = [blocks[0]];
|
|
2769
|
+
for (let i = 1; i < blocks.length; i++) {
|
|
2770
|
+
const prev = result[result.length - 1];
|
|
2771
|
+
const curr = blocks[i];
|
|
2772
|
+
if (prev.type === "table" && curr.type === "table" && prev.table && curr.table && prev.table.cols === curr.table.cols) {
|
|
2773
|
+
const merged = {
|
|
2774
|
+
rows: prev.table.rows + curr.table.rows,
|
|
2775
|
+
cols: prev.table.cols,
|
|
2776
|
+
cells: [...prev.table.cells, ...curr.table.cells],
|
|
2777
|
+
hasHeader: prev.table.hasHeader
|
|
2778
|
+
};
|
|
2779
|
+
result[result.length - 1] = { ...prev, table: merged };
|
|
2780
|
+
} else {
|
|
2781
|
+
result.push(curr);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
return result;
|
|
2514
2785
|
}
|
|
2515
2786
|
function extractPageBlocksFallback(items, pageNum) {
|
|
2516
2787
|
if (items.length === 0) return [];
|
|
@@ -2533,11 +2804,13 @@ function extractPageBlocksFallback(items, pageNum) {
|
|
|
2533
2804
|
}));
|
|
2534
2805
|
const clusterResults = detectClusterTables(clusterItems, pageNum);
|
|
2535
2806
|
if (clusterResults.length > 0) {
|
|
2807
|
+
const ciToIdx = /* @__PURE__ */ new Map();
|
|
2808
|
+
for (let ci = 0; ci < clusterItems.length; ci++) ciToIdx.set(clusterItems[ci], ci);
|
|
2536
2809
|
const usedIndices = /* @__PURE__ */ new Set();
|
|
2537
2810
|
for (const cr of clusterResults) {
|
|
2538
2811
|
for (const ci of cr.usedItems) {
|
|
2539
|
-
const idx =
|
|
2540
|
-
if (idx
|
|
2812
|
+
const idx = ciToIdx.get(ci);
|
|
2813
|
+
if (idx !== void 0) usedIndices.add(idx);
|
|
2541
2814
|
}
|
|
2542
2815
|
blocks.push({ type: "table", table: cr.table, pageNumber: pageNum, bbox: cr.bbox });
|
|
2543
2816
|
}
|
|
@@ -2848,7 +3121,8 @@ function mergeLineSimple(items) {
|
|
|
2848
3121
|
const gap = sorted[i].x - (sorted[i - 1].x + sorted[i - 1].w);
|
|
2849
3122
|
const avgFs = (sorted[i].fontSize + sorted[i - 1].fontSize) / 2;
|
|
2850
3123
|
if (gap > 15) result += " ";
|
|
2851
|
-
else if (gap < avgFs * 0.
|
|
3124
|
+
else if (gap < avgFs * 0.15) {
|
|
3125
|
+
} else if (gap < avgFs * 0.35 && (/[가-힣]$/.test(result) || /^[가-힣]/.test(sorted[i].text))) {
|
|
2852
3126
|
} else if (gap > 3) result += " ";
|
|
2853
3127
|
result += sorted[i].text;
|
|
2854
3128
|
}
|
|
@@ -2856,8 +3130,8 @@ function mergeLineSimple(items) {
|
|
|
2856
3130
|
}
|
|
2857
3131
|
function cleanPdfText(text) {
|
|
2858
3132
|
return mergeKoreanLines(
|
|
2859
|
-
text.replace(/^[\s]*[-–—]\s
|
|
2860
|
-
).replace(/\n{3,}/g, "\n\n").trim();
|
|
3133
|
+
text.replace(/^[\s]*[-–—]\s*[-–—]?\d+[-–—]?[\s]*[-–—]?[\s]*$/gm, "").replace(/^\s*\d+\s*\/\s*\d+\s*$/gm, "").replace(/\n\d{1,4}\n/g, "\n").replace(/\n\d{1,4}$/, "")
|
|
3134
|
+
).replace(/^(?!\|).{3,30}$/gm, (line) => collapseEvenSpacing(line)).replace(/\n{3,}/g, "\n\n").trim();
|
|
2861
3135
|
}
|
|
2862
3136
|
function startsWithMarker(line) {
|
|
2863
3137
|
const t = line.trimStart();
|
|
@@ -2871,15 +3145,13 @@ function detectListBlocks(blocks) {
|
|
|
2871
3145
|
for (let i = 0; i < blocks.length; i++) {
|
|
2872
3146
|
const block = blocks[i];
|
|
2873
3147
|
if (block.type === "paragraph" && block.text) {
|
|
2874
|
-
const
|
|
2875
|
-
if (
|
|
2876
|
-
result.push({
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
text: block.text
|
|
2882
|
-
});
|
|
3148
|
+
const text = block.text.trim();
|
|
3149
|
+
if (/^\d+\.\s/.test(text)) {
|
|
3150
|
+
result.push({ ...block, type: "list", listType: "ordered", text: block.text });
|
|
3151
|
+
continue;
|
|
3152
|
+
}
|
|
3153
|
+
if (/^[○●·※▶▷◆◇\-]\s/.test(text)) {
|
|
3154
|
+
result.push({ ...block, type: "list", listType: "unordered", text: block.text });
|
|
2883
3155
|
continue;
|
|
2884
3156
|
}
|
|
2885
3157
|
}
|
|
@@ -3038,11 +3310,20 @@ function mergeKoreanLines(text) {
|
|
|
3038
3310
|
for (let i = 1; i < lines.length; i++) {
|
|
3039
3311
|
const prev = result[result.length - 1];
|
|
3040
3312
|
const curr = lines[i];
|
|
3041
|
-
|
|
3313
|
+
const currTrimmed = curr.trim();
|
|
3314
|
+
if (/^#{1,6}\s/.test(prev) || /^#{1,6}\s/.test(curr) || /^\|/.test(currTrimmed) || /^---/.test(currTrimmed)) {
|
|
3042
3315
|
result.push(curr);
|
|
3043
3316
|
continue;
|
|
3044
3317
|
}
|
|
3045
|
-
if (
|
|
3318
|
+
if (/,$/.test(prev.trim()) && currTrimmed.length > 0) {
|
|
3319
|
+
result[result.length - 1] = prev + "\n" + curr;
|
|
3320
|
+
continue;
|
|
3321
|
+
}
|
|
3322
|
+
if (/^\(※/.test(currTrimmed)) {
|
|
3323
|
+
result[result.length - 1] = prev + " " + currTrimmed;
|
|
3324
|
+
continue;
|
|
3325
|
+
}
|
|
3326
|
+
if (/[가-힣·,\-]$/.test(prev) && /^[가-힣(]/.test(curr) && !startsWithMarker(curr) && !isStandaloneHeader(prev)) {
|
|
3046
3327
|
result[result.length - 1] = prev + " " + curr;
|
|
3047
3328
|
} else {
|
|
3048
3329
|
result.push(curr);
|
|
@@ -3051,6 +3332,716 @@ function mergeKoreanLines(text) {
|
|
|
3051
3332
|
return result.join("\n");
|
|
3052
3333
|
}
|
|
3053
3334
|
|
|
3335
|
+
// src/xlsx/parser.ts
|
|
3336
|
+
var import_jszip3 = __toESM(require("jszip"), 1);
|
|
3337
|
+
var import_xmldom2 = require("@xmldom/xmldom");
|
|
3338
|
+
var MAX_SHEETS = 100;
|
|
3339
|
+
var MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
|
|
3340
|
+
var MAX_ROWS2 = 1e4;
|
|
3341
|
+
var MAX_COLS2 = 200;
|
|
3342
|
+
function cleanNumericValue(raw) {
|
|
3343
|
+
if (!/^-?\d+\.\d+$/.test(raw)) return raw;
|
|
3344
|
+
const num = parseFloat(raw);
|
|
3345
|
+
if (!isFinite(num)) return raw;
|
|
3346
|
+
const cleaned = parseFloat(num.toPrecision(15)).toString();
|
|
3347
|
+
return cleaned;
|
|
3348
|
+
}
|
|
3349
|
+
function parseCellRef(ref) {
|
|
3350
|
+
const m = ref.match(/^([A-Z]+)(\d+)$/);
|
|
3351
|
+
if (!m) return null;
|
|
3352
|
+
let col = 0;
|
|
3353
|
+
for (const ch of m[1]) col = col * 26 + (ch.charCodeAt(0) - 64);
|
|
3354
|
+
return { col: col - 1, row: parseInt(m[2], 10) - 1 };
|
|
3355
|
+
}
|
|
3356
|
+
function parseMergeRef(ref) {
|
|
3357
|
+
const parts = ref.split(":");
|
|
3358
|
+
if (parts.length !== 2) return null;
|
|
3359
|
+
const start = parseCellRef(parts[0]);
|
|
3360
|
+
const end = parseCellRef(parts[1]);
|
|
3361
|
+
if (!start || !end) return null;
|
|
3362
|
+
return { startCol: start.col, startRow: start.row, endCol: end.col, endRow: end.row };
|
|
3363
|
+
}
|
|
3364
|
+
function getElements(parent, tagName) {
|
|
3365
|
+
const nodes = parent.getElementsByTagName(tagName);
|
|
3366
|
+
const result = [];
|
|
3367
|
+
for (let i = 0; i < nodes.length; i++) result.push(nodes[i]);
|
|
3368
|
+
return result;
|
|
3369
|
+
}
|
|
3370
|
+
function getTextContent(el) {
|
|
3371
|
+
return el.textContent?.trim() ?? "";
|
|
3372
|
+
}
|
|
3373
|
+
function parseXml(text) {
|
|
3374
|
+
return new import_xmldom2.DOMParser().parseFromString(text, "text/xml");
|
|
3375
|
+
}
|
|
3376
|
+
function parseSharedStrings(xml) {
|
|
3377
|
+
const doc = parseXml(xml);
|
|
3378
|
+
const strings = [];
|
|
3379
|
+
const siList = getElements(doc.documentElement, "si");
|
|
3380
|
+
for (const si of siList) {
|
|
3381
|
+
const tElements = getElements(si, "t");
|
|
3382
|
+
strings.push(tElements.map((t) => t.textContent ?? "").join(""));
|
|
3383
|
+
}
|
|
3384
|
+
return strings;
|
|
3385
|
+
}
|
|
3386
|
+
function parseWorkbook(xml) {
|
|
3387
|
+
const doc = parseXml(xml);
|
|
3388
|
+
const sheets = [];
|
|
3389
|
+
const sheetElements = getElements(doc.documentElement, "sheet");
|
|
3390
|
+
for (const el of sheetElements) {
|
|
3391
|
+
sheets.push({
|
|
3392
|
+
name: el.getAttribute("name") ?? `Sheet${sheets.length + 1}`,
|
|
3393
|
+
sheetId: el.getAttribute("sheetId") ?? "",
|
|
3394
|
+
rId: el.getAttribute("r:id") ?? ""
|
|
3395
|
+
});
|
|
3396
|
+
}
|
|
3397
|
+
return sheets;
|
|
3398
|
+
}
|
|
3399
|
+
function parseRels(xml) {
|
|
3400
|
+
const doc = parseXml(xml);
|
|
3401
|
+
const map = /* @__PURE__ */ new Map();
|
|
3402
|
+
const rels = getElements(doc.documentElement, "Relationship");
|
|
3403
|
+
for (const rel of rels) {
|
|
3404
|
+
const id = rel.getAttribute("Id");
|
|
3405
|
+
const target = rel.getAttribute("Target");
|
|
3406
|
+
if (id && target) map.set(id, target);
|
|
3407
|
+
}
|
|
3408
|
+
return map;
|
|
3409
|
+
}
|
|
3410
|
+
function parseWorksheet(xml, sharedStrings) {
|
|
3411
|
+
const doc = parseXml(xml);
|
|
3412
|
+
const grid = [];
|
|
3413
|
+
let maxRow = 0;
|
|
3414
|
+
let maxCol = 0;
|
|
3415
|
+
const rows = getElements(doc.documentElement, "row");
|
|
3416
|
+
for (const rowEl of rows) {
|
|
3417
|
+
const rowNum = parseInt(rowEl.getAttribute("r") ?? "0", 10) - 1;
|
|
3418
|
+
if (rowNum < 0 || rowNum >= MAX_ROWS2) continue;
|
|
3419
|
+
const cells = getElements(rowEl, "c");
|
|
3420
|
+
for (const cellEl of cells) {
|
|
3421
|
+
const ref = cellEl.getAttribute("r");
|
|
3422
|
+
if (!ref) continue;
|
|
3423
|
+
const pos = parseCellRef(ref);
|
|
3424
|
+
if (!pos || pos.col >= MAX_COLS2) continue;
|
|
3425
|
+
const type = cellEl.getAttribute("t");
|
|
3426
|
+
const vElements = getElements(cellEl, "v");
|
|
3427
|
+
const fElements = getElements(cellEl, "f");
|
|
3428
|
+
let value = "";
|
|
3429
|
+
if (vElements.length > 0) {
|
|
3430
|
+
const raw = getTextContent(vElements[0]);
|
|
3431
|
+
if (type === "s") {
|
|
3432
|
+
const idx = parseInt(raw, 10);
|
|
3433
|
+
value = sharedStrings[idx] ?? "";
|
|
3434
|
+
} else if (type === "b") {
|
|
3435
|
+
value = raw === "1" ? "TRUE" : "FALSE";
|
|
3436
|
+
} else {
|
|
3437
|
+
value = cleanNumericValue(raw);
|
|
3438
|
+
}
|
|
3439
|
+
} else if (type === "inlineStr") {
|
|
3440
|
+
const isEl = getElements(cellEl, "is");
|
|
3441
|
+
if (isEl.length > 0) {
|
|
3442
|
+
const tElements = getElements(isEl[0], "t");
|
|
3443
|
+
value = tElements.map((t) => t.textContent ?? "").join("");
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
if (!value && fElements.length > 0) {
|
|
3447
|
+
value = `=${getTextContent(fElements[0])}`;
|
|
3448
|
+
}
|
|
3449
|
+
while (grid.length <= pos.row) grid.push([]);
|
|
3450
|
+
while (grid[pos.row].length <= pos.col) grid[pos.row].push("");
|
|
3451
|
+
grid[pos.row][pos.col] = value;
|
|
3452
|
+
if (pos.row > maxRow) maxRow = pos.row;
|
|
3453
|
+
if (pos.col > maxCol) maxCol = pos.col;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
const merges = [];
|
|
3457
|
+
const mergeCellElements = getElements(doc.documentElement, "mergeCell");
|
|
3458
|
+
for (const el of mergeCellElements) {
|
|
3459
|
+
const ref = el.getAttribute("ref");
|
|
3460
|
+
if (!ref) continue;
|
|
3461
|
+
const m = parseMergeRef(ref);
|
|
3462
|
+
if (m) merges.push(m);
|
|
3463
|
+
}
|
|
3464
|
+
return { grid, merges, maxRow, maxCol };
|
|
3465
|
+
}
|
|
3466
|
+
function sheetToBlocks(sheetName, grid, merges, maxRow, maxCol, sheetIndex) {
|
|
3467
|
+
const blocks = [];
|
|
3468
|
+
if (sheetName) {
|
|
3469
|
+
blocks.push({
|
|
3470
|
+
type: "heading",
|
|
3471
|
+
text: sheetName,
|
|
3472
|
+
level: 2,
|
|
3473
|
+
pageNumber: sheetIndex + 1
|
|
3474
|
+
});
|
|
3475
|
+
}
|
|
3476
|
+
if (maxRow < 0 || maxCol < 0 || grid.length === 0) return blocks;
|
|
3477
|
+
const mergeMap = /* @__PURE__ */ new Map();
|
|
3478
|
+
const mergeSkip = /* @__PURE__ */ new Set();
|
|
3479
|
+
for (const m of merges) {
|
|
3480
|
+
const colSpan = m.endCol - m.startCol + 1;
|
|
3481
|
+
const rowSpan = m.endRow - m.startRow + 1;
|
|
3482
|
+
mergeMap.set(`${m.startRow},${m.startCol}`, { colSpan, rowSpan });
|
|
3483
|
+
for (let r = m.startRow; r <= m.endRow; r++) {
|
|
3484
|
+
for (let c = m.startCol; c <= m.endCol; c++) {
|
|
3485
|
+
if (r !== m.startRow || c !== m.startCol) {
|
|
3486
|
+
mergeSkip.add(`${r},${c}`);
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
let firstRow = -1;
|
|
3492
|
+
let lastRow = -1;
|
|
3493
|
+
for (let r = 0; r <= maxRow; r++) {
|
|
3494
|
+
const row = grid[r];
|
|
3495
|
+
if (row && row.some((cell) => cell !== "")) {
|
|
3496
|
+
if (firstRow === -1) firstRow = r;
|
|
3497
|
+
lastRow = r;
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
if (firstRow === -1) return blocks;
|
|
3501
|
+
const cellRows = [];
|
|
3502
|
+
for (let r = firstRow; r <= lastRow; r++) {
|
|
3503
|
+
const row = [];
|
|
3504
|
+
for (let c = 0; c <= maxCol; c++) {
|
|
3505
|
+
const key = `${r},${c}`;
|
|
3506
|
+
if (mergeSkip.has(key)) continue;
|
|
3507
|
+
const text = (grid[r] && grid[r][c]) ?? "";
|
|
3508
|
+
const merge = mergeMap.get(key);
|
|
3509
|
+
row.push({
|
|
3510
|
+
text,
|
|
3511
|
+
colSpan: merge?.colSpan ?? 1,
|
|
3512
|
+
rowSpan: merge?.rowSpan ?? 1
|
|
3513
|
+
});
|
|
3514
|
+
}
|
|
3515
|
+
cellRows.push(row);
|
|
3516
|
+
}
|
|
3517
|
+
if (cellRows.length > 0) {
|
|
3518
|
+
const table = buildTable(cellRows);
|
|
3519
|
+
if (table.rows > 0) {
|
|
3520
|
+
blocks.push({ type: "table", table, pageNumber: sheetIndex + 1 });
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
return blocks;
|
|
3524
|
+
}
|
|
3525
|
+
async function parseXlsxDocument(buffer, options) {
|
|
3526
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE3);
|
|
3527
|
+
const zip = await import_jszip3.default.loadAsync(buffer);
|
|
3528
|
+
const warnings = [];
|
|
3529
|
+
const workbookFile = zip.file("xl/workbook.xml");
|
|
3530
|
+
if (!workbookFile) {
|
|
3531
|
+
throw new KordocError("\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 XLSX \uD30C\uC77C: xl/workbook.xml\uC774 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
3532
|
+
}
|
|
3533
|
+
let sharedStrings = [];
|
|
3534
|
+
const ssFile = zip.file("xl/sharedStrings.xml");
|
|
3535
|
+
if (ssFile) {
|
|
3536
|
+
sharedStrings = parseSharedStrings(await ssFile.async("text"));
|
|
3537
|
+
}
|
|
3538
|
+
const sheets = parseWorkbook(await workbookFile.async("text"));
|
|
3539
|
+
if (sheets.length === 0) {
|
|
3540
|
+
throw new KordocError("XLSX \uD30C\uC77C\uC5D0 \uC2DC\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
3541
|
+
}
|
|
3542
|
+
let relsMap = /* @__PURE__ */ new Map();
|
|
3543
|
+
const relsFile = zip.file("xl/_rels/workbook.xml.rels");
|
|
3544
|
+
if (relsFile) {
|
|
3545
|
+
relsMap = parseRels(await relsFile.async("text"));
|
|
3546
|
+
}
|
|
3547
|
+
let pageFilter = null;
|
|
3548
|
+
if (options?.pages) {
|
|
3549
|
+
const { parsePageRange: parsePageRange2 } = await Promise.resolve().then(() => (init_page_range(), page_range_exports));
|
|
3550
|
+
pageFilter = parsePageRange2(options.pages, sheets.length);
|
|
3551
|
+
}
|
|
3552
|
+
const blocks = [];
|
|
3553
|
+
const processedSheets = Math.min(sheets.length, MAX_SHEETS);
|
|
3554
|
+
for (let i = 0; i < processedSheets; i++) {
|
|
3555
|
+
if (pageFilter && !pageFilter.has(i + 1)) continue;
|
|
3556
|
+
const sheet = sheets[i];
|
|
3557
|
+
options?.onProgress?.(i + 1, processedSheets);
|
|
3558
|
+
let sheetPath = relsMap.get(sheet.rId);
|
|
3559
|
+
if (sheetPath) {
|
|
3560
|
+
if (!sheetPath.startsWith("xl/") && !sheetPath.startsWith("/")) {
|
|
3561
|
+
sheetPath = `xl/${sheetPath}`;
|
|
3562
|
+
} else if (sheetPath.startsWith("/")) {
|
|
3563
|
+
sheetPath = sheetPath.slice(1);
|
|
3564
|
+
}
|
|
3565
|
+
} else {
|
|
3566
|
+
sheetPath = `xl/worksheets/sheet${i + 1}.xml`;
|
|
3567
|
+
}
|
|
3568
|
+
const sheetFile = zip.file(sheetPath);
|
|
3569
|
+
if (!sheetFile) {
|
|
3570
|
+
warnings.push({
|
|
3571
|
+
page: i + 1,
|
|
3572
|
+
message: `\uC2DC\uD2B8 "${sheet.name}" \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${sheetPath}`,
|
|
3573
|
+
code: "PARTIAL_PARSE"
|
|
3574
|
+
});
|
|
3575
|
+
continue;
|
|
3576
|
+
}
|
|
3577
|
+
try {
|
|
3578
|
+
const sheetXml = await sheetFile.async("text");
|
|
3579
|
+
const { grid, merges, maxRow, maxCol } = parseWorksheet(sheetXml, sharedStrings);
|
|
3580
|
+
const sheetBlocks = sheetToBlocks(sheet.name, grid, merges, maxRow, maxCol, i);
|
|
3581
|
+
blocks.push(...sheetBlocks);
|
|
3582
|
+
} catch (err) {
|
|
3583
|
+
warnings.push({
|
|
3584
|
+
page: i + 1,
|
|
3585
|
+
message: `\uC2DC\uD2B8 "${sheet.name}" \uD30C\uC2F1 \uC2E4\uD328: ${err instanceof Error ? err.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`,
|
|
3586
|
+
code: "PARTIAL_PARSE"
|
|
3587
|
+
});
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
const metadata = {
|
|
3591
|
+
pageCount: processedSheets
|
|
3592
|
+
};
|
|
3593
|
+
const coreFile = zip.file("docProps/core.xml");
|
|
3594
|
+
if (coreFile) {
|
|
3595
|
+
try {
|
|
3596
|
+
const coreXml = await coreFile.async("text");
|
|
3597
|
+
const doc = parseXml(coreXml);
|
|
3598
|
+
const getFirst = (tag) => {
|
|
3599
|
+
const els = doc.getElementsByTagName(tag);
|
|
3600
|
+
return els.length > 0 ? (els[0].textContent ?? "").trim() : void 0;
|
|
3601
|
+
};
|
|
3602
|
+
metadata.title = getFirst("dc:title") || getFirst("dcterms:title");
|
|
3603
|
+
metadata.author = getFirst("dc:creator");
|
|
3604
|
+
metadata.description = getFirst("dc:description");
|
|
3605
|
+
const created = getFirst("dcterms:created");
|
|
3606
|
+
if (created) metadata.createdAt = created;
|
|
3607
|
+
const modified = getFirst("dcterms:modified");
|
|
3608
|
+
if (modified) metadata.modifiedAt = modified;
|
|
3609
|
+
} catch {
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
const markdown = blocksToMarkdown(blocks);
|
|
3613
|
+
return { markdown, blocks, metadata, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
// src/docx/parser.ts
|
|
3617
|
+
var import_jszip4 = __toESM(require("jszip"), 1);
|
|
3618
|
+
var import_xmldom3 = require("@xmldom/xmldom");
|
|
3619
|
+
var MAX_DECOMPRESS_SIZE4 = 100 * 1024 * 1024;
|
|
3620
|
+
function getChildElements(parent, localName) {
|
|
3621
|
+
const result = [];
|
|
3622
|
+
const children = parent.childNodes;
|
|
3623
|
+
for (let i = 0; i < children.length; i++) {
|
|
3624
|
+
const node = children[i];
|
|
3625
|
+
if (node.nodeType === 1) {
|
|
3626
|
+
const el = node;
|
|
3627
|
+
if (el.localName === localName || el.tagName?.endsWith(`:${localName}`)) {
|
|
3628
|
+
result.push(el);
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
return result;
|
|
3633
|
+
}
|
|
3634
|
+
function findElements(parent, localName) {
|
|
3635
|
+
const result = [];
|
|
3636
|
+
const walk = (node) => {
|
|
3637
|
+
const children = node.childNodes;
|
|
3638
|
+
for (let i = 0; i < children.length; i++) {
|
|
3639
|
+
const child = children[i];
|
|
3640
|
+
if (child.nodeType === 1) {
|
|
3641
|
+
const el = child;
|
|
3642
|
+
if (el.localName === localName || el.tagName?.endsWith(`:${localName}`)) {
|
|
3643
|
+
result.push(el);
|
|
3644
|
+
}
|
|
3645
|
+
walk(el);
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
};
|
|
3649
|
+
walk(parent);
|
|
3650
|
+
return result;
|
|
3651
|
+
}
|
|
3652
|
+
function getAttr(el, localName) {
|
|
3653
|
+
const attrs = el.attributes;
|
|
3654
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
3655
|
+
const attr = attrs[i];
|
|
3656
|
+
if (attr.localName === localName || attr.name === localName) return attr.value;
|
|
3657
|
+
}
|
|
3658
|
+
return null;
|
|
3659
|
+
}
|
|
3660
|
+
function parseXml2(text) {
|
|
3661
|
+
return new import_xmldom3.DOMParser().parseFromString(text, "text/xml");
|
|
3662
|
+
}
|
|
3663
|
+
function parseStyles(xml) {
|
|
3664
|
+
const doc = parseXml2(xml);
|
|
3665
|
+
const styles = /* @__PURE__ */ new Map();
|
|
3666
|
+
const styleElements = findElements(doc, "style");
|
|
3667
|
+
for (const el of styleElements) {
|
|
3668
|
+
const styleId = getAttr(el, "styleId");
|
|
3669
|
+
if (!styleId) continue;
|
|
3670
|
+
const nameEls = getChildElements(el, "name");
|
|
3671
|
+
const name = nameEls.length > 0 ? getAttr(nameEls[0], "val") ?? "" : "";
|
|
3672
|
+
const basedOnEls = getChildElements(el, "basedOn");
|
|
3673
|
+
const basedOn = basedOnEls.length > 0 ? getAttr(basedOnEls[0], "val") ?? void 0 : void 0;
|
|
3674
|
+
const pPrEls = getChildElements(el, "pPr");
|
|
3675
|
+
let outlineLevel;
|
|
3676
|
+
if (pPrEls.length > 0) {
|
|
3677
|
+
const outlineEls = getChildElements(pPrEls[0], "outlineLvl");
|
|
3678
|
+
if (outlineEls.length > 0) {
|
|
3679
|
+
const val = getAttr(outlineEls[0], "val");
|
|
3680
|
+
if (val !== null) outlineLevel = parseInt(val, 10);
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
if (outlineLevel === void 0) {
|
|
3684
|
+
const headingMatch = name.match(/^(?:heading|Heading)\s*(\d+)$/i);
|
|
3685
|
+
if (headingMatch) outlineLevel = parseInt(headingMatch[1], 10) - 1;
|
|
3686
|
+
}
|
|
3687
|
+
styles.set(styleId, { name, basedOn, outlineLevel });
|
|
3688
|
+
}
|
|
3689
|
+
return styles;
|
|
3690
|
+
}
|
|
3691
|
+
function parseNumbering(xml) {
|
|
3692
|
+
const doc = parseXml2(xml);
|
|
3693
|
+
const abstractNums = /* @__PURE__ */ new Map();
|
|
3694
|
+
const abstractElements = findElements(doc, "abstractNum");
|
|
3695
|
+
for (const el of abstractElements) {
|
|
3696
|
+
const abstractNumId = getAttr(el, "abstractNumId");
|
|
3697
|
+
if (!abstractNumId) continue;
|
|
3698
|
+
const levels = /* @__PURE__ */ new Map();
|
|
3699
|
+
const lvlElements = getChildElements(el, "lvl");
|
|
3700
|
+
for (const lvl of lvlElements) {
|
|
3701
|
+
const ilvl = parseInt(getAttr(lvl, "ilvl") ?? "0", 10);
|
|
3702
|
+
const numFmtEls = getChildElements(lvl, "numFmt");
|
|
3703
|
+
const numFmt = numFmtEls.length > 0 ? getAttr(numFmtEls[0], "val") ?? "bullet" : "bullet";
|
|
3704
|
+
levels.set(ilvl, { numFmt, level: ilvl });
|
|
3705
|
+
}
|
|
3706
|
+
abstractNums.set(abstractNumId, levels);
|
|
3707
|
+
}
|
|
3708
|
+
const nums = /* @__PURE__ */ new Map();
|
|
3709
|
+
const numElements = findElements(doc, "num");
|
|
3710
|
+
for (const el of numElements) {
|
|
3711
|
+
const numId = getAttr(el, "numId");
|
|
3712
|
+
if (!numId) continue;
|
|
3713
|
+
const abstractRefs = getChildElements(el, "abstractNumId");
|
|
3714
|
+
if (abstractRefs.length > 0) {
|
|
3715
|
+
const ref = getAttr(abstractRefs[0], "val");
|
|
3716
|
+
if (ref && abstractNums.has(ref)) {
|
|
3717
|
+
nums.set(numId, abstractNums.get(ref));
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
return nums;
|
|
3722
|
+
}
|
|
3723
|
+
function parseRels2(xml) {
|
|
3724
|
+
const doc = parseXml2(xml);
|
|
3725
|
+
const map = /* @__PURE__ */ new Map();
|
|
3726
|
+
const rels = findElements(doc, "Relationship");
|
|
3727
|
+
for (const rel of rels) {
|
|
3728
|
+
const id = getAttr(rel, "Id");
|
|
3729
|
+
const target = getAttr(rel, "Target");
|
|
3730
|
+
if (id && target) map.set(id, target);
|
|
3731
|
+
}
|
|
3732
|
+
return map;
|
|
3733
|
+
}
|
|
3734
|
+
function parseFootnotes(xml) {
|
|
3735
|
+
const doc = parseXml2(xml);
|
|
3736
|
+
const notes = /* @__PURE__ */ new Map();
|
|
3737
|
+
const fnElements = findElements(doc, "footnote");
|
|
3738
|
+
for (const fn of fnElements) {
|
|
3739
|
+
const id = getAttr(fn, "id");
|
|
3740
|
+
if (!id || id === "0" || id === "-1") continue;
|
|
3741
|
+
const texts = [];
|
|
3742
|
+
const pElements = findElements(fn, "p");
|
|
3743
|
+
for (const p of pElements) {
|
|
3744
|
+
const runs = findElements(p, "r");
|
|
3745
|
+
for (const r of runs) {
|
|
3746
|
+
const tElements = getChildElements(r, "t");
|
|
3747
|
+
for (const t of tElements) texts.push(t.textContent ?? "");
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
notes.set(id, texts.join("").trim());
|
|
3751
|
+
}
|
|
3752
|
+
return notes;
|
|
3753
|
+
}
|
|
3754
|
+
function extractRun(r) {
|
|
3755
|
+
const tElements = getChildElements(r, "t");
|
|
3756
|
+
const text = tElements.map((t) => t.textContent ?? "").join("");
|
|
3757
|
+
let bold = false;
|
|
3758
|
+
let italic = false;
|
|
3759
|
+
const rPrEls = getChildElements(r, "rPr");
|
|
3760
|
+
if (rPrEls.length > 0) {
|
|
3761
|
+
bold = getChildElements(rPrEls[0], "b").length > 0;
|
|
3762
|
+
italic = getChildElements(rPrEls[0], "i").length > 0;
|
|
3763
|
+
}
|
|
3764
|
+
return { text, bold, italic };
|
|
3765
|
+
}
|
|
3766
|
+
function parseParagraph(p, styles, numbering, footnotes, rels) {
|
|
3767
|
+
const pPrEls = getChildElements(p, "pPr");
|
|
3768
|
+
let styleId = "";
|
|
3769
|
+
let numId = "";
|
|
3770
|
+
let ilvl = 0;
|
|
3771
|
+
if (pPrEls.length > 0) {
|
|
3772
|
+
const pStyleEls = getChildElements(pPrEls[0], "pStyle");
|
|
3773
|
+
if (pStyleEls.length > 0) styleId = getAttr(pStyleEls[0], "val") ?? "";
|
|
3774
|
+
const numPrEls = getChildElements(pPrEls[0], "numPr");
|
|
3775
|
+
if (numPrEls.length > 0) {
|
|
3776
|
+
const numIdEls = getChildElements(numPrEls[0], "numId");
|
|
3777
|
+
const ilvlEls = getChildElements(numPrEls[0], "ilvl");
|
|
3778
|
+
numId = numIdEls.length > 0 ? getAttr(numIdEls[0], "val") ?? "" : "";
|
|
3779
|
+
ilvl = ilvlEls.length > 0 ? parseInt(getAttr(ilvlEls[0], "val") ?? "0", 10) : 0;
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
const parts = [];
|
|
3783
|
+
let hasBold = false;
|
|
3784
|
+
let hasItalic = false;
|
|
3785
|
+
let href;
|
|
3786
|
+
let footnoteText;
|
|
3787
|
+
const hyperlinks = getChildElements(p, "hyperlink");
|
|
3788
|
+
const hyperlinkTexts = /* @__PURE__ */ new Set();
|
|
3789
|
+
for (const hl of hyperlinks) {
|
|
3790
|
+
const rId = getAttr(hl, "id");
|
|
3791
|
+
const hlText = [];
|
|
3792
|
+
const runs2 = findElements(hl, "r");
|
|
3793
|
+
for (const r of runs2) {
|
|
3794
|
+
const result = extractRun(r);
|
|
3795
|
+
hlText.push(result.text);
|
|
3796
|
+
}
|
|
3797
|
+
const text2 = hlText.join("");
|
|
3798
|
+
if (text2) {
|
|
3799
|
+
hyperlinkTexts.add(text2);
|
|
3800
|
+
if (rId && rels.has(rId)) {
|
|
3801
|
+
href = rels.get(rId);
|
|
3802
|
+
parts.push(text2);
|
|
3803
|
+
} else {
|
|
3804
|
+
parts.push(text2);
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
const runs = getChildElements(p, "r");
|
|
3809
|
+
for (const r of runs) {
|
|
3810
|
+
if (r.parentNode && r.parentNode.localName === "hyperlink") continue;
|
|
3811
|
+
const result = extractRun(r);
|
|
3812
|
+
if (result.bold) hasBold = true;
|
|
3813
|
+
if (result.italic) hasItalic = true;
|
|
3814
|
+
const fnRefEls = getChildElements(r, "footnoteReference");
|
|
3815
|
+
if (fnRefEls.length > 0) {
|
|
3816
|
+
const fnId = getAttr(fnRefEls[0], "id");
|
|
3817
|
+
if (fnId && footnotes.has(fnId)) {
|
|
3818
|
+
footnoteText = footnotes.get(fnId);
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
if (result.text) parts.push(result.text);
|
|
3822
|
+
}
|
|
3823
|
+
const text = parts.join("").trim();
|
|
3824
|
+
if (!text) return null;
|
|
3825
|
+
const style = styles.get(styleId);
|
|
3826
|
+
if (style?.outlineLevel !== void 0 && style.outlineLevel >= 0 && style.outlineLevel <= 5) {
|
|
3827
|
+
return {
|
|
3828
|
+
type: "heading",
|
|
3829
|
+
text,
|
|
3830
|
+
level: style.outlineLevel + 1
|
|
3831
|
+
};
|
|
3832
|
+
}
|
|
3833
|
+
if (numId && numId !== "0") {
|
|
3834
|
+
const numDef = numbering.get(numId);
|
|
3835
|
+
const levelInfo = numDef?.get(ilvl);
|
|
3836
|
+
const listType = levelInfo?.numFmt === "bullet" ? "unordered" : "ordered";
|
|
3837
|
+
return { type: "list", text, listType };
|
|
3838
|
+
}
|
|
3839
|
+
const block = { type: "paragraph", text };
|
|
3840
|
+
if (hasBold || hasItalic) {
|
|
3841
|
+
block.style = { bold: hasBold || void 0, italic: hasItalic || void 0 };
|
|
3842
|
+
}
|
|
3843
|
+
if (href) block.href = href;
|
|
3844
|
+
if (footnoteText) block.footnoteText = footnoteText;
|
|
3845
|
+
return block;
|
|
3846
|
+
}
|
|
3847
|
+
function parseTable(tbl, styles, numbering, footnotes, rels) {
|
|
3848
|
+
const trElements = getChildElements(tbl, "tr");
|
|
3849
|
+
if (trElements.length === 0) return null;
|
|
3850
|
+
const rows = [];
|
|
3851
|
+
let maxCols = 0;
|
|
3852
|
+
for (const tr of trElements) {
|
|
3853
|
+
const tcElements = getChildElements(tr, "tc");
|
|
3854
|
+
const row = [];
|
|
3855
|
+
for (const tc of tcElements) {
|
|
3856
|
+
let colSpan = 1;
|
|
3857
|
+
let rowSpan = 1;
|
|
3858
|
+
const tcPrEls = getChildElements(tc, "tcPr");
|
|
3859
|
+
if (tcPrEls.length > 0) {
|
|
3860
|
+
const gridSpanEls = getChildElements(tcPrEls[0], "gridSpan");
|
|
3861
|
+
if (gridSpanEls.length > 0) {
|
|
3862
|
+
colSpan = parseInt(getAttr(gridSpanEls[0], "val") ?? "1", 10);
|
|
3863
|
+
}
|
|
3864
|
+
const vMergeEls = getChildElements(tcPrEls[0], "vMerge");
|
|
3865
|
+
if (vMergeEls.length > 0) {
|
|
3866
|
+
const val = getAttr(vMergeEls[0], "val");
|
|
3867
|
+
if (val !== "restart" && val !== null) {
|
|
3868
|
+
row.push({ text: "", colSpan, rowSpan: 0 });
|
|
3869
|
+
continue;
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
const cellTexts = [];
|
|
3874
|
+
const pElements = getChildElements(tc, "p");
|
|
3875
|
+
for (const p of pElements) {
|
|
3876
|
+
const block = parseParagraph(p, styles, numbering, footnotes, rels);
|
|
3877
|
+
if (block?.text) cellTexts.push(block.text);
|
|
3878
|
+
}
|
|
3879
|
+
row.push({ text: cellTexts.join("\n"), colSpan, rowSpan });
|
|
3880
|
+
}
|
|
3881
|
+
rows.push(row);
|
|
3882
|
+
if (row.length > maxCols) maxCols = row.length;
|
|
3883
|
+
}
|
|
3884
|
+
for (let c = 0; c < maxCols; c++) {
|
|
3885
|
+
for (let r = 0; r < rows.length; r++) {
|
|
3886
|
+
const cell = rows[r][c];
|
|
3887
|
+
if (!cell || cell.rowSpan === 0) continue;
|
|
3888
|
+
let span = 1;
|
|
3889
|
+
for (let nr = r + 1; nr < rows.length; nr++) {
|
|
3890
|
+
if (rows[nr][c]?.rowSpan === 0) span++;
|
|
3891
|
+
else break;
|
|
3892
|
+
}
|
|
3893
|
+
cell.rowSpan = span;
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
const cleanRows = [];
|
|
3897
|
+
for (const row of rows) {
|
|
3898
|
+
const clean = row.filter((cell) => cell.rowSpan !== 0);
|
|
3899
|
+
cleanRows.push(clean);
|
|
3900
|
+
}
|
|
3901
|
+
if (cleanRows.length === 0) return null;
|
|
3902
|
+
let cols = 0;
|
|
3903
|
+
for (const row of cleanRows) {
|
|
3904
|
+
let c = 0;
|
|
3905
|
+
for (const cell of row) c += cell.colSpan;
|
|
3906
|
+
if (c > cols) cols = c;
|
|
3907
|
+
}
|
|
3908
|
+
const table = {
|
|
3909
|
+
rows: cleanRows.length,
|
|
3910
|
+
cols,
|
|
3911
|
+
cells: cleanRows,
|
|
3912
|
+
hasHeader: cleanRows.length > 1
|
|
3913
|
+
};
|
|
3914
|
+
return { type: "table", table };
|
|
3915
|
+
}
|
|
3916
|
+
async function extractImages(zip, rels, doc) {
|
|
3917
|
+
const blocks = [];
|
|
3918
|
+
const images = [];
|
|
3919
|
+
const drawingElements = findElements(doc.documentElement, "drawing");
|
|
3920
|
+
let imgIdx = 0;
|
|
3921
|
+
for (const drawing of drawingElements) {
|
|
3922
|
+
const blips = findElements(drawing, "blip");
|
|
3923
|
+
for (const blip of blips) {
|
|
3924
|
+
const embedId = getAttr(blip, "embed");
|
|
3925
|
+
if (!embedId) continue;
|
|
3926
|
+
const target = rels.get(embedId);
|
|
3927
|
+
if (!target) continue;
|
|
3928
|
+
const imgPath = target.startsWith("/") ? target.slice(1) : target.startsWith("word/") ? target : `word/${target}`;
|
|
3929
|
+
const imgFile = zip.file(imgPath);
|
|
3930
|
+
if (!imgFile) continue;
|
|
3931
|
+
try {
|
|
3932
|
+
const data = await imgFile.async("uint8array");
|
|
3933
|
+
imgIdx++;
|
|
3934
|
+
const ext = imgPath.split(".").pop()?.toLowerCase() ?? "png";
|
|
3935
|
+
const mimeMap = {
|
|
3936
|
+
png: "image/png",
|
|
3937
|
+
jpg: "image/jpeg",
|
|
3938
|
+
jpeg: "image/jpeg",
|
|
3939
|
+
gif: "image/gif",
|
|
3940
|
+
bmp: "image/bmp",
|
|
3941
|
+
wmf: "image/wmf",
|
|
3942
|
+
emf: "image/emf"
|
|
3943
|
+
};
|
|
3944
|
+
const filename = `image_${String(imgIdx).padStart(3, "0")}.${ext}`;
|
|
3945
|
+
images.push({ filename, data, mimeType: mimeMap[ext] ?? "image/png" });
|
|
3946
|
+
blocks.push({ type: "image", text: filename });
|
|
3947
|
+
} catch {
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
return { blocks, images };
|
|
3952
|
+
}
|
|
3953
|
+
async function parseDocxDocument(buffer, options) {
|
|
3954
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE4);
|
|
3955
|
+
const zip = await import_jszip4.default.loadAsync(buffer);
|
|
3956
|
+
const warnings = [];
|
|
3957
|
+
const docFile = zip.file("word/document.xml");
|
|
3958
|
+
if (!docFile) {
|
|
3959
|
+
throw new KordocError("\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 DOCX \uD30C\uC77C: word/document.xml\uC774 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
3960
|
+
}
|
|
3961
|
+
let rels = /* @__PURE__ */ new Map();
|
|
3962
|
+
const relsFile = zip.file("word/_rels/document.xml.rels");
|
|
3963
|
+
if (relsFile) {
|
|
3964
|
+
rels = parseRels2(await relsFile.async("text"));
|
|
3965
|
+
}
|
|
3966
|
+
let styles = /* @__PURE__ */ new Map();
|
|
3967
|
+
const stylesFile = zip.file("word/styles.xml");
|
|
3968
|
+
if (stylesFile) {
|
|
3969
|
+
try {
|
|
3970
|
+
styles = parseStyles(await stylesFile.async("text"));
|
|
3971
|
+
} catch {
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
let numbering = /* @__PURE__ */ new Map();
|
|
3975
|
+
const numFile = zip.file("word/numbering.xml");
|
|
3976
|
+
if (numFile) {
|
|
3977
|
+
try {
|
|
3978
|
+
numbering = parseNumbering(await numFile.async("text"));
|
|
3979
|
+
} catch {
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
let footnotes = /* @__PURE__ */ new Map();
|
|
3983
|
+
const fnFile = zip.file("word/footnotes.xml");
|
|
3984
|
+
if (fnFile) {
|
|
3985
|
+
try {
|
|
3986
|
+
footnotes = parseFootnotes(await fnFile.async("text"));
|
|
3987
|
+
} catch {
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
const docXml = await docFile.async("text");
|
|
3991
|
+
const doc = parseXml2(docXml);
|
|
3992
|
+
const body = findElements(doc, "body");
|
|
3993
|
+
if (body.length === 0) {
|
|
3994
|
+
throw new KordocError("DOCX \uBCF8\uBB38(w:body)\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
3995
|
+
}
|
|
3996
|
+
const blocks = [];
|
|
3997
|
+
const bodyEl = body[0];
|
|
3998
|
+
const children = bodyEl.childNodes;
|
|
3999
|
+
for (let i = 0; i < children.length; i++) {
|
|
4000
|
+
const node = children[i];
|
|
4001
|
+
if (node.nodeType !== 1) continue;
|
|
4002
|
+
const el = node;
|
|
4003
|
+
const localName = el.localName ?? el.tagName?.split(":").pop();
|
|
4004
|
+
if (localName === "p") {
|
|
4005
|
+
const block = parseParagraph(el, styles, numbering, footnotes, rels);
|
|
4006
|
+
if (block) blocks.push(block);
|
|
4007
|
+
} else if (localName === "tbl") {
|
|
4008
|
+
const block = parseTable(el, styles, numbering, footnotes, rels);
|
|
4009
|
+
if (block) blocks.push(block);
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
const { blocks: imgBlocks, images } = await extractImages(zip, rels, doc);
|
|
4013
|
+
const metadata = {};
|
|
4014
|
+
const coreFile = zip.file("docProps/core.xml");
|
|
4015
|
+
if (coreFile) {
|
|
4016
|
+
try {
|
|
4017
|
+
const coreXml = await coreFile.async("text");
|
|
4018
|
+
const coreDoc = parseXml2(coreXml);
|
|
4019
|
+
const getFirst = (tag) => {
|
|
4020
|
+
const els = coreDoc.getElementsByTagName(tag);
|
|
4021
|
+
return els.length > 0 ? (els[0].textContent ?? "").trim() : void 0;
|
|
4022
|
+
};
|
|
4023
|
+
metadata.title = getFirst("dc:title") || getFirst("dcterms:title");
|
|
4024
|
+
metadata.author = getFirst("dc:creator");
|
|
4025
|
+
metadata.description = getFirst("dc:description");
|
|
4026
|
+
const created = getFirst("dcterms:created");
|
|
4027
|
+
if (created) metadata.createdAt = created;
|
|
4028
|
+
const modified = getFirst("dcterms:modified");
|
|
4029
|
+
if (modified) metadata.modifiedAt = modified;
|
|
4030
|
+
} catch {
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
const outline = blocks.filter((b) => b.type === "heading").map((b) => ({ level: b.level ?? 2, text: b.text ?? "" }));
|
|
4034
|
+
const markdown = blocksToMarkdown(blocks);
|
|
4035
|
+
return {
|
|
4036
|
+
markdown,
|
|
4037
|
+
blocks,
|
|
4038
|
+
metadata,
|
|
4039
|
+
outline: outline.length > 0 ? outline : void 0,
|
|
4040
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
4041
|
+
images: images.length > 0 ? images : void 0
|
|
4042
|
+
};
|
|
4043
|
+
}
|
|
4044
|
+
|
|
3054
4045
|
// src/diff/text-diff.ts
|
|
3055
4046
|
function similarity(a, b) {
|
|
3056
4047
|
if (a === b) return 1;
|
|
@@ -3349,12 +4340,12 @@ function extractInlineFields(text) {
|
|
|
3349
4340
|
}
|
|
3350
4341
|
|
|
3351
4342
|
// src/hwpx/generator.ts
|
|
3352
|
-
var
|
|
4343
|
+
var import_jszip5 = __toESM(require("jszip"), 1);
|
|
3353
4344
|
var HWPML_NS = "http://www.hancom.co.kr/hwpml/2016/HwpMl";
|
|
3354
4345
|
async function markdownToHwpx(markdown) {
|
|
3355
4346
|
const blocks = parseMarkdownToBlocks(markdown);
|
|
3356
4347
|
const sectionXml = blocksToSectionXml(blocks);
|
|
3357
|
-
const zip = new
|
|
4348
|
+
const zip = new import_jszip5.default();
|
|
3358
4349
|
zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
|
|
3359
4350
|
zip.file("Contents/content.hpf", generateManifest());
|
|
3360
4351
|
zip.file("Contents/section0.xml", sectionXml);
|
|
@@ -3464,8 +4455,12 @@ async function parse(input, options) {
|
|
|
3464
4455
|
}
|
|
3465
4456
|
const format = detectFormat(buffer);
|
|
3466
4457
|
switch (format) {
|
|
3467
|
-
case "hwpx":
|
|
4458
|
+
case "hwpx": {
|
|
4459
|
+
const zipFormat = await detectZipFormat(buffer);
|
|
4460
|
+
if (zipFormat === "xlsx") return parseXlsx(buffer, options);
|
|
4461
|
+
if (zipFormat === "docx") return parseDocx(buffer, options);
|
|
3468
4462
|
return parseHwpx(buffer, options);
|
|
4463
|
+
}
|
|
3469
4464
|
case "hwp":
|
|
3470
4465
|
return parseHwp(buffer, options);
|
|
3471
4466
|
case "pdf":
|
|
@@ -3492,9 +4487,27 @@ async function parseHwp(buffer, options) {
|
|
|
3492
4487
|
}
|
|
3493
4488
|
async function parsePdf(buffer, options) {
|
|
3494
4489
|
try {
|
|
3495
|
-
|
|
4490
|
+
const { markdown, blocks, metadata, outline, warnings, isImageBased } = await parsePdfDocument(buffer, options);
|
|
4491
|
+
return { success: true, fileType: "pdf", markdown, blocks, metadata, outline, warnings, isImageBased };
|
|
4492
|
+
} catch (err) {
|
|
4493
|
+
const isImageBased = err instanceof Error && "isImageBased" in err ? true : void 0;
|
|
4494
|
+
return { success: false, fileType: "pdf", error: err instanceof Error ? err.message : "PDF \uD30C\uC2F1 \uC2E4\uD328", code: classifyError(err), isImageBased };
|
|
4495
|
+
}
|
|
4496
|
+
}
|
|
4497
|
+
async function parseXlsx(buffer, options) {
|
|
4498
|
+
try {
|
|
4499
|
+
const { markdown, blocks, metadata, warnings } = await parseXlsxDocument(buffer, options);
|
|
4500
|
+
return { success: true, fileType: "xlsx", markdown, blocks, metadata, warnings };
|
|
4501
|
+
} catch (err) {
|
|
4502
|
+
return { success: false, fileType: "xlsx", error: err instanceof Error ? err.message : "XLSX \uD30C\uC2F1 \uC2E4\uD328", code: classifyError(err) };
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
async function parseDocx(buffer, options) {
|
|
4506
|
+
try {
|
|
4507
|
+
const { markdown, blocks, metadata, outline, warnings, images } = await parseDocxDocument(buffer, options);
|
|
4508
|
+
return { success: true, fileType: "docx", markdown, blocks, metadata, outline, warnings, images: images?.length ? images : void 0 };
|
|
3496
4509
|
} catch (err) {
|
|
3497
|
-
return { success: false, fileType: "
|
|
4510
|
+
return { success: false, fileType: "docx", error: err instanceof Error ? err.message : "DOCX \uD30C\uC2F1 \uC2E4\uD328", code: classifyError(err) };
|
|
3498
4511
|
}
|
|
3499
4512
|
}
|
|
3500
4513
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -3503,15 +4516,19 @@ async function parsePdf(buffer, options) {
|
|
|
3503
4516
|
blocksToMarkdown,
|
|
3504
4517
|
compare,
|
|
3505
4518
|
detectFormat,
|
|
4519
|
+
detectZipFormat,
|
|
3506
4520
|
diffBlocks,
|
|
3507
4521
|
extractFormFields,
|
|
3508
4522
|
isHwpxFile,
|
|
3509
4523
|
isOldHwpFile,
|
|
3510
4524
|
isPdfFile,
|
|
4525
|
+
isZipFile,
|
|
3511
4526
|
markdownToHwpx,
|
|
3512
4527
|
parse,
|
|
4528
|
+
parseDocx,
|
|
3513
4529
|
parseHwp,
|
|
3514
4530
|
parseHwpx,
|
|
3515
|
-
parsePdf
|
|
4531
|
+
parsePdf,
|
|
4532
|
+
parseXlsx
|
|
3516
4533
|
});
|
|
3517
4534
|
//# sourceMappingURL=index.cjs.map
|