kordoc 4.14.2 → 4.14.3
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 +38 -5
- package/dist/{-AQKXLFIM.js → -7XBPXCXN.js} +13 -11
- package/dist/{chunk-FOJZNMMK.js → chunk-2LQGSDZ6.js} +1 -1
- package/dist/chunk-2LQGSDZ6.js.map +1 -0
- package/dist/chunk-4R67INKT.js +1002 -0
- package/dist/chunk-4R67INKT.js.map +1 -0
- package/dist/{chunk-ILU76UF3.js → chunk-4RJ5D7MT.js} +2 -2
- package/dist/chunk-4RJ5D7MT.js.map +1 -0
- package/dist/{chunk-EQZI77PP.cjs → chunk-5VVS2QIJ.cjs} +3 -3
- package/dist/{chunk-EQZI77PP.cjs.map → chunk-5VVS2QIJ.cjs.map} +1 -1
- package/dist/{chunk-3DUADAY2.js → chunk-A275ZQLX.js} +209 -87
- package/dist/chunk-A275ZQLX.js.map +1 -0
- package/dist/{chunk-EISO3POT.js → chunk-CVEERPMP.js} +788 -194
- package/dist/chunk-CVEERPMP.js.map +1 -0
- package/dist/{chunk-ZWD734EY.js → chunk-D6KQKMU3.js} +2 -2
- package/dist/{chunk-QOMNEWCQ.cjs → chunk-EBZUZC6F.cjs} +305 -31
- package/dist/chunk-EBZUZC6F.cjs.map +1 -0
- package/dist/{chunk-HR2XNERB.cjs → chunk-EMGF6EEV.cjs} +2 -2
- package/dist/{chunk-HR2XNERB.cjs.map → chunk-EMGF6EEV.cjs.map} +1 -1
- package/dist/{chunk-GDW4YP7K.js → chunk-FAZR34CA.js} +2 -2
- package/dist/{chunk-TNCKOL2O.js → chunk-GTINVM5K.js} +2 -2
- package/dist/{chunk-L7DMSMAG.cjs → chunk-HX4UUF7Y.cjs} +1 -1
- package/dist/chunk-HX4UUF7Y.cjs.map +1 -0
- package/dist/{chunk-UET462HB.js → chunk-I4NNYWIR.js} +2 -2
- package/dist/chunk-IH56ZISC.js +620 -0
- package/dist/chunk-IH56ZISC.js.map +1 -0
- package/dist/{chunk-EDNJLUSE.js → chunk-IHZEFW4L.js} +2 -2
- package/dist/chunk-IW2CB755.cjs +1002 -0
- package/dist/chunk-IW2CB755.cjs.map +1 -0
- package/dist/{chunk-ZWOFQRIB.js → chunk-JCVIOLBF.js} +3 -3
- package/dist/{chunk-JQEFP45L.js → chunk-L5EH2NVO.js} +2 -2
- package/dist/{chunk-247ANM72.cjs → chunk-NOAQRCV6.cjs} +219 -97
- package/dist/chunk-NOAQRCV6.cjs.map +1 -0
- package/dist/{chunk-7IAQ4HOK.js → chunk-NQCUAGLR.js} +1 -1
- package/dist/chunk-NQCUAGLR.js.map +1 -0
- package/dist/{chunk-NS55SM7A.js → chunk-O2PGQNYE.js} +2 -2
- package/dist/{chunk-QC5T6D4J.js → chunk-QGW7NMTX.js} +299 -25
- package/dist/chunk-QGW7NMTX.js.map +1 -0
- package/dist/{chunk-A7QC2ZVP.js → chunk-UVVEBI22.js} +209 -87
- package/dist/chunk-UVVEBI22.js.map +1 -0
- package/dist/chunk-VJHAUMCF.js +949 -0
- package/dist/chunk-VJHAUMCF.js.map +1 -0
- package/dist/{chunk-C7FQBSON.js → chunk-VNFJPTU4.js} +2 -2
- package/dist/{chunk-VQ5W4MYO.js → chunk-VYOGXNQ3.js} +298 -24
- package/dist/chunk-VYOGXNQ3.js.map +1 -0
- package/dist/{chunk-DOGPOY4B.js → chunk-WB5LW52Z.js} +2 -2
- package/dist/{chunk-TSFS5VKD.js → chunk-YGBZABI4.js} +332 -141
- package/dist/chunk-YGBZABI4.js.map +1 -0
- package/dist/chunks-I7TEMJXH.js +10 -0
- package/dist/cli.js +228 -97
- package/dist/cli.js.map +1 -1
- package/dist/{formula-PJJ6S3Z2.cjs → formula-CPO2TZGB.cjs} +9 -9
- package/dist/{formula-PJJ6S3Z2.cjs.map → formula-CPO2TZGB.cjs.map} +1 -1
- package/dist/{formula-5S7WWXGH.js → formula-NOIDZEBA.js} +4 -4
- package/dist/{formula-JGFTXWYT.js → formula-X44GV7TN.js} +3 -3
- package/dist/{image-ocr-WCAYXLOA.js → image-ocr-72RAR4WR.js} +34 -16
- package/dist/image-ocr-72RAR4WR.js.map +1 -0
- package/dist/{image-ocr-FLIL7MGN.js → image-ocr-R6W6A75P.js} +32 -14
- package/dist/image-ocr-R6W6A75P.js.map +1 -0
- package/dist/{image-ocr-ZTYAGV3W.cjs → image-ocr-SUGT64ZM.cjs} +36 -18
- package/dist/image-ocr-SUGT64ZM.cjs.map +1 -0
- package/dist/index.cjs +2416 -1184
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -25
- package/dist/index.d.ts +62 -25
- package/dist/index.js +1853 -621
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +74 -53
- package/dist/mcp.js.map +1 -1
- package/dist/{model-bundle-FSPZZ2EM.js → model-bundle-57CLEBCO.js} +5 -5
- package/dist/{models-CJPAQK5R.js → models-7ELIFTD7.js} +5 -5
- package/dist/{parser-SCUTZAIJ.js → parser-4NKAYWAX.js} +261 -241
- package/dist/parser-4NKAYWAX.js.map +1 -0
- package/dist/{parser-A4TMPJAM.js → parser-HWFQRSUQ.js} +261 -241
- package/dist/parser-HWFQRSUQ.js.map +1 -0
- package/dist/{parser-VCXE4I5A.cjs → parser-RANGXGBD.cjs} +289 -269
- package/dist/parser-RANGXGBD.cjs.map +1 -0
- package/dist/pdf-ocr-BNC34MNU.js +13 -0
- package/dist/pdf-ocr-QX2GB33Q.js +16 -0
- package/dist/pdf-ocr-VHSFACH7.cjs +14 -0
- package/dist/{pdf-ocr-MBZZ57LD.cjs.map → pdf-ocr-VHSFACH7.cjs.map} +1 -1
- package/dist/{profile-io-LBM263K7.js → profile-io-V52PL5ZO.js} +3 -3
- package/dist/{rasterize-46RG4ZF6.js → rasterize-ZIGLJDOG.js} +3 -3
- package/dist/redact-TIQEQI3M.js +18 -0
- package/dist/redact-doc-KXZ3TZAG.js +1216 -0
- package/dist/redact-doc-KXZ3TZAG.js.map +1 -0
- package/dist/{render-NGP5T24S.js → render-U5PE6V2E.js} +7 -7
- package/dist/seal-DNXJBILR.js +10 -0
- package/dist/{setup-AU4OCHNH.js → setup-YRDM4ODP.js} +3 -3
- package/dist/visual-5XWMA4EV.js +19 -0
- package/dist/{watch-MKPUCPXM.js → watch-5IOLBATT.js} +12 -12
- package/package.json +2 -2
- package/dist/chunk-247ANM72.cjs.map +0 -1
- package/dist/chunk-3DUADAY2.js.map +0 -1
- package/dist/chunk-422427XJ.cjs +0 -571
- package/dist/chunk-422427XJ.cjs.map +0 -1
- package/dist/chunk-7IAQ4HOK.js.map +0 -1
- package/dist/chunk-A7QC2ZVP.js.map +0 -1
- package/dist/chunk-DZIXKL7E.js +0 -145
- package/dist/chunk-DZIXKL7E.js.map +0 -1
- package/dist/chunk-EISO3POT.js.map +0 -1
- package/dist/chunk-EP7KOI2V.js +0 -571
- package/dist/chunk-EP7KOI2V.js.map +0 -1
- package/dist/chunk-FOJZNMMK.js.map +0 -1
- package/dist/chunk-ILU76UF3.js.map +0 -1
- package/dist/chunk-KVWV4ULP.js +0 -516
- package/dist/chunk-KVWV4ULP.js.map +0 -1
- package/dist/chunk-L7DMSMAG.cjs.map +0 -1
- package/dist/chunk-QC5T6D4J.js.map +0 -1
- package/dist/chunk-QOMNEWCQ.cjs.map +0 -1
- package/dist/chunk-TSFS5VKD.js.map +0 -1
- package/dist/chunk-VQ5W4MYO.js.map +0 -1
- package/dist/chunks-SX77P5CV.js +0 -10
- package/dist/image-ocr-FLIL7MGN.js.map +0 -1
- package/dist/image-ocr-WCAYXLOA.js.map +0 -1
- package/dist/image-ocr-ZTYAGV3W.cjs.map +0 -1
- package/dist/parser-A4TMPJAM.js.map +0 -1
- package/dist/parser-SCUTZAIJ.js.map +0 -1
- package/dist/parser-VCXE4I5A.cjs.map +0 -1
- package/dist/pdf-ocr-76SAUXMZ.js +0 -13
- package/dist/pdf-ocr-CPU55CVF.js +0 -16
- package/dist/pdf-ocr-MBZZ57LD.cjs +0 -14
- package/dist/redact-7ILEAUTS.js +0 -12
- package/dist/seal-RGUPOLXD.js +0 -10
- package/dist/visual-NZLYIYEM.js +0 -19
- /package/dist/{-AQKXLFIM.js.map → -7XBPXCXN.js.map} +0 -0
- /package/dist/{chunk-ZWD734EY.js.map → chunk-D6KQKMU3.js.map} +0 -0
- /package/dist/{chunk-GDW4YP7K.js.map → chunk-FAZR34CA.js.map} +0 -0
- /package/dist/{chunk-TNCKOL2O.js.map → chunk-GTINVM5K.js.map} +0 -0
- /package/dist/{chunk-UET462HB.js.map → chunk-I4NNYWIR.js.map} +0 -0
- /package/dist/{chunk-EDNJLUSE.js.map → chunk-IHZEFW4L.js.map} +0 -0
- /package/dist/{chunk-ZWOFQRIB.js.map → chunk-JCVIOLBF.js.map} +0 -0
- /package/dist/{chunk-JQEFP45L.js.map → chunk-L5EH2NVO.js.map} +0 -0
- /package/dist/{chunk-NS55SM7A.js.map → chunk-O2PGQNYE.js.map} +0 -0
- /package/dist/{chunk-C7FQBSON.js.map → chunk-VNFJPTU4.js.map} +0 -0
- /package/dist/{chunk-DOGPOY4B.js.map → chunk-WB5LW52Z.js.map} +0 -0
- /package/dist/{chunks-SX77P5CV.js.map → chunks-I7TEMJXH.js.map} +0 -0
- /package/dist/{formula-5S7WWXGH.js.map → formula-NOIDZEBA.js.map} +0 -0
- /package/dist/{formula-JGFTXWYT.js.map → formula-X44GV7TN.js.map} +0 -0
- /package/dist/{model-bundle-FSPZZ2EM.js.map → model-bundle-57CLEBCO.js.map} +0 -0
- /package/dist/{models-CJPAQK5R.js.map → models-7ELIFTD7.js.map} +0 -0
- /package/dist/{pdf-ocr-76SAUXMZ.js.map → pdf-ocr-BNC34MNU.js.map} +0 -0
- /package/dist/{pdf-ocr-CPU55CVF.js.map → pdf-ocr-QX2GB33Q.js.map} +0 -0
- /package/dist/{profile-io-LBM263K7.js.map → profile-io-V52PL5ZO.js.map} +0 -0
- /package/dist/{rasterize-46RG4ZF6.js.map → rasterize-ZIGLJDOG.js.map} +0 -0
- /package/dist/{redact-7ILEAUTS.js.map → redact-TIQEQI3M.js.map} +0 -0
- /package/dist/{render-NGP5T24S.js.map → render-U5PE6V2E.js.map} +0 -0
- /package/dist/{seal-RGUPOLXD.js.map → seal-DNXJBILR.js.map} +0 -0
- /package/dist/{setup-AU4OCHNH.js.map → setup-YRDM4ODP.js.map} +0 -0
- /package/dist/{visual-NZLYIYEM.js.map → visual-5XWMA4EV.js.map} +0 -0
- /package/dist/{watch-MKPUCPXM.js.map → watch-5IOLBATT.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -5,11 +5,14 @@ import {
|
|
|
5
5
|
buildTable,
|
|
6
6
|
convertTableToText,
|
|
7
7
|
dedupeRunningHeaders,
|
|
8
|
+
escapeGfm,
|
|
9
|
+
escapeLiteralDollar,
|
|
8
10
|
flattenLayoutTables,
|
|
9
11
|
hasStructuredCellContent,
|
|
10
12
|
inlineImagesIntoMarkdown,
|
|
11
|
-
mapPuaText
|
|
12
|
-
|
|
13
|
+
mapPuaText,
|
|
14
|
+
noteSuffix
|
|
15
|
+
} from "./chunk-VYOGXNQ3.js";
|
|
13
16
|
import {
|
|
14
17
|
parsePageRange
|
|
15
18
|
} from "./chunk-GE43BE46.js";
|
|
@@ -17,7 +20,7 @@ import {
|
|
|
17
20
|
HEADING_RATIO_H1,
|
|
18
21
|
HEADING_RATIO_H2,
|
|
19
22
|
HEADING_RATIO_H3
|
|
20
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-2LQGSDZ6.js";
|
|
21
24
|
import {
|
|
22
25
|
KordocError,
|
|
23
26
|
VERSION,
|
|
@@ -30,7 +33,7 @@ import {
|
|
|
30
33
|
sanitizeHref,
|
|
31
34
|
stripDtd,
|
|
32
35
|
toArrayBuffer
|
|
33
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-L5EH2NVO.js";
|
|
34
37
|
|
|
35
38
|
// src/index.ts
|
|
36
39
|
import { readFile as readFile3 } from "fs/promises";
|
|
@@ -1292,7 +1295,7 @@ function replaceBracket(strList) {
|
|
|
1292
1295
|
}
|
|
1293
1296
|
function hmlToLatex(hmlEqStr) {
|
|
1294
1297
|
if (!hmlEqStr) return "";
|
|
1295
|
-
let s = hmlEqStr.replace(/`/g, " ");
|
|
1298
|
+
let s = hmlEqStr.replace(/[\r\n\t]+/g, " ").replace(/`/g, " ");
|
|
1296
1299
|
s = s.replace(/\{/g, " { ").replace(/\}/g, " } ").replace(/&/g, " & ");
|
|
1297
1300
|
let tokens = s.split(" ");
|
|
1298
1301
|
for (let i = 0; i < tokens.length; i++) {
|
|
@@ -1342,9 +1345,9 @@ function hangulAmount(n) {
|
|
|
1342
1345
|
const groups = [];
|
|
1343
1346
|
for (let end = digits.length; end > 0; end -= 4) groups.unshift(digits.slice(Math.max(0, end - 4), end));
|
|
1344
1347
|
let out = "";
|
|
1345
|
-
groups.forEach((
|
|
1348
|
+
groups.forEach((g2, i) => {
|
|
1346
1349
|
let part = "";
|
|
1347
|
-
const padded =
|
|
1350
|
+
const padded = g2.padStart(4, "0");
|
|
1348
1351
|
for (let k = 0; k < 4; k++) {
|
|
1349
1352
|
const d = Number(padded[k]);
|
|
1350
1353
|
if (d) part += AMOUNT_DIGITS[d] + AMOUNT_SMALL[3 - k];
|
|
@@ -1477,7 +1480,7 @@ function buildTableWithCellMeta(state, keepAnchoredEmptyCols, keepEmptyParagraph
|
|
|
1477
1480
|
for (const row of state.rows) {
|
|
1478
1481
|
for (const src of row) {
|
|
1479
1482
|
flatIdx++;
|
|
1480
|
-
const needsBlocks = !!src.blocks && src.blocks.length > 0 && (src.hasStructure || src.blocks.some((b) => b.spans));
|
|
1483
|
+
const needsBlocks = !!src.blocks && src.blocks.length > 0 && (src.hasStructure || src.blocks.some((b) => b.spans || b.footnoteText));
|
|
1481
1484
|
if (!needsBlocks && !src.isHeader) continue;
|
|
1482
1485
|
let target;
|
|
1483
1486
|
const trimmed = keepEmptyParagraphs ? src.text : src.text.trim();
|
|
@@ -1509,7 +1512,7 @@ function buildTableWithCellMeta(state, keepAnchoredEmptyCols, keepEmptyParagraph
|
|
|
1509
1512
|
}
|
|
1510
1513
|
function completeTable(newTable, tableStack, blocks, ctx) {
|
|
1511
1514
|
const parentTable = tableStack.length > 0 ? tableStack.pop() : null;
|
|
1512
|
-
if (newTable.rows.length === 0) {
|
|
1515
|
+
if (newTable.rows.every((r) => r.length === 0)) {
|
|
1513
1516
|
if (newTable.caption) blocks.push({ type: "paragraph", text: newTable.caption, pageNumber: ctx.page });
|
|
1514
1517
|
return parentTable;
|
|
1515
1518
|
}
|
|
@@ -1659,6 +1662,162 @@ function detectHwpxSectionPages(root) {
|
|
|
1659
1662
|
};
|
|
1660
1663
|
}
|
|
1661
1664
|
|
|
1665
|
+
// src/hwpx/notes.ts
|
|
1666
|
+
function codePointAttr(v) {
|
|
1667
|
+
if (v == null || v === "") return void 0;
|
|
1668
|
+
const n = parseInt(v, 10);
|
|
1669
|
+
if (!Number.isFinite(n) || n < 0 || n > 1114111) return void 0;
|
|
1670
|
+
return n === 0 ? "" : String.fromCodePoint(n);
|
|
1671
|
+
}
|
|
1672
|
+
function noteFormatFrom(get) {
|
|
1673
|
+
return {
|
|
1674
|
+
type: get("type") || "DIGIT",
|
|
1675
|
+
userChar: get("userChar") ?? "",
|
|
1676
|
+
prefixChar: get("prefixChar") ?? "",
|
|
1677
|
+
suffixChar: get("suffixChar") ?? ""
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
function noteCore(num4, fmt, userChar) {
|
|
1681
|
+
if (fmt.type === "USER_CHAR") return userChar ?? fmt.userChar;
|
|
1682
|
+
return formatHeadNumber(num4, fmt.type);
|
|
1683
|
+
}
|
|
1684
|
+
function noteRefMark(attrs, fmt) {
|
|
1685
|
+
const f = fmt ?? { type: "DIGIT", userChar: "", prefixChar: "", suffixChar: ")" };
|
|
1686
|
+
const n = parseInt(attrs.number ?? "", 10);
|
|
1687
|
+
const prefix = codePointAttr(attrs.prefixChar) ?? f.prefixChar;
|
|
1688
|
+
const suffix = codePointAttr(attrs.suffixChar) ?? f.suffixChar;
|
|
1689
|
+
return prefix + noteCore(Number.isFinite(n) ? n : 1, f, codePointAttr(attrs.userChar)) + suffix;
|
|
1690
|
+
}
|
|
1691
|
+
function noteAutoNumText(num4, fmt) {
|
|
1692
|
+
const n = parseInt(num4 ?? "", 10);
|
|
1693
|
+
return fmt.prefixChar + noteCore(Number.isFinite(n) ? n : 1, fmt) + fmt.suffixChar;
|
|
1694
|
+
}
|
|
1695
|
+
function isRenderedAutoNum(numType) {
|
|
1696
|
+
return numType === "FOOTNOTE" || numType === "ENDNOTE" || numType === "PICTURE" || numType === "TABLE" || numType === "EQUATION";
|
|
1697
|
+
}
|
|
1698
|
+
function noteAttrsOf(el) {
|
|
1699
|
+
return {
|
|
1700
|
+
number: el.getAttribute("number"),
|
|
1701
|
+
prefixChar: el.getAttribute("prefixChar"),
|
|
1702
|
+
suffixChar: el.getAttribute("suffixChar"),
|
|
1703
|
+
userChar: el.getAttribute("userChar")
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
function readSectionNoteFormats(root) {
|
|
1707
|
+
const out = {};
|
|
1708
|
+
const pick2 = (names) => {
|
|
1709
|
+
for (const name of names) {
|
|
1710
|
+
const els = root.getElementsByTagName(name);
|
|
1711
|
+
if (els.length === 0) continue;
|
|
1712
|
+
const f = findChildByLocalName(els[0], "autoNumFormat");
|
|
1713
|
+
return f ? noteFormatFrom((n) => f.getAttribute(n)) : void 0;
|
|
1714
|
+
}
|
|
1715
|
+
return void 0;
|
|
1716
|
+
};
|
|
1717
|
+
out.footnote = pick2(["hp:footNotePr", "footNotePr"]);
|
|
1718
|
+
out.endnote = pick2(["hp:endNotePr", "endNotePr"]);
|
|
1719
|
+
return out;
|
|
1720
|
+
}
|
|
1721
|
+
function noteAutoNumOf(el) {
|
|
1722
|
+
if (!isRenderedAutoNum(el.getAttribute("numType"))) return "";
|
|
1723
|
+
const f = findChildByLocalName(el, "autoNumFormat");
|
|
1724
|
+
return noteAutoNumText(el.getAttribute("num"), noteFormatFrom((n) => f?.getAttribute(n)));
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// src/hwpx/run-spans.ts
|
|
1728
|
+
var KORDOC_CHAR_CODE = "4";
|
|
1729
|
+
var KORDOC_PARA_QUOTE = "6";
|
|
1730
|
+
function spanModeOf(layout) {
|
|
1731
|
+
if (layout === "default") return "kordoc";
|
|
1732
|
+
if (layout === "gongmun") return "gongmun";
|
|
1733
|
+
if (!layout) return "foreign";
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
function extractRunSpans(para2, ctx, mode, requireMixed) {
|
|
1737
|
+
const styleMap = ctx.styleMap;
|
|
1738
|
+
if (!styleMap) return null;
|
|
1739
|
+
const spans = [];
|
|
1740
|
+
let styled = false;
|
|
1741
|
+
const kids = para2.childNodes;
|
|
1742
|
+
if (!kids) return null;
|
|
1743
|
+
for (let i = 0; i < kids.length; i++) {
|
|
1744
|
+
const child = kids[i];
|
|
1745
|
+
if (child.nodeType !== 1) continue;
|
|
1746
|
+
const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
|
|
1747
|
+
if (tag === "linesegarray") continue;
|
|
1748
|
+
if (tag !== "run" && tag !== "r") return null;
|
|
1749
|
+
let text = "";
|
|
1750
|
+
const rkids = child.childNodes;
|
|
1751
|
+
for (let j = 0; j < (rkids?.length ?? 0); j++) {
|
|
1752
|
+
const rc = rkids[j];
|
|
1753
|
+
if (rc.nodeType !== 1) continue;
|
|
1754
|
+
const rtag = (rc.tagName || rc.localName || "").replace(/^[^:]+:/, "");
|
|
1755
|
+
if (rtag === "t") {
|
|
1756
|
+
const tkids = rc.childNodes;
|
|
1757
|
+
for (let k = 0; k < (tkids?.length ?? 0); k++) {
|
|
1758
|
+
const tk = tkids[k];
|
|
1759
|
+
if (tk.nodeType === 3 || tk.nodeType === 4) text += escapeLiteralDollar(tk.textContent || "");
|
|
1760
|
+
else if (tk.nodeType === 1) {
|
|
1761
|
+
const ttag = (tk.tagName || tk.localName || "").replace(/^[^:]+:/, "");
|
|
1762
|
+
if (ttag === "tab" || ttag === "fwSpace" || ttag === "hwSpace" || ttag === "nbSpace") text += " ";
|
|
1763
|
+
else return null;
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
} else if (rtag === "secPr" || rtag === "colPr") {
|
|
1767
|
+
} else if (rtag === "ctrl") {
|
|
1768
|
+
if (extractTextFromNode(rc)) return null;
|
|
1769
|
+
} else {
|
|
1770
|
+
return null;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
if (!text) continue;
|
|
1774
|
+
const prId = child.getAttribute("charPrIDRef") ?? "";
|
|
1775
|
+
const cp = styleMap.charProperties.get(prId);
|
|
1776
|
+
const span = { text };
|
|
1777
|
+
if (mode !== "foreign" && prId === KORDOC_CHAR_CODE) span.code = true;
|
|
1778
|
+
else {
|
|
1779
|
+
if (cp?.bold) span.bold = true;
|
|
1780
|
+
if (cp?.italic) span.italic = true;
|
|
1781
|
+
if (cp?.strike) span.strike = true;
|
|
1782
|
+
if (cp?.underline) span.underline = true;
|
|
1783
|
+
}
|
|
1784
|
+
if (span.bold || span.italic || span.strike || span.underline || span.code) styled = true;
|
|
1785
|
+
spans.push(span);
|
|
1786
|
+
}
|
|
1787
|
+
if (!styled || spans.length === 0) return null;
|
|
1788
|
+
const merged = [];
|
|
1789
|
+
for (const s of spans) {
|
|
1790
|
+
const last = merged[merged.length - 1];
|
|
1791
|
+
if (last && !!last.bold === !!s.bold && !!last.italic === !!s.italic && !!last.strike === !!s.strike && !!last.underline === !!s.underline && !!last.code === !!s.code) {
|
|
1792
|
+
last.text += s.text;
|
|
1793
|
+
} else {
|
|
1794
|
+
merged.push(s);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
if (requireMixed && !merged.some((s) => s.strike || s.underline) && !merged.some((s) => !(s.bold || s.italic || s.code))) return null;
|
|
1798
|
+
return merged;
|
|
1799
|
+
}
|
|
1800
|
+
function gongmunDepthFromIndent(para2, left, ctx) {
|
|
1801
|
+
const styleMap = ctx.styleMap;
|
|
1802
|
+
if (!styleMap) return null;
|
|
1803
|
+
let unit = 0;
|
|
1804
|
+
const kids = para2.childNodes;
|
|
1805
|
+
for (let i = 0; i < (kids?.length ?? 0); i++) {
|
|
1806
|
+
const child = kids[i];
|
|
1807
|
+
if (child.nodeType !== 1) continue;
|
|
1808
|
+
const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
|
|
1809
|
+
if (tag !== "run" && tag !== "r") continue;
|
|
1810
|
+
const size = styleMap.charProperties.get(child.getAttribute("charPrIDRef") ?? "")?.fontSize;
|
|
1811
|
+
if (size) unit = size * 100;
|
|
1812
|
+
break;
|
|
1813
|
+
}
|
|
1814
|
+
if (!unit) return null;
|
|
1815
|
+
const r = left / unit;
|
|
1816
|
+
const nearInt = Math.round(r);
|
|
1817
|
+
const depth = Math.abs(r - nearInt) < 0.2 ? nearInt : Math.round(2 * r - 1);
|
|
1818
|
+
return depth >= 1 && depth < 8 ? depth : null;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1662
1821
|
// src/hwpx/section-walker.ts
|
|
1663
1822
|
function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
|
|
1664
1823
|
const parser = createXmlParser(warnings);
|
|
@@ -1666,13 +1825,13 @@ function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
|
|
|
1666
1825
|
if (!doc.documentElement) return [];
|
|
1667
1826
|
const ctx = { styleMap, warnings, sectionNum, shared: shared ?? createSectionShared() };
|
|
1668
1827
|
ctx.shared.track.deleteDepth = 0;
|
|
1669
|
-
const
|
|
1828
|
+
const detect2 = detectHwpxSectionPages(doc.documentElement);
|
|
1670
1829
|
const ps = ctx.shared.pageState;
|
|
1671
|
-
if (!
|
|
1672
|
-
ctx.paraPage =
|
|
1830
|
+
if (!detect2.usable) ps.allUsable = false;
|
|
1831
|
+
ctx.paraPage = detect2.paraPage;
|
|
1673
1832
|
ctx.pageBase = ps.base;
|
|
1674
1833
|
ctx.page = ps.base + 1;
|
|
1675
|
-
ps.base +=
|
|
1834
|
+
ps.base += detect2.pages;
|
|
1676
1835
|
for (const tagName of ["hp:secPr", "secPr"]) {
|
|
1677
1836
|
const els = doc.getElementsByTagName(tagName);
|
|
1678
1837
|
if (els.length > 0) {
|
|
@@ -1681,6 +1840,7 @@ function parseSectionXml(xml, styleMap, warnings, sectionNum, shared) {
|
|
|
1681
1840
|
break;
|
|
1682
1841
|
}
|
|
1683
1842
|
}
|
|
1843
|
+
ctx.noteFormats = readSectionNoteFormats(doc.documentElement);
|
|
1684
1844
|
const blocks = [];
|
|
1685
1845
|
walkSection(doc.documentElement, blocks, null, [], ctx);
|
|
1686
1846
|
return blocks;
|
|
@@ -1747,7 +1907,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
|
|
|
1747
1907
|
if (tableCtx) {
|
|
1748
1908
|
tableCtx.currentRow = [];
|
|
1749
1909
|
walkSection(el, blocks, tableCtx, tableStack, ctx, depth + 1);
|
|
1750
|
-
|
|
1910
|
+
tableCtx.rows.push(tableCtx.currentRow);
|
|
1751
1911
|
tableCtx.currentRow = [];
|
|
1752
1912
|
}
|
|
1753
1913
|
break;
|
|
@@ -1783,7 +1943,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
|
|
|
1783
1943
|
case "p": {
|
|
1784
1944
|
const paraPg = ctx.paraPage?.get(el);
|
|
1785
1945
|
if (paraPg !== void 0 && ctx.pageBase !== void 0) ctx.page = ctx.pageBase + paraPg + 1;
|
|
1786
|
-
const { text: rawText, href, footnote, style, segments } = extractParagraphInfo(el, ctx.styleMap, ctx);
|
|
1946
|
+
const { text: rawText, href, footnote, style, segments, placeholderSpans } = extractParagraphInfo(el, ctx.styleMap, ctx);
|
|
1787
1947
|
let text = rawText;
|
|
1788
1948
|
let headingLevel;
|
|
1789
1949
|
const ph = resolveParaHeading(el, ctx);
|
|
@@ -1801,10 +1961,12 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
|
|
|
1801
1961
|
}
|
|
1802
1962
|
let segIdx = 0;
|
|
1803
1963
|
let first = true;
|
|
1964
|
+
let lastCellBlock;
|
|
1804
1965
|
const flush = () => {
|
|
1805
1966
|
const s = segs[segIdx++];
|
|
1806
1967
|
if (!s) return;
|
|
1807
1968
|
const block = { type: "paragraph", text: s, pageNumber: ctx.page };
|
|
1969
|
+
if (cell2) lastCellBlock = block;
|
|
1808
1970
|
if (first && !cell2) {
|
|
1809
1971
|
first = false;
|
|
1810
1972
|
if (style) block.style = style;
|
|
@@ -1819,16 +1981,19 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
|
|
|
1819
1981
|
};
|
|
1820
1982
|
tableCtx = walkParagraphChildren(el, blocks, tableCtx, tableStack, ctx, depth + 1, flush);
|
|
1821
1983
|
while (segIdx < segs.length) flush();
|
|
1822
|
-
if (footnote && cell2)
|
|
1984
|
+
if (footnote && cell2) {
|
|
1985
|
+
cell2.text += (cell2.text ? "\n" : "") + `(\uC8FC: ${footnote})`;
|
|
1986
|
+
if (lastCellBlock) lastCellBlock.footnoteText = footnote;
|
|
1987
|
+
}
|
|
1823
1988
|
break;
|
|
1824
1989
|
}
|
|
1825
1990
|
if (text) {
|
|
1826
1991
|
if (tableCtx?.cell) {
|
|
1827
1992
|
const cell2 = tableCtx.cell;
|
|
1828
|
-
|
|
1829
|
-
cell2.text += (cell2.text || cell2.paraSeen ? "\n" : "") + text;
|
|
1993
|
+
cell2.text += (cell2.text || cell2.paraSeen ? "\n" : "") + (footnote ? `${text} (\uC8FC: ${footnote})` : text);
|
|
1830
1994
|
cell2.paraSeen = true;
|
|
1831
1995
|
const cellBlock = { type: "paragraph", text, pageNumber: ctx.page };
|
|
1996
|
+
if (footnote) cellBlock.footnoteText = footnote;
|
|
1832
1997
|
const cellSpanMode = spanModeOf(ctx.shared.kordocLayout);
|
|
1833
1998
|
if (cellSpanMode && !ph?.prefix) {
|
|
1834
1999
|
const spans = extractRunSpans(el, ctx, cellSpanMode, cellSpanMode !== "kordoc");
|
|
@@ -1836,7 +2001,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
|
|
|
1836
2001
|
cellBlock.spans = spans;
|
|
1837
2002
|
}
|
|
1838
2003
|
}
|
|
1839
|
-
;
|
|
2004
|
+
if (!cellBlock.spans && placeholderSpans) cellBlock.spans = withPrefixSpan(placeholderSpans, ph?.prefix);
|
|
1840
2005
|
(cell2.blocks ??= []).push(cellBlock);
|
|
1841
2006
|
} else if (!tableCtx) {
|
|
1842
2007
|
if (ctx.shared.kordocLayout && /^─{10,}$/.test(text)) {
|
|
@@ -1868,6 +2033,7 @@ function walkSection(node, blocks, tableCtx, tableStack, ctx, depth = 0) {
|
|
|
1868
2033
|
}
|
|
1869
2034
|
if (spanMode !== "foreign" && el.getAttribute("paraPrIDRef") === KORDOC_PARA_QUOTE) block.quote = true;
|
|
1870
2035
|
}
|
|
2036
|
+
if (!headingLevel && !block.spans && placeholderSpans) block.spans = withPrefixSpan(placeholderSpans, ph?.prefix);
|
|
1871
2037
|
blocks.push(block);
|
|
1872
2038
|
} else {
|
|
1873
2039
|
blocks.push({ type: "paragraph", text, pageNumber: ctx.page });
|
|
@@ -1947,7 +2113,7 @@ function userShapeComment(el) {
|
|
|
1947
2113
|
function mergeBlocksIntoCell(cell2, sink) {
|
|
1948
2114
|
for (const b of sink) {
|
|
1949
2115
|
if ((b.type === "paragraph" || b.type === "heading") && b.text) {
|
|
1950
|
-
cell2.text += (cell2.text ? "\n" : "") + b.text;
|
|
2116
|
+
cell2.text += (cell2.text ? "\n" : "") + b.text + (b.footnoteText ? ` (\uC8FC: ${b.footnoteText})` : "");
|
|
1951
2117
|
(cell2.blocks ??= []).push(b);
|
|
1952
2118
|
} else if (b.type === "image" || b.type === "table") {
|
|
1953
2119
|
if (b.type === "image" && b.text) {
|
|
@@ -2003,7 +2169,7 @@ function hasPageAutoNum(el, depth = 0) {
|
|
|
2003
2169
|
function collectSubListText(el, ctx, depth = 0) {
|
|
2004
2170
|
return collectSubListContent(el, ctx, depth).text;
|
|
2005
2171
|
}
|
|
2006
|
-
function collectSubListContent(el, ctx, depth = 0) {
|
|
2172
|
+
function collectSubListContent(el, ctx, depth = 0, sep = "\n") {
|
|
2007
2173
|
const out = { text: "", blocks: [], hasStructure: false };
|
|
2008
2174
|
if (depth > 10) return out;
|
|
2009
2175
|
const parts = [];
|
|
@@ -2014,7 +2180,9 @@ function collectSubListContent(el, ctx, depth = 0) {
|
|
|
2014
2180
|
if (ch.nodeType !== 1) continue;
|
|
2015
2181
|
const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
|
|
2016
2182
|
if (tag === "p" || tag === "para") {
|
|
2017
|
-
|
|
2183
|
+
let t = extractParagraphInfo(ch, ctx.styleMap, ctx).text;
|
|
2184
|
+
const ph = resolveParaHeading(ch, ctx);
|
|
2185
|
+
if (t && ph?.prefix) t = ph.prefix + " " + t;
|
|
2018
2186
|
if (t) {
|
|
2019
2187
|
parts.push(t);
|
|
2020
2188
|
out.blocks.push({ type: "paragraph", text: t, pageNumber: ctx.page });
|
|
@@ -2029,6 +2197,10 @@ function collectSubListContent(el, ctx, depth = 0) {
|
|
|
2029
2197
|
out.hasStructure = true;
|
|
2030
2198
|
}
|
|
2031
2199
|
}
|
|
2200
|
+
for (const t2 of drawTextParaTexts(ch, ctx)) {
|
|
2201
|
+
parts.push(t2);
|
|
2202
|
+
out.blocks.push({ type: "paragraph", text: t2, pageNumber: ctx.page });
|
|
2203
|
+
}
|
|
2032
2204
|
} else if (tag === "tbl") {
|
|
2033
2205
|
const built = buildSubListTable(ch, ctx, depth);
|
|
2034
2206
|
if (built.text) parts.push(built.text);
|
|
@@ -2037,13 +2209,41 @@ function collectSubListContent(el, ctx, depth = 0) {
|
|
|
2037
2209
|
out.hasStructure = true;
|
|
2038
2210
|
}
|
|
2039
2211
|
} else {
|
|
2040
|
-
const sub = collectSubListContent(ch, ctx, depth + 1);
|
|
2212
|
+
const sub = collectSubListContent(ch, ctx, depth + 1, sep);
|
|
2041
2213
|
if (sub.text) parts.push(sub.text);
|
|
2042
2214
|
out.blocks.push(...sub.blocks);
|
|
2043
2215
|
if (sub.hasStructure) out.hasStructure = true;
|
|
2044
2216
|
}
|
|
2045
2217
|
}
|
|
2046
|
-
out.text = parts.join(
|
|
2218
|
+
out.text = parts.join(sep).trim();
|
|
2219
|
+
return out;
|
|
2220
|
+
}
|
|
2221
|
+
function drawTextParaTexts(para2, ctx) {
|
|
2222
|
+
const out = [];
|
|
2223
|
+
const visit = (node, depth, inDrawText) => {
|
|
2224
|
+
if (depth > MAX_XML_DEPTH) return;
|
|
2225
|
+
const kids = node.childNodes;
|
|
2226
|
+
if (!kids) return;
|
|
2227
|
+
for (let i = 0; i < kids.length; i++) {
|
|
2228
|
+
const ch = kids[i];
|
|
2229
|
+
if (ch.nodeType !== 1) continue;
|
|
2230
|
+
const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
|
|
2231
|
+
if (tag === "tbl" || tag === "footNote" || tag === "endNote") continue;
|
|
2232
|
+
if (tag === "drawText") {
|
|
2233
|
+
if (!inDrawText) visit(ch, depth + 1, true);
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
if (inDrawText && (tag === "p" || tag === "para")) {
|
|
2237
|
+
let t = extractParagraphInfo(ch, ctx.styleMap, ctx).text.trim();
|
|
2238
|
+
const ph = resolveParaHeading(ch, ctx);
|
|
2239
|
+
if (t && ph?.prefix) t = ph.prefix + " " + t;
|
|
2240
|
+
if (t) out.push(t);
|
|
2241
|
+
continue;
|
|
2242
|
+
}
|
|
2243
|
+
visit(ch, depth + 1, inDrawText);
|
|
2244
|
+
}
|
|
2245
|
+
};
|
|
2246
|
+
visit(para2, 0, false);
|
|
2047
2247
|
return out;
|
|
2048
2248
|
}
|
|
2049
2249
|
function buildSubListTable(el, ctx, depth) {
|
|
@@ -2191,6 +2391,29 @@ function extractDrawTextBlocks(drawTextNode, blocks, ctx) {
|
|
|
2191
2391
|
}
|
|
2192
2392
|
}
|
|
2193
2393
|
}
|
|
2394
|
+
var PH_OPEN = "";
|
|
2395
|
+
var PH_CLOSE = "";
|
|
2396
|
+
function clickHereGuide(fieldBegin) {
|
|
2397
|
+
if ((fieldBegin.getAttribute("type") || "").toUpperCase() !== "CLICK_HERE") return void 0;
|
|
2398
|
+
if (fieldBegin.getAttribute("dirty") === "1") return void 0;
|
|
2399
|
+
const children = findChildByLocalName(fieldBegin, "parameters")?.childNodes;
|
|
2400
|
+
if (!children) return void 0;
|
|
2401
|
+
let fromCommand;
|
|
2402
|
+
for (let i = 0; i < children.length; i++) {
|
|
2403
|
+
const ch = children[i];
|
|
2404
|
+
if (ch.nodeType !== 1) continue;
|
|
2405
|
+
const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
|
|
2406
|
+
if (tag !== "stringParam") continue;
|
|
2407
|
+
const name = ch.getAttribute("name");
|
|
2408
|
+
if (name === "Direction") return ch.textContent || void 0;
|
|
2409
|
+
if (name === "Command") {
|
|
2410
|
+
const cmd = ch.textContent || "";
|
|
2411
|
+
const m = /Direction:wstring:(\d+):/.exec(cmd);
|
|
2412
|
+
if (m) fromCommand = cmd.slice(m.index + m[0].length, m.index + m[0].length + Number(m[1])) || void 0;
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
return fromCommand;
|
|
2416
|
+
}
|
|
2194
2417
|
function extractHyperlinkHref(fieldBegin) {
|
|
2195
2418
|
if ((fieldBegin.getAttribute("type") || "").toUpperCase() !== "HYPERLINK") return void 0;
|
|
2196
2419
|
const params = findChildByLocalName(fieldBegin, "parameters");
|
|
@@ -2227,12 +2450,26 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2227
2450
|
openFields.push({ rangeIdx: linkRanges.length - 1 });
|
|
2228
2451
|
if (!href) href = url;
|
|
2229
2452
|
} else {
|
|
2230
|
-
|
|
2453
|
+
const guide = clickHereGuide(el);
|
|
2454
|
+
openFields.push(guide ? { guide, start: text.length } : {});
|
|
2231
2455
|
}
|
|
2232
2456
|
};
|
|
2233
2457
|
const onFieldEnd = () => {
|
|
2234
2458
|
const open = openFields.pop();
|
|
2235
2459
|
if (open?.rangeIdx !== void 0) linkRanges[open.rangeIdx].end = text.length;
|
|
2460
|
+
if (open?.guide !== void 0 && open.start !== void 0) {
|
|
2461
|
+
const value = text.slice(open.start).replace(/\\\$/g, "$");
|
|
2462
|
+
if (value && (value === open.guide || value.trimEnd() === open.guide)) {
|
|
2463
|
+
text = text.slice(0, open.start) + PH_OPEN + text.slice(open.start) + PH_CLOSE;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
};
|
|
2467
|
+
const addNote = (noteEl, tag) => {
|
|
2468
|
+
if (isInDeletedRange(ctx)) return;
|
|
2469
|
+
const endnote = tag === "endNote" || tag === "en";
|
|
2470
|
+
text += noteRefMark(noteAttrsOf(noteEl), endnote ? ctx?.noteFormats?.endnote : ctx?.noteFormats?.footnote);
|
|
2471
|
+
const noteText = ctx ? collectSubListContent(noteEl, ctx, 0, " ").text : extractTextFromNode(noteEl);
|
|
2472
|
+
if (noteText) footnote = (footnote ? footnote + "; " : "") + noteText;
|
|
2236
2473
|
};
|
|
2237
2474
|
const handleCtrl = (ctrlEl) => {
|
|
2238
2475
|
const kids = ctrlEl.childNodes;
|
|
@@ -2256,13 +2493,11 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2256
2493
|
}
|
|
2257
2494
|
break;
|
|
2258
2495
|
}
|
|
2259
|
-
// 각주/미주 — 해당 문단의 footnote로 인라인 보존
|
|
2496
|
+
// 각주/미주 — 개체 자리에 참조 부호, 본문은 해당 문단의 footnote로 인라인 보존
|
|
2260
2497
|
case "footNote":
|
|
2261
|
-
case "endNote":
|
|
2262
|
-
|
|
2263
|
-
if (noteText) footnote = (footnote ? footnote + "; " : "") + noteText;
|
|
2498
|
+
case "endNote":
|
|
2499
|
+
addNote(k, ktag);
|
|
2264
2500
|
break;
|
|
2265
|
-
}
|
|
2266
2501
|
// 하이퍼링크 — fieldBegin type=HYPERLINK의 Path 파라미터 (extent 오프셋 추적)
|
|
2267
2502
|
case "fieldBegin":
|
|
2268
2503
|
onFieldBegin(k);
|
|
@@ -2288,13 +2523,16 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2288
2523
|
}
|
|
2289
2524
|
break;
|
|
2290
2525
|
}
|
|
2526
|
+
// 자동번호 — 주석 머리 번호("1)"·"문1)")·캡션 번호("<그림 1>")만 그린다. 쪽번호 등은 종전대로 미방출
|
|
2527
|
+
case "autoNum":
|
|
2528
|
+
if (!isInDeletedRange(ctx)) text += noteAutoNumOf(k);
|
|
2529
|
+
break;
|
|
2291
2530
|
// 콘텐츠 없는 제어 요소 — 스킵
|
|
2292
2531
|
case "bookmark":
|
|
2293
2532
|
case "pageNum":
|
|
2294
2533
|
case "pageNumCtrl":
|
|
2295
2534
|
case "pageHiding":
|
|
2296
2535
|
case "newNum":
|
|
2297
|
-
case "autoNum":
|
|
2298
2536
|
case "indexmark":
|
|
2299
2537
|
case "colPr":
|
|
2300
2538
|
break;
|
|
@@ -2324,7 +2562,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2324
2562
|
ctx.warnings?.push({ page: ctx.page, message: "\uBCC0\uACBD\uCD94\uC801 \uC0AD\uC81C \uD14D\uC2A4\uD2B8 \uCD9C\uB825 \uC81C\uC678", code: "HIDDEN_TEXT_FILTERED" });
|
|
2325
2563
|
}
|
|
2326
2564
|
} else {
|
|
2327
|
-
text += t.replace(/\x1E/g, "");
|
|
2565
|
+
text += escapeLiteralDollar(t.replace(/\x1E/g, ""));
|
|
2328
2566
|
}
|
|
2329
2567
|
continue;
|
|
2330
2568
|
}
|
|
@@ -2377,9 +2615,14 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2377
2615
|
case "footNote":
|
|
2378
2616
|
case "endNote":
|
|
2379
2617
|
case "fn":
|
|
2380
|
-
case "en":
|
|
2381
|
-
|
|
2382
|
-
|
|
2618
|
+
case "en":
|
|
2619
|
+
addNote(child, tag);
|
|
2620
|
+
break;
|
|
2621
|
+
// 대체 표현 묶음 — 한컴은 지원하는 hp:case 하나만 그린다(차트, 없으면 hp:default 의 OLE).
|
|
2622
|
+
// 둘 다 순회하면 같은 캡션이 두 번 나온다 (1790387 [그림 5] 이중 방출)
|
|
2623
|
+
case "switch": {
|
|
2624
|
+
const branch = findChildByLocalName(child, "case") ?? findChildByLocalName(child, "default");
|
|
2625
|
+
if (branch) walk(branch, depth + 1);
|
|
2383
2626
|
break;
|
|
2384
2627
|
}
|
|
2385
2628
|
// 제어 요소 — 선별 순회 (머리말/꼬리말/각주/하이퍼링크/변경추적, v3.0)
|
|
@@ -2431,7 +2674,7 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2431
2674
|
if (raw.trim()) {
|
|
2432
2675
|
try {
|
|
2433
2676
|
const latex = hmlToLatex(raw).trim();
|
|
2434
|
-
if (latex) text += " $" + latex + "$ ";
|
|
2677
|
+
if (latex) text += " $" + latex.replace(/\$/g, "\\$") + "$ ";
|
|
2435
2678
|
} catch {
|
|
2436
2679
|
if (ctx?.warnings) {
|
|
2437
2680
|
ctx.warnings.push({
|
|
@@ -2480,10 +2723,21 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2480
2723
|
};
|
|
2481
2724
|
let segments;
|
|
2482
2725
|
if (text.includes("")) {
|
|
2483
|
-
segments = text.split("").map(cleanParaText);
|
|
2726
|
+
segments = text.split("").map((s) => cleanParaText(stripPlaceholderMarks(s)));
|
|
2484
2727
|
text = text.replace(/\x1E/g, "");
|
|
2485
2728
|
}
|
|
2486
|
-
|
|
2729
|
+
let cleanText = cleanParaText(segments ? stripPlaceholderMarks(text) : text);
|
|
2730
|
+
let placeholderSpans;
|
|
2731
|
+
if (cleanText.includes(PH_OPEN)) {
|
|
2732
|
+
placeholderSpans = [];
|
|
2733
|
+
for (const part of cleanText.split(/(\x1C[^\x1C\x1D]*\x1D)/)) {
|
|
2734
|
+
if (!part) continue;
|
|
2735
|
+
if (part.startsWith(PH_OPEN)) placeholderSpans.push({ text: part.slice(1, -1), placeholder: true });
|
|
2736
|
+
else placeholderSpans.push({ text: stripPlaceholderMarks(part) });
|
|
2737
|
+
}
|
|
2738
|
+
cleanText = stripPlaceholderMarks(cleanText);
|
|
2739
|
+
if (!placeholderSpans.some((s) => s.placeholder && s.text)) placeholderSpans = void 0;
|
|
2740
|
+
}
|
|
2487
2741
|
let style;
|
|
2488
2742
|
if (styleMap && charPrId) {
|
|
2489
2743
|
const charProp = styleMap.charProperties.get(charPrId);
|
|
@@ -2496,99 +2750,13 @@ function extractParagraphInfo(para2, styleMap, ctx) {
|
|
|
2496
2750
|
if (!style.fontSize && !style.bold && !style.italic) style = void 0;
|
|
2497
2751
|
}
|
|
2498
2752
|
}
|
|
2499
|
-
return { text: cleanText, href, footnote, style, segments };
|
|
2753
|
+
return { text: cleanText, href, footnote, style, segments, placeholderSpans };
|
|
2500
2754
|
}
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
function spanModeOf(layout) {
|
|
2504
|
-
if (layout === "default") return "kordoc";
|
|
2505
|
-
if (layout === "gongmun") return "gongmun";
|
|
2506
|
-
if (!layout) return "foreign";
|
|
2507
|
-
return null;
|
|
2755
|
+
function withPrefixSpan(spans, prefix) {
|
|
2756
|
+
return prefix ? [{ text: prefix + " " }, ...spans] : spans;
|
|
2508
2757
|
}
|
|
2509
|
-
function
|
|
2510
|
-
|
|
2511
|
-
if (!styleMap) return null;
|
|
2512
|
-
const spans = [];
|
|
2513
|
-
let styled = false;
|
|
2514
|
-
const kids = para2.childNodes;
|
|
2515
|
-
if (!kids) return null;
|
|
2516
|
-
for (let i = 0; i < kids.length; i++) {
|
|
2517
|
-
const child = kids[i];
|
|
2518
|
-
if (child.nodeType !== 1) continue;
|
|
2519
|
-
const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
|
|
2520
|
-
if (tag === "linesegarray") continue;
|
|
2521
|
-
if (tag !== "run" && tag !== "r") return null;
|
|
2522
|
-
let text = "";
|
|
2523
|
-
const rkids = child.childNodes;
|
|
2524
|
-
for (let j = 0; j < (rkids?.length ?? 0); j++) {
|
|
2525
|
-
const rc = rkids[j];
|
|
2526
|
-
if (rc.nodeType !== 1) continue;
|
|
2527
|
-
const rtag = (rc.tagName || rc.localName || "").replace(/^[^:]+:/, "");
|
|
2528
|
-
if (rtag === "t") {
|
|
2529
|
-
const tkids = rc.childNodes;
|
|
2530
|
-
for (let k = 0; k < (tkids?.length ?? 0); k++) {
|
|
2531
|
-
const tk = tkids[k];
|
|
2532
|
-
if (tk.nodeType === 3 || tk.nodeType === 4) text += tk.textContent || "";
|
|
2533
|
-
else if (tk.nodeType === 1) {
|
|
2534
|
-
const ttag = (tk.tagName || tk.localName || "").replace(/^[^:]+:/, "");
|
|
2535
|
-
if (ttag === "tab" || ttag === "fwSpace" || ttag === "hwSpace" || ttag === "nbSpace") text += " ";
|
|
2536
|
-
else return null;
|
|
2537
|
-
}
|
|
2538
|
-
}
|
|
2539
|
-
} else if (rtag === "secPr" || rtag === "colPr") {
|
|
2540
|
-
} else if (rtag === "ctrl") {
|
|
2541
|
-
if (extractTextFromNode(rc)) return null;
|
|
2542
|
-
} else {
|
|
2543
|
-
return null;
|
|
2544
|
-
}
|
|
2545
|
-
}
|
|
2546
|
-
if (!text) continue;
|
|
2547
|
-
const prId = child.getAttribute("charPrIDRef") ?? "";
|
|
2548
|
-
const cp = styleMap.charProperties.get(prId);
|
|
2549
|
-
const span = { text };
|
|
2550
|
-
if (mode !== "foreign" && prId === KORDOC_CHAR_CODE) span.code = true;
|
|
2551
|
-
else {
|
|
2552
|
-
if (cp?.bold) span.bold = true;
|
|
2553
|
-
if (cp?.italic) span.italic = true;
|
|
2554
|
-
if (cp?.strike) span.strike = true;
|
|
2555
|
-
if (cp?.underline) span.underline = true;
|
|
2556
|
-
}
|
|
2557
|
-
if (span.bold || span.italic || span.strike || span.underline || span.code) styled = true;
|
|
2558
|
-
spans.push(span);
|
|
2559
|
-
}
|
|
2560
|
-
if (!styled || spans.length === 0) return null;
|
|
2561
|
-
const merged = [];
|
|
2562
|
-
for (const s of spans) {
|
|
2563
|
-
const last = merged[merged.length - 1];
|
|
2564
|
-
if (last && !!last.bold === !!s.bold && !!last.italic === !!s.italic && !!last.strike === !!s.strike && !!last.underline === !!s.underline && !!last.code === !!s.code) {
|
|
2565
|
-
last.text += s.text;
|
|
2566
|
-
} else {
|
|
2567
|
-
merged.push(s);
|
|
2568
|
-
}
|
|
2569
|
-
}
|
|
2570
|
-
if (requireMixed && !merged.some((s) => s.strike || s.underline) && !merged.some((s) => !(s.bold || s.italic || s.code))) return null;
|
|
2571
|
-
return merged;
|
|
2572
|
-
}
|
|
2573
|
-
function gongmunDepthFromIndent(para2, left, ctx) {
|
|
2574
|
-
const styleMap = ctx.styleMap;
|
|
2575
|
-
if (!styleMap) return null;
|
|
2576
|
-
let unit = 0;
|
|
2577
|
-
const kids = para2.childNodes;
|
|
2578
|
-
for (let i = 0; i < (kids?.length ?? 0); i++) {
|
|
2579
|
-
const child = kids[i];
|
|
2580
|
-
if (child.nodeType !== 1) continue;
|
|
2581
|
-
const tag = (child.tagName || child.localName || "").replace(/^[^:]+:/, "");
|
|
2582
|
-
if (tag !== "run" && tag !== "r") continue;
|
|
2583
|
-
const size = styleMap.charProperties.get(child.getAttribute("charPrIDRef") ?? "")?.fontSize;
|
|
2584
|
-
if (size) unit = size * 100;
|
|
2585
|
-
break;
|
|
2586
|
-
}
|
|
2587
|
-
if (!unit) return null;
|
|
2588
|
-
const r = left / unit;
|
|
2589
|
-
const nearInt = Math.round(r);
|
|
2590
|
-
const depth = Math.abs(r - nearInt) < 0.2 ? nearInt : Math.round(2 * r - 1);
|
|
2591
|
-
return depth >= 1 && depth < 8 ? depth : null;
|
|
2758
|
+
function stripPlaceholderMarks(s) {
|
|
2759
|
+
return s.includes(PH_OPEN) || s.includes(PH_CLOSE) ? s.replace(/[\x1C\x1D]/g, "") : s;
|
|
2592
2760
|
}
|
|
2593
2761
|
|
|
2594
2762
|
// src/hwp5/record.ts
|
|
@@ -2776,6 +2944,8 @@ function parseDocInfo(records) {
|
|
|
2776
2944
|
}
|
|
2777
2945
|
return { charShapes, paraShapes, styles, binData, numberings, bullets };
|
|
2778
2946
|
}
|
|
2947
|
+
var LEADER_TAB_MARK = "";
|
|
2948
|
+
var LITERAL_DOLLAR_MARK = "\uFDD0";
|
|
2779
2949
|
function createParaTextState() {
|
|
2780
2950
|
return { text: "", ctrlIdx: 0, fieldStack: [], fieldRanges: [] };
|
|
2781
2951
|
}
|
|
@@ -2829,8 +2999,9 @@ function appendParaText(state, data, resolveControl) {
|
|
|
2829
2999
|
break;
|
|
2830
3000
|
// 고정폭 공백
|
|
2831
3001
|
// ── inline 타입 (2바이트 + 14바이트 확장) ──
|
|
3002
|
+
// 확장 u16[7] 중 [2] 의 하위 바이트 = 채움 모양(0 없음·3 점선 …), 상위 = 탭 종류+1 (rhwp tab_extended 실측)
|
|
2832
3003
|
case CHAR_TAB:
|
|
2833
|
-
result += " ";
|
|
3004
|
+
result += state.leaderMark && i + 14 <= data.length && data[i + 4] !== 0 ? LEADER_TAB_MARK : " ";
|
|
2834
3005
|
if (i + 14 <= data.length) i += 14;
|
|
2835
3006
|
break;
|
|
2836
3007
|
default:
|
|
@@ -2863,7 +3034,7 @@ function appendParaText(state, data, resolveControl) {
|
|
|
2863
3034
|
break;
|
|
2864
3035
|
}
|
|
2865
3036
|
}
|
|
2866
|
-
result += String.fromCharCode(ch);
|
|
3037
|
+
result += ch === 36 && state.dollarMark ? LITERAL_DOLLAR_MARK : String.fromCharCode(ch);
|
|
2867
3038
|
}
|
|
2868
3039
|
break;
|
|
2869
3040
|
}
|
|
@@ -2888,9 +3059,21 @@ function detectImageMime(data) {
|
|
|
2888
3059
|
if (data[0] === 71 && data[1] === 73 && data[2] === 70) return "image/gif";
|
|
2889
3060
|
if (data[0] === 66 && data[1] === 77) return "image/bmp";
|
|
2890
3061
|
if (data[0] === 215 && data[1] === 205 && data[2] === 198 && data[3] === 154) return "image/wmf";
|
|
3062
|
+
if ((data[0] === 1 || data[0] === 2) && data[1] === 0 && data[2] === 9 && data[3] === 0) return "image/wmf";
|
|
2891
3063
|
if (data[0] === 1 && data[1] === 0 && data[2] === 0 && data[3] === 0) return "image/emf";
|
|
3064
|
+
if (data[0] === 73 && data[1] === 73 && data[2] === 42 && data[3] === 0 || data[0] === 77 && data[1] === 77 && data[2] === 0 && data[3] === 42) return "image/tiff";
|
|
2892
3065
|
return null;
|
|
2893
3066
|
}
|
|
3067
|
+
function mimeExt(mime) {
|
|
3068
|
+
if (mime.includes("jpeg")) return "jpg";
|
|
3069
|
+
if (mime.includes("png")) return "png";
|
|
3070
|
+
if (mime.includes("gif")) return "gif";
|
|
3071
|
+
if (mime.includes("bmp")) return "bmp";
|
|
3072
|
+
if (mime.includes("tiff")) return "tif";
|
|
3073
|
+
if (mime.includes("wmf")) return "wmf";
|
|
3074
|
+
if (mime.includes("emf")) return "emf";
|
|
3075
|
+
return "bin";
|
|
3076
|
+
}
|
|
2894
3077
|
function normalizeBinPayload(data) {
|
|
2895
3078
|
if (detectImageMime(data)) return data;
|
|
2896
3079
|
try {
|
|
@@ -2916,29 +3099,28 @@ function collectImageBlocks(blocks, out, depth = 0) {
|
|
|
2916
3099
|
if (b.children) collectImageBlocks(b.children, out, depth + 1);
|
|
2917
3100
|
}
|
|
2918
3101
|
}
|
|
2919
|
-
|
|
3102
|
+
var IMAGE_SENTINEL_RE = /!\[image\]\(hwp5bin:(\d+)\)/g;
|
|
3103
|
+
function resolveImageSentinels(blocks, renamed, depth = 0) {
|
|
3104
|
+
if (depth > MAX_BLOCK_DEPTH) return;
|
|
3105
|
+
const fix = (s) => s.includes("hwp5bin:") ? s.replace(IMAGE_SENTINEL_RE, (_m, idStr) => {
|
|
3106
|
+
const filename = renamed.get(Number(idStr));
|
|
3107
|
+
return filename ? `` : "[\uC774\uBBF8\uC9C0]";
|
|
3108
|
+
}) : s;
|
|
2920
3109
|
for (const b of blocks) {
|
|
3110
|
+
if (b.text) b.text = fix(b.text);
|
|
3111
|
+
if (b.footnoteText) b.footnoteText = fix(b.footnoteText);
|
|
2921
3112
|
if (b.table) {
|
|
3113
|
+
if (b.table.caption) b.table.caption = fix(b.table.caption);
|
|
2922
3114
|
for (const row of b.table.cells) {
|
|
2923
3115
|
for (const cell2 of row) {
|
|
2924
|
-
|
|
2925
|
-
if (cell2.blocks)
|
|
3116
|
+
cell2.text = fix(cell2.text);
|
|
3117
|
+
if (cell2.blocks) resolveImageSentinels(cell2.blocks, renamed, depth + 1);
|
|
2926
3118
|
}
|
|
2927
3119
|
}
|
|
2928
3120
|
}
|
|
2929
|
-
if (b.children)
|
|
3121
|
+
if (b.children) resolveImageSentinels(b.children, renamed, depth + 1);
|
|
2930
3122
|
}
|
|
2931
3123
|
}
|
|
2932
|
-
var CELL_IMAGE_SENTINEL_RE = /!\[image\]\(hwp5bin:(\d+)\)/g;
|
|
2933
|
-
function resolveCellImageSentinels(blocks, renamed) {
|
|
2934
|
-
forEachTableCell(blocks, (cell2) => {
|
|
2935
|
-
if (!cell2.text.includes("hwp5bin:")) return;
|
|
2936
|
-
cell2.text = cell2.text.replace(CELL_IMAGE_SENTINEL_RE, (_m, idStr) => {
|
|
2937
|
-
const filename = renamed.get(Number(idStr));
|
|
2938
|
-
return filename ? `` : "[\uC774\uBBF8\uC9C0]";
|
|
2939
|
-
});
|
|
2940
|
-
});
|
|
2941
|
-
}
|
|
2942
3124
|
function resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced) {
|
|
2943
3125
|
const imageBlocks = [];
|
|
2944
3126
|
collectImageBlocks(blocks, imageBlocks);
|
|
@@ -2963,7 +3145,7 @@ function resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced) {
|
|
|
2963
3145
|
resolved.set(storageId, null);
|
|
2964
3146
|
} else {
|
|
2965
3147
|
imageIndex++;
|
|
2966
|
-
const ext =
|
|
3148
|
+
const ext = mimeExt(mime);
|
|
2967
3149
|
img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
|
|
2968
3150
|
resolved.set(storageId, img);
|
|
2969
3151
|
images.push({ filename: img.filename, data: img.data, mimeType: img.mime, source: bin.name });
|
|
@@ -2987,14 +3169,15 @@ function resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced) {
|
|
|
2987
3169
|
const mime = detectImageMime(bin.data);
|
|
2988
3170
|
if (!mime) continue;
|
|
2989
3171
|
imageIndex++;
|
|
2990
|
-
const ext =
|
|
3172
|
+
const ext = mimeExt(mime);
|
|
2991
3173
|
const filename = `image_${String(imageIndex).padStart(3, "0")}.${ext}`;
|
|
2992
3174
|
const data = new Uint8Array(bin.data);
|
|
2993
3175
|
images.push({ filename, data, mimeType: mime, source: bin.name });
|
|
2994
3176
|
blocks.push({ type: "image", text: filename, imageData: { data, mimeType: mime, filename: bin.name } });
|
|
3177
|
+
renamed.set(storageId, filename);
|
|
2995
3178
|
}
|
|
2996
3179
|
}
|
|
2997
|
-
|
|
3180
|
+
resolveImageSentinels(blocks, renamed);
|
|
2998
3181
|
return images;
|
|
2999
3182
|
}
|
|
3000
3183
|
function collectHwp5BinData(fileIndex) {
|
|
@@ -3027,7 +3210,7 @@ function collectHwp5BinDataLenient(lcfb) {
|
|
|
3027
3210
|
function extractHwp5Images(fileIndex, blocks, warnings, sweepUnreferenced) {
|
|
3028
3211
|
const binDataMap = collectHwp5BinData(fileIndex);
|
|
3029
3212
|
if (binDataMap.size === 0) {
|
|
3030
|
-
|
|
3213
|
+
resolveImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3031
3214
|
return [];
|
|
3032
3215
|
}
|
|
3033
3216
|
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
@@ -3035,7 +3218,7 @@ function extractHwp5Images(fileIndex, blocks, warnings, sweepUnreferenced) {
|
|
|
3035
3218
|
function extractHwp5ImagesLenient(lcfb, blocks, warnings, sweepUnreferenced) {
|
|
3036
3219
|
const binDataMap = collectHwp5BinDataLenient(lcfb);
|
|
3037
3220
|
if (binDataMap.size === 0) {
|
|
3038
|
-
|
|
3221
|
+
resolveImageSentinels(blocks, /* @__PURE__ */ new Map());
|
|
3039
3222
|
return [];
|
|
3040
3223
|
}
|
|
3041
3224
|
return resolveImageBlocks(binDataMap, blocks, warnings, sweepUnreferenced);
|
|
@@ -3311,21 +3494,65 @@ function parseSectionPathsFromManifest(xml) {
|
|
|
3311
3494
|
// src/hwpx/metadata.ts
|
|
3312
3495
|
async function extractHwpxMetadata(zip, metadata, decompressed) {
|
|
3313
3496
|
try {
|
|
3314
|
-
const
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
if (!file) continue;
|
|
3497
|
+
const read = async (path) => {
|
|
3498
|
+
const file = zip.file(path) || Object.values(zip.files).find((f) => f.name.toLowerCase() === path.toLowerCase()) || null;
|
|
3499
|
+
if (!file) return null;
|
|
3318
3500
|
const xml = await file.async("text");
|
|
3319
3501
|
if (decompressed) {
|
|
3320
3502
|
decompressed.total += xml.length * 2;
|
|
3321
3503
|
if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
|
|
3322
3504
|
}
|
|
3505
|
+
return xml;
|
|
3506
|
+
};
|
|
3507
|
+
for (const hp of ["Contents/content.hpf", "content.hpf"]) {
|
|
3508
|
+
const xml = await read(hp);
|
|
3509
|
+
if (!xml) continue;
|
|
3510
|
+
parseOpfMetadata(xml, metadata);
|
|
3511
|
+
break;
|
|
3512
|
+
}
|
|
3513
|
+
if (metadata.title || metadata.author) return;
|
|
3514
|
+
const metaPaths = ["meta.xml", "META-INF/meta.xml", "docProps/core.xml"];
|
|
3515
|
+
for (const mp of metaPaths) {
|
|
3516
|
+
const xml = await read(mp);
|
|
3517
|
+
if (!xml) continue;
|
|
3323
3518
|
parseDublinCoreMetadata(xml, metadata);
|
|
3324
3519
|
if (metadata.title || metadata.author) return;
|
|
3325
3520
|
}
|
|
3326
3521
|
} catch {
|
|
3327
3522
|
}
|
|
3328
3523
|
}
|
|
3524
|
+
function parseOpfMetadata(xml, metadata) {
|
|
3525
|
+
const root = createXmlParser().parseFromString(stripDtd(xml), "text/xml").documentElement;
|
|
3526
|
+
const md2 = root ? findChildByLocalName(root, "metadata") : null;
|
|
3527
|
+
if (!md2) return;
|
|
3528
|
+
let title;
|
|
3529
|
+
const meta = /* @__PURE__ */ new Map();
|
|
3530
|
+
for (const el of elementChildren(md2)) {
|
|
3531
|
+
const text = el.textContent?.trim();
|
|
3532
|
+
if (!text) continue;
|
|
3533
|
+
const tag = localName(el);
|
|
3534
|
+
if (tag === "title") title ??= text;
|
|
3535
|
+
else if (tag === "meta") {
|
|
3536
|
+
const name = el.getAttribute("name");
|
|
3537
|
+
if (name && !meta.has(name)) meta.set(name, text);
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
const put = (k, v) => {
|
|
3541
|
+
if (v !== void 0 && !metadata[k]) metadata[k] = v;
|
|
3542
|
+
};
|
|
3543
|
+
const keywords = meta.get("keyword")?.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
3544
|
+
put("title", title);
|
|
3545
|
+
put("author", meta.get("creator"));
|
|
3546
|
+
put("description", meta.get("description") || meta.get("subject"));
|
|
3547
|
+
put("keywords", keywords?.length ? keywords : void 0);
|
|
3548
|
+
put("createdAt", isoDate(meta.get("CreatedDate")));
|
|
3549
|
+
put("modifiedAt", isoDate(meta.get("ModifiedDate")));
|
|
3550
|
+
}
|
|
3551
|
+
function isoDate(s) {
|
|
3552
|
+
if (!s || s.startsWith("1601-01-01")) return void 0;
|
|
3553
|
+
const m = /^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})$/.exec(s);
|
|
3554
|
+
return m ? `${m[1]}T${m[2]}` : s;
|
|
3555
|
+
}
|
|
3329
3556
|
function parseDublinCoreMetadata(xml, metadata) {
|
|
3330
3557
|
const parser = createXmlParser();
|
|
3331
3558
|
const doc = parser.parseFromString(stripDtd(xml), "text/xml");
|
|
@@ -4594,6 +4821,176 @@ function hwpEquationToLatex(equation) {
|
|
|
4594
4821
|
return hmlToLatex(s).replace(/\s+/g, " ").trim();
|
|
4595
4822
|
}
|
|
4596
4823
|
|
|
4824
|
+
// src/hwp5/ir-assemble.ts
|
|
4825
|
+
var INLINE_TABLE_MARK = "";
|
|
4826
|
+
var INLINE_JOIN = /* @__PURE__ */ new WeakSet();
|
|
4827
|
+
function emitParagraphBlocks(p) {
|
|
4828
|
+
const { headMarker, style, footnotes, objects, pageNumber } = p;
|
|
4829
|
+
const blocks = [];
|
|
4830
|
+
const decorate = (t) => {
|
|
4831
|
+
let deco = style?.strike ? `~~${t}~~` : t;
|
|
4832
|
+
if (style?.underline) deco = `<u>${deco}</u>`;
|
|
4833
|
+
return deco;
|
|
4834
|
+
};
|
|
4835
|
+
if (p.text.includes(INLINE_TABLE_MARK)) {
|
|
4836
|
+
const segments = p.text.split(INLINE_TABLE_MARK);
|
|
4837
|
+
let segIdx = 0;
|
|
4838
|
+
let first = true;
|
|
4839
|
+
let lineOpen = false;
|
|
4840
|
+
let markerPending = !!headMarker;
|
|
4841
|
+
const flush = () => {
|
|
4842
|
+
if (segIdx >= segments.length) return;
|
|
4843
|
+
const s = segments[segIdx++].replace(/\$\$/g, "$ $").trim();
|
|
4844
|
+
if (!s) return;
|
|
4845
|
+
let t = decorate(s);
|
|
4846
|
+
if (markerPending) {
|
|
4847
|
+
t = `${headMarker} ${t}`;
|
|
4848
|
+
markerPending = false;
|
|
4849
|
+
}
|
|
4850
|
+
const block = { type: "paragraph", text: t, pageNumber };
|
|
4851
|
+
if (first) {
|
|
4852
|
+
if (style) block.style = style;
|
|
4853
|
+
if (footnotes.length > 0) block.footnoteText = footnotes.join("; ");
|
|
4854
|
+
first = false;
|
|
4855
|
+
}
|
|
4856
|
+
if (lineOpen) INLINE_JOIN.add(block);
|
|
4857
|
+
lineOpen = true;
|
|
4858
|
+
blocks.push(block);
|
|
4859
|
+
};
|
|
4860
|
+
for (const obj of objects) {
|
|
4861
|
+
if (obj.table) {
|
|
4862
|
+
flush();
|
|
4863
|
+
if (obj.inline && lineOpen) for (const b of obj.blocks) INLINE_JOIN.add(b);
|
|
4864
|
+
lineOpen = obj.inline;
|
|
4865
|
+
}
|
|
4866
|
+
blocks.push(...obj.blocks);
|
|
4867
|
+
}
|
|
4868
|
+
while (segIdx < segments.length) flush();
|
|
4869
|
+
if (first && footnotes.length > 0) blocks.unshift({ type: "paragraph", text: `(\uC8FC: ${footnotes.join("; ")})`, pageNumber });
|
|
4870
|
+
return blocks;
|
|
4871
|
+
}
|
|
4872
|
+
const trimmed = p.text.replace(/\$\$/g, "$ $").trim();
|
|
4873
|
+
if (trimmed) {
|
|
4874
|
+
const block = {
|
|
4875
|
+
type: p.headingLevel > 0 ? "heading" : "paragraph",
|
|
4876
|
+
text: headMarker ? `${headMarker} ${trimmed}` : trimmed,
|
|
4877
|
+
pageNumber
|
|
4878
|
+
};
|
|
4879
|
+
if (p.headingLevel > 0) block.level = p.headingLevel;
|
|
4880
|
+
if (style) {
|
|
4881
|
+
block.style = style;
|
|
4882
|
+
if (style.strike || style.underline) block.text = headMarker ? `${headMarker} ${decorate(trimmed)}` : decorate(trimmed);
|
|
4883
|
+
}
|
|
4884
|
+
if (footnotes.length > 0) block.footnoteText = footnotes.join("; ");
|
|
4885
|
+
blocks.push(block);
|
|
4886
|
+
} else if (footnotes.length > 0) {
|
|
4887
|
+
blocks.push({ type: "paragraph", text: `(\uC8FC: ${footnotes.join("; ")})`, pageNumber });
|
|
4888
|
+
}
|
|
4889
|
+
for (const obj of objects) blocks.push(...obj.blocks);
|
|
4890
|
+
return blocks;
|
|
4891
|
+
}
|
|
4892
|
+
function tableFlatText(table2) {
|
|
4893
|
+
const flat = convertTableToText(table2.cells);
|
|
4894
|
+
return table2.caption ? table2.caption + (flat ? "\n" + flat : "") : flat;
|
|
4895
|
+
}
|
|
4896
|
+
function blocksPlainText(blocks, sep) {
|
|
4897
|
+
const parts = [];
|
|
4898
|
+
for (const b of blocks) {
|
|
4899
|
+
if (b.type === "image") continue;
|
|
4900
|
+
if (b.type === "table") {
|
|
4901
|
+
const flat = b.table ? tableFlatText(b.table) : "";
|
|
4902
|
+
if (flat) parts.push(flat);
|
|
4903
|
+
continue;
|
|
4904
|
+
}
|
|
4905
|
+
if (b.text) {
|
|
4906
|
+
let t = b.text;
|
|
4907
|
+
if (b.footnoteText) t += ` (\uC8FC: ${b.footnoteText})`;
|
|
4908
|
+
parts.push(t);
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
return parts.join(sep).trim();
|
|
4912
|
+
}
|
|
4913
|
+
function cellTextFromBlocks(blocks) {
|
|
4914
|
+
let text = "";
|
|
4915
|
+
const add = (part, b) => {
|
|
4916
|
+
text += (text ? INLINE_JOIN.has(b) ? " " : "\n" : "") + part;
|
|
4917
|
+
};
|
|
4918
|
+
let hasStructure = false;
|
|
4919
|
+
for (const b of blocks) {
|
|
4920
|
+
if (b.type === "image" && b.text) {
|
|
4921
|
+
add(``, b);
|
|
4922
|
+
hasStructure = true;
|
|
4923
|
+
} else if (b.type === "table" && b.table) {
|
|
4924
|
+
const flat = tableFlatText(b.table);
|
|
4925
|
+
if (flat) add(flat, b);
|
|
4926
|
+
hasStructure = true;
|
|
4927
|
+
} else if (b.text) {
|
|
4928
|
+
if (b.footnoteText) {
|
|
4929
|
+
b.text += ` (\uC8FC: ${b.footnoteText})`;
|
|
4930
|
+
delete b.footnoteText;
|
|
4931
|
+
}
|
|
4932
|
+
add(b.text, b);
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
return { text, hasStructure };
|
|
4936
|
+
}
|
|
4937
|
+
function resolveCellOverlaps(cells, rows, cols) {
|
|
4938
|
+
const owner = /* @__PURE__ */ new Map();
|
|
4939
|
+
const out = [];
|
|
4940
|
+
for (const cell2 of cells) {
|
|
4941
|
+
const r0 = cell2.rowAddr ?? 0, c0 = cell2.colAddr ?? 0;
|
|
4942
|
+
const hit = owner.get(r0 * cols + c0);
|
|
4943
|
+
if (hit) {
|
|
4944
|
+
if (cell2.blocks?.length) {
|
|
4945
|
+
hit.blocks = [...hit.blocks ?? (hit.text ? [{ type: "paragraph", text: hit.text }] : []), ...cell2.blocks];
|
|
4946
|
+
}
|
|
4947
|
+
if (cell2.text.trim()) hit.text = hit.text.trim() ? `${hit.text}
|
|
4948
|
+
${cell2.text}` : cell2.text;
|
|
4949
|
+
continue;
|
|
4950
|
+
}
|
|
4951
|
+
const csIn = Math.max(1, Math.min(cell2.colSpan, cols - c0));
|
|
4952
|
+
let cs = cell2.colSpan;
|
|
4953
|
+
for (let c = c0 + 1; c < c0 + csIn; c++) if (owner.has(r0 * cols + c)) {
|
|
4954
|
+
cs = c - c0;
|
|
4955
|
+
break;
|
|
4956
|
+
}
|
|
4957
|
+
const rsIn = Math.max(1, Math.min(cell2.rowSpan, rows - r0));
|
|
4958
|
+
let rs = cell2.rowSpan;
|
|
4959
|
+
scan: for (let r = r0 + 1; r < r0 + rsIn; r++) {
|
|
4960
|
+
for (let c = c0; c < c0 + Math.min(cs, csIn); c++) if (owner.has(r * cols + c)) {
|
|
4961
|
+
rs = r - r0;
|
|
4962
|
+
break scan;
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
const placed = cs === cell2.colSpan && rs === cell2.rowSpan ? cell2 : { ...cell2, colSpan: cs, rowSpan: rs };
|
|
4966
|
+
for (let r = r0; r < Math.min(r0 + rs, rows); r++) for (let c = c0; c < Math.min(c0 + cs, cols); c++) owner.set(r * cols + c, placed);
|
|
4967
|
+
out.push(placed);
|
|
4968
|
+
}
|
|
4969
|
+
return out;
|
|
4970
|
+
}
|
|
4971
|
+
function buildAddressedTable(cells, rows, cols, keepAnchoredEmptyCols) {
|
|
4972
|
+
for (const c of cells) {
|
|
4973
|
+
rows = Math.max(rows, Math.min((c.rowAddr ?? 0) + 1, MAX_ROWS));
|
|
4974
|
+
cols = Math.max(cols, Math.min((c.colAddr ?? 0) + 1, MAX_COLS));
|
|
4975
|
+
}
|
|
4976
|
+
const placed = resolveCellOverlaps(cells, rows, cols);
|
|
4977
|
+
const byRow = Array.from({ length: rows }, () => []);
|
|
4978
|
+
for (const c of placed) {
|
|
4979
|
+
const r = c.rowAddr ?? 0;
|
|
4980
|
+
if (r < rows && (c.colAddr ?? 0) < cols) byRow[r].push(c);
|
|
4981
|
+
}
|
|
4982
|
+
const table2 = buildTable(byRow, { keepAnchoredEmptyCols });
|
|
4983
|
+
if (table2.rows === 0) return null;
|
|
4984
|
+
for (const c of placed) {
|
|
4985
|
+
if (!c.blocks?.length && !c.isHeader) continue;
|
|
4986
|
+
const target = table2.cells[c.rowAddr ?? 0]?.[c.colAddr ?? 0];
|
|
4987
|
+
if (!target || target.text !== c.text.trim()) continue;
|
|
4988
|
+
if (c.blocks?.length) target.blocks = c.blocks;
|
|
4989
|
+
if (c.isHeader) target.isHeader = true;
|
|
4990
|
+
}
|
|
4991
|
+
return table2;
|
|
4992
|
+
}
|
|
4993
|
+
|
|
4597
4994
|
// src/hwp5/body.ts
|
|
4598
4995
|
var MAX_NEST_DEPTH = 8;
|
|
4599
4996
|
function cid(s) {
|
|
@@ -4743,8 +5140,13 @@ function parseParagraph(records, start, end, ctx) {
|
|
|
4743
5140
|
}
|
|
4744
5141
|
for (const ctrl of ctrls) {
|
|
4745
5142
|
applyCtrlEffect(ctrl, records, ctx);
|
|
5143
|
+
if (ctrl.id === CTRL_TBL && ctrl.afterBlocks && ctrl.data.length >= 8 && ctrl.data.readUInt32LE(4) & 1) {
|
|
5144
|
+
ctrl.inlineText = INLINE_TABLE_MARK;
|
|
5145
|
+
}
|
|
4746
5146
|
}
|
|
4747
5147
|
const state = createParaTextState();
|
|
5148
|
+
state.leaderMark = true;
|
|
5149
|
+
state.dollarMark = true;
|
|
4748
5150
|
const resolver = (idx, id) => {
|
|
4749
5151
|
let ctrl = idx >= 0 && idx < ctrls.length ? ctrls[idx] : void 0;
|
|
4750
5152
|
if (!ctrl || ctrl.idRaw !== id && ctrl.id !== id) {
|
|
@@ -4763,17 +5165,28 @@ function parseParagraph(records, start, end, ctx) {
|
|
|
4763
5165
|
const applied = [];
|
|
4764
5166
|
for (const r of ranges) {
|
|
4765
5167
|
const ctrl = ctrls[r.ctrlIdx];
|
|
4766
|
-
if (!ctrl
|
|
5168
|
+
if (!ctrl || r.end <= r.start) continue;
|
|
4767
5169
|
if (applied.some(([s, e]) => r.start < e && r.end > s)) continue;
|
|
5170
|
+
const anchor = text.slice(r.start, r.end);
|
|
5171
|
+
if (ctrl.guide !== void 0) {
|
|
5172
|
+
const plain4 = anchor.replaceAll(LITERAL_DOLLAR_MARK, "$");
|
|
5173
|
+
if (plain4 === ctrl.guide || plain4.trimEnd() === ctrl.guide) {
|
|
5174
|
+
text = text.slice(0, r.start) + text.slice(r.end);
|
|
5175
|
+
applied.push([r.start, r.end]);
|
|
5176
|
+
}
|
|
5177
|
+
continue;
|
|
5178
|
+
}
|
|
5179
|
+
if (!ctrl.href) continue;
|
|
4768
5180
|
const href = sanitizeHref(ctrl.href);
|
|
4769
5181
|
if (!href) continue;
|
|
4770
|
-
|
|
4771
|
-
if (!anchor.trim()) continue;
|
|
5182
|
+
if (!anchor.trim() || anchor.includes(INLINE_TABLE_MARK) || anchor.includes(LEADER_TAB_MARK)) continue;
|
|
4772
5183
|
text = text.slice(0, r.start) + `[${anchor}](${href})` + text.slice(r.end);
|
|
4773
5184
|
applied.push([r.start, r.end]);
|
|
4774
5185
|
}
|
|
4775
5186
|
}
|
|
4776
|
-
const
|
|
5187
|
+
const leaderAt = text.indexOf(LEADER_TAB_MARK);
|
|
5188
|
+
if (leaderAt >= 0) text = text.slice(0, leaderAt);
|
|
5189
|
+
if (text.includes(LITERAL_DOLLAR_MARK)) text = text.replaceAll(LITERAL_DOLLAR_MARK, "\\$");
|
|
4777
5190
|
let headingLevel = 0;
|
|
4778
5191
|
let headMarker = null;
|
|
4779
5192
|
const ps = ctx.docInfo && paraShapeId >= 0 && paraShapeId < ctx.docInfo.paraShapes.length ? ctx.docInfo.paraShapes[paraShapeId] : null;
|
|
@@ -4797,35 +5210,16 @@ function parseParagraph(records, start, end, ctx) {
|
|
|
4797
5210
|
if (bullet && bullet.char !== "\uFFFF") headMarker = bullet.char;
|
|
4798
5211
|
}
|
|
4799
5212
|
}
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
}
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
const style = resolveCharStyle(charShapeIds, ctx.docInfo);
|
|
4811
|
-
if (style) {
|
|
4812
|
-
block.style = style;
|
|
4813
|
-
if (style.strike || style.underline) {
|
|
4814
|
-
let deco = style.strike ? `~~${trimmed}~~` : trimmed;
|
|
4815
|
-
if (style.underline) deco = `<u>${deco}</u>`;
|
|
4816
|
-
block.text = headMarker ? `${headMarker} ${deco}` : deco;
|
|
4817
|
-
}
|
|
4818
|
-
}
|
|
4819
|
-
}
|
|
4820
|
-
if (footnotes.length > 0) block.footnoteText = footnotes.join("; ");
|
|
4821
|
-
blocks.push(block);
|
|
4822
|
-
} else if (footnotes.length > 0) {
|
|
4823
|
-
blocks.push({ type: "paragraph", text: `(\uC8FC: ${footnotes.join("; ")})`, pageNumber: ctx.page });
|
|
4824
|
-
}
|
|
4825
|
-
for (const ctrl of ctrls) {
|
|
4826
|
-
if (ctrl.afterBlocks) blocks.push(...ctrl.afterBlocks);
|
|
4827
|
-
}
|
|
4828
|
-
return blocks;
|
|
5213
|
+
return emitParagraphBlocks({
|
|
5214
|
+
text,
|
|
5215
|
+
headMarker,
|
|
5216
|
+
headingLevel,
|
|
5217
|
+
style: ctx.docInfo && charShapeIds.length > 0 ? resolveCharStyle(charShapeIds, ctx.docInfo) : void 0,
|
|
5218
|
+
footnotes: ctrls.filter((c) => c.footnote).map((c) => c.footnote),
|
|
5219
|
+
// 컨트롤 파생 블록 (표/이미지/글상자) — 컨트롤 순서대로
|
|
5220
|
+
objects: ctrls.filter((c) => c.afterBlocks).map((c) => ({ blocks: c.afterBlocks, table: c.id === CTRL_TBL, inline: c.inlineText === INLINE_TABLE_MARK })),
|
|
5221
|
+
pageNumber: ctx.page
|
|
5222
|
+
});
|
|
4829
5223
|
}
|
|
4830
5224
|
function applyCtrlEffect(ctrl, records, ctx) {
|
|
4831
5225
|
switch (ctrl.id) {
|
|
@@ -4920,33 +5314,20 @@ function parseListHeaderParagraphs(ctrl, records, ctx) {
|
|
|
4920
5314
|
}
|
|
4921
5315
|
return [];
|
|
4922
5316
|
}
|
|
4923
|
-
function blocksPlainText(blocks, sep) {
|
|
4924
|
-
const parts = [];
|
|
4925
|
-
for (const b of blocks) {
|
|
4926
|
-
if (b.type === "image") continue;
|
|
4927
|
-
if (b.type === "table") continue;
|
|
4928
|
-
if (b.text) {
|
|
4929
|
-
let t = b.text;
|
|
4930
|
-
if (b.footnoteText) t += ` (\uC8FC: ${b.footnoteText})`;
|
|
4931
|
-
parts.push(t);
|
|
4932
|
-
}
|
|
4933
|
-
}
|
|
4934
|
-
return parts.join(sep).trim();
|
|
4935
|
-
}
|
|
4936
5317
|
function applyNoteEffect(ctrl, records, ctx, autoType) {
|
|
4937
5318
|
const num4 = ctx.doc.autoCounters.get(autoType) ?? 1;
|
|
4938
|
-
let
|
|
4939
|
-
let
|
|
5319
|
+
let before2 = "";
|
|
5320
|
+
let after2 = "";
|
|
4940
5321
|
let shape = 0;
|
|
4941
5322
|
if (ctrl.data.length >= 12) {
|
|
4942
|
-
|
|
4943
|
-
|
|
5323
|
+
before2 = wcharAt(ctrl.data, 8);
|
|
5324
|
+
after2 = wcharAt(ctrl.data, 10);
|
|
4944
5325
|
}
|
|
4945
5326
|
if (ctrl.data.length >= 16) {
|
|
4946
5327
|
shape = ctrl.data.readUInt32LE(12) & 255;
|
|
4947
5328
|
}
|
|
4948
5329
|
const formatted = formatNumber(num4, shapeFormatToNumFmt(shape));
|
|
4949
|
-
const marker =
|
|
5330
|
+
const marker = before2 || after2 ? `${before2}${formatted}${after2}` : `${formatted})`;
|
|
4950
5331
|
const content = blocksPlainText(parseListHeaderParagraphs(ctrl, records, ctx), " ");
|
|
4951
5332
|
if ((ctx.doc.autoCounters.get(autoType) ?? 1) <= num4) {
|
|
4952
5333
|
ctx.doc.autoCounters.set(autoType, num4 + 1);
|
|
@@ -4971,8 +5352,18 @@ function applyFieldEffect(ctrl) {
|
|
|
4971
5352
|
const url = hyperlinkUrlFromCommand(command);
|
|
4972
5353
|
if (url) ctrl.href = url;
|
|
4973
5354
|
}
|
|
5355
|
+
} else if (ctrl.id === FIELD_CLK && ctrl.data.length >= 8 && !(ctrl.data.readUInt32LE(4) & 1 << 15)) {
|
|
5356
|
+
const command = parseFieldCommand(ctrl.data);
|
|
5357
|
+
const guide = command ? clickHereGuide2(command) : void 0;
|
|
5358
|
+
if (guide) ctrl.guide = guide;
|
|
4974
5359
|
}
|
|
4975
5360
|
}
|
|
5361
|
+
function clickHereGuide2(command) {
|
|
5362
|
+
const m = /Direction:wstring:(\d+):/.exec(command);
|
|
5363
|
+
if (!m) return void 0;
|
|
5364
|
+
const start = m.index + m[0].length;
|
|
5365
|
+
return command.slice(start, start + Number(m[1])) || void 0;
|
|
5366
|
+
}
|
|
4976
5367
|
function parseFieldCommand(data) {
|
|
4977
5368
|
if (data.length < 11) return null;
|
|
4978
5369
|
const cmdLen = data.readUInt16LE(9);
|
|
@@ -5001,18 +5392,19 @@ function parseTableControl(ctrl, records, ctx) {
|
|
|
5001
5392
|
if (ctx.depth >= MAX_NEST_DEPTH) return null;
|
|
5002
5393
|
const { childStart, childEnd } = ctrl;
|
|
5003
5394
|
const sourceId = ctx.tableIds?.get(childStart - 1);
|
|
5395
|
+
const directLevel = records[childStart - 1].level + 1;
|
|
5004
5396
|
let rows = 0;
|
|
5005
5397
|
let cols = 0;
|
|
5006
5398
|
let tableIdx = -1;
|
|
5007
5399
|
for (let i2 = childStart; i2 < childEnd; i2++) {
|
|
5008
|
-
if (records[i2].tagId === TAG_TABLE && records[i2].data.length >= 8) {
|
|
5400
|
+
if (records[i2].tagId === TAG_TABLE && records[i2].level === directLevel && records[i2].data.length >= 8) {
|
|
5009
5401
|
rows = Math.min(records[i2].data.readUInt16LE(4), MAX_ROWS);
|
|
5010
5402
|
cols = Math.min(records[i2].data.readUInt16LE(6), MAX_COLS);
|
|
5011
5403
|
tableIdx = i2;
|
|
5012
5404
|
break;
|
|
5013
5405
|
}
|
|
5014
5406
|
}
|
|
5015
|
-
if (tableIdx < 0
|
|
5407
|
+
if (tableIdx < 0) return null;
|
|
5016
5408
|
let caption;
|
|
5017
5409
|
for (let i2 = childStart; i2 < tableIdx; i2++) {
|
|
5018
5410
|
if (records[i2].tagId === TAG_LIST_HEADER) {
|
|
@@ -5042,20 +5434,17 @@ function parseTableControl(ctrl, records, ctx) {
|
|
|
5042
5434
|
i++;
|
|
5043
5435
|
}
|
|
5044
5436
|
if (cells.length === 0) return null;
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
const irCells = cellRows2.map((row) => row.map((c) => {
|
|
5049
|
-
const ir = { text: c.text.trim(), colSpan: c.colSpan, rowSpan: c.rowSpan };
|
|
5050
|
-
if (c.blocks?.length) ir.blocks = c.blocks;
|
|
5051
|
-
if (c.isHeader) ir.isHeader = true;
|
|
5052
|
-
return ir;
|
|
5053
|
-
}));
|
|
5054
|
-
const table3 = { rows, cols, cells: irCells, hasHeader: rows > 1 };
|
|
5437
|
+
if (cells.some((c) => c.colAddr !== void 0 && c.rowAddr !== void 0)) {
|
|
5438
|
+
const table3 = buildAddressedTable(cells, rows, cols, ctx.doc.keepTrailingEmptyCols);
|
|
5439
|
+
if (!table3) return null;
|
|
5055
5440
|
if (caption) table3.caption = caption;
|
|
5056
5441
|
if (sourceId) table3.sourceId = sourceId;
|
|
5057
5442
|
return table3;
|
|
5058
5443
|
}
|
|
5444
|
+
if (rows === 0 || cols === 0) {
|
|
5445
|
+
rows = Math.min(cells.length, MAX_ROWS);
|
|
5446
|
+
cols = 1;
|
|
5447
|
+
}
|
|
5059
5448
|
const cellRows = arrangeCells(rows, cols, cells);
|
|
5060
5449
|
const table2 = buildTable(cellRows, { keepAnchoredEmptyCols: ctx.doc.keepTrailingEmptyCols });
|
|
5061
5450
|
if (caption && table2.rows > 0) table2.caption = caption;
|
|
@@ -5079,38 +5468,19 @@ function parseCell(records, lhIdx, end, ctx) {
|
|
|
5079
5468
|
if (rs > 0) rowSpan = Math.min(rs, MAX_ROWS);
|
|
5080
5469
|
}
|
|
5081
5470
|
const blocks = ctx.depth < MAX_NEST_DEPTH ? parseParagraphList(records, lhIdx + 1, end, { ...ctx, depth: ctx.depth + 1 }) : [];
|
|
5082
|
-
const
|
|
5083
|
-
|
|
5084
|
-
for (const b of blocks) {
|
|
5085
|
-
if (b.type === "image" && b.text) {
|
|
5086
|
-
parts.push(``);
|
|
5087
|
-
hasStructure = true;
|
|
5088
|
-
} else if (b.type === "table" && b.table) {
|
|
5089
|
-
const flat = convertTableToText(b.table.cells);
|
|
5090
|
-
if (flat) parts.push(flat);
|
|
5091
|
-
hasStructure = true;
|
|
5092
|
-
} else if (b.text) {
|
|
5093
|
-
let t = b.text;
|
|
5094
|
-
if (b.footnoteText) {
|
|
5095
|
-
t += ` (\uC8FC: ${b.footnoteText})`;
|
|
5096
|
-
hasStructure = true;
|
|
5097
|
-
}
|
|
5098
|
-
parts.push(t);
|
|
5099
|
-
}
|
|
5100
|
-
}
|
|
5101
|
-
const cell2 = { text: parts.join("\n"), colSpan, rowSpan, colAddr, rowAddr };
|
|
5471
|
+
const { text, hasStructure } = cellTextFromBlocks(blocks);
|
|
5472
|
+
const cell2 = { text, colSpan, rowSpan, colAddr, rowAddr };
|
|
5102
5473
|
if (hasStructure && blocks.length > 0) cell2.blocks = blocks;
|
|
5103
5474
|
if (isHeader) cell2.isHeader = true;
|
|
5104
5475
|
return cell2;
|
|
5105
5476
|
}
|
|
5106
5477
|
function arrangeCells(rows, cols, cells) {
|
|
5107
5478
|
const grid = Array.from({ length: rows }, () => Array(cols).fill(null));
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
for (
|
|
5111
|
-
|
|
5112
|
-
const
|
|
5113
|
-
if (r >= rows || c >= cols) continue;
|
|
5479
|
+
let cellIdx = 0;
|
|
5480
|
+
for (let r = 0; r < rows && cellIdx < cells.length; r++) {
|
|
5481
|
+
for (let c = 0; c < cols && cellIdx < cells.length; c++) {
|
|
5482
|
+
if (grid[r][c] !== null) continue;
|
|
5483
|
+
const cell2 = cells[cellIdx++];
|
|
5114
5484
|
grid[r][c] = cell2;
|
|
5115
5485
|
for (let dr = 0; dr < cell2.rowSpan; dr++) {
|
|
5116
5486
|
for (let dc = 0; dc < cell2.colSpan; dc++) {
|
|
@@ -5120,22 +5490,6 @@ function arrangeCells(rows, cols, cells) {
|
|
|
5120
5490
|
}
|
|
5121
5491
|
}
|
|
5122
5492
|
}
|
|
5123
|
-
} else {
|
|
5124
|
-
let cellIdx = 0;
|
|
5125
|
-
for (let r = 0; r < rows && cellIdx < cells.length; r++) {
|
|
5126
|
-
for (let c = 0; c < cols && cellIdx < cells.length; c++) {
|
|
5127
|
-
if (grid[r][c] !== null) continue;
|
|
5128
|
-
const cell2 = cells[cellIdx++];
|
|
5129
|
-
grid[r][c] = cell2;
|
|
5130
|
-
for (let dr = 0; dr < cell2.rowSpan; dr++) {
|
|
5131
|
-
for (let dc = 0; dc < cell2.colSpan; dc++) {
|
|
5132
|
-
if (dr === 0 && dc === 0) continue;
|
|
5133
|
-
if (r + dr < rows && c + dc < cols)
|
|
5134
|
-
grid[r + dr][c + dc] = { text: "", colSpan: 1, rowSpan: 1 };
|
|
5135
|
-
}
|
|
5136
|
-
}
|
|
5137
|
-
}
|
|
5138
|
-
}
|
|
5139
5493
|
}
|
|
5140
5494
|
return grid.map((row) => row.map((c) => c || { text: "", colSpan: 1, rowSpan: 1 }));
|
|
5141
5495
|
}
|
|
@@ -5350,7 +5704,7 @@ function parseHwp5Document(buffer, options) {
|
|
|
5350
5704
|
for (let si = 0; si < sections.length; si++) {
|
|
5351
5705
|
const records = sectionRecords[si];
|
|
5352
5706
|
if (!records) continue;
|
|
5353
|
-
const
|
|
5707
|
+
const detect2 = sectionDetects[si];
|
|
5354
5708
|
if (!layoutPages && pageFilter && !pageFilter.has(si + 1)) continue;
|
|
5355
5709
|
try {
|
|
5356
5710
|
const sectionBlocks = parseSection(
|
|
@@ -5359,7 +5713,7 @@ function parseHwp5Document(buffer, options) {
|
|
|
5359
5713
|
warnings,
|
|
5360
5714
|
si + 1,
|
|
5361
5715
|
doc,
|
|
5362
|
-
layoutPages ? { base: pageBase, pageAtPara:
|
|
5716
|
+
layoutPages ? { base: pageBase, pageAtPara: detect2.pageAtPara } : void 0,
|
|
5363
5717
|
sectionTableIds[si]
|
|
5364
5718
|
);
|
|
5365
5719
|
bodyBlocks.push(...sectionBlocks);
|
|
@@ -5369,7 +5723,7 @@ function parseHwp5Document(buffer, options) {
|
|
|
5369
5723
|
if (secErr instanceof KordocError) throw secErr;
|
|
5370
5724
|
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" });
|
|
5371
5725
|
} finally {
|
|
5372
|
-
pageBase +=
|
|
5726
|
+
pageBase += detect2.pages;
|
|
5373
5727
|
}
|
|
5374
5728
|
}
|
|
5375
5729
|
if (pageFilter && layoutPages) {
|
|
@@ -5389,7 +5743,7 @@ function parseHwp5Document(buffer, options) {
|
|
|
5389
5743
|
}
|
|
5390
5744
|
const outline = flatBlocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
|
|
5391
5745
|
let markdown = blocksToMarkdown(flatBlocks);
|
|
5392
|
-
if (options?.inlineImages && images.length > 0) {
|
|
5746
|
+
if (options?.inlineImages && options.images !== false && images.length > 0) {
|
|
5393
5747
|
try {
|
|
5394
5748
|
markdown = inlineImagesIntoMarkdown(markdown, images, { compress: true });
|
|
5395
5749
|
} catch (inlineErr) {
|
|
@@ -5470,6 +5824,11 @@ function detectHwp5Headings(blocks, docInfo) {
|
|
|
5470
5824
|
}
|
|
5471
5825
|
}
|
|
5472
5826
|
}
|
|
5827
|
+
var VT_I2 = 2;
|
|
5828
|
+
var VT_LPSTR = 30;
|
|
5829
|
+
var VT_LPWSTR = 31;
|
|
5830
|
+
var VT_FILETIME = 64;
|
|
5831
|
+
var CODE_PAGE_LABELS = { 949: "euc-kr", 932: "shift_jis", 936: "gbk", 950: "big5", 65001: "utf-8" };
|
|
5473
5832
|
function extractHwp5Metadata(cfb, metadata) {
|
|
5474
5833
|
try {
|
|
5475
5834
|
const summaryEntry = CFB.find(cfb, "/HwpSummaryInformation") || CFB.find(cfb, "/SummaryInformation");
|
|
@@ -5482,26 +5841,60 @@ function extractHwp5Metadata(cfb, metadata) {
|
|
|
5482
5841
|
if (setOffset >= data.length - 8) return;
|
|
5483
5842
|
const numProps = data.readUInt32LE(setOffset + 4);
|
|
5484
5843
|
if (numProps === 0 || numProps > 100) return;
|
|
5844
|
+
const props = /* @__PURE__ */ new Map();
|
|
5485
5845
|
for (let i = 0; i < numProps; i++) {
|
|
5486
5846
|
const entryOffset = setOffset + 8 + i * 8;
|
|
5487
5847
|
if (entryOffset + 8 > data.length) break;
|
|
5488
|
-
const propId = data.readUInt32LE(entryOffset);
|
|
5489
5848
|
const propOffset = setOffset + data.readUInt32LE(entryOffset + 4);
|
|
5490
|
-
if (propOffset + 8
|
|
5491
|
-
if (propId !== 2 && propId !== 4 && propId !== 6) continue;
|
|
5492
|
-
const propType = data.readUInt32LE(propOffset);
|
|
5493
|
-
if (propType !== 30) continue;
|
|
5494
|
-
const strLen = data.readUInt32LE(propOffset + 4);
|
|
5495
|
-
if (strLen === 0 || strLen > 1e4 || propOffset + 8 + strLen > data.length) continue;
|
|
5496
|
-
const str = data.subarray(propOffset + 8, propOffset + 8 + strLen).toString("utf8").replace(/\0+$/, "").trim();
|
|
5497
|
-
if (!str) continue;
|
|
5498
|
-
if (propId === 2) metadata.title = str;
|
|
5499
|
-
else if (propId === 4) metadata.author = str;
|
|
5500
|
-
else if (propId === 6) metadata.description = str;
|
|
5849
|
+
if (propOffset + 8 <= data.length) props.set(data.readUInt32LE(entryOffset), propOffset);
|
|
5501
5850
|
}
|
|
5851
|
+
const cpAt = props.get(1);
|
|
5852
|
+
const codePage = cpAt !== void 0 && data.readUInt32LE(cpAt) === VT_I2 ? data.readUInt16LE(cpAt + 4) : 0;
|
|
5853
|
+
const text = (id) => {
|
|
5854
|
+
const at = props.get(id);
|
|
5855
|
+
return at === void 0 ? void 0 : readPropString(data, at, codePage);
|
|
5856
|
+
};
|
|
5857
|
+
const time = (id) => {
|
|
5858
|
+
const at = props.get(id);
|
|
5859
|
+
return at === void 0 ? void 0 : readPropFileTime(data, at);
|
|
5860
|
+
};
|
|
5861
|
+
const title = text(2), author = text(4), description = text(6) || text(3);
|
|
5862
|
+
if (title) metadata.title = title;
|
|
5863
|
+
if (author) metadata.author = author;
|
|
5864
|
+
if (description) metadata.description = description;
|
|
5865
|
+
const keywords = text(5)?.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
|
|
5866
|
+
if (keywords?.length) metadata.keywords = keywords;
|
|
5867
|
+
const createdAt = time(12), modifiedAt = time(13);
|
|
5868
|
+
if (createdAt) metadata.createdAt = createdAt;
|
|
5869
|
+
if (modifiedAt) metadata.modifiedAt = modifiedAt;
|
|
5502
5870
|
} catch {
|
|
5503
5871
|
}
|
|
5504
5872
|
}
|
|
5873
|
+
function readPropString(data, at, codePage) {
|
|
5874
|
+
const type = data.readUInt32LE(at);
|
|
5875
|
+
const unit = type === VT_LPWSTR ? 2 : type === VT_LPSTR ? 1 : 0;
|
|
5876
|
+
const count = data.readUInt32LE(at + 4);
|
|
5877
|
+
if (!unit || count === 0 || count > 1e4 || at + 8 + count * unit > data.length) return void 0;
|
|
5878
|
+
const bytes = data.subarray(at + 8, at + 8 + count * unit);
|
|
5879
|
+
let s;
|
|
5880
|
+
if (unit === 2 || codePage === 1200) s = bytes.toString("utf16le");
|
|
5881
|
+
else {
|
|
5882
|
+
const label = CODE_PAGE_LABELS[codePage] ?? (codePage >= 1250 && codePage <= 1258 ? `windows-${codePage}` : "utf-8");
|
|
5883
|
+
try {
|
|
5884
|
+
s = new TextDecoder(label).decode(bytes);
|
|
5885
|
+
} catch {
|
|
5886
|
+
s = bytes.toString("utf8");
|
|
5887
|
+
}
|
|
5888
|
+
}
|
|
5889
|
+
const nul = s.indexOf("\0");
|
|
5890
|
+
return (nul >= 0 ? s.slice(0, nul) : s).trim() || void 0;
|
|
5891
|
+
}
|
|
5892
|
+
function readPropFileTime(data, at) {
|
|
5893
|
+
if (data.readUInt32LE(at) !== VT_FILETIME || at + 12 > data.length) return void 0;
|
|
5894
|
+
const ticks = data.readBigUInt64LE(at + 4);
|
|
5895
|
+
if (ticks === 0n) return void 0;
|
|
5896
|
+
return new Date((Number(ticks / 10000000n) - 11644473600) * 1e3).toISOString().replace(".000Z", "Z");
|
|
5897
|
+
}
|
|
5505
5898
|
function findViewTextSections(cfb, compressed) {
|
|
5506
5899
|
const sections = [];
|
|
5507
5900
|
for (let i = 0; i < MAX_SECTIONS; i++) {
|
|
@@ -17923,6 +18316,32 @@ function decodeHwp3Extra(ch) {
|
|
|
17923
18316
|
case 12038:
|
|
17924
18317
|
return 9632;
|
|
17925
18318
|
// ■ hwp3-sample10 "제목차례" 좌우 장식 2건
|
|
18319
|
+
// SO-SUEOP ↔ 한컴 HWP3→HWPX 변환본 문맥 정렬 — 코드 개수와 변환본 글자 증가분이 전부 일치
|
|
18320
|
+
// (“ 13·” 14·‧ 4·《 3·》 3·— 1·∣ 6·↳ 18). 앞 셋은 항등, 0x3062~0x30BD 는 사적 문장부호
|
|
18321
|
+
case 8212:
|
|
18322
|
+
return 8212;
|
|
18323
|
+
// —
|
|
18324
|
+
case 8739:
|
|
18325
|
+
return 8739;
|
|
18326
|
+
// ∣
|
|
18327
|
+
case 8627:
|
|
18328
|
+
return 8627;
|
|
18329
|
+
// ↳
|
|
18330
|
+
case 12386:
|
|
18331
|
+
return 8220;
|
|
18332
|
+
// “
|
|
18333
|
+
case 12387:
|
|
18334
|
+
return 8221;
|
|
18335
|
+
// ”
|
|
18336
|
+
case 12390:
|
|
18337
|
+
return 8231;
|
|
18338
|
+
// ‧
|
|
18339
|
+
case 12475:
|
|
18340
|
+
return 12298;
|
|
18341
|
+
// 《
|
|
18342
|
+
case 12477:
|
|
18343
|
+
return 12299;
|
|
18344
|
+
// 》
|
|
17926
18345
|
// 관인·서명란 도장 기호. rhwp 는 U+F012B 로 보존하고 렌더러가 `(인)` 으로 편다 —
|
|
17927
18346
|
// kordoc 도 PUA 를 그대로 내보내면 shared/pua.ts 의 검증표가 같은 문자열로 편다.
|
|
17928
18347
|
case 11214:
|
|
@@ -18074,16 +18493,78 @@ function decryptHwp3Document(input, password) {
|
|
|
18074
18493
|
return out;
|
|
18075
18494
|
}
|
|
18076
18495
|
|
|
18496
|
+
// src/hwp3/table.ts
|
|
18497
|
+
var NEIGHBOR_SLACK = 10;
|
|
18498
|
+
var MAX_NEIGHBOR_SCAN = 2048;
|
|
18499
|
+
var uniqSorted = (values) => [...new Set(values)].sort((a, b) => a - b);
|
|
18500
|
+
function edgeAt(edges, v) {
|
|
18501
|
+
let lo = 0, hi = edges.length - 1;
|
|
18502
|
+
while (lo < hi) {
|
|
18503
|
+
const mid = lo + hi >> 1;
|
|
18504
|
+
if (edges[mid] < v) lo = mid + 1;
|
|
18505
|
+
else hi = mid;
|
|
18506
|
+
}
|
|
18507
|
+
return lo;
|
|
18508
|
+
}
|
|
18509
|
+
function nextStarts(geo, axis) {
|
|
18510
|
+
const start = axis === "x" ? (g2) => g2.x : (g2) => g2.y;
|
|
18511
|
+
const end = axis === "x" ? (g2) => g2.r : (g2) => g2.b;
|
|
18512
|
+
const overlap = axis === "x" ? (a, b) => b.y < a.b && b.b > a.y : (a, b) => b.x < a.r && b.r > a.x;
|
|
18513
|
+
const order = geo.map((_, i) => i).sort((i, j) => start(geo[i]) - start(geo[j]));
|
|
18514
|
+
const starts = order.map((i) => start(geo[i]));
|
|
18515
|
+
return geo.map((a) => {
|
|
18516
|
+
let k = edgeAt(starts, Math.max(end(a) - NEIGHBOR_SLACK, start(a) + 1));
|
|
18517
|
+
for (let steps = 0; k < order.length && steps < MAX_NEIGHBOR_SCAN; k++, steps++) {
|
|
18518
|
+
const b = geo[order[k]];
|
|
18519
|
+
if (start(b) <= start(a)) continue;
|
|
18520
|
+
if (overlap(a, b)) return start(b);
|
|
18521
|
+
}
|
|
18522
|
+
return void 0;
|
|
18523
|
+
});
|
|
18524
|
+
}
|
|
18525
|
+
function hwp3CellGrid(info, count) {
|
|
18526
|
+
const geo = [];
|
|
18527
|
+
let right = 0, bottom = 0;
|
|
18528
|
+
for (let k = 0; k < count; k++) {
|
|
18529
|
+
const o = k * 27;
|
|
18530
|
+
const x = info.readUInt16LE(o + 4), y = info.readUInt16LE(o + 6);
|
|
18531
|
+
const g2 = { x, y, r: x + info.readUInt16LE(o + 8), b: y + info.readUInt16LE(o + 10) };
|
|
18532
|
+
geo.push(g2);
|
|
18533
|
+
if (g2.r > right) right = g2.r;
|
|
18534
|
+
if (g2.b > bottom) bottom = g2.b;
|
|
18535
|
+
}
|
|
18536
|
+
const xs = uniqSorted([...geo.map((g2) => g2.x), right]);
|
|
18537
|
+
const ys = uniqSorted([...geo.map((g2) => g2.y), bottom]);
|
|
18538
|
+
const nextX = nextStarts(geo, "x");
|
|
18539
|
+
const nextY = nextStarts(geo, "y");
|
|
18540
|
+
const cells = geo.map((g2, k) => {
|
|
18541
|
+
const col = edgeAt(xs, g2.x), row = edgeAt(ys, g2.y);
|
|
18542
|
+
const c2 = nextX[k] !== void 0 ? edgeAt(xs, nextX[k]) : xs.length - 1;
|
|
18543
|
+
const r2 = nextY[k] !== void 0 ? edgeAt(ys, nextY[k]) : ys.length - 1;
|
|
18544
|
+
return { row, col, rowSpan: Math.min(Math.max(1, r2 - row), MAX_ROWS), colSpan: Math.min(Math.max(1, c2 - col), MAX_COLS) };
|
|
18545
|
+
});
|
|
18546
|
+
return { rows: Math.min(Math.max(1, ys.length - 1), MAX_ROWS), cols: Math.min(Math.max(1, xs.length - 1), MAX_COLS), cells };
|
|
18547
|
+
}
|
|
18548
|
+
|
|
18077
18549
|
// src/hwp3/parser.ts
|
|
18078
18550
|
var MAX_DECOMPRESS_SIZE3 = 100 * 1024 * 1024;
|
|
18079
18551
|
var PIC_TYPE_DRAWING = 3;
|
|
18552
|
+
var OBJ_EQUATION = 2;
|
|
18553
|
+
var OPT_HYPERTEXT = 16;
|
|
18554
|
+
var LEADER_TAB_MARK2 = "";
|
|
18555
|
+
var OUTLINE_FORMATS = [
|
|
18556
|
+
{ fmt: "romanUpper", before: "", after: "." },
|
|
18557
|
+
{ fmt: "digit", before: "", after: ")" },
|
|
18558
|
+
{ fmt: "digit", before: "(", after: ")" },
|
|
18559
|
+
{ fmt: "ganada", before: "", after: "." },
|
|
18560
|
+
{ fmt: "ganada", before: "", after: ")" },
|
|
18561
|
+
{ fmt: "ganada", before: "(", after: ")" },
|
|
18562
|
+
{ fmt: "circled", before: "", after: "" }
|
|
18563
|
+
];
|
|
18080
18564
|
var PARA_SHAPE_SIZE = 187;
|
|
18081
18565
|
var LINE_INFO_SIZE = 14;
|
|
18082
18566
|
var INLINE_CHAR_SHAPE_SIZE = 31;
|
|
18083
18567
|
var SIMPLE_CTRL = /* @__PURE__ */ new Map([
|
|
18084
|
-
[9, { extraBytes: 6, extraHchar: 3, emit: " " }],
|
|
18085
|
-
[18, { extraBytes: 6, extraHchar: 3, emit: " " }],
|
|
18086
|
-
// AutoNumber → 공백 (HWP5 패턴)
|
|
18087
18568
|
// 19/20/21 (새 번호·쪽 번호 위치·쪽 감추기) 는 비가시 마커다 — 한글도 그 자리에
|
|
18088
18569
|
// 아무 글자를 내지 않는다 (rhwp #4957). 종전엔 U+FFFC 를 본문에 심어 마크다운에
|
|
18089
18570
|
// 대체 문자가 그대로 남았다 (samples 33개 중 6문서 11건). 차례 표식(25) 과 같은 계약.
|
|
@@ -18096,8 +18577,7 @@ var SIMPLE_CTRL = /* @__PURE__ */ new Map([
|
|
|
18096
18577
|
[25, { extraBytes: 4, extraHchar: 2, emit: null }],
|
|
18097
18578
|
// 차례 표식 — 비가시 (rhwp #2765)
|
|
18098
18579
|
[26, { extraBytes: 244, extraHchar: 122, emit: "\uFFFC" }],
|
|
18099
|
-
[
|
|
18100
|
-
[30, { extraBytes: 2, extraHchar: 1, emit: "\xA0" }],
|
|
18580
|
+
[30, { extraBytes: 2, extraHchar: 1, emit: " " }],
|
|
18101
18581
|
[31, { extraBytes: 2, extraHchar: 1, emit: " " }]
|
|
18102
18582
|
]);
|
|
18103
18583
|
function parseHwp3Document(buffer, options) {
|
|
@@ -18130,18 +18610,18 @@ function parseHwp3Document(buffer, options) {
|
|
|
18130
18610
|
body = tail;
|
|
18131
18611
|
}
|
|
18132
18612
|
const bodyReader = new Reader(body);
|
|
18133
|
-
const ctx = {
|
|
18613
|
+
const ctx = { warnings, headerBlocks: [], footerBlocks: [], headerTexts: /* @__PURE__ */ new Set(), pageNumbers: 0, noteSeq: [0, 0] };
|
|
18614
|
+
const bodyBlocks = [];
|
|
18134
18615
|
try {
|
|
18135
18616
|
skipFontFacesAndStyles(bodyReader);
|
|
18136
|
-
parseParagraphList2(bodyReader, ctx);
|
|
18617
|
+
parseParagraphList2(bodyReader, ctx, bodyBlocks);
|
|
18137
18618
|
} catch (err) {
|
|
18138
18619
|
warnings.push({
|
|
18139
18620
|
code: "PARTIAL_PARSE",
|
|
18140
18621
|
message: `HWP3 paragraph stream \uB3C4\uC911 \uD30C\uC2F1 \uC911\uB2E8: ${err instanceof Error ? err.message : String(err)}`
|
|
18141
18622
|
});
|
|
18142
18623
|
}
|
|
18143
|
-
const
|
|
18144
|
-
const blocks = ctx.paragraphs.map((p) => ({ type: "paragraph", text: p }));
|
|
18624
|
+
const blocks = flattenLayoutTables([...ctx.headerBlocks, ...bodyBlocks, ...ctx.footerBlocks]);
|
|
18145
18625
|
const metadata = {
|
|
18146
18626
|
title: header.title || void 0,
|
|
18147
18627
|
author: header.author || void 0,
|
|
@@ -18150,7 +18630,7 @@ function parseHwp3Document(buffer, options) {
|
|
|
18150
18630
|
version: "3.0"
|
|
18151
18631
|
};
|
|
18152
18632
|
return {
|
|
18153
|
-
markdown:
|
|
18633
|
+
markdown: blocksToMarkdown(blocks),
|
|
18154
18634
|
blocks,
|
|
18155
18635
|
metadata,
|
|
18156
18636
|
warnings: warnings.length ? warnings : void 0
|
|
@@ -18165,7 +18645,7 @@ function skipFontFacesAndStyles(reader) {
|
|
|
18165
18645
|
const nStyles = reader.readU16();
|
|
18166
18646
|
reader.skip(nStyles * STYLE_RECORD_SIZE);
|
|
18167
18647
|
}
|
|
18168
|
-
function parseParagraphList2(reader, ctx) {
|
|
18648
|
+
function parseParagraphList2(reader, ctx, sink) {
|
|
18169
18649
|
for (; ; ) {
|
|
18170
18650
|
if (reader.eof()) return;
|
|
18171
18651
|
const followPrev = reader.readU8();
|
|
@@ -18195,40 +18675,77 @@ function parseParagraphList2(reader, ctx) {
|
|
|
18195
18675
|
if (flag !== 1) reader.skip(INLINE_CHAR_SHAPE_SIZE);
|
|
18196
18676
|
}
|
|
18197
18677
|
}
|
|
18678
|
+
const para2 = { text: "", objects: [], footnotes: [] };
|
|
18198
18679
|
try {
|
|
18199
|
-
|
|
18200
|
-
ctx.paragraphs.push(text);
|
|
18680
|
+
parseCharStream(reader, charCount, ctx, para2);
|
|
18201
18681
|
} catch (err) {
|
|
18682
|
+
sink.push(...emitHwp3Paragraph(para2));
|
|
18202
18683
|
ctx.warnings.push({
|
|
18203
18684
|
code: "PARTIAL_PARSE",
|
|
18204
|
-
message: `HWP3 paragraph
|
|
18685
|
+
message: `HWP3 paragraph char stream \uD30C\uC2F1 \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`
|
|
18205
18686
|
});
|
|
18206
18687
|
return;
|
|
18207
18688
|
}
|
|
18689
|
+
sink.push(...emitHwp3Paragraph(para2));
|
|
18208
18690
|
}
|
|
18209
18691
|
}
|
|
18210
|
-
function
|
|
18211
|
-
let
|
|
18692
|
+
function emitHwp3Paragraph(para2) {
|
|
18693
|
+
let text = para2.text;
|
|
18694
|
+
const leaderAt = text.indexOf(LEADER_TAB_MARK2);
|
|
18695
|
+
if (leaderAt >= 0) text = text.slice(0, leaderAt);
|
|
18696
|
+
let headMarker = null;
|
|
18697
|
+
if (para2.outline) {
|
|
18698
|
+
const { level, numbers } = para2.outline;
|
|
18699
|
+
const f = OUTLINE_FORMATS[level];
|
|
18700
|
+
const n = numbers[level];
|
|
18701
|
+
if (f && n > 0) headMarker = f.before + formatNumber(n, f.fmt) + f.after;
|
|
18702
|
+
text = text.replace(/^ +/, "");
|
|
18703
|
+
}
|
|
18704
|
+
return emitParagraphBlocks({ text: text.trim(), headMarker, headingLevel: 0, footnotes: para2.footnotes, objects: para2.objects });
|
|
18705
|
+
}
|
|
18706
|
+
function parseCharStream(reader, charCount, ctx, para2) {
|
|
18212
18707
|
let i = 0;
|
|
18213
18708
|
while (i < charCount) {
|
|
18214
18709
|
const ch = reader.readU16();
|
|
18215
18710
|
i += 1;
|
|
18216
18711
|
if (ch === 13) {
|
|
18217
|
-
|
|
18712
|
+
para2.text += "\n";
|
|
18218
18713
|
continue;
|
|
18219
18714
|
}
|
|
18220
18715
|
if (ch === 0) {
|
|
18221
18716
|
continue;
|
|
18222
18717
|
}
|
|
18223
18718
|
if (ch >= 32) {
|
|
18224
|
-
|
|
18719
|
+
para2.text += escapeLiteralDollar(decodeJohabText(ch) ?? "");
|
|
18720
|
+
continue;
|
|
18721
|
+
}
|
|
18722
|
+
if (ch === 9) {
|
|
18723
|
+
reader.skip(2);
|
|
18724
|
+
const leader = reader.readU16();
|
|
18725
|
+
reader.skip(2);
|
|
18726
|
+
i += 3;
|
|
18727
|
+
para2.text += leader ? LEADER_TAB_MARK2 : " ";
|
|
18728
|
+
continue;
|
|
18729
|
+
}
|
|
18730
|
+
if (ch === 18) {
|
|
18731
|
+
const info = reader.readBytes(6);
|
|
18732
|
+
i += 3;
|
|
18733
|
+
para2.text += autoNumberText(info.readUInt16LE(0), info.readUInt16LE(2), ctx);
|
|
18734
|
+
continue;
|
|
18735
|
+
}
|
|
18736
|
+
if (ch === 28) {
|
|
18737
|
+
const info = reader.readBytes(62);
|
|
18738
|
+
i += 31;
|
|
18739
|
+
if (info.readUInt16LE(0) === 1) {
|
|
18740
|
+
para2.outline = { level: Math.min(info[3], 6), numbers: Array.from({ length: 7 }, (_, k) => info.readUInt16LE(4 + k * 2)) };
|
|
18741
|
+
}
|
|
18225
18742
|
continue;
|
|
18226
18743
|
}
|
|
18227
18744
|
const simple = SIMPLE_CTRL.get(ch);
|
|
18228
18745
|
if (simple) {
|
|
18229
18746
|
reader.skip(simple.extraBytes);
|
|
18230
18747
|
i += simple.extraHchar;
|
|
18231
|
-
if (simple.emit)
|
|
18748
|
+
if (simple.emit) para2.text += simple.emit;
|
|
18232
18749
|
continue;
|
|
18233
18750
|
}
|
|
18234
18751
|
const headerVal1 = reader.readU32();
|
|
@@ -18236,27 +18753,31 @@ function parseCharStream(reader, charCount, ctx) {
|
|
|
18236
18753
|
i += 3;
|
|
18237
18754
|
switch (ch) {
|
|
18238
18755
|
case 10:
|
|
18239
|
-
|
|
18756
|
+
parseTableLike(reader, ctx, para2);
|
|
18240
18757
|
break;
|
|
18241
18758
|
case 11:
|
|
18242
|
-
parsePicture(reader, ctx);
|
|
18759
|
+
para2.objects.push({ blocks: parsePicture(reader, ctx), table: false, inline: false });
|
|
18243
18760
|
break;
|
|
18244
18761
|
case 14:
|
|
18245
18762
|
reader.skip(84);
|
|
18246
18763
|
break;
|
|
18247
18764
|
case 15: {
|
|
18248
18765
|
reader.skip(8);
|
|
18249
|
-
|
|
18766
|
+
const hidden = [];
|
|
18767
|
+
parseParagraphList2(reader, ctx, hidden);
|
|
18768
|
+
if (hidden.length > 0 && !ctx.warnings.some((w) => w.code === "HIDDEN_TEXT_FILTERED")) {
|
|
18769
|
+
ctx.warnings.push({ code: "HIDDEN_TEXT_FILTERED", message: "HWP3 \uC228\uC740 \uC124\uBA85 \uD14D\uC2A4\uD2B8 \uBCF8\uBB38 \uC81C\uC678" });
|
|
18770
|
+
}
|
|
18250
18771
|
break;
|
|
18251
18772
|
}
|
|
18252
18773
|
case 16: {
|
|
18253
|
-
reader.
|
|
18254
|
-
|
|
18774
|
+
const info = reader.readBytes(10);
|
|
18775
|
+
parseHeaderFooter(reader, ctx, info[8] === 1);
|
|
18255
18776
|
break;
|
|
18256
18777
|
}
|
|
18257
18778
|
case 17: {
|
|
18258
|
-
reader.
|
|
18259
|
-
|
|
18779
|
+
const info = reader.readBytes(14);
|
|
18780
|
+
parseNote(reader, ctx, para2, info.readUInt16LE(8), info.readUInt16LE(10) === 1 ? 1 : 0);
|
|
18260
18781
|
break;
|
|
18261
18782
|
}
|
|
18262
18783
|
case 5:
|
|
@@ -18284,9 +18805,38 @@ function parseCharStream(reader, charCount, ctx) {
|
|
|
18284
18805
|
break;
|
|
18285
18806
|
}
|
|
18286
18807
|
}
|
|
18287
|
-
|
|
18808
|
+
para2.text = para2.text.trim();
|
|
18288
18809
|
}
|
|
18289
|
-
function
|
|
18810
|
+
function autoNumberText(type, num4, ctx) {
|
|
18811
|
+
if (type === 0) {
|
|
18812
|
+
ctx.pageNumbers++;
|
|
18813
|
+
return "";
|
|
18814
|
+
}
|
|
18815
|
+
if (num4 <= 0) return " ";
|
|
18816
|
+
return type === 1 || type === 2 ? `${num4})` : String(num4);
|
|
18817
|
+
}
|
|
18818
|
+
function parseNote(reader, ctx, para2, number, kind) {
|
|
18819
|
+
const blocks = [];
|
|
18820
|
+
parseParagraphList2(reader, ctx, blocks);
|
|
18821
|
+
const n = number > 0 ? number : ++ctx.noteSeq[kind];
|
|
18822
|
+
const marker = `${n})`;
|
|
18823
|
+
para2.text += marker;
|
|
18824
|
+
const content = blocksPlainText(blocks, " ");
|
|
18825
|
+
if (content) para2.footnotes.push(content.startsWith(marker) ? content : `${marker} ${content}`);
|
|
18826
|
+
}
|
|
18827
|
+
function parseHeaderFooter(reader, ctx, isFooter) {
|
|
18828
|
+
const pagesBefore = ctx.pageNumbers;
|
|
18829
|
+
const blocks = [];
|
|
18830
|
+
parseParagraphList2(reader, ctx, blocks);
|
|
18831
|
+
const text = blocksPlainText(blocks, "\n");
|
|
18832
|
+
if (!text) return;
|
|
18833
|
+
if (ctx.pageNumbers > pagesBefore && !/\p{L}/u.test(text)) return;
|
|
18834
|
+
const key = (isFooter ? "f:" : "h:") + text;
|
|
18835
|
+
if (ctx.headerTexts.has(key)) return;
|
|
18836
|
+
ctx.headerTexts.add(key);
|
|
18837
|
+
(isFooter ? ctx.footerBlocks : ctx.headerBlocks).push({ type: "paragraph", text });
|
|
18838
|
+
}
|
|
18839
|
+
function parseTableLike(reader, ctx, para2) {
|
|
18290
18840
|
const info = reader.readBytes(84);
|
|
18291
18841
|
const cellCount = info.readUInt16LE(80) || 1;
|
|
18292
18842
|
if (27 * cellCount > reader.remaining()) {
|
|
@@ -18296,22 +18846,63 @@ function parseTableLike(reader, ctx) {
|
|
|
18296
18846
|
});
|
|
18297
18847
|
throw new Error(`HWP3 \uBE44\uC815\uC0C1 cell_count=${cellCount}`);
|
|
18298
18848
|
}
|
|
18299
|
-
reader.
|
|
18300
|
-
|
|
18301
|
-
|
|
18849
|
+
const cellInfo = reader.readBytes(27 * cellCount);
|
|
18850
|
+
const cellBlocks = [];
|
|
18851
|
+
try {
|
|
18852
|
+
for (let i = 0; i < cellCount; i++) {
|
|
18853
|
+
const blocks = [];
|
|
18854
|
+
cellBlocks.push(blocks);
|
|
18855
|
+
parseParagraphList2(reader, ctx, blocks);
|
|
18856
|
+
}
|
|
18857
|
+
} catch (err) {
|
|
18858
|
+
para2.objects.push({ blocks: cellBlocks.flat(), table: false, inline: false });
|
|
18859
|
+
throw err;
|
|
18860
|
+
}
|
|
18861
|
+
const captionBlocks = [];
|
|
18862
|
+
parseParagraphList2(reader, ctx, captionBlocks);
|
|
18863
|
+
const caption = blocksPlainText(captionBlocks, " ");
|
|
18864
|
+
if (info.readUInt16LE(78) === OBJ_EQUATION) {
|
|
18865
|
+
const script = (cellBlocks[0] ?? []).map((b) => b.text ?? "").join("\n").trim();
|
|
18866
|
+
const latex = script ? hwpEquationToLatex(script) : "";
|
|
18867
|
+
if (latex) para2.text += `$${latex.replace(/\$/g, "\\$")}$`;
|
|
18868
|
+
if (caption) para2.objects.push({ blocks: [{ type: "paragraph", text: caption }], table: false, inline: false });
|
|
18869
|
+
return;
|
|
18302
18870
|
}
|
|
18303
|
-
|
|
18304
|
-
|
|
18871
|
+
if (info.readUInt16LE(14) & OPT_HYPERTEXT) {
|
|
18872
|
+
para2.text += blocksPlainText(cellBlocks.flat(), " ");
|
|
18873
|
+
if (caption) para2.objects.push({ blocks: [{ type: "paragraph", text: caption }], table: false, inline: false });
|
|
18874
|
+
return;
|
|
18875
|
+
}
|
|
18876
|
+
const grid = hwp3CellGrid(cellInfo, cellCount);
|
|
18877
|
+
const cells = cellBlocks.map((blocks, k) => {
|
|
18878
|
+
const pos = grid.cells[k];
|
|
18879
|
+
const { text, hasStructure } = cellTextFromBlocks(blocks);
|
|
18880
|
+
const cell2 = { text, colSpan: pos.colSpan, rowSpan: pos.rowSpan, colAddr: pos.col, rowAddr: pos.row };
|
|
18881
|
+
if (hasStructure && blocks.length > 0) cell2.blocks = blocks;
|
|
18882
|
+
return cell2;
|
|
18883
|
+
});
|
|
18884
|
+
cells.sort((a, b) => a.rowAddr - b.rowAddr || a.colAddr - b.colAddr);
|
|
18885
|
+
const table2 = buildAddressedTable(cells, grid.rows, grid.cols);
|
|
18886
|
+
if (!table2) {
|
|
18887
|
+
para2.objects.push({ blocks: [...cellBlocks.flat(), ...captionBlocks], table: false, inline: false });
|
|
18888
|
+
return;
|
|
18889
|
+
}
|
|
18890
|
+
if (caption) table2.caption = caption;
|
|
18891
|
+
const inline = info[8] === 0;
|
|
18892
|
+
if (inline) para2.text += INLINE_TABLE_MARK;
|
|
18893
|
+
para2.objects.push({ blocks: [{ type: "table", table: table2 }], table: true, inline });
|
|
18305
18894
|
}
|
|
18306
18895
|
function parsePicture(reader, ctx) {
|
|
18307
18896
|
const info = reader.readBytes(348);
|
|
18308
18897
|
const nExt = info.readUInt32LE(0);
|
|
18309
18898
|
let ext = null;
|
|
18310
18899
|
if (nExt > 0 && nExt < 100 * 1024 * 1024) ext = reader.readBytes(nExt);
|
|
18311
|
-
|
|
18312
|
-
|
|
18900
|
+
const blocks = [];
|
|
18901
|
+
if (ext && info[74] === PIC_TYPE_DRAWING) parseDrawingObject(ext, ctx, blocks);
|
|
18902
|
+
parseParagraphList2(reader, ctx, blocks);
|
|
18903
|
+
return blocks;
|
|
18313
18904
|
}
|
|
18314
|
-
function parseDrawingObject(ext, ctx) {
|
|
18905
|
+
function parseDrawingObject(ext, ctx, sink) {
|
|
18315
18906
|
let lists;
|
|
18316
18907
|
try {
|
|
18317
18908
|
lists = collectDrawingTextBoxLists(ext);
|
|
@@ -18321,7 +18912,7 @@ function parseDrawingObject(ext, ctx) {
|
|
|
18321
18912
|
}
|
|
18322
18913
|
for (const list of lists) {
|
|
18323
18914
|
try {
|
|
18324
|
-
parseParagraphList2(new Reader(list), ctx);
|
|
18915
|
+
parseParagraphList2(new Reader(list), ctx, sink);
|
|
18325
18916
|
} catch (err) {
|
|
18326
18917
|
pushDrawingWarning(ctx, `HWP3 \uAE00\uC0C1\uC790 \uBB38\uB2E8 \uB9AC\uC2A4\uD2B8 \uD30C\uC2F1 \uC2E4\uD328: ${errText(err)}`);
|
|
18327
18918
|
}
|
|
@@ -22118,7 +22709,9 @@ function scanClickHereRegions(xml) {
|
|
|
22118
22709
|
placeholder: extractPlaceholder(fieldBeginXml),
|
|
22119
22710
|
start,
|
|
22120
22711
|
end,
|
|
22121
|
-
prefix
|
|
22712
|
+
prefix,
|
|
22713
|
+
beginTagStart: m.index,
|
|
22714
|
+
beginTagEnd: m.index + m[0].length
|
|
22122
22715
|
});
|
|
22123
22716
|
}
|
|
22124
22717
|
return regions;
|
|
@@ -22142,6 +22735,13 @@ function fillClickHereInXml(xml, cursor, blockedLabels) {
|
|
|
22142
22735
|
const current = xml.slice(region.start, region.end);
|
|
22143
22736
|
matchedKeys.add(key);
|
|
22144
22737
|
filled.push({ label: region.name, value, row: -1, col: -1, key, source: "clickhere" });
|
|
22738
|
+
if (value !== "") {
|
|
22739
|
+
const tag = xml.slice(region.beginTagStart, region.beginTagEnd);
|
|
22740
|
+
if (attrOf2(tag, "dirty") !== "1") {
|
|
22741
|
+
const dirtyTag = /\sdirty="[^"]*"/.test(tag) ? tag.replace(/(\sdirty=")[^"]*"/, (_, head) => head + '1"') : tag.replace(/(\/?>)$/, ' dirty="1"$1');
|
|
22742
|
+
splices.push({ start: region.beginTagStart, end: region.beginTagEnd, replacement: dirtyTag });
|
|
22743
|
+
}
|
|
22744
|
+
}
|
|
22145
22745
|
if (current === replacement) continue;
|
|
22146
22746
|
splices.push({ start: region.start, end: region.end, replacement });
|
|
22147
22747
|
}
|
|
@@ -24687,8 +25287,8 @@ function resolveLevels(levels, bodyHeight) {
|
|
|
24687
25287
|
}
|
|
24688
25288
|
return Object.keys(out).length > 0 ? out : null;
|
|
24689
25289
|
}
|
|
24690
|
-
function levelMarkerHeight(
|
|
24691
|
-
return
|
|
25290
|
+
function levelMarkerHeight(g2, depth) {
|
|
25291
|
+
return g2.levels?.[depth]?.height ?? g2.bodyHeight;
|
|
24692
25292
|
}
|
|
24693
25293
|
function incompatibleGongmunWarnings(opts) {
|
|
24694
25294
|
const warns = [];
|
|
@@ -25112,8 +25712,8 @@ function parseMarkdownToBlocks(md2) {
|
|
|
25112
25712
|
if (!l.trim() || /^\s*(`{3,}|~{3,})/.test(l)) break;
|
|
25113
25713
|
const end = findMathDelim(l, 0);
|
|
25114
25714
|
if (end >= 0) {
|
|
25115
|
-
const
|
|
25116
|
-
if (
|
|
25715
|
+
const before2 = l.slice(0, end);
|
|
25716
|
+
if (before2.trim()) mathLines.push(before2);
|
|
25117
25717
|
trailing = l.slice(end + 2).trim();
|
|
25118
25718
|
closed = true;
|
|
25119
25719
|
j++;
|
|
@@ -25254,7 +25854,7 @@ function parseMarkdownToBlocks(md2) {
|
|
|
25254
25854
|
}
|
|
25255
25855
|
function parseInlineMarkdown(text) {
|
|
25256
25856
|
const literals = [];
|
|
25257
|
-
text = text.replace(/\x00/g, "").replace(/\\([\\`*_{}[\]()
|
|
25857
|
+
text = text.replace(/\x00/g, "").replace(/\\([\\`*_{}[\]()#+\-.!|<>~$])/g, (_, c) => {
|
|
25258
25858
|
literals.push(c);
|
|
25259
25859
|
return `\0${literals.length - 1}\0`;
|
|
25260
25860
|
});
|
|
@@ -26209,16 +26809,16 @@ function table(rows, w, h, cols, tblBf = 1, bodyWidth = GAEJOSIK_BASE_WIDTH) {
|
|
|
26209
26809
|
return `<hp:tbl id="${++gjTableId}" zOrder="0" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="0" rowCnt="${rows.length}" colCnt="${cols}" cellSpacing="0" borderFillIDRef="${tblBf}" noAdjust="1"><hp:sz width="${w}" widthRelTo="ABSOLUTE" height="${h}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="${outLR}" right="${outLR}" top="283" bottom="283"/><hp:inMargin left="141" right="141" top="141" bottom="141"/>` + rows.map((r) => `<hp:tr>${r}</hp:tr>`).join("") + `</hp:tbl>`;
|
|
26210
26810
|
}
|
|
26211
26811
|
function buildGaejosikCover(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
|
|
26212
|
-
const
|
|
26812
|
+
const g2 = coverGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth);
|
|
26213
26813
|
const sz = gaejosikSizes(gongmun.bodyHeight, gongmun.sizes);
|
|
26214
|
-
const innerW =
|
|
26814
|
+
const innerW = g2.totalW - 282;
|
|
26215
26815
|
const titleCharPr = simulateWrap(title, innerW, innerW, sz.coverTitle, 100, "keep").lines > 2 ? GJ_CHAR_COVER_SUB : GJ_CHAR_COVER_TITLE;
|
|
26216
26816
|
const titlePara = `<hp:p paraPrIDRef="${GJ_PARA_COVER}" styleIDRef="0"><hp:run charPrIDRef="${titleCharPr}"><hp:t>${escapeTextXml(title)}</hp:t></hp:run></hp:p>`;
|
|
26217
26817
|
const barEmpty = emptyPara(GJ_PARA_BAR, false, GJ_CHAR_BAR);
|
|
26218
|
-
const topRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, colSpan: 2, row: 0, w:
|
|
26219
|
-
const titleRow = cell({ bf: 1, col: 0, colSpan: 3, row: 1, w:
|
|
26220
|
-
const botRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, row: 2, w:
|
|
26221
|
-
const tbl2 = table([topRow, titleRow, botRow],
|
|
26818
|
+
const topRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, colSpan: 2, row: 0, w: g2.topDarkW, h: g2.barH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 2, row: 0, w: g2.topLightW, h: g2.barH, paras: barEmpty });
|
|
26819
|
+
const titleRow = cell({ bf: 1, col: 0, colSpan: 3, row: 1, w: g2.totalW, h: g2.titleH, paras: titlePara, name: "__kordoc_h1" });
|
|
26820
|
+
const botRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, row: 2, w: g2.botDarkW, h: g2.botBarH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 1, colSpan: 2, row: 2, w: g2.botLightW, h: g2.botBarH, paras: barEmpty });
|
|
26821
|
+
const tbl2 = table([topRow, titleRow, botRow], g2.totalW, g2.barH + g2.titleH + g2.botBarH, 3, 1, bodyWidth);
|
|
26222
26822
|
const host2 = `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${tbl2}</hp:run></hp:p>`;
|
|
26223
26823
|
const date = gongmun.cover?.date ?? formatGaejosikDate(/* @__PURE__ */ new Date());
|
|
26224
26824
|
const org = gongmun.cover?.org ?? "";
|
|
@@ -26233,14 +26833,14 @@ function buildGaejosikCover(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
|
|
|
26233
26833
|
];
|
|
26234
26834
|
}
|
|
26235
26835
|
function buildTocBanner(gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
|
|
26236
|
-
const
|
|
26836
|
+
const g2 = tocBannerGeom(gongmun.bodyHeight, gongmun.sizes);
|
|
26237
26837
|
const bfs = [GJ_BF_BAR_DARK, 1, GJ_BF_TOC_STRIPE, 1, GJ_BF_TOC_STRIPE, 1, GJ_BF_BAR_DARK];
|
|
26238
26838
|
const filler = emptyPara(GJ_PARA_BAR, false, GJ_CHAR_BAR);
|
|
26239
26839
|
const label = `<hp:p paraPrIDRef="${GONGMUN_CENTER}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_TOC_LABEL}"><hp:t>\uBAA9 \uCC28</hp:t></hp:run></hp:p>`;
|
|
26240
|
-
const row =
|
|
26241
|
-
(w, c) => cell({ bf: bfs[c], col: c, w, h:
|
|
26840
|
+
const row = g2.colWs.map(
|
|
26841
|
+
(w, c) => cell({ bf: bfs[c], col: c, w, h: g2.h, paras: c === 3 ? label : filler, name: c === 3 ? "__kordoc_toc" : void 0 })
|
|
26242
26842
|
).join("");
|
|
26243
|
-
const banner = table([row],
|
|
26843
|
+
const banner = table([row], g2.colWs.reduce((a, b) => a + b, 0), g2.h, 7, 2, bodyWidth);
|
|
26244
26844
|
return `<hp:p paraPrIDRef="${GJ_PARA_BAR}" styleIDRef="0" pageBreak="1"><hp:run charPrIDRef="${CHAR_NORMAL}">${banner}</hp:run></hp:p>`;
|
|
26245
26845
|
}
|
|
26246
26846
|
function buildGaejosikToc(chapters, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
|
|
@@ -26261,22 +26861,22 @@ function buildGaejosikToc(chapters, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
|
|
|
26261
26861
|
return [label, emptyPara(GJ_PARA_BAR, false, GJ_CHAR_BAR), host2];
|
|
26262
26862
|
}
|
|
26263
26863
|
function buildGaejosikBodyTitle(title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH) {
|
|
26264
|
-
const
|
|
26864
|
+
const g2 = coverGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth);
|
|
26265
26865
|
const bt = bodyTitleGeom(gongmun.bodyHeight, gongmun.sizes);
|
|
26266
26866
|
const barEmpty = emptyPara(GJ_PARA_BAR, false, GJ_CHAR_BAR);
|
|
26267
26867
|
const titlePara = `<hp:p paraPrIDRef="${GONGMUN_CENTER}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_BODY_TITLE}"><hp:t>${escapeTextXml(title)}</hp:t></hp:run></hp:p>`;
|
|
26268
|
-
const topRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, colSpan: 2, row: 0, w:
|
|
26269
|
-
const titleRow = cell({ bf: 1, col: 0, colSpan: 3, row: 1, w:
|
|
26270
|
-
const botRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, row: 2, w:
|
|
26271
|
-
const box = table([topRow, titleRow, botRow],
|
|
26868
|
+
const topRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, colSpan: 2, row: 0, w: g2.topDarkW, h: bt.barH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 2, row: 0, w: g2.topLightW, h: bt.barH, paras: barEmpty });
|
|
26869
|
+
const titleRow = cell({ bf: 1, col: 0, colSpan: 3, row: 1, w: g2.totalW, h: bt.titleH, paras: titlePara, name: "__kordoc_skip" });
|
|
26870
|
+
const botRow = cell({ bf: GJ_BF_BAR_DARK, col: 0, row: 2, w: g2.botDarkW, h: bt.barH, paras: barEmpty }) + cell({ bf: GJ_BF_BAR_LIGHT, col: 1, colSpan: 2, row: 2, w: g2.botLightW, h: bt.barH, paras: barEmpty });
|
|
26871
|
+
const box = table([topRow, titleRow, botRow], g2.totalW, bt.barH * 2 + bt.titleH, 3, 1, bodyWidth);
|
|
26272
26872
|
return `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${box}</hp:run></hp:p>`;
|
|
26273
26873
|
}
|
|
26274
26874
|
function buildGaejosikChapter(n, title, gongmun, bodyWidth = GAEJOSIK_BASE_WIDTH, srcLevel = 2) {
|
|
26275
|
-
const
|
|
26875
|
+
const g2 = gongmun ? chapterGeom(gongmun.bodyHeight, gongmun.sizes, bodyWidth) : chapterGeom(1500, {}, bodyWidth);
|
|
26276
26876
|
const numPara = `<hp:p paraPrIDRef="${GONGMUN_CENTER}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_CHAPTER_NUM}"><hp:t>${chapterRoman(n)}</hp:t></hp:run></hp:p>`;
|
|
26277
26877
|
const titlePara = `<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${GJ_CHAR_CHAPTER_TITLE}"><hp:t> ${escapeTextXml(title)}</hp:t></hp:run></hp:p>`;
|
|
26278
|
-
const row = cell({ bf: GJ_BF_CHAPTER_NUM, col: 0, w:
|
|
26279
|
-
const tbl2 = table([row],
|
|
26878
|
+
const row = cell({ bf: GJ_BF_CHAPTER_NUM, col: 0, w: g2.numW, h: g2.rowH, paras: numPara }) + cell({ bf: GJ_BF_CHAPTER_GAP, col: 1, w: g2.gapW, h: g2.rowH }) + cell({ bf: GJ_BF_CHAPTER_TITLE, col: 2, w: g2.titleW, h: g2.rowH, paras: titlePara, name: `__kordoc_h${Math.min(Math.max(srcLevel, 1), 6)}` });
|
|
26879
|
+
const tbl2 = table([row], g2.numW + g2.gapW + g2.titleW, g2.rowH, 3, 1, bodyWidth);
|
|
26280
26880
|
return `<hp:p paraPrIDRef="${GJ_PARA_CHAPTER}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${tbl2}</hp:run></hp:p>`;
|
|
26281
26881
|
}
|
|
26282
26882
|
|
|
@@ -26299,15 +26899,15 @@ function approxDistance(a, b) {
|
|
|
26299
26899
|
const bigramCounts = (s) => {
|
|
26300
26900
|
const m = /* @__PURE__ */ new Map();
|
|
26301
26901
|
for (let i = 0; i < s.length - 1; i++) {
|
|
26302
|
-
const
|
|
26303
|
-
m.set(
|
|
26902
|
+
const g2 = s.slice(i, i + 2);
|
|
26903
|
+
m.set(g2, (m.get(g2) ?? 0) + 1);
|
|
26304
26904
|
}
|
|
26305
26905
|
return m;
|
|
26306
26906
|
};
|
|
26307
26907
|
const ca = bigramCounts(a);
|
|
26308
26908
|
const cb = bigramCounts(b);
|
|
26309
26909
|
let inter = 0;
|
|
26310
|
-
for (const [
|
|
26910
|
+
for (const [g2, n] of ca) inter += Math.min(n, cb.get(g2) ?? 0);
|
|
26311
26911
|
const total = Math.max(a.length - 1, 0) + Math.max(b.length - 1, 0);
|
|
26312
26912
|
const dice = total > 0 ? 2 * inter / total : 1;
|
|
26313
26913
|
return Math.round(Math.max(a.length, b.length) * (1 - dice));
|
|
@@ -26465,16 +27065,6 @@ function bestSimInRange(arr, from, to, target) {
|
|
|
26465
27065
|
}
|
|
26466
27066
|
return best;
|
|
26467
27067
|
}
|
|
26468
|
-
function escapeGfm(text) {
|
|
26469
|
-
const NUL = String.fromCharCode(0);
|
|
26470
|
-
const spans = [];
|
|
26471
|
-
const masked = text.replace(/!\[[^\]]*\]\([^)\n]*\)|\]\((?:https?:|mailto:|tel:|#)[^)\n]*\)/gi, (m) => {
|
|
26472
|
-
spans.push(m);
|
|
26473
|
-
return NUL + (spans.length - 1) + NUL;
|
|
26474
|
-
});
|
|
26475
|
-
const escaped = masked.replace(/([~*_`])/g, "\\$1");
|
|
26476
|
-
return escaped.replace(new RegExp(NUL + "(\\d+)" + NUL, "g"), (_, n) => spans[Number(n)]);
|
|
26477
|
-
}
|
|
26478
27068
|
var HWP_SHAPE_ALT_TEXT_RE = /^(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?$/gm;
|
|
26479
27069
|
function sanitizeText(text) {
|
|
26480
27070
|
let result = mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
|
|
@@ -26488,10 +27078,10 @@ function sanitizeText(text) {
|
|
|
26488
27078
|
return result;
|
|
26489
27079
|
}
|
|
26490
27080
|
function normForMatch(text) {
|
|
26491
|
-
return sanitizeText(text).replace(/\s+/g, " ").trim();
|
|
27081
|
+
return sanitizeText(text.replace(/\\\$/g, "$")).replace(/\s+/g, " ").trim();
|
|
26492
27082
|
}
|
|
26493
27083
|
function unescapeGfm(text) {
|
|
26494
|
-
return text.replace(/\\([~*_
|
|
27084
|
+
return text.replace(/\\([~*_`|#<$\\])/g, "$1");
|
|
26495
27085
|
}
|
|
26496
27086
|
function summarize(text) {
|
|
26497
27087
|
const t = text.replace(/\s+/g, " ").trim();
|
|
@@ -26510,7 +27100,7 @@ function replicateGfmTable(table2) {
|
|
|
26510
27100
|
const cell2 = cells[r]?.[c];
|
|
26511
27101
|
if (!cell2) continue;
|
|
26512
27102
|
display[r][c] = {
|
|
26513
|
-
text: escapeGfm(sanitizeText(cell2.text)).replace(
|
|
27103
|
+
text: escapeGfm(sanitizeText(cell2.text)).replace(/(?<!\\)\|/g, "\\|").replace(/\n/g, "<br>"),
|
|
26514
27104
|
gridR: r,
|
|
26515
27105
|
gridC: c
|
|
26516
27106
|
};
|
|
@@ -26558,7 +27148,7 @@ function parseGfmTable(lines) {
|
|
|
26558
27148
|
return rows;
|
|
26559
27149
|
}
|
|
26560
27150
|
function unescapeGfmCell(text) {
|
|
26561
|
-
return text.replace(
|
|
27151
|
+
return text.replace(/(?<!\\)<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\([~*_`#<$\\])/g, "$1");
|
|
26562
27152
|
}
|
|
26563
27153
|
function replicateCellInnerHtml(cell2) {
|
|
26564
27154
|
if (cell2.blocks?.length) {
|
|
@@ -26569,7 +27159,7 @@ function replicateCellInnerHtml(cell2) {
|
|
|
26569
27159
|
}
|
|
26570
27160
|
if (b.type === "image" && b.text) return `<img src="${b.text}" alt="image">`;
|
|
26571
27161
|
const t = sanitizeText(b.text ?? "");
|
|
26572
|
-
return t ? t.replace(/\n/g, "<br>") : "";
|
|
27162
|
+
return t ? (t + noteSuffix(b)).replace(/\n/g, "<br>") : "";
|
|
26573
27163
|
}).filter(Boolean).join("<br>");
|
|
26574
27164
|
}
|
|
26575
27165
|
return sanitizeText(cell2.text).replace(/\n/g, "<br>");
|
|
@@ -26586,7 +27176,7 @@ function replicateTableToHtml(table2) {
|
|
|
26586
27176
|
const attrStr = attrs.length ? " " + attrs.join(" ") : "";
|
|
26587
27177
|
return `<${tag}${attrStr}>${cell2.inner}</${tag}>`;
|
|
26588
27178
|
});
|
|
26589
|
-
|
|
27179
|
+
lines.push(`<tr>${rowHtml.join("")}</tr>`);
|
|
26590
27180
|
}
|
|
26591
27181
|
lines.push("</table>");
|
|
26592
27182
|
return lines.join("\n");
|
|
@@ -26616,7 +27206,7 @@ function replicateHtmlTable(table2) {
|
|
|
26616
27206
|
gridC: c
|
|
26617
27207
|
});
|
|
26618
27208
|
}
|
|
26619
|
-
|
|
27209
|
+
result.push({ tag, cells: rowCells });
|
|
26620
27210
|
}
|
|
26621
27211
|
return result;
|
|
26622
27212
|
}
|
|
@@ -27663,8 +28253,8 @@ function buildTitleBox(title, titleCharPr, barCharPr, bodyWidth, reg) {
|
|
|
27663
28253
|
function docframeIds(base) {
|
|
27664
28254
|
return { org: base, sender: base + 1, foot: base + 2, small: base + 3, pressLabel: base + 4, pressHead: base + 5, pressTitle: base + 6, pressSub: base + 7, pressContact: base + 8 };
|
|
27665
28255
|
}
|
|
27666
|
-
function docframeActive(
|
|
27667
|
-
return !!(
|
|
28256
|
+
function docframeActive(g2) {
|
|
28257
|
+
return !!(g2.docHead || g2.docFoot || g2.reportInfo || g2.noticeHead || g2.press);
|
|
27668
28258
|
}
|
|
27669
28259
|
function docframeCharPrXmls(base, richAssets) {
|
|
27670
28260
|
const body = richAssets ? 4 : 0;
|
|
@@ -27687,15 +28277,15 @@ function blank() {
|
|
|
27687
28277
|
function isInternalApproval(to) {
|
|
27688
28278
|
return !!to && /^\s*내\s*부\s*결\s*재/.test(to);
|
|
27689
28279
|
}
|
|
27690
|
-
function buildReportInfo(
|
|
27691
|
-
return para(
|
|
28280
|
+
function buildReportInfo(g2, ids) {
|
|
28281
|
+
return para(g2.reportInfo, GONGMUN_RIGHT, ids.small);
|
|
27692
28282
|
}
|
|
27693
|
-
function buildNoticeHead(
|
|
27694
|
-
const n =
|
|
28283
|
+
function buildNoticeHead(g2) {
|
|
28284
|
+
const n = g2.noticeHead;
|
|
27695
28285
|
return n.no ? [para(n.no, PARA_NORMAL, CHAR_BOLD), blank()] : [];
|
|
27696
28286
|
}
|
|
27697
|
-
function buildNoticeFoot(
|
|
27698
|
-
const n =
|
|
28287
|
+
function buildNoticeFoot(g2) {
|
|
28288
|
+
const n = g2.noticeHead;
|
|
27699
28289
|
const out = [];
|
|
27700
28290
|
if (n.date) out.push(blank(), para(n.date, GONGMUN_RIGHT, CHAR_NORMAL));
|
|
27701
28291
|
if (n.sender) out.push(blank(), para(n.sender, GONGMUN_RIGHT, CHAR_BOLD));
|
|
@@ -27703,8 +28293,8 @@ function buildNoticeFoot(g) {
|
|
|
27703
28293
|
}
|
|
27704
28294
|
var PRESS_LABEL_H = 3200;
|
|
27705
28295
|
var PRESS_HEAD_H = 900;
|
|
27706
|
-
function buildPressHead(
|
|
27707
|
-
const p =
|
|
28296
|
+
function buildPressHead(g2, ids, bodyWidth, reg) {
|
|
28297
|
+
const p = g2.press;
|
|
27708
28298
|
const w = bodyWidth - 280;
|
|
27709
28299
|
const thick = { t: "thick", b: "thick", l: "thick", r: "thick" };
|
|
27710
28300
|
const rows = [tc({ bf: reg.get({ ...thick, b: "thin" }), row: 0, col: 0, w, h: PRESS_LABEL_H, paras: para("\uBCF4\uB3C4\uC790\uB8CC", GONGMUN_TBL_CENTER, ids.pressLabel) })];
|
|
@@ -27716,8 +28306,8 @@ function buildPressHead(g, ids, bodyWidth, reg) {
|
|
|
27716
28306
|
const table2 = tbl(rows, w, PRESS_LABEL_H + (headLine ? PRESS_HEAD_H : 0), 1);
|
|
27717
28307
|
return [`<hp:p paraPrIDRef="${PARA_NORMAL}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_NORMAL}">${table2}</hp:run></hp:p>`, blank()];
|
|
27718
28308
|
}
|
|
27719
|
-
function buildPressContact(
|
|
27720
|
-
const c =
|
|
28309
|
+
function buildPressContact(g2, ids, bodyWidth, reg) {
|
|
28310
|
+
const c = g2.press?.contact;
|
|
27721
28311
|
if (!c || !c.dept && !c.manager && !c.phone) return [];
|
|
27722
28312
|
const w = bodyWidth - 280;
|
|
27723
28313
|
const cells = [["\uB2F4\uB2F9 \uBD80\uC11C", c.dept ?? ""], ["\uB2F4\uB2F9\uC790", c.manager ?? ""], ["\uC5F0\uB77D\uCC98", c.phone ?? ""]];
|
|
@@ -28518,19 +29108,19 @@ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? prec
|
|
|
28518
29108
|
}
|
|
28519
29109
|
|
|
28520
29110
|
// src/hwpx/gen-levels.ts
|
|
28521
|
-
function levelDepths(
|
|
28522
|
-
return
|
|
29111
|
+
function levelDepths(g2) {
|
|
29112
|
+
return g2.levels ? Object.keys(g2.levels).map(Number).sort((a, b) => a - b) : [];
|
|
28523
29113
|
}
|
|
28524
|
-
function levelFontFaces(
|
|
29114
|
+
function levelFontFaces(g2) {
|
|
28525
29115
|
const out = [];
|
|
28526
|
-
for (const d of levelDepths(
|
|
28527
|
-
const f =
|
|
29116
|
+
for (const d of levelDepths(g2)) {
|
|
29117
|
+
const f = g2.levels[d].font;
|
|
28528
29118
|
if (f && !out.includes(f)) out.push(f);
|
|
28529
29119
|
}
|
|
28530
29120
|
return out;
|
|
28531
29121
|
}
|
|
28532
|
-
function levelCharIds(
|
|
28533
|
-
const depths = levelDepths(
|
|
29122
|
+
function levelCharIds(g2, base) {
|
|
29123
|
+
const depths = levelDepths(g2);
|
|
28534
29124
|
if (depths.length === 0) return null;
|
|
28535
29125
|
const ids = {};
|
|
28536
29126
|
depths.forEach((d, i) => {
|
|
@@ -28538,13 +29128,13 @@ function levelCharIds(g, base) {
|
|
|
28538
29128
|
});
|
|
28539
29129
|
return ids;
|
|
28540
29130
|
}
|
|
28541
|
-
function levelCharPrXmls(
|
|
28542
|
-
const ids = levelCharIds(
|
|
29131
|
+
function levelCharPrXmls(g2, base, fontBase, bodyFontId) {
|
|
29132
|
+
const ids = levelCharIds(g2, base);
|
|
28543
29133
|
if (!ids) return [];
|
|
28544
|
-
const faces = levelFontFaces(
|
|
29134
|
+
const faces = levelFontFaces(g2);
|
|
28545
29135
|
const out = [];
|
|
28546
|
-
for (const d of levelDepths(
|
|
28547
|
-
const st =
|
|
29136
|
+
for (const d of levelDepths(g2)) {
|
|
29137
|
+
const st = g2.levels[d];
|
|
28548
29138
|
const fontId = st.font ? fontBase + faces.indexOf(st.font) : bodyFontId;
|
|
28549
29139
|
out.push(charPr(ids[d].normal, st.height, st.bold, false, fontId, void 0, GONGMUN_BODY_RATIO, fontId));
|
|
28550
29140
|
out.push(charPr(ids[d].bold, st.height, true, false, fontId, void 0, GONGMUN_BODY_RATIO, fontId));
|
|
@@ -28738,25 +29328,25 @@ function seoulLegalScheme(bodyFont = "\uAD74\uB9BC\uCCB4", bodyPt = 12, lineSp =
|
|
|
28738
29328
|
blankBetweenTop: false
|
|
28739
29329
|
};
|
|
28740
29330
|
}
|
|
28741
|
-
function applySchemeOverrides(s,
|
|
29331
|
+
function applySchemeOverrides(s, g2) {
|
|
28742
29332
|
const out = { ...s, levels: s.levels.map((l) => ({ ...l })), body: { ...s.body }, ref: { ...s.ref }, table: { ...s.table } };
|
|
28743
|
-
if (
|
|
28744
|
-
out.levels[0].font =
|
|
28745
|
-
out.chapter = { ...out.chapter, font:
|
|
28746
|
-
}
|
|
28747
|
-
if (
|
|
28748
|
-
out.body.font =
|
|
28749
|
-
if (s.kind === "legal") for (const l of out.levels) l.font =
|
|
28750
|
-
else out.levels[1].font =
|
|
28751
|
-
}
|
|
28752
|
-
if (
|
|
28753
|
-
if (
|
|
28754
|
-
if (
|
|
28755
|
-
for (const [k, st] of Object.entries(
|
|
29333
|
+
if (g2.fonts.heading) {
|
|
29334
|
+
out.levels[0].font = g2.fonts.heading;
|
|
29335
|
+
out.chapter = { ...out.chapter, font: g2.fonts.heading };
|
|
29336
|
+
}
|
|
29337
|
+
if (g2.fonts.body) {
|
|
29338
|
+
out.body.font = g2.fonts.body;
|
|
29339
|
+
if (s.kind === "legal") for (const l of out.levels) l.font = g2.fonts.body;
|
|
29340
|
+
else out.levels[1].font = g2.fonts.body;
|
|
29341
|
+
}
|
|
29342
|
+
if (g2.fonts.ref) out.ref.font = g2.fonts.ref;
|
|
29343
|
+
if (g2.fonts.table) out.table.font = g2.fonts.table;
|
|
29344
|
+
if (g2.levels) {
|
|
29345
|
+
for (const [k, st] of Object.entries(g2.levels)) {
|
|
28756
29346
|
const l = out.levels[Number(k)];
|
|
28757
29347
|
if (!l) continue;
|
|
28758
29348
|
if (st.font) l.font = st.font;
|
|
28759
|
-
if (st.height !==
|
|
29349
|
+
if (st.height !== g2.bodyHeight) l.pt = st.height / 100;
|
|
28760
29350
|
l.bold = st.bold;
|
|
28761
29351
|
}
|
|
28762
29352
|
}
|
|
@@ -28765,13 +29355,13 @@ function applySchemeOverrides(s, g) {
|
|
|
28765
29355
|
function taHu(pt2) {
|
|
28766
29356
|
return Math.round(pt2 * 50);
|
|
28767
29357
|
}
|
|
28768
|
-
function pickScheme(
|
|
28769
|
-
if (
|
|
28770
|
-
return applySchemeOverrides(ministryScheme(
|
|
29358
|
+
function pickScheme(g2, bodyHasBoxMarkers) {
|
|
29359
|
+
if (g2.preset === "ministry") {
|
|
29360
|
+
return applySchemeOverrides(ministryScheme(g2.bodyPtExplicit ? g2.bodyHeight / 100 : 15, g2.lineSpacingExplicit ? g2.lineSpacing : 145), g2);
|
|
28771
29361
|
}
|
|
28772
|
-
const gaejosik =
|
|
28773
|
-
const base = gaejosik ? seoulGaejosikScheme(
|
|
28774
|
-
return applySchemeOverrides(base,
|
|
29362
|
+
const gaejosik = g2.numbering !== "standard" || bodyHasBoxMarkers;
|
|
29363
|
+
const base = gaejosik ? seoulGaejosikScheme(g2.bodyPtExplicit ? g2.bodyHeight / 100 : 15, g2.lineSpacingExplicit ? g2.lineSpacing : 180, g2.bullet2) : seoulLegalScheme(g2.fonts.body ?? (g2.bodyFontExplicit ? g2.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5" : "\uAD74\uB9BC\uCCB4"), g2.bodyPtExplicit ? g2.bodyHeight / 100 : 12, g2.lineSpacingExplicit ? g2.lineSpacing : 160);
|
|
29364
|
+
return applySchemeOverrides(base, g2);
|
|
28775
29365
|
}
|
|
28776
29366
|
|
|
28777
29367
|
// src/hwpx/outline.ts
|
|
@@ -29104,7 +29694,7 @@ function neighbor(s, i, dir) {
|
|
|
29104
29694
|
for (let k = i + dir; k >= 0 && k < s.length; k += dir) if (!/[*_~]/.test(s[k])) return s[k];
|
|
29105
29695
|
return "";
|
|
29106
29696
|
}
|
|
29107
|
-
function curlQuotes(s,
|
|
29697
|
+
function curlQuotes(s, before2 = "") {
|
|
29108
29698
|
let out = "";
|
|
29109
29699
|
for (let i = 0; i < s.length; i++) {
|
|
29110
29700
|
const c = s[i];
|
|
@@ -29112,7 +29702,7 @@ function curlQuotes(s, before = "") {
|
|
|
29112
29702
|
out += c;
|
|
29113
29703
|
continue;
|
|
29114
29704
|
}
|
|
29115
|
-
const prev = neighbor(s, i, -1) ||
|
|
29705
|
+
const prev = neighbor(s, i, -1) || before2;
|
|
29116
29706
|
const opening = prev === "" || OPENER_BEFORE.test(prev);
|
|
29117
29707
|
if (c === "'") {
|
|
29118
29708
|
if (opening && /^\d{2}(?![\d'])/.test(s.slice(i + 1))) {
|
|
@@ -29333,7 +29923,7 @@ function buildReportTitleTable(title, contact, ctx) {
|
|
|
29333
29923
|
return { xml: host(ftbl(rows, w, h, 1, { bottomGap: 600 }), reg.para({ align: "CENTER", lineSp: 100 }), cTitle), overflow: fit.overflow };
|
|
29334
29924
|
}
|
|
29335
29925
|
var CHAPTER_BAND_DEFAULT = { color: "#003366", textColor: "#FFFFFF", numFont: "HY\uD5E4\uB4DC\uB77C\uC778M", numPt: 17 };
|
|
29336
|
-
function buildChapterBand(roman, title, ctx, spec,
|
|
29926
|
+
function buildChapterBand(roman, title, ctx, spec, before2) {
|
|
29337
29927
|
const { reg, bf } = ctx;
|
|
29338
29928
|
const w = ctx.W;
|
|
29339
29929
|
const numW = 3100, gap = 400, h = 2600;
|
|
@@ -29347,7 +29937,7 @@ function buildChapterBand(roman, title, ctx, spec, before) {
|
|
|
29347
29937
|
const center = reg.para({ align: "CENTER", lineSp: 130 });
|
|
29348
29938
|
const left = reg.para({ align: "LEFT", lineSp: 130, left: 300 });
|
|
29349
29939
|
const row = tc({ bf: numBf, row: 0, col: 0, w: numW, h, paras: para(roman, center, cNum), vAlign: "CENTER" }) + tc({ bf: gapBf, row: 0, col: 1, w: gap, h, paras: para("", center, cNum), vAlign: "CENTER" }) + tc({ bf: titleBf, row: 0, col: 2, w: titleW, h, paras: para(title, left, cTitle), name: "__kordoc_h2", vAlign: "CENTER" });
|
|
29350
|
-
return { xml: host(ftbl([row], w, h, 3, { bottomGap: 1e3 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cTitle), overflow: fit.overflow };
|
|
29940
|
+
return { xml: host(ftbl([row], w, h, 3, { bottomGap: 1e3 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cTitle), overflow: fit.overflow };
|
|
29351
29941
|
}
|
|
29352
29942
|
var SUMMARY_PAD = 1e3;
|
|
29353
29943
|
function buildSummaryBox(text, ctx) {
|
|
@@ -29552,7 +30142,7 @@ function romanOf(n) {
|
|
|
29552
30142
|
}
|
|
29553
30143
|
return s;
|
|
29554
30144
|
}
|
|
29555
|
-
function buildMinistryChapterBand(roman, title, ctx,
|
|
30145
|
+
function buildMinistryChapterBand(roman, title, ctx, before2) {
|
|
29556
30146
|
const { reg, bf } = ctx;
|
|
29557
30147
|
const c = MINISTRY.chapter;
|
|
29558
30148
|
const w = ctx.W;
|
|
@@ -29562,9 +30152,9 @@ function buildMinistryChapterBand(roman, title, ctx, before) {
|
|
|
29562
30152
|
const cT = reg.char({ font: MINISTRY.headFont, pt: fit.pt, ratio: fit.ratio, spacing: fit.spacing });
|
|
29563
30153
|
const p = reg.para({ align: "LEFT", lineSp: 130, left: 500 });
|
|
29564
30154
|
const cell2 = tc({ bf: band, row: 0, col: 0, w, h: c.h, paras: para(text, p, cT), name: "__kordoc_h2" });
|
|
29565
|
-
return { xml: host(ftbl([cell2], w, c.h, 1, { bottomGap: 800 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT), overflow: fit.overflow };
|
|
30155
|
+
return { xml: host(ftbl([cell2], w, c.h, 1, { bottomGap: 800 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cT), overflow: fit.overflow };
|
|
29566
30156
|
}
|
|
29567
|
-
function buildMinistrySectionBand(n, title, ctx,
|
|
30157
|
+
function buildMinistrySectionBand(n, title, ctx, before2) {
|
|
29568
30158
|
const { reg, bf } = ctx;
|
|
29569
30159
|
const s = MINISTRY.section;
|
|
29570
30160
|
const t = plain(title);
|
|
@@ -29576,9 +30166,9 @@ function buildMinistrySectionBand(n, title, ctx, before) {
|
|
|
29576
30166
|
const center = reg.para({ align: "CENTER", lineSp: 130 });
|
|
29577
30167
|
const left = reg.para({ align: "LEFT", lineSp: 130, left: 300 });
|
|
29578
30168
|
const row = tc({ bf: numBf, row: 0, col: 0, w: s.numW, h: s.h, paras: para(String(n), center, cNum) }) + tc({ bf: titleBf, row: 0, col: 1, w: titleW, h: s.h, paras: para(t, left, cT), name: "__kordoc_h3" });
|
|
29579
|
-
return host(ftbl([row], s.numW + titleW, s.h, 2, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT);
|
|
30169
|
+
return host(ftbl([row], s.numW + titleW, s.h, 2, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cT);
|
|
29580
30170
|
}
|
|
29581
|
-
function buildMinistrySubheadBox(n, title, ctx,
|
|
30171
|
+
function buildMinistrySubheadBox(n, title, ctx, before2) {
|
|
29582
30172
|
const { reg, bf } = ctx;
|
|
29583
30173
|
const s = MINISTRY.subhead;
|
|
29584
30174
|
const text = `${n}. ${plain(title)}`;
|
|
@@ -29587,9 +30177,9 @@ function buildMinistrySubheadBox(n, title, ctx, before) {
|
|
|
29587
30177
|
const cT = reg.char({ font: MINISTRY.headFont, pt: s.pt, color: "#FFFFFF" });
|
|
29588
30178
|
const p = reg.para({ align: "LEFT", lineSp: 130, left: 400 });
|
|
29589
30179
|
const cell2 = tc({ bf: box, row: 0, col: 0, w, h: s.h, paras: para(text, p, cT), name: "__kordoc_h4" });
|
|
29590
|
-
return host(ftbl([cell2], w, s.h, 1, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT);
|
|
30180
|
+
return host(ftbl([cell2], w, s.h, 1, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cT);
|
|
29591
30181
|
}
|
|
29592
|
-
function buildMinistryItemBand(n, text, ctx,
|
|
30182
|
+
function buildMinistryItemBand(n, text, ctx, before2) {
|
|
29593
30183
|
const { reg, bf } = ctx;
|
|
29594
30184
|
const s = MINISTRY.item;
|
|
29595
30185
|
const w = ctx.W;
|
|
@@ -29600,7 +30190,7 @@ function buildMinistryItemBand(n, text, ctx, before) {
|
|
|
29600
30190
|
const runs = ministryRuns(circledNumber(n - 1), text, ctx, styled, MINISTRY.blue, MINISTRY.blue);
|
|
29601
30191
|
const p = reg.para({ align: "LEFT", lineSp: 130, left: 300 });
|
|
29602
30192
|
const cell2 = tc({ bf: band, row: 0, col: 0, w, h: s.h, paras: `<hp:p paraPrIDRef="${p}" styleIDRef="0">${runs}</hp:p>`, name: "__kordoc_h5" });
|
|
29603
|
-
return host(ftbl([cell2], w, s.h, 1, { bottomGap: 700 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), reg.char(base));
|
|
30193
|
+
return host(ftbl([cell2], w, s.h, 1, { bottomGap: 700 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), reg.char(base));
|
|
29604
30194
|
}
|
|
29605
30195
|
function buildMinistrySummaryBox(text, ctx) {
|
|
29606
30196
|
const { reg, bf } = ctx;
|
|
@@ -29628,7 +30218,7 @@ function buildMinistrySummaryBox(text, ctx) {
|
|
|
29628
30218
|
const cell2 = tc({ bf: box, row: 0, col: 0, w, h, paras: paras.join(""), vAlign: "TOP", name: "__kordoc_summary" });
|
|
29629
30219
|
return host(ftbl([cell2], w, h, 1, { bottomGap: 900 }), reg.para({ align: "CENTER", lineSp: 100, before: 400 }), reg.char(baseItem));
|
|
29630
30220
|
}
|
|
29631
|
-
function buildMinistryAttachBand(title, ctx,
|
|
30221
|
+
function buildMinistryAttachBand(title, ctx, before2) {
|
|
29632
30222
|
const { reg, bf } = ctx;
|
|
29633
30223
|
const a = MINISTRY.attach;
|
|
29634
30224
|
const w = ctx.W;
|
|
@@ -29642,7 +30232,7 @@ function buildMinistryAttachBand(title, ctx, before) {
|
|
|
29642
30232
|
const cT = reg.char({ font: MINISTRY.headFont, pt: fit.pt, ratio: fit.ratio, spacing: fit.spacing });
|
|
29643
30233
|
const center = reg.para({ align: "CENTER", lineSp: 130 });
|
|
29644
30234
|
const row = tc({ bf: labelBf, row: 0, col: 0, w: a.labelW, h: a.h, paras: para("\uBCC4 \uCCA8", center, cLabel) }) + tc({ bf: gapBf, row: 0, col: 1, w: a.gap, h: a.h, paras: para("", center, cT) }) + tc({ bf: titleBf, row: 0, col: 2, w: titleW, h: a.h, paras: para(t, center, cT), name: "__kordoc_h2" });
|
|
29645
|
-
return host(ftbl([row], w, a.h, 3, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before, keepWithNext: true }), cT);
|
|
30235
|
+
return host(ftbl([row], w, a.h, 3, { bottomGap: 900 }), reg.para({ align: "LEFT", lineSp: 100, before: before2, keepWithNext: true }), cT);
|
|
29646
30236
|
}
|
|
29647
30237
|
function isAttachHeading(text) {
|
|
29648
30238
|
return /^\[?\s*별\s*첨\s*\]?/u.test(text.trim());
|
|
@@ -29665,13 +30255,13 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
29665
30255
|
resetFrameTableIds();
|
|
29666
30256
|
const { reg, bfReg, remap, images, page } = deps;
|
|
29667
30257
|
const warnings = [];
|
|
29668
|
-
const
|
|
29669
|
-
const preset =
|
|
30258
|
+
const g2 = gongmun;
|
|
30259
|
+
const preset = g2.preset;
|
|
29670
30260
|
const isReport = preset === "report" || preset === "plan";
|
|
29671
30261
|
const isMinistry = preset === "ministry";
|
|
29672
|
-
const W = mmToHwpunit(210 -
|
|
30262
|
+
const W = mmToHwpunit(210 - g2.margins.left - g2.margins.right);
|
|
29673
30263
|
const pre = buildOutline(blocks, isMinistry ? { gaejosik: true, consumeTitle: true, summaryFromQuote: true, quoteBox: true, headingFrames: true, keepMarkers: true } : { gaejosik: true, consumeTitle: true, summaryFromQuote: isReport });
|
|
29674
|
-
const scheme = pickScheme(
|
|
30264
|
+
const scheme = pickScheme(g2, pre.hasBoxMarkers);
|
|
29675
30265
|
const gaejosik = scheme.kind === "gaejosik";
|
|
29676
30266
|
const raw = gaejosik ? pre : buildOutline(blocks, { gaejosik: false, consumeTitle: true, summaryFromQuote: false });
|
|
29677
30267
|
const outline = { ...raw, title: raw.title && polishGongmunText(raw.title), nodes: raw.nodes.map(polishNode) };
|
|
@@ -29702,48 +30292,48 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
29702
30292
|
if (box.lines > 3) warnings.push(`\uC694\uC57D\uBC15\uC2A4\uAC00 ${box.lines}\uC904\uC785\uB2C8\uB2E4 \u2014 \uBCF4\uACE0 \uBAA9\uC801\uC744 \uD55C \uBB38\uC7A5(\uC27C\uD45C \uD5C8\uC6A9) 3\uC904 \uC774\uB0B4 "\u2026\uD558\uACE0\uC790 \uD568"\uC73C\uB85C \uC904\uC774\uC138\uC694`);
|
|
29703
30293
|
paras.push(box.xml);
|
|
29704
30294
|
};
|
|
29705
|
-
const docTitle = polishGongmunText(
|
|
29706
|
-
const reportInfo =
|
|
30295
|
+
const docTitle = polishGongmunText(g2.docHead?.title ?? outline.title ?? "");
|
|
30296
|
+
const reportInfo = g2.reportInfo && polishGongmunText(g2.reportInfo);
|
|
29707
30297
|
if (preset === "official") {
|
|
29708
|
-
if (
|
|
30298
|
+
if (g2.docHead) paras.push(buildDocHeadTable({ ...g2.docHead, title: docTitle }, frame));
|
|
29709
30299
|
else if (outline.title) paras.push(generateParagraph(outline.title, reg.para({ align: "CENTER", lineSp: scheme.lineSp, after: lineHu(scheme.body) }), reg.char({ font: scheme.body.font, pt: scheme.body.pt + 2, bold: true }), void 0, 1));
|
|
29710
30300
|
if (reportInfo) paras.push(generateParagraph(reportInfo, reg.para({ align: "RIGHT", lineSp: scheme.lineSp }), reg.char({ font: scheme.body.font, pt: 12 })));
|
|
29711
|
-
if (
|
|
30301
|
+
if (g2.approval) paras.push(buildApprovalSeoul(g2.approval, null, frame));
|
|
29712
30302
|
} else if (isMinistry) {
|
|
29713
|
-
if (
|
|
29714
|
-
const cover = buildMinistryCover({ title: docTitle, date:
|
|
29715
|
-
if (
|
|
30303
|
+
if (g2.cover) {
|
|
30304
|
+
const cover = buildMinistryCover({ title: docTitle, date: g2.cover.date ?? formatGaejosikDate(/* @__PURE__ */ new Date()), org: g2.cover.org || void 0, label: g2.cover.label }, frame);
|
|
30305
|
+
if (g2.pageNumbers) cover[0] = cover[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${pageHidingCtrl()}`);
|
|
29716
30306
|
paras.push(...cover);
|
|
29717
30307
|
pendingPageBreak = true;
|
|
29718
30308
|
}
|
|
29719
|
-
if (
|
|
30309
|
+
if (g2.toc && outline.chapters > 0) {
|
|
29720
30310
|
const tocChapters = [];
|
|
29721
30311
|
for (const n of outline.nodes) {
|
|
29722
30312
|
if (n.kind === "chapter") tocChapters.push({ title: plain2(n.text), subs: [], attach: isAttachHeading(n.text) });
|
|
29723
30313
|
else if (n.kind === "heading" && n.level === 3 && tocChapters.length) tocChapters[tocChapters.length - 1].subs.push(plain2(n.text));
|
|
29724
30314
|
}
|
|
29725
30315
|
const toc = buildMinistryToc(tocChapters, frame);
|
|
29726
|
-
if (
|
|
30316
|
+
if (g2.pageNumbers) toc[0] = toc[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${pageHidingCtrl()}`);
|
|
29727
30317
|
paras.push(...toc);
|
|
29728
30318
|
pendingPageBreak = true;
|
|
29729
30319
|
}
|
|
29730
|
-
if (!
|
|
30320
|
+
if (!g2.cover && !g2.toc && docTitle) {
|
|
29731
30321
|
paras.push(generateParagraph(docTitle, reg.para({ align: "CENTER", lineSp: scheme.lineSp, after: lineHu(scheme.body) }), reg.char({ font: scheme.frame.titleFont, pt: 20 }), void 0, 1));
|
|
29732
30322
|
frontKind = "title";
|
|
29733
30323
|
}
|
|
29734
30324
|
} else if (isReport) {
|
|
29735
|
-
if (
|
|
30325
|
+
if (g2.cover) {
|
|
29736
30326
|
paras.push(...buildReportCover({
|
|
29737
30327
|
title: docTitle,
|
|
29738
|
-
date:
|
|
29739
|
-
org:
|
|
29740
|
-
dept:
|
|
29741
|
-
docInfo:
|
|
29742
|
-
approval:
|
|
30328
|
+
date: g2.cover.date ?? formatGaejosikDate(/* @__PURE__ */ new Date()).replace(/\s\d+\.$/, "."),
|
|
30329
|
+
org: g2.cover.org || void 0,
|
|
30330
|
+
dept: g2.cover.dept,
|
|
30331
|
+
docInfo: g2.docInfo ?? void 0,
|
|
30332
|
+
approval: g2.approval ?? void 0
|
|
29743
30333
|
}, frame));
|
|
29744
30334
|
pendingPageBreak = true;
|
|
29745
|
-
} else if (
|
|
29746
|
-
paras.push(buildApprovalSeoul(
|
|
30335
|
+
} else if (g2.approval) {
|
|
30336
|
+
paras.push(buildApprovalSeoul(g2.approval, null, frame));
|
|
29747
30337
|
}
|
|
29748
30338
|
if (docTitle) {
|
|
29749
30339
|
const t = buildReportTitleTable(docTitle, reportInfo, frame);
|
|
@@ -29755,16 +30345,16 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
29755
30345
|
frontKind = "title";
|
|
29756
30346
|
}
|
|
29757
30347
|
if (!docTitle && reportInfo) paras.push(generateParagraph(reportInfo, reg.para({ align: "RIGHT", lineSp: scheme.lineSp }), reg.char({ font: scheme.body.font, pt: 12 })));
|
|
29758
|
-
if (
|
|
30348
|
+
if (g2.summary) pushSummary(g2.summary);
|
|
29759
30349
|
else if (!outline.nodes.some((n) => n.kind === "summary")) warnings.push('\uBCF4\uACE0\uC11C \uC694\uC57D\uBC15\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC81C\uBAA9 \uC9C1\uD6C4 \uC778\uC6A9\uBB38(>)\uC5D0 \uBCF4\uACE0 \uBAA9\uC801\uC744 \uD55C \uBB38\uC7A5(\uC27C\uD45C \uD5C8\uC6A9) 3\uC904 \uC774\uB0B4 "\u2026\uD558\uACE0\uC790 \uD568"\uC73C\uB85C \uB123\uAC70\uB098 summary \uC635\uC158\uC744 \uC9C0\uC815\uD558\uC138\uC694');
|
|
29760
30350
|
} else {
|
|
29761
|
-
if (
|
|
29762
|
-
if (
|
|
30351
|
+
if (g2.noticeHead) paras.push(...buildNoticeHead(g2));
|
|
30352
|
+
if (g2.approval) paras.push(buildApprovalSeoul(g2.approval, null, frame));
|
|
29763
30353
|
if (outline.title) paras.push(generateParagraph(outline.title, reg.para({ align: "CENTER", lineSp: scheme.lineSp, before: 400, after: lineHu(scheme.body) }), reg.char({ font: scheme.frame.titleFont, pt: 20, bold: true }), void 0, 1));
|
|
29764
30354
|
}
|
|
29765
|
-
const numberer = new GongmunNumberer("standard",
|
|
30355
|
+
const numberer = new GongmunNumberer("standard", g2.bullet2);
|
|
29766
30356
|
const itemDepths = outline.nodes.filter((n) => n.kind === "item" && !n.legalMarker).map((n) => n.depth);
|
|
29767
|
-
const suppress = !gaejosik &&
|
|
30357
|
+
const suppress = !gaejosik && g2.suppressSingle ? computeSuppression(itemDepths) : null;
|
|
29768
30358
|
let itemSeq = 0;
|
|
29769
30359
|
const BOX_BLANK_HU = 1e3;
|
|
29770
30360
|
const BOX_GAP_AFTER_BAND = 1200;
|
|
@@ -29774,8 +30364,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
29774
30364
|
let nextNode;
|
|
29775
30365
|
let tableSeq = 0;
|
|
29776
30366
|
let sectionSeq = 0, subheadSeq = 0, itemBandSeq = 0;
|
|
29777
|
-
let pendingNewPageNum = isMinistry && (!!
|
|
29778
|
-
let chapterStyle =
|
|
30367
|
+
let pendingNewPageNum = isMinistry && (!!g2.cover || !!g2.toc);
|
|
30368
|
+
let chapterStyle = g2.h2Marker === "box" || g2.h2Marker === "number" || g2.h2Marker === "none" || g2.h2Marker === "band" ? g2.h2Marker : "roman";
|
|
29779
30369
|
if (!gaejosik) chapterStyle = "number";
|
|
29780
30370
|
const leadLeft = (depth, pt2) => (scheme.levels[Math.min(depth, 7)]?.leadTa ?? 0) * taHu(pt2);
|
|
29781
30371
|
const renderItem = (node, styleId = 0) => {
|
|
@@ -29806,8 +30396,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
29806
30396
|
}
|
|
29807
30397
|
} else {
|
|
29808
30398
|
const t = plain2(node.text);
|
|
29809
|
-
const cb = fitCharBreaks(t, st.font, st.pt, textW2, textW2,
|
|
29810
|
-
const f = cb ?? (
|
|
30399
|
+
const cb = fitCharBreaks(t, st.font, st.pt, textW2, textW2, g2.autoFitMinRatio ?? 101);
|
|
30400
|
+
const f = cb ?? (g2.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, textW2, textW2, g2.autoFitMinRatio) : null);
|
|
29811
30401
|
if (f) {
|
|
29812
30402
|
ratio = f.ratio;
|
|
29813
30403
|
spacing = f.spacing;
|
|
@@ -29817,9 +30407,9 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
29817
30407
|
const base = { font: st.font, pt: st.pt, bold: st.bold, ratio, spacing };
|
|
29818
30408
|
const consecutiveBox = prevKind === "item" && prevItemDepth === 0 && depth === 0;
|
|
29819
30409
|
const afterBand = depth === 0 && (prevKind === "chapter" && chapterStyle === "band" || prevKind === "title" && !!st.blankBefore);
|
|
29820
|
-
const
|
|
30410
|
+
const before2 = afterBand ? BOX_GAP_AFTER_BAND : st.blankBefore && !consecutiveBox && prevKind !== "chapter" && prevKind !== "start" && prevKind !== "summary" ? BOX_BLANK_HU : 0;
|
|
29821
30411
|
const keepNext = !!st.keepWithNext && !!nextNode && (nextNode.kind === "item" && nextNode.depth > depth || nextNode.kind === "ref");
|
|
29822
|
-
const paraSpec = { align: "JUSTIFY", left: lay.left, indent: -lay.hang, before, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: !charBreaks, autoTab: lay.hang > 0, widowOrphan: true };
|
|
30412
|
+
const paraSpec = { align: "JUSTIFY", left: lay.left, indent: -lay.hang, before: before2, lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: keepNext, keepWord: !charBreaks, autoTab: lay.hang > 0, widowOrphan: true };
|
|
29823
30413
|
const markerRun = mk ? markerRunXml(mk, reg.char({ font: st.font, pt: st.pt, bold: st.bold }), lay) : "";
|
|
29824
30414
|
if (isMinistry) {
|
|
29825
30415
|
const paraId2 = reg.para({ ...paraSpec, after: 600 });
|
|
@@ -29836,8 +30426,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
29836
30426
|
const left = isMinistry ? st.leadTa * taHu(st.pt) : leadLeft(node.depth, st.pt);
|
|
29837
30427
|
const textW2 = W - left - lay.hang;
|
|
29838
30428
|
const t = plain2(node.text);
|
|
29839
|
-
const cb = fitCharBreaks(t, st.font, st.pt, textW2, textW2,
|
|
29840
|
-
const f = cb ?? (
|
|
30429
|
+
const cb = fitCharBreaks(t, st.font, st.pt, textW2, textW2, g2.autoFitMinRatio ?? 101);
|
|
30430
|
+
const f = cb ?? (g2.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, textW2, textW2, g2.autoFitMinRatio) : null);
|
|
29841
30431
|
const base = { font: st.font, pt: st.pt, bold: st.bold, ratio: f?.ratio ?? 100, spacing: f?.spacing ?? 0 };
|
|
29842
30432
|
const paraId = reg.para({ align: "JUSTIFY", left, indent: -lay.hang, after: isMinistry ? 400 : 0, lineSp: isMinistry ? 130 : st.lineSp ?? scheme.lineSp, keepWord: !cb, autoTab: true, widowOrphan: true });
|
|
29843
30433
|
const markerRun = markerRunXml(marker, reg.char({ font: st.font, pt: st.pt, bold: st.bold }), lay);
|
|
@@ -29858,8 +30448,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
29858
30448
|
let ratio = 100, spacing = 0, charBreaks = false;
|
|
29859
30449
|
if (!node.align) {
|
|
29860
30450
|
const t = plain2(node.text);
|
|
29861
|
-
const cb = fitCharBreaks(t, st.font, st.pt, W, W,
|
|
29862
|
-
const f = cb ?? (
|
|
30451
|
+
const cb = fitCharBreaks(t, st.font, st.pt, W, W, g2.autoFitMinRatio ?? 101);
|
|
30452
|
+
const f = cb ?? (g2.autoFitMinRatio !== null ? fitParagraph(t, st.font, st.pt, W, W, g2.autoFitMinRatio) : null);
|
|
29863
30453
|
if (f) {
|
|
29864
30454
|
ratio = f.ratio;
|
|
29865
30455
|
spacing = f.spacing;
|
|
@@ -29885,8 +30475,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
29885
30475
|
return first ? xml : xml.replace(/^<hp:p /, `<hp:p pageBreak="1" `);
|
|
29886
30476
|
}
|
|
29887
30477
|
if (chapterStyle === "band") {
|
|
29888
|
-
const
|
|
29889
|
-
const band = buildChapterBand(chapterLabel(node.index, "roman").replace(/\.$/, ""), plain2(node.text), frame, { ...CHAPTER_BAND_DEFAULT, color:
|
|
30478
|
+
const before3 = prevKind === "start" || prevKind === "title" ? 0 : prevKind === "summary" ? BOX_GAP_AFTER_BAND : BAND_BEFORE_HU;
|
|
30479
|
+
const band = buildChapterBand(chapterLabel(node.index, "roman").replace(/\.$/, ""), plain2(node.text), frame, { ...CHAPTER_BAND_DEFAULT, color: g2.bandColor, textColor: g2.bandTextColor, titleFont: st.font, titlePt: st.pt }, before3);
|
|
29890
30480
|
if (band.overflow) warnings.push(`\uC7A5 \uC81C\uBAA9\uC774 \uB760 \uD45C \uD55C \uC904\uC5D0 \uB2F4\uAE30\uC9C0 \uC54A\uC544 \uCD95\uC18C\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uC81C\uBAA9\uC744 \uC904\uC774\uC138\uC694: "${node.text.slice(0, 30)}\u2026"`);
|
|
29891
30481
|
return band.xml;
|
|
29892
30482
|
}
|
|
@@ -29894,8 +30484,8 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
29894
30484
|
const text = label ? `${label} ${node.text}` : node.text;
|
|
29895
30485
|
const fit = fitOneLine(plain2(text), st.font, st.pt, W, st.pt - 2);
|
|
29896
30486
|
const base = { font: st.font, pt: fit.pt, bold: st.bold, ratio: fit.ratio, spacing: fit.spacing };
|
|
29897
|
-
const
|
|
29898
|
-
const paraId = reg.para({ align: "LEFT", before, after: Math.round(lineHu(scheme.body) * 0.3), lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: true });
|
|
30487
|
+
const before2 = prevKind === "start" || prevKind === "title" || prevKind === "summary" ? 0 : lineHu(scheme.body);
|
|
30488
|
+
const paraId = reg.para({ align: "LEFT", before: before2, after: Math.round(lineHu(scheme.body) * 0.3), lineSp: st.lineSp ?? scheme.lineSp, keepWithNext: true });
|
|
29899
30489
|
return generateParagraph(text, paraId, reg.char(base), inlineMapper(reg, base), 2);
|
|
29900
30490
|
};
|
|
29901
30491
|
const renderAttach = (node, first) => {
|
|
@@ -29964,7 +30554,7 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
29964
30554
|
xml = buildMinistrySummaryBox(node.text, frame);
|
|
29965
30555
|
break;
|
|
29966
30556
|
}
|
|
29967
|
-
if (isReport && !
|
|
30557
|
+
if (isReport && !g2.summary) {
|
|
29968
30558
|
pushSummary(node.text);
|
|
29969
30559
|
prevKind = node.kind;
|
|
29970
30560
|
return;
|
|
@@ -29972,15 +30562,15 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
29972
30562
|
xml = renderRef({ kind: "ref", depth: 0, text: node.text });
|
|
29973
30563
|
break;
|
|
29974
30564
|
case "heading": {
|
|
29975
|
-
const
|
|
30565
|
+
const before2 = prevKind === "chapter" || prevKind === "heading" || prevKind === "start" ? 0 : BAND_BEFORE_HU;
|
|
29976
30566
|
if (node.level <= 3) {
|
|
29977
30567
|
subheadSeq = 0;
|
|
29978
30568
|
itemBandSeq = 0;
|
|
29979
|
-
xml = buildMinistrySectionBand(++sectionSeq, node.text, frame,
|
|
30569
|
+
xml = buildMinistrySectionBand(++sectionSeq, node.text, frame, before2);
|
|
29980
30570
|
} else if (node.level === 4) {
|
|
29981
30571
|
itemBandSeq = 0;
|
|
29982
|
-
xml = buildMinistrySubheadBox(++subheadSeq, node.text, frame,
|
|
29983
|
-
} else xml = buildMinistryItemBand(++itemBandSeq, node.text, frame,
|
|
30572
|
+
xml = buildMinistrySubheadBox(++subheadSeq, node.text, frame, before2);
|
|
30573
|
+
} else xml = buildMinistryItemBand(++itemBandSeq, node.text, frame, before2);
|
|
29984
30574
|
lastItemFromHeading = true;
|
|
29985
30575
|
lastTextNode = node;
|
|
29986
30576
|
break;
|
|
@@ -30034,16 +30624,16 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
30034
30624
|
if (node.kind === "item") prevItemDepth = node.depth;
|
|
30035
30625
|
else if (node.kind === "chapter" && gaejosik && chapterStyle === "box") prevItemDepth = 0;
|
|
30036
30626
|
});
|
|
30037
|
-
if (
|
|
30038
|
-
if (
|
|
30627
|
+
if (g2.noticeHead) paras.push(...buildNoticeFoot(g2));
|
|
30628
|
+
if (g2.endMark && paras.length > 0) {
|
|
30039
30629
|
const lastText = lastTextNode && (lastTextNode.kind === "item" || lastTextNode.kind === "para" || lastTextNode.kind === "attach" || lastTextNode.kind === "ref") ? lastTextNode.text : "";
|
|
30040
30630
|
if (!hasEndMark(lastText)) {
|
|
30041
30631
|
const st = scheme.body;
|
|
30042
30632
|
paras.push(generateParagraph(" \uB05D.", reg.para({ align: "LEFT", lineSp: scheme.lineSp }), reg.char({ font: st.font, pt: st.pt })));
|
|
30043
30633
|
}
|
|
30044
30634
|
}
|
|
30045
|
-
if (preset === "official" &&
|
|
30046
|
-
const f =
|
|
30635
|
+
if (preset === "official" && g2.docFoot) {
|
|
30636
|
+
const f = g2.docFoot;
|
|
30047
30637
|
const approvers = (f.approvers ?? [f.drafter, f.reviewer, f.approver].filter((x) => !!x)).map(splitTitleName);
|
|
30048
30638
|
const cooperators = (f.cooperator ? f.cooperator.split(/[,、·]/).map((s) => s.trim()).filter(Boolean) : []).map(splitTitleName);
|
|
30049
30639
|
paras.push(buildDocFootTable({
|
|
@@ -30060,10 +30650,10 @@ function buildGongmunSectionV5(blocks, gongmun, deps, theme) {
|
|
|
30060
30650
|
fax: f.fax,
|
|
30061
30651
|
email: f.email,
|
|
30062
30652
|
disclosure: f.disclosure
|
|
30063
|
-
}, frame, isInternalApproval(
|
|
30653
|
+
}, frame, isInternalApproval(g2.docHead?.to)));
|
|
30064
30654
|
}
|
|
30065
30655
|
if (paras.length === 0) paras.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0"><hp:t></hp:t></hp:run></hp:p>`);
|
|
30066
|
-
paras[0] = paras[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${generateSecPr(
|
|
30656
|
+
paras[0] = paras[0].replace(/<hp:run charPrIDRef="(\d+)">/, `<hp:run charPrIDRef="$1">${generateSecPr(g2, page)}`);
|
|
30067
30657
|
return {
|
|
30068
30658
|
xml: `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
|
30069
30659
|
<hs:sec xmlns:hs="${NS_SECTION}" xmlns:hp="${NS_PARA}">
|
|
@@ -30484,11 +31074,11 @@ function findDeep(el, name, depth = 0) {
|
|
|
30484
31074
|
return null;
|
|
30485
31075
|
}
|
|
30486
31076
|
function parseParaGeom(el) {
|
|
30487
|
-
const
|
|
31077
|
+
const g2 = { ...DEFAULT_PARA_GEOM };
|
|
30488
31078
|
const ls = findDeep(el, "lineSpacing");
|
|
30489
31079
|
if (ls) {
|
|
30490
|
-
|
|
30491
|
-
|
|
31080
|
+
g2.lineSpacingType = ls.getAttribute("type") ?? "PERCENT";
|
|
31081
|
+
g2.lineSpacingValue = Number(ls.getAttribute("value")) || 160;
|
|
30492
31082
|
}
|
|
30493
31083
|
const margin = findDeep(el, "margin");
|
|
30494
31084
|
if (margin) {
|
|
@@ -30496,19 +31086,19 @@ function parseParaGeom(el) {
|
|
|
30496
31086
|
const c = findDeep(margin, n);
|
|
30497
31087
|
return c ? Number(c.getAttribute("value")) || 0 : 0;
|
|
30498
31088
|
};
|
|
30499
|
-
|
|
30500
|
-
|
|
30501
|
-
|
|
30502
|
-
|
|
30503
|
-
|
|
31089
|
+
g2.marginLeft = v("left");
|
|
31090
|
+
g2.marginRight = v("right");
|
|
31091
|
+
g2.marginIntent = v("intent");
|
|
31092
|
+
g2.spaceBefore = v("prev");
|
|
31093
|
+
g2.spaceAfter = v("next");
|
|
30504
31094
|
}
|
|
30505
31095
|
const bs = findDeep(el, "breakSetting");
|
|
30506
31096
|
if (bs) {
|
|
30507
31097
|
const nl = bs.getAttribute("breakNonLatinWord");
|
|
30508
|
-
if (nl === "BREAK_WORD")
|
|
30509
|
-
else if (nl === "KEEP_WORD")
|
|
31098
|
+
if (nl === "BREAK_WORD") g2.wrapMode = "keep";
|
|
31099
|
+
else if (nl === "KEEP_WORD") g2.wrapMode = "charAll";
|
|
30510
31100
|
}
|
|
30511
|
-
return
|
|
31101
|
+
return g2;
|
|
30512
31102
|
}
|
|
30513
31103
|
function parseRenderStyles(headXml) {
|
|
30514
31104
|
const styles = { charPr: /* @__PURE__ */ new Map(), paraAlign: /* @__PURE__ */ new Map(), paraGeom: /* @__PURE__ */ new Map(), borderFill: /* @__PURE__ */ new Map() };
|
|
@@ -30578,8 +31168,8 @@ function parseRenderStyles(headXml) {
|
|
|
30578
31168
|
};
|
|
30579
31169
|
walk(root);
|
|
30580
31170
|
for (const [id, tab] of paraTabRef) {
|
|
30581
|
-
const
|
|
30582
|
-
if (
|
|
31171
|
+
const g2 = styles.paraGeom.get(id);
|
|
31172
|
+
if (g2 && autoTabPr.has(tab)) g2.autoTabLeft = true;
|
|
30583
31173
|
}
|
|
30584
31174
|
return styles;
|
|
30585
31175
|
}
|
|
@@ -30654,8 +31244,8 @@ function anchorRunInfo(xml, para2, anchor) {
|
|
|
30654
31244
|
tr = para2.tRanges.find((r) => !r.selfClosing && xml.slice(r.contentStart, r.contentEnd).includes(head)) ?? para2.tRanges[para2.tRanges.length - 1];
|
|
30655
31245
|
}
|
|
30656
31246
|
if (!tr) return null;
|
|
30657
|
-
const
|
|
30658
|
-
const runOpen = [...
|
|
31247
|
+
const before2 = xml.slice(Math.max(0, para2.start), tr.contentStart);
|
|
31248
|
+
const runOpen = [...before2.matchAll(/<([A-Za-z0-9]+):run\b[^>]*>/g)].pop();
|
|
30659
31249
|
const prefix = runOpen?.[1] ?? tr.prefix ?? "hp";
|
|
30660
31250
|
const charPr2 = runOpen && attrOf3(runOpen[0], "charPrIDRef") || "0";
|
|
30661
31251
|
const close = xml.indexOf(`</${prefix}:run>`, tr.contentEnd);
|
|
@@ -32273,9 +32863,9 @@ function patchTextChunkTable(table2, scanTable, orig, edited, ctx, skip) {
|
|
|
32273
32863
|
}
|
|
32274
32864
|
return skip("\uD45C \uB80C\uB354 \uACBD\uB85C \uC2DD\uBCC4 \uC2E4\uD328");
|
|
32275
32865
|
}
|
|
32276
|
-
function applyCellEdit(table2, scanTable, gridR, gridC, newLines, ctx,
|
|
32866
|
+
function applyCellEdit(table2, scanTable, gridR, gridC, newLines, ctx, before2, after2, origLineCount) {
|
|
32277
32867
|
const skip = (reason) => {
|
|
32278
|
-
ctx.skipped.push({ reason, before: summarize(
|
|
32868
|
+
ctx.skipped.push({ reason, before: summarize(before2), after: summarize(after2) });
|
|
32279
32869
|
return 0;
|
|
32280
32870
|
};
|
|
32281
32871
|
const cell2 = scanTable.cellByAnchor.get(`${gridR},${gridC}`);
|
|
@@ -32320,7 +32910,7 @@ function applyCellEdit(table2, scanTable, gridR, gridC, newLines, ctx, before, a
|
|
|
32320
32910
|
splices.push(...sp);
|
|
32321
32911
|
sectionIndex = target.sectionIndex;
|
|
32322
32912
|
if (newLines.length > 1) {
|
|
32323
|
-
ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uD55C \uBB38\uB2E8\uC73C\uB85C \uBCD1\uD569 \uC801\uC6A9", after: summarize(
|
|
32913
|
+
ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uD55C \uBB38\uB2E8\uC73C\uB85C \uBCD1\uD569 \uC801\uC6A9", after: summarize(after2), partial: true });
|
|
32324
32914
|
}
|
|
32325
32915
|
} else {
|
|
32326
32916
|
const assigned = [];
|
|
@@ -32332,9 +32922,9 @@ function applyCellEdit(table2, scanTable, gridR, gridC, newLines, ctx, before, a
|
|
|
32332
32922
|
}
|
|
32333
32923
|
}
|
|
32334
32924
|
if (newLines.length > nonEmpty.length) {
|
|
32335
|
-
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(
|
|
32925
|
+
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(after2), partial: true });
|
|
32336
32926
|
} else if (newLines.length < nonEmpty.length && nonEmpty.length > 1) {
|
|
32337
|
-
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(
|
|
32927
|
+
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(before2), after: summarize(after2), partial: true });
|
|
32338
32928
|
}
|
|
32339
32929
|
for (let i = 0; i < nonEmpty.length; i++) {
|
|
32340
32930
|
if (assigned[i] === nonEmpty[i].text || normForMatch(assigned[i]) === normForMatch(nonEmpty[i].text)) continue;
|
|
@@ -32600,6 +33190,11 @@ function resolveParagraphMappings(blocks, scans) {
|
|
|
32600
33190
|
if (b.type !== "paragraph" && b.type !== "heading" || !b.text || !footerNorms.has(normForMatch(b.text))) break;
|
|
32601
33191
|
pageText.add(i);
|
|
32602
33192
|
}
|
|
33193
|
+
const noted = scans.flatMap((scan) => {
|
|
33194
|
+
if (!/<(?:\w+:)?(?:footNote|endNote)\b/.test(scan.xml)) return [];
|
|
33195
|
+
const fmts = sectionNoteFormats(scan.xml);
|
|
33196
|
+
return scan.bodyParagraphs.map((p) => ({ key: normForMatch(p.text), marks: paraNoteMarks(scan.xml, p, fmts) })).filter((x) => x.key && x.marks.length > 0);
|
|
33197
|
+
});
|
|
32603
33198
|
const counters = /* @__PURE__ */ new Map();
|
|
32604
33199
|
const result = /* @__PURE__ */ new Map();
|
|
32605
33200
|
for (let i = 0; i < blocks.length; i++) {
|
|
@@ -32611,13 +33206,26 @@ function resolveParagraphMappings(blocks, scans) {
|
|
|
32611
33206
|
}
|
|
32612
33207
|
let key = normForMatch(b.text);
|
|
32613
33208
|
let prefixStripped = false;
|
|
33209
|
+
let noteMarks;
|
|
32614
33210
|
if (!buckets.has(key)) {
|
|
32615
33211
|
const sp = b.text.indexOf(" ");
|
|
32616
|
-
|
|
32617
|
-
|
|
32618
|
-
|
|
32619
|
-
|
|
32620
|
-
|
|
33212
|
+
const alt = sp > 0 ? normForMatch(b.text.slice(sp + 1)) : "";
|
|
33213
|
+
if (alt && buckets.has(alt)) {
|
|
33214
|
+
key = alt;
|
|
33215
|
+
prefixStripped = true;
|
|
33216
|
+
} else {
|
|
33217
|
+
for (const n of noted) {
|
|
33218
|
+
if (textWithoutMarks(key, n.key, n.marks)) {
|
|
33219
|
+
key = n.key;
|
|
33220
|
+
noteMarks = n.marks;
|
|
33221
|
+
break;
|
|
33222
|
+
}
|
|
33223
|
+
if (alt && textWithoutMarks(alt, n.key, n.marks)) {
|
|
33224
|
+
key = n.key;
|
|
33225
|
+
noteMarks = n.marks;
|
|
33226
|
+
prefixStripped = true;
|
|
33227
|
+
break;
|
|
33228
|
+
}
|
|
32621
33229
|
}
|
|
32622
33230
|
}
|
|
32623
33231
|
}
|
|
@@ -32628,10 +33236,90 @@ function resolveParagraphMappings(blocks, scans) {
|
|
|
32628
33236
|
}
|
|
32629
33237
|
const occ = counters.get(key) ?? 0;
|
|
32630
33238
|
counters.set(key, occ + 1);
|
|
32631
|
-
result.set(i, occ < list.length ? { para: list[occ], prefixStripped } : {});
|
|
33239
|
+
result.set(i, occ < list.length ? { para: list[occ], prefixStripped, noteMarks } : {});
|
|
32632
33240
|
}
|
|
32633
33241
|
return result;
|
|
32634
33242
|
}
|
|
33243
|
+
function sectionNoteFormats(xml) {
|
|
33244
|
+
const pick2 = (tag) => {
|
|
33245
|
+
const m = new RegExp(`<(?:\\w+:)?${tag}\\b[^>]*>\\s*<(?:\\w+:)?autoNumFormat\\b([^>]*?)/?>`).exec(xml);
|
|
33246
|
+
return m ? noteFormatFrom((n) => xmlAttr(m[1], n)) : void 0;
|
|
33247
|
+
};
|
|
33248
|
+
return { footnote: pick2("footNotePr"), endnote: pick2("endNotePr") };
|
|
33249
|
+
}
|
|
33250
|
+
function xmlAttr(attrs, name) {
|
|
33251
|
+
const m = new RegExp(`(?:^|\\s)${name}\\s*=\\s*"([^"]*)"`).exec(attrs);
|
|
33252
|
+
return m ? decodeXmlEntities(m[1]) : void 0;
|
|
33253
|
+
}
|
|
33254
|
+
function paraNoteMarks(xml, para2, fmts) {
|
|
33255
|
+
const end = findElementEnd(xml, para2.start);
|
|
33256
|
+
if (end < 0) return [];
|
|
33257
|
+
const seg = xml.slice(para2.start, end);
|
|
33258
|
+
if (!/<(?:\w+:)?(?:footNote|endNote)\b/.test(seg)) return [];
|
|
33259
|
+
const marks = [];
|
|
33260
|
+
let depth = 0;
|
|
33261
|
+
for (const m of seg.matchAll(/<(\/?)(?:\w+:)?(tbl|drawText|caption|footNote|endNote|header|footer|memo|hiddenComment)\b([^>]*?)(\/?)>/g)) {
|
|
33262
|
+
const [, close, tag, attrs, selfClose] = m;
|
|
33263
|
+
if (selfClose) continue;
|
|
33264
|
+
if (close) {
|
|
33265
|
+
depth = Math.max(0, depth - 1);
|
|
33266
|
+
continue;
|
|
33267
|
+
}
|
|
33268
|
+
if (depth === 0 && (tag === "footNote" || tag === "endNote")) {
|
|
33269
|
+
const get = (n) => xmlAttr(attrs, n);
|
|
33270
|
+
marks.push(noteRefMark(
|
|
33271
|
+
{ number: get("number"), prefixChar: get("prefixChar"), suffixChar: get("suffixChar"), userChar: get("userChar") },
|
|
33272
|
+
tag === "endNote" ? fmts.endnote : fmts.footnote
|
|
33273
|
+
));
|
|
33274
|
+
}
|
|
33275
|
+
depth++;
|
|
33276
|
+
}
|
|
33277
|
+
return marks;
|
|
33278
|
+
}
|
|
33279
|
+
function textWithoutMarks(irKey, scanKey, marks) {
|
|
33280
|
+
let i = 0, j = 0, k = 0;
|
|
33281
|
+
while (i < irKey.length) {
|
|
33282
|
+
if (j < scanKey.length && irKey[i] === scanKey[j]) {
|
|
33283
|
+
i++;
|
|
33284
|
+
j++;
|
|
33285
|
+
continue;
|
|
33286
|
+
}
|
|
33287
|
+
if (k < marks.length && marks[k] && irKey.startsWith(marks[k], i)) {
|
|
33288
|
+
i += marks[k].length;
|
|
33289
|
+
k++;
|
|
33290
|
+
continue;
|
|
33291
|
+
}
|
|
33292
|
+
if (irKey[i] === " " && (j === 0 || scanKey[j - 1] === " ")) {
|
|
33293
|
+
i++;
|
|
33294
|
+
continue;
|
|
33295
|
+
}
|
|
33296
|
+
return false;
|
|
33297
|
+
}
|
|
33298
|
+
return j === scanKey.length && k === marks.length;
|
|
33299
|
+
}
|
|
33300
|
+
function removeNoteMarks(text, marks) {
|
|
33301
|
+
let out = text;
|
|
33302
|
+
let cursor = 0;
|
|
33303
|
+
const missing = [];
|
|
33304
|
+
for (const mark of marks) {
|
|
33305
|
+
if (!mark) continue;
|
|
33306
|
+
let at = -1;
|
|
33307
|
+
for (let p = out.indexOf(mark, cursor); p !== -1; p = out.indexOf(mark, p + 1)) {
|
|
33308
|
+
if (p === 0 || !/\s/.test(out[p - 1])) {
|
|
33309
|
+
at = p;
|
|
33310
|
+
break;
|
|
33311
|
+
}
|
|
33312
|
+
if (at === -1) at = p;
|
|
33313
|
+
}
|
|
33314
|
+
if (at === -1) {
|
|
33315
|
+
missing.push(mark);
|
|
33316
|
+
continue;
|
|
33317
|
+
}
|
|
33318
|
+
out = out.slice(0, at) + out.slice(at + mark.length);
|
|
33319
|
+
cursor = at;
|
|
33320
|
+
}
|
|
33321
|
+
return { text: out, missing };
|
|
33322
|
+
}
|
|
32635
33323
|
function handleModifiedUnit(orig, edited, ctx) {
|
|
32636
33324
|
const block = ctx.origBlocks[orig.blockIdx];
|
|
32637
33325
|
const skip = (reason) => {
|
|
@@ -32714,6 +33402,11 @@ function patchParagraphUnit(block, orig, edited, ctx, skip) {
|
|
|
32714
33402
|
ctx.skipped.push({ reason: "\uAC01\uC8FC \uD45C\uAE30 \uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 \u2014 \uAC01\uC8FC \uC720\uC9C0, \uBCF8\uBB38\uB9CC \uC801\uC6A9", before: `(\uC8FC: ${block.footnoteText})` });
|
|
32715
33403
|
}
|
|
32716
33404
|
}
|
|
33405
|
+
if (mapping.noteMarks?.length) {
|
|
33406
|
+
const r = removeNoteMarks(newPlain, mapping.noteMarks);
|
|
33407
|
+
newPlain = r.text;
|
|
33408
|
+
if (r.missing.length) ctx.skipped.push({ reason: "\uAC01\uC8FC \uCC38\uC870 \uBD80\uD638 \uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 \u2014 \uAC01\uC8FC \uC720\uC9C0, \uBCF8\uBB38\uB9CC \uC801\uC6A9", before: r.missing.join(" ") });
|
|
33409
|
+
}
|
|
32717
33410
|
if (mapping.prefixStripped) {
|
|
32718
33411
|
const origPrefix = block.text.split(" ", 1)[0];
|
|
32719
33412
|
const sp = newPlain.indexOf(" ");
|
|
@@ -33102,6 +33795,47 @@ var Surgeon = class {
|
|
|
33102
33795
|
entry.size = newData.length;
|
|
33103
33796
|
this.writeDirEntry(entry);
|
|
33104
33797
|
}
|
|
33798
|
+
/**
|
|
33799
|
+
* wipeFreeSectors 본체 — 어느 체인에도 닿지 않는 섹터·미니섹터와 스트림 꼬리 슬랙을 0으로.
|
|
33800
|
+
* FREESECT 표시를 믿지 않고 도달성으로 판정한다(쓰는 쪽에 따라 빈 FAT 칸을 ENDOFCHAIN 으로 채우기도 한다).
|
|
33801
|
+
*/
|
|
33802
|
+
wipeUnallocated() {
|
|
33803
|
+
const used = /* @__PURE__ */ new Set([...this.fatSectors, ...this.miniFatSectors, ...this.dirSectors]);
|
|
33804
|
+
let d = this.buf.readUInt32LE(68);
|
|
33805
|
+
for (let guard = 0; d !== ENDOFCHAIN && d !== FREESECT && guard < 1e6; guard++) {
|
|
33806
|
+
used.add(d);
|
|
33807
|
+
d = this.buf.readUInt32LE(this.sectorOffset(d) + 127 * 4);
|
|
33808
|
+
}
|
|
33809
|
+
const root = this.rootEntry();
|
|
33810
|
+
const rootChain = root.start === ENDOFCHAIN || root.size === 0 ? [] : this.chain(root.start);
|
|
33811
|
+
for (const s of rootChain) used.add(s);
|
|
33812
|
+
const usedMini = /* @__PURE__ */ new Set();
|
|
33813
|
+
for (const e of this.entries) {
|
|
33814
|
+
if (e.type !== 2 || e.size === 0 || e.start === ENDOFCHAIN) continue;
|
|
33815
|
+
if (e.size < MINI_CUTOFF) {
|
|
33816
|
+
const chain = this.miniChain(e.start);
|
|
33817
|
+
for (const s of chain) usedMini.add(s);
|
|
33818
|
+
const tail = e.size - (chain.length - 1) * MINI_SECTOR;
|
|
33819
|
+
const off = this.miniOffset(chain[chain.length - 1], rootChain);
|
|
33820
|
+
if (tail > 0 && tail < MINI_SECTOR) this.buf.fill(0, off + tail, off + MINI_SECTOR);
|
|
33821
|
+
} else {
|
|
33822
|
+
const chain = this.chain(e.start);
|
|
33823
|
+
for (const s of chain) used.add(s);
|
|
33824
|
+
const tail = e.size - (chain.length - 1) * SECTOR;
|
|
33825
|
+
const off = this.sectorOffset(chain[chain.length - 1]);
|
|
33826
|
+
if (tail > 0 && tail < SECTOR) this.buf.fill(0, off + tail, off + SECTOR);
|
|
33827
|
+
}
|
|
33828
|
+
}
|
|
33829
|
+
for (let i = 0; SECTOR + (i + 1) * SECTOR <= this.len; i++) {
|
|
33830
|
+
if (!used.has(i)) this.buf.fill(0, SECTOR + i * SECTOR, SECTOR + (i + 1) * SECTOR);
|
|
33831
|
+
}
|
|
33832
|
+
const capacity = rootChain.length * (SECTOR / MINI_SECTOR);
|
|
33833
|
+
for (let i = 0; i < capacity; i++) {
|
|
33834
|
+
if (usedMini.has(i)) continue;
|
|
33835
|
+
const off = this.miniOffset(i, rootChain);
|
|
33836
|
+
this.buf.fill(0, off, off + MINI_SECTOR);
|
|
33837
|
+
}
|
|
33838
|
+
}
|
|
33105
33839
|
finish() {
|
|
33106
33840
|
this.wipeFreedSectors();
|
|
33107
33841
|
this.flushFat();
|
|
@@ -33434,6 +34168,11 @@ function resolveTableMappings(blocks, scanTables) {
|
|
|
33434
34168
|
for (let k = si; k < scanTables.length; k++) {
|
|
33435
34169
|
if (scanTables[k].rows === table2.rows && scanTables[k].cols === table2.cols) cands.push(k);
|
|
33436
34170
|
}
|
|
34171
|
+
if (cands.length === 0) {
|
|
34172
|
+
for (let k = si; k < scanTables.length; k++) {
|
|
34173
|
+
if (scanTables[k].rows === table2.rows && scanTables[k].cols > table2.cols) cands.push(k);
|
|
34174
|
+
}
|
|
34175
|
+
}
|
|
33437
34176
|
if (cands.length === 0) continue;
|
|
33438
34177
|
let pick2 = cands[0];
|
|
33439
34178
|
if (cands.length > 1) {
|
|
@@ -33554,11 +34293,11 @@ function patchGfmCells(scanTable, orig, edited, ctx, skip) {
|
|
|
33554
34293
|
cellSkip("\uC11C\uC2DD/\uB9C1\uD06C/\uC774\uBBF8\uC9C0 \uD3EC\uD568 \uC140 \uC218\uC815\uC740 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
33555
34294
|
continue;
|
|
33556
34295
|
}
|
|
33557
|
-
const
|
|
33558
|
-
const
|
|
33559
|
-
if (
|
|
34296
|
+
const before2 = beforeEach.join("\n");
|
|
34297
|
+
const after2 = afterEach.join("\n");
|
|
34298
|
+
if (before2 === after2) continue;
|
|
33560
34299
|
const para2 = cell2.paras[0];
|
|
33561
|
-
if (normForMatch(para2.rawText) !== normForMatch(
|
|
34300
|
+
if (normForMatch(para2.rawText) !== normForMatch(before2)) {
|
|
33562
34301
|
cellSkip("\uC140 \uD14D\uC2A4\uD2B8 \uBD88\uC77C\uCE58 \u2014 \uC18C\uC2A4\uB9F5 \uC2E0\uB8B0 \uBD88\uAC00");
|
|
33563
34302
|
continue;
|
|
33564
34303
|
}
|
|
@@ -33566,7 +34305,7 @@ function patchGfmCells(scanTable, orig, edited, ctx, skip) {
|
|
|
33566
34305
|
cellSkip("\uACF5\uBC31 \uC815\uADDC\uD654 \uBD88\uC548\uC815 \uD14D\uC2A4\uD2B8 \u2014 \uBBF8\uC9C0\uC6D0");
|
|
33567
34306
|
continue;
|
|
33568
34307
|
}
|
|
33569
|
-
applied += stageParaPatch(ctx.scans[para2.sectionIndex], para2,
|
|
34308
|
+
applied += stageParaPatch(ctx.scans[para2.sectionIndex], para2, after2, cellSkip);
|
|
33570
34309
|
continue;
|
|
33571
34310
|
}
|
|
33572
34311
|
if (beforeParts.length !== cell2.paras.length || afterParts.length !== cell2.paras.length) {
|
|
@@ -33574,27 +34313,27 @@ function patchGfmCells(scanTable, orig, edited, ctx, skip) {
|
|
|
33574
34313
|
continue;
|
|
33575
34314
|
}
|
|
33576
34315
|
for (let k = 0; k < cell2.paras.length; k++) {
|
|
33577
|
-
const
|
|
33578
|
-
const
|
|
33579
|
-
if (
|
|
34316
|
+
const before2 = gfmCellToPlain(beforeParts[k]);
|
|
34317
|
+
const after2 = gfmCellToPlain(afterParts[k]);
|
|
34318
|
+
if (before2 === null || after2 === null) {
|
|
33580
34319
|
cellSkip("\uC11C\uC2DD/\uB9C1\uD06C/\uC774\uBBF8\uC9C0 \uD3EC\uD568 \uC140 \uC218\uC815\uC740 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
33581
34320
|
break;
|
|
33582
34321
|
}
|
|
33583
|
-
if (
|
|
33584
|
-
if (
|
|
34322
|
+
if (before2 === after2) continue;
|
|
34323
|
+
if (after2.includes("\n")) {
|
|
33585
34324
|
cellSkip("\uC140 \uB0B4 \uC904\uBC14\uAFC8 \uCD94\uAC00\uB294 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
33586
34325
|
break;
|
|
33587
34326
|
}
|
|
33588
34327
|
const para2 = cell2.paras[k];
|
|
33589
|
-
if (normForMatch(para2.rawText) !== normForMatch(
|
|
34328
|
+
if (normForMatch(para2.rawText) !== normForMatch(before2)) {
|
|
33590
34329
|
cellSkip("\uC140 \uD14D\uC2A4\uD2B8 \uBD88\uC77C\uCE58 \u2014 \uC18C\uC2A4\uB9F5 \uC2E0\uB8B0 \uBD88\uAC00");
|
|
33591
34330
|
break;
|
|
33592
34331
|
}
|
|
33593
|
-
if (sanitizeText(
|
|
34332
|
+
if (sanitizeText(after2) !== after2) {
|
|
33594
34333
|
cellSkip("\uACF5\uBC31 \uC815\uADDC\uD654 \uBD88\uC548\uC815 \uD14D\uC2A4\uD2B8 \u2014 \uBBF8\uC9C0\uC6D0");
|
|
33595
34334
|
break;
|
|
33596
34335
|
}
|
|
33597
|
-
applied += stageParaPatch(ctx.scans[para2.sectionIndex], para2,
|
|
34336
|
+
applied += stageParaPatch(ctx.scans[para2.sectionIndex], para2, after2, cellSkip);
|
|
33598
34337
|
}
|
|
33599
34338
|
}
|
|
33600
34339
|
}
|
|
@@ -33684,9 +34423,9 @@ function patchTextChunk5(table2, scanTable, orig, edited, ctx, skip) {
|
|
|
33684
34423
|
}
|
|
33685
34424
|
return skip("\uD45C \uB80C\uB354 \uACBD\uB85C \uC2DD\uBCC4 \uC2E4\uD328");
|
|
33686
34425
|
}
|
|
33687
|
-
function applyCellEdit5(table2, scanTable, gridR, gridC, newLines, ctx,
|
|
34426
|
+
function applyCellEdit5(table2, scanTable, gridR, gridC, newLines, ctx, before2, after2, origLineCount) {
|
|
33688
34427
|
const skip = (reason) => {
|
|
33689
|
-
ctx.skipped.push({ reason, before: summarize(
|
|
34428
|
+
ctx.skipped.push({ reason, before: summarize(before2), after: summarize(after2) });
|
|
33690
34429
|
return 0;
|
|
33691
34430
|
};
|
|
33692
34431
|
const cell2 = scanTable.cells.get(`${gridR},${gridC}`);
|
|
@@ -33719,9 +34458,9 @@ function applyCellEdit5(table2, scanTable, gridR, gridC, newLines, ctx, before,
|
|
|
33719
34458
|
}
|
|
33720
34459
|
}
|
|
33721
34460
|
if (newLines.length > targets.length) {
|
|
33722
|
-
ctx.skipped.push({ reason: "\uC140 \uB0B4 \uCD94\uAC00 \uC904\uC744 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8\uC5D0 \uAC15\uC81C \uC904\uBC14\uAFC8\uC73C\uB85C \uBCD1\uD569(\uBB38\uB2E8 \uC0DD\uC131 \uB300\uC2E0)", after: summarize(
|
|
34461
|
+
ctx.skipped.push({ reason: "\uC140 \uB0B4 \uCD94\uAC00 \uC904\uC744 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8\uC5D0 \uAC15\uC81C \uC904\uBC14\uAFC8\uC73C\uB85C \uBCD1\uD569(\uBB38\uB2E8 \uC0DD\uC131 \uB300\uC2E0)", after: summarize(after2), partial: true });
|
|
33723
34462
|
} else if (newLines.length < nonEmpty.length && nonEmpty.length > 1) {
|
|
33724
|
-
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(
|
|
34463
|
+
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(before2), after: summarize(after2), partial: true });
|
|
33725
34464
|
}
|
|
33726
34465
|
let staged = 0;
|
|
33727
34466
|
for (let i = 0; i < targets.length; i++) {
|
|
@@ -34037,22 +34776,18 @@ async function validateHwpx(buffer) {
|
|
|
34037
34776
|
return { ok: issues.length === 0, issues, entryCount: names.length };
|
|
34038
34777
|
}
|
|
34039
34778
|
|
|
34040
|
-
// src/redact.ts
|
|
34041
|
-
var DEFAULT_REDACT_RULES = [
|
|
34042
|
-
"rrn",
|
|
34043
|
-
"phone",
|
|
34044
|
-
"email",
|
|
34045
|
-
"card",
|
|
34046
|
-
"account"
|
|
34047
|
-
];
|
|
34779
|
+
// src/redact-rules.ts
|
|
34048
34780
|
var RULE_PRIORITY = [
|
|
34781
|
+
"crn",
|
|
34049
34782
|
"rrn",
|
|
34050
34783
|
"email",
|
|
34051
34784
|
"card",
|
|
34785
|
+
"brn",
|
|
34052
34786
|
"phone",
|
|
34053
34787
|
"driver",
|
|
34788
|
+
"passport",
|
|
34054
34789
|
"account",
|
|
34055
|
-
"
|
|
34790
|
+
"ip"
|
|
34056
34791
|
];
|
|
34057
34792
|
function luhnValid(digits) {
|
|
34058
34793
|
let sum = 0;
|
|
@@ -34066,110 +34801,586 @@ function luhnValid(digits) {
|
|
|
34066
34801
|
}
|
|
34067
34802
|
return sum % 10 === 0;
|
|
34068
34803
|
}
|
|
34069
|
-
|
|
34804
|
+
var DAYS_IN_MONTH = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
34805
|
+
function birthdateValid(front6, genderDigit) {
|
|
34806
|
+
const yy = Number(front6.slice(0, 2));
|
|
34070
34807
|
const mm = Number(front6.slice(2, 4));
|
|
34071
34808
|
const dd = Number(front6.slice(4, 6));
|
|
34072
|
-
|
|
34809
|
+
if (mm < 1 || mm > 12 || dd < 1) return false;
|
|
34810
|
+
const g2 = Number(genderDigit);
|
|
34811
|
+
const year = (g2 === 3 || g2 === 4 || g2 === 7 || g2 === 8 ? 2e3 : 1900) + yy;
|
|
34812
|
+
if (year > (/* @__PURE__ */ new Date()).getFullYear()) return false;
|
|
34813
|
+
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
34814
|
+
const max = mm === 2 ? leap ? 29 : 28 : DAYS_IN_MONTH[mm - 1];
|
|
34815
|
+
return dd <= max;
|
|
34816
|
+
}
|
|
34817
|
+
function brnChecksumValid(d) {
|
|
34818
|
+
const w = [1, 3, 7, 1, 3, 7, 1, 3, 5];
|
|
34819
|
+
let sum = 0;
|
|
34820
|
+
for (let i = 0; i < 9; i++) sum += w[i] * (d.charCodeAt(i) - 48);
|
|
34821
|
+
sum += Math.floor((d.charCodeAt(8) - 48) * 5 / 10);
|
|
34822
|
+
return (10 - sum % 10) % 10 === d.charCodeAt(9) - 48;
|
|
34073
34823
|
}
|
|
34824
|
+
function crnChecksumValid(d) {
|
|
34825
|
+
let sum = 0;
|
|
34826
|
+
for (let i = 0; i < 12; i++) sum += (i % 2 === 0 ? 1 : 2) * (d.charCodeAt(i) - 48);
|
|
34827
|
+
return (10 - sum % 10) % 10 === d.charCodeAt(12) - 48;
|
|
34828
|
+
}
|
|
34829
|
+
var digitsOf = (s) => s.replace(/\D/g, "");
|
|
34830
|
+
var allSameDigit = (d) => d.length > 0 && /^(\d)\1*$/.test(d);
|
|
34831
|
+
var LABELS = [
|
|
34832
|
+
["crn", /법\s*인\s*(?:등\s*록\s*)?번\s*호/g],
|
|
34833
|
+
["rrn", /주\s*민\s*(?:등\s*록\s*)?번\s*호|주\s*민\s*등\s*록|외\s*국\s*인\s*(?:등\s*록\s*)?번\s*호|거\s*소\s*신\s*고\s*번\s*호|생\s*년\s*월\s*일|resident\s*registration|\bRRN\b/gi],
|
|
34834
|
+
["brn", /사\s*업\s*자\s*(?:등\s*록\s*)?번\s*호|고\s*유\s*번\s*호|business\s*registration/gi],
|
|
34835
|
+
["account", /계\s*좌|예\s*금\s*주|입\s*금|은\s*행|뱅\s*크|농\s*협|신\s*협|수\s*협|우\s*체\s*국|금\s*고|bank\s*account|account\s*(?:no|number)/gi],
|
|
34836
|
+
["card", /카\s*드|credit\s*card|card\s*(?:no|number)/gi],
|
|
34837
|
+
["phone", /전\s*화|연\s*락\s*처|휴\s*대|핸\s*드\s*폰|팩\s*스|☎|☏|콜\s*센\s*터|고\s*객\s*센\s*터|대\s*표\s*번\s*호|상\s*담|문\s*의|\b(?:tel|fax|mobile|phone|h\.?p|ARS)\b/gi],
|
|
34838
|
+
["passport", /여\s*권|passport/gi],
|
|
34839
|
+
["driver", /운\s*전\s*면\s*허|면\s*허\s*번\s*호|driver'?s?\s*licen[cs]e/gi],
|
|
34840
|
+
["ip", /\bIP\b|아\s*이\s*피/g],
|
|
34841
|
+
["other", /출\s*원\s*번\s*호|과\s*제\s*번\s*호|사\s*업\s*번\s*호|접\s*수\s*번\s*호|민\s*원\s*번\s*호|신\s*청\s*번\s*호|문\s*서\s*번\s*호|관\s*리\s*번\s*호|주\s*문\s*번\s*호|일\s*련\s*번\s*호|발\s*간\s*등\s*록|등\s*록\s*번\s*호\s*\(\s*기\s*록|ISBN|ISSN|예\s*산|과\s*목|코\s*드|모\s*델|제\s*품|부\s*품|시\s*리\s*얼|serial|model|\bS\/N\b|바\s*코\s*드|기\s*록\s*물|사\s*건\s*번\s*호/gi]
|
|
34842
|
+
];
|
|
34843
|
+
var CELL_SEP_RE = /(?<!\\)\||<\/t[dh]>/gi;
|
|
34844
|
+
var PREV_VALUE_RE = /\d[\d\-.\s]{4,}\d/g;
|
|
34845
|
+
function labelsIn(s) {
|
|
34846
|
+
const out = /* @__PURE__ */ new Set();
|
|
34847
|
+
for (const [ctx, re] of LABELS) {
|
|
34848
|
+
re.lastIndex = 0;
|
|
34849
|
+
if (re.test(s)) out.add(ctx);
|
|
34850
|
+
}
|
|
34851
|
+
return out;
|
|
34852
|
+
}
|
|
34853
|
+
function labelsBefore(norm2, start) {
|
|
34854
|
+
let win = norm2.slice(Math.max(0, start - 120), start);
|
|
34855
|
+
const seps = [...win.matchAll(CELL_SEP_RE)];
|
|
34856
|
+
if (seps.length >= 2) {
|
|
34857
|
+
const s = seps[seps.length - 2];
|
|
34858
|
+
win = win.slice(s.index + s[0].length);
|
|
34859
|
+
}
|
|
34860
|
+
win = win.replace(/<[^>]*>/g, " ").slice(-40);
|
|
34861
|
+
const prev = [...win.matchAll(PREV_VALUE_RE)].pop();
|
|
34862
|
+
if (prev) win = win.slice(prev.index + prev[0].length);
|
|
34863
|
+
return labelsIn(win);
|
|
34864
|
+
}
|
|
34865
|
+
var OTHER_AFTER_RE = /^\s{0,2}[(\[]?\s{0,2}(?:접\s*수|신\s*청|주\s*문|사\s*건|과\s*제|문\s*서|관\s*리|부\s*품|제\s*품|모\s*델|코\s*드|시\s*리\s*얼|serial|model)/i;
|
|
34866
|
+
var g = (x, name) => x.m.groups?.[name] ?? "";
|
|
34867
|
+
var has = (x, c) => x.ctx().has(c);
|
|
34868
|
+
var crnOnly = (x) => has(x, "crn") && !has(x, "rrn");
|
|
34869
|
+
var after = (x, n = 16) => {
|
|
34870
|
+
const end = x.m.index + x.m[0].length;
|
|
34871
|
+
return x.m.input.slice(end, end + n);
|
|
34872
|
+
};
|
|
34873
|
+
var before = (x, n = 2) => {
|
|
34874
|
+
const start = x.m.index;
|
|
34875
|
+
return x.m.input.slice(Math.max(0, start - n), start);
|
|
34876
|
+
};
|
|
34877
|
+
var otherCtx = (x, own) => has(x, "other") && !has(x, own) || OTHER_AFTER_RE.test(after(x));
|
|
34878
|
+
var SERIAL_BEFORE_RE = /(?:\bS\/N|\bserial(?:\s*(?:no|number))?|시\s*리\s*얼(?:\s*번\s*호)?|일\s*련\s*번\s*호|바\s*코\s*드)\s*[:.]?\s*$/i;
|
|
34879
|
+
var serialBefore = (x) => SERIAL_BEFORE_RE.test(before(x, 20)) && !has(x, "phone");
|
|
34880
|
+
var PHONE_PREFIX = "0(?:1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80|60|50[2-8])";
|
|
34881
|
+
var PHONE_AREA = "0(?:1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80|50[2-8])";
|
|
34882
|
+
var PHONE_SEP = "(?: {0,2}- {0,2}| {1,2}|\\.)";
|
|
34883
|
+
var PHONE_END = "(?![\\w@-])";
|
|
34884
|
+
function phoneSepsOk(x, s1, s2) {
|
|
34885
|
+
if (s1 === "" !== (s2 === "")) return false;
|
|
34886
|
+
if (s1 === "." !== (s2 === ".")) return false;
|
|
34887
|
+
return s1 !== "." || !/^\.\d/.test(after(x, 2));
|
|
34888
|
+
}
|
|
34889
|
+
function phoneDigitsOk(pre, mid, last, bare) {
|
|
34890
|
+
if (/^0+$/.test(mid + last)) return false;
|
|
34891
|
+
return !bare || !/^0(?:2|3[1-3]|4[1-4]|5[1-5]|6[1-4])$/.test(pre) || mid[0] !== "0";
|
|
34892
|
+
}
|
|
34893
|
+
var EMAIL_START = "(?<![\\w%+])(?<!\\w[.\\-])(?<![A-Za-z0-9][*\u25CF\u25CB\u25EF\xD7#]+)";
|
|
34894
|
+
var EMAIL_DOMAIN = "(?<domain>[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*\\.(?<tld>[A-Za-z]{2,24}))(?![\\w-])";
|
|
34895
|
+
var FILE_EXT_TLD = /^(?:png|jpe?g|gif|bmp|svg|webp|tiff?|hwpx?|hwp|pdf|docx?|xlsx?|pptx?|txt|zip|csv|json|xml|html?|js|css|mp[34]|wav|avi|exe)$/i;
|
|
34074
34896
|
var RULES2 = {
|
|
34075
|
-
//
|
|
34076
|
-
|
|
34077
|
-
|
|
34078
|
-
|
|
34079
|
-
|
|
34080
|
-
|
|
34081
|
-
|
|
34082
|
-
|
|
34083
|
-
email: {
|
|
34084
|
-
pattern: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g,
|
|
34085
|
-
mask: (m, mc) => {
|
|
34086
|
-
const at = m[0].indexOf("@");
|
|
34087
|
-
return m[0][0] + mc.repeat(at - 1) + m[0].slice(at);
|
|
34088
|
-
}
|
|
34089
|
-
},
|
|
34090
|
-
// 카드번호 — 구분자 필수(무구분 16자리는 오탐 높아 제외), 동일 구분자 강제(\2),
|
|
34091
|
-
// Luhn 체크. 가운데 8자리 마스크.
|
|
34092
|
-
card: {
|
|
34093
|
-
pattern: /(?<!\d)(\d{4})([- ])(\d{4})\2(\d{4})\2(\d{4})(?!\d)/g,
|
|
34094
|
-
validate: (m) => luhnValid(m[1] + m[3] + m[4] + m[5]),
|
|
34095
|
-
mask: (m, mc) => m[1] + m[2] + mc.repeat(4) + m[2] + mc.repeat(4) + m[2] + m[5]
|
|
34096
|
-
},
|
|
34097
|
-
// 전화번호 — 휴대폰(01[016789])·서울(02)·지역(0[3-6]\d)·인터넷(070)은 구분자
|
|
34098
|
-
// -·.·공백 또는 무구분(동일 구분자 강제), 대표번호(15xx/16xx/18xx)는 구분자 필수.
|
|
34099
|
-
// 가운데 자리만 마스크 (대표번호는 뒤 4자리). 선행 [\d-] 금지 — 계좌 부분매치 방지.
|
|
34100
|
-
phone: {
|
|
34101
|
-
pattern: /(?<![\d-])(?:(01[016789]|070|02|0[3-6]\d)([-. ]?)(\d{3,4})\2(\d{4})|(1[568]\d{2})([-. ])(\d{4}))(?!\d)/g,
|
|
34102
|
-
mask: (m, mc) => m[1] !== void 0 ? m[1] + m[2] + mc.repeat(m[3].length) + m[2] + m[4] : m[5] + m[6] + mc.repeat(4)
|
|
34103
|
-
},
|
|
34104
|
-
// 운전면허 (기본 OFF) — 신형 12자리만 (지역명 2글자 선행 구버전은 스킵). 뒷 8자리 마스크.
|
|
34105
|
-
driver: {
|
|
34106
|
-
pattern: /(?<![\d-])(\d{2})-(\d{2})-\d{6}-\d{2}(?!-?\d)/g,
|
|
34107
|
-
mask: (m, mc) => m[1] + "-" + m[2] + "-" + mc.repeat(6) + "-" + mc.repeat(2)
|
|
34108
|
-
},
|
|
34109
|
-
// 계좌번호 — 3~4그룹 + 총 자릿수 10~16. rrn·card·phone과 겹치면 그쪽 우선.
|
|
34110
|
-
// 마지막 그룹 빼고 전부 마스크. 사업자등록번호(3-2-5, 10자리)도 걸린다 — 계약상 의도
|
|
34111
|
-
// (테스트로 명시). 날짜(2026-07-16)는 8자리라 총자릿수 검증에서 탈락.
|
|
34112
|
-
account: {
|
|
34113
|
-
pattern: /(?<!\d)(?<!\d-)\d{2,6}(?:-\d{2,6}){1,2}-\d{2,8}(?!-?\d)/g,
|
|
34114
|
-
validate: (m) => {
|
|
34115
|
-
const digits = m[0].replace(/-/g, "").length;
|
|
34116
|
-
return digits >= 10 && digits <= 16;
|
|
34897
|
+
// 주민·외국인등록번호 — 앞 6자리(생년월일) 유지, 뒤 7자리 마스크
|
|
34898
|
+
rrn: [
|
|
34899
|
+
{
|
|
34900
|
+
// 하이픈형 (앞뒤 공백 0~2칸, 유니코드 대시는 정규화로 흡수). 뒤에 "-글자"는 허용, "-숫자"는 더 긴 코드.
|
|
34901
|
+
// 법인 라벨만 있으면 제외
|
|
34902
|
+
re: /(?<![\w-])(?<front>\d{6})(?<sep> {0,2}- {0,2})(?<back>[1-8]\d{6})(?![\w])(?!-\d)/dg,
|
|
34903
|
+
ok: (x) => birthdateValid(g(x, "front"), g(x, "back")[0]) && !crnOnly(x),
|
|
34904
|
+
mask: ["back"]
|
|
34117
34905
|
},
|
|
34118
|
-
|
|
34119
|
-
|
|
34120
|
-
|
|
34121
|
-
|
|
34122
|
-
|
|
34123
|
-
|
|
34124
|
-
|
|
34125
|
-
|
|
34126
|
-
|
|
34127
|
-
|
|
34906
|
+
{
|
|
34907
|
+
// 뒷자리 일부만 가린 형태 (900101-1******, 900101-1694***) — 남은 숫자를 가린다
|
|
34908
|
+
re: /(?<![\w-])(?<front>\d{6})(?<sep> {0,2}- {0,2})(?<back>[1-8][0-9*●○◯×xX#]{6})(?![\w*●○◯×#])(?!-\d)/dg,
|
|
34909
|
+
ok: (x) => /[*●○◯×xX#]/.test(g(x, "back")) && birthdateValid(g(x, "front"), g(x, "back")[0]) && !crnOnly(x),
|
|
34910
|
+
mask: ["back"]
|
|
34911
|
+
},
|
|
34912
|
+
{
|
|
34913
|
+
// 구분자 없음·공백/온점/가운뎃점/빗금/물결 구분 — 주민/외국인등록번호(생년월일) 라벨이 있을 때만
|
|
34914
|
+
re: /(?<![\w@.-])(?<front>\d{6})(?<sep>[ .·/~]?)(?<back>[1-8]\d{6})(?![\w@-])/dg,
|
|
34915
|
+
needs: "rrn",
|
|
34916
|
+
ok: (x) => birthdateValid(g(x, "front"), g(x, "back")[0]),
|
|
34917
|
+
mask: ["back"]
|
|
34918
|
+
}
|
|
34919
|
+
],
|
|
34920
|
+
// 법인등록번호 (opt-in) — 법인 라벨이 있거나, 뒷자리가 0으로 시작(주민번호 불가)하고 체크섬 유효
|
|
34921
|
+
crn: [
|
|
34922
|
+
{
|
|
34923
|
+
re: /(?<![\w-])(?<front>\d{6})(?<sep> {0,2}- {0,2})(?<back>\d{7})(?![\w-])/dg,
|
|
34924
|
+
ok: (x) => !allSameDigit(g(x, "front") + g(x, "back")) && (crnOnly(x) || g(x, "back")[0] === "0" && crnChecksumValid(g(x, "front") + g(x, "back"))),
|
|
34925
|
+
mask: ["back"]
|
|
34926
|
+
},
|
|
34927
|
+
{
|
|
34928
|
+
re: /(?<![\w@.-])(?<front>\d{6})(?<back>\d{7})(?![\w@-])/dg,
|
|
34929
|
+
needs: "crn",
|
|
34930
|
+
ok: (x) => !allSameDigit(g(x, "front") + g(x, "back")) && !has(x, "rrn"),
|
|
34931
|
+
mask: ["back"]
|
|
34932
|
+
}
|
|
34933
|
+
],
|
|
34934
|
+
// 이메일 — 로컬파트 첫 글자만 남기고 마스크, 도메인 유지. 파일명(image@2x.png)은 제외
|
|
34935
|
+
// 이미 가린 로컬파트(j*********5@…) 뒤꼬리에서 시작하지 않는다 — 가림표 바로 뒤 매치 금지
|
|
34936
|
+
email: [
|
|
34937
|
+
{
|
|
34938
|
+
re: new RegExp(`${EMAIL_START}(?<l1>[A-Za-z0-9])(?<lrest>[A-Za-z0-9._%+-]+)@${EMAIL_DOMAIN}`, "dg"),
|
|
34939
|
+
ok: (x) => !FILE_EXT_TLD.test(g(x, "tld")),
|
|
34940
|
+
mask: ["lrest"],
|
|
34941
|
+
maskAll: true
|
|
34942
|
+
},
|
|
34943
|
+
{
|
|
34944
|
+
// 한 글자 로컬파트 — 첫 글자를 남기면 가린 게 없으므로 전부 가린다
|
|
34945
|
+
re: new RegExp(`${EMAIL_START}(?<l1>[A-Za-z0-9])@${EMAIL_DOMAIN}`, "dg"),
|
|
34946
|
+
ok: (x) => !FILE_EXT_TLD.test(g(x, "tld")),
|
|
34947
|
+
mask: ["l1"]
|
|
34948
|
+
}
|
|
34949
|
+
],
|
|
34950
|
+
// 카드번호 — 가운데 마스크 + Luhn. 구분자 없는 15~16자리는 카드 라벨이 있을 때만
|
|
34951
|
+
card: [
|
|
34952
|
+
{
|
|
34953
|
+
// 4-4-4-4 (같은 구분자 강제)
|
|
34954
|
+
re: /(?<![\w-])(?<g1>\d{4})(?<sep>[- ])(?<g2>\d{4})\k<sep>(?<g3>\d{4})\k<sep>(?<g4>\d{4})(?![\w-])(?![- ]\d)/dg,
|
|
34955
|
+
ok: (x) => {
|
|
34956
|
+
const d = digitsOf(x.m[0]);
|
|
34957
|
+
return luhnValid(d) && !allSameDigit(d);
|
|
34958
|
+
},
|
|
34959
|
+
mask: ["g2", "g3"]
|
|
34960
|
+
},
|
|
34961
|
+
{
|
|
34962
|
+
// AMEX 4-6-5
|
|
34963
|
+
re: /(?<![\w-])(?<g1>3[47]\d{2})(?<sep>[- ])(?<g2>\d{6})\k<sep>(?<g3a>\d)(?<g3b>\d{4})(?![\w-])(?![- ]\d)/dg,
|
|
34964
|
+
ok: (x) => luhnValid(digitsOf(x.m[0])),
|
|
34965
|
+
mask: ["g2", "g3a"]
|
|
34966
|
+
},
|
|
34967
|
+
{
|
|
34968
|
+
// 온점 구분 4.4.4.4 — 카드 라벨 필요 (버전·좌표 모양과 겹침)
|
|
34969
|
+
re: /(?<![\w.-])(?<g1>\d{4})\.(?<g2>\d{4})\.(?<g3>\d{4})\.(?<g4>\d{4})(?![\w-])(?!\.\d)/dg,
|
|
34970
|
+
needs: "card",
|
|
34971
|
+
ok: (x) => {
|
|
34972
|
+
const d = digitsOf(x.m[0]);
|
|
34973
|
+
return luhnValid(d) && !allSameDigit(d);
|
|
34974
|
+
},
|
|
34975
|
+
mask: ["g2", "g3"]
|
|
34976
|
+
},
|
|
34977
|
+
{
|
|
34978
|
+
// 무구분 15~16자리 — 카드 라벨 필요
|
|
34979
|
+
re: /(?<![\w@.-])(?<g1>\d{4})(?<mid>\d{7,8})(?<g4>\d{4})(?![\w@-])/dg,
|
|
34980
|
+
needs: "card",
|
|
34981
|
+
ok: (x) => {
|
|
34982
|
+
const d = digitsOf(x.m[0]);
|
|
34983
|
+
return luhnValid(d) && !allSameDigit(d);
|
|
34984
|
+
},
|
|
34985
|
+
mask: ["mid"]
|
|
34986
|
+
}
|
|
34987
|
+
],
|
|
34988
|
+
// 사업자등록번호 3-2-5 — 체크섬 유효 또는 사업자 라벨. 뒤 5자리(일련·검증번호) 마스크
|
|
34989
|
+
brn: [
|
|
34990
|
+
{
|
|
34991
|
+
re: /(?<![\w-])(?<a>\d{3})-(?<b>\d{2})-(?<c>\d{5})(?![\w-])/dg,
|
|
34992
|
+
ok: (x) => {
|
|
34993
|
+
const d = digitsOf(x.m[0]);
|
|
34994
|
+
if (allSameDigit(d)) return false;
|
|
34995
|
+
return has(x, "brn") || brnChecksumValid(d) && !has(x, "account") && !otherCtx(x, "brn");
|
|
34996
|
+
},
|
|
34997
|
+
mask: ["c"]
|
|
34998
|
+
},
|
|
34999
|
+
{
|
|
35000
|
+
re: /(?<![\w@.-])(?<a>\d{3})(?<b>\d{2})(?<c>\d{5})(?![\w@-])/dg,
|
|
35001
|
+
needs: "brn",
|
|
35002
|
+
ok: (x) => !allSameDigit(digitsOf(x.m[0])),
|
|
35003
|
+
mask: ["c"]
|
|
35004
|
+
}
|
|
35005
|
+
],
|
|
35006
|
+
// 전화번호 — 가운데 자리만 마스크 (대표번호는 뒤 4자리).
|
|
35007
|
+
// 뒤에 "-"가 붙으면 더 긴 코드(예산과목 064-1100-1131-301-210)의 일부라 제외.
|
|
35008
|
+
phone: [
|
|
35009
|
+
{
|
|
35010
|
+
// 국내 일반: 휴대폰·지역·070/080/060·050x 안심번호. 구분자 -·공백·온점(PHONE_SEP) 또는 무구분
|
|
35011
|
+
re: new RegExp(`(?<![\\w@+-])(?<pre>${PHONE_PREFIX})(?<s1>${PHONE_SEP}?)(?<mid>\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\d{4})${PHONE_END}`, "dg"),
|
|
35012
|
+
ok: (x) => {
|
|
35013
|
+
const pre = g(x, "pre");
|
|
35014
|
+
const bare = g(x, "s1") === "";
|
|
35015
|
+
if (!phoneSepsOk(x, g(x, "s1"), g(x, "s2"))) return false;
|
|
35016
|
+
if (!phoneDigitsOk(pre, g(x, "mid"), g(x, "last"), bare)) return false;
|
|
35017
|
+
if (serialBefore(x)) return false;
|
|
35018
|
+
if (!bare) return true;
|
|
35019
|
+
if (/\d\.$/.test(before(x))) return false;
|
|
35020
|
+
if (pre.startsWith("01")) return true;
|
|
35021
|
+
return !/\d,$/.test(before(x)) && !/^,\d/.test(after(x, 2));
|
|
35022
|
+
},
|
|
35023
|
+
mask: ["mid"]
|
|
35024
|
+
},
|
|
35025
|
+
{
|
|
35026
|
+
// 괄호 지역번호: (02) 123-4567, (042)481-6300
|
|
35027
|
+
re: new RegExp(`(?<![\\w@])\\((?<pre>${PHONE_AREA})\\) ?(?<mid>\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\d{4})${PHONE_END}`, "dg"),
|
|
35028
|
+
ok: (x) => phoneDigitsOk(g(x, "pre"), g(x, "mid"), g(x, "last"), false) && (g(x, "s2") !== "." || !/^\.\d/.test(after(x, 2))),
|
|
35029
|
+
mask: ["mid"]
|
|
35030
|
+
},
|
|
35031
|
+
{
|
|
35032
|
+
// 지역번호 뒤 닫는 괄호: 02)450-1234
|
|
35033
|
+
re: new RegExp(`(?<![\\w@(+-])(?<!\\d\\.)(?<pre>${PHONE_AREA})\\) ?(?<mid>\\d{3,4})(?<s2>${PHONE_SEP})(?<last>\\d{4})${PHONE_END}`, "dg"),
|
|
35034
|
+
ok: (x) => phoneDigitsOk(g(x, "pre"), g(x, "mid"), g(x, "last"), false) && (g(x, "s2") !== "." || !/^\.\d/.test(after(x, 2))),
|
|
35035
|
+
mask: ["mid"]
|
|
35036
|
+
},
|
|
35037
|
+
{
|
|
35038
|
+
// 국제 표기: +82 10-1234-5678, +82-2-123-4567, +82 (0)10 1234 5678, +821012345678
|
|
35039
|
+
re: new RegExp(`(?<![\\w@+])(?<cc>\\+82)(?<s0>[-. ]?)(?:\\(0\\) ?)?(?<pre>1[016789]|2|3[1-3]|4[1-4]|5[1-5]|6[1-4]|70|80)(?<s1>${PHONE_SEP}?)(?<mid>\\d{3,4})(?<s2>${PHONE_SEP}?)(?<last>\\d{4})${PHONE_END}`, "dg"),
|
|
35040
|
+
ok: (x) => phoneSepsOk(x, g(x, "s1"), g(x, "s2")) && phoneDigitsOk("0" + g(x, "pre"), g(x, "mid"), g(x, "last"), g(x, "s1") === ""),
|
|
35041
|
+
mask: ["mid"]
|
|
35042
|
+
},
|
|
35043
|
+
{
|
|
35044
|
+
// 대표번호 15xx·16xx·18xx — 구분자 필수. 연도 범위 모양(1592-1598)·온점/공백 구분은 전화 라벨이 있을 때만.
|
|
35045
|
+
// 뒤 4자리 0000 은 자리표시자
|
|
35046
|
+
re: new RegExp(`(?<![\\w@+-])(?<!\\d\\.)(?<pre>1(?:5[2-9]\\d|6\\d\\d|8\\d\\d))(?<sep> {0,2}- {0,2}| {1,2}|\\.)(?<last>\\d{4})${PHONE_END}`, "dg"),
|
|
35047
|
+
ok: (x) => {
|
|
35048
|
+
if (g(x, "last") === "0000" || serialBefore(x)) return false;
|
|
35049
|
+
if (g(x, "sep") === "." && /^\.\d/.test(after(x, 2))) return false;
|
|
35050
|
+
const a = Number(g(x, "pre"));
|
|
35051
|
+
const b = Number(g(x, "last"));
|
|
35052
|
+
const yearRange = b >= a && b <= (/* @__PURE__ */ new Date()).getFullYear();
|
|
35053
|
+
return g(x, "sep").trim() === "-" && !yearRange || has(x, "phone");
|
|
35054
|
+
},
|
|
35055
|
+
mask: ["last"]
|
|
35056
|
+
}
|
|
35057
|
+
],
|
|
35058
|
+
// 운전면허번호 — 지역코드(11~28)-연도-일련6-검증2 / 구형 지역명 + 2-6-2. 뒤 8자리 마스크
|
|
35059
|
+
driver: [
|
|
35060
|
+
{
|
|
35061
|
+
// 신형 — 무문맥이지만 비개인 식별번호 문맥("(접수)" 등)이면 아님
|
|
35062
|
+
re: /(?<![\w-])(?<r>1[1-9]|2[0-8])-(?<y>\d{2})-(?<s>\d{6})-(?<c>\d{2})(?![\w-])/dg,
|
|
35063
|
+
ok: (x) => has(x, "driver") || !otherCtx(x, "driver"),
|
|
35064
|
+
mask: ["s", "c"]
|
|
35065
|
+
},
|
|
35066
|
+
{
|
|
35067
|
+
re: /(?<rn>서울|부산|경기|강원|충북|충남|전북|전남|경북|경남|제주|대구|인천|광주|대전|울산) ?(?<y>\d{2})-(?<s>\d{6})-(?<c>\d{2})(?![\w-])/dg,
|
|
35068
|
+
mask: ["s", "c"]
|
|
35069
|
+
},
|
|
35070
|
+
{
|
|
35071
|
+
re: /(?<![\w@.-])(?<r>1[1-9]|2[0-8])(?<y>\d{2})(?<s>\d{6})(?<c>\d{2})(?![\w@-])/dg,
|
|
35072
|
+
needs: "driver",
|
|
35073
|
+
mask: ["s", "c"]
|
|
35074
|
+
}
|
|
35075
|
+
],
|
|
35076
|
+
// 여권번호 — 신형 복수여권(M123A4567)은 무문맥, 그 밖의 종류 글자(S·R·O·D·G)와 구형(M12345678)은 여권 라벨
|
|
35077
|
+
// 필요 ("S123A4567 부품" 같은 제품 코드). 첫 글자만 남김
|
|
35078
|
+
passport: [
|
|
35079
|
+
{
|
|
35080
|
+
re: /(?<![A-Za-z0-9])(?<l>[MSRODG])(?<num>\d{3}[A-Z]\d{4})(?![A-Za-z0-9])/dg,
|
|
35081
|
+
ok: (x) => has(x, "passport") || g(x, "l") === "M" && !otherCtx(x, "passport"),
|
|
35082
|
+
mask: ["num"]
|
|
35083
|
+
},
|
|
35084
|
+
{
|
|
35085
|
+
re: /(?<![A-Za-z0-9])(?<l>[MSRODG])(?<num>\d{8})(?![A-Za-z0-9])/dg,
|
|
35086
|
+
needs: "passport",
|
|
35087
|
+
mask: ["num"]
|
|
35088
|
+
},
|
|
35089
|
+
{
|
|
35090
|
+
// 소문자로 친 여권번호(m123a4567) — 여권 라벨이 있을 때만
|
|
35091
|
+
re: /(?<![A-Za-z0-9])(?<l>[msrodg])(?<num>\d{3}[a-z]\d{4}|\d{8})(?![A-Za-z0-9])/dg,
|
|
35092
|
+
needs: "passport",
|
|
35093
|
+
mask: ["num"]
|
|
35094
|
+
}
|
|
35095
|
+
],
|
|
35096
|
+
// 계좌번호 — 하이픈 3~4그룹, 총 11~16자리 (10자리는 계좌 라벨 필요). 마지막 그룹 빼고 마스크.
|
|
35097
|
+
// 무구분 10~14자리는 계좌·은행 라벨이 있을 때만 (뒤 4자리 유지)
|
|
35098
|
+
account: [
|
|
35099
|
+
{
|
|
35100
|
+
// 마지막 그룹 1자리 허용 — 새마을금고 9002-1234-5678-9, 3-2-6-1·3-3-6-1 형식
|
|
35101
|
+
re: /(?<![\w-])(?<!\d\.)(?<head>\d{2,6}(?:-\d{2,6}){1,2})-(?<last>\d{1,8})(?![\w-])(?!\.\d)/dg,
|
|
35102
|
+
ok: (x) => {
|
|
35103
|
+
const s = x.m[0];
|
|
35104
|
+
const d = digitsOf(s);
|
|
35105
|
+
const acct = has(x, "account");
|
|
35106
|
+
if (allSameDigit(d) || !acct && otherCtx(x, "account")) return false;
|
|
35107
|
+
if (!acct && (/\d,$/.test(before(x)) || /^,\d/.test(after(x, 2)))) return false;
|
|
35108
|
+
if (/^\d+(?:-0+)+$/.test(s)) return false;
|
|
35109
|
+
if (d.length < 10 || d.length > 16 || d.length === 10 && !acct) return false;
|
|
35110
|
+
if (/^\s*원/.test(after(x, 3))) return false;
|
|
35111
|
+
if (acct) return true;
|
|
35112
|
+
if (/^(?:10|20|30|40)-(?:19|20)\d\d-\d{6,7}$/.test(s)) return false;
|
|
35113
|
+
if (/^(?:19|20)\d\d-(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])?-/.test(s)) return false;
|
|
35114
|
+
if (s.split("-").filter((p) => /^(?:19|20)\d\d$/.test(p)).length >= 2) return false;
|
|
35115
|
+
if (/^\d{3}-\d{2}-\d{5}$/.test(s) && brnChecksumValid(d)) return false;
|
|
35116
|
+
return true;
|
|
35117
|
+
},
|
|
35118
|
+
mask: ["head"]
|
|
35119
|
+
},
|
|
35120
|
+
{
|
|
35121
|
+
re: /(?<![\w@.-])(?<!\d,)(?<head>\d{6,10})(?<last>\d{4})(?![\w@-])(?!,\d)/dg,
|
|
35122
|
+
needs: "account",
|
|
35123
|
+
ok: (x) => !allSameDigit(x.m[0]),
|
|
35124
|
+
mask: ["head"]
|
|
35125
|
+
}
|
|
35126
|
+
],
|
|
35127
|
+
// IPv4 (opt-in) — 옥텟 0~255, 첫 옥텟 10 이상(1.2.3.4 절 번호·버전 제외) 또는 IP 라벨. 뒤 두 옥텟 마스크
|
|
35128
|
+
ip: [
|
|
35129
|
+
{
|
|
35130
|
+
re: /(?<![\w.])(?<a>\d{1,3})\.(?<b>\d{1,3})\.(?<c>\d{1,3})\.(?<d>\d{1,3})(?![\w])(?!\.\d)/dg,
|
|
35131
|
+
ok: (x) => {
|
|
35132
|
+
const oct = [g(x, "a"), g(x, "b"), g(x, "c"), g(x, "d")];
|
|
35133
|
+
if (oct.some((o) => Number(o) > 255 || o.length > 1 && o[0] === "0")) return false;
|
|
35134
|
+
return Number(oct[0]) >= 10 || has(x, "ip");
|
|
35135
|
+
},
|
|
35136
|
+
mask: ["c", "d"]
|
|
35137
|
+
}
|
|
35138
|
+
]
|
|
34128
35139
|
};
|
|
34129
|
-
|
|
34130
|
-
|
|
34131
|
-
|
|
34132
|
-
|
|
34133
|
-
|
|
34134
|
-
|
|
35140
|
+
|
|
35141
|
+
// src/redact.ts
|
|
35142
|
+
var DEFAULT_REDACT_RULES = [
|
|
35143
|
+
"rrn",
|
|
35144
|
+
"phone",
|
|
35145
|
+
"email",
|
|
35146
|
+
"card",
|
|
35147
|
+
"account",
|
|
35148
|
+
"brn",
|
|
35149
|
+
"passport",
|
|
35150
|
+
"driver"
|
|
35151
|
+
];
|
|
35152
|
+
var ALL_REDACT_RULES = [...DEFAULT_REDACT_RULES, "crn", "ip"];
|
|
35153
|
+
var DASHES = /* @__PURE__ */ new Set([8208, 8209, 8210, 8211, 8212, 8213, 8722, 65112, 65123, 65293, 9472, 9473, 12641, 12540]);
|
|
35154
|
+
var SPACES = /* @__PURE__ */ new Set([160, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201, 8202, 8239, 12288]);
|
|
35155
|
+
function normalizeForDetect(text) {
|
|
35156
|
+
let out = "";
|
|
35157
|
+
let changed = false;
|
|
35158
|
+
for (let i = 0; i < text.length; i++) {
|
|
35159
|
+
const c = text.charCodeAt(i);
|
|
35160
|
+
let r = c;
|
|
35161
|
+
if (c >= 65281 && c <= 65374) r = c - 65248;
|
|
35162
|
+
if (DASHES.has(c)) r = 45;
|
|
35163
|
+
else if (SPACES.has(c)) r = 32;
|
|
35164
|
+
if (r !== c) changed = true;
|
|
35165
|
+
out += r === c ? text[i] : String.fromCharCode(r);
|
|
35166
|
+
}
|
|
35167
|
+
return changed ? out : text;
|
|
35168
|
+
}
|
|
35169
|
+
function maskMatch(orig, x, v, maskChar) {
|
|
35170
|
+
const base = x.m.index;
|
|
35171
|
+
const norm2 = x.m[0];
|
|
35172
|
+
const chars = orig.split("");
|
|
35173
|
+
const idx = x.m.indices?.groups;
|
|
35174
|
+
for (const name of v.mask) {
|
|
35175
|
+
const r = idx?.[name];
|
|
35176
|
+
if (!r) continue;
|
|
35177
|
+
for (let i = r[0] - base; i < r[1] - base; i++) {
|
|
35178
|
+
if (v.maskAll || /[0-9A-Za-z]/.test(norm2[i])) chars[i] = maskChar;
|
|
35179
|
+
}
|
|
35180
|
+
}
|
|
35181
|
+
return chars.join("");
|
|
35182
|
+
}
|
|
35183
|
+
function assertMaskChar(maskChar) {
|
|
35184
|
+
const n = normalizeForDetect(maskChar);
|
|
35185
|
+
if (maskChar.length !== 1 || /[0-9A-Za-z|\\<>&"'._\-@+:/,;]/.test(n) || /[\p{Z}\p{C}\p{M}]/u.test(maskChar) || /[\uFDD0-\uFDEF\uFFFE\uFFFF]/.test(maskChar)) {
|
|
35186
|
+
throw new Error(`maskChar\uB294 \uC601\uC22B\uC790\xB7\uACF5\uBC31\xB7\uC81C\uC5B4/\uACB0\uD569 \uBB38\uC790\xB7\uAD6C\uBD84 \uAE30\uD638(|\\<>&"'._-@+:/,;)\uAC00 \uC544\uB2CC \uBCF4\uC774\uB294 1\uAE00\uC790\uC5EC\uC57C \uD568: ${JSON.stringify(maskChar)}`);
|
|
35187
|
+
}
|
|
35188
|
+
}
|
|
35189
|
+
var LIST_SEP_RE = /^\s{0,2}(?:[,;/·、]|및|또는)?\s{0,2}$/;
|
|
35190
|
+
function detect(text, rules, maskChar, headerAt) {
|
|
35191
|
+
const norm2 = normalizeForDetect(text);
|
|
34135
35192
|
const hits = [];
|
|
34136
|
-
if (text === "" || enabled.length === 0) return { text, hits };
|
|
34137
35193
|
const occupied = [];
|
|
34138
35194
|
for (const rule of RULE_PRIORITY) {
|
|
34139
|
-
if (!
|
|
34140
|
-
const
|
|
34141
|
-
|
|
34142
|
-
|
|
34143
|
-
|
|
34144
|
-
|
|
34145
|
-
|
|
34146
|
-
|
|
34147
|
-
|
|
34148
|
-
|
|
34149
|
-
|
|
34150
|
-
|
|
35195
|
+
if (!rules.includes(rule)) continue;
|
|
35196
|
+
for (const v of RULES2[rule]) {
|
|
35197
|
+
for (const m of norm2.matchAll(v.re)) {
|
|
35198
|
+
const start = m.index;
|
|
35199
|
+
const end = start + m[0].length;
|
|
35200
|
+
if (occupied.some((o) => start < o.end && end > o.start)) continue;
|
|
35201
|
+
let cached;
|
|
35202
|
+
const ctx = () => {
|
|
35203
|
+
if (!cached) {
|
|
35204
|
+
let c = labelsBefore(norm2, start);
|
|
35205
|
+
if (c.size === 0) {
|
|
35206
|
+
const prev = occupied.find((o) => o.end < start && start - o.end <= 6 && LIST_SEP_RE.test(norm2.slice(o.end, start)));
|
|
35207
|
+
if (prev) c = prev.rule === "email" ? prev.ctx() : /* @__PURE__ */ new Set([...prev.ctx(), prev.rule]);
|
|
35208
|
+
}
|
|
35209
|
+
if (c.size === 0 && headerAt) {
|
|
35210
|
+
const h = headerAt(start);
|
|
35211
|
+
if (h) c = labelsIn(normalizeForDetect(h));
|
|
35212
|
+
}
|
|
35213
|
+
cached = c;
|
|
35214
|
+
}
|
|
35215
|
+
return cached;
|
|
35216
|
+
};
|
|
35217
|
+
const x = { m, ctx };
|
|
35218
|
+
if (v.needs && !ctx().has(v.needs)) continue;
|
|
35219
|
+
if (v.ok && !v.ok(x)) continue;
|
|
35220
|
+
occupied.push({ start, end, ctx, rule });
|
|
35221
|
+
hits.push({ rule, masked: maskMatch(text.slice(start, end), x, v, maskChar), index: start, length: end - start });
|
|
35222
|
+
}
|
|
35223
|
+
}
|
|
35224
|
+
}
|
|
35225
|
+
return hits.sort((a, b) => a.index - b.index);
|
|
35226
|
+
}
|
|
35227
|
+
function applyHits(text, hits) {
|
|
34151
35228
|
let out = "";
|
|
34152
35229
|
let cursor = 0;
|
|
34153
35230
|
for (const h of hits) {
|
|
34154
35231
|
out += text.slice(cursor, h.index) + h.masked;
|
|
34155
35232
|
cursor = h.index + h.length;
|
|
34156
35233
|
}
|
|
34157
|
-
out
|
|
34158
|
-
|
|
35234
|
+
return out + text.slice(cursor);
|
|
35235
|
+
}
|
|
35236
|
+
function redactText(text, options) {
|
|
35237
|
+
const maskChar = options?.maskChar ?? "\u25CF";
|
|
35238
|
+
assertMaskChar(maskChar);
|
|
35239
|
+
const rules = options?.rules ?? DEFAULT_REDACT_RULES;
|
|
35240
|
+
if (text === "" || rules.length === 0) return { text, hits: [] };
|
|
35241
|
+
const hits = detect(text, rules, maskChar);
|
|
35242
|
+
return { text: applyHits(text, hits), hits };
|
|
35243
|
+
}
|
|
35244
|
+
function pipePositions(line) {
|
|
35245
|
+
const out = [];
|
|
35246
|
+
for (let i = 0; i < line.length; i++) {
|
|
35247
|
+
if (line[i] === "|" && line[i - 1] !== "\\") out.push(i);
|
|
35248
|
+
}
|
|
35249
|
+
return out;
|
|
35250
|
+
}
|
|
35251
|
+
var isGfmRow = (l) => /^\s*\|.*\|\s*$/.test(l);
|
|
35252
|
+
var isGfmSep = (l) => /^\s*\|(?:\s*:?-{3,}:?\s*\|)+\s*$/.test(l);
|
|
35253
|
+
function htmlCells(line) {
|
|
35254
|
+
const out = [];
|
|
35255
|
+
let col = 0;
|
|
35256
|
+
for (const m of line.matchAll(/<t([dh])\b([^>]*)>([\s\S]*?)<\/t\1>/gi)) {
|
|
35257
|
+
const span = Math.max(1, Number(m[2].match(/colspan="?(\d+)/i)?.[1] ?? 1));
|
|
35258
|
+
out.push({ start: m.index, end: m.index + m[0].length, col, span, text: m[3].replace(/<[^>]*>/g, " ") });
|
|
35259
|
+
col += span;
|
|
35260
|
+
}
|
|
35261
|
+
return out;
|
|
35262
|
+
}
|
|
35263
|
+
function tableHeaderContext(lines) {
|
|
35264
|
+
const out = /* @__PURE__ */ new Map();
|
|
35265
|
+
for (let i = 0; i < lines.length; i++) {
|
|
35266
|
+
if (isGfmRow(lines[i]) && i + 1 < lines.length && isGfmSep(lines[i + 1])) {
|
|
35267
|
+
const head = lines[i];
|
|
35268
|
+
const hp = pipePositions(head);
|
|
35269
|
+
const headers = hp.slice(0, -1).map((p, k) => head.slice(p + 1, hp[k + 1]));
|
|
35270
|
+
let j = i + 2;
|
|
35271
|
+
for (; j < lines.length && isGfmRow(lines[j]); j++) {
|
|
35272
|
+
const rp = pipePositions(lines[j]);
|
|
35273
|
+
out.set(j, (off) => {
|
|
35274
|
+
let k = -1;
|
|
35275
|
+
for (const p of rp) {
|
|
35276
|
+
if (p < off) k++;
|
|
35277
|
+
else break;
|
|
35278
|
+
}
|
|
35279
|
+
return k >= 0 ? headers[k] : void 0;
|
|
35280
|
+
});
|
|
35281
|
+
}
|
|
35282
|
+
i = j - 1;
|
|
35283
|
+
continue;
|
|
35284
|
+
}
|
|
35285
|
+
if (/<table\b/i.test(lines[i])) {
|
|
35286
|
+
let headers = null;
|
|
35287
|
+
let depth = 0;
|
|
35288
|
+
for (let j = i; j < lines.length; j++) {
|
|
35289
|
+
depth += (lines[j].match(/<table\b/gi) ?? []).length;
|
|
35290
|
+
if (/<tr\b/i.test(lines[j]) && depth === 1) {
|
|
35291
|
+
const cells = htmlCells(lines[j]);
|
|
35292
|
+
if (!headers) headers = cells;
|
|
35293
|
+
else if (cells.length > 0) {
|
|
35294
|
+
const hdr = headers;
|
|
35295
|
+
out.set(j, (off) => {
|
|
35296
|
+
const c = cells.find((x) => off >= x.start && off < x.end);
|
|
35297
|
+
if (!c) return void 0;
|
|
35298
|
+
const h = hdr.find((x) => c.col >= x.col && c.col < x.col + x.span);
|
|
35299
|
+
return h?.text;
|
|
35300
|
+
});
|
|
35301
|
+
}
|
|
35302
|
+
}
|
|
35303
|
+
depth -= (lines[j].match(/<\/table>/gi) ?? []).length;
|
|
35304
|
+
if (depth <= 0) {
|
|
35305
|
+
i = j;
|
|
35306
|
+
break;
|
|
35307
|
+
}
|
|
35308
|
+
}
|
|
35309
|
+
}
|
|
35310
|
+
}
|
|
35311
|
+
return out;
|
|
35312
|
+
}
|
|
35313
|
+
var MD_ALWAYS_MARKER_RE = new RegExp([
|
|
35314
|
+
"(?<!\\*)\\*\\*(?!\\*)",
|
|
35315
|
+
"(?<!_)__(?!_)",
|
|
35316
|
+
"(?<!~)~~(?!~)",
|
|
35317
|
+
"<\\/?(?:u|b|i|s|em|strong|sup|sub|del|span|mark|small|big|ins|strike|tt|code|kbd|samp|var|q|cite|abbr|dfn)>",
|
|
35318
|
+
"\\\\(?=[\\\\`*_{}[\\]()#+\\-.!|~>])"
|
|
35319
|
+
].join("|"), "g");
|
|
35320
|
+
var MD_OPEN_RE = /(?<![\w*\\])(\*\*\*|\*|_)(?=[0-9A-Za-z0-9])/g;
|
|
35321
|
+
var MD_CLOSE_RE = /(?<=[0-9A-Za-z0-9])(\*\*\*|\*|_)(?![\w*])/g;
|
|
35322
|
+
function mdMarkerRanges(line) {
|
|
35323
|
+
const out = [];
|
|
35324
|
+
for (const m of line.matchAll(MD_ALWAYS_MARKER_RE)) out.push([m.index, m.index + m[0].length]);
|
|
35325
|
+
const taken = (a, b) => out.some(([s, e]) => a < e && b > s);
|
|
35326
|
+
const closes = [...line.matchAll(MD_CLOSE_RE)].filter((m) => !taken(m.index, m.index + m[0].length));
|
|
35327
|
+
let from = 0;
|
|
35328
|
+
for (const o of line.matchAll(MD_OPEN_RE)) {
|
|
35329
|
+
const os = o.index;
|
|
35330
|
+
const oe = os + o[0].length;
|
|
35331
|
+
if (os < from || taken(os, oe)) continue;
|
|
35332
|
+
const c = closes.find((m) => m.index >= oe && m[0] === o[0]);
|
|
35333
|
+
if (!c) continue;
|
|
35334
|
+
out.push([os, oe], [c.index, c.index + c[0].length]);
|
|
35335
|
+
from = c.index + c[0].length;
|
|
35336
|
+
}
|
|
35337
|
+
return out.sort((a, b) => a[0] - b[0]);
|
|
35338
|
+
}
|
|
35339
|
+
function detectMarkdownLine(line, rules, maskChar, headerAt) {
|
|
35340
|
+
const markers = mdMarkerRanges(line);
|
|
35341
|
+
if (markers.length === 0) return detect(line, rules, maskChar, headerAt);
|
|
35342
|
+
let clean = "";
|
|
35343
|
+
const map = [];
|
|
35344
|
+
let cursor = 0;
|
|
35345
|
+
const keep = (to) => {
|
|
35346
|
+
for (let i = cursor; i < to; i++) {
|
|
35347
|
+
clean += line[i];
|
|
35348
|
+
map.push(i);
|
|
35349
|
+
}
|
|
35350
|
+
};
|
|
35351
|
+
for (const [ms, me] of markers) {
|
|
35352
|
+
keep(ms);
|
|
35353
|
+
cursor = me;
|
|
35354
|
+
}
|
|
35355
|
+
keep(line.length);
|
|
35356
|
+
const hits = detect(clean, rules, maskChar, headerAt ? (i) => headerAt(map[i] ?? i) : void 0);
|
|
35357
|
+
return hits.map((h) => {
|
|
35358
|
+
const start = map[h.index];
|
|
35359
|
+
const end = map[h.index + h.length - 1] + 1;
|
|
35360
|
+
const chars = line.slice(start, end).split("");
|
|
35361
|
+
for (let k = 0; k < h.length; k++) {
|
|
35362
|
+
if (h.masked[k] !== clean[h.index + k]) chars[map[h.index + k] - start] = maskChar;
|
|
35363
|
+
}
|
|
35364
|
+
return { rule: h.rule, masked: chars.join(""), index: start, length: end - start };
|
|
35365
|
+
});
|
|
34159
35366
|
}
|
|
34160
35367
|
function redactMarkdown(markdown, options) {
|
|
35368
|
+
const maskChar = options?.maskChar ?? "\u25CF";
|
|
35369
|
+
assertMaskChar(maskChar);
|
|
35370
|
+
const rules = options?.rules ?? DEFAULT_REDACT_RULES;
|
|
34161
35371
|
const lines = markdown.split("\n");
|
|
35372
|
+
const headers = tableHeaderContext(lines);
|
|
34162
35373
|
const hits = [];
|
|
34163
35374
|
let offset = 0;
|
|
34164
|
-
const outLines = lines.map((line) => {
|
|
34165
|
-
if (line.includes("data:image/")) {
|
|
35375
|
+
const outLines = lines.map((line, i) => {
|
|
35376
|
+
if (line.includes("data:image/") || line === "" || rules.length === 0) {
|
|
34166
35377
|
offset += line.length + 1;
|
|
34167
35378
|
return line;
|
|
34168
35379
|
}
|
|
34169
|
-
const
|
|
34170
|
-
for (const h of
|
|
35380
|
+
const lineHits = detectMarkdownLine(line, rules, maskChar, headers.get(i));
|
|
35381
|
+
for (const h of lineHits) hits.push({ ...h, index: h.index + offset });
|
|
34171
35382
|
offset += line.length + 1;
|
|
34172
|
-
return
|
|
35383
|
+
return applyHits(line, lineHits);
|
|
34173
35384
|
});
|
|
34174
35385
|
return { text: outLines.join("\n"), hits };
|
|
34175
35386
|
}
|
|
@@ -34534,6 +35745,11 @@ var HwpxSession = class _HwpxSession {
|
|
|
34534
35745
|
return skip("\uBB38\uB2E8 \uC18C\uC2A4\uB9F5 \uB9E4\uD551 \uC2E4\uD328 (\uBA38\uB9AC\uB9D0/\uAE00\uC0C1\uC790/\uCEA1\uC158 \uC601\uC5ED\uC774\uAC70\uB098 \uD14D\uC2A4\uD2B8 \uBD88\uC77C\uCE58)");
|
|
34535
35746
|
}
|
|
34536
35747
|
let newPlain = newTextRaw.split("\n").map((l) => l.trim()).filter(Boolean).join(" ");
|
|
35748
|
+
if (mapping.noteMarks?.length) {
|
|
35749
|
+
const r = removeNoteMarks(newPlain, mapping.noteMarks);
|
|
35750
|
+
newPlain = r.text;
|
|
35751
|
+
if (r.missing.length) skipped.push({ reason: "\uAC01\uC8FC \uCC38\uC870 \uBD80\uD638 \uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 \u2014 \uAC01\uC8FC \uC720\uC9C0, \uBCF8\uBB38\uB9CC \uC801\uC6A9", before: r.missing.join(" ") });
|
|
35752
|
+
}
|
|
34537
35753
|
if (mapping.prefixStripped) {
|
|
34538
35754
|
const origPrefix = block.text.split(" ", 1)[0];
|
|
34539
35755
|
const sp = newPlain.indexOf(" ");
|
|
@@ -35180,8 +36396,8 @@ function reflowBlockFlow(container, doc, styles, areaW, mode, counter, bodyH) {
|
|
|
35180
36396
|
for (const p of elements2(container)) {
|
|
35181
36397
|
if (ln2(p) !== "p") continue;
|
|
35182
36398
|
reflowTablesIn(p, doc, styles, mode, counter);
|
|
35183
|
-
const
|
|
35184
|
-
const startV = cursorV + prevSpaceAfter + (
|
|
36399
|
+
const g2 = styles.paraGeom.get(p.getAttribute("paraPrIDRef") ?? "");
|
|
36400
|
+
const startV = cursorV + prevSpaceAfter + (g2?.spaceBefore ?? 0);
|
|
35185
36401
|
const res = reflowPara(p, doc, styles, areaW, startV, mode);
|
|
35186
36402
|
if (!res) {
|
|
35187
36403
|
const lsa = findChildByLocalName(p, "linesegarray");
|
|
@@ -35194,7 +36410,7 @@ function reflowBlockFlow(container, doc, styles, areaW, mode, counter, bodyH) {
|
|
|
35194
36410
|
}
|
|
35195
36411
|
if (Number.isFinite(bottom)) {
|
|
35196
36412
|
cursorV = bottom;
|
|
35197
|
-
prevSpaceAfter =
|
|
36413
|
+
prevSpaceAfter = g2?.spaceAfter ?? 0;
|
|
35198
36414
|
}
|
|
35199
36415
|
}
|
|
35200
36416
|
continue;
|
|
@@ -35626,14 +36842,14 @@ function drawTable(tbl2, tx, ty, ctx, depth) {
|
|
|
35626
36842
|
w: colX[Math.min(c.ca + c.cs, nCols)] - colX[c.ca],
|
|
35627
36843
|
h: rowY[Math.min(c.ra + c.rs, nRows)] - rowY[c.ra]
|
|
35628
36844
|
}));
|
|
35629
|
-
for (const
|
|
35630
|
-
const bf =
|
|
35631
|
-
if (bf?.fill) emit(ctx, `<rect x="${pt(
|
|
36845
|
+
for (const g2 of geom) {
|
|
36846
|
+
const bf = g2.c.bfId != null ? ctx.styles.borderFill.get(g2.c.bfId) : void 0;
|
|
36847
|
+
if (bf?.fill) emit(ctx, `<rect x="${pt(g2.x)}" y="${pt(g2.y)}" width="${pt(g2.w)}" height="${pt(g2.h)}" fill="${escapeXml7(bf.fill)}"/>`);
|
|
35632
36848
|
}
|
|
35633
|
-
for (const
|
|
35634
|
-
const { c } =
|
|
36849
|
+
for (const g2 of geom) {
|
|
36850
|
+
const { c } = g2;
|
|
35635
36851
|
if (!c.sub) continue;
|
|
35636
|
-
const innerH =
|
|
36852
|
+
const innerH = g2.h - c.marginT - c.marginB;
|
|
35637
36853
|
const extent = cellContentExtent(c, ctx.extentMemo);
|
|
35638
36854
|
const va = c.sub.getAttribute("vertAlign") ?? "TOP";
|
|
35639
36855
|
let yoff = 0;
|
|
@@ -35641,16 +36857,16 @@ function drawTable(tbl2, tx, ty, ctx, depth) {
|
|
|
35641
36857
|
else if (va === "BOTTOM") yoff = Math.max(0, innerH - extent);
|
|
35642
36858
|
for (const p of elements(c.sub)) {
|
|
35643
36859
|
if (ln(p) !== "p") continue;
|
|
35644
|
-
drawPara(p,
|
|
36860
|
+
drawPara(p, g2.x + c.marginL, g2.y + c.marginT + yoff, g2.w - c.marginL - c.marginR, ctx, depth + 1);
|
|
35645
36861
|
}
|
|
35646
36862
|
}
|
|
35647
|
-
for (const
|
|
35648
|
-
const bf =
|
|
36863
|
+
for (const g2 of geom) {
|
|
36864
|
+
const bf = g2.c.bfId != null ? ctx.styles.borderFill.get(g2.c.bfId) : void 0;
|
|
35649
36865
|
if (!bf) continue;
|
|
35650
|
-
if (bf.top) emit(ctx, edgeLine(
|
|
35651
|
-
if (bf.bottom) emit(ctx, edgeLine(
|
|
35652
|
-
if (bf.left) emit(ctx, edgeLine(
|
|
35653
|
-
if (bf.right) emit(ctx, edgeLine(
|
|
36866
|
+
if (bf.top) emit(ctx, edgeLine(g2.x, g2.y, g2.x + g2.w, g2.y, bf.top));
|
|
36867
|
+
if (bf.bottom) emit(ctx, edgeLine(g2.x, g2.y + g2.h, g2.x + g2.w, g2.y + g2.h, bf.bottom));
|
|
36868
|
+
if (bf.left) emit(ctx, edgeLine(g2.x, g2.y, g2.x, g2.y + g2.h, bf.left));
|
|
36869
|
+
if (bf.right) emit(ctx, edgeLine(g2.x + g2.w, g2.y, g2.x + g2.w, g2.y + g2.h, bf.right));
|
|
35654
36870
|
}
|
|
35655
36871
|
ctx.parentStack.pop();
|
|
35656
36872
|
emit(ctx, "</g>");
|
|
@@ -36414,11 +37630,11 @@ function shapeElement(records, sc, scIdx, end, posXml, drawText, ctx, wrap) {
|
|
|
36414
37630
|
return `<hp:line${wa}>${posXml}${sizes}${line}${pts}</hp:line>`;
|
|
36415
37631
|
}
|
|
36416
37632
|
if (sc.chid === CHID_POL || sc.chid === CHID_CUR) {
|
|
36417
|
-
const
|
|
37633
|
+
const g2 = find(sc.chid === CHID_POL ? TAG_SHAPE_COMPONENT_POLYGON : TAG_SHAPE_COMPONENT_CURVE);
|
|
36418
37634
|
let pts = "";
|
|
36419
|
-
if (
|
|
36420
|
-
const n = Math.min(
|
|
36421
|
-
for (let k = 0; k < n && 2 + k * 8 + 8 <=
|
|
37635
|
+
if (g2 && g2.length >= 2) {
|
|
37636
|
+
const n = Math.min(g2.readInt16LE(0), 4096);
|
|
37637
|
+
for (let k = 0; k < n && 2 + k * 8 + 8 <= g2.length; k++) pts += `<hp:pt x="${g2.readInt32LE(2 + k * 8)}" y="${g2.readInt32LE(6 + k * 8)}"/>`;
|
|
36422
37638
|
}
|
|
36423
37639
|
const tag2 = sc.chid === CHID_POL ? "polygon" : "curv";
|
|
36424
37640
|
return `<hp:${tag2}${wa}>${posXml}${sizes}${line}${pts}${drawText}</hp:${tag2}>`;
|
|
@@ -36809,6 +38025,10 @@ async function parse(input, options) {
|
|
|
36809
38025
|
}
|
|
36810
38026
|
const format = detectFormat(buffer);
|
|
36811
38027
|
const result = await dispatch(format, buffer, opts);
|
|
38028
|
+
if (result.success && opts?.images === false) {
|
|
38029
|
+
delete result.images;
|
|
38030
|
+
dropImageBytes(result.blocks);
|
|
38031
|
+
}
|
|
36812
38032
|
if (result.success && opts?.classifyTables) classifyTableTree(result.blocks);
|
|
36813
38033
|
if (result.success && !result.pages) {
|
|
36814
38034
|
const pages = blocksToPages(result.blocks);
|
|
@@ -36816,6 +38036,17 @@ async function parse(input, options) {
|
|
|
36816
38036
|
}
|
|
36817
38037
|
return result;
|
|
36818
38038
|
}
|
|
38039
|
+
function dropImageBytes(blocks, depth = 0) {
|
|
38040
|
+
if (!blocks || depth > 64) return;
|
|
38041
|
+
for (const b of blocks) {
|
|
38042
|
+
if (b.imageData) delete b.imageData;
|
|
38043
|
+
dropImageBytes(b.children, depth + 1);
|
|
38044
|
+
if (b.table) {
|
|
38045
|
+
for (const row of b.table.cells) for (const cell2 of row) dropImageBytes(cell2.blocks, depth + 1);
|
|
38046
|
+
dropImageBytes(b.table.captionBlocks, depth + 1);
|
|
38047
|
+
}
|
|
38048
|
+
}
|
|
38049
|
+
}
|
|
36819
38050
|
async function dispatch(format, buffer, opts) {
|
|
36820
38051
|
switch (format) {
|
|
36821
38052
|
case "hwpx": {
|
|
@@ -36846,7 +38077,7 @@ async function dispatch(format, buffer, opts) {
|
|
|
36846
38077
|
}
|
|
36847
38078
|
async function parseImage(buffer, options) {
|
|
36848
38079
|
try {
|
|
36849
|
-
const { parseImageDocument } = await import("./image-ocr-
|
|
38080
|
+
const { parseImageDocument } = await import("./image-ocr-R6W6A75P.js");
|
|
36850
38081
|
const { blocks, warnings } = await parseImageDocument(buffer, options);
|
|
36851
38082
|
return {
|
|
36852
38083
|
success: true,
|
|
@@ -36905,7 +38136,7 @@ async function parseHwp(buffer, options) {
|
|
|
36905
38136
|
async function parsePdf(buffer, options) {
|
|
36906
38137
|
let parsePdfDocument;
|
|
36907
38138
|
try {
|
|
36908
|
-
const mod = await import("./parser-
|
|
38139
|
+
const mod = await import("./parser-HWFQRSUQ.js");
|
|
36909
38140
|
parsePdfDocument = mod.parsePdfDocument;
|
|
36910
38141
|
} catch {
|
|
36911
38142
|
return {
|
|
@@ -37030,6 +38261,7 @@ export {
|
|
|
37030
38261
|
fillHwpx,
|
|
37031
38262
|
fillWithUniqueGuard,
|
|
37032
38263
|
fitRatioForFewerLines,
|
|
38264
|
+
flattenLayoutTables,
|
|
37033
38265
|
formatFillValue,
|
|
37034
38266
|
gongmunLintWarnings,
|
|
37035
38267
|
hasStructuredCellContent,
|