kordoc 4.0.8 → 4.2.0
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 +50 -16
- package/dist/{-LE4RLXVA.js → -SXQIZHYA.js} +24 -10
- package/dist/{parser-TCTCSBYZ.js → chunk-26AJJCFF.js} +1427 -2027
- package/dist/chunk-26AJJCFF.js.map +1 -0
- package/dist/{parser-KMMUS73Q.js → chunk-4JPPLIIV.js} +1425 -2025
- package/dist/chunk-4JPPLIIV.js.map +1 -0
- package/dist/chunk-4VNLZ2GZ.js +13 -0
- package/dist/chunk-4VNLZ2GZ.js.map +1 -0
- package/dist/{chunk-2DZQF6YJ.js → chunk-6SHO3BNV.js} +24 -15
- package/dist/chunk-6SHO3BNV.js.map +1 -0
- package/dist/chunk-7FCNOKOR.js +144 -0
- package/dist/chunk-7FCNOKOR.js.map +1 -0
- package/dist/{chunk-6XAAYAWL.cjs → chunk-CMN3SYZ3.js} +62 -304
- package/dist/chunk-CMN3SYZ3.js.map +1 -0
- package/dist/chunk-DZIXKL7E.js +145 -0
- package/dist/chunk-DZIXKL7E.js.map +1 -0
- package/dist/chunk-ELOV2GIJ.js +82 -0
- package/dist/chunk-ELOV2GIJ.js.map +1 -0
- package/dist/chunk-F2HYH62H.cjs +130 -0
- package/dist/chunk-F2HYH62H.cjs.map +1 -0
- package/dist/{chunk-HMUEU7D7.js → chunk-FSXLSHCW.js} +3 -3
- package/dist/{chunk-HMUEU7D7.js.map → chunk-FSXLSHCW.js.map} +1 -1
- package/dist/chunk-GEPINOL6.js +83 -0
- package/dist/chunk-GEPINOL6.js.map +1 -0
- package/dist/chunk-H7OWEYH2.cjs +243 -0
- package/dist/chunk-H7OWEYH2.cjs.map +1 -0
- package/dist/{chunk-7S3M4N4E.js → chunk-IGGRLWT5.js} +683 -226
- package/dist/chunk-IGGRLWT5.js.map +1 -0
- package/dist/chunk-J2RZ444H.js +243 -0
- package/dist/chunk-J2RZ444H.js.map +1 -0
- package/dist/chunk-JP5YH3Z4.js +130 -0
- package/dist/chunk-JP5YH3Z4.js.map +1 -0
- package/dist/{parser-ITMU2WN5.cjs → chunk-LHROM2BO.cjs} +1409 -2012
- package/dist/chunk-LHROM2BO.cjs.map +1 -0
- package/dist/chunk-NNDVFNMJ.js +246 -0
- package/dist/chunk-NNDVFNMJ.js.map +1 -0
- package/dist/{chunk-RROH5WHO.js → chunk-PLIY23LY.js} +2 -2
- package/dist/{chunk-BNU5QGIZ.js → chunk-V6U6YS5B.js} +12 -4
- package/dist/chunk-V6U6YS5B.js.map +1 -0
- package/dist/{chunk-UEIYETCQ.js → chunk-WF2E5P2G.js} +40 -35
- package/dist/chunk-WF2E5P2G.js.map +1 -0
- package/dist/{chunk-D35VBACN.js → chunk-WQLHPOMJ.js} +7 -4
- package/dist/chunk-WQLHPOMJ.js.map +1 -0
- package/dist/{chunk-OQPILS7B.js → chunk-XTCT6KQ5.cjs} +79 -183
- package/dist/chunk-XTCT6KQ5.cjs.map +1 -0
- package/dist/{chunk-MEPHGCPQ.js → chunk-YOO6ET6M.js} +7 -3
- package/dist/chunk-YOO6ET6M.js.map +1 -0
- package/dist/chunks-ZQEFAZNC.js +10 -0
- package/dist/cli.js +228 -36
- package/dist/cli.js.map +1 -1
- package/dist/{detect-RI2MQ33K.js → detect-YEZX2XCF.js} +2 -2
- package/dist/{formula-RXVSQPXI.js → formula-KSNAEBZ2.js} +23 -232
- package/dist/formula-KSNAEBZ2.js.map +1 -0
- package/dist/{formula-5NKVS2LR.cjs → formula-SB3NHDUM.cjs} +30 -240
- package/dist/formula-SB3NHDUM.cjs.map +1 -0
- package/dist/{formula-JCNF43NE.js → formula-SXC6LRRC.js} +23 -233
- package/dist/formula-SXC6LRRC.js.map +1 -0
- package/dist/index.cjs +1280 -589
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -4
- package/dist/index.d.ts +112 -4
- package/dist/index.js +929 -238
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +278 -68
- package/dist/mcp.js.map +1 -1
- package/dist/models-KA4GLQK5.js +25 -0
- package/dist/parser-ND2YGQZR.js +800 -0
- package/dist/parser-ND2YGQZR.js.map +1 -0
- package/dist/parser-RBZHA6SY.js +804 -0
- package/dist/parser-RBZHA6SY.js.map +1 -0
- package/dist/parser-WDZOMDG4.cjs +803 -0
- package/dist/parser-WDZOMDG4.cjs.map +1 -0
- package/dist/pdf-ocr-2Z7VZIFI.cjs +439 -0
- package/dist/pdf-ocr-2Z7VZIFI.cjs.map +1 -0
- package/dist/pdf-ocr-FJFXFADQ.js +438 -0
- package/dist/pdf-ocr-FJFXFADQ.js.map +1 -0
- package/dist/pdf-ocr-VYE4DHD4.js +386 -0
- package/dist/pdf-ocr-VYE4DHD4.js.map +1 -0
- package/dist/{profile-io-SLN4P76T.js → profile-io-TWHDIVPL.js} +3 -3
- package/dist/rasterize-W2NEZUSW.js +40 -0
- package/dist/rasterize-W2NEZUSW.js.map +1 -0
- package/dist/redact-7ILEAUTS.js +12 -0
- package/dist/redact-7ILEAUTS.js.map +1 -0
- package/dist/render-4MG5QUDG.js +10 -0
- package/dist/render-4MG5QUDG.js.map +1 -0
- package/dist/seal-D2QNXJVO.js +10 -0
- package/dist/seal-D2QNXJVO.js.map +1 -0
- package/dist/{setup-57FB3LSP.js → setup-Q2PRE7UA.js} +5 -3
- package/dist/setup-Q2PRE7UA.js.map +1 -0
- package/dist/{watch-OHQWSVPE.js → watch-VWXJZT2R.js} +78 -23
- package/dist/watch-VWXJZT2R.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-2DZQF6YJ.js.map +0 -1
- package/dist/chunk-6XAAYAWL.cjs.map +0 -1
- package/dist/chunk-7S3M4N4E.js.map +0 -1
- package/dist/chunk-BNU5QGIZ.js.map +0 -1
- package/dist/chunk-D35VBACN.js.map +0 -1
- package/dist/chunk-MEPHGCPQ.js.map +0 -1
- package/dist/chunk-OQPILS7B.js.map +0 -1
- package/dist/chunk-UEIYETCQ.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/parser-ITMU2WN5.cjs.map +0 -1
- package/dist/parser-KMMUS73Q.js.map +0 -1
- package/dist/parser-TCTCSBYZ.js.map +0 -1
- package/dist/provider-4ZJKV3DC.js +0 -37
- package/dist/provider-4ZJKV3DC.js.map +0 -1
- package/dist/provider-AKROB7WQ.js +0 -39
- package/dist/provider-AKROB7WQ.js.map +0 -1
- package/dist/provider-G4C2V2PD.cjs +0 -39
- package/dist/provider-G4C2V2PD.cjs.map +0 -1
- package/dist/render-25BT623I.js +0 -10
- package/dist/seal-R4TETA4C.js +0 -10
- package/dist/setup-57FB3LSP.js.map +0 -1
- package/dist/watch-OHQWSVPE.js.map +0 -1
- /package/dist/{-LE4RLXVA.js.map → -SXQIZHYA.js.map} +0 -0
- /package/dist/{chunk-RROH5WHO.js.map → chunk-PLIY23LY.js.map} +0 -0
- /package/dist/{detect-RI2MQ33K.js.map → chunks-ZQEFAZNC.js.map} +0 -0
- /package/dist/{profile-io-SLN4P76T.js.map → detect-YEZX2XCF.js.map} +0 -0
- /package/dist/{render-25BT623I.js.map → models-KA4GLQK5.js.map} +0 -0
- /package/dist/{seal-R4TETA4C.js.map → profile-io-TWHDIVPL.js.map} +0 -0
|
@@ -19,8 +19,11 @@ function parseLenientCfb(data) {
|
|
|
19
19
|
const miniSectorSizeShift = data.readUInt16LE(32);
|
|
20
20
|
if (miniSectorSizeShift > 16) throw new Error("\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBBF8\uB2C8 \uC139\uD130 \uD06C\uAE30 \uC2DC\uD504\uD2B8: " + miniSectorSizeShift);
|
|
21
21
|
const miniSectorSize = 1 << miniSectorSizeShift;
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
22
|
+
const headerFatSectorCount = data.readUInt32LE(44);
|
|
23
|
+
if (headerFatSectorCount > 1e4) throw new Error("FAT \uC139\uD130 \uC218\uAC00 \uB108\uBB34 \uB9CE\uC2B5\uB2C8\uB2E4: " + headerFatSectorCount);
|
|
24
|
+
const maxRealSectors = Math.ceil(Math.max(0, data.length - 512) / sectorSize);
|
|
25
|
+
const maxFatSectors = Math.max(1, Math.ceil(maxRealSectors / (sectorSize / 4)));
|
|
26
|
+
const fatSectorCount = Math.min(headerFatSectorCount, maxFatSectors);
|
|
24
27
|
const firstDirSector = data.readUInt32LE(48);
|
|
25
28
|
const miniStreamCutoff = data.readUInt32LE(56);
|
|
26
29
|
const firstMiniFatSector = data.readUInt32LE(60);
|
|
@@ -47,6 +50,7 @@ function parseLenientCfb(data) {
|
|
|
47
50
|
if (visitedDifat.has(difatSector)) break;
|
|
48
51
|
visitedDifat.add(difatSector);
|
|
49
52
|
const buf = readSectorData(difatSector);
|
|
53
|
+
if (buf.length < sectorSize) break;
|
|
50
54
|
const entriesPerSector = sectorSize / 4 - 1;
|
|
51
55
|
for (let i = 0; i < entriesPerSector && fatSectors.length < fatSectorCount; i++) {
|
|
52
56
|
const sid = buf.readUInt32LE(i * 4);
|
|
@@ -263,4 +267,4 @@ export {
|
|
|
263
267
|
detectOle2Format,
|
|
264
268
|
detectZipFormat
|
|
265
269
|
};
|
|
266
|
-
//# sourceMappingURL=chunk-
|
|
270
|
+
//# sourceMappingURL=chunk-YOO6ET6M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/detect.ts","../src/hwp5/cfb-lenient.ts"],"sourcesContent":["/** 매직 바이트 기반 파일 포맷 감지 */\n\nimport JSZip from \"jszip\"\nimport type { FileType } from \"./types.js\"\nimport { parseLenientCfb } from \"./hwp5/cfb-lenient.js\"\n\n/** 매직 바이트 뷰 생성 (복사 없이 view) */\nfunction magicBytes(buffer: ArrayBuffer): Uint8Array {\n return new Uint8Array(buffer, 0, Math.min(4, buffer.byteLength))\n}\n\n/** ZIP 파일 여부: PK\\x03\\x04 */\nexport function isZipFile(buffer: ArrayBuffer): boolean {\n const b = magicBytes(buffer)\n return b[0] === 0x50 && b[1] === 0x4b && b[2] === 0x03 && b[3] === 0x04\n}\n\n/** HWPX (ZIP 기반 한컴 문서): PK\\x03\\x04 — 하위 호환용 */\nexport function isHwpxFile(buffer: ArrayBuffer): boolean {\n return isZipFile(buffer)\n}\n\n/** HWP 5.x (OLE2 바이너리 한컴 문서): \\xD0\\xCF\\x11\\xE0 */\nexport function isOldHwpFile(buffer: ArrayBuffer): boolean {\n const b = magicBytes(buffer)\n return b[0] === 0xd0 && b[1] === 0xcf && b[2] === 0x11 && b[3] === 0xe0\n}\n\n/**\n * HWP 3.x (한글 워드프로세서 3.0): \"HWP Document File V3.00 \\x1A\\x01\\x02\\x03\\x04\\x05\" 30 byte.\n * CFB(OLE2) 컨테이너 아닌 단일 binary stream — isOldHwpFile 과 magic 이 다르다.\n */\nconst HWP3_PREFIX = new TextEncoder().encode(\"HWP Document File V3.00\")\nexport function isHwp3File(buffer: ArrayBuffer): boolean {\n if (buffer.byteLength < HWP3_PREFIX.length) return false\n const head = new Uint8Array(buffer, 0, HWP3_PREFIX.length)\n for (let i = 0; i < HWP3_PREFIX.length; i++) {\n if (head[i] !== HWP3_PREFIX[i]) return false\n }\n return true\n}\n\n/** PDF 문서: %PDF */\nexport function isPdfFile(buffer: ArrayBuffer): boolean {\n const b = magicBytes(buffer)\n return b[0] === 0x25 && b[1] === 0x50 && b[2] === 0x44 && b[3] === 0x46\n}\n\n/** HWPML (XML 기반 한컴 문서): <?xml ... <HWPML */\nexport function isHwpmlFile(buffer: ArrayBuffer): boolean {\n const bytes = new Uint8Array(buffer, 0, Math.min(512, buffer.byteLength))\n const head = new TextDecoder(\"utf-8\", { fatal: false }).decode(bytes).replace(/^\\uFEFF/, \"\")\n return head.trimStart().startsWith(\"<?xml\") && head.includes(\"<HWPML\")\n}\n\n/** 동기 포맷 감지 — ZIP은 모두 \"hwpx\"로 반환 (하위 호환) */\nexport function detectFormat(buffer: ArrayBuffer): FileType {\n if (buffer.byteLength < 4) return \"unknown\"\n if (isHwp3File(buffer)) return \"hwp3\"\n if (isZipFile(buffer)) return \"hwpx\"\n if (isOldHwpFile(buffer)) return \"hwp\"\n if (isPdfFile(buffer)) return \"pdf\"\n if (isHwpmlFile(buffer)) return \"hwpml\"\n return \"unknown\"\n}\n\n/**\n * OLE2 컨테이너 내부 스트림 기반 포맷 세분화.\n * HWP 5.x, XLS 모두 OLE2이므로 스트림 이름으로 구분.\n * - \"Workbook\" 또는 \"Book\" → 'xls'\n * - 그 외 (FileHeader 등) → 'hwp'\n */\nexport function detectOle2Format(buffer: ArrayBuffer): \"hwp\" | \"xls\" | \"unknown\" {\n try {\n const cfb = parseLenientCfb(Buffer.from(buffer))\n const names = cfb.entries().map(e => e.name)\n if (names.includes(\"Workbook\") || names.includes(\"Book\")) return \"xls\"\n if (names.includes(\"FileHeader\")) return \"hwp\"\n // FileHeader 없어도 BodyText/DocInfo 있으면 hwp\n if (names.some(n => n === \"DocInfo\" || n.startsWith(\"Section\"))) return \"hwp\"\n return \"unknown\"\n } catch {\n return \"unknown\"\n }\n}\n\n/**\n * ZIP 내부 구조 기반 포맷 세분화.\n * HWPX, XLSX, DOCX 모두 ZIP이므로 내부 파일로 구분.\n */\nexport async function detectZipFormat(buffer: ArrayBuffer): Promise<\"hwpx\" | \"xlsx\" | \"docx\" | \"unknown\"> {\n try {\n const zip = await JSZip.loadAsync(buffer)\n // XLSX: xl/workbook.xml\n if (zip.file(\"xl/workbook.xml\")) return \"xlsx\"\n // DOCX: word/document.xml\n if (zip.file(\"word/document.xml\")) return \"docx\"\n // HWPX: Contents/ 또는 content.hpf 또는 mimetype\n if (zip.file(\"Contents/content.hpf\") || zip.file(\"mimetype\")) return \"hwpx\"\n // 기타 ZIP 내에 section 파일이 있으면 HWPX로 추정\n const hasSection = Object.keys(zip.files).some(f => f.startsWith(\"Contents/\"))\n if (hasSection) return \"hwpx\"\n return \"unknown\"\n } catch {\n return \"unknown\"\n }\n}\n","/**\n * Lenient CFB (Compound File Binary / OLE2) 파서.\n *\n * 표준 cfb 모듈이 FAT 검증 실패로 거부하는 손상된 HWP 파일을 열기 위한 폴백.\n * 직접 헤더/FAT/디렉토리를 파싱하여 스트림 데이터를 추출.\n *\n * 참조: rhwp (MIT) src/parser/cfb_reader.rs (LenientCfbReader)\n * 참조: MS-CFB spec (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb)\n */\n\nimport { decompressStream } from \"./record.js\"\n\n// ── 상수 ──\n\nconst CFB_MAGIC = Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1])\nconst END_OF_CHAIN = 0xfffffffe\nconst FREE_SECT = 0xffffffff\n\n/** 순환 감지용 최대 체인 길이 */\nconst MAX_CHAIN_LENGTH = 1_000_000\n/** 최대 디렉토리 엔트리 수 */\nconst MAX_DIR_ENTRIES = 100_000\n/** 최대 스트림 크기 (100MB) */\nconst MAX_STREAM_SIZE = 100 * 1024 * 1024\n\n// ── 디렉토리 엔트리 ──\n\ninterface DirEntry {\n name: string\n type: number // 0=unknown, 1=storage, 2=stream, 5=root\n startSector: number\n size: number\n}\n\n// ── CFB 컨테이너 ──\n\nexport interface LenientCfbContainer {\n /** 이름 기반 스트림 탐색 */\n findStream(path: string): Buffer | null\n /** 디렉토리 엔트리 목록 */\n entries(): DirEntry[]\n}\n\n// ── 구현 ──\n\nexport function parseLenientCfb(data: Buffer): LenientCfbContainer {\n if (data.length < 512) throw new Error(\"CFB 파일이 너무 짧습니다 (최소 512바이트)\")\n if (!data.subarray(0, 8).equals(CFB_MAGIC)) throw new Error(\"CFB 매직 바이트 불일치\")\n\n // ── 헤더 파싱 ──\n\n const sectorSizeShift = data.readUInt16LE(30)\n if (sectorSizeShift < 7 || sectorSizeShift > 16) throw new Error(\"유효하지 않은 섹터 크기 시프트: \" + sectorSizeShift)\n const sectorSize = 1 << sectorSizeShift // 보통 512\n const miniSectorSizeShift = data.readUInt16LE(32)\n if (miniSectorSizeShift > 16) throw new Error(\"유효하지 않은 미니 섹터 크기 시프트: \" + miniSectorSizeShift)\n const miniSectorSize = 1 << miniSectorSizeShift // 보통 64\n\n const headerFatSectorCount = data.readUInt32LE(44)\n if (headerFatSectorCount > 10000) throw new Error(\"FAT 섹터 수가 너무 많습니다: \" + headerFatSectorCount)\n // 실 파일 크기 sanity — FAT 엔트리는 파일 내 실존 섹터 수를 넘을 수 없다\n // (헤더 카운트 뻥튀기로 인한 fatTable 할당 증폭 방지: ~130KB 파일이 655MB 할당하던 버그)\n const maxRealSectors = Math.ceil(Math.max(0, data.length - 512) / sectorSize)\n const maxFatSectors = Math.max(1, Math.ceil(maxRealSectors / (sectorSize / 4)))\n const fatSectorCount = Math.min(headerFatSectorCount, maxFatSectors)\n const firstDirSector = data.readUInt32LE(48)\n const miniStreamCutoff = data.readUInt32LE(56) // 보통 4096\n const firstMiniFatSector = data.readUInt32LE(60)\n const miniFatSectorCount = data.readUInt32LE(64)\n const firstDifatSector = data.readUInt32LE(68)\n const difatSectorCount = data.readUInt32LE(72)\n\n // ── 유틸 ──\n\n function sectorOffset(id: number): number {\n return 512 + id * sectorSize\n }\n\n function readSectorData(id: number): Buffer {\n const off = sectorOffset(id)\n if (off + sectorSize > data.length) return Buffer.alloc(0)\n return data.subarray(off, off + sectorSize)\n }\n\n // ── DIFAT → FAT 섹터 목록 ──\n\n const fatSectors: number[] = []\n\n // 헤더 내 DIFAT (최대 109개)\n for (let i = 0; i < 109 && fatSectors.length < fatSectorCount; i++) {\n const sid = data.readUInt32LE(76 + i * 4)\n if (sid === FREE_SECT || sid === END_OF_CHAIN) break\n fatSectors.push(sid)\n }\n\n // 추가 DIFAT 섹터 체인\n let difatSector = firstDifatSector\n const visitedDifat = new Set<number>()\n for (let d = 0; d < difatSectorCount && difatSector !== END_OF_CHAIN && difatSector !== FREE_SECT; d++) {\n if (visitedDifat.has(difatSector)) break\n visitedDifat.add(difatSector)\n\n const buf = readSectorData(difatSector)\n if (buf.length < sectorSize) break // 파일 범위 밖 DIFAT 섹터(빈 버퍼) — readUInt32LE RangeError 방지, lenient 중단\n const entriesPerSector = (sectorSize / 4) - 1 // 마지막 4바이트는 다음 DIFAT 포인터\n for (let i = 0; i < entriesPerSector && fatSectors.length < fatSectorCount; i++) {\n const sid = buf.readUInt32LE(i * 4)\n if (sid === FREE_SECT || sid === END_OF_CHAIN) continue\n fatSectors.push(sid)\n }\n difatSector = buf.readUInt32LE(entriesPerSector * 4)\n }\n\n // ── FAT 테이블 구축 ──\n\n const entriesPerFatSector = sectorSize / 4\n const fatTable = new Uint32Array(fatSectors.length * entriesPerFatSector)\n\n for (let fi = 0; fi < fatSectors.length; fi++) {\n const buf = readSectorData(fatSectors[fi])\n for (let i = 0; i < entriesPerFatSector; i++) {\n fatTable[fi * entriesPerFatSector + i] = i * 4 + 3 < buf.length\n ? buf.readUInt32LE(i * 4)\n : FREE_SECT\n }\n }\n\n // ── 체인 리더 (순환 방지) ──\n\n function readChain(startSector: number, maxBytes: number): Buffer {\n if (startSector === END_OF_CHAIN || startSector === FREE_SECT) return Buffer.alloc(0)\n if (maxBytes > MAX_STREAM_SIZE) throw new Error(\"스트림이 너무 큽니다\")\n\n const chunks: Buffer[] = []\n let current = startSector\n let totalRead = 0\n const visited = new Set<number>()\n\n while (current !== END_OF_CHAIN && current !== FREE_SECT && totalRead < maxBytes) {\n if (visited.has(current)) break // 순환 감지\n if (visited.size > MAX_CHAIN_LENGTH) break\n visited.add(current)\n\n const buf = readSectorData(current)\n const remaining = maxBytes - totalRead\n chunks.push(remaining < sectorSize ? buf.subarray(0, remaining) : buf)\n totalRead += Math.min(buf.length, remaining)\n\n current = current < fatTable.length ? fatTable[current] : END_OF_CHAIN\n }\n\n return Buffer.concat(chunks)\n }\n\n // ── Mini-FAT 테이블 ──\n\n let miniFatTable: Uint32Array | null = null\n\n function getMiniFatTable(): Uint32Array {\n if (miniFatTable) return miniFatTable\n\n if (miniFatSectorCount === 0 || firstMiniFatSector === END_OF_CHAIN) {\n miniFatTable = new Uint32Array(0)\n return miniFatTable\n }\n\n const miniFatData = readChain(firstMiniFatSector, miniFatSectorCount * sectorSize)\n const entries = miniFatData.length / 4\n miniFatTable = new Uint32Array(entries)\n for (let i = 0; i < entries; i++) {\n miniFatTable[i] = miniFatData.readUInt32LE(i * 4)\n }\n return miniFatTable\n }\n\n // ── 디렉토리 엔트리 파싱 ──\n\n const dirData = readChain(firstDirSector, MAX_DIR_ENTRIES * 128)\n const dirEntries: DirEntry[] = []\n\n for (let offset = 0; offset + 128 <= dirData.length && dirEntries.length < MAX_DIR_ENTRIES; offset += 128) {\n const nameLen = dirData.readUInt16LE(offset + 64) // 바이트 수 (null 포함)\n if (nameLen <= 0 || nameLen > 64) {\n dirEntries.push({ name: \"\", type: 0, startSector: 0, size: 0 })\n continue\n }\n\n const nameBytes = nameLen - 2 // null terminator 제외\n const name = nameBytes > 0\n ? dirData.subarray(offset, offset + nameBytes).toString(\"utf16le\")\n : \"\"\n\n const type = dirData[offset + 66]\n const startSector = dirData.readUInt32LE(offset + 116)\n // CFBv3에서는 size가 u32 (offset 120), v4에서는 u64\n const size = dirData.readUInt32LE(offset + 120)\n\n dirEntries.push({ name, type, startSector, size })\n }\n\n // ── Root 엔트리에서 미니 스트림 추출 ──\n\n let miniStreamData: Buffer | null = null\n\n function getMiniStream(): Buffer {\n if (miniStreamData) return miniStreamData\n const root = dirEntries[0]\n if (!root || root.type !== 5) {\n miniStreamData = Buffer.alloc(0)\n return miniStreamData\n }\n miniStreamData = readChain(root.startSector, root.size || MAX_STREAM_SIZE)\n return miniStreamData\n }\n\n // ── 미니 스트림에서 읽기 ──\n\n function readMiniStream(startSector: number, size: number): Buffer {\n const mft = getMiniFatTable()\n const ms = getMiniStream()\n if (mft.length === 0 || ms.length === 0) return Buffer.alloc(0)\n\n const chunks: Buffer[] = []\n let current = startSector\n let totalRead = 0\n const visited = new Set<number>()\n\n while (current !== END_OF_CHAIN && current !== FREE_SECT && totalRead < size) {\n if (visited.has(current)) break\n if (visited.size > MAX_CHAIN_LENGTH) break\n visited.add(current)\n\n const off = current * miniSectorSize\n const remaining = size - totalRead\n const chunkSize = Math.min(miniSectorSize, remaining)\n if (off + chunkSize <= ms.length) {\n chunks.push(ms.subarray(off, off + chunkSize))\n }\n totalRead += chunkSize\n\n current = current < mft.length ? mft[current] : END_OF_CHAIN\n }\n\n return Buffer.concat(chunks)\n }\n\n // ── 스트림 읽기 (일반/미니 자동 분기) ──\n\n function readStreamData(entry: DirEntry): Buffer {\n if (entry.size === 0) return Buffer.alloc(0)\n if (entry.size < miniStreamCutoff) {\n const miniResult = readMiniStream(entry.startSector, entry.size)\n // 미니스트림이 비어있으면 일반 체인으로 폴백 (lenient)\n if (miniResult.length > 0) return miniResult\n }\n return readChain(entry.startSector, entry.size)\n }\n\n // ── 경로 기반 탐색 ──\n\n // 전체 경로 맵 구축 (간이: 이름 기반 flat lookup)\n // HWP 파일의 디렉토리 구조는 보통 1~2 depth이므로 이름 매칭으로 충분\n function findEntryByPath(path: string): DirEntry | null {\n // \"/FileHeader\" → \"FileHeader\"\n // \"/BodyText/Section0\" → path component matching\n const parts = path.replace(/^\\//, \"\").split(\"/\")\n\n if (parts.length === 1) {\n // 단일 이름 매칭\n return dirEntries.find(e => e.name === parts[0] && e.type === 2) ?? null\n }\n\n // 2-depth: storage/stream\n // HWP 구조: Root/BodyText/Section0, Root/DocInfo, Root/BinData/BIN0001 등\n const storageName = parts[0]\n const streamName = parts.slice(1).join(\"/\")\n\n // 디렉토리 구조 대신 이름 패턴으로 찾기 (lenient)\n for (const e of dirEntries) {\n if (e.type === 2 && e.name === streamName) {\n // 부모 확인은 생략 (lenient) — 중복 이름 시 첫 번째 반환\n return e\n }\n }\n\n // 정확한 이름이 아닌 경우 (ViewText/Section0 등)\n const lastPart = parts[parts.length - 1]\n return dirEntries.find(e => e.type === 2 && e.name === lastPart) ?? null\n }\n\n // ── 공개 API ──\n\n return {\n findStream(path: string): Buffer | null {\n // \\005 prefix 처리 (SummaryInformation)\n const normalized = path.replace(/^\\//, \"\")\n const entry = findEntryByPath(normalized)\n if (!entry || entry.type !== 2) return null\n const stream = readStreamData(entry)\n return stream.length > 0 ? stream : null\n },\n\n entries(): DirEntry[] {\n return dirEntries.filter(e => e.type === 2) // stream만\n },\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
parseFormatProfileJson
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PLIY23LY.js";
|
|
5
5
|
import {
|
|
6
6
|
BODY_FONTS,
|
|
7
7
|
BULLET2_CHARS,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
extractFormFields,
|
|
14
14
|
fillFormFields,
|
|
15
15
|
fillHwpx,
|
|
16
|
+
fillWithUniqueGuard,
|
|
16
17
|
gongmunLintWarnings,
|
|
17
18
|
hwpxToProfile,
|
|
18
19
|
incompatibleGongmunWarnings,
|
|
@@ -20,36 +21,44 @@ import {
|
|
|
20
21
|
markdownToHwpx,
|
|
21
22
|
parse,
|
|
22
23
|
unknownFontWarnings
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import
|
|
25
|
-
|
|
26
|
-
} from "./chunk-UEIYETCQ.js";
|
|
24
|
+
} from "./chunk-IGGRLWT5.js";
|
|
25
|
+
import "./chunk-7FCNOKOR.js";
|
|
26
|
+
import "./chunk-4VNLZ2GZ.js";
|
|
27
27
|
import {
|
|
28
28
|
detectFormat,
|
|
29
29
|
detectZipFormat
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-YOO6ET6M.js";
|
|
31
31
|
import "./chunk-MOL7MDBG.js";
|
|
32
|
-
import "./chunk-
|
|
33
|
-
import "./chunk-
|
|
34
|
-
import "./chunk-
|
|
32
|
+
import "./chunk-WQLHPOMJ.js";
|
|
33
|
+
import "./chunk-DZIXKL7E.js";
|
|
34
|
+
import "./chunk-ELOV2GIJ.js";
|
|
35
|
+
import {
|
|
36
|
+
blocksToMarkdown
|
|
37
|
+
} from "./chunk-CMN3SYZ3.js";
|
|
38
|
+
import "./chunk-6SHO3BNV.js";
|
|
39
|
+
import "./chunk-V6U6YS5B.js";
|
|
35
40
|
import {
|
|
36
41
|
VERSION,
|
|
37
42
|
sanitizeError,
|
|
38
43
|
toArrayBuffer
|
|
39
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-FSXLSHCW.js";
|
|
40
45
|
|
|
41
46
|
// src/cli.ts
|
|
42
47
|
import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
|
|
43
48
|
import { basename, dirname, resolve, extname } from "path";
|
|
44
49
|
import { Command } from "commander";
|
|
45
50
|
var program = new Command();
|
|
46
|
-
program.name("kordoc").description("\uBAA8\uB450 \uD30C\uC2F1\uD574\uBC84\uB9AC\uACA0\uB2E4 \u2014 HWP, HWPX, PDF, XLSX, DOCX \u2192 Markdown").version(VERSION).argument("<files...>", "\uBCC0\uD658\uD560 \uD30C\uC77C \uACBD\uB85C (HWP, HWPX, PDF, XLSX, DOCX)").option("-o, --output <path>", "\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C (\uB2E8\uC77C \uD30C\uC77C \uC2DC)").option("-d, --out-dir <dir>", "\uCD9C\uB825 \uB514\uB809\uD1A0\uB9AC (\uB2E4\uC911 \uD30C\uC77C \uC2DC)").option("-p, --pages <range>", "\uD398\uC774\uC9C0/\uC139\uC158 \uBC94\uC704 (\uC608: 1-3, 1,3,5)").option("--format <type>", "\uCD9C\uB825 \uD615\uC2DD: markdown (\uAE30\uBCF8) \
|
|
47
|
-
const validFormats = ["markdown", "json"];
|
|
51
|
+
program.name("kordoc").description("\uBAA8\uB450 \uD30C\uC2F1\uD574\uBC84\uB9AC\uACA0\uB2E4 \u2014 HWP, HWPX, PDF, XLSX, DOCX \u2192 Markdown").version(VERSION).argument("<files...>", "\uBCC0\uD658\uD560 \uD30C\uC77C \uACBD\uB85C (HWP, HWPX, PDF, XLSX, DOCX)").option("-o, --output <path>", "\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C (\uB2E8\uC77C \uD30C\uC77C \uC2DC)").option("-d, --out-dir <dir>", "\uCD9C\uB825 \uB514\uB809\uD1A0\uB9AC (\uB2E4\uC911 \uD30C\uC77C \uC2DC)").option("-p, --pages <range>", "\uD398\uC774\uC9C0/\uC139\uC158 \uBC94\uC704 (\uC608: 1-3, 1,3,5)").option("--format <type>", "\uCD9C\uB825 \uD615\uC2DD: markdown (\uAE30\uBCF8), json, chunks (RAG\uC6A9 \uAD6C\uC870 \uCCAD\uD06C JSON \u2014 \uD5E4\uB529\xB7\uAC1C\uC870\uC2DD \uC704\uACC4 breadcrumb + \uD45C \uB3C5\uB9BD \uCCAD\uD06C)", "markdown").option("--no-header-footer", "PDF \uBA38\uB9AC\uAE00/\uBC14\uB2E5\uAE00 \uC790\uB3D9 \uC81C\uAC70 \uB044\uAE30 (\uAE30\uBCF8: \uC81C\uAC70\uD568)").option("--formula-ocr", "PDF \uC218\uC2DD OCR \uD65C\uC131\uD654 (MFD+MFR ONNX, \uCCAB \uC0AC\uC6A9 \uC2DC \uBAA8\uB378 ~155MB \uC790\uB3D9 \uB2E4\uC6B4\uB85C\uB4DC)").option("--ocr", "\uC2A4\uCE94/\uC774\uBBF8\uC9C0 PDF \uD14D\uC2A4\uD2B8 OCR (\uB0B4\uC7A5 PP-OCRv5 korean, \uCCAB \uC0AC\uC6A9 \uC2DC \uBAA8\uB378 ~18MB \uC790\uB3D9 \uB2E4\uC6B4\uB85C\uB4DC \u2014 OCR \uD544\uC694 \uD398\uC774\uC9C0\uB9CC \uC778\uC2DD)").option("--ocr-force", "\uC804 \uD398\uC774\uC9C0 \uAC15\uC81C OCR (\uD14D\uC2A4\uD2B8\uCE35\uC774 \uC788\uC5B4\uB3C4 \uBB34\uC2DC\uD558\uACE0 \uC7AC\uC778\uC2DD)").option("--dedupe-headers", "HWP5 \uB808\uC774\uC544\uC6C3 \uD45C \uD398\uC774\uC9C0 \uBC18\uBCF5 \uB7EC\uB2DD \uD5E4\uB354 \uC911\uBCF5 \uC81C\uAC70 (\uAE30\uBCF8 off \u2014 \uBD99\uC784\uBCC4 \uC7AC\uBC88\uD638 \uC624\uC0AD\uC81C \uC8FC\uC758)").option("--keep-empty-cols", "\uD45C \uC624\uB978\uCABD \uB05D \uBE48 \uC5F4(\uC11C\uC2DD \uC785\uB825\uB780) \uBCF4\uC874 (#47, \uAE30\uBCF8 off: \uD6C4\uD589 \uBE48 \uC5F4 \uD2B8\uB9BC)").option("--inline-images", "\uC774\uBBF8\uC9C0\uB97C base64 data URI \uB85C \uB9C8\uD06C\uB2E4\uC6B4\uC5D0 \uC778\uB77C\uC778 (BMP\u2192PNG \uC555\uCD95, HWP5 \uC804\uC6A9 \u2014 \uC778\uB77C\uC778\uB41C \uACBD\uC6B0\uB9CC \uD30C\uC77C \uBBF8\uC800\uC7A5, \uADF8 \uC678 \uD3EC\uB9F7\uC740 \uC800\uC7A5 \uC720\uC9C0)").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (files, opts) => {
|
|
52
|
+
const validFormats = ["markdown", "json", "chunks"];
|
|
48
53
|
if (!validFormats.includes(opts.format)) {
|
|
49
|
-
process.stderr.write(`[kordoc] \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD615\uC2DD: ${opts.format} (markdown
|
|
54
|
+
process.stderr.write(`[kordoc] \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD615\uC2DD: ${opts.format} (markdown, json, chunks)
|
|
50
55
|
`);
|
|
51
56
|
process.exit(1);
|
|
52
57
|
}
|
|
58
|
+
if (opts.output && files.length > 1) {
|
|
59
|
+
process.stderr.write(`[kordoc] \u26A0\uFE0F -o/--output \uC740 \uB2E8\uC77C \uD30C\uC77C \uC804\uC6A9\uC774\uB77C \uBB34\uC2DC\uB429\uB2C8\uB2E4 \u2014 \uB2E4\uC911 \uD30C\uC77C\uC740 -d/--out-dir \uB97C \uC0AC\uC6A9\uD558\uC138\uC694
|
|
60
|
+
`);
|
|
61
|
+
}
|
|
53
62
|
for (let fi = 0; fi < files.length; fi++) {
|
|
54
63
|
const filePath = files[fi];
|
|
55
64
|
const absPath = resolve(filePath);
|
|
@@ -74,7 +83,10 @@ program.name("kordoc").description("\uBAA8\uB450 \uD30C\uC2F1\uD574\uBC84\uB9AC\
|
|
|
74
83
|
if (opts.pages) parseOptions.pages = opts.pages;
|
|
75
84
|
if (opts.headerFooter === false) parseOptions.removeHeaderFooter = false;
|
|
76
85
|
if (opts.formulaOcr) parseOptions.formulaOcr = true;
|
|
86
|
+
if (opts.ocrForce) parseOptions.ocr = "force";
|
|
87
|
+
else if (opts.ocr) parseOptions.ocr = true;
|
|
77
88
|
if (opts.dedupeHeaders) parseOptions.dedupeRunningHeaders = true;
|
|
89
|
+
if (opts.keepEmptyCols) parseOptions.keepTrailingEmptyCols = true;
|
|
78
90
|
if (opts.inlineImages) parseOptions.inlineImages = true;
|
|
79
91
|
if (!opts.silent) {
|
|
80
92
|
parseOptions.onProgress = (current, total) => {
|
|
@@ -97,11 +109,19 @@ program.name("kordoc").description("\uBAA8\uB450 \uD30C\uC2F1\uD574\uBC84\uB9AC\
|
|
|
97
109
|
if (opts.outDir && result.images?.length && !imagesInlined) {
|
|
98
110
|
markdown = markdown.replace(/!\[image\]\(image_/g, ".replace(/(<img\b[^>]*\bsrc=")image_/g, "$1images/image_");
|
|
99
111
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
let output;
|
|
113
|
+
if (opts.format === "json") {
|
|
114
|
+
output = JSON.stringify(
|
|
115
|
+
result,
|
|
116
|
+
(_key, value) => value instanceof Uint8Array ? Buffer.from(value).toString("base64") : value,
|
|
117
|
+
2
|
|
118
|
+
);
|
|
119
|
+
} else if (opts.format === "chunks") {
|
|
120
|
+
const { blocksToChunks } = await import("./chunks-ZQEFAZNC.js");
|
|
121
|
+
output = JSON.stringify(blocksToChunks(result.blocks), null, 2);
|
|
122
|
+
} else {
|
|
123
|
+
output = markdown;
|
|
124
|
+
}
|
|
105
125
|
const saveImages = (dir) => {
|
|
106
126
|
if (!result.images?.length || imagesInlined) return;
|
|
107
127
|
const imgDir = resolve(dir, "images");
|
|
@@ -119,7 +139,7 @@ program.name("kordoc").description("\uBAA8\uB450 \uD30C\uC2F1\uD574\uBC84\uB9AC\
|
|
|
119
139
|
saveImages(resolve(opts.output, ".."));
|
|
120
140
|
} else if (opts.outDir) {
|
|
121
141
|
mkdirSync(opts.outDir, { recursive: true });
|
|
122
|
-
const outExt = opts.format === "json" ? ".json" : ".md";
|
|
142
|
+
const outExt = opts.format === "json" ? ".json" : opts.format === "chunks" ? ".chunks.json" : ".md";
|
|
123
143
|
const outPath = resolve(opts.outDir, fileName.replace(/\.[^.]+$/, outExt));
|
|
124
144
|
writeFileSync(outPath, output, "utf-8");
|
|
125
145
|
if (!opts.silent) process.stderr.write(` \u2192 ${outPath}
|
|
@@ -144,7 +164,7 @@ program.command("watch <dir>").description("\uB514\uB809\uD1A0\uB9AC \uAC10\uC2D
|
|
|
144
164
|
if (command.getOptionValueSource("format") === "default" && program.getOptionValueSource("format") === "cli") {
|
|
145
165
|
opts.format = rootOpts.format;
|
|
146
166
|
}
|
|
147
|
-
const { watchDirectory } = await import("./watch-
|
|
167
|
+
const { watchDirectory } = await import("./watch-VWXJZT2R.js");
|
|
148
168
|
await watchDirectory({
|
|
149
169
|
dir,
|
|
150
170
|
outDir: opts.outDir,
|
|
@@ -154,7 +174,7 @@ program.command("watch <dir>").description("\uB514\uB809\uD1A0\uB9AC \uAC10\uC2D
|
|
|
154
174
|
silent: opts.silent
|
|
155
175
|
});
|
|
156
176
|
});
|
|
157
|
-
program.command("fill <template>").description("\uC11C\uC2DD \uBB38\uC11C\uC758 \uBE48\uCE78\uC744 \uCC44\uC6CC\uC11C \uCD9C\uB825 \u2014 kordoc fill \uC2E0\uCCAD\uC11C.hwpx -f '\uC131\uBA85=\uD64D\uAE38\uB3D9,\uC804\uD654=010-1234-5678' -o \uACB0\uACFC.hwpx").option("-f, --fields <pairs>", "\uCC44\uC6B8 \uD544\uB4DC (key=value \uC27C\uD45C \uAD6C\uBD84 \uB610\uB294 JSON)").option("-j, --json <path>", "\uCC44\uC6B8 \uD544\uB4DC JSON \uD30C\uC77C \uACBD\uB85C").option("-o, --output <path>", "\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C (\uD655\uC7A5\uC790\uB85C \uD3EC\uB9F7 \uACB0\uC815: .md, .hwpx)").option("--format <type>", "\uCD9C\uB825 \uD3EC\uB9F7: hwpx-preserve (\uAE30\uBCF8, \uC6D0\uBCF8 \uC2A4\uD0C0\uC77C \uBCF4\uC874), hwpx, markdown", "hwpx-preserve").option("--dry-run", "\uCC44\uC6B0\uC9C0 \uC54A\uACE0 \uC11C\uC2DD \uD544\uB4DC \uBAA9\uB85D\uB9CC \uCD9C\uB825").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (template, opts, command) => {
|
|
177
|
+
program.command("fill <template>").description("\uC11C\uC2DD \uBB38\uC11C\uC758 \uBE48\uCE78\uC744 \uCC44\uC6CC\uC11C \uCD9C\uB825 \u2014 kordoc fill \uC2E0\uCCAD\uC11C.hwpx -f '\uC131\uBA85=\uD64D\uAE38\uB3D9,\uC804\uD654=010-1234-5678' -o \uACB0\uACFC.hwpx").option("-f, --fields <pairs>", "\uCC44\uC6B8 \uD544\uB4DC (key=value \uC27C\uD45C \uAD6C\uBD84 \uB610\uB294 JSON)").option("-j, --json <path>", "\uCC44\uC6B8 \uD544\uB4DC JSON \uD30C\uC77C \uACBD\uB85C").option("-o, --output <path>", "\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C (\uD655\uC7A5\uC790\uB85C \uD3EC\uB9F7 \uACB0\uC815: .md, .hwpx)").option("--format <type>", "\uCD9C\uB825 \uD3EC\uB9F7: hwpx-preserve (\uAE30\uBCF8, \uC6D0\uBCF8 \uC2A4\uD0C0\uC77C \uBCF4\uC874), hwpx, markdown", "hwpx-preserve").option("--formats <json>", `\uD544\uB4DC\uBCC4 \uAC12 \uC11C\uC2DD JSON (\uB77C\uBCA8\u2192\uD3EC\uB9F7) \u2014 \uC608: '{"\uB0A0\uC9DC":"yy.mm.dd","\uC8FC\uBBFC\uB4F1\uB85D\uBC88\uD638":"rrn:masked"}'`).option("--require-unique", "\uD55C \uD0A4\uAC00 \uC11C\uC2DD\uC758 2\uACF3 \uC774\uC0C1\uC5D0 \uB9E4\uCE6D\uB418\uBA74 \uCC44\uC6B0\uC9C0 \uC54A\uACE0 \uAC70\uBD80 (\uBC18\uBCF5 \uB77C\uBCA8 \uC591\uC2DD \uC624\uC5FC \uBC29\uC9C0)").option("--mask", "\uCC44\uC6B4 \uAC12 \uBBF8\uB178\uCD9C \u2014 \uCD9C\uB825 \uD30C\uC77C \uC5C6\uC774 markdown\uC744 stdout\uC73C\uB85C \uB0BC \uB54C \uBCF8\uBB38 \uB300\uC2E0 \uC548\uB0B4\uB9CC \uD45C\uC2DC").option("--dry-run", "\uCC44\uC6B0\uC9C0 \uC54A\uACE0 \uC11C\uC2DD \uD544\uB4DC \uBAA9\uB85D\uB9CC \uCD9C\uB825").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (template, opts, command) => {
|
|
158
178
|
try {
|
|
159
179
|
const rootOpts = program.opts();
|
|
160
180
|
opts.output ??= rootOpts.output;
|
|
@@ -174,7 +194,7 @@ program.command("fill <template>").description("\uC11C\uC2DD \uBB38\uC11C\uC758
|
|
|
174
194
|
if (!opts.silent) process.stderr.write(`[kordoc] ${basename(absPath)} \uD30C\uC2F1 \uC911...
|
|
175
195
|
`);
|
|
176
196
|
if (opts.dryRun) {
|
|
177
|
-
const result2 = await parse(arrayBuffer);
|
|
197
|
+
const result2 = await parse(arrayBuffer, { keepTrailingEmptyCols: true });
|
|
178
198
|
if (!result2.success) {
|
|
179
199
|
process.stderr.write(`[kordoc] \uD30C\uC2F1 \uC2E4\uD328: ${result2.error}
|
|
180
200
|
`);
|
|
@@ -214,11 +234,32 @@ program.command("fill <template>").description("\uC11C\uC2DD \uBB38\uC11C\uC758
|
|
|
214
234
|
`);
|
|
215
235
|
process.exit(1);
|
|
216
236
|
}
|
|
237
|
+
let inputs = values;
|
|
238
|
+
if (opts.formats) {
|
|
239
|
+
let formatMap;
|
|
240
|
+
try {
|
|
241
|
+
formatMap = JSON.parse(String(opts.formats));
|
|
242
|
+
} catch {
|
|
243
|
+
process.stderr.write(`[kordoc] --formats \uB294 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uB77C\uBCA8\u2192\uD3EC\uB9F7)
|
|
244
|
+
`);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
inputs = Object.fromEntries(Object.entries(values).map(([k, v]) => {
|
|
248
|
+
const format = formatMap[k];
|
|
249
|
+
return [k, format ? { value: v, format } : v];
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
217
252
|
let outputFormat = opts.format;
|
|
218
253
|
if (opts.output) {
|
|
219
254
|
const ext = extname(opts.output).toLowerCase();
|
|
255
|
+
const explicitFormat = command.getOptionValueSource("format") === "cli" || program.getOptionValueSource("format") === "cli";
|
|
256
|
+
const before = outputFormat;
|
|
220
257
|
if (ext === ".hwpx") outputFormat = outputFormat === "markdown" ? "hwpx-preserve" : outputFormat;
|
|
221
258
|
else if (ext === ".md") outputFormat = "markdown";
|
|
259
|
+
if (explicitFormat && before !== outputFormat) {
|
|
260
|
+
process.stderr.write(`[kordoc] \u26A0\uFE0F --format ${before} \uAC00 \uCD9C\uB825 \uD655\uC7A5\uC790(${ext})\uC640 \uCDA9\uB3CC\uD569\uB2C8\uB2E4 \u2014 \uD655\uC7A5\uC790 \uAE30\uC900 ${outputFormat} \uB85C \uC9C4\uD589
|
|
261
|
+
`);
|
|
262
|
+
}
|
|
222
263
|
}
|
|
223
264
|
if (outputFormat === "hwpx-preserve") {
|
|
224
265
|
const format = detectFormat(arrayBuffer);
|
|
@@ -232,10 +273,14 @@ program.command("fill <template>").description("\uC11C\uC2DD \uBB38\uC11C\uC758
|
|
|
232
273
|
`);
|
|
233
274
|
outputFormat = "hwpx";
|
|
234
275
|
} else {
|
|
235
|
-
const hwpxResult = await fillHwpx(arrayBuffer,
|
|
276
|
+
const hwpxResult = opts.requireUnique ? await fillWithUniqueGuard(inputs, (vals, blocked) => fillHwpx(arrayBuffer, vals, blocked)) : { ...await fillHwpx(arrayBuffer, inputs), rejected: [] };
|
|
236
277
|
if (!opts.silent) {
|
|
237
278
|
process.stderr.write(`[kordoc] ${hwpxResult.filled.length}\uAC1C \uD544\uB4DC \uCC44\uC6C0 (\uC6D0\uBCF8 \uC2A4\uD0C0\uC77C \uBCF4\uC874)
|
|
238
279
|
`);
|
|
280
|
+
if (hwpxResult.rejected.length > 0) {
|
|
281
|
+
process.stderr.write(`[kordoc] \u26A0\uFE0F \uBAA8\uD638 \uB77C\uBCA8 \uAC70\uBD80(2\uACF3+ \uB9E4\uCE6D): ${hwpxResult.rejected.join(", ")}
|
|
282
|
+
`);
|
|
283
|
+
}
|
|
239
284
|
if (hwpxResult.unmatched.length > 0) {
|
|
240
285
|
process.stderr.write(`[kordoc] \u26A0\uFE0F \uB9E4\uCE6D \uC2E4\uD328: ${hwpxResult.unmatched.join(", ")}
|
|
241
286
|
`);
|
|
@@ -252,7 +297,7 @@ program.command("fill <template>").description("\uC11C\uC2DD \uBB38\uC11C\uC758
|
|
|
252
297
|
return;
|
|
253
298
|
}
|
|
254
299
|
}
|
|
255
|
-
const result = await parse(arrayBuffer);
|
|
300
|
+
const result = await parse(arrayBuffer, { keepTrailingEmptyCols: true });
|
|
256
301
|
if (!result.success) {
|
|
257
302
|
process.stderr.write(`[kordoc] \uD30C\uC2F1 \uC2E4\uD328: ${result.error}
|
|
258
303
|
`);
|
|
@@ -263,10 +308,14 @@ program.command("fill <template>").description("\uC11C\uC2DD \uBB38\uC11C\uC758
|
|
|
263
308
|
process.stderr.write(`[kordoc] \uC11C\uC2DD \uD544\uB4DC ${formInfo.fields.length}\uAC1C \uAC10\uC9C0 (\uD655\uC2E0\uB3C4 ${(formInfo.confidence * 100).toFixed(0)}%)
|
|
264
309
|
`);
|
|
265
310
|
}
|
|
266
|
-
const fillResult = fillFormFields(result.blocks,
|
|
311
|
+
const fillResult = opts.requireUnique ? await fillWithUniqueGuard(inputs, (vals, blocked) => fillFormFields(result.blocks, vals, blocked)) : { ...fillFormFields(result.blocks, inputs), rejected: [] };
|
|
267
312
|
if (!opts.silent) {
|
|
268
313
|
process.stderr.write(`[kordoc] ${fillResult.filled.length}\uAC1C \uD544\uB4DC \uCC44\uC6C0
|
|
269
314
|
`);
|
|
315
|
+
if (fillResult.rejected.length > 0) {
|
|
316
|
+
process.stderr.write(`[kordoc] \u26A0\uFE0F \uBAA8\uD638 \uB77C\uBCA8 \uAC70\uBD80(2\uACF3+ \uB9E4\uCE6D): ${fillResult.rejected.join(", ")}
|
|
317
|
+
`);
|
|
318
|
+
}
|
|
270
319
|
if (fillResult.unmatched.length > 0) {
|
|
271
320
|
process.stderr.write(`[kordoc] \u26A0\uFE0F \uB9E4\uCE6D \uC2E4\uD328: ${fillResult.unmatched.join(", ")}
|
|
272
321
|
`);
|
|
@@ -288,6 +337,9 @@ program.command("fill <template>").description("\uC11C\uC2DD \uBB38\uC11C\uC758
|
|
|
288
337
|
mkdirSync(dirname(resolve(opts.output)), { recursive: true });
|
|
289
338
|
writeFileSync(resolve(opts.output), markdown, "utf-8");
|
|
290
339
|
if (!opts.silent) process.stderr.write(`[kordoc] \u2192 ${resolve(opts.output)}
|
|
340
|
+
`);
|
|
341
|
+
} else if (opts.mask) {
|
|
342
|
+
process.stdout.write(`\u26A0\uFE0F --mask \uD65C\uC131 \u2014 \uAC1C\uC778\uC815\uBCF4 \uB178\uCD9C \uBC29\uC9C0\uB97C \uC704\uD574 \uBCF8\uBB38\uC744 \uCD9C\uB825\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. -o \uB85C \uD30C\uC77C \uC800\uC7A5 \uD6C4 \uD655\uC778\uD558\uC138\uC694.
|
|
291
343
|
`);
|
|
292
344
|
} else {
|
|
293
345
|
process.stdout.write(markdown + "\n");
|
|
@@ -301,7 +353,7 @@ program.command("fill <template>").description("\uC11C\uC2DD \uBB38\uC11C\uC758
|
|
|
301
353
|
});
|
|
302
354
|
program.command("seal <file>").description('\uB3C4\uC7A5/\uC11C\uBA85 \uC774\uBBF8\uC9C0\uB97C \uC575\uCEE4 \uBB38\uAD6C \uC704\uC5D0 \uBD80\uC720 \uBC30\uCE58 (\uD45C/\uD398\uC774\uC9C0 \uBD88\uD655\uC7A5) \u2014 kordoc seal \uC2E0\uCCAD\uC11C.hwpx --image \uB3C4\uC7A5.png --anchor "(\uC778)" -o \uACB0\uACFC.hwpx').requiredOption("--image <path>", "\uB3C4\uC7A5/\uC11C\uBA85 \uC774\uBBF8\uC9C0 (\uD22C\uBA85 \uBC30\uACBD PNG \uAD8C\uC7A5)").option("--anchor <text>", "\uC575\uCEE4 \uBB38\uAD6C", "(\uC778)").option("-n, --occurrence <num>", "\uAC19\uC740 \uC575\uCEE4\uAC00 \uC5EC\uB7FF\uC77C \uB54C 0-based \uC120\uD0DD", "0").option("--size-mm <num>", "\uB3C4\uC7A5 \uD55C \uBCC0 \uD06C\uAE30 mm (\uAE30\uBCF8: \uC904\uB192\uC774\xD71.6, 7~18 \uD074\uB7A8\uD504)").option("--mode <mode>", "overlap(\uBB38\uAD6C \uC704 \uACB9\uCE68) | right(\uC624\uB978\uCABD \uC606) | auto", "auto").option("--dx <mm>", "x \uBBF8\uC138\uC870\uC815 mm", "0").option("--dy <mm>", "y \uBBF8\uC138\uC870\uC815 mm", "0").option("-o, --output <path>", "\uCD9C\uB825 \uACBD\uB85C (\uAE30\uBCF8: <\uC785\uB825>.sealed.hwpx)").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (file, opts) => {
|
|
303
355
|
try {
|
|
304
|
-
const { placeSealHwpx, detectFormat: detectFormat2 } = await import("./-
|
|
356
|
+
const { placeSealHwpx, detectFormat: detectFormat2 } = await import("./-SXQIZHYA.js");
|
|
305
357
|
const rootOpts = program.opts();
|
|
306
358
|
const output = opts.output ?? rootOpts.output;
|
|
307
359
|
const silent = opts.silent ?? rootOpts.silent;
|
|
@@ -377,9 +429,9 @@ program.command("seal <file>").description('\uB3C4\uC7A5/\uC11C\uBA85 \uC774\uBB
|
|
|
377
429
|
process.exit(1);
|
|
378
430
|
}
|
|
379
431
|
});
|
|
380
|
-
program.command("patch <original> <edited>").description("\uC11C\uC2DD \uBCF4\uC874 \uB77C\uC6B4\uB4DC\uD2B8\uB9BD \uD328\uCE58 \u2014 \uD3B8\uC9D1\uB41C \uB9C8\uD06C\uB2E4\uC6B4\uC744 \uC6D0\uBCF8 HWPX/HWP\uC5D0 in-place \uBC18\uC601 (kordoc patch \uC6D0\uBCF8.hwpx \uD3B8\uC9D1.md -o \uCD9C\uB825.hwpx)").option("-o, --output <path>", "\uCD9C\uB825 \uACBD\uB85C (\uAE30\uBCF8: <\uC6D0\uBCF8>.patched.hwpx|.hwp)").option("--no-verify", "\uD328\uCE58 \uD6C4 \uC7AC\uD30C\uC2F1 \uC790\uB3D9 \uAC80\uC99D \uC0DD\uB7B5").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (original, edited, opts) => {
|
|
432
|
+
program.command("patch <original> <edited>").description("\uC11C\uC2DD \uBCF4\uC874 \uB77C\uC6B4\uB4DC\uD2B8\uB9BD \uD328\uCE58 \u2014 \uD3B8\uC9D1\uB41C \uB9C8\uD06C\uB2E4\uC6B4\uC744 \uC6D0\uBCF8 HWPX/HWP\uC5D0 in-place \uBC18\uC601 (kordoc patch \uC6D0\uBCF8.hwpx \uD3B8\uC9D1.md -o \uCD9C\uB825.hwpx). \uBBF8\uC801\uC6A9(skip) \uD3B8\uC9D1\uC774 \uC788\uC73C\uBA74 exit 2").option("-o, --output <path>", "\uCD9C\uB825 \uACBD\uB85C (\uAE30\uBCF8: <\uC6D0\uBCF8>.patched.hwpx|.hwp)").option("--no-verify", "\uD328\uCE58 \uD6C4 \uC7AC\uD30C\uC2F1 \uC790\uB3D9 \uAC80\uC99D \uC0DD\uB7B5").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (original, edited, opts) => {
|
|
381
433
|
try {
|
|
382
|
-
const { patchHwpx, patchHwp, detectFormat: detectFormat2 } = await import("./-
|
|
434
|
+
const { patchHwpx, patchHwp, detectFormat: detectFormat2 } = await import("./-SXQIZHYA.js");
|
|
383
435
|
const rootOpts = program.opts();
|
|
384
436
|
const output = opts.output ?? rootOpts.output;
|
|
385
437
|
const silent = opts.silent ?? rootOpts.silent;
|
|
@@ -420,7 +472,7 @@ program.command("patch <original> <edited>").description("\uC11C\uC2DD \uBCF4\uC
|
|
|
420
472
|
});
|
|
421
473
|
program.command("validate <file>").description("HWPX \uAD6C\uC870 \uAC80\uC99D \u2014 ZIP\xB7mimetype\xB7\uD544\uC218 \uD30C\uC77C\xB7XML \uC6F0\uD3FC\uB4DC\xB7secCnt\xB7manifest \uCC38\uC870 (\uD55C\uCEF4\uB3C5\uC2A4 \uAC70\uBD80 \uC694\uC778 \uC0AC\uC804 \uCC28\uB2E8)").option("--json", "\uACB0\uACFC\uB97C JSON\uC73C\uB85C stdout\uC5D0 \uCD9C\uB825").action(async (file, opts) => {
|
|
422
474
|
try {
|
|
423
|
-
const { validateHwpx } = await import("./-
|
|
475
|
+
const { validateHwpx } = await import("./-SXQIZHYA.js");
|
|
424
476
|
const buf = new Uint8Array(readFileSync(resolve(file)));
|
|
425
477
|
const result = await validateHwpx(buf);
|
|
426
478
|
if (opts.json) {
|
|
@@ -584,10 +636,10 @@ program.command("lint <file>").description("\uACF5\uBB38\uC11C \uD45C\uAE30\uBC9
|
|
|
584
636
|
if (opts.json) {
|
|
585
637
|
process.stdout.write(JSON.stringify({ findings, summary: { total: findings.length, errors, ok: errors === 0 } }, null, 2) + "\n");
|
|
586
638
|
} else {
|
|
587
|
-
process.
|
|
639
|
+
process.stderr.write(`[kordoc] \uD45C\uAE30\uBC95 \uAC80\uC218: \uC704\uBC18 ${findings.length}\uAC74 (error ${errors}, warning ${findings.length - errors})
|
|
588
640
|
`);
|
|
589
641
|
for (const f of findings) {
|
|
590
|
-
process.
|
|
642
|
+
process.stderr.write(` L${f.line} [${f.severity}] ${f.rule}: "${f.match}" \u2014 ${f.message}${f.suggest ? ` \u2192 ${f.suggest}` : ""}
|
|
591
643
|
`);
|
|
592
644
|
}
|
|
593
645
|
}
|
|
@@ -598,12 +650,97 @@ program.command("lint <file>").description("\uACF5\uBB38\uC11C \uD45C\uAE30\uBC9
|
|
|
598
650
|
process.exit(1);
|
|
599
651
|
}
|
|
600
652
|
});
|
|
653
|
+
program.command("redact <files...>").description("\uAC1C\uC778\uC815\uBCF4 \uC11C\uC2DD \uBCF4\uC874 \uB9C8\uC2A4\uD0B9 \u2014 \uC8FC\uBBFC\uBC88\uD638\xB7\uC804\uD654\xB7\uC774\uBA54\uC77C\xB7\uCE74\uB4DC\xB7\uACC4\uC88C\uB97C \uD0D0\uC9C0\uD574 HWPX/HWP\uB294 \uC6D0\uBCF8 \uC11C\uC2DD \uADF8\uB300\uB85C patch, \uADF8 \uC678 \uD3EC\uB9F7\uC740 \uB9C8\uC2A4\uD0B9\uB41C \uB9C8\uD06C\uB2E4\uC6B4 \uCD9C\uB825. \uC790\uB3D9 \uAC80\uCD9C \uBCF4\uC870 \uB3C4\uAD6C\uC774\uBBC0\uB85C \uACB0\uACFC\uB294 \uBC18\uB4DC\uC2DC \uC0AC\uB78C\uC774 \uCD5C\uC885 \uD655\uC778\uD558\uC138\uC694 (\uC774\uBBF8\uC9C0 \uC548 \uD14D\uC2A4\uD2B8\uB294 \uD0D0\uC9C0 \uBD88\uAC00)").option("--rules <csv>", "\uC801\uC6A9 \uB8F0 (\uAE30\uBCF8: rrn,phone,email,card,account \u2014 passport,driver\uB294 opt-in)").option("--mask-char <ch>", "\uB9C8\uC2A4\uD06C \uBB38\uC790 1\uAE00\uC790 (\uAE30\uBCF8: \u25CF)").option("-o, --output <path>", "\uCD9C\uB825 \uACBD\uB85C (\uB2E8\uC77C \uD30C\uC77C \uC2DC)").option("-d, --out-dir <dir>", "\uCD9C\uB825 \uB514\uB809\uD1A0\uB9AC (\uB2E4\uC911 \uD30C\uC77C \uC2DC)").option("--dry-run", "\uD0D0\uC9C0 \uB9AC\uD3EC\uD2B8\uB9CC \uCD9C\uB825, \uD30C\uC77C \uBBF8\uC0DD\uC131").option("--json", "\uB9AC\uD3EC\uD2B8\uB97C JSON\uC73C\uB85C stdout \uCD9C\uB825").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (files, opts) => {
|
|
654
|
+
const { redactMarkdown, DEFAULT_REDACT_RULES, patchHwpx, patchHwp } = await import("./-SXQIZHYA.js");
|
|
655
|
+
const rootOpts = program.opts();
|
|
656
|
+
const output = opts.output ?? rootOpts.output;
|
|
657
|
+
const silent = opts.silent ?? rootOpts.silent;
|
|
658
|
+
const KNOWN_RULES = /* @__PURE__ */ new Set(["rrn", "phone", "email", "card", "account", "passport", "driver"]);
|
|
659
|
+
const rules = opts.rules ? String(opts.rules).split(",").map((r) => r.trim()).filter(Boolean) : [...DEFAULT_REDACT_RULES];
|
|
660
|
+
const badRule = rules.find((r) => !KNOWN_RULES.has(r));
|
|
661
|
+
if (badRule) {
|
|
662
|
+
process.stderr.write(`[kordoc] \uC54C \uC218 \uC5C6\uB294 \uB8F0: ${badRule} (\uD5C8\uC6A9: ${[...KNOWN_RULES].join(", ")})
|
|
663
|
+
`);
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
if (output && files.length > 1) {
|
|
667
|
+
process.stderr.write(`[kordoc] \u26A0\uFE0F -o/--output \uC740 \uB2E8\uC77C \uD30C\uC77C \uC804\uC6A9\uC774\uB77C \uBB34\uC2DC\uB429\uB2C8\uB2E4 \u2014 \uB2E4\uC911 \uD30C\uC77C\uC740 -d/--out-dir \uB97C \uC0AC\uC6A9\uD558\uC138\uC694
|
|
668
|
+
`);
|
|
669
|
+
}
|
|
670
|
+
const jsonReports = [];
|
|
671
|
+
for (const filePath of files) {
|
|
672
|
+
const absPath = resolve(filePath);
|
|
673
|
+
const fileName = basename(absPath);
|
|
674
|
+
try {
|
|
675
|
+
const buffer = readFileSync(absPath);
|
|
676
|
+
const arrayBuffer = toArrayBuffer(buffer);
|
|
677
|
+
const format = detectFormat(arrayBuffer);
|
|
678
|
+
const parsed = await parse(arrayBuffer, { filePath: absPath });
|
|
679
|
+
if (!parsed.success) {
|
|
680
|
+
process.stderr.write(`[kordoc] FAIL: ${fileName} \u2014 ${parsed.error}
|
|
681
|
+
`);
|
|
682
|
+
process.exitCode = 1;
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
const r = redactMarkdown(parsed.markdown, { rules, maskChar: opts.maskChar });
|
|
686
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
687
|
+
for (const h of r.hits) byRule.set(h.rule, (byRule.get(h.rule) ?? 0) + 1);
|
|
688
|
+
const ruleSummary = [...byRule.entries()].map(([k, v]) => `${k} ${v}\uAC74`).join(", ") || "0\uAC74";
|
|
689
|
+
let outPath = null;
|
|
690
|
+
let patchNote = "";
|
|
691
|
+
if (!opts.dryRun && r.hits.length > 0) {
|
|
692
|
+
const patchable = format === "hwpx" || format === "hwp";
|
|
693
|
+
if (patchable) {
|
|
694
|
+
const original = new Uint8Array(arrayBuffer);
|
|
695
|
+
const result = format === "hwp" ? await patchHwp(original, r.text) : await patchHwpx(original, r.text);
|
|
696
|
+
if (!result.success || !result.data) {
|
|
697
|
+
process.stderr.write(`[kordoc] \uD328\uCE58 \uC2E4\uD328: ${fileName} \u2014 ${result.error ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}
|
|
698
|
+
`);
|
|
699
|
+
process.exitCode = 1;
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
const ext = format === "hwp" ? ".hwp" : ".hwpx";
|
|
703
|
+
outPath = resolve(
|
|
704
|
+
output && files.length === 1 ? output : resolve(opts.outDir ?? dirname(absPath), fileName.replace(/\.[^.]+$/, "") + ".redacted" + ext)
|
|
705
|
+
);
|
|
706
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
707
|
+
writeFileSync(outPath, result.data);
|
|
708
|
+
if (result.skipped.length > 0) {
|
|
709
|
+
patchNote = ` (\u26A0\uFE0F \uBBF8\uC801\uC6A9 ${result.skipped.length}\uAC74 \u2014 \uD574\uB2F9 \uC704\uCE58\uB294 \uC6D0\uBB38 \uC794\uC874, \uC218\uB3D9 \uD655\uC778 \uD544\uC694)`;
|
|
710
|
+
process.exitCode = 2;
|
|
711
|
+
}
|
|
712
|
+
} else {
|
|
713
|
+
outPath = resolve(
|
|
714
|
+
output && files.length === 1 ? output : resolve(opts.outDir ?? dirname(absPath), fileName.replace(/\.[^.]+$/, "") + ".redacted.md")
|
|
715
|
+
);
|
|
716
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
717
|
+
writeFileSync(outPath, r.text, "utf-8");
|
|
718
|
+
patchNote = ` (${format}\uB294 \uC11C\uC2DD \uBCF4\uC874 \uBBF8\uC9C0\uC6D0 \u2014 \uB9C8\uC2A4\uD0B9\uB41C \uB9C8\uD06C\uB2E4\uC6B4\uC73C\uB85C \uCD9C\uB825)`;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (opts.json) {
|
|
722
|
+
jsonReports.push({ file: absPath, format, rules, hits: r.hits, output: outPath });
|
|
723
|
+
}
|
|
724
|
+
if (!silent) {
|
|
725
|
+
process.stderr.write(`[kordoc] ${fileName}: ${ruleSummary}${outPath ? ` \u2192 ${outPath}` : opts.dryRun ? " (dry-run)" : ""}${patchNote}
|
|
726
|
+
`);
|
|
727
|
+
for (const h of r.hits) process.stderr.write(` - [${h.rule}] ${h.masked}
|
|
728
|
+
`);
|
|
729
|
+
}
|
|
730
|
+
} catch (err) {
|
|
731
|
+
process.stderr.write(`[kordoc] ERROR: ${fileName} \u2014 ${sanitizeError(err)}
|
|
732
|
+
`);
|
|
733
|
+
process.exitCode = 1;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (opts.json) process.stdout.write(JSON.stringify(files.length === 1 ? jsonReports[0] ?? null : jsonReports, null, 2) + "\n");
|
|
737
|
+
});
|
|
601
738
|
program.command("render <file>").description("\uB808\uC774\uC544\uC6C3 \uBCF4\uC874 \uB80C\uB354 \u2014 \uD55C\uCEF4 \uC800\uC7A5 HWPX\uC758 \uC870\uD310 \uCE90\uC2DC\uB97C SVG\uB85C (\uC804\uCCB4 \uD398\uC774\uC9C0 \uC138\uB85C \uC2A4\uD0DD) \u2014 kordoc render \uBB38\uC11C.hwpx -o \uBB38\uC11C.svg").option("-o, --output <path>", "\uCD9C\uB825 SVG \uACBD\uB85C (\uAE30\uBCF8: <\uC785\uB825>.svg)").option("--highlight <terms>", "\uAC80\uC0C9\uC5B4 \uD615\uAD11\uD39C (\uC27C\uD45C \uAD6C\uBD84)").option("--reflow", "\uC870\uD310 \uCE90\uC2DC \uC5C6\uB294 HWPX\uB3C4 \uC21C\uC218 TS \uC870\uD310\uC73C\uB85C \uB80C\uB354 (markdownToHwpx \uC0B0\uCD9C\uBB3C\xB7\uD3B8\uC9D1\uBCF8)").option("--reflow-mode <mode>", "reflow \uC904\uBC14\uAFC8 \uBAA8\uB4DC: keep(\uC5B4\uC808) | charAll(\uAE00\uC790)", "keep").option("--silent", "\uC9C4\uD589 \uBA54\uC2DC\uC9C0 \uC228\uAE30\uAE30").action(async (file, opts) => {
|
|
602
739
|
try {
|
|
603
740
|
const rootOpts = program.opts();
|
|
604
741
|
const output = opts.output ?? rootOpts.output;
|
|
605
742
|
const silent = opts.silent ?? rootOpts.silent;
|
|
606
|
-
const { renderHwpxToSvg } = await import("./render-
|
|
743
|
+
const { renderHwpxToSvg } = await import("./render-4MG5QUDG.js");
|
|
607
744
|
const absPath = resolve(file);
|
|
608
745
|
const buffer = readFileSync(absPath);
|
|
609
746
|
const highlights = opts.highlight ? String(opts.highlight).split(",") : void 0;
|
|
@@ -625,7 +762,7 @@ program.command("render <file>").description("\uB808\uC774\uC544\uC6C3 \uBCF4\uC
|
|
|
625
762
|
});
|
|
626
763
|
program.command("render-worker").description("persistent \uB80C\uB354 \uC6CC\uCEE4 \u2014 stdin NDJSON \uC694\uCCAD \u2192 \uC870\uD310 SVG \uD30C\uC77C \uCD9C\uB825 (\uD504\uB85C\uC138\uC2A4 \uC720\uC9C0, \uCF5C\uB4DC\uC2A4\uD0C0\uD2B8 \uC81C\uAC70)").action(async () => {
|
|
627
764
|
const { createInterface } = await import("readline");
|
|
628
|
-
const { renderHwpxToSvg } = await import("./render-
|
|
765
|
+
const { renderHwpxToSvg } = await import("./render-4MG5QUDG.js");
|
|
629
766
|
const rl = createInterface({ input: process.stdin });
|
|
630
767
|
const write = (o) => void process.stdout.write(JSON.stringify(o) + "\n");
|
|
631
768
|
write({ ready: true, version: VERSION });
|
|
@@ -666,15 +803,16 @@ program.command("render-worker").description("persistent \uB80C\uB354 \uC6CC\uCE
|
|
|
666
803
|
}
|
|
667
804
|
});
|
|
668
805
|
program.command("mcp").description("MCP \uC11C\uBC84 \uC2E4\uD589 (Claude / Cursor / Windsurf \uC5F0\uB3D9)").action(async () => {
|
|
669
|
-
await import("./mcp.js");
|
|
806
|
+
const { startMcpServer } = await import("./mcp.js");
|
|
807
|
+
await startMcpServer();
|
|
670
808
|
});
|
|
671
809
|
program.command("setup").description("\uB300\uD654\uD615 \uC124\uCE58 \uB9C8\uBC95\uC0AC \u2014 AI \uD074\uB77C\uC774\uC5B8\uD2B8 \uC790\uB3D9 \uB4F1\uB85D (Mac/Win/Linux)").action(async () => {
|
|
672
|
-
const { runSetup } = await import("./setup-
|
|
810
|
+
const { runSetup } = await import("./setup-Q2PRE7UA.js");
|
|
673
811
|
await runSetup();
|
|
674
812
|
});
|
|
675
813
|
program.command("check-formula-models").description("PDF \uC218\uC2DD OCR \uBAA8\uB378(MFD + MFR + tokenizer, ~155MB) \uC0C1\uD0DC \uD655\uC778 \u2014 \uC5C6\uAC70\uB098 SHA \uBD88\uC77C\uCE58\uBA74 \uB2E4\uC6B4\uB85C\uB4DC").option("--status-only", "\uC0C1\uD0DC\uB9CC JSON \uC73C\uB85C \uCD9C\uB825 (\uB2E4\uC6B4\uB85C\uB4DC \uC548 \uD568)").action(async (opts) => {
|
|
676
814
|
try {
|
|
677
|
-
const { getFormulaModelStatus, ensureFormulaModels, getFormulaModelsDir } = await import("./formula-
|
|
815
|
+
const { getFormulaModelStatus, ensureFormulaModels, getFormulaModelsDir } = await import("./formula-SXC6LRRC.js");
|
|
678
816
|
const dir = getFormulaModelsDir();
|
|
679
817
|
if (opts.statusOnly) {
|
|
680
818
|
const status = await getFormulaModelStatus();
|
|
@@ -726,5 +864,59 @@ program.command("check-formula-models").description("PDF \uC218\uC2DD OCR \uBAA8
|
|
|
726
864
|
process.exit(1);
|
|
727
865
|
}
|
|
728
866
|
});
|
|
867
|
+
program.command("check-ocr-models").description("\uD14D\uC2A4\uD2B8 OCR \uBAA8\uB378(PP-OCRv5 korean det+rec, ~18MB) \uC0C1\uD0DC \uD655\uC778 \u2014 \uC5C6\uAC70\uB098 SHA \uBD88\uC77C\uCE58\uBA74 \uB2E4\uC6B4\uB85C\uB4DC").option("--status-only", "\uC0C1\uD0DC\uB9CC JSON \uC73C\uB85C \uCD9C\uB825 (\uB2E4\uC6B4\uB85C\uB4DC \uC548 \uD568)").action(async (opts) => {
|
|
868
|
+
try {
|
|
869
|
+
const { getOcrModelStatus, ensureOcrModels, getOcrModelsDir } = await import("./models-KA4GLQK5.js");
|
|
870
|
+
const dir = getOcrModelsDir();
|
|
871
|
+
if (opts.statusOnly) {
|
|
872
|
+
const status = await getOcrModelStatus();
|
|
873
|
+
process.stdout.write(
|
|
874
|
+
JSON.stringify(
|
|
875
|
+
{
|
|
876
|
+
modelsDir: dir,
|
|
877
|
+
allReady: status.every((s) => s.verified),
|
|
878
|
+
models: status.map((s) => ({
|
|
879
|
+
name: s.spec.name,
|
|
880
|
+
filename: s.spec.filename,
|
|
881
|
+
sizeMb: s.spec.sizeMb,
|
|
882
|
+
exists: s.exists,
|
|
883
|
+
verified: s.verified,
|
|
884
|
+
invalidReason: s.invalidReason,
|
|
885
|
+
path: s.localPath
|
|
886
|
+
}))
|
|
887
|
+
},
|
|
888
|
+
null,
|
|
889
|
+
2
|
|
890
|
+
) + "\n"
|
|
891
|
+
);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
process.stderr.write(`[kordoc-ocr] \uCE90\uC2DC \uB514\uB809\uD1A0\uB9AC: ${dir}
|
|
895
|
+
`);
|
|
896
|
+
await ensureOcrModels((p) => {
|
|
897
|
+
if (p.phase === "download" && p.total) {
|
|
898
|
+
const pct = Math.floor(p.downloaded / p.total * 100);
|
|
899
|
+
process.stderr.write(
|
|
900
|
+
`\r[kordoc-ocr] ${p.spec.name} ${pct}% (${(p.downloaded / 1024 / 1024).toFixed(1)}/${(p.total / 1024 / 1024).toFixed(1)}MB)`
|
|
901
|
+
);
|
|
902
|
+
if (p.downloaded >= p.total) process.stderr.write("\n");
|
|
903
|
+
} else if (p.phase === "verify") {
|
|
904
|
+
process.stderr.write(`[kordoc-ocr] ${p.spec.name} SHA-256 \uAC80\uC99D \uC911...
|
|
905
|
+
`);
|
|
906
|
+
} else if (p.phase === "done") {
|
|
907
|
+
process.stderr.write(`[kordoc-ocr] ${p.spec.name} \uC900\uBE44 \uC644\uB8CC
|
|
908
|
+
`);
|
|
909
|
+
} else if (p.phase === "skip") {
|
|
910
|
+
process.stderr.write(`[kordoc-ocr] ${p.spec.name} \uC774\uBBF8 \uC874\uC7AC (skip)
|
|
911
|
+
`);
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
process.stdout.write("ok\n");
|
|
915
|
+
} catch (err) {
|
|
916
|
+
process.stderr.write(`[kordoc] OCR \uBAA8\uB378 \uC900\uBE44 \uC2E4\uD328: ${sanitizeError(err)}
|
|
917
|
+
`);
|
|
918
|
+
process.exit(1);
|
|
919
|
+
}
|
|
920
|
+
});
|
|
729
921
|
program.parse();
|
|
730
922
|
//# sourceMappingURL=cli.js.map
|