kordoc 3.8.0 → 3.8.1
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 +6 -1
- package/dist/{-ATVQYFSW.js → -LD4BZDDJ.js} +3 -3
- package/dist/{chunk-3R3YK7EM.js → chunk-IFYJFWD2.js} +2 -2
- package/dist/{chunk-SLKF72QF.js → chunk-KT2BCHXI.js} +690 -671
- package/dist/chunk-KT2BCHXI.js.map +1 -0
- package/dist/{chunk-ITJIALN5.cjs → chunk-LFCS3UVG.cjs} +2 -2
- package/dist/{chunk-ITJIALN5.cjs.map → chunk-LFCS3UVG.cjs.map} +1 -1
- package/dist/{chunk-QV25HMU7.js → chunk-PELBIL4K.js} +2 -2
- package/dist/cli.js +4 -4
- package/dist/index.cjs +1052 -1033
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +689 -670
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +3 -3
- package/dist/{parser-7G5F7PT2.js → parser-FFEBMLSH.js} +1830 -1803
- package/dist/parser-FFEBMLSH.js.map +1 -0
- package/dist/{parser-GUSJH44K.cjs → parser-IXK5V7YG.cjs} +1830 -1803
- package/dist/parser-IXK5V7YG.cjs.map +1 -0
- package/dist/{parser-DCK42RMA.js → parser-XEDROIM7.js} +1830 -1803
- package/dist/parser-XEDROIM7.js.map +1 -0
- package/dist/{watch-GVZESOCE.js → watch-MAWCDNFI.js} +3 -3
- package/package.json +2 -2
- package/dist/chunk-SLKF72QF.js.map +0 -1
- package/dist/parser-7G5F7PT2.js.map +0 -1
- package/dist/parser-DCK42RMA.js.map +0 -1
- package/dist/parser-GUSJH44K.cjs.map +0 -1
- /package/dist/{-ATVQYFSW.js.map → -LD4BZDDJ.js.map} +0 -0
- /package/dist/{chunk-3R3YK7EM.js.map → chunk-IFYJFWD2.js.map} +0 -0
- /package/dist/{chunk-QV25HMU7.js.map → chunk-PELBIL4K.js.map} +0 -0
- /package/dist/{watch-GVZESOCE.js.map → watch-MAWCDNFI.js.map} +0 -0
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
sanitizeHref,
|
|
26
26
|
stripDtd,
|
|
27
27
|
toArrayBuffer
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-IFYJFWD2.js";
|
|
29
29
|
import {
|
|
30
30
|
parsePageRange
|
|
31
31
|
} from "./chunk-MOL7MDBG.js";
|
|
@@ -34,9 +34,7 @@ import {
|
|
|
34
34
|
import { readFile } from "fs/promises";
|
|
35
35
|
|
|
36
36
|
// src/hwpx/parser.ts
|
|
37
|
-
import
|
|
38
|
-
import { inflateRawSync } from "zlib";
|
|
39
|
-
import { DOMParser } from "@xmldom/xmldom";
|
|
37
|
+
import JSZip2 from "jszip";
|
|
40
38
|
|
|
41
39
|
// src/hwpx/com-fallback.ts
|
|
42
40
|
import { execFileSync } from "child_process";
|
|
@@ -145,6 +143,245 @@ function comResultToParseResult(pages, pageCount, warnings) {
|
|
|
145
143
|
};
|
|
146
144
|
}
|
|
147
145
|
|
|
146
|
+
// src/hwpx/parser-shared.ts
|
|
147
|
+
import { DOMParser } from "@xmldom/xmldom";
|
|
148
|
+
var MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024;
|
|
149
|
+
var MAX_ZIP_ENTRIES = 500;
|
|
150
|
+
function clampSpan(val, max) {
|
|
151
|
+
return Math.max(1, Math.min(val, max));
|
|
152
|
+
}
|
|
153
|
+
var MAX_XML_DEPTH = 200;
|
|
154
|
+
function createSectionShared() {
|
|
155
|
+
return { numState: /* @__PURE__ */ new Map(), pageText: { headers: [], footers: [] }, track: { deleteDepth: 0, warned: false } };
|
|
156
|
+
}
|
|
157
|
+
function createXmlParser(warnings) {
|
|
158
|
+
return new DOMParser({
|
|
159
|
+
onError(level, msg2) {
|
|
160
|
+
if (level === "fatalError") throw new KordocError(`XML \uD30C\uC2F1 \uC2E4\uD328: ${msg2}`);
|
|
161
|
+
warnings?.push({ code: "MALFORMED_XML", message: `XML ${level === "warn" ? "\uACBD\uACE0" : "\uC624\uB958"}: ${msg2}` });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function applyPageText(blocks, shared) {
|
|
166
|
+
const { headers, footers } = shared.pageText;
|
|
167
|
+
if (headers.length > 0) {
|
|
168
|
+
blocks.unshift(...headers.map((t) => ({ type: "paragraph", text: t, pageNumber: 1 })));
|
|
169
|
+
}
|
|
170
|
+
if (footers.length > 0) {
|
|
171
|
+
blocks.push(...footers.map((t) => ({ type: "paragraph", text: t })));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function findChildByLocalName(parent, name) {
|
|
175
|
+
const children = parent.childNodes;
|
|
176
|
+
if (!children) return null;
|
|
177
|
+
for (let i = 0; i < children.length; i++) {
|
|
178
|
+
const ch = children[i];
|
|
179
|
+
if (ch.nodeType !== 1) continue;
|
|
180
|
+
const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
|
|
181
|
+
if (tag === name) return ch;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
function extractTextFromNode(node) {
|
|
186
|
+
let result = "";
|
|
187
|
+
const children = node.childNodes;
|
|
188
|
+
if (!children) return result;
|
|
189
|
+
for (let i = 0; i < children.length; i++) {
|
|
190
|
+
const child = children[i];
|
|
191
|
+
if (child.nodeType === 3) result += child.textContent || "";
|
|
192
|
+
else if (child.nodeType === 1) result += extractTextFromNode(child);
|
|
193
|
+
}
|
|
194
|
+
return result.trim();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/hwpx/styles.ts
|
|
198
|
+
async function extractHwpxStyles(zip, decompressed) {
|
|
199
|
+
const result = {
|
|
200
|
+
charProperties: /* @__PURE__ */ new Map(),
|
|
201
|
+
styles: /* @__PURE__ */ new Map(),
|
|
202
|
+
numberings: /* @__PURE__ */ new Map(),
|
|
203
|
+
bullets: /* @__PURE__ */ new Map(),
|
|
204
|
+
paraHeadings: /* @__PURE__ */ new Map()
|
|
205
|
+
};
|
|
206
|
+
const headerPaths = ["Contents/header.xml", "header.xml", "Contents/head.xml", "head.xml"];
|
|
207
|
+
for (const hp of headerPaths) {
|
|
208
|
+
const hpLower = hp.toLowerCase();
|
|
209
|
+
const file = zip.file(hp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === hpLower) || null;
|
|
210
|
+
if (!file) continue;
|
|
211
|
+
try {
|
|
212
|
+
const xml = await file.async("text");
|
|
213
|
+
if (decompressed) {
|
|
214
|
+
decompressed.total += xml.length * 2;
|
|
215
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
216
|
+
}
|
|
217
|
+
const parser = createXmlParser();
|
|
218
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
219
|
+
if (!doc.documentElement) continue;
|
|
220
|
+
parseCharProperties(doc, result.charProperties);
|
|
221
|
+
parseStyleElements(doc, result.styles);
|
|
222
|
+
const domDoc = doc;
|
|
223
|
+
parseNumberings(domDoc, result.numberings);
|
|
224
|
+
parseBullets(domDoc, result.bullets);
|
|
225
|
+
parseParaHeadings(domDoc, result.paraHeadings);
|
|
226
|
+
break;
|
|
227
|
+
} catch {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
function parseCharProperties(doc, map) {
|
|
234
|
+
const tagNames = ["hh:charPr", "charPr", "hp:charPr"];
|
|
235
|
+
for (const tagName of tagNames) {
|
|
236
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
237
|
+
for (let i = 0; i < elements.length; i++) {
|
|
238
|
+
const el = elements[i];
|
|
239
|
+
const id = el.getAttribute("id") || el.getAttribute("IDRef") || "";
|
|
240
|
+
if (!id) continue;
|
|
241
|
+
const prop = {};
|
|
242
|
+
const height = el.getAttribute("height");
|
|
243
|
+
if (height) {
|
|
244
|
+
const parsedHeight = parseInt(height, 10);
|
|
245
|
+
if (!isNaN(parsedHeight) && parsedHeight > 0) {
|
|
246
|
+
prop.fontSize = parsedHeight / 100;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const bold = el.getAttribute("bold");
|
|
250
|
+
if (bold === "true" || bold === "1") prop.bold = true;
|
|
251
|
+
const italic = el.getAttribute("italic");
|
|
252
|
+
if (italic === "true" || italic === "1") prop.italic = true;
|
|
253
|
+
const fontFaces = el.getElementsByTagName("*");
|
|
254
|
+
for (let j = 0; j < fontFaces.length; j++) {
|
|
255
|
+
const ff = fontFaces[j];
|
|
256
|
+
const localTag = (ff.tagName || "").replace(/^[^:]+:/, "");
|
|
257
|
+
if (localTag === "fontface" || localTag === "fontRef") {
|
|
258
|
+
const face = ff.getAttribute("face") || ff.getAttribute("FontFace");
|
|
259
|
+
if (face) {
|
|
260
|
+
prop.fontName = face;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
map.set(id, prop);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function parseStyleElements(doc, map) {
|
|
270
|
+
const tagNames = ["hh:style", "style", "hp:style"];
|
|
271
|
+
for (const tagName of tagNames) {
|
|
272
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
273
|
+
for (let i = 0; i < elements.length; i++) {
|
|
274
|
+
const el = elements[i];
|
|
275
|
+
const id = el.getAttribute("id") || el.getAttribute("IDRef") || String(i);
|
|
276
|
+
const name = el.getAttribute("name") || el.getAttribute("engName") || "";
|
|
277
|
+
const charPrId = el.getAttribute("charPrIDRef") || void 0;
|
|
278
|
+
const paraPrId = el.getAttribute("paraPrIDRef") || void 0;
|
|
279
|
+
map.set(id, { name, charPrId, paraPrId });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function parseNumberings(doc, map) {
|
|
284
|
+
const tagNames = ["hh:numbering", "numbering"];
|
|
285
|
+
for (const tagName of tagNames) {
|
|
286
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
287
|
+
for (let i = 0; i < elements.length; i++) {
|
|
288
|
+
const el = elements[i];
|
|
289
|
+
const id = el.getAttribute("id") || "";
|
|
290
|
+
if (!id) continue;
|
|
291
|
+
const def = { heads: /* @__PURE__ */ new Map() };
|
|
292
|
+
const children = el.childNodes;
|
|
293
|
+
for (let j = 0; j < children.length; j++) {
|
|
294
|
+
const ch = children[j];
|
|
295
|
+
if (ch.nodeType !== 1) continue;
|
|
296
|
+
const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
|
|
297
|
+
if (tag !== "paraHead") continue;
|
|
298
|
+
const level = parseInt(ch.getAttribute("level") || "", 10);
|
|
299
|
+
if (isNaN(level) || level < 1 || level > 10) continue;
|
|
300
|
+
const start = parseInt(ch.getAttribute("start") || "1", 10);
|
|
301
|
+
def.heads.set(level, {
|
|
302
|
+
numFormat: ch.getAttribute("numFormat") || "DIGIT",
|
|
303
|
+
text: ch.textContent || "",
|
|
304
|
+
start: isNaN(start) ? 1 : start
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
if (def.heads.size > 0) map.set(id, def);
|
|
308
|
+
}
|
|
309
|
+
if (map.size > 0) break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function parseBullets(doc, map) {
|
|
313
|
+
const tagNames = ["hh:bullet", "bullet"];
|
|
314
|
+
for (const tagName of tagNames) {
|
|
315
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
316
|
+
for (let i = 0; i < elements.length; i++) {
|
|
317
|
+
const el = elements[i];
|
|
318
|
+
const id = el.getAttribute("id") || "";
|
|
319
|
+
const char = el.getAttribute("char") || "";
|
|
320
|
+
if (id && char) map.set(id, char);
|
|
321
|
+
}
|
|
322
|
+
if (map.size > 0) break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function parseParaHeadings(doc, map) {
|
|
326
|
+
const tagNames = ["hh:paraPr", "paraPr"];
|
|
327
|
+
for (const tagName of tagNames) {
|
|
328
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
329
|
+
for (let i = 0; i < elements.length; i++) {
|
|
330
|
+
const el = elements[i];
|
|
331
|
+
const id = el.getAttribute("id") || "";
|
|
332
|
+
if (!id) continue;
|
|
333
|
+
const heading = findChildByLocalName(el, "heading");
|
|
334
|
+
if (!heading) continue;
|
|
335
|
+
const type = heading.getAttribute("type") || "NONE";
|
|
336
|
+
if (type !== "NUMBER" && type !== "BULLET" && type !== "OUTLINE") continue;
|
|
337
|
+
const level = parseInt(heading.getAttribute("level") || "0", 10);
|
|
338
|
+
map.set(id, {
|
|
339
|
+
type,
|
|
340
|
+
idRef: heading.getAttribute("idRef") || "0",
|
|
341
|
+
level: isNaN(level) ? 0 : Math.max(0, Math.min(level, 9))
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
if (map.size > 0) break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function detectHwpxHeadings(blocks, styleMap) {
|
|
348
|
+
if (blocks.some((b) => b.type === "heading")) return;
|
|
349
|
+
let baseFontSize = 0;
|
|
350
|
+
const sizeFreq = /* @__PURE__ */ new Map();
|
|
351
|
+
for (const b of blocks) {
|
|
352
|
+
if (b.style?.fontSize) {
|
|
353
|
+
sizeFreq.set(b.style.fontSize, (sizeFreq.get(b.style.fontSize) || 0) + 1);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
let maxCount = 0;
|
|
357
|
+
for (const [size, count] of sizeFreq) {
|
|
358
|
+
if (count > maxCount) {
|
|
359
|
+
maxCount = count;
|
|
360
|
+
baseFontSize = size;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
for (const block of blocks) {
|
|
364
|
+
if (block.type !== "paragraph" || !block.text) continue;
|
|
365
|
+
const text = block.text.trim();
|
|
366
|
+
if (text.length === 0 || text.length > 200 || /^\d+$/.test(text)) continue;
|
|
367
|
+
let level = 0;
|
|
368
|
+
if (baseFontSize > 0 && block.style?.fontSize) {
|
|
369
|
+
const ratio = block.style.fontSize / baseFontSize;
|
|
370
|
+
if (ratio >= HEADING_RATIO_H1) level = 1;
|
|
371
|
+
else if (ratio >= HEADING_RATIO_H2) level = 2;
|
|
372
|
+
else if (ratio >= HEADING_RATIO_H3) level = 3;
|
|
373
|
+
}
|
|
374
|
+
const compactText = text.replace(/\s+/g, "");
|
|
375
|
+
if (/^제\d+[조장절편]/.test(compactText) && text.length <= 50) {
|
|
376
|
+
if (level === 0) level = 3;
|
|
377
|
+
}
|
|
378
|
+
if (level > 0) {
|
|
379
|
+
block.type = "heading";
|
|
380
|
+
block.level = level;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
148
385
|
// src/hwpx/equation.ts
|
|
149
386
|
var CONVERT_MAP = {
|
|
150
387
|
TIMES: "\\times",
|
|
@@ -584,173 +821,7 @@ function hmlToLatex(hmlEqStr) {
|
|
|
584
821
|
return out;
|
|
585
822
|
}
|
|
586
823
|
|
|
587
|
-
// src/hwpx/
|
|
588
|
-
var MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024;
|
|
589
|
-
var MAX_ZIP_ENTRIES = 500;
|
|
590
|
-
function clampSpan(val, max) {
|
|
591
|
-
return Math.max(1, Math.min(val, max));
|
|
592
|
-
}
|
|
593
|
-
var MAX_XML_DEPTH = 200;
|
|
594
|
-
function createSectionShared() {
|
|
595
|
-
return { numState: /* @__PURE__ */ new Map(), pageText: { headers: [], footers: [] }, track: { deleteDepth: 0, warned: false } };
|
|
596
|
-
}
|
|
597
|
-
function createXmlParser(warnings) {
|
|
598
|
-
return new DOMParser({
|
|
599
|
-
onError(level, msg2) {
|
|
600
|
-
if (level === "fatalError") throw new KordocError(`XML \uD30C\uC2F1 \uC2E4\uD328: ${msg2}`);
|
|
601
|
-
warnings?.push({ code: "MALFORMED_XML", message: `XML ${level === "warn" ? "\uACBD\uACE0" : "\uC624\uB958"}: ${msg2}` });
|
|
602
|
-
}
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
async function extractHwpxStyles(zip, decompressed) {
|
|
606
|
-
const result = {
|
|
607
|
-
charProperties: /* @__PURE__ */ new Map(),
|
|
608
|
-
styles: /* @__PURE__ */ new Map(),
|
|
609
|
-
numberings: /* @__PURE__ */ new Map(),
|
|
610
|
-
bullets: /* @__PURE__ */ new Map(),
|
|
611
|
-
paraHeadings: /* @__PURE__ */ new Map()
|
|
612
|
-
};
|
|
613
|
-
const headerPaths = ["Contents/header.xml", "header.xml", "Contents/head.xml", "head.xml"];
|
|
614
|
-
for (const hp of headerPaths) {
|
|
615
|
-
const hpLower = hp.toLowerCase();
|
|
616
|
-
const file = zip.file(hp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === hpLower) || null;
|
|
617
|
-
if (!file) continue;
|
|
618
|
-
try {
|
|
619
|
-
const xml = await file.async("text");
|
|
620
|
-
if (decompressed) {
|
|
621
|
-
decompressed.total += xml.length * 2;
|
|
622
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
623
|
-
}
|
|
624
|
-
const parser = createXmlParser();
|
|
625
|
-
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
626
|
-
if (!doc.documentElement) continue;
|
|
627
|
-
parseCharProperties(doc, result.charProperties);
|
|
628
|
-
parseStyleElements(doc, result.styles);
|
|
629
|
-
const domDoc = doc;
|
|
630
|
-
parseNumberings(domDoc, result.numberings);
|
|
631
|
-
parseBullets(domDoc, result.bullets);
|
|
632
|
-
parseParaHeadings(domDoc, result.paraHeadings);
|
|
633
|
-
break;
|
|
634
|
-
} catch {
|
|
635
|
-
continue;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
return result;
|
|
639
|
-
}
|
|
640
|
-
function parseCharProperties(doc, map) {
|
|
641
|
-
const tagNames = ["hh:charPr", "charPr", "hp:charPr"];
|
|
642
|
-
for (const tagName of tagNames) {
|
|
643
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
644
|
-
for (let i = 0; i < elements.length; i++) {
|
|
645
|
-
const el = elements[i];
|
|
646
|
-
const id = el.getAttribute("id") || el.getAttribute("IDRef") || "";
|
|
647
|
-
if (!id) continue;
|
|
648
|
-
const prop = {};
|
|
649
|
-
const height = el.getAttribute("height");
|
|
650
|
-
if (height) {
|
|
651
|
-
const parsedHeight = parseInt(height, 10);
|
|
652
|
-
if (!isNaN(parsedHeight) && parsedHeight > 0) {
|
|
653
|
-
prop.fontSize = parsedHeight / 100;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
const bold = el.getAttribute("bold");
|
|
657
|
-
if (bold === "true" || bold === "1") prop.bold = true;
|
|
658
|
-
const italic = el.getAttribute("italic");
|
|
659
|
-
if (italic === "true" || italic === "1") prop.italic = true;
|
|
660
|
-
const fontFaces = el.getElementsByTagName("*");
|
|
661
|
-
for (let j = 0; j < fontFaces.length; j++) {
|
|
662
|
-
const ff = fontFaces[j];
|
|
663
|
-
const localTag = (ff.tagName || "").replace(/^[^:]+:/, "");
|
|
664
|
-
if (localTag === "fontface" || localTag === "fontRef") {
|
|
665
|
-
const face = ff.getAttribute("face") || ff.getAttribute("FontFace");
|
|
666
|
-
if (face) {
|
|
667
|
-
prop.fontName = face;
|
|
668
|
-
break;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
map.set(id, prop);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
function parseStyleElements(doc, map) {
|
|
677
|
-
const tagNames = ["hh:style", "style", "hp:style"];
|
|
678
|
-
for (const tagName of tagNames) {
|
|
679
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
680
|
-
for (let i = 0; i < elements.length; i++) {
|
|
681
|
-
const el = elements[i];
|
|
682
|
-
const id = el.getAttribute("id") || el.getAttribute("IDRef") || String(i);
|
|
683
|
-
const name = el.getAttribute("name") || el.getAttribute("engName") || "";
|
|
684
|
-
const charPrId = el.getAttribute("charPrIDRef") || void 0;
|
|
685
|
-
const paraPrId = el.getAttribute("paraPrIDRef") || void 0;
|
|
686
|
-
map.set(id, { name, charPrId, paraPrId });
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
function parseNumberings(doc, map) {
|
|
691
|
-
const tagNames = ["hh:numbering", "numbering"];
|
|
692
|
-
for (const tagName of tagNames) {
|
|
693
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
694
|
-
for (let i = 0; i < elements.length; i++) {
|
|
695
|
-
const el = elements[i];
|
|
696
|
-
const id = el.getAttribute("id") || "";
|
|
697
|
-
if (!id) continue;
|
|
698
|
-
const def = { heads: /* @__PURE__ */ new Map() };
|
|
699
|
-
const children = el.childNodes;
|
|
700
|
-
for (let j = 0; j < children.length; j++) {
|
|
701
|
-
const ch = children[j];
|
|
702
|
-
if (ch.nodeType !== 1) continue;
|
|
703
|
-
const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
|
|
704
|
-
if (tag !== "paraHead") continue;
|
|
705
|
-
const level = parseInt(ch.getAttribute("level") || "", 10);
|
|
706
|
-
if (isNaN(level) || level < 1 || level > 10) continue;
|
|
707
|
-
const start = parseInt(ch.getAttribute("start") || "1", 10);
|
|
708
|
-
def.heads.set(level, {
|
|
709
|
-
numFormat: ch.getAttribute("numFormat") || "DIGIT",
|
|
710
|
-
text: ch.textContent || "",
|
|
711
|
-
start: isNaN(start) ? 1 : start
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
if (def.heads.size > 0) map.set(id, def);
|
|
715
|
-
}
|
|
716
|
-
if (map.size > 0) break;
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
function parseBullets(doc, map) {
|
|
720
|
-
const tagNames = ["hh:bullet", "bullet"];
|
|
721
|
-
for (const tagName of tagNames) {
|
|
722
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
723
|
-
for (let i = 0; i < elements.length; i++) {
|
|
724
|
-
const el = elements[i];
|
|
725
|
-
const id = el.getAttribute("id") || "";
|
|
726
|
-
const char = el.getAttribute("char") || "";
|
|
727
|
-
if (id && char) map.set(id, char);
|
|
728
|
-
}
|
|
729
|
-
if (map.size > 0) break;
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
function parseParaHeadings(doc, map) {
|
|
733
|
-
const tagNames = ["hh:paraPr", "paraPr"];
|
|
734
|
-
for (const tagName of tagNames) {
|
|
735
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
736
|
-
for (let i = 0; i < elements.length; i++) {
|
|
737
|
-
const el = elements[i];
|
|
738
|
-
const id = el.getAttribute("id") || "";
|
|
739
|
-
if (!id) continue;
|
|
740
|
-
const heading = findChildByLocalName(el, "heading");
|
|
741
|
-
if (!heading) continue;
|
|
742
|
-
const type = heading.getAttribute("type") || "NONE";
|
|
743
|
-
if (type !== "NUMBER" && type !== "BULLET" && type !== "OUTLINE") continue;
|
|
744
|
-
const level = parseInt(heading.getAttribute("level") || "0", 10);
|
|
745
|
-
map.set(id, {
|
|
746
|
-
type,
|
|
747
|
-
idRef: heading.getAttribute("idRef") || "0",
|
|
748
|
-
level: isNaN(level) ? 0 : Math.max(0, Math.min(level, 9))
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
if (map.size > 0) break;
|
|
752
|
-
}
|
|
753
|
-
}
|
|
824
|
+
// src/hwpx/para-heading.ts
|
|
754
825
|
var HANGUL_SYLLABLE_SEQ = "\uAC00\uB098\uB2E4\uB77C\uB9C8\uBC14\uC0AC\uC544\uC790\uCC28\uCE74\uD0C0\uD30C\uD558";
|
|
755
826
|
var HANGUL_JAMO_SEQ = "\u3131\u3134\u3137\u3139\u3141\u3142\u3145\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
|
|
756
827
|
function toRoman(n) {
|
|
@@ -764,462 +835,87 @@ function toRoman(n) {
|
|
|
764
835
|
[90, "XC"],
|
|
765
836
|
[50, "L"],
|
|
766
837
|
[40, "XL"],
|
|
767
|
-
[10, "X"],
|
|
768
|
-
[9, "IX"],
|
|
769
|
-
[5, "V"],
|
|
770
|
-
[4, "IV"],
|
|
771
|
-
[1, "I"]
|
|
772
|
-
];
|
|
773
|
-
let out = "";
|
|
774
|
-
for (const [v, s] of table) {
|
|
775
|
-
while (n >= v) {
|
|
776
|
-
out += s;
|
|
777
|
-
n -= v;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
return out;
|
|
781
|
-
}
|
|
782
|
-
function formatHeadNumber(n, numFormat) {
|
|
783
|
-
if (n <= 0) n = 1;
|
|
784
|
-
switch (numFormat) {
|
|
785
|
-
case "DIGIT":
|
|
786
|
-
return String(n);
|
|
787
|
-
case "CIRCLED_DIGIT":
|
|
788
|
-
return n <= 20 ? String.fromCodePoint(9312 + n - 1) : `(${n})`;
|
|
789
|
-
case "HANGUL_SYLLABLE":
|
|
790
|
-
return HANGUL_SYLLABLE_SEQ[(n - 1) % HANGUL_SYLLABLE_SEQ.length];
|
|
791
|
-
case "CIRCLED_HANGUL_SYLLABLE":
|
|
792
|
-
return n <= 14 ? String.fromCodePoint(12910 + n - 1) : HANGUL_SYLLABLE_SEQ[(n - 1) % 14];
|
|
793
|
-
case "HANGUL_JAMO":
|
|
794
|
-
return HANGUL_JAMO_SEQ[(n - 1) % HANGUL_JAMO_SEQ.length];
|
|
795
|
-
case "CIRCLED_HANGUL_JAMO":
|
|
796
|
-
return n <= 14 ? String.fromCodePoint(12896 + n - 1) : HANGUL_JAMO_SEQ[(n - 1) % 14];
|
|
797
|
-
case "LATIN_CAPITAL":
|
|
798
|
-
return String.fromCharCode(65 + (n - 1) % 26);
|
|
799
|
-
case "LATIN_SMALL":
|
|
800
|
-
return String.fromCharCode(97 + (n - 1) % 26);
|
|
801
|
-
case "CIRCLED_LATIN_CAPITAL":
|
|
802
|
-
return n <= 26 ? String.fromCodePoint(9398 + n - 1) : String.fromCharCode(65 + (n - 1) % 26);
|
|
803
|
-
case "CIRCLED_LATIN_SMALL":
|
|
804
|
-
return n <= 26 ? String.fromCodePoint(9424 + n - 1) : String.fromCharCode(97 + (n - 1) % 26);
|
|
805
|
-
case "ROMAN_CAPITAL":
|
|
806
|
-
return toRoman(n);
|
|
807
|
-
case "ROMAN_SMALL":
|
|
808
|
-
return toRoman(n).toLowerCase();
|
|
809
|
-
default:
|
|
810
|
-
return String(n);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
function resolveParaHeading(paraEl, ctx) {
|
|
814
|
-
const sm = ctx.styleMap;
|
|
815
|
-
if (!sm) return null;
|
|
816
|
-
const prId = paraEl.getAttribute("paraPrIDRef");
|
|
817
|
-
if (!prId) return null;
|
|
818
|
-
const ref = sm.paraHeadings.get(prId);
|
|
819
|
-
if (!ref) return null;
|
|
820
|
-
if (ref.type === "BULLET") {
|
|
821
|
-
const char = sm.bullets.get(ref.idRef);
|
|
822
|
-
return char ? { prefix: char } : null;
|
|
823
|
-
}
|
|
824
|
-
const numId = ref.type === "OUTLINE" ? ctx.outlineNumId || "1" : ref.idRef;
|
|
825
|
-
const level = Math.min(ref.level + 1, 10);
|
|
826
|
-
const headingLevel = ref.type === "OUTLINE" ? Math.min(ref.level + 1, 6) : void 0;
|
|
827
|
-
const numDef = sm.numberings.get(numId);
|
|
828
|
-
if (!numDef) return headingLevel ? { headingLevel } : null;
|
|
829
|
-
let counters = ctx.shared.numState.get(numId);
|
|
830
|
-
if (!counters) {
|
|
831
|
-
counters = new Array(11).fill(0);
|
|
832
|
-
ctx.shared.numState.set(numId, counters);
|
|
833
|
-
}
|
|
834
|
-
const head = numDef.heads.get(level);
|
|
835
|
-
counters[level] = counters[level] === 0 ? head?.start ?? 1 : counters[level] + 1;
|
|
836
|
-
for (let l = level + 1; l <= 10; l++) counters[l] = 0;
|
|
837
|
-
const fmtText = head?.text?.trim() || `^${level}.`;
|
|
838
|
-
const prefix = fmtText.replace(/\^(10|[1-9])/g, (_, d) => {
|
|
839
|
-
const lv = parseInt(d, 10);
|
|
840
|
-
const refHead = numDef.heads.get(lv);
|
|
841
|
-
const n = counters[lv] || refHead?.start || 1;
|
|
842
|
-
return formatHeadNumber(n, refHead?.numFormat || "DIGIT");
|
|
843
|
-
});
|
|
844
|
-
return { prefix, headingLevel };
|
|
845
|
-
}
|
|
846
|
-
async function parseHwpxDocument(buffer, options) {
|
|
847
|
-
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
848
|
-
let zip;
|
|
849
|
-
try {
|
|
850
|
-
zip = await JSZip.loadAsync(buffer);
|
|
851
|
-
} catch {
|
|
852
|
-
return extractFromBrokenZip(buffer);
|
|
853
|
-
}
|
|
854
|
-
const actualEntryCount = Object.keys(zip.files).length;
|
|
855
|
-
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
856
|
-
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
857
|
-
}
|
|
858
|
-
const manifestFile = zip.file("META-INF/manifest.xml");
|
|
859
|
-
if (manifestFile) {
|
|
860
|
-
const manifestXml = await manifestFile.async("text");
|
|
861
|
-
if (isEncryptedHwpx(manifestXml)) {
|
|
862
|
-
if (isComFallbackAvailable() && options?.filePath) {
|
|
863
|
-
const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
|
|
864
|
-
if (pages.some((p) => p && p.trim().length > 0)) {
|
|
865
|
-
return comResultToParseResult(pages, pageCount, warnings2);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
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.");
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
const decompressed = { total: 0 };
|
|
872
|
-
const metadata = {};
|
|
873
|
-
await extractHwpxMetadata(zip, metadata, decompressed);
|
|
874
|
-
const styleMap = await extractHwpxStyles(zip, decompressed);
|
|
875
|
-
const warnings = [];
|
|
876
|
-
const sectionPaths = await resolveSectionPaths(zip);
|
|
877
|
-
if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
878
|
-
metadata.pageCount = sectionPaths.length;
|
|
879
|
-
const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null;
|
|
880
|
-
const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length;
|
|
881
|
-
const blocks = [];
|
|
882
|
-
const shared = createSectionShared();
|
|
883
|
-
let parsedSections = 0;
|
|
884
|
-
for (let si = 0; si < sectionPaths.length; si++) {
|
|
885
|
-
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
886
|
-
const file = zip.file(sectionPaths[si]);
|
|
887
|
-
if (!file) continue;
|
|
888
|
-
try {
|
|
889
|
-
const xml = await file.async("text");
|
|
890
|
-
decompressed.total += xml.length * 2;
|
|
891
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
892
|
-
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
893
|
-
parsedSections++;
|
|
894
|
-
options?.onProgress?.(parsedSections, totalTarget);
|
|
895
|
-
} catch (secErr) {
|
|
896
|
-
if (secErr instanceof KordocError) throw secErr;
|
|
897
|
-
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" });
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
applyPageText(blocks, shared);
|
|
901
|
-
const images = await extractImagesFromZip(zip, blocks, decompressed, warnings);
|
|
902
|
-
detectHwpxHeadings(blocks, styleMap);
|
|
903
|
-
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
904
|
-
const markdown = blocksToMarkdown(blocks);
|
|
905
|
-
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 };
|
|
906
|
-
}
|
|
907
|
-
function applyPageText(blocks, shared) {
|
|
908
|
-
const { headers, footers } = shared.pageText;
|
|
909
|
-
if (headers.length > 0) {
|
|
910
|
-
blocks.unshift(...headers.map((t) => ({ type: "paragraph", text: t, pageNumber: 1 })));
|
|
911
|
-
}
|
|
912
|
-
if (footers.length > 0) {
|
|
913
|
-
blocks.push(...footers.map((t) => ({ type: "paragraph", text: t })));
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
function imageExtToMime(ext) {
|
|
917
|
-
switch (ext.toLowerCase()) {
|
|
918
|
-
case "jpg":
|
|
919
|
-
case "jpeg":
|
|
920
|
-
return "image/jpeg";
|
|
921
|
-
case "png":
|
|
922
|
-
return "image/png";
|
|
923
|
-
case "gif":
|
|
924
|
-
return "image/gif";
|
|
925
|
-
case "bmp":
|
|
926
|
-
return "image/bmp";
|
|
927
|
-
case "tif":
|
|
928
|
-
case "tiff":
|
|
929
|
-
return "image/tiff";
|
|
930
|
-
case "wmf":
|
|
931
|
-
return "image/wmf";
|
|
932
|
-
case "emf":
|
|
933
|
-
return "image/emf";
|
|
934
|
-
case "svg":
|
|
935
|
-
return "image/svg+xml";
|
|
936
|
-
default:
|
|
937
|
-
return "application/octet-stream";
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
function mimeToExt(mime) {
|
|
941
|
-
if (mime.includes("jpeg")) return "jpg";
|
|
942
|
-
if (mime.includes("png")) return "png";
|
|
943
|
-
if (mime.includes("gif")) return "gif";
|
|
944
|
-
if (mime.includes("bmp")) return "bmp";
|
|
945
|
-
if (mime.includes("tiff")) return "tif";
|
|
946
|
-
if (mime.includes("wmf")) return "wmf";
|
|
947
|
-
if (mime.includes("emf")) return "emf";
|
|
948
|
-
if (mime.includes("svg")) return "svg";
|
|
949
|
-
return "bin";
|
|
950
|
-
}
|
|
951
|
-
function collectImageBlocks(blocks, out, ownerCell, depth = 0) {
|
|
952
|
-
if (depth > MAX_XML_DEPTH) return;
|
|
953
|
-
for (const block of blocks) {
|
|
954
|
-
if (block.type === "image") {
|
|
955
|
-
out.push({ block, ownerCell });
|
|
956
|
-
} else if (block.type === "table" && block.table) {
|
|
957
|
-
for (const row of block.table.cells) {
|
|
958
|
-
for (const cell of row) {
|
|
959
|
-
if (cell.blocks?.length) collectImageBlocks(cell.blocks, out, cell, depth + 1);
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
async function extractImagesFromZip(zip, blocks, decompressed, warnings) {
|
|
966
|
-
const images = [];
|
|
967
|
-
let imageIndex = 0;
|
|
968
|
-
const imageBlocks = [];
|
|
969
|
-
collectImageBlocks(blocks, imageBlocks);
|
|
970
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
971
|
-
for (const { block, ownerCell } of imageBlocks) {
|
|
972
|
-
if (block.type !== "image" || !block.text) continue;
|
|
973
|
-
const ref = block.text;
|
|
974
|
-
let img = resolved.get(ref);
|
|
975
|
-
if (img === void 0) {
|
|
976
|
-
img = null;
|
|
977
|
-
const candidates = [
|
|
978
|
-
`BinData/${ref}`,
|
|
979
|
-
`Contents/BinData/${ref}`,
|
|
980
|
-
ref
|
|
981
|
-
// 절대 경로일 수도 있음
|
|
982
|
-
];
|
|
983
|
-
let resolvedPath = null;
|
|
984
|
-
if (!ref.includes(".")) {
|
|
985
|
-
const prefixes = [`BinData/${ref}`, `Contents/BinData/${ref}`];
|
|
986
|
-
for (const prefix of prefixes) {
|
|
987
|
-
const match = zip.file(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.[a-zA-Z0-9]+$`));
|
|
988
|
-
if (match.length > 0) {
|
|
989
|
-
resolvedPath = match[0].name;
|
|
990
|
-
break;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
const allCandidates = resolvedPath ? [resolvedPath, ...candidates] : candidates;
|
|
995
|
-
for (const path of allCandidates) {
|
|
996
|
-
if (isPathTraversal(path)) continue;
|
|
997
|
-
const file = zip.file(path);
|
|
998
|
-
if (!file) continue;
|
|
999
|
-
try {
|
|
1000
|
-
const data = await file.async("uint8array");
|
|
1001
|
-
decompressed.total += data.length;
|
|
1002
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1003
|
-
const ext = path.includes(".") ? path.split(".").pop() || "png" : "png";
|
|
1004
|
-
const mimeType = imageExtToMime(ext);
|
|
1005
|
-
imageIndex++;
|
|
1006
|
-
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
1007
|
-
img = { filename, data, mimeType };
|
|
1008
|
-
images.push(img);
|
|
1009
|
-
break;
|
|
1010
|
-
} catch (err) {
|
|
1011
|
-
if (err instanceof KordocError) throw err;
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
if (!img) warnings?.push({ page: block.pageNumber, message: `\uC774\uBBF8\uC9C0 \uD30C\uC77C \uC5C6\uC74C: ${ref}`, code: "SKIPPED_IMAGE" });
|
|
1015
|
-
resolved.set(ref, img);
|
|
1016
|
-
}
|
|
1017
|
-
if (!img) {
|
|
1018
|
-
block.type = "paragraph";
|
|
1019
|
-
block.text = `[\uC774\uBBF8\uC9C0: ${ref}]`;
|
|
1020
|
-
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
1021
|
-
continue;
|
|
1022
|
-
}
|
|
1023
|
-
block.text = img.filename;
|
|
1024
|
-
block.imageData = { data: img.data, mimeType: img.mimeType, filename: ref };
|
|
1025
|
-
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, ``);
|
|
1026
|
-
}
|
|
1027
|
-
return images;
|
|
1028
|
-
}
|
|
1029
|
-
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
1030
|
-
try {
|
|
1031
|
-
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
1032
|
-
for (const mp of metaPaths) {
|
|
1033
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
1034
|
-
if (!file) continue;
|
|
1035
|
-
const xml = await file.async("text");
|
|
1036
|
-
if (decompressed) {
|
|
1037
|
-
decompressed.total += xml.length * 2;
|
|
1038
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1039
|
-
}
|
|
1040
|
-
parseDublinCoreMetadata(xml, metadata);
|
|
1041
|
-
if (metadata.title || metadata.author) return;
|
|
1042
|
-
}
|
|
1043
|
-
} catch {
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
function parseDublinCoreMetadata(xml, metadata) {
|
|
1047
|
-
const parser = createXmlParser();
|
|
1048
|
-
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
1049
|
-
if (!doc.documentElement) return;
|
|
1050
|
-
const getText = (tagNames) => {
|
|
1051
|
-
for (const tag of tagNames) {
|
|
1052
|
-
const els = doc.getElementsByTagName(tag);
|
|
1053
|
-
if (els.length > 0) {
|
|
1054
|
-
const text = els[0].textContent?.trim();
|
|
1055
|
-
if (text) return text;
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
return void 0;
|
|
1059
|
-
};
|
|
1060
|
-
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
1061
|
-
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
1062
|
-
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
1063
|
-
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
1064
|
-
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
1065
|
-
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
1066
|
-
if (keywords && !metadata.keywords) {
|
|
1067
|
-
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
async function extractHwpxMetadataOnly(buffer) {
|
|
1071
|
-
let zip;
|
|
1072
|
-
try {
|
|
1073
|
-
zip = await JSZip.loadAsync(buffer);
|
|
1074
|
-
} catch {
|
|
1075
|
-
throw new KordocError("HWPX ZIP\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
1076
|
-
}
|
|
1077
|
-
const metadata = {};
|
|
1078
|
-
await extractHwpxMetadata(zip, metadata);
|
|
1079
|
-
const sectionPaths = await resolveSectionPaths(zip);
|
|
1080
|
-
metadata.pageCount = sectionPaths.length;
|
|
1081
|
-
return metadata;
|
|
1082
|
-
}
|
|
1083
|
-
function extractFromBrokenZip(buffer) {
|
|
1084
|
-
const data = new Uint8Array(buffer);
|
|
1085
|
-
const view = new DataView(buffer);
|
|
1086
|
-
let pos = 0;
|
|
1087
|
-
const blocks = [];
|
|
1088
|
-
const warnings = [
|
|
1089
|
-
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
1090
|
-
];
|
|
1091
|
-
let totalDecompressed = 0;
|
|
1092
|
-
let entryCount = 0;
|
|
1093
|
-
let sectionNum = 0;
|
|
1094
|
-
const shared = createSectionShared();
|
|
1095
|
-
while (pos < data.length - 30) {
|
|
1096
|
-
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
1097
|
-
pos++;
|
|
1098
|
-
while (pos < data.length - 30) {
|
|
1099
|
-
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
1100
|
-
pos++;
|
|
1101
|
-
}
|
|
1102
|
-
continue;
|
|
1103
|
-
}
|
|
1104
|
-
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
1105
|
-
const method = view.getUint16(pos + 8, true);
|
|
1106
|
-
const compSize = view.getUint32(pos + 18, true);
|
|
1107
|
-
const nameLen = view.getUint16(pos + 26, true);
|
|
1108
|
-
const extraLen = view.getUint16(pos + 28, true);
|
|
1109
|
-
if (nameLen > 1024 || extraLen > 65535) {
|
|
1110
|
-
pos += 30 + nameLen + extraLen;
|
|
1111
|
-
continue;
|
|
1112
|
-
}
|
|
1113
|
-
const fileStart = pos + 30 + nameLen + extraLen;
|
|
1114
|
-
if (fileStart + compSize > data.length) break;
|
|
1115
|
-
if (compSize === 0 && method !== 0) {
|
|
1116
|
-
pos = fileStart;
|
|
1117
|
-
continue;
|
|
1118
|
-
}
|
|
1119
|
-
const nameBytes = data.slice(pos + 30, pos + 30 + nameLen);
|
|
1120
|
-
const name = new TextDecoder().decode(nameBytes);
|
|
1121
|
-
if (isPathTraversal(name)) {
|
|
1122
|
-
pos = fileStart + compSize;
|
|
1123
|
-
continue;
|
|
1124
|
-
}
|
|
1125
|
-
const fileData = data.slice(fileStart, fileStart + compSize);
|
|
1126
|
-
pos = fileStart + compSize;
|
|
1127
|
-
if (!name.toLowerCase().includes("section") || !name.endsWith(".xml")) continue;
|
|
1128
|
-
try {
|
|
1129
|
-
let content;
|
|
1130
|
-
if (method === 0) {
|
|
1131
|
-
content = new TextDecoder().decode(fileData);
|
|
1132
|
-
} else if (method === 8) {
|
|
1133
|
-
const decompressed = inflateRawSync(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
1134
|
-
content = new TextDecoder().decode(decompressed);
|
|
1135
|
-
} else {
|
|
1136
|
-
continue;
|
|
1137
|
-
}
|
|
1138
|
-
totalDecompressed += content.length * 2;
|
|
1139
|
-
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
1140
|
-
sectionNum++;
|
|
1141
|
-
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
1142
|
-
} catch {
|
|
1143
|
-
continue;
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
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");
|
|
1147
|
-
applyPageText(blocks, shared);
|
|
1148
|
-
const markdown = blocksToMarkdown(blocks);
|
|
1149
|
-
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
1150
|
-
}
|
|
1151
|
-
async function resolveSectionPaths(zip) {
|
|
1152
|
-
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
1153
|
-
for (const mp of manifestPaths) {
|
|
1154
|
-
const mpLower = mp.toLowerCase();
|
|
1155
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
1156
|
-
if (!file) continue;
|
|
1157
|
-
const xml = await file.async("text");
|
|
1158
|
-
const paths = parseSectionPathsFromManifest(xml);
|
|
1159
|
-
if (paths.length > 0) return paths;
|
|
1160
|
-
}
|
|
1161
|
-
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
1162
|
-
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
1163
|
-
}
|
|
1164
|
-
function parseSectionPathsFromManifest(xml) {
|
|
1165
|
-
const parser = createXmlParser();
|
|
1166
|
-
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
1167
|
-
const items = doc.getElementsByTagName("opf:item");
|
|
1168
|
-
const spine = doc.getElementsByTagName("opf:itemref");
|
|
1169
|
-
const idToHref = /* @__PURE__ */ new Map();
|
|
1170
|
-
for (let i = 0; i < items.length; i++) {
|
|
1171
|
-
const item = items[i];
|
|
1172
|
-
const id = item.getAttribute("id") || "";
|
|
1173
|
-
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
1174
|
-
if (id && href) idToHref.set(id, href);
|
|
1175
|
-
}
|
|
1176
|
-
if (spine.length > 0) {
|
|
1177
|
-
const ordered = [];
|
|
1178
|
-
for (let i = 0; i < spine.length; i++) {
|
|
1179
|
-
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
1180
|
-
if (href) ordered.push(href);
|
|
838
|
+
[10, "X"],
|
|
839
|
+
[9, "IX"],
|
|
840
|
+
[5, "V"],
|
|
841
|
+
[4, "IV"],
|
|
842
|
+
[1, "I"]
|
|
843
|
+
];
|
|
844
|
+
let out = "";
|
|
845
|
+
for (const [v, s] of table) {
|
|
846
|
+
while (n >= v) {
|
|
847
|
+
out += s;
|
|
848
|
+
n -= v;
|
|
1181
849
|
}
|
|
1182
|
-
if (ordered.length > 0) return ordered;
|
|
1183
850
|
}
|
|
1184
|
-
return
|
|
851
|
+
return out;
|
|
1185
852
|
}
|
|
1186
|
-
function
|
|
1187
|
-
if (
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
853
|
+
function formatHeadNumber(n, numFormat) {
|
|
854
|
+
if (n <= 0) n = 1;
|
|
855
|
+
switch (numFormat) {
|
|
856
|
+
case "DIGIT":
|
|
857
|
+
return String(n);
|
|
858
|
+
case "CIRCLED_DIGIT":
|
|
859
|
+
return n <= 20 ? String.fromCodePoint(9312 + n - 1) : `(${n})`;
|
|
860
|
+
case "HANGUL_SYLLABLE":
|
|
861
|
+
return HANGUL_SYLLABLE_SEQ[(n - 1) % HANGUL_SYLLABLE_SEQ.length];
|
|
862
|
+
case "CIRCLED_HANGUL_SYLLABLE":
|
|
863
|
+
return n <= 14 ? String.fromCodePoint(12910 + n - 1) : HANGUL_SYLLABLE_SEQ[(n - 1) % 14];
|
|
864
|
+
case "HANGUL_JAMO":
|
|
865
|
+
return HANGUL_JAMO_SEQ[(n - 1) % HANGUL_JAMO_SEQ.length];
|
|
866
|
+
case "CIRCLED_HANGUL_JAMO":
|
|
867
|
+
return n <= 14 ? String.fromCodePoint(12896 + n - 1) : HANGUL_JAMO_SEQ[(n - 1) % 14];
|
|
868
|
+
case "LATIN_CAPITAL":
|
|
869
|
+
return String.fromCharCode(65 + (n - 1) % 26);
|
|
870
|
+
case "LATIN_SMALL":
|
|
871
|
+
return String.fromCharCode(97 + (n - 1) % 26);
|
|
872
|
+
case "CIRCLED_LATIN_CAPITAL":
|
|
873
|
+
return n <= 26 ? String.fromCodePoint(9398 + n - 1) : String.fromCharCode(65 + (n - 1) % 26);
|
|
874
|
+
case "CIRCLED_LATIN_SMALL":
|
|
875
|
+
return n <= 26 ? String.fromCodePoint(9424 + n - 1) : String.fromCharCode(97 + (n - 1) % 26);
|
|
876
|
+
case "ROMAN_CAPITAL":
|
|
877
|
+
return toRoman(n);
|
|
878
|
+
case "ROMAN_SMALL":
|
|
879
|
+
return toRoman(n).toLowerCase();
|
|
880
|
+
default:
|
|
881
|
+
return String(n);
|
|
1194
882
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
883
|
+
}
|
|
884
|
+
function resolveParaHeading(paraEl, ctx) {
|
|
885
|
+
const sm = ctx.styleMap;
|
|
886
|
+
if (!sm) return null;
|
|
887
|
+
const prId = paraEl.getAttribute("paraPrIDRef");
|
|
888
|
+
if (!prId) return null;
|
|
889
|
+
const ref = sm.paraHeadings.get(prId);
|
|
890
|
+
if (!ref) return null;
|
|
891
|
+
if (ref.type === "BULLET") {
|
|
892
|
+
const char = sm.bullets.get(ref.idRef);
|
|
893
|
+
return char ? { prefix: char } : null;
|
|
1201
894
|
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
else if (ratio >= HEADING_RATIO_H3) level = 3;
|
|
1212
|
-
}
|
|
1213
|
-
const compactText = text.replace(/\s+/g, "");
|
|
1214
|
-
if (/^제\d+[조장절편]/.test(compactText) && text.length <= 50) {
|
|
1215
|
-
if (level === 0) level = 3;
|
|
1216
|
-
}
|
|
1217
|
-
if (level > 0) {
|
|
1218
|
-
block.type = "heading";
|
|
1219
|
-
block.level = level;
|
|
1220
|
-
}
|
|
895
|
+
const numId = ref.type === "OUTLINE" ? ctx.outlineNumId || "1" : ref.idRef;
|
|
896
|
+
const level = Math.min(ref.level + 1, 10);
|
|
897
|
+
const headingLevel = ref.type === "OUTLINE" ? Math.min(ref.level + 1, 6) : void 0;
|
|
898
|
+
const numDef = sm.numberings.get(numId);
|
|
899
|
+
if (!numDef) return headingLevel ? { headingLevel } : null;
|
|
900
|
+
let counters = ctx.shared.numState.get(numId);
|
|
901
|
+
if (!counters) {
|
|
902
|
+
counters = new Array(11).fill(0);
|
|
903
|
+
ctx.shared.numState.set(numId, counters);
|
|
1221
904
|
}
|
|
905
|
+
const head = numDef.heads.get(level);
|
|
906
|
+
counters[level] = counters[level] === 0 ? head?.start ?? 1 : counters[level] + 1;
|
|
907
|
+
for (let l = level + 1; l <= 10; l++) counters[l] = 0;
|
|
908
|
+
const fmtText = head?.text?.trim() || `^${level}.`;
|
|
909
|
+
const prefix = fmtText.replace(/\^(10|[1-9])/g, (_, d) => {
|
|
910
|
+
const lv = parseInt(d, 10);
|
|
911
|
+
const refHead = numDef.heads.get(lv);
|
|
912
|
+
const n = counters[lv] || refHead?.start || 1;
|
|
913
|
+
return formatHeadNumber(n, refHead?.numFormat || "DIGIT");
|
|
914
|
+
});
|
|
915
|
+
return { prefix, headingLevel };
|
|
1222
916
|
}
|
|
917
|
+
|
|
918
|
+
// src/hwpx/table-build.ts
|
|
1223
919
|
function buildTableWithCellMeta(state) {
|
|
1224
920
|
const table = buildTable(state.rows);
|
|
1225
921
|
if (state.caption) table.caption = state.caption;
|
|
@@ -1299,6 +995,8 @@ function completeTable(newTable, tableStack, blocks, ctx) {
|
|
|
1299
995
|
}
|
|
1300
996
|
return parentTable;
|
|
1301
997
|
}
|
|
998
|
+
|
|
999
|
+
// src/hwpx/section-walker.ts
|
|
1302
1000
|
function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
|
|
1303
1001
|
const parser = createXmlParser(warnings);
|
|
1304
1002
|
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
@@ -1832,49 +1530,370 @@ function extractParagraphInfo(para, styleMap, ctx) {
|
|
|
1832
1530
|
break;
|
|
1833
1531
|
}
|
|
1834
1532
|
}
|
|
1835
|
-
};
|
|
1836
|
-
walk(para);
|
|
1837
|
-
const leaderIdx = text.indexOf("");
|
|
1838
|
-
if (leaderIdx >= 0) text = text.substring(0, leaderIdx);
|
|
1839
|
-
let cleanText = text.replace(/[ \t]+/g, " ").trim();
|
|
1840
|
-
if (/^그림입니다\.?\s*원본\s*그림의\s*(이름|크기)/.test(cleanText)) cleanText = "";
|
|
1841
|
-
cleanText = cleanText.replace(/그림입니다\.?\s*원본\s*그림의\s*(이름|크기)[^\n]*(\n[^\n]*원본\s*그림의\s*(이름|크기)[^\n]*)*/g, "").trim();
|
|
1842
|
-
cleanText = cleanText.replace(/(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|선|직선|곡선|화살표|오각형|육각형|팔각형|별|십자|구름|마름모|도넛|평행사변형|사다리꼴|개체|그리기\s?개체|묶음\s?개체|글상자|표|그림|OLE\s?개체)\s?입니다\.?/g, "").trim();
|
|
1843
|
-
let style;
|
|
1844
|
-
if (styleMap && charPrId) {
|
|
1845
|
-
const charProp = styleMap.charProperties.get(charPrId);
|
|
1846
|
-
if (charProp) {
|
|
1847
|
-
style = {};
|
|
1848
|
-
if (charProp.fontSize) style.fontSize = charProp.fontSize;
|
|
1849
|
-
if (charProp.bold) style.bold = true;
|
|
1850
|
-
if (charProp.italic) style.italic = true;
|
|
1851
|
-
if (charProp.fontName) style.fontName = charProp.fontName;
|
|
1852
|
-
if (!style.fontSize && !style.bold && !style.italic) style = void 0;
|
|
1533
|
+
};
|
|
1534
|
+
walk(para);
|
|
1535
|
+
const leaderIdx = text.indexOf("");
|
|
1536
|
+
if (leaderIdx >= 0) text = text.substring(0, leaderIdx);
|
|
1537
|
+
let cleanText = text.replace(/[ \t]+/g, " ").trim();
|
|
1538
|
+
if (/^그림입니다\.?\s*원본\s*그림의\s*(이름|크기)/.test(cleanText)) cleanText = "";
|
|
1539
|
+
cleanText = cleanText.replace(/그림입니다\.?\s*원본\s*그림의\s*(이름|크기)[^\n]*(\n[^\n]*원본\s*그림의\s*(이름|크기)[^\n]*)*/g, "").trim();
|
|
1540
|
+
cleanText = cleanText.replace(/(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|선|직선|곡선|화살표|오각형|육각형|팔각형|별|십자|구름|마름모|도넛|평행사변형|사다리꼴|개체|그리기\s?개체|묶음\s?개체|글상자|표|그림|OLE\s?개체)\s?입니다\.?/g, "").trim();
|
|
1541
|
+
let style;
|
|
1542
|
+
if (styleMap && charPrId) {
|
|
1543
|
+
const charProp = styleMap.charProperties.get(charPrId);
|
|
1544
|
+
if (charProp) {
|
|
1545
|
+
style = {};
|
|
1546
|
+
if (charProp.fontSize) style.fontSize = charProp.fontSize;
|
|
1547
|
+
if (charProp.bold) style.bold = true;
|
|
1548
|
+
if (charProp.italic) style.italic = true;
|
|
1549
|
+
if (charProp.fontName) style.fontName = charProp.fontName;
|
|
1550
|
+
if (!style.fontSize && !style.bold && !style.italic) style = void 0;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
return { text: cleanText, href, footnote, style };
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// src/hwpx/images.ts
|
|
1557
|
+
function imageExtToMime(ext) {
|
|
1558
|
+
switch (ext.toLowerCase()) {
|
|
1559
|
+
case "jpg":
|
|
1560
|
+
case "jpeg":
|
|
1561
|
+
return "image/jpeg";
|
|
1562
|
+
case "png":
|
|
1563
|
+
return "image/png";
|
|
1564
|
+
case "gif":
|
|
1565
|
+
return "image/gif";
|
|
1566
|
+
case "bmp":
|
|
1567
|
+
return "image/bmp";
|
|
1568
|
+
case "tif":
|
|
1569
|
+
case "tiff":
|
|
1570
|
+
return "image/tiff";
|
|
1571
|
+
case "wmf":
|
|
1572
|
+
return "image/wmf";
|
|
1573
|
+
case "emf":
|
|
1574
|
+
return "image/emf";
|
|
1575
|
+
case "svg":
|
|
1576
|
+
return "image/svg+xml";
|
|
1577
|
+
default:
|
|
1578
|
+
return "application/octet-stream";
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
function mimeToExt(mime) {
|
|
1582
|
+
if (mime.includes("jpeg")) return "jpg";
|
|
1583
|
+
if (mime.includes("png")) return "png";
|
|
1584
|
+
if (mime.includes("gif")) return "gif";
|
|
1585
|
+
if (mime.includes("bmp")) return "bmp";
|
|
1586
|
+
if (mime.includes("tiff")) return "tif";
|
|
1587
|
+
if (mime.includes("wmf")) return "wmf";
|
|
1588
|
+
if (mime.includes("emf")) return "emf";
|
|
1589
|
+
if (mime.includes("svg")) return "svg";
|
|
1590
|
+
return "bin";
|
|
1591
|
+
}
|
|
1592
|
+
function collectImageBlocks(blocks, out, ownerCell, depth = 0) {
|
|
1593
|
+
if (depth > MAX_XML_DEPTH) return;
|
|
1594
|
+
for (const block of blocks) {
|
|
1595
|
+
if (block.type === "image") {
|
|
1596
|
+
out.push({ block, ownerCell });
|
|
1597
|
+
} else if (block.type === "table" && block.table) {
|
|
1598
|
+
for (const row of block.table.cells) {
|
|
1599
|
+
for (const cell of row) {
|
|
1600
|
+
if (cell.blocks?.length) collectImageBlocks(cell.blocks, out, cell, depth + 1);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
async function extractImagesFromZip(zip, blocks, decompressed, warnings) {
|
|
1607
|
+
const images = [];
|
|
1608
|
+
let imageIndex = 0;
|
|
1609
|
+
const imageBlocks = [];
|
|
1610
|
+
collectImageBlocks(blocks, imageBlocks);
|
|
1611
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1612
|
+
for (const { block, ownerCell } of imageBlocks) {
|
|
1613
|
+
if (block.type !== "image" || !block.text) continue;
|
|
1614
|
+
const ref = block.text;
|
|
1615
|
+
let img = resolved.get(ref);
|
|
1616
|
+
if (img === void 0) {
|
|
1617
|
+
img = null;
|
|
1618
|
+
const candidates = [
|
|
1619
|
+
`BinData/${ref}`,
|
|
1620
|
+
`Contents/BinData/${ref}`,
|
|
1621
|
+
ref
|
|
1622
|
+
// 절대 경로일 수도 있음
|
|
1623
|
+
];
|
|
1624
|
+
let resolvedPath = null;
|
|
1625
|
+
if (!ref.includes(".")) {
|
|
1626
|
+
const prefixes = [`BinData/${ref}`, `Contents/BinData/${ref}`];
|
|
1627
|
+
for (const prefix of prefixes) {
|
|
1628
|
+
const match = zip.file(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.[a-zA-Z0-9]+$`));
|
|
1629
|
+
if (match.length > 0) {
|
|
1630
|
+
resolvedPath = match[0].name;
|
|
1631
|
+
break;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
const allCandidates = resolvedPath ? [resolvedPath, ...candidates] : candidates;
|
|
1636
|
+
for (const path of allCandidates) {
|
|
1637
|
+
if (isPathTraversal(path)) continue;
|
|
1638
|
+
const file = zip.file(path);
|
|
1639
|
+
if (!file) continue;
|
|
1640
|
+
try {
|
|
1641
|
+
const data = await file.async("uint8array");
|
|
1642
|
+
decompressed.total += data.length;
|
|
1643
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1644
|
+
const ext = path.includes(".") ? path.split(".").pop() || "png" : "png";
|
|
1645
|
+
const mimeType = imageExtToMime(ext);
|
|
1646
|
+
imageIndex++;
|
|
1647
|
+
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
1648
|
+
img = { filename, data, mimeType };
|
|
1649
|
+
images.push(img);
|
|
1650
|
+
break;
|
|
1651
|
+
} catch (err) {
|
|
1652
|
+
if (err instanceof KordocError) throw err;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
if (!img) warnings?.push({ page: block.pageNumber, message: `\uC774\uBBF8\uC9C0 \uD30C\uC77C \uC5C6\uC74C: ${ref}`, code: "SKIPPED_IMAGE" });
|
|
1656
|
+
resolved.set(ref, img);
|
|
1657
|
+
}
|
|
1658
|
+
if (!img) {
|
|
1659
|
+
block.type = "paragraph";
|
|
1660
|
+
block.text = `[\uC774\uBBF8\uC9C0: ${ref}]`;
|
|
1661
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
block.text = img.filename;
|
|
1665
|
+
block.imageData = { data: img.data, mimeType: img.mimeType, filename: ref };
|
|
1666
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, ``);
|
|
1667
|
+
}
|
|
1668
|
+
return images;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
// src/hwpx/metadata.ts
|
|
1672
|
+
import JSZip from "jszip";
|
|
1673
|
+
|
|
1674
|
+
// src/hwpx/zip-sections.ts
|
|
1675
|
+
import { inflateRawSync } from "zlib";
|
|
1676
|
+
function extractFromBrokenZip(buffer) {
|
|
1677
|
+
const data = new Uint8Array(buffer);
|
|
1678
|
+
const view = new DataView(buffer);
|
|
1679
|
+
let pos = 0;
|
|
1680
|
+
const blocks = [];
|
|
1681
|
+
const warnings = [
|
|
1682
|
+
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
1683
|
+
];
|
|
1684
|
+
let totalDecompressed = 0;
|
|
1685
|
+
let entryCount = 0;
|
|
1686
|
+
let sectionNum = 0;
|
|
1687
|
+
const shared = createSectionShared();
|
|
1688
|
+
while (pos < data.length - 30) {
|
|
1689
|
+
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
1690
|
+
pos++;
|
|
1691
|
+
while (pos < data.length - 30) {
|
|
1692
|
+
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
1693
|
+
pos++;
|
|
1694
|
+
}
|
|
1695
|
+
continue;
|
|
1696
|
+
}
|
|
1697
|
+
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
1698
|
+
const method = view.getUint16(pos + 8, true);
|
|
1699
|
+
const compSize = view.getUint32(pos + 18, true);
|
|
1700
|
+
const nameLen = view.getUint16(pos + 26, true);
|
|
1701
|
+
const extraLen = view.getUint16(pos + 28, true);
|
|
1702
|
+
if (nameLen > 1024 || extraLen > 65535) {
|
|
1703
|
+
pos += 30 + nameLen + extraLen;
|
|
1704
|
+
continue;
|
|
1705
|
+
}
|
|
1706
|
+
const fileStart = pos + 30 + nameLen + extraLen;
|
|
1707
|
+
if (fileStart + compSize > data.length) break;
|
|
1708
|
+
if (compSize === 0 && method !== 0) {
|
|
1709
|
+
pos = fileStart;
|
|
1710
|
+
continue;
|
|
1711
|
+
}
|
|
1712
|
+
const nameBytes = data.slice(pos + 30, pos + 30 + nameLen);
|
|
1713
|
+
const name = new TextDecoder().decode(nameBytes);
|
|
1714
|
+
if (isPathTraversal(name)) {
|
|
1715
|
+
pos = fileStart + compSize;
|
|
1716
|
+
continue;
|
|
1717
|
+
}
|
|
1718
|
+
const fileData = data.slice(fileStart, fileStart + compSize);
|
|
1719
|
+
pos = fileStart + compSize;
|
|
1720
|
+
if (!name.toLowerCase().includes("section") || !name.endsWith(".xml")) continue;
|
|
1721
|
+
try {
|
|
1722
|
+
let content;
|
|
1723
|
+
if (method === 0) {
|
|
1724
|
+
content = new TextDecoder().decode(fileData);
|
|
1725
|
+
} else if (method === 8) {
|
|
1726
|
+
const decompressed = inflateRawSync(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
1727
|
+
content = new TextDecoder().decode(decompressed);
|
|
1728
|
+
} else {
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1731
|
+
totalDecompressed += content.length * 2;
|
|
1732
|
+
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
1733
|
+
sectionNum++;
|
|
1734
|
+
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
1735
|
+
} catch {
|
|
1736
|
+
continue;
|
|
1853
1737
|
}
|
|
1854
1738
|
}
|
|
1855
|
-
|
|
1739
|
+
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");
|
|
1740
|
+
applyPageText(blocks, shared);
|
|
1741
|
+
const markdown = blocksToMarkdown(blocks);
|
|
1742
|
+
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
1856
1743
|
}
|
|
1857
|
-
function
|
|
1858
|
-
const
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
const
|
|
1862
|
-
if (
|
|
1863
|
-
const
|
|
1864
|
-
|
|
1744
|
+
async function resolveSectionPaths(zip) {
|
|
1745
|
+
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
1746
|
+
for (const mp of manifestPaths) {
|
|
1747
|
+
const mpLower = mp.toLowerCase();
|
|
1748
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
1749
|
+
if (!file) continue;
|
|
1750
|
+
const xml = await file.async("text");
|
|
1751
|
+
const paths = parseSectionPathsFromManifest(xml);
|
|
1752
|
+
if (paths.length > 0) return paths;
|
|
1865
1753
|
}
|
|
1866
|
-
|
|
1754
|
+
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
1755
|
+
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
1867
1756
|
}
|
|
1868
|
-
function
|
|
1869
|
-
|
|
1870
|
-
const
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1757
|
+
function parseSectionPathsFromManifest(xml) {
|
|
1758
|
+
const parser = createXmlParser();
|
|
1759
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
1760
|
+
const items = doc.getElementsByTagName("opf:item");
|
|
1761
|
+
const spine = doc.getElementsByTagName("opf:itemref");
|
|
1762
|
+
const idToHref = /* @__PURE__ */ new Map();
|
|
1763
|
+
for (let i = 0; i < items.length; i++) {
|
|
1764
|
+
const item = items[i];
|
|
1765
|
+
const id = item.getAttribute("id") || "";
|
|
1766
|
+
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
1767
|
+
if (id && href) idToHref.set(id, href);
|
|
1876
1768
|
}
|
|
1877
|
-
|
|
1769
|
+
if (spine.length > 0) {
|
|
1770
|
+
const ordered = [];
|
|
1771
|
+
for (let i = 0; i < spine.length; i++) {
|
|
1772
|
+
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
1773
|
+
if (href) ordered.push(href);
|
|
1774
|
+
}
|
|
1775
|
+
if (ordered.length > 0) return ordered;
|
|
1776
|
+
}
|
|
1777
|
+
return Array.from(idToHref.values()).sort(compareSectionPaths);
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// src/hwpx/metadata.ts
|
|
1781
|
+
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
1782
|
+
try {
|
|
1783
|
+
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
1784
|
+
for (const mp of metaPaths) {
|
|
1785
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
1786
|
+
if (!file) continue;
|
|
1787
|
+
const xml = await file.async("text");
|
|
1788
|
+
if (decompressed) {
|
|
1789
|
+
decompressed.total += xml.length * 2;
|
|
1790
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1791
|
+
}
|
|
1792
|
+
parseDublinCoreMetadata(xml, metadata);
|
|
1793
|
+
if (metadata.title || metadata.author) return;
|
|
1794
|
+
}
|
|
1795
|
+
} catch {
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
function parseDublinCoreMetadata(xml, metadata) {
|
|
1799
|
+
const parser = createXmlParser();
|
|
1800
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
1801
|
+
if (!doc.documentElement) return;
|
|
1802
|
+
const getText = (tagNames) => {
|
|
1803
|
+
for (const tag of tagNames) {
|
|
1804
|
+
const els = doc.getElementsByTagName(tag);
|
|
1805
|
+
if (els.length > 0) {
|
|
1806
|
+
const text = els[0].textContent?.trim();
|
|
1807
|
+
if (text) return text;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
return void 0;
|
|
1811
|
+
};
|
|
1812
|
+
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
1813
|
+
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
1814
|
+
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
1815
|
+
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
1816
|
+
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
1817
|
+
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
1818
|
+
if (keywords && !metadata.keywords) {
|
|
1819
|
+
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
async function extractHwpxMetadataOnly(buffer) {
|
|
1823
|
+
let zip;
|
|
1824
|
+
try {
|
|
1825
|
+
zip = await JSZip.loadAsync(buffer);
|
|
1826
|
+
} catch {
|
|
1827
|
+
throw new KordocError("HWPX ZIP\uC744 \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
1828
|
+
}
|
|
1829
|
+
const metadata = {};
|
|
1830
|
+
await extractHwpxMetadata(zip, metadata);
|
|
1831
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
1832
|
+
metadata.pageCount = sectionPaths.length;
|
|
1833
|
+
return metadata;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
// src/hwpx/parser.ts
|
|
1837
|
+
async function parseHwpxDocument(buffer, options) {
|
|
1838
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
1839
|
+
let zip;
|
|
1840
|
+
try {
|
|
1841
|
+
zip = await JSZip2.loadAsync(buffer);
|
|
1842
|
+
} catch {
|
|
1843
|
+
return extractFromBrokenZip(buffer);
|
|
1844
|
+
}
|
|
1845
|
+
const actualEntryCount = Object.keys(zip.files).length;
|
|
1846
|
+
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
1847
|
+
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1848
|
+
}
|
|
1849
|
+
const manifestFile = zip.file("META-INF/manifest.xml");
|
|
1850
|
+
if (manifestFile) {
|
|
1851
|
+
const manifestXml = await manifestFile.async("text");
|
|
1852
|
+
if (isEncryptedHwpx(manifestXml)) {
|
|
1853
|
+
if (isComFallbackAvailable() && options?.filePath) {
|
|
1854
|
+
const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
|
|
1855
|
+
if (pages.some((p) => p && p.trim().length > 0)) {
|
|
1856
|
+
return comResultToParseResult(pages, pageCount, warnings2);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
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.");
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
const decompressed = { total: 0 };
|
|
1863
|
+
const metadata = {};
|
|
1864
|
+
await extractHwpxMetadata(zip, metadata, decompressed);
|
|
1865
|
+
const styleMap = await extractHwpxStyles(zip, decompressed);
|
|
1866
|
+
const warnings = [];
|
|
1867
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
1868
|
+
if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
1869
|
+
metadata.pageCount = sectionPaths.length;
|
|
1870
|
+
const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null;
|
|
1871
|
+
const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length;
|
|
1872
|
+
const blocks = [];
|
|
1873
|
+
const shared = createSectionShared();
|
|
1874
|
+
let parsedSections = 0;
|
|
1875
|
+
for (let si = 0; si < sectionPaths.length; si++) {
|
|
1876
|
+
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
1877
|
+
const file = zip.file(sectionPaths[si]);
|
|
1878
|
+
if (!file) continue;
|
|
1879
|
+
try {
|
|
1880
|
+
const xml = await file.async("text");
|
|
1881
|
+
decompressed.total += xml.length * 2;
|
|
1882
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1883
|
+
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
1884
|
+
parsedSections++;
|
|
1885
|
+
options?.onProgress?.(parsedSections, totalTarget);
|
|
1886
|
+
} catch (secErr) {
|
|
1887
|
+
if (secErr instanceof KordocError) throw secErr;
|
|
1888
|
+
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" });
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
applyPageText(blocks, shared);
|
|
1892
|
+
const images = await extractImagesFromZip(zip, blocks, decompressed, warnings);
|
|
1893
|
+
detectHwpxHeadings(blocks, styleMap);
|
|
1894
|
+
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
1895
|
+
const markdown = blocksToMarkdown(blocks);
|
|
1896
|
+
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 };
|
|
1878
1897
|
}
|
|
1879
1898
|
|
|
1880
1899
|
// src/hwp5/record.ts
|
|
@@ -16530,7 +16549,7 @@ function isDistributionSentinel(markdown) {
|
|
|
16530
16549
|
}
|
|
16531
16550
|
|
|
16532
16551
|
// src/xlsx/parser.ts
|
|
16533
|
-
import
|
|
16552
|
+
import JSZip3 from "jszip";
|
|
16534
16553
|
import { DOMParser as DOMParser2 } from "@xmldom/xmldom";
|
|
16535
16554
|
var MAX_SHEETS = 100;
|
|
16536
16555
|
var MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
|
|
@@ -16721,7 +16740,7 @@ function sheetToBlocks(sheetName, grid, merges, maxRow, maxCol, sheetIndex) {
|
|
|
16721
16740
|
}
|
|
16722
16741
|
async function parseXlsxDocument(buffer, options) {
|
|
16723
16742
|
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE3);
|
|
16724
|
-
const zip = await
|
|
16743
|
+
const zip = await JSZip3.loadAsync(buffer);
|
|
16725
16744
|
const warnings = [];
|
|
16726
16745
|
const workbookFile = zip.file("xl/workbook.xml");
|
|
16727
16746
|
if (!workbookFile) {
|
|
@@ -17427,7 +17446,7 @@ async function parseXlsDocument(buffer, options) {
|
|
|
17427
17446
|
}
|
|
17428
17447
|
|
|
17429
17448
|
// src/docx/parser.ts
|
|
17430
|
-
import
|
|
17449
|
+
import JSZip4 from "jszip";
|
|
17431
17450
|
import { DOMParser as DOMParser3 } from "@xmldom/xmldom";
|
|
17432
17451
|
|
|
17433
17452
|
// src/docx/equation.ts
|
|
@@ -18181,7 +18200,7 @@ async function extractImages(zip, rels, doc, warnings) {
|
|
|
18181
18200
|
}
|
|
18182
18201
|
async function parseDocxDocument(buffer, options) {
|
|
18183
18202
|
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE4);
|
|
18184
|
-
const zip = await
|
|
18203
|
+
const zip = await JSZip4.loadAsync(buffer);
|
|
18185
18204
|
const warnings = [];
|
|
18186
18205
|
const docFile = zip.file("word/document.xml");
|
|
18187
18206
|
if (!docFile) {
|
|
@@ -19099,7 +19118,7 @@ function fillInlineFields(text, values, filled, matchedLabels) {
|
|
|
19099
19118
|
}
|
|
19100
19119
|
|
|
19101
19120
|
// src/form/filler-hwpx.ts
|
|
19102
|
-
import
|
|
19121
|
+
import JSZip5 from "jszip";
|
|
19103
19122
|
|
|
19104
19123
|
// src/roundtrip/source-map.ts
|
|
19105
19124
|
function escapeXmlText(text) {
|
|
@@ -19773,7 +19792,7 @@ function patchZipEntries(original, replacements) {
|
|
|
19773
19792
|
// src/form/filler-hwpx.ts
|
|
19774
19793
|
async function fillHwpx(hwpxBuffer, values) {
|
|
19775
19794
|
const u8 = new Uint8Array(hwpxBuffer);
|
|
19776
|
-
const zip = await
|
|
19795
|
+
const zip = await JSZip5.loadAsync(hwpxBuffer);
|
|
19777
19796
|
const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
|
|
19778
19797
|
if (sectionPaths.length === 0) {
|
|
19779
19798
|
throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
@@ -20014,7 +20033,7 @@ async function fillHwpx(hwpxBuffer, values) {
|
|
|
20014
20033
|
}
|
|
20015
20034
|
|
|
20016
20035
|
// src/hwpx/generator.ts
|
|
20017
|
-
import
|
|
20036
|
+
import JSZip6 from "jszip";
|
|
20018
20037
|
|
|
20019
20038
|
// src/hwpx/text-metrics.ts
|
|
20020
20039
|
var ASCII_W = [
|
|
@@ -20850,7 +20869,7 @@ async function markdownToHwpx(markdown, options) {
|
|
|
20850
20869
|
const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
|
|
20851
20870
|
const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
|
|
20852
20871
|
const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit);
|
|
20853
|
-
const zip = new
|
|
20872
|
+
const zip = new JSZip6();
|
|
20854
20873
|
zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
|
|
20855
20874
|
zip.file("META-INF/container.xml", generateContainerXml());
|
|
20856
20875
|
zip.file("Contents/content.hpf", generateManifest());
|
|
@@ -21677,7 +21696,7 @@ function diffTableCells(a, b) {
|
|
|
21677
21696
|
}
|
|
21678
21697
|
|
|
21679
21698
|
// src/roundtrip/patcher.ts
|
|
21680
|
-
import
|
|
21699
|
+
import JSZip7 from "jszip";
|
|
21681
21700
|
|
|
21682
21701
|
// src/roundtrip/table-rows.ts
|
|
21683
21702
|
var ROW_OBJECT_RE = /<(?:[A-Za-z0-9_]+:)?(?:tbl|pic|equation|ole|container|shape|drawingObject|drawText|video|chart|fieldBegin|fieldEnd|ctrl)\b/;
|
|
@@ -22315,7 +22334,7 @@ async function patchHwpx(original, editedMarkdown, options) {
|
|
|
22315
22334
|
}
|
|
22316
22335
|
let zip;
|
|
22317
22336
|
try {
|
|
22318
|
-
zip = await
|
|
22337
|
+
zip = await JSZip7.loadAsync(original);
|
|
22319
22338
|
} catch {
|
|
22320
22339
|
return { success: false, applied: 0, skipped, error: "ZIP \uB85C\uB4DC \uC2E4\uD328" };
|
|
22321
22340
|
}
|
|
@@ -23682,10 +23701,10 @@ function stageParaPatch(scan, para, newPlain, skip) {
|
|
|
23682
23701
|
}
|
|
23683
23702
|
|
|
23684
23703
|
// src/roundtrip/session.ts
|
|
23685
|
-
import
|
|
23704
|
+
import JSZip8 from "jszip";
|
|
23686
23705
|
async function buildState(bytes) {
|
|
23687
23706
|
const parsed = await parseHwpxDocument(u8ToArrayBuffer2(bytes));
|
|
23688
|
-
const zip = await
|
|
23707
|
+
const zip = await JSZip8.loadAsync(bytes);
|
|
23689
23708
|
const sectionPaths = await resolveSectionEntryNames(zip);
|
|
23690
23709
|
if (sectionPaths.length === 0) {
|
|
23691
23710
|
throw new Error("HWPX \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
@@ -24266,7 +24285,7 @@ async function parseHwp(buffer, options) {
|
|
|
24266
24285
|
async function parsePdf(buffer, options) {
|
|
24267
24286
|
let parsePdfDocument;
|
|
24268
24287
|
try {
|
|
24269
|
-
const mod = await import("./parser-
|
|
24288
|
+
const mod = await import("./parser-FFEBMLSH.js");
|
|
24270
24289
|
parsePdfDocument = mod.parsePdfDocument;
|
|
24271
24290
|
} catch {
|
|
24272
24291
|
return {
|
|
@@ -24401,4 +24420,4 @@ export {
|
|
|
24401
24420
|
parseHwpml,
|
|
24402
24421
|
fillForm
|
|
24403
24422
|
};
|
|
24404
|
-
//# sourceMappingURL=chunk-
|
|
24423
|
+
//# sourceMappingURL=chunk-KT2BCHXI.js.map
|