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
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
sanitizeHref,
|
|
20
20
|
stripDtd,
|
|
21
21
|
toArrayBuffer
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-PELBIL4K.js";
|
|
23
23
|
import {
|
|
24
24
|
parsePageRange
|
|
25
25
|
} from "./chunk-GE43BE46.js";
|
|
@@ -279,9 +279,7 @@ async function detectZipFormat(buffer) {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
// src/hwpx/parser.ts
|
|
282
|
-
import
|
|
283
|
-
import { inflateRawSync } from "zlib";
|
|
284
|
-
import { DOMParser } from "@xmldom/xmldom";
|
|
282
|
+
import JSZip3 from "jszip";
|
|
285
283
|
|
|
286
284
|
// src/hwpx/com-fallback.ts
|
|
287
285
|
import { execFileSync } from "child_process";
|
|
@@ -390,6 +388,245 @@ function comResultToParseResult(pages, pageCount, warnings) {
|
|
|
390
388
|
};
|
|
391
389
|
}
|
|
392
390
|
|
|
391
|
+
// src/hwpx/parser-shared.ts
|
|
392
|
+
import { DOMParser } from "@xmldom/xmldom";
|
|
393
|
+
var MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024;
|
|
394
|
+
var MAX_ZIP_ENTRIES = 500;
|
|
395
|
+
function clampSpan(val, max) {
|
|
396
|
+
return Math.max(1, Math.min(val, max));
|
|
397
|
+
}
|
|
398
|
+
var MAX_XML_DEPTH = 200;
|
|
399
|
+
function createSectionShared() {
|
|
400
|
+
return { numState: /* @__PURE__ */ new Map(), pageText: { headers: [], footers: [] }, track: { deleteDepth: 0, warned: false } };
|
|
401
|
+
}
|
|
402
|
+
function createXmlParser(warnings) {
|
|
403
|
+
return new DOMParser({
|
|
404
|
+
onError(level, msg2) {
|
|
405
|
+
if (level === "fatalError") throw new KordocError(`XML \uD30C\uC2F1 \uC2E4\uD328: ${msg2}`);
|
|
406
|
+
warnings?.push({ code: "MALFORMED_XML", message: `XML ${level === "warn" ? "\uACBD\uACE0" : "\uC624\uB958"}: ${msg2}` });
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
function applyPageText(blocks, shared) {
|
|
411
|
+
const { headers, footers } = shared.pageText;
|
|
412
|
+
if (headers.length > 0) {
|
|
413
|
+
blocks.unshift(...headers.map((t) => ({ type: "paragraph", text: t, pageNumber: 1 })));
|
|
414
|
+
}
|
|
415
|
+
if (footers.length > 0) {
|
|
416
|
+
blocks.push(...footers.map((t) => ({ type: "paragraph", text: t })));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function findChildByLocalName(parent, name) {
|
|
420
|
+
const children = parent.childNodes;
|
|
421
|
+
if (!children) return null;
|
|
422
|
+
for (let i = 0; i < children.length; i++) {
|
|
423
|
+
const ch = children[i];
|
|
424
|
+
if (ch.nodeType !== 1) continue;
|
|
425
|
+
const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
|
|
426
|
+
if (tag === name) return ch;
|
|
427
|
+
}
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
function extractTextFromNode(node) {
|
|
431
|
+
let result = "";
|
|
432
|
+
const children = node.childNodes;
|
|
433
|
+
if (!children) return result;
|
|
434
|
+
for (let i = 0; i < children.length; i++) {
|
|
435
|
+
const child = children[i];
|
|
436
|
+
if (child.nodeType === 3) result += child.textContent || "";
|
|
437
|
+
else if (child.nodeType === 1) result += extractTextFromNode(child);
|
|
438
|
+
}
|
|
439
|
+
return result.trim();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/hwpx/styles.ts
|
|
443
|
+
async function extractHwpxStyles(zip, decompressed) {
|
|
444
|
+
const result = {
|
|
445
|
+
charProperties: /* @__PURE__ */ new Map(),
|
|
446
|
+
styles: /* @__PURE__ */ new Map(),
|
|
447
|
+
numberings: /* @__PURE__ */ new Map(),
|
|
448
|
+
bullets: /* @__PURE__ */ new Map(),
|
|
449
|
+
paraHeadings: /* @__PURE__ */ new Map()
|
|
450
|
+
};
|
|
451
|
+
const headerPaths = ["Contents/header.xml", "header.xml", "Contents/head.xml", "head.xml"];
|
|
452
|
+
for (const hp of headerPaths) {
|
|
453
|
+
const hpLower = hp.toLowerCase();
|
|
454
|
+
const file = zip.file(hp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === hpLower) || null;
|
|
455
|
+
if (!file) continue;
|
|
456
|
+
try {
|
|
457
|
+
const xml = await file.async("text");
|
|
458
|
+
if (decompressed) {
|
|
459
|
+
decompressed.total += xml.length * 2;
|
|
460
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
461
|
+
}
|
|
462
|
+
const parser = createXmlParser();
|
|
463
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
464
|
+
if (!doc.documentElement) continue;
|
|
465
|
+
parseCharProperties(doc, result.charProperties);
|
|
466
|
+
parseStyleElements(doc, result.styles);
|
|
467
|
+
const domDoc = doc;
|
|
468
|
+
parseNumberings(domDoc, result.numberings);
|
|
469
|
+
parseBullets(domDoc, result.bullets);
|
|
470
|
+
parseParaHeadings(domDoc, result.paraHeadings);
|
|
471
|
+
break;
|
|
472
|
+
} catch {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return result;
|
|
477
|
+
}
|
|
478
|
+
function parseCharProperties(doc, map) {
|
|
479
|
+
const tagNames = ["hh:charPr", "charPr", "hp:charPr"];
|
|
480
|
+
for (const tagName of tagNames) {
|
|
481
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
482
|
+
for (let i = 0; i < elements.length; i++) {
|
|
483
|
+
const el = elements[i];
|
|
484
|
+
const id = el.getAttribute("id") || el.getAttribute("IDRef") || "";
|
|
485
|
+
if (!id) continue;
|
|
486
|
+
const prop = {};
|
|
487
|
+
const height = el.getAttribute("height");
|
|
488
|
+
if (height) {
|
|
489
|
+
const parsedHeight = parseInt(height, 10);
|
|
490
|
+
if (!isNaN(parsedHeight) && parsedHeight > 0) {
|
|
491
|
+
prop.fontSize = parsedHeight / 100;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const bold = el.getAttribute("bold");
|
|
495
|
+
if (bold === "true" || bold === "1") prop.bold = true;
|
|
496
|
+
const italic = el.getAttribute("italic");
|
|
497
|
+
if (italic === "true" || italic === "1") prop.italic = true;
|
|
498
|
+
const fontFaces = el.getElementsByTagName("*");
|
|
499
|
+
for (let j = 0; j < fontFaces.length; j++) {
|
|
500
|
+
const ff = fontFaces[j];
|
|
501
|
+
const localTag = (ff.tagName || "").replace(/^[^:]+:/, "");
|
|
502
|
+
if (localTag === "fontface" || localTag === "fontRef") {
|
|
503
|
+
const face = ff.getAttribute("face") || ff.getAttribute("FontFace");
|
|
504
|
+
if (face) {
|
|
505
|
+
prop.fontName = face;
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
map.set(id, prop);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function parseStyleElements(doc, map) {
|
|
515
|
+
const tagNames = ["hh:style", "style", "hp:style"];
|
|
516
|
+
for (const tagName of tagNames) {
|
|
517
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
518
|
+
for (let i = 0; i < elements.length; i++) {
|
|
519
|
+
const el = elements[i];
|
|
520
|
+
const id = el.getAttribute("id") || el.getAttribute("IDRef") || String(i);
|
|
521
|
+
const name = el.getAttribute("name") || el.getAttribute("engName") || "";
|
|
522
|
+
const charPrId = el.getAttribute("charPrIDRef") || void 0;
|
|
523
|
+
const paraPrId = el.getAttribute("paraPrIDRef") || void 0;
|
|
524
|
+
map.set(id, { name, charPrId, paraPrId });
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function parseNumberings(doc, map) {
|
|
529
|
+
const tagNames = ["hh:numbering", "numbering"];
|
|
530
|
+
for (const tagName of tagNames) {
|
|
531
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
532
|
+
for (let i = 0; i < elements.length; i++) {
|
|
533
|
+
const el = elements[i];
|
|
534
|
+
const id = el.getAttribute("id") || "";
|
|
535
|
+
if (!id) continue;
|
|
536
|
+
const def = { heads: /* @__PURE__ */ new Map() };
|
|
537
|
+
const children = el.childNodes;
|
|
538
|
+
for (let j = 0; j < children.length; j++) {
|
|
539
|
+
const ch = children[j];
|
|
540
|
+
if (ch.nodeType !== 1) continue;
|
|
541
|
+
const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
|
|
542
|
+
if (tag !== "paraHead") continue;
|
|
543
|
+
const level = parseInt(ch.getAttribute("level") || "", 10);
|
|
544
|
+
if (isNaN(level) || level < 1 || level > 10) continue;
|
|
545
|
+
const start = parseInt(ch.getAttribute("start") || "1", 10);
|
|
546
|
+
def.heads.set(level, {
|
|
547
|
+
numFormat: ch.getAttribute("numFormat") || "DIGIT",
|
|
548
|
+
text: ch.textContent || "",
|
|
549
|
+
start: isNaN(start) ? 1 : start
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
if (def.heads.size > 0) map.set(id, def);
|
|
553
|
+
}
|
|
554
|
+
if (map.size > 0) break;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function parseBullets(doc, map) {
|
|
558
|
+
const tagNames = ["hh:bullet", "bullet"];
|
|
559
|
+
for (const tagName of tagNames) {
|
|
560
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
561
|
+
for (let i = 0; i < elements.length; i++) {
|
|
562
|
+
const el = elements[i];
|
|
563
|
+
const id = el.getAttribute("id") || "";
|
|
564
|
+
const char = el.getAttribute("char") || "";
|
|
565
|
+
if (id && char) map.set(id, char);
|
|
566
|
+
}
|
|
567
|
+
if (map.size > 0) break;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
function parseParaHeadings(doc, map) {
|
|
571
|
+
const tagNames = ["hh:paraPr", "paraPr"];
|
|
572
|
+
for (const tagName of tagNames) {
|
|
573
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
574
|
+
for (let i = 0; i < elements.length; i++) {
|
|
575
|
+
const el = elements[i];
|
|
576
|
+
const id = el.getAttribute("id") || "";
|
|
577
|
+
if (!id) continue;
|
|
578
|
+
const heading = findChildByLocalName(el, "heading");
|
|
579
|
+
if (!heading) continue;
|
|
580
|
+
const type = heading.getAttribute("type") || "NONE";
|
|
581
|
+
if (type !== "NUMBER" && type !== "BULLET" && type !== "OUTLINE") continue;
|
|
582
|
+
const level = parseInt(heading.getAttribute("level") || "0", 10);
|
|
583
|
+
map.set(id, {
|
|
584
|
+
type,
|
|
585
|
+
idRef: heading.getAttribute("idRef") || "0",
|
|
586
|
+
level: isNaN(level) ? 0 : Math.max(0, Math.min(level, 9))
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
if (map.size > 0) break;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function detectHwpxHeadings(blocks, styleMap) {
|
|
593
|
+
if (blocks.some((b) => b.type === "heading")) return;
|
|
594
|
+
let baseFontSize = 0;
|
|
595
|
+
const sizeFreq = /* @__PURE__ */ new Map();
|
|
596
|
+
for (const b of blocks) {
|
|
597
|
+
if (b.style?.fontSize) {
|
|
598
|
+
sizeFreq.set(b.style.fontSize, (sizeFreq.get(b.style.fontSize) || 0) + 1);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
let maxCount = 0;
|
|
602
|
+
for (const [size, count] of sizeFreq) {
|
|
603
|
+
if (count > maxCount) {
|
|
604
|
+
maxCount = count;
|
|
605
|
+
baseFontSize = size;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
for (const block of blocks) {
|
|
609
|
+
if (block.type !== "paragraph" || !block.text) continue;
|
|
610
|
+
const text = block.text.trim();
|
|
611
|
+
if (text.length === 0 || text.length > 200 || /^\d+$/.test(text)) continue;
|
|
612
|
+
let level = 0;
|
|
613
|
+
if (baseFontSize > 0 && block.style?.fontSize) {
|
|
614
|
+
const ratio = block.style.fontSize / baseFontSize;
|
|
615
|
+
if (ratio >= HEADING_RATIO_H1) level = 1;
|
|
616
|
+
else if (ratio >= HEADING_RATIO_H2) level = 2;
|
|
617
|
+
else if (ratio >= HEADING_RATIO_H3) level = 3;
|
|
618
|
+
}
|
|
619
|
+
const compactText = text.replace(/\s+/g, "");
|
|
620
|
+
if (/^제\d+[조장절편]/.test(compactText) && text.length <= 50) {
|
|
621
|
+
if (level === 0) level = 3;
|
|
622
|
+
}
|
|
623
|
+
if (level > 0) {
|
|
624
|
+
block.type = "heading";
|
|
625
|
+
block.level = level;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
393
630
|
// src/hwpx/equation.ts
|
|
394
631
|
var CONVERT_MAP = {
|
|
395
632
|
TIMES: "\\times",
|
|
@@ -829,629 +1066,101 @@ function hmlToLatex(hmlEqStr) {
|
|
|
829
1066
|
return out;
|
|
830
1067
|
}
|
|
831
1068
|
|
|
832
|
-
// src/hwpx/
|
|
833
|
-
var
|
|
834
|
-
var
|
|
835
|
-
function
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
};
|
|
858
|
-
const headerPaths = ["Contents/header.xml", "header.xml", "Contents/head.xml", "head.xml"];
|
|
859
|
-
for (const hp of headerPaths) {
|
|
860
|
-
const hpLower = hp.toLowerCase();
|
|
861
|
-
const file = zip.file(hp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === hpLower) || null;
|
|
862
|
-
if (!file) continue;
|
|
863
|
-
try {
|
|
864
|
-
const xml = await file.async("text");
|
|
865
|
-
if (decompressed) {
|
|
866
|
-
decompressed.total += xml.length * 2;
|
|
867
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
868
|
-
}
|
|
869
|
-
const parser = createXmlParser();
|
|
870
|
-
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
871
|
-
if (!doc.documentElement) continue;
|
|
872
|
-
parseCharProperties(doc, result.charProperties);
|
|
873
|
-
parseStyleElements(doc, result.styles);
|
|
874
|
-
const domDoc = doc;
|
|
875
|
-
parseNumberings(domDoc, result.numberings);
|
|
876
|
-
parseBullets(domDoc, result.bullets);
|
|
877
|
-
parseParaHeadings(domDoc, result.paraHeadings);
|
|
878
|
-
break;
|
|
879
|
-
} catch {
|
|
880
|
-
continue;
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
return result;
|
|
884
|
-
}
|
|
885
|
-
function parseCharProperties(doc, map) {
|
|
886
|
-
const tagNames = ["hh:charPr", "charPr", "hp:charPr"];
|
|
887
|
-
for (const tagName of tagNames) {
|
|
888
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
889
|
-
for (let i = 0; i < elements.length; i++) {
|
|
890
|
-
const el = elements[i];
|
|
891
|
-
const id = el.getAttribute("id") || el.getAttribute("IDRef") || "";
|
|
892
|
-
if (!id) continue;
|
|
893
|
-
const prop = {};
|
|
894
|
-
const height = el.getAttribute("height");
|
|
895
|
-
if (height) {
|
|
896
|
-
const parsedHeight = parseInt(height, 10);
|
|
897
|
-
if (!isNaN(parsedHeight) && parsedHeight > 0) {
|
|
898
|
-
prop.fontSize = parsedHeight / 100;
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
const bold = el.getAttribute("bold");
|
|
902
|
-
if (bold === "true" || bold === "1") prop.bold = true;
|
|
903
|
-
const italic = el.getAttribute("italic");
|
|
904
|
-
if (italic === "true" || italic === "1") prop.italic = true;
|
|
905
|
-
const fontFaces = el.getElementsByTagName("*");
|
|
906
|
-
for (let j = 0; j < fontFaces.length; j++) {
|
|
907
|
-
const ff = fontFaces[j];
|
|
908
|
-
const localTag = (ff.tagName || "").replace(/^[^:]+:/, "");
|
|
909
|
-
if (localTag === "fontface" || localTag === "fontRef") {
|
|
910
|
-
const face = ff.getAttribute("face") || ff.getAttribute("FontFace");
|
|
911
|
-
if (face) {
|
|
912
|
-
prop.fontName = face;
|
|
913
|
-
break;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
map.set(id, prop);
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
function parseStyleElements(doc, map) {
|
|
922
|
-
const tagNames = ["hh:style", "style", "hp:style"];
|
|
923
|
-
for (const tagName of tagNames) {
|
|
924
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
925
|
-
for (let i = 0; i < elements.length; i++) {
|
|
926
|
-
const el = elements[i];
|
|
927
|
-
const id = el.getAttribute("id") || el.getAttribute("IDRef") || String(i);
|
|
928
|
-
const name = el.getAttribute("name") || el.getAttribute("engName") || "";
|
|
929
|
-
const charPrId = el.getAttribute("charPrIDRef") || void 0;
|
|
930
|
-
const paraPrId = el.getAttribute("paraPrIDRef") || void 0;
|
|
931
|
-
map.set(id, { name, charPrId, paraPrId });
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
function parseNumberings(doc, map) {
|
|
936
|
-
const tagNames = ["hh:numbering", "numbering"];
|
|
937
|
-
for (const tagName of tagNames) {
|
|
938
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
939
|
-
for (let i = 0; i < elements.length; i++) {
|
|
940
|
-
const el = elements[i];
|
|
941
|
-
const id = el.getAttribute("id") || "";
|
|
942
|
-
if (!id) continue;
|
|
943
|
-
const def = { heads: /* @__PURE__ */ new Map() };
|
|
944
|
-
const children = el.childNodes;
|
|
945
|
-
for (let j = 0; j < children.length; j++) {
|
|
946
|
-
const ch = children[j];
|
|
947
|
-
if (ch.nodeType !== 1) continue;
|
|
948
|
-
const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
|
|
949
|
-
if (tag !== "paraHead") continue;
|
|
950
|
-
const level = parseInt(ch.getAttribute("level") || "", 10);
|
|
951
|
-
if (isNaN(level) || level < 1 || level > 10) continue;
|
|
952
|
-
const start = parseInt(ch.getAttribute("start") || "1", 10);
|
|
953
|
-
def.heads.set(level, {
|
|
954
|
-
numFormat: ch.getAttribute("numFormat") || "DIGIT",
|
|
955
|
-
text: ch.textContent || "",
|
|
956
|
-
start: isNaN(start) ? 1 : start
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
if (def.heads.size > 0) map.set(id, def);
|
|
960
|
-
}
|
|
961
|
-
if (map.size > 0) break;
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
function parseBullets(doc, map) {
|
|
965
|
-
const tagNames = ["hh:bullet", "bullet"];
|
|
966
|
-
for (const tagName of tagNames) {
|
|
967
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
968
|
-
for (let i = 0; i < elements.length; i++) {
|
|
969
|
-
const el = elements[i];
|
|
970
|
-
const id = el.getAttribute("id") || "";
|
|
971
|
-
const char = el.getAttribute("char") || "";
|
|
972
|
-
if (id && char) map.set(id, char);
|
|
973
|
-
}
|
|
974
|
-
if (map.size > 0) break;
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
function parseParaHeadings(doc, map) {
|
|
978
|
-
const tagNames = ["hh:paraPr", "paraPr"];
|
|
979
|
-
for (const tagName of tagNames) {
|
|
980
|
-
const elements = doc.getElementsByTagName(tagName);
|
|
981
|
-
for (let i = 0; i < elements.length; i++) {
|
|
982
|
-
const el = elements[i];
|
|
983
|
-
const id = el.getAttribute("id") || "";
|
|
984
|
-
if (!id) continue;
|
|
985
|
-
const heading = findChildByLocalName(el, "heading");
|
|
986
|
-
if (!heading) continue;
|
|
987
|
-
const type = heading.getAttribute("type") || "NONE";
|
|
988
|
-
if (type !== "NUMBER" && type !== "BULLET" && type !== "OUTLINE") continue;
|
|
989
|
-
const level = parseInt(heading.getAttribute("level") || "0", 10);
|
|
990
|
-
map.set(id, {
|
|
991
|
-
type,
|
|
992
|
-
idRef: heading.getAttribute("idRef") || "0",
|
|
993
|
-
level: isNaN(level) ? 0 : Math.max(0, Math.min(level, 9))
|
|
994
|
-
});
|
|
995
|
-
}
|
|
996
|
-
if (map.size > 0) break;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
var HANGUL_SYLLABLE_SEQ = "\uAC00\uB098\uB2E4\uB77C\uB9C8\uBC14\uC0AC\uC544\uC790\uCC28\uCE74\uD0C0\uD30C\uD558";
|
|
1000
|
-
var HANGUL_JAMO_SEQ = "\u3131\u3134\u3137\u3139\u3141\u3142\u3145\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
|
|
1001
|
-
function toRoman(n) {
|
|
1002
|
-
if (n <= 0 || n >= 4e3) return String(n);
|
|
1003
|
-
const table = [
|
|
1004
|
-
[1e3, "M"],
|
|
1005
|
-
[900, "CM"],
|
|
1006
|
-
[500, "D"],
|
|
1007
|
-
[400, "CD"],
|
|
1008
|
-
[100, "C"],
|
|
1009
|
-
[90, "XC"],
|
|
1010
|
-
[50, "L"],
|
|
1011
|
-
[40, "XL"],
|
|
1012
|
-
[10, "X"],
|
|
1013
|
-
[9, "IX"],
|
|
1014
|
-
[5, "V"],
|
|
1015
|
-
[4, "IV"],
|
|
1016
|
-
[1, "I"]
|
|
1017
|
-
];
|
|
1018
|
-
let out = "";
|
|
1019
|
-
for (const [v, s] of table) {
|
|
1020
|
-
while (n >= v) {
|
|
1021
|
-
out += s;
|
|
1022
|
-
n -= v;
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
return out;
|
|
1026
|
-
}
|
|
1027
|
-
function formatHeadNumber(n, numFormat) {
|
|
1028
|
-
if (n <= 0) n = 1;
|
|
1029
|
-
switch (numFormat) {
|
|
1030
|
-
case "DIGIT":
|
|
1031
|
-
return String(n);
|
|
1032
|
-
case "CIRCLED_DIGIT":
|
|
1033
|
-
return n <= 20 ? String.fromCodePoint(9312 + n - 1) : `(${n})`;
|
|
1034
|
-
case "HANGUL_SYLLABLE":
|
|
1035
|
-
return HANGUL_SYLLABLE_SEQ[(n - 1) % HANGUL_SYLLABLE_SEQ.length];
|
|
1036
|
-
case "CIRCLED_HANGUL_SYLLABLE":
|
|
1037
|
-
return n <= 14 ? String.fromCodePoint(12910 + n - 1) : HANGUL_SYLLABLE_SEQ[(n - 1) % 14];
|
|
1038
|
-
case "HANGUL_JAMO":
|
|
1039
|
-
return HANGUL_JAMO_SEQ[(n - 1) % HANGUL_JAMO_SEQ.length];
|
|
1040
|
-
case "CIRCLED_HANGUL_JAMO":
|
|
1041
|
-
return n <= 14 ? String.fromCodePoint(12896 + n - 1) : HANGUL_JAMO_SEQ[(n - 1) % 14];
|
|
1042
|
-
case "LATIN_CAPITAL":
|
|
1043
|
-
return String.fromCharCode(65 + (n - 1) % 26);
|
|
1044
|
-
case "LATIN_SMALL":
|
|
1045
|
-
return String.fromCharCode(97 + (n - 1) % 26);
|
|
1046
|
-
case "CIRCLED_LATIN_CAPITAL":
|
|
1047
|
-
return n <= 26 ? String.fromCodePoint(9398 + n - 1) : String.fromCharCode(65 + (n - 1) % 26);
|
|
1048
|
-
case "CIRCLED_LATIN_SMALL":
|
|
1049
|
-
return n <= 26 ? String.fromCodePoint(9424 + n - 1) : String.fromCharCode(97 + (n - 1) % 26);
|
|
1050
|
-
case "ROMAN_CAPITAL":
|
|
1051
|
-
return toRoman(n);
|
|
1052
|
-
case "ROMAN_SMALL":
|
|
1053
|
-
return toRoman(n).toLowerCase();
|
|
1054
|
-
default:
|
|
1055
|
-
return String(n);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
function resolveParaHeading(paraEl, ctx) {
|
|
1059
|
-
const sm = ctx.styleMap;
|
|
1060
|
-
if (!sm) return null;
|
|
1061
|
-
const prId = paraEl.getAttribute("paraPrIDRef");
|
|
1062
|
-
if (!prId) return null;
|
|
1063
|
-
const ref = sm.paraHeadings.get(prId);
|
|
1064
|
-
if (!ref) return null;
|
|
1065
|
-
if (ref.type === "BULLET") {
|
|
1066
|
-
const char = sm.bullets.get(ref.idRef);
|
|
1067
|
-
return char ? { prefix: char } : null;
|
|
1068
|
-
}
|
|
1069
|
-
const numId = ref.type === "OUTLINE" ? ctx.outlineNumId || "1" : ref.idRef;
|
|
1070
|
-
const level = Math.min(ref.level + 1, 10);
|
|
1071
|
-
const headingLevel = ref.type === "OUTLINE" ? Math.min(ref.level + 1, 6) : void 0;
|
|
1072
|
-
const numDef = sm.numberings.get(numId);
|
|
1073
|
-
if (!numDef) return headingLevel ? { headingLevel } : null;
|
|
1074
|
-
let counters = ctx.shared.numState.get(numId);
|
|
1075
|
-
if (!counters) {
|
|
1076
|
-
counters = new Array(11).fill(0);
|
|
1077
|
-
ctx.shared.numState.set(numId, counters);
|
|
1078
|
-
}
|
|
1079
|
-
const head = numDef.heads.get(level);
|
|
1080
|
-
counters[level] = counters[level] === 0 ? head?.start ?? 1 : counters[level] + 1;
|
|
1081
|
-
for (let l = level + 1; l <= 10; l++) counters[l] = 0;
|
|
1082
|
-
const fmtText = head?.text?.trim() || `^${level}.`;
|
|
1083
|
-
const prefix = fmtText.replace(/\^(10|[1-9])/g, (_, d) => {
|
|
1084
|
-
const lv = parseInt(d, 10);
|
|
1085
|
-
const refHead = numDef.heads.get(lv);
|
|
1086
|
-
const n = counters[lv] || refHead?.start || 1;
|
|
1087
|
-
return formatHeadNumber(n, refHead?.numFormat || "DIGIT");
|
|
1088
|
-
});
|
|
1089
|
-
return { prefix, headingLevel };
|
|
1090
|
-
}
|
|
1091
|
-
async function parseHwpxDocument(buffer, options) {
|
|
1092
|
-
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
1093
|
-
let zip;
|
|
1094
|
-
try {
|
|
1095
|
-
zip = await JSZip2.loadAsync(buffer);
|
|
1096
|
-
} catch {
|
|
1097
|
-
return extractFromBrokenZip(buffer);
|
|
1098
|
-
}
|
|
1099
|
-
const actualEntryCount = Object.keys(zip.files).length;
|
|
1100
|
-
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
1101
|
-
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1102
|
-
}
|
|
1103
|
-
const manifestFile = zip.file("META-INF/manifest.xml");
|
|
1104
|
-
if (manifestFile) {
|
|
1105
|
-
const manifestXml = await manifestFile.async("text");
|
|
1106
|
-
if (isEncryptedHwpx(manifestXml)) {
|
|
1107
|
-
if (isComFallbackAvailable() && options?.filePath) {
|
|
1108
|
-
const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
|
|
1109
|
-
if (pages.some((p) => p && p.trim().length > 0)) {
|
|
1110
|
-
return comResultToParseResult(pages, pageCount, warnings2);
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
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.");
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
const decompressed = { total: 0 };
|
|
1117
|
-
const metadata = {};
|
|
1118
|
-
await extractHwpxMetadata(zip, metadata, decompressed);
|
|
1119
|
-
const styleMap = await extractHwpxStyles(zip, decompressed);
|
|
1120
|
-
const warnings = [];
|
|
1121
|
-
const sectionPaths = await resolveSectionPaths(zip);
|
|
1122
|
-
if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
1123
|
-
metadata.pageCount = sectionPaths.length;
|
|
1124
|
-
const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null;
|
|
1125
|
-
const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length;
|
|
1126
|
-
const blocks = [];
|
|
1127
|
-
const shared = createSectionShared();
|
|
1128
|
-
let parsedSections = 0;
|
|
1129
|
-
for (let si = 0; si < sectionPaths.length; si++) {
|
|
1130
|
-
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
1131
|
-
const file = zip.file(sectionPaths[si]);
|
|
1132
|
-
if (!file) continue;
|
|
1133
|
-
try {
|
|
1134
|
-
const xml = await file.async("text");
|
|
1135
|
-
decompressed.total += xml.length * 2;
|
|
1136
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1137
|
-
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
1138
|
-
parsedSections++;
|
|
1139
|
-
options?.onProgress?.(parsedSections, totalTarget);
|
|
1140
|
-
} catch (secErr) {
|
|
1141
|
-
if (secErr instanceof KordocError) throw secErr;
|
|
1142
|
-
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" });
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
applyPageText(blocks, shared);
|
|
1146
|
-
const images = await extractImagesFromZip(zip, blocks, decompressed, warnings);
|
|
1147
|
-
detectHwpxHeadings(blocks, styleMap);
|
|
1148
|
-
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
1149
|
-
const markdown = blocksToMarkdown(blocks);
|
|
1150
|
-
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 };
|
|
1151
|
-
}
|
|
1152
|
-
function applyPageText(blocks, shared) {
|
|
1153
|
-
const { headers, footers } = shared.pageText;
|
|
1154
|
-
if (headers.length > 0) {
|
|
1155
|
-
blocks.unshift(...headers.map((t) => ({ type: "paragraph", text: t, pageNumber: 1 })));
|
|
1156
|
-
}
|
|
1157
|
-
if (footers.length > 0) {
|
|
1158
|
-
blocks.push(...footers.map((t) => ({ type: "paragraph", text: t })));
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
function imageExtToMime(ext) {
|
|
1162
|
-
switch (ext.toLowerCase()) {
|
|
1163
|
-
case "jpg":
|
|
1164
|
-
case "jpeg":
|
|
1165
|
-
return "image/jpeg";
|
|
1166
|
-
case "png":
|
|
1167
|
-
return "image/png";
|
|
1168
|
-
case "gif":
|
|
1169
|
-
return "image/gif";
|
|
1170
|
-
case "bmp":
|
|
1171
|
-
return "image/bmp";
|
|
1172
|
-
case "tif":
|
|
1173
|
-
case "tiff":
|
|
1174
|
-
return "image/tiff";
|
|
1175
|
-
case "wmf":
|
|
1176
|
-
return "image/wmf";
|
|
1177
|
-
case "emf":
|
|
1178
|
-
return "image/emf";
|
|
1179
|
-
case "svg":
|
|
1180
|
-
return "image/svg+xml";
|
|
1181
|
-
default:
|
|
1182
|
-
return "application/octet-stream";
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
function mimeToExt(mime) {
|
|
1186
|
-
if (mime.includes("jpeg")) return "jpg";
|
|
1187
|
-
if (mime.includes("png")) return "png";
|
|
1188
|
-
if (mime.includes("gif")) return "gif";
|
|
1189
|
-
if (mime.includes("bmp")) return "bmp";
|
|
1190
|
-
if (mime.includes("tiff")) return "tif";
|
|
1191
|
-
if (mime.includes("wmf")) return "wmf";
|
|
1192
|
-
if (mime.includes("emf")) return "emf";
|
|
1193
|
-
if (mime.includes("svg")) return "svg";
|
|
1194
|
-
return "bin";
|
|
1195
|
-
}
|
|
1196
|
-
function collectImageBlocks(blocks, out, ownerCell, depth = 0) {
|
|
1197
|
-
if (depth > MAX_XML_DEPTH) return;
|
|
1198
|
-
for (const block of blocks) {
|
|
1199
|
-
if (block.type === "image") {
|
|
1200
|
-
out.push({ block, ownerCell });
|
|
1201
|
-
} else if (block.type === "table" && block.table) {
|
|
1202
|
-
for (const row of block.table.cells) {
|
|
1203
|
-
for (const cell of row) {
|
|
1204
|
-
if (cell.blocks?.length) collectImageBlocks(cell.blocks, out, cell, depth + 1);
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
async function extractImagesFromZip(zip, blocks, decompressed, warnings) {
|
|
1211
|
-
const images = [];
|
|
1212
|
-
let imageIndex = 0;
|
|
1213
|
-
const imageBlocks = [];
|
|
1214
|
-
collectImageBlocks(blocks, imageBlocks);
|
|
1215
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
1216
|
-
for (const { block, ownerCell } of imageBlocks) {
|
|
1217
|
-
if (block.type !== "image" || !block.text) continue;
|
|
1218
|
-
const ref = block.text;
|
|
1219
|
-
let img = resolved.get(ref);
|
|
1220
|
-
if (img === void 0) {
|
|
1221
|
-
img = null;
|
|
1222
|
-
const candidates = [
|
|
1223
|
-
`BinData/${ref}`,
|
|
1224
|
-
`Contents/BinData/${ref}`,
|
|
1225
|
-
ref
|
|
1226
|
-
// 절대 경로일 수도 있음
|
|
1227
|
-
];
|
|
1228
|
-
let resolvedPath = null;
|
|
1229
|
-
if (!ref.includes(".")) {
|
|
1230
|
-
const prefixes = [`BinData/${ref}`, `Contents/BinData/${ref}`];
|
|
1231
|
-
for (const prefix of prefixes) {
|
|
1232
|
-
const match = zip.file(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.[a-zA-Z0-9]+$`));
|
|
1233
|
-
if (match.length > 0) {
|
|
1234
|
-
resolvedPath = match[0].name;
|
|
1235
|
-
break;
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
const allCandidates = resolvedPath ? [resolvedPath, ...candidates] : candidates;
|
|
1240
|
-
for (const path of allCandidates) {
|
|
1241
|
-
if (isPathTraversal(path)) continue;
|
|
1242
|
-
const file = zip.file(path);
|
|
1243
|
-
if (!file) continue;
|
|
1244
|
-
try {
|
|
1245
|
-
const data = await file.async("uint8array");
|
|
1246
|
-
decompressed.total += data.length;
|
|
1247
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1248
|
-
const ext = path.includes(".") ? path.split(".").pop() || "png" : "png";
|
|
1249
|
-
const mimeType = imageExtToMime(ext);
|
|
1250
|
-
imageIndex++;
|
|
1251
|
-
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
1252
|
-
img = { filename, data, mimeType };
|
|
1253
|
-
images.push(img);
|
|
1254
|
-
break;
|
|
1255
|
-
} catch (err) {
|
|
1256
|
-
if (err instanceof KordocError) throw err;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
if (!img) warnings?.push({ page: block.pageNumber, message: `\uC774\uBBF8\uC9C0 \uD30C\uC77C \uC5C6\uC74C: ${ref}`, code: "SKIPPED_IMAGE" });
|
|
1260
|
-
resolved.set(ref, img);
|
|
1261
|
-
}
|
|
1262
|
-
if (!img) {
|
|
1263
|
-
block.type = "paragraph";
|
|
1264
|
-
block.text = `[\uC774\uBBF8\uC9C0: ${ref}]`;
|
|
1265
|
-
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
1266
|
-
continue;
|
|
1267
|
-
}
|
|
1268
|
-
block.text = img.filename;
|
|
1269
|
-
block.imageData = { data: img.data, mimeType: img.mimeType, filename: ref };
|
|
1270
|
-
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, ``);
|
|
1271
|
-
}
|
|
1272
|
-
return images;
|
|
1273
|
-
}
|
|
1274
|
-
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
1275
|
-
try {
|
|
1276
|
-
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
1277
|
-
for (const mp of metaPaths) {
|
|
1278
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
1279
|
-
if (!file) continue;
|
|
1280
|
-
const xml = await file.async("text");
|
|
1281
|
-
if (decompressed) {
|
|
1282
|
-
decompressed.total += xml.length * 2;
|
|
1283
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1284
|
-
}
|
|
1285
|
-
parseDublinCoreMetadata(xml, metadata);
|
|
1286
|
-
if (metadata.title || metadata.author) return;
|
|
1287
|
-
}
|
|
1288
|
-
} catch {
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
function parseDublinCoreMetadata(xml, metadata) {
|
|
1292
|
-
const parser = createXmlParser();
|
|
1293
|
-
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
1294
|
-
if (!doc.documentElement) return;
|
|
1295
|
-
const getText = (tagNames) => {
|
|
1296
|
-
for (const tag of tagNames) {
|
|
1297
|
-
const els = doc.getElementsByTagName(tag);
|
|
1298
|
-
if (els.length > 0) {
|
|
1299
|
-
const text = els[0].textContent?.trim();
|
|
1300
|
-
if (text) return text;
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
return void 0;
|
|
1304
|
-
};
|
|
1305
|
-
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
1306
|
-
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
1307
|
-
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
1308
|
-
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
1309
|
-
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
1310
|
-
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
1311
|
-
if (keywords && !metadata.keywords) {
|
|
1312
|
-
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
function extractFromBrokenZip(buffer) {
|
|
1316
|
-
const data = new Uint8Array(buffer);
|
|
1317
|
-
const view = new DataView(buffer);
|
|
1318
|
-
let pos = 0;
|
|
1319
|
-
const blocks = [];
|
|
1320
|
-
const warnings = [
|
|
1321
|
-
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
1322
|
-
];
|
|
1323
|
-
let totalDecompressed = 0;
|
|
1324
|
-
let entryCount = 0;
|
|
1325
|
-
let sectionNum = 0;
|
|
1326
|
-
const shared = createSectionShared();
|
|
1327
|
-
while (pos < data.length - 30) {
|
|
1328
|
-
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
1329
|
-
pos++;
|
|
1330
|
-
while (pos < data.length - 30) {
|
|
1331
|
-
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
1332
|
-
pos++;
|
|
1333
|
-
}
|
|
1334
|
-
continue;
|
|
1335
|
-
}
|
|
1336
|
-
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
1337
|
-
const method = view.getUint16(pos + 8, true);
|
|
1338
|
-
const compSize = view.getUint32(pos + 18, true);
|
|
1339
|
-
const nameLen = view.getUint16(pos + 26, true);
|
|
1340
|
-
const extraLen = view.getUint16(pos + 28, true);
|
|
1341
|
-
if (nameLen > 1024 || extraLen > 65535) {
|
|
1342
|
-
pos += 30 + nameLen + extraLen;
|
|
1343
|
-
continue;
|
|
1344
|
-
}
|
|
1345
|
-
const fileStart = pos + 30 + nameLen + extraLen;
|
|
1346
|
-
if (fileStart + compSize > data.length) break;
|
|
1347
|
-
if (compSize === 0 && method !== 0) {
|
|
1348
|
-
pos = fileStart;
|
|
1349
|
-
continue;
|
|
1350
|
-
}
|
|
1351
|
-
const nameBytes = data.slice(pos + 30, pos + 30 + nameLen);
|
|
1352
|
-
const name = new TextDecoder().decode(nameBytes);
|
|
1353
|
-
if (isPathTraversal(name)) {
|
|
1354
|
-
pos = fileStart + compSize;
|
|
1355
|
-
continue;
|
|
1356
|
-
}
|
|
1357
|
-
const fileData = data.slice(fileStart, fileStart + compSize);
|
|
1358
|
-
pos = fileStart + compSize;
|
|
1359
|
-
if (!name.toLowerCase().includes("section") || !name.endsWith(".xml")) continue;
|
|
1360
|
-
try {
|
|
1361
|
-
let content;
|
|
1362
|
-
if (method === 0) {
|
|
1363
|
-
content = new TextDecoder().decode(fileData);
|
|
1364
|
-
} else if (method === 8) {
|
|
1365
|
-
const decompressed = inflateRawSync(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
1366
|
-
content = new TextDecoder().decode(decompressed);
|
|
1367
|
-
} else {
|
|
1368
|
-
continue;
|
|
1369
|
-
}
|
|
1370
|
-
totalDecompressed += content.length * 2;
|
|
1371
|
-
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
1372
|
-
sectionNum++;
|
|
1373
|
-
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
1374
|
-
} catch {
|
|
1375
|
-
continue;
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
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");
|
|
1379
|
-
applyPageText(blocks, shared);
|
|
1380
|
-
const markdown = blocksToMarkdown(blocks);
|
|
1381
|
-
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
1382
|
-
}
|
|
1383
|
-
async function resolveSectionPaths(zip) {
|
|
1384
|
-
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
1385
|
-
for (const mp of manifestPaths) {
|
|
1386
|
-
const mpLower = mp.toLowerCase();
|
|
1387
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
1388
|
-
if (!file) continue;
|
|
1389
|
-
const xml = await file.async("text");
|
|
1390
|
-
const paths = parseSectionPathsFromManifest(xml);
|
|
1391
|
-
if (paths.length > 0) return paths;
|
|
1392
|
-
}
|
|
1393
|
-
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
1394
|
-
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
1395
|
-
}
|
|
1396
|
-
function parseSectionPathsFromManifest(xml) {
|
|
1397
|
-
const parser = createXmlParser();
|
|
1398
|
-
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
1399
|
-
const items = doc.getElementsByTagName("opf:item");
|
|
1400
|
-
const spine = doc.getElementsByTagName("opf:itemref");
|
|
1401
|
-
const idToHref = /* @__PURE__ */ new Map();
|
|
1402
|
-
for (let i = 0; i < items.length; i++) {
|
|
1403
|
-
const item = items[i];
|
|
1404
|
-
const id = item.getAttribute("id") || "";
|
|
1405
|
-
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
1406
|
-
if (id && href) idToHref.set(id, href);
|
|
1407
|
-
}
|
|
1408
|
-
if (spine.length > 0) {
|
|
1409
|
-
const ordered = [];
|
|
1410
|
-
for (let i = 0; i < spine.length; i++) {
|
|
1411
|
-
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
1412
|
-
if (href) ordered.push(href);
|
|
1069
|
+
// src/hwpx/para-heading.ts
|
|
1070
|
+
var HANGUL_SYLLABLE_SEQ = "\uAC00\uB098\uB2E4\uB77C\uB9C8\uBC14\uC0AC\uC544\uC790\uCC28\uCE74\uD0C0\uD30C\uD558";
|
|
1071
|
+
var HANGUL_JAMO_SEQ = "\u3131\u3134\u3137\u3139\u3141\u3142\u3145\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
|
|
1072
|
+
function toRoman(n) {
|
|
1073
|
+
if (n <= 0 || n >= 4e3) return String(n);
|
|
1074
|
+
const table = [
|
|
1075
|
+
[1e3, "M"],
|
|
1076
|
+
[900, "CM"],
|
|
1077
|
+
[500, "D"],
|
|
1078
|
+
[400, "CD"],
|
|
1079
|
+
[100, "C"],
|
|
1080
|
+
[90, "XC"],
|
|
1081
|
+
[50, "L"],
|
|
1082
|
+
[40, "XL"],
|
|
1083
|
+
[10, "X"],
|
|
1084
|
+
[9, "IX"],
|
|
1085
|
+
[5, "V"],
|
|
1086
|
+
[4, "IV"],
|
|
1087
|
+
[1, "I"]
|
|
1088
|
+
];
|
|
1089
|
+
let out = "";
|
|
1090
|
+
for (const [v, s] of table) {
|
|
1091
|
+
while (n >= v) {
|
|
1092
|
+
out += s;
|
|
1093
|
+
n -= v;
|
|
1413
1094
|
}
|
|
1414
|
-
if (ordered.length > 0) return ordered;
|
|
1415
1095
|
}
|
|
1416
|
-
return
|
|
1096
|
+
return out;
|
|
1417
1097
|
}
|
|
1418
|
-
function
|
|
1419
|
-
if (
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1098
|
+
function formatHeadNumber(n, numFormat) {
|
|
1099
|
+
if (n <= 0) n = 1;
|
|
1100
|
+
switch (numFormat) {
|
|
1101
|
+
case "DIGIT":
|
|
1102
|
+
return String(n);
|
|
1103
|
+
case "CIRCLED_DIGIT":
|
|
1104
|
+
return n <= 20 ? String.fromCodePoint(9312 + n - 1) : `(${n})`;
|
|
1105
|
+
case "HANGUL_SYLLABLE":
|
|
1106
|
+
return HANGUL_SYLLABLE_SEQ[(n - 1) % HANGUL_SYLLABLE_SEQ.length];
|
|
1107
|
+
case "CIRCLED_HANGUL_SYLLABLE":
|
|
1108
|
+
return n <= 14 ? String.fromCodePoint(12910 + n - 1) : HANGUL_SYLLABLE_SEQ[(n - 1) % 14];
|
|
1109
|
+
case "HANGUL_JAMO":
|
|
1110
|
+
return HANGUL_JAMO_SEQ[(n - 1) % HANGUL_JAMO_SEQ.length];
|
|
1111
|
+
case "CIRCLED_HANGUL_JAMO":
|
|
1112
|
+
return n <= 14 ? String.fromCodePoint(12896 + n - 1) : HANGUL_JAMO_SEQ[(n - 1) % 14];
|
|
1113
|
+
case "LATIN_CAPITAL":
|
|
1114
|
+
return String.fromCharCode(65 + (n - 1) % 26);
|
|
1115
|
+
case "LATIN_SMALL":
|
|
1116
|
+
return String.fromCharCode(97 + (n - 1) % 26);
|
|
1117
|
+
case "CIRCLED_LATIN_CAPITAL":
|
|
1118
|
+
return n <= 26 ? String.fromCodePoint(9398 + n - 1) : String.fromCharCode(65 + (n - 1) % 26);
|
|
1119
|
+
case "CIRCLED_LATIN_SMALL":
|
|
1120
|
+
return n <= 26 ? String.fromCodePoint(9424 + n - 1) : String.fromCharCode(97 + (n - 1) % 26);
|
|
1121
|
+
case "ROMAN_CAPITAL":
|
|
1122
|
+
return toRoman(n);
|
|
1123
|
+
case "ROMAN_SMALL":
|
|
1124
|
+
return toRoman(n).toLowerCase();
|
|
1125
|
+
default:
|
|
1126
|
+
return String(n);
|
|
1426
1127
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1128
|
+
}
|
|
1129
|
+
function resolveParaHeading(paraEl, ctx) {
|
|
1130
|
+
const sm = ctx.styleMap;
|
|
1131
|
+
if (!sm) return null;
|
|
1132
|
+
const prId = paraEl.getAttribute("paraPrIDRef");
|
|
1133
|
+
if (!prId) return null;
|
|
1134
|
+
const ref = sm.paraHeadings.get(prId);
|
|
1135
|
+
if (!ref) return null;
|
|
1136
|
+
if (ref.type === "BULLET") {
|
|
1137
|
+
const char = sm.bullets.get(ref.idRef);
|
|
1138
|
+
return char ? { prefix: char } : null;
|
|
1433
1139
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
else if (ratio >= HEADING_RATIO_H3) level = 3;
|
|
1444
|
-
}
|
|
1445
|
-
const compactText = text.replace(/\s+/g, "");
|
|
1446
|
-
if (/^제\d+[조장절편]/.test(compactText) && text.length <= 50) {
|
|
1447
|
-
if (level === 0) level = 3;
|
|
1448
|
-
}
|
|
1449
|
-
if (level > 0) {
|
|
1450
|
-
block.type = "heading";
|
|
1451
|
-
block.level = level;
|
|
1452
|
-
}
|
|
1140
|
+
const numId = ref.type === "OUTLINE" ? ctx.outlineNumId || "1" : ref.idRef;
|
|
1141
|
+
const level = Math.min(ref.level + 1, 10);
|
|
1142
|
+
const headingLevel = ref.type === "OUTLINE" ? Math.min(ref.level + 1, 6) : void 0;
|
|
1143
|
+
const numDef = sm.numberings.get(numId);
|
|
1144
|
+
if (!numDef) return headingLevel ? { headingLevel } : null;
|
|
1145
|
+
let counters = ctx.shared.numState.get(numId);
|
|
1146
|
+
if (!counters) {
|
|
1147
|
+
counters = new Array(11).fill(0);
|
|
1148
|
+
ctx.shared.numState.set(numId, counters);
|
|
1453
1149
|
}
|
|
1150
|
+
const head = numDef.heads.get(level);
|
|
1151
|
+
counters[level] = counters[level] === 0 ? head?.start ?? 1 : counters[level] + 1;
|
|
1152
|
+
for (let l = level + 1; l <= 10; l++) counters[l] = 0;
|
|
1153
|
+
const fmtText = head?.text?.trim() || `^${level}.`;
|
|
1154
|
+
const prefix = fmtText.replace(/\^(10|[1-9])/g, (_, d) => {
|
|
1155
|
+
const lv = parseInt(d, 10);
|
|
1156
|
+
const refHead = numDef.heads.get(lv);
|
|
1157
|
+
const n = counters[lv] || refHead?.start || 1;
|
|
1158
|
+
return formatHeadNumber(n, refHead?.numFormat || "DIGIT");
|
|
1159
|
+
});
|
|
1160
|
+
return { prefix, headingLevel };
|
|
1454
1161
|
}
|
|
1162
|
+
|
|
1163
|
+
// src/hwpx/table-build.ts
|
|
1455
1164
|
function buildTableWithCellMeta(state) {
|
|
1456
1165
|
const table = buildTable(state.rows);
|
|
1457
1166
|
if (state.caption) table.caption = state.caption;
|
|
@@ -1531,6 +1240,8 @@ function completeTable(newTable, tableStack, blocks, ctx) {
|
|
|
1531
1240
|
}
|
|
1532
1241
|
return parentTable;
|
|
1533
1242
|
}
|
|
1243
|
+
|
|
1244
|
+
// src/hwpx/section-walker.ts
|
|
1534
1245
|
function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
|
|
1535
1246
|
const parser = createXmlParser(warnings);
|
|
1536
1247
|
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
@@ -2064,49 +1775,357 @@ function extractParagraphInfo(para, styleMap, ctx) {
|
|
|
2064
1775
|
break;
|
|
2065
1776
|
}
|
|
2066
1777
|
}
|
|
2067
|
-
};
|
|
2068
|
-
walk(para);
|
|
2069
|
-
const leaderIdx = text.indexOf("");
|
|
2070
|
-
if (leaderIdx >= 0) text = text.substring(0, leaderIdx);
|
|
2071
|
-
let cleanText = text.replace(/[ \t]+/g, " ").trim();
|
|
2072
|
-
if (/^그림입니다\.?\s*원본\s*그림의\s*(이름|크기)/.test(cleanText)) cleanText = "";
|
|
2073
|
-
cleanText = cleanText.replace(/그림입니다\.?\s*원본\s*그림의\s*(이름|크기)[^\n]*(\n[^\n]*원본\s*그림의\s*(이름|크기)[^\n]*)*/g, "").trim();
|
|
2074
|
-
cleanText = cleanText.replace(/(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|선|직선|곡선|화살표|오각형|육각형|팔각형|별|십자|구름|마름모|도넛|평행사변형|사다리꼴|개체|그리기\s?개체|묶음\s?개체|글상자|표|그림|OLE\s?개체)\s?입니다\.?/g, "").trim();
|
|
2075
|
-
let style;
|
|
2076
|
-
if (styleMap && charPrId) {
|
|
2077
|
-
const charProp = styleMap.charProperties.get(charPrId);
|
|
2078
|
-
if (charProp) {
|
|
2079
|
-
style = {};
|
|
2080
|
-
if (charProp.fontSize) style.fontSize = charProp.fontSize;
|
|
2081
|
-
if (charProp.bold) style.bold = true;
|
|
2082
|
-
if (charProp.italic) style.italic = true;
|
|
2083
|
-
if (charProp.fontName) style.fontName = charProp.fontName;
|
|
2084
|
-
if (!style.fontSize && !style.bold && !style.italic) style = void 0;
|
|
1778
|
+
};
|
|
1779
|
+
walk(para);
|
|
1780
|
+
const leaderIdx = text.indexOf("");
|
|
1781
|
+
if (leaderIdx >= 0) text = text.substring(0, leaderIdx);
|
|
1782
|
+
let cleanText = text.replace(/[ \t]+/g, " ").trim();
|
|
1783
|
+
if (/^그림입니다\.?\s*원본\s*그림의\s*(이름|크기)/.test(cleanText)) cleanText = "";
|
|
1784
|
+
cleanText = cleanText.replace(/그림입니다\.?\s*원본\s*그림의\s*(이름|크기)[^\n]*(\n[^\n]*원본\s*그림의\s*(이름|크기)[^\n]*)*/g, "").trim();
|
|
1785
|
+
cleanText = cleanText.replace(/(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|선|직선|곡선|화살표|오각형|육각형|팔각형|별|십자|구름|마름모|도넛|평행사변형|사다리꼴|개체|그리기\s?개체|묶음\s?개체|글상자|표|그림|OLE\s?개체)\s?입니다\.?/g, "").trim();
|
|
1786
|
+
let style;
|
|
1787
|
+
if (styleMap && charPrId) {
|
|
1788
|
+
const charProp = styleMap.charProperties.get(charPrId);
|
|
1789
|
+
if (charProp) {
|
|
1790
|
+
style = {};
|
|
1791
|
+
if (charProp.fontSize) style.fontSize = charProp.fontSize;
|
|
1792
|
+
if (charProp.bold) style.bold = true;
|
|
1793
|
+
if (charProp.italic) style.italic = true;
|
|
1794
|
+
if (charProp.fontName) style.fontName = charProp.fontName;
|
|
1795
|
+
if (!style.fontSize && !style.bold && !style.italic) style = void 0;
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
return { text: cleanText, href, footnote, style };
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// src/hwpx/images.ts
|
|
1802
|
+
function imageExtToMime(ext) {
|
|
1803
|
+
switch (ext.toLowerCase()) {
|
|
1804
|
+
case "jpg":
|
|
1805
|
+
case "jpeg":
|
|
1806
|
+
return "image/jpeg";
|
|
1807
|
+
case "png":
|
|
1808
|
+
return "image/png";
|
|
1809
|
+
case "gif":
|
|
1810
|
+
return "image/gif";
|
|
1811
|
+
case "bmp":
|
|
1812
|
+
return "image/bmp";
|
|
1813
|
+
case "tif":
|
|
1814
|
+
case "tiff":
|
|
1815
|
+
return "image/tiff";
|
|
1816
|
+
case "wmf":
|
|
1817
|
+
return "image/wmf";
|
|
1818
|
+
case "emf":
|
|
1819
|
+
return "image/emf";
|
|
1820
|
+
case "svg":
|
|
1821
|
+
return "image/svg+xml";
|
|
1822
|
+
default:
|
|
1823
|
+
return "application/octet-stream";
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
function mimeToExt(mime) {
|
|
1827
|
+
if (mime.includes("jpeg")) return "jpg";
|
|
1828
|
+
if (mime.includes("png")) return "png";
|
|
1829
|
+
if (mime.includes("gif")) return "gif";
|
|
1830
|
+
if (mime.includes("bmp")) return "bmp";
|
|
1831
|
+
if (mime.includes("tiff")) return "tif";
|
|
1832
|
+
if (mime.includes("wmf")) return "wmf";
|
|
1833
|
+
if (mime.includes("emf")) return "emf";
|
|
1834
|
+
if (mime.includes("svg")) return "svg";
|
|
1835
|
+
return "bin";
|
|
1836
|
+
}
|
|
1837
|
+
function collectImageBlocks(blocks, out, ownerCell, depth = 0) {
|
|
1838
|
+
if (depth > MAX_XML_DEPTH) return;
|
|
1839
|
+
for (const block of blocks) {
|
|
1840
|
+
if (block.type === "image") {
|
|
1841
|
+
out.push({ block, ownerCell });
|
|
1842
|
+
} else if (block.type === "table" && block.table) {
|
|
1843
|
+
for (const row of block.table.cells) {
|
|
1844
|
+
for (const cell of row) {
|
|
1845
|
+
if (cell.blocks?.length) collectImageBlocks(cell.blocks, out, cell, depth + 1);
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
async function extractImagesFromZip(zip, blocks, decompressed, warnings) {
|
|
1852
|
+
const images = [];
|
|
1853
|
+
let imageIndex = 0;
|
|
1854
|
+
const imageBlocks = [];
|
|
1855
|
+
collectImageBlocks(blocks, imageBlocks);
|
|
1856
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1857
|
+
for (const { block, ownerCell } of imageBlocks) {
|
|
1858
|
+
if (block.type !== "image" || !block.text) continue;
|
|
1859
|
+
const ref = block.text;
|
|
1860
|
+
let img = resolved.get(ref);
|
|
1861
|
+
if (img === void 0) {
|
|
1862
|
+
img = null;
|
|
1863
|
+
const candidates = [
|
|
1864
|
+
`BinData/${ref}`,
|
|
1865
|
+
`Contents/BinData/${ref}`,
|
|
1866
|
+
ref
|
|
1867
|
+
// 절대 경로일 수도 있음
|
|
1868
|
+
];
|
|
1869
|
+
let resolvedPath = null;
|
|
1870
|
+
if (!ref.includes(".")) {
|
|
1871
|
+
const prefixes = [`BinData/${ref}`, `Contents/BinData/${ref}`];
|
|
1872
|
+
for (const prefix of prefixes) {
|
|
1873
|
+
const match = zip.file(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.[a-zA-Z0-9]+$`));
|
|
1874
|
+
if (match.length > 0) {
|
|
1875
|
+
resolvedPath = match[0].name;
|
|
1876
|
+
break;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
const allCandidates = resolvedPath ? [resolvedPath, ...candidates] : candidates;
|
|
1881
|
+
for (const path of allCandidates) {
|
|
1882
|
+
if (isPathTraversal(path)) continue;
|
|
1883
|
+
const file = zip.file(path);
|
|
1884
|
+
if (!file) continue;
|
|
1885
|
+
try {
|
|
1886
|
+
const data = await file.async("uint8array");
|
|
1887
|
+
decompressed.total += data.length;
|
|
1888
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1889
|
+
const ext = path.includes(".") ? path.split(".").pop() || "png" : "png";
|
|
1890
|
+
const mimeType = imageExtToMime(ext);
|
|
1891
|
+
imageIndex++;
|
|
1892
|
+
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
1893
|
+
img = { filename, data, mimeType };
|
|
1894
|
+
images.push(img);
|
|
1895
|
+
break;
|
|
1896
|
+
} catch (err) {
|
|
1897
|
+
if (err instanceof KordocError) throw err;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
if (!img) warnings?.push({ page: block.pageNumber, message: `\uC774\uBBF8\uC9C0 \uD30C\uC77C \uC5C6\uC74C: ${ref}`, code: "SKIPPED_IMAGE" });
|
|
1901
|
+
resolved.set(ref, img);
|
|
1902
|
+
}
|
|
1903
|
+
if (!img) {
|
|
1904
|
+
block.type = "paragraph";
|
|
1905
|
+
block.text = `[\uC774\uBBF8\uC9C0: ${ref}]`;
|
|
1906
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
block.text = img.filename;
|
|
1910
|
+
block.imageData = { data: img.data, mimeType: img.mimeType, filename: ref };
|
|
1911
|
+
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, ``);
|
|
1912
|
+
}
|
|
1913
|
+
return images;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// src/hwpx/metadata.ts
|
|
1917
|
+
import JSZip2 from "jszip";
|
|
1918
|
+
|
|
1919
|
+
// src/hwpx/zip-sections.ts
|
|
1920
|
+
import { inflateRawSync } from "zlib";
|
|
1921
|
+
function extractFromBrokenZip(buffer) {
|
|
1922
|
+
const data = new Uint8Array(buffer);
|
|
1923
|
+
const view = new DataView(buffer);
|
|
1924
|
+
let pos = 0;
|
|
1925
|
+
const blocks = [];
|
|
1926
|
+
const warnings = [
|
|
1927
|
+
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
1928
|
+
];
|
|
1929
|
+
let totalDecompressed = 0;
|
|
1930
|
+
let entryCount = 0;
|
|
1931
|
+
let sectionNum = 0;
|
|
1932
|
+
const shared = createSectionShared();
|
|
1933
|
+
while (pos < data.length - 30) {
|
|
1934
|
+
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
1935
|
+
pos++;
|
|
1936
|
+
while (pos < data.length - 30) {
|
|
1937
|
+
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
1938
|
+
pos++;
|
|
1939
|
+
}
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
1943
|
+
const method = view.getUint16(pos + 8, true);
|
|
1944
|
+
const compSize = view.getUint32(pos + 18, true);
|
|
1945
|
+
const nameLen = view.getUint16(pos + 26, true);
|
|
1946
|
+
const extraLen = view.getUint16(pos + 28, true);
|
|
1947
|
+
if (nameLen > 1024 || extraLen > 65535) {
|
|
1948
|
+
pos += 30 + nameLen + extraLen;
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
const fileStart = pos + 30 + nameLen + extraLen;
|
|
1952
|
+
if (fileStart + compSize > data.length) break;
|
|
1953
|
+
if (compSize === 0 && method !== 0) {
|
|
1954
|
+
pos = fileStart;
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
const nameBytes = data.slice(pos + 30, pos + 30 + nameLen);
|
|
1958
|
+
const name = new TextDecoder().decode(nameBytes);
|
|
1959
|
+
if (isPathTraversal(name)) {
|
|
1960
|
+
pos = fileStart + compSize;
|
|
1961
|
+
continue;
|
|
1962
|
+
}
|
|
1963
|
+
const fileData = data.slice(fileStart, fileStart + compSize);
|
|
1964
|
+
pos = fileStart + compSize;
|
|
1965
|
+
if (!name.toLowerCase().includes("section") || !name.endsWith(".xml")) continue;
|
|
1966
|
+
try {
|
|
1967
|
+
let content;
|
|
1968
|
+
if (method === 0) {
|
|
1969
|
+
content = new TextDecoder().decode(fileData);
|
|
1970
|
+
} else if (method === 8) {
|
|
1971
|
+
const decompressed = inflateRawSync(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
1972
|
+
content = new TextDecoder().decode(decompressed);
|
|
1973
|
+
} else {
|
|
1974
|
+
continue;
|
|
1975
|
+
}
|
|
1976
|
+
totalDecompressed += content.length * 2;
|
|
1977
|
+
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
1978
|
+
sectionNum++;
|
|
1979
|
+
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
1980
|
+
} catch {
|
|
1981
|
+
continue;
|
|
2085
1982
|
}
|
|
2086
1983
|
}
|
|
2087
|
-
|
|
1984
|
+
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");
|
|
1985
|
+
applyPageText(blocks, shared);
|
|
1986
|
+
const markdown = blocksToMarkdown(blocks);
|
|
1987
|
+
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
2088
1988
|
}
|
|
2089
|
-
function
|
|
2090
|
-
const
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
const
|
|
2094
|
-
if (
|
|
2095
|
-
const
|
|
2096
|
-
|
|
1989
|
+
async function resolveSectionPaths(zip) {
|
|
1990
|
+
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
1991
|
+
for (const mp of manifestPaths) {
|
|
1992
|
+
const mpLower = mp.toLowerCase();
|
|
1993
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
1994
|
+
if (!file) continue;
|
|
1995
|
+
const xml = await file.async("text");
|
|
1996
|
+
const paths = parseSectionPathsFromManifest(xml);
|
|
1997
|
+
if (paths.length > 0) return paths;
|
|
2097
1998
|
}
|
|
2098
|
-
|
|
1999
|
+
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
2000
|
+
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
2099
2001
|
}
|
|
2100
|
-
function
|
|
2101
|
-
|
|
2102
|
-
const
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2002
|
+
function parseSectionPathsFromManifest(xml) {
|
|
2003
|
+
const parser = createXmlParser();
|
|
2004
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
2005
|
+
const items = doc.getElementsByTagName("opf:item");
|
|
2006
|
+
const spine = doc.getElementsByTagName("opf:itemref");
|
|
2007
|
+
const idToHref = /* @__PURE__ */ new Map();
|
|
2008
|
+
for (let i = 0; i < items.length; i++) {
|
|
2009
|
+
const item = items[i];
|
|
2010
|
+
const id = item.getAttribute("id") || "";
|
|
2011
|
+
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
2012
|
+
if (id && href) idToHref.set(id, href);
|
|
2108
2013
|
}
|
|
2109
|
-
|
|
2014
|
+
if (spine.length > 0) {
|
|
2015
|
+
const ordered = [];
|
|
2016
|
+
for (let i = 0; i < spine.length; i++) {
|
|
2017
|
+
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
2018
|
+
if (href) ordered.push(href);
|
|
2019
|
+
}
|
|
2020
|
+
if (ordered.length > 0) return ordered;
|
|
2021
|
+
}
|
|
2022
|
+
return Array.from(idToHref.values()).sort(compareSectionPaths);
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
// src/hwpx/metadata.ts
|
|
2026
|
+
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
2027
|
+
try {
|
|
2028
|
+
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
2029
|
+
for (const mp of metaPaths) {
|
|
2030
|
+
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
2031
|
+
if (!file) continue;
|
|
2032
|
+
const xml = await file.async("text");
|
|
2033
|
+
if (decompressed) {
|
|
2034
|
+
decompressed.total += xml.length * 2;
|
|
2035
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2036
|
+
}
|
|
2037
|
+
parseDublinCoreMetadata(xml, metadata);
|
|
2038
|
+
if (metadata.title || metadata.author) return;
|
|
2039
|
+
}
|
|
2040
|
+
} catch {
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
function parseDublinCoreMetadata(xml, metadata) {
|
|
2044
|
+
const parser = createXmlParser();
|
|
2045
|
+
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
2046
|
+
if (!doc.documentElement) return;
|
|
2047
|
+
const getText = (tagNames) => {
|
|
2048
|
+
for (const tag of tagNames) {
|
|
2049
|
+
const els = doc.getElementsByTagName(tag);
|
|
2050
|
+
if (els.length > 0) {
|
|
2051
|
+
const text = els[0].textContent?.trim();
|
|
2052
|
+
if (text) return text;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
return void 0;
|
|
2056
|
+
};
|
|
2057
|
+
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
2058
|
+
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
2059
|
+
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
2060
|
+
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
2061
|
+
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
2062
|
+
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
2063
|
+
if (keywords && !metadata.keywords) {
|
|
2064
|
+
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
// src/hwpx/parser.ts
|
|
2069
|
+
async function parseHwpxDocument(buffer, options) {
|
|
2070
|
+
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
2071
|
+
let zip;
|
|
2072
|
+
try {
|
|
2073
|
+
zip = await JSZip3.loadAsync(buffer);
|
|
2074
|
+
} catch {
|
|
2075
|
+
return extractFromBrokenZip(buffer);
|
|
2076
|
+
}
|
|
2077
|
+
const actualEntryCount = Object.keys(zip.files).length;
|
|
2078
|
+
if (actualEntryCount > MAX_ZIP_ENTRIES) {
|
|
2079
|
+
throw new KordocError("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2080
|
+
}
|
|
2081
|
+
const manifestFile = zip.file("META-INF/manifest.xml");
|
|
2082
|
+
if (manifestFile) {
|
|
2083
|
+
const manifestXml = await manifestFile.async("text");
|
|
2084
|
+
if (isEncryptedHwpx(manifestXml)) {
|
|
2085
|
+
if (isComFallbackAvailable() && options?.filePath) {
|
|
2086
|
+
const { pages, pageCount, warnings: warnings2 } = extractTextViaCom(options.filePath);
|
|
2087
|
+
if (pages.some((p) => p && p.trim().length > 0)) {
|
|
2088
|
+
return comResultToParseResult(pages, pageCount, warnings2);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
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.");
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
const decompressed = { total: 0 };
|
|
2095
|
+
const metadata = {};
|
|
2096
|
+
await extractHwpxMetadata(zip, metadata, decompressed);
|
|
2097
|
+
const styleMap = await extractHwpxStyles(zip, decompressed);
|
|
2098
|
+
const warnings = [];
|
|
2099
|
+
const sectionPaths = await resolveSectionPaths(zip);
|
|
2100
|
+
if (sectionPaths.length === 0) throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
2101
|
+
metadata.pageCount = sectionPaths.length;
|
|
2102
|
+
const pageFilter = options?.pages ? parsePageRange(options.pages, sectionPaths.length) : null;
|
|
2103
|
+
const totalTarget = pageFilter ? pageFilter.size : sectionPaths.length;
|
|
2104
|
+
const blocks = [];
|
|
2105
|
+
const shared = createSectionShared();
|
|
2106
|
+
let parsedSections = 0;
|
|
2107
|
+
for (let si = 0; si < sectionPaths.length; si++) {
|
|
2108
|
+
if (pageFilter && !pageFilter.has(si + 1)) continue;
|
|
2109
|
+
const file = zip.file(sectionPaths[si]);
|
|
2110
|
+
if (!file) continue;
|
|
2111
|
+
try {
|
|
2112
|
+
const xml = await file.async("text");
|
|
2113
|
+
decompressed.total += xml.length * 2;
|
|
2114
|
+
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
2115
|
+
blocks.push(...parseSectionXml(xml, styleMap, warnings, si + 1, shared));
|
|
2116
|
+
parsedSections++;
|
|
2117
|
+
options?.onProgress?.(parsedSections, totalTarget);
|
|
2118
|
+
} catch (secErr) {
|
|
2119
|
+
if (secErr instanceof KordocError) throw secErr;
|
|
2120
|
+
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" });
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
applyPageText(blocks, shared);
|
|
2124
|
+
const images = await extractImagesFromZip(zip, blocks, decompressed, warnings);
|
|
2125
|
+
detectHwpxHeadings(blocks, styleMap);
|
|
2126
|
+
const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
2127
|
+
const markdown = blocksToMarkdown(blocks);
|
|
2128
|
+
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 };
|
|
2110
2129
|
}
|
|
2111
2130
|
|
|
2112
2131
|
// src/hwp5/record.ts
|
|
@@ -16748,7 +16767,7 @@ function isDistributionSentinel(markdown) {
|
|
|
16748
16767
|
}
|
|
16749
16768
|
|
|
16750
16769
|
// src/xlsx/parser.ts
|
|
16751
|
-
import
|
|
16770
|
+
import JSZip4 from "jszip";
|
|
16752
16771
|
import { DOMParser as DOMParser2 } from "@xmldom/xmldom";
|
|
16753
16772
|
var MAX_SHEETS = 100;
|
|
16754
16773
|
var MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
|
|
@@ -16939,7 +16958,7 @@ function sheetToBlocks(sheetName, grid, merges, maxRow, maxCol, sheetIndex) {
|
|
|
16939
16958
|
}
|
|
16940
16959
|
async function parseXlsxDocument(buffer, options) {
|
|
16941
16960
|
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE3);
|
|
16942
|
-
const zip = await
|
|
16961
|
+
const zip = await JSZip4.loadAsync(buffer);
|
|
16943
16962
|
const warnings = [];
|
|
16944
16963
|
const workbookFile = zip.file("xl/workbook.xml");
|
|
16945
16964
|
if (!workbookFile) {
|
|
@@ -17645,7 +17664,7 @@ async function parseXlsDocument(buffer, options) {
|
|
|
17645
17664
|
}
|
|
17646
17665
|
|
|
17647
17666
|
// src/docx/parser.ts
|
|
17648
|
-
import
|
|
17667
|
+
import JSZip5 from "jszip";
|
|
17649
17668
|
import { DOMParser as DOMParser3 } from "@xmldom/xmldom";
|
|
17650
17669
|
|
|
17651
17670
|
// src/docx/equation.ts
|
|
@@ -18399,7 +18418,7 @@ async function extractImages(zip, rels, doc, warnings) {
|
|
|
18399
18418
|
}
|
|
18400
18419
|
async function parseDocxDocument(buffer, options) {
|
|
18401
18420
|
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE4);
|
|
18402
|
-
const zip = await
|
|
18421
|
+
const zip = await JSZip5.loadAsync(buffer);
|
|
18403
18422
|
const warnings = [];
|
|
18404
18423
|
const docFile = zip.file("word/document.xml");
|
|
18405
18424
|
if (!docFile) {
|
|
@@ -19317,7 +19336,7 @@ function fillInlineFields(text, values, filled, matchedLabels) {
|
|
|
19317
19336
|
}
|
|
19318
19337
|
|
|
19319
19338
|
// src/form/filler-hwpx.ts
|
|
19320
|
-
import
|
|
19339
|
+
import JSZip6 from "jszip";
|
|
19321
19340
|
|
|
19322
19341
|
// src/roundtrip/source-map.ts
|
|
19323
19342
|
function escapeXmlText(text) {
|
|
@@ -19991,7 +20010,7 @@ function patchZipEntries(original, replacements) {
|
|
|
19991
20010
|
// src/form/filler-hwpx.ts
|
|
19992
20011
|
async function fillHwpx(hwpxBuffer, values) {
|
|
19993
20012
|
const u8 = new Uint8Array(hwpxBuffer);
|
|
19994
|
-
const zip = await
|
|
20013
|
+
const zip = await JSZip6.loadAsync(hwpxBuffer);
|
|
19995
20014
|
const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
|
|
19996
20015
|
if (sectionPaths.length === 0) {
|
|
19997
20016
|
throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
@@ -20232,7 +20251,7 @@ async function fillHwpx(hwpxBuffer, values) {
|
|
|
20232
20251
|
}
|
|
20233
20252
|
|
|
20234
20253
|
// src/hwpx/generator.ts
|
|
20235
|
-
import
|
|
20254
|
+
import JSZip7 from "jszip";
|
|
20236
20255
|
|
|
20237
20256
|
// src/hwpx/text-metrics.ts
|
|
20238
20257
|
var ASCII_W = [
|
|
@@ -21068,7 +21087,7 @@ async function markdownToHwpx(markdown, options) {
|
|
|
21068
21087
|
const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
|
|
21069
21088
|
const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
|
|
21070
21089
|
const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit);
|
|
21071
|
-
const zip = new
|
|
21090
|
+
const zip = new JSZip7();
|
|
21072
21091
|
zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
|
|
21073
21092
|
zip.file("META-INF/container.xml", generateContainerXml());
|
|
21074
21093
|
zip.file("Contents/content.hpf", generateManifest());
|
|
@@ -21895,7 +21914,7 @@ function diffTableCells(a, b) {
|
|
|
21895
21914
|
}
|
|
21896
21915
|
|
|
21897
21916
|
// src/roundtrip/patcher.ts
|
|
21898
|
-
import
|
|
21917
|
+
import JSZip8 from "jszip";
|
|
21899
21918
|
|
|
21900
21919
|
// src/roundtrip/table-rows.ts
|
|
21901
21920
|
var ROW_OBJECT_RE = /<(?:[A-Za-z0-9_]+:)?(?:tbl|pic|equation|ole|container|shape|drawingObject|drawText|video|chart|fieldBegin|fieldEnd|ctrl)\b/;
|
|
@@ -22533,7 +22552,7 @@ async function patchHwpx(original, editedMarkdown, options) {
|
|
|
22533
22552
|
}
|
|
22534
22553
|
let zip;
|
|
22535
22554
|
try {
|
|
22536
|
-
zip = await
|
|
22555
|
+
zip = await JSZip8.loadAsync(original);
|
|
22537
22556
|
} catch {
|
|
22538
22557
|
return { success: false, applied: 0, skipped, error: "ZIP \uB85C\uB4DC \uC2E4\uD328" };
|
|
22539
22558
|
}
|
|
@@ -23900,10 +23919,10 @@ function stageParaPatch(scan, para, newPlain, skip) {
|
|
|
23900
23919
|
}
|
|
23901
23920
|
|
|
23902
23921
|
// src/roundtrip/session.ts
|
|
23903
|
-
import
|
|
23922
|
+
import JSZip9 from "jszip";
|
|
23904
23923
|
async function buildState(bytes) {
|
|
23905
23924
|
const parsed = await parseHwpxDocument(u8ToArrayBuffer2(bytes));
|
|
23906
|
-
const zip = await
|
|
23925
|
+
const zip = await JSZip9.loadAsync(bytes);
|
|
23907
23926
|
const sectionPaths = await resolveSectionEntryNames(zip);
|
|
23908
23927
|
if (sectionPaths.length === 0) {
|
|
23909
23928
|
throw new Error("HWPX \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
@@ -24484,7 +24503,7 @@ async function parseHwp(buffer, options) {
|
|
|
24484
24503
|
async function parsePdf(buffer, options) {
|
|
24485
24504
|
let parsePdfDocument;
|
|
24486
24505
|
try {
|
|
24487
|
-
const mod = await import("./parser-
|
|
24506
|
+
const mod = await import("./parser-XEDROIM7.js");
|
|
24488
24507
|
parsePdfDocument = mod.parsePdfDocument;
|
|
24489
24508
|
} catch {
|
|
24490
24509
|
return {
|