kordoc 3.3.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +584 -584
- package/dist/{-RAVJL5UK.js → -YOQGQ22S.js} +5 -5
- package/dist/{chunk-MOL7MDBG.js → chunk-3TBUDJDE.js} +1 -1
- package/dist/chunk-3TBUDJDE.js.map +1 -0
- package/dist/{chunk-GE43BE46.js → chunk-AIQ3ISQU.js} +1 -1
- package/dist/chunk-AIQ3ISQU.js.map +1 -0
- package/dist/{chunk-MEPHGCPQ.js → chunk-CFQLK3LM.js} +1 -1
- package/dist/chunk-CFQLK3LM.js.map +1 -0
- package/dist/{chunk-J72F7FIT.js → chunk-CGHPBI3X.js} +256 -84
- package/dist/chunk-CGHPBI3X.js.map +1 -0
- package/dist/{chunk-HNUFTEV5.js → chunk-FPFB7AFX.js} +2 -2
- package/dist/chunk-FPFB7AFX.js.map +1 -0
- package/dist/chunk-GS7T56RP.cjs.map +1 -1
- package/dist/{chunk-ESBGMBRF.js → chunk-SE3KEL4G.js} +2 -2
- package/dist/chunk-SE3KEL4G.js.map +1 -0
- package/dist/{chunk-FUB2BGXV.cjs → chunk-W7JHJQ2O.cjs} +2 -2
- package/dist/chunk-W7JHJQ2O.cjs.map +1 -0
- package/dist/{chunk-DCZVOIEO.cjs → chunk-X34YWRL5.cjs} +1 -1
- package/dist/chunk-X34YWRL5.cjs.map +1 -0
- package/dist/cli.js +8 -8
- package/dist/cli.js.map +1 -1
- package/dist/{detect-RI2MQ33K.js → detect-NTR2FUEG.js} +2 -2
- package/dist/{formula-JCNF43NE.js → formula-KOQV353G.js} +1 -1
- package/dist/formula-KOQV353G.js.map +1 -0
- package/dist/{formula-RXVSQPXI.js → formula-MEDGYQXS.js} +1 -1
- package/dist/formula-MEDGYQXS.js.map +1 -0
- package/dist/{formula-5NKVS2LR.cjs → formula-ZYUDQHD3.cjs} +1 -1
- package/dist/formula-ZYUDQHD3.cjs.map +1 -0
- package/dist/index.cjs +424 -252
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +254 -82
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +6 -6
- package/dist/mcp.js.map +1 -1
- package/dist/page-range-CIRRJPXJ.js +7 -0
- package/dist/page-range-OF5I4PQY.js +8 -0
- package/dist/page-range-TPIRSA3J.cjs +8 -0
- package/dist/page-range-TPIRSA3J.cjs.map +1 -0
- package/dist/{parser-FLE2ZPAW.js → parser-5HDZZWDH.js} +5 -5
- package/dist/parser-5HDZZWDH.js.map +1 -0
- package/dist/{parser-DADWQYCQ.js → parser-ET64VEEX.js} +5 -5
- package/dist/parser-ET64VEEX.js.map +1 -0
- package/dist/{parser-YWKNDGKI.cjs → parser-JAZCZJI6.cjs} +18 -18
- package/dist/parser-JAZCZJI6.cjs.map +1 -0
- package/dist/{provider-G4C2V2PD.cjs → provider-5K7O3Z2O.cjs} +1 -1
- package/dist/provider-5K7O3Z2O.cjs.map +1 -0
- package/dist/{provider-AKROB7WQ.js → provider-7H4CPZYS.js} +1 -1
- package/dist/provider-7H4CPZYS.js.map +1 -0
- package/dist/{provider-4ZJKV3DC.js → provider-H4WWSPOV.js} +1 -1
- package/dist/provider-H4WWSPOV.js.map +1 -0
- package/dist/{setup-57FB3LSP.js → setup-QSJ2INNS.js} +1 -1
- package/dist/setup-QSJ2INNS.js.map +1 -0
- package/dist/{watch-6QPQU277.js → watch-56GNC2YN.js} +5 -5
- package/dist/watch-56GNC2YN.js.map +1 -0
- package/package.json +98 -98
- package/dist/chunk-DCZVOIEO.cjs.map +0 -1
- package/dist/chunk-ESBGMBRF.js.map +0 -1
- package/dist/chunk-FUB2BGXV.cjs.map +0 -1
- package/dist/chunk-GE43BE46.js.map +0 -1
- package/dist/chunk-HNUFTEV5.js.map +0 -1
- package/dist/chunk-J72F7FIT.js.map +0 -1
- package/dist/chunk-MEPHGCPQ.js.map +0 -1
- package/dist/chunk-MOL7MDBG.js.map +0 -1
- package/dist/formula-5NKVS2LR.cjs.map +0 -1
- package/dist/formula-JCNF43NE.js.map +0 -1
- package/dist/formula-RXVSQPXI.js.map +0 -1
- package/dist/page-range-737B4EZW.js +0 -8
- package/dist/page-range-LNMXJU6W.js +0 -7
- package/dist/page-range-P7SDW6LR.cjs +0 -8
- package/dist/page-range-P7SDW6LR.cjs.map +0 -1
- package/dist/parser-DADWQYCQ.js.map +0 -1
- package/dist/parser-FLE2ZPAW.js.map +0 -1
- package/dist/parser-YWKNDGKI.cjs.map +0 -1
- package/dist/provider-4ZJKV3DC.js.map +0 -1
- package/dist/provider-AKROB7WQ.js.map +0 -1
- package/dist/provider-G4C2V2PD.cjs.map +0 -1
- package/dist/setup-57FB3LSP.js.map +0 -1
- package/dist/watch-6QPQU277.js.map +0 -1
- /package/dist/{-RAVJL5UK.js.map → -YOQGQ22S.js.map} +0 -0
- /package/dist/{detect-RI2MQ33K.js.map → detect-NTR2FUEG.js.map} +0 -0
- /package/dist/{page-range-737B4EZW.js.map → page-range-CIRRJPXJ.js.map} +0 -0
- /package/dist/{page-range-LNMXJU6W.js.map → page-range-OF5I4PQY.js.map} +0 -0
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
patchHwpxBlocks,
|
|
32
32
|
renderHtml,
|
|
33
33
|
scanSectionXml
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-CGHPBI3X.js";
|
|
35
35
|
import {
|
|
36
36
|
detectFormat,
|
|
37
37
|
detectOle2Format,
|
|
@@ -40,12 +40,12 @@ import {
|
|
|
40
40
|
isOldHwpFile,
|
|
41
41
|
isPdfFile,
|
|
42
42
|
isZipFile
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-CFQLK3LM.js";
|
|
44
44
|
import {
|
|
45
45
|
VERSION,
|
|
46
46
|
blocksToMarkdown
|
|
47
|
-
} from "./chunk-
|
|
48
|
-
import "./chunk-
|
|
47
|
+
} from "./chunk-FPFB7AFX.js";
|
|
48
|
+
import "./chunk-3TBUDJDE.js";
|
|
49
49
|
export {
|
|
50
50
|
HwpxSession,
|
|
51
51
|
VERSION,
|
|
@@ -88,4 +88,4 @@ export {
|
|
|
88
88
|
renderHtml,
|
|
89
89
|
scanSectionXml
|
|
90
90
|
};
|
|
91
|
-
//# sourceMappingURL=-
|
|
91
|
+
//# sourceMappingURL=-YOQGQ22S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/page-range.ts"],"sourcesContent":["/** 페이지/섹션 범위 파싱 유틸리티 */\r\n\r\n/**\r\n * 페이지 범위 지정을 1-based Set<number>로 변환.\r\n *\r\n * @param spec - [1,2,3] 또는 \"1-3\" 또는 \"1,3,5-7\"\r\n * @param maxPages - 최대 페이지 수 (클램핑 상한)\r\n * @returns 1-based 페이지 번호 Set\r\n */\r\nexport function parsePageRange(spec: number[] | string, maxPages: number): Set<number> {\r\n const result = new Set<number>()\r\n if (maxPages <= 0) return result\r\n\r\n if (Array.isArray(spec)) {\r\n for (const n of spec) {\r\n const page = Math.round(n)\r\n if (page >= 1 && page <= maxPages) result.add(page)\r\n }\r\n return result\r\n }\r\n\r\n if (typeof spec !== \"string\" || spec.trim() === \"\") return result\r\n\r\n const parts = spec.split(\",\")\r\n for (const part of parts) {\r\n const trimmed = part.trim()\r\n if (!trimmed) continue\r\n\r\n const rangeMatch = trimmed.match(/^(\\d+)\\s*-\\s*(\\d+)$/)\r\n if (rangeMatch) {\r\n const start = Math.max(1, parseInt(rangeMatch[1], 10))\r\n const end = Math.min(maxPages, parseInt(rangeMatch[2], 10))\r\n for (let i = start; i <= end; i++) result.add(i)\r\n } else {\r\n const page = parseInt(trimmed, 10)\r\n if (!isNaN(page) && page >= 1 && page <= maxPages) result.add(page)\r\n }\r\n }\r\n\r\n return result\r\n}\r\n"],"mappings":";;;AASO,SAAS,eAAe,MAAyB,UAA+B;AACrF,QAAM,SAAS,oBAAI,IAAY;AAC/B,MAAI,YAAY,EAAG,QAAO;AAE1B,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,eAAW,KAAK,MAAM;AACpB,YAAM,OAAO,KAAK,MAAM,CAAC;AACzB,UAAI,QAAQ,KAAK,QAAQ,SAAU,QAAO,IAAI,IAAI;AAAA,IACpD;AACA,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,SAAS,YAAY,KAAK,KAAK,MAAM,GAAI,QAAO;AAE3D,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AAEd,UAAM,aAAa,QAAQ,MAAM,qBAAqB;AACtD,QAAI,YAAY;AACd,YAAM,QAAQ,KAAK,IAAI,GAAG,SAAS,WAAW,CAAC,GAAG,EAAE,CAAC;AACrD,YAAM,MAAM,KAAK,IAAI,UAAU,SAAS,WAAW,CAAC,GAAG,EAAE,CAAC;AAC1D,eAAS,IAAI,OAAO,KAAK,KAAK,IAAK,QAAO,IAAI,CAAC;AAAA,IACjD,OAAO;AACL,YAAM,OAAO,SAAS,SAAS,EAAE;AACjC,UAAI,CAAC,MAAM,IAAI,KAAK,QAAQ,KAAK,QAAQ,SAAU,QAAO,IAAI,IAAI;AAAA,IACpE;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/page-range.ts"],"sourcesContent":["/** 페이지/섹션 범위 파싱 유틸리티 */\r\n\r\n/**\r\n * 페이지 범위 지정을 1-based Set<number>로 변환.\r\n *\r\n * @param spec - [1,2,3] 또는 \"1-3\" 또는 \"1,3,5-7\"\r\n * @param maxPages - 최대 페이지 수 (클램핑 상한)\r\n * @returns 1-based 페이지 번호 Set\r\n */\r\nexport function parsePageRange(spec: number[] | string, maxPages: number): Set<number> {\r\n const result = new Set<number>()\r\n if (maxPages <= 0) return result\r\n\r\n if (Array.isArray(spec)) {\r\n for (const n of spec) {\r\n const page = Math.round(n)\r\n if (page >= 1 && page <= maxPages) result.add(page)\r\n }\r\n return result\r\n }\r\n\r\n if (typeof spec !== \"string\" || spec.trim() === \"\") return result\r\n\r\n const parts = spec.split(\",\")\r\n for (const part of parts) {\r\n const trimmed = part.trim()\r\n if (!trimmed) continue\r\n\r\n const rangeMatch = trimmed.match(/^(\\d+)\\s*-\\s*(\\d+)$/)\r\n if (rangeMatch) {\r\n const start = Math.max(1, parseInt(rangeMatch[1], 10))\r\n const end = Math.min(maxPages, parseInt(rangeMatch[2], 10))\r\n for (let i = start; i <= end; i++) result.add(i)\r\n } else {\r\n const page = parseInt(trimmed, 10)\r\n if (!isNaN(page) && page >= 1 && page <= maxPages) result.add(page)\r\n }\r\n }\r\n\r\n return result\r\n}\r\n"],"mappings":";AASO,SAAS,eAAe,MAAyB,UAA+B;AACrF,QAAM,SAAS,oBAAI,IAAY;AAC/B,MAAI,YAAY,EAAG,QAAO;AAE1B,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,eAAW,KAAK,MAAM;AACpB,YAAM,OAAO,KAAK,MAAM,CAAC;AACzB,UAAI,QAAQ,KAAK,QAAQ,SAAU,QAAO,IAAI,IAAI;AAAA,IACpD;AACA,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,SAAS,YAAY,KAAK,KAAK,MAAM,GAAI,QAAO;AAE3D,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AAEd,UAAM,aAAa,QAAQ,MAAM,qBAAqB;AACtD,QAAI,YAAY;AACd,YAAM,QAAQ,KAAK,IAAI,GAAG,SAAS,WAAW,CAAC,GAAG,EAAE,CAAC;AACrD,YAAM,MAAM,KAAK,IAAI,UAAU,SAAS,WAAW,CAAC,GAAG,EAAE,CAAC;AAC1D,eAAS,IAAI,OAAO,KAAK,KAAK,IAAK,QAAO,IAAI,CAAC;AAAA,IACjD,OAAO;AACL,YAAM,OAAO,SAAS,SAAS,EAAE;AACjC,UAAI,CAAC,MAAM,IAAI,KAAK,QAAQ,KAAK,QAAQ,SAAU,QAAO,IAAI,IAAI;AAAA,IACpE;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/detect.ts","../src/hwp5/cfb-lenient.ts"],"sourcesContent":["/** 매직 바이트 기반 파일 포맷 감지 */\r\n\r\nimport JSZip from \"jszip\"\r\nimport type { FileType } from \"./types.js\"\r\nimport { parseLenientCfb } from \"./hwp5/cfb-lenient.js\"\r\n\r\n/** 매직 바이트 뷰 생성 (복사 없이 view) */\r\nfunction magicBytes(buffer: ArrayBuffer): Uint8Array {\r\n return new Uint8Array(buffer, 0, Math.min(4, buffer.byteLength))\r\n}\r\n\r\n/** ZIP 파일 여부: PK\\x03\\x04 */\r\nexport function isZipFile(buffer: ArrayBuffer): boolean {\r\n const b = magicBytes(buffer)\r\n return b[0] === 0x50 && b[1] === 0x4b && b[2] === 0x03 && b[3] === 0x04\r\n}\r\n\r\n/** HWPX (ZIP 기반 한컴 문서): PK\\x03\\x04 — 하위 호환용 */\r\nexport function isHwpxFile(buffer: ArrayBuffer): boolean {\r\n return isZipFile(buffer)\r\n}\r\n\r\n/** HWP 5.x (OLE2 바이너리 한컴 문서): \\xD0\\xCF\\x11\\xE0 */\r\nexport function isOldHwpFile(buffer: ArrayBuffer): boolean {\r\n const b = magicBytes(buffer)\r\n return b[0] === 0xd0 && b[1] === 0xcf && b[2] === 0x11 && b[3] === 0xe0\r\n}\r\n\r\n/**\r\n * HWP 3.x (한글 워드프로세서 3.0): \"HWP Document File V3.00 \\x1A\\x01\\x02\\x03\\x04\\x05\" 30 byte.\r\n * CFB(OLE2) 컨테이너 아닌 단일 binary stream — isOldHwpFile 과 magic 이 다르다.\r\n */\r\nconst HWP3_PREFIX = new TextEncoder().encode(\"HWP Document File V3.00\")\r\nexport function isHwp3File(buffer: ArrayBuffer): boolean {\r\n if (buffer.byteLength < HWP3_PREFIX.length) return false\r\n const head = new Uint8Array(buffer, 0, HWP3_PREFIX.length)\r\n for (let i = 0; i < HWP3_PREFIX.length; i++) {\r\n if (head[i] !== HWP3_PREFIX[i]) return false\r\n }\r\n return true\r\n}\r\n\r\n/** PDF 문서: %PDF */\r\nexport function isPdfFile(buffer: ArrayBuffer): boolean {\r\n const b = magicBytes(buffer)\r\n return b[0] === 0x25 && b[1] === 0x50 && b[2] === 0x44 && b[3] === 0x46\r\n}\r\n\r\n/** HWPML (XML 기반 한컴 문서): <?xml ... <HWPML */\r\nexport function isHwpmlFile(buffer: ArrayBuffer): boolean {\r\n const bytes = new Uint8Array(buffer, 0, Math.min(512, buffer.byteLength))\r\n const head = new TextDecoder(\"utf-8\", { fatal: false }).decode(bytes).replace(/^\\uFEFF/, \"\")\r\n return head.trimStart().startsWith(\"<?xml\") && head.includes(\"<HWPML\")\r\n}\r\n\r\n/** 동기 포맷 감지 — ZIP은 모두 \"hwpx\"로 반환 (하위 호환) */\r\nexport function detectFormat(buffer: ArrayBuffer): FileType {\r\n if (buffer.byteLength < 4) return \"unknown\"\r\n if (isHwp3File(buffer)) return \"hwp3\"\r\n if (isZipFile(buffer)) return \"hwpx\"\r\n if (isOldHwpFile(buffer)) return \"hwp\"\r\n if (isPdfFile(buffer)) return \"pdf\"\r\n if (isHwpmlFile(buffer)) return \"hwpml\"\r\n return \"unknown\"\r\n}\r\n\r\n/**\r\n * OLE2 컨테이너 내부 스트림 기반 포맷 세분화.\r\n * HWP 5.x, XLS 모두 OLE2이므로 스트림 이름으로 구분.\r\n * - \"Workbook\" 또는 \"Book\" → 'xls'\r\n * - 그 외 (FileHeader 등) → 'hwp'\r\n */\r\nexport function detectOle2Format(buffer: ArrayBuffer): \"hwp\" | \"xls\" | \"unknown\" {\r\n try {\r\n const cfb = parseLenientCfb(Buffer.from(buffer))\r\n const names = cfb.entries().map(e => e.name)\r\n if (names.includes(\"Workbook\") || names.includes(\"Book\")) return \"xls\"\r\n if (names.includes(\"FileHeader\")) return \"hwp\"\r\n // FileHeader 없어도 BodyText/DocInfo 있으면 hwp\r\n if (names.some(n => n === \"DocInfo\" || n.startsWith(\"Section\"))) return \"hwp\"\r\n return \"unknown\"\r\n } catch {\r\n return \"unknown\"\r\n }\r\n}\r\n\r\n/**\r\n * ZIP 내부 구조 기반 포맷 세분화.\r\n * HWPX, XLSX, DOCX 모두 ZIP이므로 내부 파일로 구분.\r\n */\r\nexport async function detectZipFormat(buffer: ArrayBuffer): Promise<\"hwpx\" | \"xlsx\" | \"docx\" | \"unknown\"> {\r\n try {\r\n const zip = await JSZip.loadAsync(buffer)\r\n // XLSX: xl/workbook.xml\r\n if (zip.file(\"xl/workbook.xml\")) return \"xlsx\"\r\n // DOCX: word/document.xml\r\n if (zip.file(\"word/document.xml\")) return \"docx\"\r\n // HWPX: Contents/ 또는 content.hpf 또는 mimetype\r\n if (zip.file(\"Contents/content.hpf\") || zip.file(\"mimetype\")) return \"hwpx\"\r\n // 기타 ZIP 내에 section 파일이 있으면 HWPX로 추정\r\n const hasSection = Object.keys(zip.files).some(f => f.startsWith(\"Contents/\"))\r\n if (hasSection) return \"hwpx\"\r\n return \"unknown\"\r\n } catch {\r\n return \"unknown\"\r\n }\r\n}\r\n","/**\r\n * Lenient CFB (Compound File Binary / OLE2) 파서.\r\n *\r\n * 표준 cfb 모듈이 FAT 검증 실패로 거부하는 손상된 HWP 파일을 열기 위한 폴백.\r\n * 직접 헤더/FAT/디렉토리를 파싱하여 스트림 데이터를 추출.\r\n *\r\n * 참조: rhwp (MIT) src/parser/cfb_reader.rs (LenientCfbReader)\r\n * 참조: MS-CFB spec (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb)\r\n */\r\n\r\nimport { decompressStream } from \"./record.js\"\r\n\r\n// ── 상수 ──\r\n\r\nconst CFB_MAGIC = Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1])\r\nconst END_OF_CHAIN = 0xfffffffe\r\nconst FREE_SECT = 0xffffffff\r\n\r\n/** 순환 감지용 최대 체인 길이 */\r\nconst MAX_CHAIN_LENGTH = 1_000_000\r\n/** 최대 디렉토리 엔트리 수 */\r\nconst MAX_DIR_ENTRIES = 100_000\r\n/** 최대 스트림 크기 (100MB) */\r\nconst MAX_STREAM_SIZE = 100 * 1024 * 1024\r\n\r\n// ── 디렉토리 엔트리 ──\r\n\r\ninterface DirEntry {\r\n name: string\r\n type: number // 0=unknown, 1=storage, 2=stream, 5=root\r\n startSector: number\r\n size: number\r\n}\r\n\r\n// ── CFB 컨테이너 ──\r\n\r\nexport interface LenientCfbContainer {\r\n /** 이름 기반 스트림 탐색 */\r\n findStream(path: string): Buffer | null\r\n /** 디렉토리 엔트리 목록 */\r\n entries(): DirEntry[]\r\n}\r\n\r\n// ── 구현 ──\r\n\r\nexport function parseLenientCfb(data: Buffer): LenientCfbContainer {\r\n if (data.length < 512) throw new Error(\"CFB 파일이 너무 짧습니다 (최소 512바이트)\")\r\n if (!data.subarray(0, 8).equals(CFB_MAGIC)) throw new Error(\"CFB 매직 바이트 불일치\")\r\n\r\n // ── 헤더 파싱 ──\r\n\r\n const sectorSizeShift = data.readUInt16LE(30)\r\n if (sectorSizeShift < 7 || sectorSizeShift > 16) throw new Error(\"유효하지 않은 섹터 크기 시프트: \" + sectorSizeShift)\r\n const sectorSize = 1 << sectorSizeShift // 보통 512\r\n const miniSectorSizeShift = data.readUInt16LE(32)\r\n if (miniSectorSizeShift > 16) throw new Error(\"유효하지 않은 미니 섹터 크기 시프트: \" + miniSectorSizeShift)\r\n const miniSectorSize = 1 << miniSectorSizeShift // 보통 64\r\n\r\n const fatSectorCount = data.readUInt32LE(44)\r\n if (fatSectorCount > 10000) throw new Error(\"FAT 섹터 수가 너무 많습니다: \" + fatSectorCount)\r\n const firstDirSector = data.readUInt32LE(48)\r\n const miniStreamCutoff = data.readUInt32LE(56) // 보통 4096\r\n const firstMiniFatSector = data.readUInt32LE(60)\r\n const miniFatSectorCount = data.readUInt32LE(64)\r\n const firstDifatSector = data.readUInt32LE(68)\r\n const difatSectorCount = data.readUInt32LE(72)\r\n\r\n // ── 유틸 ──\r\n\r\n function sectorOffset(id: number): number {\r\n return 512 + id * sectorSize\r\n }\r\n\r\n function readSectorData(id: number): Buffer {\r\n const off = sectorOffset(id)\r\n if (off + sectorSize > data.length) return Buffer.alloc(0)\r\n return data.subarray(off, off + sectorSize)\r\n }\r\n\r\n // ── DIFAT → FAT 섹터 목록 ──\r\n\r\n const fatSectors: number[] = []\r\n\r\n // 헤더 내 DIFAT (최대 109개)\r\n for (let i = 0; i < 109 && fatSectors.length < fatSectorCount; i++) {\r\n const sid = data.readUInt32LE(76 + i * 4)\r\n if (sid === FREE_SECT || sid === END_OF_CHAIN) break\r\n fatSectors.push(sid)\r\n }\r\n\r\n // 추가 DIFAT 섹터 체인\r\n let difatSector = firstDifatSector\r\n const visitedDifat = new Set<number>()\r\n for (let d = 0; d < difatSectorCount && difatSector !== END_OF_CHAIN && difatSector !== FREE_SECT; d++) {\r\n if (visitedDifat.has(difatSector)) break\r\n visitedDifat.add(difatSector)\r\n\r\n const buf = readSectorData(difatSector)\r\n const entriesPerSector = (sectorSize / 4) - 1 // 마지막 4바이트는 다음 DIFAT 포인터\r\n for (let i = 0; i < entriesPerSector && fatSectors.length < fatSectorCount; i++) {\r\n const sid = buf.readUInt32LE(i * 4)\r\n if (sid === FREE_SECT || sid === END_OF_CHAIN) continue\r\n fatSectors.push(sid)\r\n }\r\n difatSector = buf.readUInt32LE(entriesPerSector * 4)\r\n }\r\n\r\n // ── FAT 테이블 구축 ──\r\n\r\n const entriesPerFatSector = sectorSize / 4\r\n const fatTable = new Uint32Array(fatSectors.length * entriesPerFatSector)\r\n\r\n for (let fi = 0; fi < fatSectors.length; fi++) {\r\n const buf = readSectorData(fatSectors[fi])\r\n for (let i = 0; i < entriesPerFatSector; i++) {\r\n fatTable[fi * entriesPerFatSector + i] = i * 4 + 3 < buf.length\r\n ? buf.readUInt32LE(i * 4)\r\n : FREE_SECT\r\n }\r\n }\r\n\r\n // ── 체인 리더 (순환 방지) ──\r\n\r\n function readChain(startSector: number, maxBytes: number): Buffer {\r\n if (startSector === END_OF_CHAIN || startSector === FREE_SECT) return Buffer.alloc(0)\r\n if (maxBytes > MAX_STREAM_SIZE) throw new Error(\"스트림이 너무 큽니다\")\r\n\r\n const chunks: Buffer[] = []\r\n let current = startSector\r\n let totalRead = 0\r\n const visited = new Set<number>()\r\n\r\n while (current !== END_OF_CHAIN && current !== FREE_SECT && totalRead < maxBytes) {\r\n if (visited.has(current)) break // 순환 감지\r\n if (visited.size > MAX_CHAIN_LENGTH) break\r\n visited.add(current)\r\n\r\n const buf = readSectorData(current)\r\n const remaining = maxBytes - totalRead\r\n chunks.push(remaining < sectorSize ? buf.subarray(0, remaining) : buf)\r\n totalRead += Math.min(buf.length, remaining)\r\n\r\n current = current < fatTable.length ? fatTable[current] : END_OF_CHAIN\r\n }\r\n\r\n return Buffer.concat(chunks)\r\n }\r\n\r\n // ── Mini-FAT 테이블 ──\r\n\r\n let miniFatTable: Uint32Array | null = null\r\n\r\n function getMiniFatTable(): Uint32Array {\r\n if (miniFatTable) return miniFatTable\r\n\r\n if (miniFatSectorCount === 0 || firstMiniFatSector === END_OF_CHAIN) {\r\n miniFatTable = new Uint32Array(0)\r\n return miniFatTable\r\n }\r\n\r\n const miniFatData = readChain(firstMiniFatSector, miniFatSectorCount * sectorSize)\r\n const entries = miniFatData.length / 4\r\n miniFatTable = new Uint32Array(entries)\r\n for (let i = 0; i < entries; i++) {\r\n miniFatTable[i] = miniFatData.readUInt32LE(i * 4)\r\n }\r\n return miniFatTable\r\n }\r\n\r\n // ── 디렉토리 엔트리 파싱 ──\r\n\r\n const dirData = readChain(firstDirSector, MAX_DIR_ENTRIES * 128)\r\n const dirEntries: DirEntry[] = []\r\n\r\n for (let offset = 0; offset + 128 <= dirData.length && dirEntries.length < MAX_DIR_ENTRIES; offset += 128) {\r\n const nameLen = dirData.readUInt16LE(offset + 64) // 바이트 수 (null 포함)\r\n if (nameLen <= 0 || nameLen > 64) {\r\n dirEntries.push({ name: \"\", type: 0, startSector: 0, size: 0 })\r\n continue\r\n }\r\n\r\n const nameBytes = nameLen - 2 // null terminator 제외\r\n const name = nameBytes > 0\r\n ? dirData.subarray(offset, offset + nameBytes).toString(\"utf16le\")\r\n : \"\"\r\n\r\n const type = dirData[offset + 66]\r\n const startSector = dirData.readUInt32LE(offset + 116)\r\n // CFBv3에서는 size가 u32 (offset 120), v4에서는 u64\r\n const size = dirData.readUInt32LE(offset + 120)\r\n\r\n dirEntries.push({ name, type, startSector, size })\r\n }\r\n\r\n // ── Root 엔트리에서 미니 스트림 추출 ──\r\n\r\n let miniStreamData: Buffer | null = null\r\n\r\n function getMiniStream(): Buffer {\r\n if (miniStreamData) return miniStreamData\r\n const root = dirEntries[0]\r\n if (!root || root.type !== 5) {\r\n miniStreamData = Buffer.alloc(0)\r\n return miniStreamData\r\n }\r\n miniStreamData = readChain(root.startSector, root.size || MAX_STREAM_SIZE)\r\n return miniStreamData\r\n }\r\n\r\n // ── 미니 스트림에서 읽기 ──\r\n\r\n function readMiniStream(startSector: number, size: number): Buffer {\r\n const mft = getMiniFatTable()\r\n const ms = getMiniStream()\r\n if (mft.length === 0 || ms.length === 0) return Buffer.alloc(0)\r\n\r\n const chunks: Buffer[] = []\r\n let current = startSector\r\n let totalRead = 0\r\n const visited = new Set<number>()\r\n\r\n while (current !== END_OF_CHAIN && current !== FREE_SECT && totalRead < size) {\r\n if (visited.has(current)) break\r\n if (visited.size > MAX_CHAIN_LENGTH) break\r\n visited.add(current)\r\n\r\n const off = current * miniSectorSize\r\n const remaining = size - totalRead\r\n const chunkSize = Math.min(miniSectorSize, remaining)\r\n if (off + chunkSize <= ms.length) {\r\n chunks.push(ms.subarray(off, off + chunkSize))\r\n }\r\n totalRead += chunkSize\r\n\r\n current = current < mft.length ? mft[current] : END_OF_CHAIN\r\n }\r\n\r\n return Buffer.concat(chunks)\r\n }\r\n\r\n // ── 스트림 읽기 (일반/미니 자동 분기) ──\r\n\r\n function readStreamData(entry: DirEntry): Buffer {\r\n if (entry.size === 0) return Buffer.alloc(0)\r\n if (entry.size < miniStreamCutoff) {\r\n const miniResult = readMiniStream(entry.startSector, entry.size)\r\n // 미니스트림이 비어있으면 일반 체인으로 폴백 (lenient)\r\n if (miniResult.length > 0) return miniResult\r\n }\r\n return readChain(entry.startSector, entry.size)\r\n }\r\n\r\n // ── 경로 기반 탐색 ──\r\n\r\n // 전체 경로 맵 구축 (간이: 이름 기반 flat lookup)\r\n // HWP 파일의 디렉토리 구조는 보통 1~2 depth이므로 이름 매칭으로 충분\r\n function findEntryByPath(path: string): DirEntry | null {\r\n // \"/FileHeader\" → \"FileHeader\"\r\n // \"/BodyText/Section0\" → path component matching\r\n const parts = path.replace(/^\\//, \"\").split(\"/\")\r\n\r\n if (parts.length === 1) {\r\n // 단일 이름 매칭\r\n return dirEntries.find(e => e.name === parts[0] && e.type === 2) ?? null\r\n }\r\n\r\n // 2-depth: storage/stream\r\n // HWP 구조: Root/BodyText/Section0, Root/DocInfo, Root/BinData/BIN0001 등\r\n const storageName = parts[0]\r\n const streamName = parts.slice(1).join(\"/\")\r\n\r\n // 디렉토리 구조 대신 이름 패턴으로 찾기 (lenient)\r\n for (const e of dirEntries) {\r\n if (e.type === 2 && e.name === streamName) {\r\n // 부모 확인은 생략 (lenient) — 중복 이름 시 첫 번째 반환\r\n return e\r\n }\r\n }\r\n\r\n // 정확한 이름이 아닌 경우 (ViewText/Section0 등)\r\n const lastPart = parts[parts.length - 1]\r\n return dirEntries.find(e => e.type === 2 && e.name === lastPart) ?? null\r\n }\r\n\r\n // ── 공개 API ──\r\n\r\n return {\r\n findStream(path: string): Buffer | null {\r\n // \\005 prefix 처리 (SummaryInformation)\r\n const normalized = path.replace(/^\\//, \"\")\r\n const entry = findEntryByPath(normalized)\r\n if (!entry || entry.type !== 2) return null\r\n const stream = readStreamData(entry)\r\n return stream.length > 0 ? stream : null\r\n },\r\n\r\n entries(): DirEntry[] {\r\n return dirEntries.filter(e => e.type === 2) // stream만\r\n },\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
detectOle2Format,
|
|
5
5
|
detectZipFormat,
|
|
6
6
|
parseLenientCfb
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-CFQLK3LM.js";
|
|
8
8
|
import {
|
|
9
9
|
HEADING_RATIO_H1,
|
|
10
10
|
HEADING_RATIO_H2,
|
|
@@ -23,10 +23,10 @@ import {
|
|
|
23
23
|
sanitizeHref,
|
|
24
24
|
stripDtd,
|
|
25
25
|
toArrayBuffer
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-FPFB7AFX.js";
|
|
27
27
|
import {
|
|
28
28
|
parsePageRange
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-3TBUDJDE.js";
|
|
30
30
|
|
|
31
31
|
// src/index.ts
|
|
32
32
|
import { readFile } from "fs/promises";
|
|
@@ -16689,7 +16689,7 @@ async function parseXlsxDocument(buffer, options) {
|
|
|
16689
16689
|
}
|
|
16690
16690
|
let pageFilter = null;
|
|
16691
16691
|
if (options?.pages) {
|
|
16692
|
-
const { parsePageRange: parsePageRange2 } = await import("./page-range-
|
|
16692
|
+
const { parsePageRange: parsePageRange2 } = await import("./page-range-OF5I4PQY.js");
|
|
16693
16693
|
pageFilter = parsePageRange2(options.pages, sheets.length);
|
|
16694
16694
|
}
|
|
16695
16695
|
const blocks = [];
|
|
@@ -17327,7 +17327,7 @@ async function parseXlsDocument(buffer, options) {
|
|
|
17327
17327
|
const totalSheets = Math.min(globals.sheets.length, MAX_SHEETS2);
|
|
17328
17328
|
let pageFilter = null;
|
|
17329
17329
|
if (options?.pages) {
|
|
17330
|
-
const { parsePageRange: parsePageRange2 } = await import("./page-range-
|
|
17330
|
+
const { parsePageRange: parsePageRange2 } = await import("./page-range-OF5I4PQY.js");
|
|
17331
17331
|
pageFilter = parsePageRange2(options.pages, totalSheets);
|
|
17332
17332
|
}
|
|
17333
17333
|
const allBlocks = [];
|
|
@@ -17750,20 +17750,32 @@ function isDisplayMath(el) {
|
|
|
17750
17750
|
|
|
17751
17751
|
// src/docx/parser.ts
|
|
17752
17752
|
var MAX_DECOMPRESS_SIZE4 = 100 * 1024 * 1024;
|
|
17753
|
-
function
|
|
17753
|
+
function matchesLocal(el, localName2) {
|
|
17754
|
+
return el.localName === localName2 || (el.tagName?.endsWith(`:${localName2}`) ?? false);
|
|
17755
|
+
}
|
|
17756
|
+
function effectiveChildElements(parent) {
|
|
17754
17757
|
const result = [];
|
|
17755
17758
|
const children = parent.childNodes;
|
|
17756
17759
|
for (let i = 0; i < children.length; i++) {
|
|
17757
17760
|
const node = children[i];
|
|
17758
|
-
if (node.nodeType
|
|
17759
|
-
|
|
17760
|
-
|
|
17761
|
-
|
|
17761
|
+
if (node.nodeType !== 1) continue;
|
|
17762
|
+
const el = node;
|
|
17763
|
+
if (matchesLocal(el, "sdt")) {
|
|
17764
|
+
for (let j = 0; j < el.childNodes.length; j++) {
|
|
17765
|
+
const c = el.childNodes[j];
|
|
17766
|
+
if (c.nodeType === 1 && matchesLocal(c, "sdtContent")) {
|
|
17767
|
+
result.push(...effectiveChildElements(c));
|
|
17768
|
+
}
|
|
17762
17769
|
}
|
|
17770
|
+
} else {
|
|
17771
|
+
result.push(el);
|
|
17763
17772
|
}
|
|
17764
17773
|
}
|
|
17765
17774
|
return result;
|
|
17766
17775
|
}
|
|
17776
|
+
function getChildElements(parent, localName2) {
|
|
17777
|
+
return effectiveChildElements(parent).filter((el) => matchesLocal(el, localName2));
|
|
17778
|
+
}
|
|
17767
17779
|
function findElements(parent, localName2) {
|
|
17768
17780
|
const result = [];
|
|
17769
17781
|
const walk = (node) => {
|
|
@@ -18154,11 +18166,8 @@ async function parseDocxDocument(buffer, options) {
|
|
|
18154
18166
|
}
|
|
18155
18167
|
const blocks = [];
|
|
18156
18168
|
const bodyEl = body[0];
|
|
18157
|
-
const
|
|
18158
|
-
for (
|
|
18159
|
-
const node = children[i];
|
|
18160
|
-
if (node.nodeType !== 1) continue;
|
|
18161
|
-
const el = node;
|
|
18169
|
+
const topLevel = effectiveChildElements(bodyEl);
|
|
18170
|
+
for (const el of topLevel) {
|
|
18162
18171
|
const localName2 = el.localName ?? el.tagName?.split(":").pop();
|
|
18163
18172
|
if (localName2 === "p") {
|
|
18164
18173
|
const block = parseParagraph2(el, styles, numbering, footnotes, rels);
|
|
@@ -18664,8 +18673,9 @@ function extractFromTable(table) {
|
|
|
18664
18673
|
if (table.cols >= 2) {
|
|
18665
18674
|
for (let r = 0; r < table.rows; r++) {
|
|
18666
18675
|
for (let c = 0; c < table.cols - 1; c++) {
|
|
18667
|
-
const labelCell = table.cells[r][c];
|
|
18668
|
-
const valueCell = table.cells[r][c + 1];
|
|
18676
|
+
const labelCell = table.cells[r]?.[c];
|
|
18677
|
+
const valueCell = table.cells[r]?.[c + 1];
|
|
18678
|
+
if (!labelCell || !valueCell) continue;
|
|
18669
18679
|
if (isLabelCell(labelCell.text)) {
|
|
18670
18680
|
fields.push({
|
|
18671
18681
|
label: labelCell.text.trim().replace(/[::]\s*$/, ""),
|
|
@@ -18686,8 +18696,8 @@ function extractFromTable(table) {
|
|
|
18686
18696
|
if (allLabels) {
|
|
18687
18697
|
for (let r = 1; r < table.rows; r++) {
|
|
18688
18698
|
for (let c = 0; c < table.cols; c++) {
|
|
18689
|
-
const label = headerRow[c]
|
|
18690
|
-
const value = table.cells[r][c]
|
|
18699
|
+
const label = headerRow[c]?.text.trim() ?? "";
|
|
18700
|
+
const value = table.cells[r]?.[c]?.text.trim() ?? "";
|
|
18691
18701
|
if (label && value) {
|
|
18692
18702
|
fields.push({ label, value, row: r, col: c });
|
|
18693
18703
|
}
|
|
@@ -18841,15 +18851,18 @@ function fillTable(table, values, filled, matchedLabels, patternFilledCells) {
|
|
|
18841
18851
|
if (!allLabels) return;
|
|
18842
18852
|
for (let r = 1; r < table.rows; r++) {
|
|
18843
18853
|
for (let c = 0; c < table.cols; c++) {
|
|
18844
|
-
const
|
|
18854
|
+
const headerCell = headerRow[c];
|
|
18855
|
+
const valueCell = table.cells[r]?.[c];
|
|
18856
|
+
if (!headerCell || !valueCell) continue;
|
|
18857
|
+
const headerLabel = normalizeLabel(headerCell.text);
|
|
18845
18858
|
const matchKey = findMatchingKey(headerLabel, values);
|
|
18846
18859
|
if (matchKey === void 0) continue;
|
|
18847
18860
|
if (matchedLabels.has(matchKey)) continue;
|
|
18848
18861
|
const newValue = values.get(matchKey);
|
|
18849
|
-
|
|
18862
|
+
valueCell.text = newValue;
|
|
18850
18863
|
matchedLabels.add(matchKey);
|
|
18851
18864
|
filled.push({
|
|
18852
|
-
label:
|
|
18865
|
+
label: headerCell.text.trim(),
|
|
18853
18866
|
value: newValue,
|
|
18854
18867
|
row: r,
|
|
18855
18868
|
col: c
|
|
@@ -22029,14 +22042,7 @@ async function patchHwp(original, editedMarkdown, options) {
|
|
|
22029
22042
|
const editedUnits = splitMarkdownUnits(editedMarkdown);
|
|
22030
22043
|
const pairs = alignUnits(origUnits.map((u) => u.raw), editedUnits.map((u) => u.raw));
|
|
22031
22044
|
const paraMap = resolveParaMappings(origBlocks, scans);
|
|
22032
|
-
const
|
|
22033
|
-
const obTableOrdinals = /* @__PURE__ */ new Map();
|
|
22034
|
-
{
|
|
22035
|
-
let ordinal = 0;
|
|
22036
|
-
for (let i = 0; i < origBlocks.length; i++) {
|
|
22037
|
-
if (origBlocks[i].type === "table" && origBlocks[i].table) obTableOrdinals.set(i, ordinal++);
|
|
22038
|
-
}
|
|
22039
|
-
}
|
|
22045
|
+
const tableMap = resolveTableMappings(origBlocks, scans.flatMap((s) => s.tables));
|
|
22040
22046
|
for (const [oi, ei] of pairs) {
|
|
22041
22047
|
if (oi !== null && ei !== null) {
|
|
22042
22048
|
const orig = origUnits[oi];
|
|
@@ -22046,8 +22052,7 @@ async function patchHwp(original, editedMarkdown, options) {
|
|
|
22046
22052
|
origBlocks,
|
|
22047
22053
|
paraMap,
|
|
22048
22054
|
scans,
|
|
22049
|
-
|
|
22050
|
-
obTableOrdinals,
|
|
22055
|
+
tableMap,
|
|
22051
22056
|
skipped
|
|
22052
22057
|
});
|
|
22053
22058
|
} else if (oi !== null) {
|
|
@@ -22132,6 +22137,46 @@ function resolveParaMappings(blocks, scans) {
|
|
|
22132
22137
|
}
|
|
22133
22138
|
return result;
|
|
22134
22139
|
}
|
|
22140
|
+
function resolveTableMappings(blocks, scanTables) {
|
|
22141
|
+
const result = /* @__PURE__ */ new Map();
|
|
22142
|
+
let si = 0;
|
|
22143
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
22144
|
+
const table = blocks[i].table;
|
|
22145
|
+
if (blocks[i].type !== "table" || !table) continue;
|
|
22146
|
+
const cands = [];
|
|
22147
|
+
for (let k = si; k < scanTables.length; k++) {
|
|
22148
|
+
if (scanTables[k].rows === table.rows && scanTables[k].cols === table.cols) cands.push(k);
|
|
22149
|
+
}
|
|
22150
|
+
if (cands.length === 0) continue;
|
|
22151
|
+
let pick = cands[0];
|
|
22152
|
+
if (cands.length > 1) {
|
|
22153
|
+
let best = tableContentScore(table, scanTables[cands[0]]);
|
|
22154
|
+
for (let ci = 1; ci < cands.length; ci++) {
|
|
22155
|
+
const sc = tableContentScore(table, scanTables[cands[ci]]);
|
|
22156
|
+
if (sc > best) {
|
|
22157
|
+
best = sc;
|
|
22158
|
+
pick = cands[ci];
|
|
22159
|
+
}
|
|
22160
|
+
}
|
|
22161
|
+
}
|
|
22162
|
+
result.set(i, scanTables[pick]);
|
|
22163
|
+
si = pick + 1;
|
|
22164
|
+
}
|
|
22165
|
+
return result;
|
|
22166
|
+
}
|
|
22167
|
+
function tableContentScore(irTable, scanTable) {
|
|
22168
|
+
let matched = 0;
|
|
22169
|
+
for (const [key, scanCell] of scanTable.cells) {
|
|
22170
|
+
const comma = key.indexOf(",");
|
|
22171
|
+
const r = Number(key.slice(0, comma)), c = Number(key.slice(comma + 1));
|
|
22172
|
+
const irCell = irTable.cells[r]?.[c];
|
|
22173
|
+
if (!irCell) continue;
|
|
22174
|
+
const a = normForMatch(scanCell.paras.map((p) => p.rawText).join(" "));
|
|
22175
|
+
const b = normForMatch(stripCellTokens(irCell.text));
|
|
22176
|
+
if (a && a === b) matched++;
|
|
22177
|
+
}
|
|
22178
|
+
return matched;
|
|
22179
|
+
}
|
|
22135
22180
|
function handleModified(orig, edited, ctx) {
|
|
22136
22181
|
const block = ctx.origBlocks[orig.blockIdx];
|
|
22137
22182
|
const skip = (reason) => {
|
|
@@ -22144,12 +22189,10 @@ function handleModified(orig, edited, ctx) {
|
|
|
22144
22189
|
if (orig.fragment) return skip("\uBB38\uB2E8 \uBD84\uC808(\uAC15\uC81C \uC904\uBC14\uAFC8/\uBCD1\uD569 \uC720\uB2DB) \u2014 \uBD80\uBD84 \uC218\uC815\uC740 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
22145
22190
|
if (block.type === "table" && block.table) {
|
|
22146
22191
|
if (orig.kind !== edited.kind) return skip("\uD45C \u2194 \uBE44\uD45C \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0 (\uD45C \uAD6C\uC870 \uBCC0\uACBD)");
|
|
22147
|
-
|
|
22148
|
-
|
|
22149
|
-
const scanTable = ordinal !== void 0 ? ctx.scanTables[ordinal] : void 0;
|
|
22150
|
-
if (!scanTable) return skip("\uD45C \uC18C\uC2A4\uB9F5 \uB9E4\uD551 \uC2E4\uD328");
|
|
22192
|
+
const scanTable = ctx.tableMap.get(orig.blockIdx);
|
|
22193
|
+
if (!scanTable) return skip("\uD45C \uC18C\uC2A4\uB9F5 \uB9E4\uD551 \uC2E4\uD328 \u2014 \uD45C \uAC1C\uC218/\uAD6C\uC870 \uBD88\uC77C\uCE58\uB85C \uC2E0\uB8B0 \uBD88\uAC00");
|
|
22151
22194
|
if (orig.kind === "gfm-table") return patchGfmCells(scanTable, orig, edited, ctx, skip);
|
|
22152
|
-
if (orig.kind === "html-table") return
|
|
22195
|
+
if (orig.kind === "html-table") return patchHtmlCells5(block.table, scanTable, orig, edited, ctx, skip);
|
|
22153
22196
|
return patchTextChunk5(block.table, scanTable, orig, edited, ctx, skip);
|
|
22154
22197
|
}
|
|
22155
22198
|
if ((block.type === "paragraph" || block.type === "heading") && orig.kind === "text" && edited.kind === "text") {
|
|
@@ -22206,35 +22249,87 @@ function patchGfmCells(scanTable, orig, edited, ctx, skip) {
|
|
|
22206
22249
|
ctx.skipped.push({ reason, before: summarize(origRows[r][c]), after: summarize(editedRows[r][c]) });
|
|
22207
22250
|
return 0;
|
|
22208
22251
|
};
|
|
22209
|
-
const before = gfmCellToPlain(origRows[r][c]);
|
|
22210
|
-
const after = gfmCellToPlain(editedRows[r][c]);
|
|
22211
|
-
if (before === null || after === null) {
|
|
22212
|
-
cellSkip("\uC11C\uC2DD/\uB9C1\uD06C/\uC774\uBBF8\uC9C0 \uD3EC\uD568 \uC140 \uC218\uC815\uC740 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
22213
|
-
continue;
|
|
22214
|
-
}
|
|
22215
|
-
if (after.includes("\n")) {
|
|
22216
|
-
cellSkip("\uC140 \uB0B4 \uC904\uBC14\uAFC8 \uCD94\uAC00\uB294 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
22217
|
-
continue;
|
|
22218
|
-
}
|
|
22219
22252
|
const cell = scanTable.cells.get(`${r},${c}`);
|
|
22220
22253
|
if (!cell) {
|
|
22221
22254
|
cellSkip("\uBCD1\uD569 \uC601\uC5ED \uC140 \u2014 \uC575\uCEE4 \uC140\uC774 \uC544\uB2C8\uBBC0\uB85C \uBBF8\uC9C0\uC6D0");
|
|
22222
22255
|
continue;
|
|
22223
22256
|
}
|
|
22224
|
-
|
|
22225
|
-
|
|
22257
|
+
const beforeParts = origRows[r][c].split(/<br\s*\/?>/i);
|
|
22258
|
+
const afterParts = editedRows[r][c].split(/<br\s*\/?>/i);
|
|
22259
|
+
if (beforeParts.length !== cell.paras.length || afterParts.length !== cell.paras.length) {
|
|
22260
|
+
cellSkip("\uC140 \uBB38\uB2E8 \uC218 \uBCC0\uACBD \u2014 \uBBF8\uC9C0\uC6D0 (\uBB38\uB2E8 \uCD94\uAC00/\uC0AD\uC81C)");
|
|
22226
22261
|
continue;
|
|
22227
22262
|
}
|
|
22228
|
-
|
|
22229
|
-
|
|
22230
|
-
|
|
22231
|
-
|
|
22263
|
+
for (let k = 0; k < cell.paras.length; k++) {
|
|
22264
|
+
const before = gfmCellToPlain(beforeParts[k]);
|
|
22265
|
+
const after = gfmCellToPlain(afterParts[k]);
|
|
22266
|
+
if (before === null || after === null) {
|
|
22267
|
+
cellSkip("\uC11C\uC2DD/\uB9C1\uD06C/\uC774\uBBF8\uC9C0 \uD3EC\uD568 \uC140 \uC218\uC815\uC740 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
22268
|
+
break;
|
|
22269
|
+
}
|
|
22270
|
+
if (before === after) continue;
|
|
22271
|
+
if (after.includes("\n")) {
|
|
22272
|
+
cellSkip("\uC140 \uB0B4 \uC904\uBC14\uAFC8 \uCD94\uAC00\uB294 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
22273
|
+
break;
|
|
22274
|
+
}
|
|
22275
|
+
const para = cell.paras[k];
|
|
22276
|
+
if (normForMatch(para.rawText) !== normForMatch(before)) {
|
|
22277
|
+
cellSkip("\uC140 \uD14D\uC2A4\uD2B8 \uBD88\uC77C\uCE58 \u2014 \uC18C\uC2A4\uB9F5 \uC2E0\uB8B0 \uBD88\uAC00");
|
|
22278
|
+
break;
|
|
22279
|
+
}
|
|
22280
|
+
if (sanitizeText(after) !== after) {
|
|
22281
|
+
cellSkip("\uACF5\uBC31 \uC815\uADDC\uD654 \uBD88\uC548\uC815 \uD14D\uC2A4\uD2B8 \u2014 \uBBF8\uC9C0\uC6D0");
|
|
22282
|
+
break;
|
|
22283
|
+
}
|
|
22284
|
+
applied += stageParaPatch(ctx.scans[para.sectionIndex], para, after, cellSkip);
|
|
22232
22285
|
}
|
|
22233
|
-
|
|
22234
|
-
|
|
22286
|
+
}
|
|
22287
|
+
}
|
|
22288
|
+
return applied;
|
|
22289
|
+
}
|
|
22290
|
+
function extractImgTags5(inner) {
|
|
22291
|
+
return (inner.match(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi) || []).join(" ");
|
|
22292
|
+
}
|
|
22293
|
+
function patchHtmlCells5(table, scanTable, orig, edited, ctx, skip) {
|
|
22294
|
+
if (replicateTableToHtml(table) !== orig.raw) return skip("\uD45C \uC88C\uD45C \uC7AC\uD604 \uBD88\uC77C\uCE58 \u2014 \uB9E4\uD551 \uC2E0\uB8B0 \uBD88\uAC00");
|
|
22295
|
+
const replica = replicateHtmlTable(table);
|
|
22296
|
+
const origRows = parseHtmlTable(orig.raw);
|
|
22297
|
+
if (!origRows || origRows.length !== replica.length || origRows.some((r, i) => r.cells.length !== replica[i].cells.length || r.cells.some((c, j) => c.inner !== replica[i].cells[j].inner))) {
|
|
22298
|
+
return skip("\uC140 \uACBD\uACC4 \uBAA8\uD638 (\uB9AC\uD130\uB7F4 \uD0DC\uADF8 \uC758\uC2EC) \u2014 \uB9E4\uD551 \uC2E0\uB8B0 \uBD88\uAC00");
|
|
22299
|
+
}
|
|
22300
|
+
const editedRows = parseHtmlTable(edited.raw);
|
|
22301
|
+
if (!editedRows) return skip("\uD3B8\uC9D1\uB41C HTML \uD45C \uD30C\uC2F1 \uC2E4\uD328");
|
|
22302
|
+
if (editedRows.length !== replica.length) return skip("\uD45C \uD589 \uCD94\uAC00/\uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 (\uD45C \uAD6C\uC870 \uBCC0\uACBD)");
|
|
22303
|
+
let applied = 0;
|
|
22304
|
+
for (let r = 0; r < replica.length; r++) {
|
|
22305
|
+
if (editedRows[r].cells.length !== replica[r].cells.length) {
|
|
22306
|
+
skip(`\uD45C ${r + 1}\uD589 \uC140 \uC218 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
|
|
22307
|
+
continue;
|
|
22308
|
+
}
|
|
22309
|
+
for (let c = 0; c < replica[r].cells.length; c++) {
|
|
22310
|
+
const oc = replica[r].cells[c];
|
|
22311
|
+
const ec = editedRows[r].cells[c];
|
|
22312
|
+
if (oc.colSpan !== ec.colSpan || oc.rowSpan !== ec.rowSpan) {
|
|
22313
|
+
skip("\uC140 \uBCD1\uD569(colspan/rowspan) \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
|
|
22235
22314
|
continue;
|
|
22236
22315
|
}
|
|
22237
|
-
|
|
22316
|
+
if (oc.inner === ec.inner) continue;
|
|
22317
|
+
const origContent = htmlCellInnerToLines(oc.inner);
|
|
22318
|
+
const editedContent = htmlCellInnerToLines(ec.inner);
|
|
22319
|
+
if (origContent.hadNonText || editedContent.hadNonText) {
|
|
22320
|
+
if (extractImgTags5(oc.inner) !== extractImgTags5(ec.inner)) {
|
|
22321
|
+
skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
|
|
22322
|
+
continue;
|
|
22323
|
+
}
|
|
22324
|
+
if (extractTopLevelTables(oc.inner).join("\n") !== extractTopLevelTables(ec.inner).join("\n")) {
|
|
22325
|
+
skip("\uC140 \uB0B4 \uC911\uCCA9\uD45C \uC218\uC815\uC740 HWP5 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
22326
|
+
continue;
|
|
22327
|
+
}
|
|
22328
|
+
}
|
|
22329
|
+
if (origContent.lines.join("\n") !== editedContent.lines.join("\n")) {
|
|
22330
|
+
const newLines = editedContent.lines.map((l) => unescapeGfm(l));
|
|
22331
|
+
applied += applyCellEdit5(table, scanTable, oc.gridR, oc.gridC, newLines, ctx, oc.inner, ec.inner, origContent.lines.length);
|
|
22332
|
+
}
|
|
22238
22333
|
}
|
|
22239
22334
|
}
|
|
22240
22335
|
return applied;
|
|
@@ -22326,15 +22421,103 @@ function gfmCellToPlain(md2) {
|
|
|
22326
22421
|
let t = md2.trim();
|
|
22327
22422
|
const bold = t.match(/^\*\*([\s\S]+)\*\*$/);
|
|
22328
22423
|
if (bold) t = bold[1];
|
|
22329
|
-
if (/[*`]|!\[|\]\(
|
|
22424
|
+
if (/[*`]|!\[|\]\(/.test(t)) return null;
|
|
22425
|
+
if (/<(?!br\s*\/?>)[a-zA-Z/]/i.test(t)) return null;
|
|
22330
22426
|
return unescapeGfm(unescapeGfmCell(t));
|
|
22331
22427
|
}
|
|
22332
|
-
|
|
22428
|
+
function splitParaText(data) {
|
|
22429
|
+
const toks = [];
|
|
22430
|
+
let i = 0;
|
|
22431
|
+
while (i + 1 < data.length) {
|
|
22432
|
+
const ch = data.readUInt16LE(i);
|
|
22433
|
+
const start = i;
|
|
22434
|
+
i += 2;
|
|
22435
|
+
if (ch >= 32) {
|
|
22436
|
+
let units = 1;
|
|
22437
|
+
if (ch >= 55296 && ch <= 56319 && i + 1 < data.length) {
|
|
22438
|
+
const lo = data.readUInt16LE(i);
|
|
22439
|
+
if (lo >= 56320 && lo <= 57343) {
|
|
22440
|
+
i += 2;
|
|
22441
|
+
units = 2;
|
|
22442
|
+
}
|
|
22443
|
+
}
|
|
22444
|
+
toks.push({ start, end: i, units, plain: true, visible: true });
|
|
22445
|
+
continue;
|
|
22446
|
+
}
|
|
22447
|
+
switch (ch) {
|
|
22448
|
+
case 0:
|
|
22449
|
+
case 24:
|
|
22450
|
+
case 25:
|
|
22451
|
+
case 30:
|
|
22452
|
+
case 31:
|
|
22453
|
+
toks.push({ start, end: i, units: 1, plain: false, visible: true });
|
|
22454
|
+
break;
|
|
22455
|
+
case 9:
|
|
22456
|
+
if (i + 14 <= data.length) i += 14;
|
|
22457
|
+
toks.push({ start, end: i, units: 1, plain: false, visible: true });
|
|
22458
|
+
break;
|
|
22459
|
+
case 13:
|
|
22460
|
+
toks.push({ start, end: i, units: 1, plain: false, visible: false });
|
|
22461
|
+
break;
|
|
22462
|
+
case 10:
|
|
22463
|
+
if (i + 16 <= data.length && data.readUInt16LE(i) === 11) {
|
|
22464
|
+
i += 16;
|
|
22465
|
+
toks.push({ start, end: i, units: 1, plain: false, visible: false });
|
|
22466
|
+
} else {
|
|
22467
|
+
if (i + 14 <= data.length) i += 14;
|
|
22468
|
+
toks.push({ start, end: i, units: 1, plain: false, visible: true });
|
|
22469
|
+
}
|
|
22470
|
+
break;
|
|
22471
|
+
default: {
|
|
22472
|
+
const ext = isExtendedOnlyCtrlChar(ch);
|
|
22473
|
+
const inl = ch >= 4 && ch <= 9 || ch >= 19 && ch <= 20;
|
|
22474
|
+
if ((ext || inl) && i + 14 <= data.length) i += 14;
|
|
22475
|
+
toks.push({ start, end: i, units: 1, plain: false, visible: false });
|
|
22476
|
+
break;
|
|
22477
|
+
}
|
|
22478
|
+
}
|
|
22479
|
+
}
|
|
22480
|
+
if (i !== data.length) return null;
|
|
22481
|
+
let firstP = -1, lastP = -1;
|
|
22482
|
+
for (let k = 0; k < toks.length; k++) if (toks[k].plain) {
|
|
22483
|
+
if (firstP < 0) firstP = k;
|
|
22484
|
+
lastP = k;
|
|
22485
|
+
}
|
|
22486
|
+
if (firstP < 0) return null;
|
|
22487
|
+
for (let k = firstP; k <= lastP; k++) if (!toks[k].plain) return null;
|
|
22488
|
+
for (let k = 0; k < firstP; k++) if (toks[k].visible) return null;
|
|
22489
|
+
for (let k = lastP + 1; k < toks.length; k++) if (toks[k].visible) return null;
|
|
22490
|
+
const prefixEnd = toks[firstP].start;
|
|
22491
|
+
const coreEnd = toks[lastP].end;
|
|
22492
|
+
const prefixUnits = prefixEnd / 2;
|
|
22493
|
+
const suffixUnits = (data.length - coreEnd) / 2;
|
|
22494
|
+
return {
|
|
22495
|
+
prefix: data.subarray(0, prefixEnd),
|
|
22496
|
+
prefixUnits,
|
|
22497
|
+
core: data.subarray(prefixEnd, coreEnd).toString("utf16le"),
|
|
22498
|
+
suffix: data.subarray(coreEnd),
|
|
22499
|
+
suffixUnits
|
|
22500
|
+
};
|
|
22501
|
+
}
|
|
22502
|
+
function rebuildCharShape(csData, coreStartUnit) {
|
|
22503
|
+
const pairs = [];
|
|
22504
|
+
for (let o = 0; o + 8 <= csData.length; o += 8) pairs.push([csData.readUInt32LE(o), csData.readUInt32LE(o + 4)]);
|
|
22505
|
+
if (pairs.length === 0) return { buf: Buffer.from(csData.subarray(0, 8)), count: 1 };
|
|
22506
|
+
let coreId = pairs[0][1];
|
|
22507
|
+
for (const [p, id] of pairs) if (p <= coreStartUnit) coreId = id;
|
|
22508
|
+
const kept = pairs.filter(([p]) => p < coreStartUnit);
|
|
22509
|
+
if (kept.length === 0 || kept[kept.length - 1][1] !== coreId) kept.push([coreStartUnit, coreId]);
|
|
22510
|
+
const buf = Buffer.alloc(kept.length * 8);
|
|
22511
|
+
kept.forEach(([p, id], k) => {
|
|
22512
|
+
buf.writeUInt32LE(p >>> 0, k * 8);
|
|
22513
|
+
buf.writeUInt32LE(id >>> 0, k * 8 + 4);
|
|
22514
|
+
});
|
|
22515
|
+
return { buf, count: kept.length };
|
|
22516
|
+
}
|
|
22333
22517
|
function stageParaPatch(scan, para, newPlain, skip) {
|
|
22334
22518
|
if (!scan.safe) return skip("\uC139\uC158 \uB808\uCF54\uB4DC \uC7AC\uC9C1\uB82C\uD654 \uBD88\uC77C\uCE58 \u2014 \uC548\uC804\uC744 \uC704\uD574 \uC774 \uC139\uC158\uC740 \uBBF8\uC9C0\uC6D0");
|
|
22335
22519
|
if (para.textIdx === -1) return skip("\uBE48 \uBB38\uB2E8 \uD14D\uC2A4\uD2B8 \uCD94\uAC00\uB294 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
22336
22520
|
if (para.textIdx === -2) return skip("\uBCF5\uC218 PARA_TEXT \uB808\uCF54\uB4DC \uBB38\uB2E8 \u2014 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
22337
|
-
if (para.ctrlMask !== 0) return skip("\uCEE8\uD2B8\uB864 \uBB38\uC790 \uD3EC\uD568 \uBB38\uB2E8(\uD0ED/\uAC1C\uCCB4/\uD544\uB4DC/\uD2B9\uC218\uACF5\uBC31) \u2014 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
22338
22521
|
if (para.rangeTagCount > 0) return skip("\uBC94\uC704 \uD0DC\uADF8(\uD615\uAD11\uD39C/\uAD50\uC815\uBD80\uD638) \uBB38\uB2E8 \u2014 \uBBF8\uC9C0\uC6D0 (v1)");
|
|
22339
22522
|
if (para.charShapeIdx < 0 || para.lineSegIdx < 0) return skip("\uBB38\uB2E8 \uB808\uCF54\uB4DC \uAD6C\uC131 \uBE44\uC815\uD615 \u2014 \uBBF8\uC9C0\uC6D0");
|
|
22340
22523
|
if (scan.repl.has(para.headerIdx)) return skip("\uB3D9\uC77C \uBB38\uB2E8 \uC911\uBCF5 \uC218\uC815 \u2014 \uCCAB \uC218\uC815\uB9CC \uC801\uC6A9");
|
|
@@ -22343,36 +22526,25 @@ function stageParaPatch(scan, para, newPlain, skip) {
|
|
|
22343
22526
|
const headerRec = records[para.headerIdx];
|
|
22344
22527
|
const textRec = records[para.textIdx];
|
|
22345
22528
|
const charShapeRec = records[para.charShapeIdx];
|
|
22346
|
-
|
|
22347
|
-
|
|
22348
|
-
|
|
22529
|
+
if (charShapeRec.data.length < 8) {
|
|
22530
|
+
return skip("CHAR_SHAPE \uB808\uCF54\uB4DC \uBE44\uC815\uD615 \u2014 \uBBF8\uC9C0\uC6D0");
|
|
22531
|
+
}
|
|
22532
|
+
const seg = splitParaText(textRec.data);
|
|
22533
|
+
if (!seg) {
|
|
22534
|
+
return skip(para.ctrlMask !== 0 ? "\uCEE8\uD2B8\uB864 \uBB38\uC790(\uD0ED/\uD544\uB4DC/\uD2B9\uC218\uACF5\uBC31 \uB4F1 \uD14D\uC2A4\uD2B8 \uC911\uAC04) \uD3EC\uD568 \uBB38\uB2E8 \u2014 \uBBF8\uC9C0\uC6D0 (v1)" : "PARA_TEXT \uC7AC\uAD6C\uC131 \uBD88\uC77C\uCE58 \u2014 \uC6D0\uBB38 \uBCF4\uC874 \uBD88\uAC00\uB85C \uBBF8\uC9C0\uC6D0");
|
|
22349
22535
|
}
|
|
22350
|
-
|
|
22351
|
-
const expect = hadBreak ? Buffer.concat([Buffer.from(para.rawText, "utf16le"), PARA_BREAK]) : Buffer.from(para.rawText, "utf16le");
|
|
22352
|
-
if (!expect.equals(textRec.data)) return skip("PARA_TEXT \uC7AC\uAD6C\uC131 \uBD88\uC77C\uCE58 \u2014 \uC6D0\uBB38 \uBCF4\uC874 \uBD88\uAC00\uB85C \uBBF8\uC9C0\uC6D0");
|
|
22536
|
+
if (seg.core !== para.rawText) return skip("PARA_TEXT \uC7AC\uAD6C\uC131 \uBD88\uC77C\uCE58 \u2014 \uC6D0\uBB38 \uBCF4\uC874 \uBD88\uAC00\uB85C \uBBF8\uC9C0\uC6D0");
|
|
22353
22537
|
const lead = para.rawText.match(/^\s*/)[0];
|
|
22354
22538
|
const trail = para.rawText.match(/\s*$/)[0];
|
|
22355
22539
|
const newRaw = para.rawText.trim() === para.rawText ? newPlain : lead + newPlain + trail;
|
|
22356
|
-
const newText =
|
|
22540
|
+
const newText = Buffer.concat([seg.prefix, Buffer.from(newRaw, "utf16le"), seg.suffix]);
|
|
22357
22541
|
scan.repl.set(para.textIdx, newText);
|
|
22358
22542
|
const newHeader = Buffer.from(headerRec.data);
|
|
22359
|
-
const nChars = newRaw.length +
|
|
22543
|
+
const nChars = seg.prefixUnits + newRaw.length + seg.suffixUnits;
|
|
22360
22544
|
newHeader.writeUInt32LE((para.nCharsRaw & 2147483648 | nChars) >>> 0, 0);
|
|
22361
|
-
|
|
22362
|
-
|
|
22363
|
-
|
|
22364
|
-
}
|
|
22365
|
-
const csData = scan.repl.get(para.charShapeIdx) ?? Buffer.from(charShapeRec.data);
|
|
22366
|
-
if (csData.readUInt32LE(0) !== 0) {
|
|
22367
|
-
csData.writeUInt32LE(0, 0);
|
|
22368
|
-
scan.repl.set(para.charShapeIdx, csData);
|
|
22369
|
-
}
|
|
22370
|
-
if (lineSegRec.data.length > 36 || lineSegRec.data.readUInt32LE(0) !== 0) {
|
|
22371
|
-
const seg = Buffer.from(lineSegRec.data.subarray(0, 36));
|
|
22372
|
-
seg.writeUInt32LE(0, 0);
|
|
22373
|
-
newHeader.writeUInt16LE(1, 16);
|
|
22374
|
-
scan.repl.set(para.lineSegIdx, seg);
|
|
22375
|
-
}
|
|
22545
|
+
const cs = rebuildCharShape(charShapeRec.data, seg.prefixUnits);
|
|
22546
|
+
scan.repl.set(para.charShapeIdx, cs.buf);
|
|
22547
|
+
newHeader.writeUInt16LE(cs.count, 12);
|
|
22376
22548
|
scan.repl.set(para.headerIdx, newHeader);
|
|
22377
22549
|
return 1;
|
|
22378
22550
|
}
|
|
@@ -22961,7 +23133,7 @@ async function parseHwp(buffer, options) {
|
|
|
22961
23133
|
async function parsePdf(buffer, options) {
|
|
22962
23134
|
let parsePdfDocument;
|
|
22963
23135
|
try {
|
|
22964
|
-
const mod = await import("./parser-
|
|
23136
|
+
const mod = await import("./parser-ET64VEEX.js");
|
|
22965
23137
|
parsePdfDocument = mod.parsePdfDocument;
|
|
22966
23138
|
} catch {
|
|
22967
23139
|
return {
|
|
@@ -23086,4 +23258,4 @@ export {
|
|
|
23086
23258
|
parseHwpml,
|
|
23087
23259
|
fillForm
|
|
23088
23260
|
};
|
|
23089
|
-
//# sourceMappingURL=chunk-
|
|
23261
|
+
//# sourceMappingURL=chunk-CGHPBI3X.js.map
|