kordoc 3.7.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 +14 -2
- package/dist/{-7UC4ZWBS.js → -LD4BZDDJ.js} +3 -3
- package/dist/{chunk-IJJMVTU5.js → chunk-IFYJFWD2.js} +2 -2
- package/dist/{chunk-NXRABCWW.js → chunk-KT2BCHXI.js} +803 -703
- package/dist/chunk-KT2BCHXI.js.map +1 -0
- package/dist/{chunk-QZCP3UWU.cjs → chunk-LFCS3UVG.cjs} +2 -2
- package/dist/{chunk-QZCP3UWU.cjs.map → chunk-LFCS3UVG.cjs.map} +1 -1
- package/dist/{chunk-MEVKYW55.js → chunk-PELBIL4K.js} +2 -2
- package/dist/cli.js +4 -4
- package/dist/index.cjs +968 -868
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +802 -702
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +3 -3
- package/dist/{parser-DR5CTZ74.js → parser-FFEBMLSH.js} +1830 -1803
- package/dist/parser-FFEBMLSH.js.map +1 -0
- package/dist/{parser-RFLPUZ7P.cjs → parser-IXK5V7YG.cjs} +1830 -1803
- package/dist/parser-IXK5V7YG.cjs.map +1 -0
- package/dist/{parser-ZHJFQR44.js → parser-XEDROIM7.js} +1830 -1803
- package/dist/parser-XEDROIM7.js.map +1 -0
- package/dist/{watch-UIX447QV.js → watch-MAWCDNFI.js} +3 -3
- package/package.json +2 -2
- package/dist/chunk-NXRABCWW.js.map +0 -1
- package/dist/parser-DR5CTZ74.js.map +0 -1
- package/dist/parser-RFLPUZ7P.cjs.map +0 -1
- package/dist/parser-ZHJFQR44.js.map +0 -1
- /package/dist/{-7UC4ZWBS.js.map → -LD4BZDDJ.js.map} +0 -0
- /package/dist/{chunk-IJJMVTU5.js.map → chunk-IFYJFWD2.js.map} +0 -0
- /package/dist/{chunk-MEVKYW55.js.map → chunk-PELBIL4K.js.map} +0 -0
- /package/dist/{watch-UIX447QV.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,173 +1066,7 @@ function hmlToLatex(hmlEqStr) {
|
|
|
829
1066
|
return out;
|
|
830
1067
|
}
|
|
831
1068
|
|
|
832
|
-
// src/hwpx/
|
|
833
|
-
var MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024;
|
|
834
|
-
var MAX_ZIP_ENTRIES = 500;
|
|
835
|
-
function clampSpan(val, max) {
|
|
836
|
-
return Math.max(1, Math.min(val, max));
|
|
837
|
-
}
|
|
838
|
-
var MAX_XML_DEPTH = 200;
|
|
839
|
-
function createSectionShared() {
|
|
840
|
-
return { numState: /* @__PURE__ */ new Map(), pageText: { headers: [], footers: [] }, track: { deleteDepth: 0, warned: false } };
|
|
841
|
-
}
|
|
842
|
-
function createXmlParser(warnings) {
|
|
843
|
-
return new DOMParser({
|
|
844
|
-
onError(level, msg2) {
|
|
845
|
-
if (level === "fatalError") throw new KordocError(`XML \uD30C\uC2F1 \uC2E4\uD328: ${msg2}`);
|
|
846
|
-
warnings?.push({ code: "MALFORMED_XML", message: `XML ${level === "warn" ? "\uACBD\uACE0" : "\uC624\uB958"}: ${msg2}` });
|
|
847
|
-
}
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
async function extractHwpxStyles(zip, decompressed) {
|
|
851
|
-
const result = {
|
|
852
|
-
charProperties: /* @__PURE__ */ new Map(),
|
|
853
|
-
styles: /* @__PURE__ */ new Map(),
|
|
854
|
-
numberings: /* @__PURE__ */ new Map(),
|
|
855
|
-
bullets: /* @__PURE__ */ new Map(),
|
|
856
|
-
paraHeadings: /* @__PURE__ */ new Map()
|
|
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
|
-
}
|
|
1069
|
+
// src/hwpx/para-heading.ts
|
|
999
1070
|
var HANGUL_SYLLABLE_SEQ = "\uAC00\uB098\uB2E4\uB77C\uB9C8\uBC14\uC0AC\uC544\uC790\uCC28\uCE74\uD0C0\uD30C\uD558";
|
|
1000
1071
|
var HANGUL_JAMO_SEQ = "\u3131\u3134\u3137\u3139\u3141\u3142\u3145\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
|
|
1001
1072
|
function toRoman(n) {
|
|
@@ -1005,448 +1076,91 @@ function toRoman(n) {
|
|
|
1005
1076
|
[900, "CM"],
|
|
1006
1077
|
[500, "D"],
|
|
1007
1078
|
[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
|
-
for (const { block, ownerCell } of imageBlocks) {
|
|
1216
|
-
if (block.type !== "image" || !block.text) continue;
|
|
1217
|
-
const ref = block.text;
|
|
1218
|
-
const candidates = [
|
|
1219
|
-
`BinData/${ref}`,
|
|
1220
|
-
`Contents/BinData/${ref}`,
|
|
1221
|
-
ref
|
|
1222
|
-
// 절대 경로일 수도 있음
|
|
1223
|
-
];
|
|
1224
|
-
let resolvedPath = null;
|
|
1225
|
-
if (!ref.includes(".")) {
|
|
1226
|
-
const prefixes = [`BinData/${ref}`, `Contents/BinData/${ref}`];
|
|
1227
|
-
for (const prefix of prefixes) {
|
|
1228
|
-
const match = zip.file(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.[a-zA-Z0-9]+$`));
|
|
1229
|
-
if (match.length > 0) {
|
|
1230
|
-
resolvedPath = match[0].name;
|
|
1231
|
-
break;
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
let found = false;
|
|
1236
|
-
const allCandidates = resolvedPath ? [resolvedPath, ...candidates] : candidates;
|
|
1237
|
-
for (const path of allCandidates) {
|
|
1238
|
-
if (isPathTraversal(path)) continue;
|
|
1239
|
-
const file = zip.file(path);
|
|
1240
|
-
if (!file) continue;
|
|
1241
|
-
try {
|
|
1242
|
-
const data = await file.async("uint8array");
|
|
1243
|
-
decompressed.total += data.length;
|
|
1244
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1245
|
-
const actualPath = path;
|
|
1246
|
-
const ext = actualPath.includes(".") ? actualPath.split(".").pop() || "png" : "png";
|
|
1247
|
-
const mimeType = imageExtToMime(ext);
|
|
1248
|
-
imageIndex++;
|
|
1249
|
-
const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
|
|
1250
|
-
images.push({ filename, data, mimeType });
|
|
1251
|
-
block.text = filename;
|
|
1252
|
-
block.imageData = { data, mimeType, filename: ref };
|
|
1253
|
-
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, ``);
|
|
1254
|
-
found = true;
|
|
1255
|
-
break;
|
|
1256
|
-
} catch (err) {
|
|
1257
|
-
if (err instanceof KordocError) throw err;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
if (!found) {
|
|
1261
|
-
warnings?.push({ page: block.pageNumber, message: `\uC774\uBBF8\uC9C0 \uD30C\uC77C \uC5C6\uC74C: ${ref}`, code: "SKIPPED_IMAGE" });
|
|
1262
|
-
block.type = "paragraph";
|
|
1263
|
-
block.text = `[\uC774\uBBF8\uC9C0: ${ref}]`;
|
|
1264
|
-
if (ownerCell) ownerCell.text = ownerCell.text.replace(``, `[\uC774\uBBF8\uC9C0: ${ref}]`);
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
return images;
|
|
1268
|
-
}
|
|
1269
|
-
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
1270
|
-
try {
|
|
1271
|
-
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
1272
|
-
for (const mp of metaPaths) {
|
|
1273
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mp.toLowerCase()) || null;
|
|
1274
|
-
if (!file) continue;
|
|
1275
|
-
const xml = await file.async("text");
|
|
1276
|
-
if (decompressed) {
|
|
1277
|
-
decompressed.total += xml.length * 2;
|
|
1278
|
-
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
1279
|
-
}
|
|
1280
|
-
parseDublinCoreMetadata(xml, metadata);
|
|
1281
|
-
if (metadata.title || metadata.author) return;
|
|
1282
|
-
}
|
|
1283
|
-
} catch {
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
function parseDublinCoreMetadata(xml, metadata) {
|
|
1287
|
-
const parser = createXmlParser();
|
|
1288
|
-
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
1289
|
-
if (!doc.documentElement) return;
|
|
1290
|
-
const getText = (tagNames) => {
|
|
1291
|
-
for (const tag of tagNames) {
|
|
1292
|
-
const els = doc.getElementsByTagName(tag);
|
|
1293
|
-
if (els.length > 0) {
|
|
1294
|
-
const text = els[0].textContent?.trim();
|
|
1295
|
-
if (text) return text;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
return void 0;
|
|
1299
|
-
};
|
|
1300
|
-
metadata.title = metadata.title || getText(["dc:title", "title"]);
|
|
1301
|
-
metadata.author = metadata.author || getText(["dc:creator", "creator", "cp:lastModifiedBy"]);
|
|
1302
|
-
metadata.description = metadata.description || getText(["dc:description", "description", "dc:subject", "subject"]);
|
|
1303
|
-
metadata.createdAt = metadata.createdAt || getText(["dcterms:created", "meta:creation-date"]);
|
|
1304
|
-
metadata.modifiedAt = metadata.modifiedAt || getText(["dcterms:modified", "meta:date"]);
|
|
1305
|
-
const keywords = getText(["dc:keyword", "cp:keywords", "meta:keyword"]);
|
|
1306
|
-
if (keywords && !metadata.keywords) {
|
|
1307
|
-
metadata.keywords = keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
function extractFromBrokenZip(buffer) {
|
|
1311
|
-
const data = new Uint8Array(buffer);
|
|
1312
|
-
const view = new DataView(buffer);
|
|
1313
|
-
let pos = 0;
|
|
1314
|
-
const blocks = [];
|
|
1315
|
-
const warnings = [
|
|
1316
|
-
{ code: "BROKEN_ZIP_RECOVERY", message: "\uC190\uC0C1\uB41C ZIP \uAD6C\uC870 \u2014 Local File Header \uAE30\uBC18 \uBCF5\uAD6C \uBAA8\uB4DC" }
|
|
1317
|
-
];
|
|
1318
|
-
let totalDecompressed = 0;
|
|
1319
|
-
let entryCount = 0;
|
|
1320
|
-
let sectionNum = 0;
|
|
1321
|
-
const shared = createSectionShared();
|
|
1322
|
-
while (pos < data.length - 30) {
|
|
1323
|
-
if (data[pos] !== 80 || data[pos + 1] !== 75 || data[pos + 2] !== 3 || data[pos + 3] !== 4) {
|
|
1324
|
-
pos++;
|
|
1325
|
-
while (pos < data.length - 30) {
|
|
1326
|
-
if (data[pos] === 80 && data[pos + 1] === 75 && data[pos + 2] === 3 && data[pos + 3] === 4) break;
|
|
1327
|
-
pos++;
|
|
1328
|
-
}
|
|
1329
|
-
continue;
|
|
1330
|
-
}
|
|
1331
|
-
if (++entryCount > MAX_ZIP_ENTRIES) break;
|
|
1332
|
-
const method = view.getUint16(pos + 8, true);
|
|
1333
|
-
const compSize = view.getUint32(pos + 18, true);
|
|
1334
|
-
const nameLen = view.getUint16(pos + 26, true);
|
|
1335
|
-
const extraLen = view.getUint16(pos + 28, true);
|
|
1336
|
-
if (nameLen > 1024 || extraLen > 65535) {
|
|
1337
|
-
pos += 30 + nameLen + extraLen;
|
|
1338
|
-
continue;
|
|
1339
|
-
}
|
|
1340
|
-
const fileStart = pos + 30 + nameLen + extraLen;
|
|
1341
|
-
if (fileStart + compSize > data.length) break;
|
|
1342
|
-
if (compSize === 0 && method !== 0) {
|
|
1343
|
-
pos = fileStart;
|
|
1344
|
-
continue;
|
|
1345
|
-
}
|
|
1346
|
-
const nameBytes = data.slice(pos + 30, pos + 30 + nameLen);
|
|
1347
|
-
const name = new TextDecoder().decode(nameBytes);
|
|
1348
|
-
if (isPathTraversal(name)) {
|
|
1349
|
-
pos = fileStart + compSize;
|
|
1350
|
-
continue;
|
|
1351
|
-
}
|
|
1352
|
-
const fileData = data.slice(fileStart, fileStart + compSize);
|
|
1353
|
-
pos = fileStart + compSize;
|
|
1354
|
-
if (!name.toLowerCase().includes("section") || !name.endsWith(".xml")) continue;
|
|
1355
|
-
try {
|
|
1356
|
-
let content;
|
|
1357
|
-
if (method === 0) {
|
|
1358
|
-
content = new TextDecoder().decode(fileData);
|
|
1359
|
-
} else if (method === 8) {
|
|
1360
|
-
const decompressed = inflateRawSync(Buffer.from(fileData), { maxOutputLength: MAX_DECOMPRESS_SIZE });
|
|
1361
|
-
content = new TextDecoder().decode(decompressed);
|
|
1362
|
-
} else {
|
|
1363
|
-
continue;
|
|
1364
|
-
}
|
|
1365
|
-
totalDecompressed += content.length * 2;
|
|
1366
|
-
if (totalDecompressed > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC");
|
|
1367
|
-
sectionNum++;
|
|
1368
|
-
blocks.push(...parseSectionXml(content, void 0, warnings, sectionNum, shared));
|
|
1369
|
-
} catch {
|
|
1370
|
-
continue;
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
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");
|
|
1374
|
-
applyPageText(blocks, shared);
|
|
1375
|
-
const markdown = blocksToMarkdown(blocks);
|
|
1376
|
-
return { markdown, blocks, warnings: warnings.length > 0 ? warnings : void 0 };
|
|
1377
|
-
}
|
|
1378
|
-
async function resolveSectionPaths(zip) {
|
|
1379
|
-
const manifestPaths = ["Contents/content.hpf", "content.hpf"];
|
|
1380
|
-
for (const mp of manifestPaths) {
|
|
1381
|
-
const mpLower = mp.toLowerCase();
|
|
1382
|
-
const file = zip.file(mp) || Object.values(zip.files).find((f) => f.name.toLowerCase() === mpLower) || null;
|
|
1383
|
-
if (!file) continue;
|
|
1384
|
-
const xml = await file.async("text");
|
|
1385
|
-
const paths = parseSectionPathsFromManifest(xml);
|
|
1386
|
-
if (paths.length > 0) return paths;
|
|
1387
|
-
}
|
|
1388
|
-
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
1389
|
-
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
1390
|
-
}
|
|
1391
|
-
function parseSectionPathsFromManifest(xml) {
|
|
1392
|
-
const parser = createXmlParser();
|
|
1393
|
-
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
1394
|
-
const items = doc.getElementsByTagName("opf:item");
|
|
1395
|
-
const spine = doc.getElementsByTagName("opf:itemref");
|
|
1396
|
-
const idToHref = /* @__PURE__ */ new Map();
|
|
1397
|
-
for (let i = 0; i < items.length; i++) {
|
|
1398
|
-
const item = items[i];
|
|
1399
|
-
const id = item.getAttribute("id") || "";
|
|
1400
|
-
const href = normalizeSectionHref(item.getAttribute("href") || "");
|
|
1401
|
-
if (id && href) idToHref.set(id, href);
|
|
1402
|
-
}
|
|
1403
|
-
if (spine.length > 0) {
|
|
1404
|
-
const ordered = [];
|
|
1405
|
-
for (let i = 0; i < spine.length; i++) {
|
|
1406
|
-
const href = idToHref.get(spine[i].getAttribute("idref") || "");
|
|
1407
|
-
if (href) ordered.push(href);
|
|
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;
|
|
1408
1094
|
}
|
|
1409
|
-
if (ordered.length > 0) return ordered;
|
|
1410
1095
|
}
|
|
1411
|
-
return
|
|
1096
|
+
return out;
|
|
1412
1097
|
}
|
|
1413
|
-
function
|
|
1414
|
-
if (
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
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);
|
|
1421
1127
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
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;
|
|
1428
1139
|
}
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
else if (ratio >= HEADING_RATIO_H3) level = 3;
|
|
1439
|
-
}
|
|
1440
|
-
const compactText = text.replace(/\s+/g, "");
|
|
1441
|
-
if (/^제\d+[조장절편]/.test(compactText) && text.length <= 50) {
|
|
1442
|
-
if (level === 0) level = 3;
|
|
1443
|
-
}
|
|
1444
|
-
if (level > 0) {
|
|
1445
|
-
block.type = "heading";
|
|
1446
|
-
block.level = level;
|
|
1447
|
-
}
|
|
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);
|
|
1448
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 };
|
|
1449
1161
|
}
|
|
1162
|
+
|
|
1163
|
+
// src/hwpx/table-build.ts
|
|
1450
1164
|
function buildTableWithCellMeta(state) {
|
|
1451
1165
|
const table = buildTable(state.rows);
|
|
1452
1166
|
if (state.caption) table.caption = state.caption;
|
|
@@ -1526,6 +1240,8 @@ function completeTable(newTable, tableStack, blocks, ctx) {
|
|
|
1526
1240
|
}
|
|
1527
1241
|
return parentTable;
|
|
1528
1242
|
}
|
|
1243
|
+
|
|
1244
|
+
// src/hwpx/section-walker.ts
|
|
1529
1245
|
function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
|
|
1530
1246
|
const parser = createXmlParser(warnings);
|
|
1531
1247
|
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
@@ -2059,49 +1775,357 @@ function extractParagraphInfo(para, styleMap, ctx) {
|
|
|
2059
1775
|
break;
|
|
2060
1776
|
}
|
|
2061
1777
|
}
|
|
2062
|
-
};
|
|
2063
|
-
walk(para);
|
|
2064
|
-
const leaderIdx = text.indexOf("");
|
|
2065
|
-
if (leaderIdx >= 0) text = text.substring(0, leaderIdx);
|
|
2066
|
-
let cleanText = text.replace(/[ \t]+/g, " ").trim();
|
|
2067
|
-
if (/^그림입니다\.?\s*원본\s*그림의\s*(이름|크기)/.test(cleanText)) cleanText = "";
|
|
2068
|
-
cleanText = cleanText.replace(/그림입니다\.?\s*원본\s*그림의\s*(이름|크기)[^\n]*(\n[^\n]*원본\s*그림의\s*(이름|크기)[^\n]*)*/g, "").trim();
|
|
2069
|
-
cleanText = cleanText.replace(/(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|선|직선|곡선|화살표|오각형|육각형|팔각형|별|십자|구름|마름모|도넛|평행사변형|사다리꼴|개체|그리기\s?개체|묶음\s?개체|글상자|표|그림|OLE\s?개체)\s?입니다\.?/g, "").trim();
|
|
2070
|
-
let style;
|
|
2071
|
-
if (styleMap && charPrId) {
|
|
2072
|
-
const charProp = styleMap.charProperties.get(charPrId);
|
|
2073
|
-
if (charProp) {
|
|
2074
|
-
style = {};
|
|
2075
|
-
if (charProp.fontSize) style.fontSize = charProp.fontSize;
|
|
2076
|
-
if (charProp.bold) style.bold = true;
|
|
2077
|
-
if (charProp.italic) style.italic = true;
|
|
2078
|
-
if (charProp.fontName) style.fontName = charProp.fontName;
|
|
2079
|
-
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;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
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 };
|
|
1988
|
+
}
|
|
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;
|
|
1998
|
+
}
|
|
1999
|
+
const sectionFiles = zip.file(/[Ss]ection\d+\.xml$/);
|
|
2000
|
+
return sectionFiles.map((f) => f.name).sort(compareSectionPaths);
|
|
2001
|
+
}
|
|
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);
|
|
2013
|
+
}
|
|
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);
|
|
2080
2019
|
}
|
|
2020
|
+
if (ordered.length > 0) return ordered;
|
|
2081
2021
|
}
|
|
2082
|
-
return
|
|
2022
|
+
return Array.from(idToHref.values()).sort(compareSectionPaths);
|
|
2083
2023
|
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
const
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
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 {
|
|
2092
2041
|
}
|
|
2093
|
-
return null;
|
|
2094
2042
|
}
|
|
2095
|
-
function
|
|
2096
|
-
|
|
2097
|
-
const
|
|
2098
|
-
if (!
|
|
2099
|
-
|
|
2100
|
-
const
|
|
2101
|
-
|
|
2102
|
-
|
|
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);
|
|
2103
2065
|
}
|
|
2104
|
-
|
|
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 };
|
|
2105
2129
|
}
|
|
2106
2130
|
|
|
2107
2131
|
// src/hwp5/record.ts
|
|
@@ -2628,32 +2652,42 @@ function resolveImageBlocks(binDataMap, blocks, warnings) {
|
|
|
2628
2652
|
if (imageBlocks.length === 0) return [];
|
|
2629
2653
|
const images = [];
|
|
2630
2654
|
const renamed = /* @__PURE__ */ new Map();
|
|
2655
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
2631
2656
|
let imageIndex = 0;
|
|
2632
2657
|
for (const block of imageBlocks) {
|
|
2633
2658
|
if (!block.text) continue;
|
|
2634
2659
|
const storageId = parseInt(block.text, 10);
|
|
2635
2660
|
if (isNaN(storageId)) continue;
|
|
2636
|
-
|
|
2637
|
-
if (
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2661
|
+
let img = resolved.get(storageId);
|
|
2662
|
+
if (img === void 0) {
|
|
2663
|
+
const bin = binDataMap.get(storageId);
|
|
2664
|
+
if (!bin) {
|
|
2665
|
+
warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
|
|
2666
|
+
resolved.set(storageId, null);
|
|
2667
|
+
} else {
|
|
2668
|
+
const mime = detectImageMime(bin.data);
|
|
2669
|
+
if (!mime) {
|
|
2670
|
+
warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
|
|
2671
|
+
resolved.set(storageId, null);
|
|
2672
|
+
} else {
|
|
2673
|
+
imageIndex++;
|
|
2674
|
+
const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
|
|
2675
|
+
img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
|
|
2676
|
+
resolved.set(storageId, img);
|
|
2677
|
+
images.push({ filename: img.filename, data: img.data, mimeType: img.mime });
|
|
2678
|
+
renamed.set(storageId, img.filename);
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
img = resolved.get(storageId);
|
|
2642
2682
|
}
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
|
|
2683
|
+
if (!img) {
|
|
2684
|
+
const bin = binDataMap.get(storageId);
|
|
2646
2685
|
block.type = "paragraph";
|
|
2647
|
-
block.text = `[\uC774\uBBF8\uC9C0: ${bin.name}]`;
|
|
2686
|
+
block.text = bin ? `[\uC774\uBBF8\uC9C0: ${bin.name}]` : `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
|
|
2648
2687
|
continue;
|
|
2649
2688
|
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
const filename = `image_${String(imageIndex).padStart(3, "0")}.${ext}`;
|
|
2653
|
-
images.push({ filename, data: new Uint8Array(bin.data), mimeType: mime });
|
|
2654
|
-
renamed.set(storageId, filename);
|
|
2655
|
-
block.text = filename;
|
|
2656
|
-
block.imageData = { data: new Uint8Array(bin.data), mimeType: mime, filename: bin.name };
|
|
2689
|
+
block.text = img.filename;
|
|
2690
|
+
block.imageData = { data: img.data, mimeType: img.mime, filename: binDataMap.get(storageId).name };
|
|
2657
2691
|
}
|
|
2658
2692
|
resolveCellImageSentinels(blocks, renamed);
|
|
2659
2693
|
return images;
|
|
@@ -16733,7 +16767,7 @@ function isDistributionSentinel(markdown) {
|
|
|
16733
16767
|
}
|
|
16734
16768
|
|
|
16735
16769
|
// src/xlsx/parser.ts
|
|
16736
|
-
import
|
|
16770
|
+
import JSZip4 from "jszip";
|
|
16737
16771
|
import { DOMParser as DOMParser2 } from "@xmldom/xmldom";
|
|
16738
16772
|
var MAX_SHEETS = 100;
|
|
16739
16773
|
var MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
|
|
@@ -16924,7 +16958,7 @@ function sheetToBlocks(sheetName, grid, merges, maxRow, maxCol, sheetIndex) {
|
|
|
16924
16958
|
}
|
|
16925
16959
|
async function parseXlsxDocument(buffer, options) {
|
|
16926
16960
|
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE3);
|
|
16927
|
-
const zip = await
|
|
16961
|
+
const zip = await JSZip4.loadAsync(buffer);
|
|
16928
16962
|
const warnings = [];
|
|
16929
16963
|
const workbookFile = zip.file("xl/workbook.xml");
|
|
16930
16964
|
if (!workbookFile) {
|
|
@@ -17630,7 +17664,7 @@ async function parseXlsDocument(buffer, options) {
|
|
|
17630
17664
|
}
|
|
17631
17665
|
|
|
17632
17666
|
// src/docx/parser.ts
|
|
17633
|
-
import
|
|
17667
|
+
import JSZip5 from "jszip";
|
|
17634
17668
|
import { DOMParser as DOMParser3 } from "@xmldom/xmldom";
|
|
17635
17669
|
|
|
17636
17670
|
// src/docx/equation.ts
|
|
@@ -18341,7 +18375,7 @@ function parseTable(tbl, styles, numbering, footnotes, rels) {
|
|
|
18341
18375
|
};
|
|
18342
18376
|
return { type: "table", table };
|
|
18343
18377
|
}
|
|
18344
|
-
async function extractImages(zip, rels, doc) {
|
|
18378
|
+
async function extractImages(zip, rels, doc, warnings) {
|
|
18345
18379
|
const blocks = [];
|
|
18346
18380
|
const images = [];
|
|
18347
18381
|
const drawingElements = findElements(doc.documentElement, "drawing");
|
|
@@ -18372,7 +18406,11 @@ async function extractImages(zip, rels, doc) {
|
|
|
18372
18406
|
const filename = `image_${String(imgIdx).padStart(3, "0")}.${ext}`;
|
|
18373
18407
|
images.push({ filename, data, mimeType: mimeMap[ext] ?? "image/png" });
|
|
18374
18408
|
blocks.push({ type: "image", text: filename });
|
|
18375
|
-
} catch {
|
|
18409
|
+
} catch (err) {
|
|
18410
|
+
warnings.push({
|
|
18411
|
+
code: "SKIPPED_IMAGE",
|
|
18412
|
+
message: `DOCX \uC774\uBBF8\uC9C0 \uCD94\uCD9C \uC2E4\uD328 (${imgPath}): ${err instanceof Error ? err.message : String(err)}`
|
|
18413
|
+
});
|
|
18376
18414
|
}
|
|
18377
18415
|
}
|
|
18378
18416
|
}
|
|
@@ -18380,7 +18418,7 @@ async function extractImages(zip, rels, doc) {
|
|
|
18380
18418
|
}
|
|
18381
18419
|
async function parseDocxDocument(buffer, options) {
|
|
18382
18420
|
precheckZipSize(buffer, MAX_DECOMPRESS_SIZE4);
|
|
18383
|
-
const zip = await
|
|
18421
|
+
const zip = await JSZip5.loadAsync(buffer);
|
|
18384
18422
|
const warnings = [];
|
|
18385
18423
|
const docFile = zip.file("word/document.xml");
|
|
18386
18424
|
if (!docFile) {
|
|
@@ -18396,7 +18434,11 @@ async function parseDocxDocument(buffer, options) {
|
|
|
18396
18434
|
if (stylesFile) {
|
|
18397
18435
|
try {
|
|
18398
18436
|
styles = parseStyles(await stylesFile.async("text"));
|
|
18399
|
-
} catch {
|
|
18437
|
+
} catch (err) {
|
|
18438
|
+
warnings.push({
|
|
18439
|
+
code: "PARTIAL_PARSE",
|
|
18440
|
+
message: `DOCX \uC2A4\uD0C0\uC77C(styles.xml) \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uAE30\uBCF8 \uC2A4\uD0C0\uC77C\uB85C \uACC4\uC18D: ${err instanceof Error ? err.message : String(err)}`
|
|
18441
|
+
});
|
|
18400
18442
|
}
|
|
18401
18443
|
}
|
|
18402
18444
|
let numbering = /* @__PURE__ */ new Map();
|
|
@@ -18404,7 +18446,11 @@ async function parseDocxDocument(buffer, options) {
|
|
|
18404
18446
|
if (numFile) {
|
|
18405
18447
|
try {
|
|
18406
18448
|
numbering = parseNumbering(await numFile.async("text"));
|
|
18407
|
-
} catch {
|
|
18449
|
+
} catch (err) {
|
|
18450
|
+
warnings.push({
|
|
18451
|
+
code: "PARTIAL_PARSE",
|
|
18452
|
+
message: `DOCX \uBC88\uD638\uB9E4\uAE30\uAE30(numbering.xml) \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uBAA9\uB85D \uBC88\uD638 \uC0DD\uB7B5: ${err instanceof Error ? err.message : String(err)}`
|
|
18453
|
+
});
|
|
18408
18454
|
}
|
|
18409
18455
|
}
|
|
18410
18456
|
let footnotes = /* @__PURE__ */ new Map();
|
|
@@ -18412,7 +18458,11 @@ async function parseDocxDocument(buffer, options) {
|
|
|
18412
18458
|
if (fnFile) {
|
|
18413
18459
|
try {
|
|
18414
18460
|
footnotes = parseFootnotes(await fnFile.async("text"));
|
|
18415
|
-
} catch {
|
|
18461
|
+
} catch (err) {
|
|
18462
|
+
warnings.push({
|
|
18463
|
+
code: "PARTIAL_PARSE",
|
|
18464
|
+
message: `DOCX \uAC01\uC8FC(footnotes.xml) \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uAC01\uC8FC \uC0DD\uB7B5: ${err instanceof Error ? err.message : String(err)}`
|
|
18465
|
+
});
|
|
18416
18466
|
}
|
|
18417
18467
|
}
|
|
18418
18468
|
const docXml = await docFile.async("text");
|
|
@@ -18434,7 +18484,7 @@ async function parseDocxDocument(buffer, options) {
|
|
|
18434
18484
|
if (block) blocks.push(block);
|
|
18435
18485
|
}
|
|
18436
18486
|
}
|
|
18437
|
-
const { blocks: imgBlocks, images } = await extractImages(zip, rels, doc);
|
|
18487
|
+
const { blocks: imgBlocks, images } = await extractImages(zip, rels, doc, warnings);
|
|
18438
18488
|
const metadata = {};
|
|
18439
18489
|
const coreFile = zip.file("docProps/core.xml");
|
|
18440
18490
|
if (coreFile) {
|
|
@@ -18452,7 +18502,11 @@ async function parseDocxDocument(buffer, options) {
|
|
|
18452
18502
|
if (created) metadata.createdAt = created;
|
|
18453
18503
|
const modified = getFirst("dcterms:modified");
|
|
18454
18504
|
if (modified) metadata.modifiedAt = modified;
|
|
18455
|
-
} catch {
|
|
18505
|
+
} catch (err) {
|
|
18506
|
+
warnings.push({
|
|
18507
|
+
code: "PARTIAL_PARSE",
|
|
18508
|
+
message: `DOCX \uBA54\uD0C0\uB370\uC774\uD130(core.xml) \uD30C\uC2F1 \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`
|
|
18509
|
+
});
|
|
18456
18510
|
}
|
|
18457
18511
|
}
|
|
18458
18512
|
const outline = blocks.filter((b) => b.type === "heading").map((b) => ({ level: b.level ?? 2, text: b.text ?? "" }));
|
|
@@ -19282,7 +19336,7 @@ function fillInlineFields(text, values, filled, matchedLabels) {
|
|
|
19282
19336
|
}
|
|
19283
19337
|
|
|
19284
19338
|
// src/form/filler-hwpx.ts
|
|
19285
|
-
import
|
|
19339
|
+
import JSZip6 from "jszip";
|
|
19286
19340
|
|
|
19287
19341
|
// src/roundtrip/source-map.ts
|
|
19288
19342
|
function escapeXmlText(text) {
|
|
@@ -19956,7 +20010,7 @@ function patchZipEntries(original, replacements) {
|
|
|
19956
20010
|
// src/form/filler-hwpx.ts
|
|
19957
20011
|
async function fillHwpx(hwpxBuffer, values) {
|
|
19958
20012
|
const u8 = new Uint8Array(hwpxBuffer);
|
|
19959
|
-
const zip = await
|
|
20013
|
+
const zip = await JSZip6.loadAsync(hwpxBuffer);
|
|
19960
20014
|
const sectionPaths = Object.keys(zip.files).filter((name) => /[Ss]ection\d+\.xml$/i.test(name)).sort();
|
|
19961
20015
|
if (sectionPaths.length === 0) {
|
|
19962
20016
|
throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
@@ -20197,7 +20251,7 @@ async function fillHwpx(hwpxBuffer, values) {
|
|
|
20197
20251
|
}
|
|
20198
20252
|
|
|
20199
20253
|
// src/hwpx/generator.ts
|
|
20200
|
-
import
|
|
20254
|
+
import JSZip7 from "jszip";
|
|
20201
20255
|
|
|
20202
20256
|
// src/hwpx/text-metrics.ts
|
|
20203
20257
|
var ASCII_W = [
|
|
@@ -21033,7 +21087,7 @@ async function markdownToHwpx(markdown, options) {
|
|
|
21033
21087
|
const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
|
|
21034
21088
|
const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
|
|
21035
21089
|
const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit);
|
|
21036
|
-
const zip = new
|
|
21090
|
+
const zip = new JSZip7();
|
|
21037
21091
|
zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
|
|
21038
21092
|
zip.file("META-INF/container.xml", generateContainerXml());
|
|
21039
21093
|
zip.file("Contents/content.hpf", generateManifest());
|
|
@@ -21577,9 +21631,22 @@ function precomputeGongmunList(blocks, gongmun) {
|
|
|
21577
21631
|
continue;
|
|
21578
21632
|
}
|
|
21579
21633
|
const run = [];
|
|
21580
|
-
while (i < blocks.length
|
|
21581
|
-
|
|
21582
|
-
|
|
21634
|
+
while (i < blocks.length) {
|
|
21635
|
+
const t = blocks[i].type;
|
|
21636
|
+
if (t === "list_item") {
|
|
21637
|
+
run.push(i);
|
|
21638
|
+
i++;
|
|
21639
|
+
continue;
|
|
21640
|
+
}
|
|
21641
|
+
if (t === "table" || t === "html_table") {
|
|
21642
|
+
let j = i + 1;
|
|
21643
|
+
while (j < blocks.length && (blocks[j].type === "table" || blocks[j].type === "html_table")) j++;
|
|
21644
|
+
if (j < blocks.length && blocks[j].type === "list_item") {
|
|
21645
|
+
i = j;
|
|
21646
|
+
continue;
|
|
21647
|
+
}
|
|
21648
|
+
}
|
|
21649
|
+
break;
|
|
21583
21650
|
}
|
|
21584
21651
|
const depths = run.map((bi) => Math.min(Math.max(blocks[bi].indent || 0, 0), GONGMUN_LIST_LEVELS - 1));
|
|
21585
21652
|
const suppress = gongmun.numbering === "standard" ? computeSuppression(depths) : depths.map(() => false);
|
|
@@ -21847,7 +21914,7 @@ function diffTableCells(a, b) {
|
|
|
21847
21914
|
}
|
|
21848
21915
|
|
|
21849
21916
|
// src/roundtrip/patcher.ts
|
|
21850
|
-
import
|
|
21917
|
+
import JSZip8 from "jszip";
|
|
21851
21918
|
|
|
21852
21919
|
// src/roundtrip/table-rows.ts
|
|
21853
21920
|
var ROW_OBJECT_RE = /<(?:[A-Za-z0-9_]+:)?(?:tbl|pic|equation|ole|container|shape|drawingObject|drawText|video|chart|fieldBegin|fieldEnd|ctrl)\b/;
|
|
@@ -22485,7 +22552,7 @@ async function patchHwpx(original, editedMarkdown, options) {
|
|
|
22485
22552
|
}
|
|
22486
22553
|
let zip;
|
|
22487
22554
|
try {
|
|
22488
|
-
zip = await
|
|
22555
|
+
zip = await JSZip8.loadAsync(original);
|
|
22489
22556
|
} catch {
|
|
22490
22557
|
return { success: false, applied: 0, skipped, error: "ZIP \uB85C\uB4DC \uC2E4\uD328" };
|
|
22491
22558
|
}
|
|
@@ -23148,21 +23215,24 @@ function readRecordsStrict(stream) {
|
|
|
23148
23215
|
}
|
|
23149
23216
|
return recs;
|
|
23150
23217
|
}
|
|
23151
|
-
function serializeRecords(recs, repl) {
|
|
23218
|
+
function serializeRecords(recs, repl, inserts) {
|
|
23152
23219
|
const parts = [];
|
|
23153
|
-
|
|
23154
|
-
const data = repl?.get(i) ?? recs[i].data;
|
|
23220
|
+
const push = (tagId, level, data) => {
|
|
23155
23221
|
const ext = data.length >= 4095;
|
|
23156
23222
|
const header = Buffer.alloc(ext ? 8 : 4);
|
|
23157
|
-
header.writeUInt32LE((
|
|
23223
|
+
header.writeUInt32LE((tagId & 1023 | (level & 1023) << 10 | (ext ? 4095 : data.length) << 20) >>> 0, 0);
|
|
23158
23224
|
if (ext) header.writeUInt32LE(data.length, 4);
|
|
23159
23225
|
parts.push(header, data);
|
|
23226
|
+
};
|
|
23227
|
+
for (let i = 0; i < recs.length; i++) {
|
|
23228
|
+
for (const ins of inserts?.get(i) ?? []) push(ins.tagId, ins.level, ins.data);
|
|
23229
|
+
push(recs[i].tagId, recs[i].level, repl?.get(i) ?? recs[i].data);
|
|
23160
23230
|
}
|
|
23161
23231
|
return Buffer.concat(parts);
|
|
23162
23232
|
}
|
|
23163
23233
|
function scanSection(stream, sectionIndex, compressed) {
|
|
23164
23234
|
const records = readRecordsStrict(stream);
|
|
23165
|
-
if (!records) return { records: [], safe: false, paras: [], tables: [], compressed, repl: /* @__PURE__ */ new Map() };
|
|
23235
|
+
if (!records) return { records: [], safe: false, paras: [], tables: [], compressed, repl: /* @__PURE__ */ new Map(), inserts: /* @__PURE__ */ new Map() };
|
|
23166
23236
|
const safe = serializeRecords(records).equals(stream);
|
|
23167
23237
|
const parent = new Int32Array(records.length).fill(-1);
|
|
23168
23238
|
const stack = [];
|
|
@@ -23261,7 +23331,7 @@ function scanSection(stream, sectionIndex, compressed) {
|
|
|
23261
23331
|
}
|
|
23262
23332
|
tables.push({ sectionIndex, rows, cols, cells });
|
|
23263
23333
|
}
|
|
23264
|
-
return { records, safe, paras, tables, compressed, repl: /* @__PURE__ */ new Map() };
|
|
23334
|
+
return { records, safe, paras, tables, compressed, repl: /* @__PURE__ */ new Map(), inserts: /* @__PURE__ */ new Map() };
|
|
23265
23335
|
}
|
|
23266
23336
|
async function patchHwp(original, editedMarkdown, options) {
|
|
23267
23337
|
const skipped = [];
|
|
@@ -23329,15 +23399,15 @@ async function patchHwp(original, editedMarkdown, options) {
|
|
|
23329
23399
|
}
|
|
23330
23400
|
}
|
|
23331
23401
|
let data;
|
|
23332
|
-
const dirty = scans.some((s) => s.repl.size > 0);
|
|
23402
|
+
const dirty = scans.some((s) => s.repl.size > 0 || s.inserts.size > 0);
|
|
23333
23403
|
if (!dirty) {
|
|
23334
23404
|
data = new Uint8Array(original);
|
|
23335
23405
|
} else {
|
|
23336
23406
|
try {
|
|
23337
23407
|
let out = originalBuf;
|
|
23338
23408
|
for (let i = 0; i < scans.length; i++) {
|
|
23339
|
-
if (scans[i].repl.size === 0) continue;
|
|
23340
|
-
const newStream = serializeRecords(scans[i].records, scans[i].repl);
|
|
23409
|
+
if (scans[i].repl.size === 0 && scans[i].inserts.size === 0) continue;
|
|
23410
|
+
const newStream = serializeRecords(scans[i].records, scans[i].repl, scans[i].inserts);
|
|
23341
23411
|
const content = compressed ? deflateRawSync2(newStream) : newStream;
|
|
23342
23412
|
out = replaceOleStream(out, sectionPaths[i], content);
|
|
23343
23413
|
}
|
|
@@ -23665,24 +23735,25 @@ function applyCellEdit5(table, scanTable, gridR, gridC, newLines, ctx, before, a
|
|
|
23665
23735
|
}
|
|
23666
23736
|
const unstable = newLines.find((l) => sanitizeText(l) !== l);
|
|
23667
23737
|
if (unstable !== void 0) return skip("\uACF5\uBC31 \uC815\uADDC\uD654 \uBD88\uC548\uC815 \uD14D\uC2A4\uD2B8 \u2014 \uD328\uCE58 \uC2DC \uC6D0\uBB38 \uBCF4\uC874 \uBD88\uAC00\uB85C \uBBF8\uC9C0\uC6D0");
|
|
23668
|
-
|
|
23738
|
+
const targets = nonEmpty.length > 0 ? nonEmpty : cell.paras;
|
|
23739
|
+
if (targets.length === 0) return skip("\uC140\uC5D0 \uBB38\uB2E8\uC774 \uC5C6\uC74C \u2014 \uBBF8\uC9C0\uC6D0");
|
|
23669
23740
|
const assigned = [];
|
|
23670
|
-
for (let i = 0; i <
|
|
23741
|
+
for (let i = 0; i < targets.length; i++) {
|
|
23671
23742
|
if (i < newLines.length) {
|
|
23672
|
-
assigned.push(i ===
|
|
23743
|
+
assigned.push(i === targets.length - 1 && newLines.length > targets.length ? newLines.slice(i).join(" ") : newLines[i]);
|
|
23673
23744
|
} else {
|
|
23674
23745
|
assigned.push("");
|
|
23675
23746
|
}
|
|
23676
23747
|
}
|
|
23677
|
-
if (newLines.length >
|
|
23748
|
+
if (newLines.length > targets.length) {
|
|
23678
23749
|
ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8\uC5D0 \uBCD1\uD569 \uC801\uC6A9", after: summarize(after), partial: true });
|
|
23679
23750
|
} else if (newLines.length < nonEmpty.length && nonEmpty.length > 1) {
|
|
23680
23751
|
ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uC0AD\uC81C\uB294 \uBB38\uB2E8 \uC81C\uAC70 \uBBF8\uC9C0\uC6D0 \u2014 \uBE48 \uBB38\uB2E8 \uC794\uC874(\uBDF0\uC5B4\uC5D0 \uBE48 \uC904 \uD45C\uC2DC \uAC00\uB2A5)", before: summarize(before), after: summarize(after), partial: true });
|
|
23681
23752
|
}
|
|
23682
23753
|
let staged = 0;
|
|
23683
|
-
for (let i = 0; i <
|
|
23684
|
-
if (assigned[i] ===
|
|
23685
|
-
staged += stageParaPatch(ctx.scans[
|
|
23754
|
+
for (let i = 0; i < targets.length; i++) {
|
|
23755
|
+
if (assigned[i] === targets[i].rawText || normForMatch(assigned[i]) === normForMatch(targets[i].rawText)) continue;
|
|
23756
|
+
staged += stageParaPatch(ctx.scans[targets[i].sectionIndex], targets[i], assigned[i], skip);
|
|
23686
23757
|
}
|
|
23687
23758
|
return staged > 0 ? 1 : 0;
|
|
23688
23759
|
}
|
|
@@ -23752,7 +23823,21 @@ function splitParaText(data) {
|
|
|
23752
23823
|
if (firstP < 0) firstP = k;
|
|
23753
23824
|
lastP = k;
|
|
23754
23825
|
}
|
|
23755
|
-
if (firstP < 0)
|
|
23826
|
+
if (firstP < 0) {
|
|
23827
|
+
if (toks.some((t) => t.visible)) return null;
|
|
23828
|
+
let cut = data.length;
|
|
23829
|
+
for (const t of toks) if (data.readUInt16LE(t.start) === 13) {
|
|
23830
|
+
cut = t.start;
|
|
23831
|
+
break;
|
|
23832
|
+
}
|
|
23833
|
+
return {
|
|
23834
|
+
prefix: data.subarray(0, cut),
|
|
23835
|
+
prefixUnits: cut / 2,
|
|
23836
|
+
core: "",
|
|
23837
|
+
suffix: data.subarray(cut),
|
|
23838
|
+
suffixUnits: (data.length - cut) / 2
|
|
23839
|
+
};
|
|
23840
|
+
}
|
|
23756
23841
|
for (let k = firstP; k <= lastP; k++) if (!toks[k].plain) return null;
|
|
23757
23842
|
for (let k = 0; k < firstP; k++) if (toks[k].visible) return null;
|
|
23758
23843
|
for (let k = lastP + 1; k < toks.length; k++) if (toks[k].visible) return null;
|
|
@@ -23785,7 +23870,6 @@ function rebuildCharShape(csData, coreStartUnit) {
|
|
|
23785
23870
|
}
|
|
23786
23871
|
function stageParaPatch(scan, para, newPlain, skip) {
|
|
23787
23872
|
if (!scan.safe) return skip("\uC139\uC158 \uB808\uCF54\uB4DC \uC7AC\uC9C1\uB82C\uD654 \uBD88\uC77C\uCE58 \u2014 \uC548\uC804\uC744 \uC704\uD574 \uC774 \uC139\uC158\uC740 \uBBF8\uC9C0\uC6D0");
|
|
23788
|
-
if (para.textIdx === -1) return skip("\uBE48 \uBB38\uB2E8 \uD14D\uC2A4\uD2B8 \uCD94\uAC00\uB294 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
23789
23873
|
if (para.textIdx === -2) return skip("\uBCF5\uC218 PARA_TEXT \uB808\uCF54\uB4DC \uBB38\uB2E8 \u2014 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
23790
23874
|
if (para.rangeTagCount > 0) return skip("\uBC94\uC704 \uD0DC\uADF8(\uD615\uAD11\uD39C/\uAD50\uC815\uBD80\uD638) \uBB38\uB2E8 \u2014 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
23791
23875
|
if (para.charShapeIdx < 0 || para.lineSegIdx < 0) return skip("\uBB38\uB2E8 \uB808\uCF54\uB4DC \uAD6C\uC131 \uBE44\uC815\uD615 \u2014 \uBBF8\uC9C0\uC6D0");
|
|
@@ -23793,11 +23877,27 @@ function stageParaPatch(scan, para, newPlain, skip) {
|
|
|
23793
23877
|
if (/[\u0000-\u001f]/.test(newPlain)) return skip("\uC0C8 \uD14D\uC2A4\uD2B8\uC5D0 \uC81C\uC5B4\uBB38\uC790 \uD3EC\uD568 \u2014 \uBBF8\uC9C0\uC6D0");
|
|
23794
23878
|
const records = scan.records;
|
|
23795
23879
|
const headerRec = records[para.headerIdx];
|
|
23796
|
-
const textRec = records[para.textIdx];
|
|
23797
23880
|
const charShapeRec = records[para.charShapeIdx];
|
|
23798
23881
|
if (charShapeRec.data.length < 8) {
|
|
23799
23882
|
return skip("CHAR_SHAPE \uB808\uCF54\uB4DC \uBE44\uC815\uD615 \u2014 \uBBF8\uC9C0\uC6D0");
|
|
23800
23883
|
}
|
|
23884
|
+
if (para.textIdx === -1) {
|
|
23885
|
+
const nCharsLow = para.nCharsRaw & 2147483647;
|
|
23886
|
+
if (nCharsLow > 1) return skip("PARA_TEXT \uC5C6\uB294 \uBB38\uB2E8\uC758 nChars \uBE44\uC815\uD615 \u2014 \uBBF8\uC9C0\uC6D0");
|
|
23887
|
+
const paraEnd = nCharsLow === 1 ? Buffer.from([13, 0]) : Buffer.alloc(0);
|
|
23888
|
+
const at = para.headerIdx + 1;
|
|
23889
|
+
const list = scan.inserts.get(at) ?? [];
|
|
23890
|
+
list.push({ tagId: TAG_PARA_TEXT, level: headerRec.level + 1, data: Buffer.concat([Buffer.from(newPlain, "utf16le"), paraEnd]) });
|
|
23891
|
+
scan.inserts.set(at, list);
|
|
23892
|
+
const newHeader2 = Buffer.from(headerRec.data);
|
|
23893
|
+
newHeader2.writeUInt32LE((para.nCharsRaw & 2147483648 | newPlain.length + nCharsLow) >>> 0, 0);
|
|
23894
|
+
const cs2 = rebuildCharShape(charShapeRec.data, 0);
|
|
23895
|
+
scan.repl.set(para.charShapeIdx, cs2.buf);
|
|
23896
|
+
newHeader2.writeUInt16LE(cs2.count, 12);
|
|
23897
|
+
scan.repl.set(para.headerIdx, newHeader2);
|
|
23898
|
+
return 1;
|
|
23899
|
+
}
|
|
23900
|
+
const textRec = records[para.textIdx];
|
|
23801
23901
|
const seg = splitParaText(textRec.data);
|
|
23802
23902
|
if (!seg) {
|
|
23803
23903
|
return skip(para.ctrlMask !== 0 ? "\uCEE8\uD2B8\uB864 \uBB38\uC790(\uD0ED/\uD544\uB4DC/\uD2B9\uC218\uACF5\uBC31 \uB4F1 \uD14D\uC2A4\uD2B8 \uC911\uAC04) \uD3EC\uD568 \uBB38\uB2E8 \u2014 \uBBF8\uC9C0\uC6D0 (v1)" : "PARA_TEXT \uC7AC\uAD6C\uC131 \uBD88\uC77C\uCE58 \u2014 \uC6D0\uBB38 \uBCF4\uC874 \uBD88\uAC00\uB85C \uBBF8\uC9C0\uC6D0");
|
|
@@ -23819,10 +23919,10 @@ function stageParaPatch(scan, para, newPlain, skip) {
|
|
|
23819
23919
|
}
|
|
23820
23920
|
|
|
23821
23921
|
// src/roundtrip/session.ts
|
|
23822
|
-
import
|
|
23922
|
+
import JSZip9 from "jszip";
|
|
23823
23923
|
async function buildState(bytes) {
|
|
23824
23924
|
const parsed = await parseHwpxDocument(u8ToArrayBuffer2(bytes));
|
|
23825
|
-
const zip = await
|
|
23925
|
+
const zip = await JSZip9.loadAsync(bytes);
|
|
23826
23926
|
const sectionPaths = await resolveSectionEntryNames(zip);
|
|
23827
23927
|
if (sectionPaths.length === 0) {
|
|
23828
23928
|
throw new Error("HWPX \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
@@ -24403,7 +24503,7 @@ async function parseHwp(buffer, options) {
|
|
|
24403
24503
|
async function parsePdf(buffer, options) {
|
|
24404
24504
|
let parsePdfDocument;
|
|
24405
24505
|
try {
|
|
24406
|
-
const mod = await import("./parser-
|
|
24506
|
+
const mod = await import("./parser-XEDROIM7.js");
|
|
24407
24507
|
parsePdfDocument = mod.parsePdfDocument;
|
|
24408
24508
|
} catch {
|
|
24409
24509
|
return {
|