kordoc 1.7.2 → 1.9.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 +36 -15
- package/dist/chunk-AHW56LNX.js +93 -0
- package/dist/chunk-AHW56LNX.js.map +1 -0
- package/dist/{chunk-NJ3R7LNR.js → chunk-MDRW3HYC.js} +1165 -234
- package/dist/chunk-MDRW3HYC.js.map +1 -0
- package/dist/chunk-MOL7MDBG.js +35 -0
- package/dist/chunk-MOL7MDBG.js.map +1 -0
- package/dist/cli.js +11 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1253 -195
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -5
- package/dist/index.d.ts +17 -5
- package/dist/index.js +1248 -194
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +10 -7
- package/dist/mcp.js.map +1 -1
- package/dist/page-range-737B4EZW.js +8 -0
- package/dist/page-range-737B4EZW.js.map +1 -0
- package/dist/provider-A4FHJSID.js +0 -0
- package/dist/utils-VU6Z7HNR.js +22 -0
- package/dist/utils-VU6Z7HNR.js.map +1 -0
- package/dist/{watch-AKTZTPVF.js → watch-5IOZWFDD.js} +13 -5
- package/dist/watch-5IOZWFDD.js.map +1 -0
- package/package.json +77 -75
- package/dist/chunk-NJ3R7LNR.js.map +0 -1
- package/dist/watch-AKTZTPVF.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
**모두 파싱해버리겠다.**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/kordoc)
|
|
6
6
|
[](https://github.com/chrisryugj/kordoc/blob/main/LICENSE)
|
|
7
7
|
|
|
8
8
|
> *대한민국에서 둘째가라면 서러울 문서지옥. 거기서 7년 버틴 공무원이 만들었습니다.*
|
|
9
9
|
|
|
10
|
-
HWP, HWPX, PDF — 관공서에서 쏟아지는 모든 문서를 파싱하고, 비교하고, 분석하고, 생성합니다.
|
|
10
|
+
HWP, HWPX, PDF, XLSX, DOCX — 관공서에서 쏟아지는 모든 문서를 파싱하고, 비교하고, 분석하고, 생성합니다.
|
|
11
11
|
|
|
12
12
|
[English](./README-EN.md)
|
|
13
13
|
|
|
@@ -19,7 +19,7 @@ HWP, HWPX, PDF — 관공서에서 쏟아지는 모든 문서를 파싱하고,
|
|
|
19
19
|
|
|
20
20
|
단순한 텍스트 추출을 넘어, **공문서 처리를 위한 모든 과정**을 자동화합니다.
|
|
21
21
|
|
|
22
|
-
* **📄 어떤 문서든 마크다운으로**: `HWP`, `HWPX`, `PDF` 파일을 즉시 `Markdown`으로 변환합니다. AI(LLM)가 문서를 읽고 분석하기 가장 좋은 상태로 만들어줍니다.
|
|
22
|
+
* **📄 어떤 문서든 마크다운으로**: `HWP`, `HWPX`, `PDF`, `XLSX`, `DOCX` 파일을 즉시 `Markdown`으로 변환합니다. AI(LLM)가 문서를 읽고 분석하기 가장 좋은 상태로 만들어줍니다.
|
|
23
23
|
* **📊 복잡한 표(Table) 완벽 재현**: 선이 없는 PDF나 복잡하게 병합된 HWP 표도 구조를 분석하여 정확한 마크다운 테이블로 복원합니다.
|
|
24
24
|
* **🔍 신구대조표 자동 생성**: 두 문서의 차이점을 분석하여 무엇이 바뀌었는지 한눈에 보여줍니다. (HWP와 HWPX 간의 비교도 가능!)
|
|
25
25
|
* **📝 마크다운을 다시 HWPX로**: AI가 작성한 내용을 다시 보고서 양식(`HWPX`)으로 되돌려줍니다. 이제 복사-붙여넣기 노가다에서 해방되세요.
|
|
@@ -27,16 +27,25 @@ HWP, HWPX, PDF — 관공서에서 쏟아지는 모든 문서를 파싱하고,
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
## v1.
|
|
30
|
+
## v1.8.0 변경사항
|
|
31
31
|
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
- **XLSX 파서 추가** — Excel 스프레드시트 파싱. 공유 문자열, 병합 셀, 다중 시트 지원. 시트별 heading + table 블록 생성.
|
|
33
|
+
- **DOCX 파서 추가** — Word 문서 파싱. 스타일 기반 heading, 번호 매기기(리스트), 각주, 하이퍼링크, 이미지 추출, vMerge/gridSpan 테이블 병합.
|
|
34
|
+
- **파싱 품질 대폭 개선** — PDF/HWPX/HWP5/XLSX 전 포맷 품질 점수 73→93점.
|
|
35
|
+
- **프로덕션 리뷰 17건 수정** — CLI `--no-header-footer` 플래그 반전 버그, MCP XLSX/DOCX 확장자 허용, ZIP bomb 보호 공유 유틸화, href XSS 살균 강화, PDF timeout 타이머 정리, HWP5 BinData O(n) 최적화, cluster indexOf O(n²)→O(n), SSRF IPv6 차단 등.
|
|
36
|
+
|
|
37
|
+
<details>
|
|
38
|
+
<summary>v1.7.x 변경사항</summary>
|
|
39
|
+
|
|
40
|
+
- **이미지 추출 (HWP/HWPX)** — ZIP 엔트리와 HWP5 BinData 스트림에서 바이너리 이미지 추출.
|
|
41
|
+
- **부분 파싱 (Graceful Degradation)** — 개별 페이지 실패가 전체 파싱을 중단하지 않음.
|
|
42
|
+
- **진행률 콜백** — `onProgress` 콜백. CLI에서 `[3/15 pages]` 형태 표시.
|
|
43
|
+
- **파일 경로 직접 입력** — `parse("path/to/file.hwp")` 문자열 오버로드.
|
|
44
|
+
- **PDF 머리글/바닥글 필터링** — `removeHeaderFooter` 옵션.
|
|
45
|
+
- **보안 강화** — ZIP bomb 추적, SSRF 방지, XSS 방어, 널바이트 감지, PDF 타임아웃.
|
|
46
|
+
- **pdfjs-dist v5 호환** — constructPath 연산자 형식 변경 대응.
|
|
47
|
+
|
|
48
|
+
</details>
|
|
40
49
|
|
|
41
50
|
<details>
|
|
42
51
|
<summary>v1.6.1 수정사항</summary>
|
|
@@ -194,7 +203,7 @@ npx kordoc watch ./문서 --webhook https://api/hook # 웹훅 알림
|
|
|
194
203
|
|
|
195
204
|
| 도구 | 설명 |
|
|
196
205
|
|------|------|
|
|
197
|
-
| `parse_document` | HWP/HWPX/PDF → 마크다운 (메타데이터 포함) |
|
|
206
|
+
| `parse_document` | HWP/HWPX/PDF/XLSX/DOCX → 마크다운 (메타데이터 포함) |
|
|
198
207
|
| `detect_format` | 매직 바이트로 포맷 감지 |
|
|
199
208
|
| `parse_metadata` | 메타데이터만 빠르게 추출 |
|
|
200
209
|
| `parse_pages` | 특정 페이지 범위만 파싱 |
|
|
@@ -212,7 +221,9 @@ npx kordoc watch ./문서 --webhook https://api/hook # 웹훅 알림
|
|
|
212
221
|
| `parseHwpx(buffer, options?)` | HWPX 전용 |
|
|
213
222
|
| `parseHwp(buffer, options?)` | HWP 5.x 전용 |
|
|
214
223
|
| `parsePdf(buffer, options?)` | PDF 전용 |
|
|
215
|
-
| `
|
|
224
|
+
| `parseXlsx(buffer, options?)` | XLSX 전용 |
|
|
225
|
+
| `parseDocx(buffer, options?)` | DOCX 전용 |
|
|
226
|
+
| `detectFormat(buffer)` | `"hwpx" \| "hwp" \| "pdf" \| "xlsx" \| "docx" \| "unknown"` |
|
|
216
227
|
|
|
217
228
|
### 고급 함수
|
|
218
229
|
|
|
@@ -242,7 +253,9 @@ import type {
|
|
|
242
253
|
|------|------|------|
|
|
243
254
|
| **HWPX** (한컴 2020+) | ZIP + XML DOM | 매니페스트, 중첩 테이블, 병합 셀, 손상 ZIP 복구 |
|
|
244
255
|
| **HWP 5.x** (한컴 레거시) | OLE2 + CFB | 21종 제어문자, zlib 압축 해제, DRM 감지, colAddr 기반 셀 배치 |
|
|
245
|
-
| **PDF** | pdfjs-dist |
|
|
256
|
+
| **PDF** | pdfjs-dist | 선 기반 테이블, XY-Cut 읽기 순서, 헤딩 감지, OCR |
|
|
257
|
+
| **XLSX** (Excel) | ZIP + XML DOM | 공유 문자열, 병합 셀, 다중 시트, 수식 표시 |
|
|
258
|
+
| **DOCX** (Word) | ZIP + XML DOM | 스타일 heading, 번호 매기기, 각주, 이미지 추출 |
|
|
246
259
|
|
|
247
260
|
## 보안
|
|
248
261
|
|
|
@@ -256,3 +269,11 @@ import type {
|
|
|
256
269
|
## 라이선스
|
|
257
270
|
|
|
258
271
|
[MIT](./LICENSE)
|
|
272
|
+
|
|
273
|
+
이 프로젝트는 아래 오픈소스를 포함합니다:
|
|
274
|
+
- **OpenDataLoader PDF** (Apache 2.0, Hancom Inc.) — PDF 테이블 감지 알고리즘
|
|
275
|
+
- **cfb** (Apache 2.0, SheetJS) — HWP5 OLE2 컨테이너 파싱
|
|
276
|
+
- **pdfjs-dist** (Apache 2.0, Mozilla) — PDF 텍스트 추출
|
|
277
|
+
- **JSZip** (MIT, Stuart Knightley 외) — ZIP 기반 포맷 파싱
|
|
278
|
+
|
|
279
|
+
자세한 내용은 [NOTICE](./NOTICE) 파일을 참조하세요.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils.ts
|
|
4
|
+
var VERSION = true ? "1.9.0" : "0.0.0-dev";
|
|
5
|
+
function toArrayBuffer(buf) {
|
|
6
|
+
if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
|
|
7
|
+
return buf.buffer;
|
|
8
|
+
}
|
|
9
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
10
|
+
}
|
|
11
|
+
var KordocError = class extends Error {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "KordocError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function sanitizeError(err) {
|
|
18
|
+
if (err instanceof KordocError) return err.message;
|
|
19
|
+
return "\uBB38\uC11C \uCC98\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4";
|
|
20
|
+
}
|
|
21
|
+
function isPathTraversal(name) {
|
|
22
|
+
if (name.includes("\0")) return true;
|
|
23
|
+
const normalized = name.replace(/\\/g, "/");
|
|
24
|
+
return normalized.includes("..") || normalized.startsWith("/") || /^[A-Za-z]:/.test(normalized);
|
|
25
|
+
}
|
|
26
|
+
function precheckZipSize(buffer, maxUncompressedSize = 100 * 1024 * 1024, maxEntries = 500) {
|
|
27
|
+
try {
|
|
28
|
+
const data = new DataView(buffer);
|
|
29
|
+
const len = buffer.byteLength;
|
|
30
|
+
let eocdOffset = -1;
|
|
31
|
+
for (let i = len - 22; i >= Math.max(0, len - 65557); i--) {
|
|
32
|
+
if (data.getUint32(i, true) === 101010256) {
|
|
33
|
+
eocdOffset = i;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (eocdOffset < 0) return { totalUncompressed: 0, entryCount: 0 };
|
|
38
|
+
const entryCount = data.getUint16(eocdOffset + 10, true);
|
|
39
|
+
if (entryCount > maxEntries) {
|
|
40
|
+
throw new KordocError(`ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC: ${entryCount} (\uCD5C\uB300 ${maxEntries})`);
|
|
41
|
+
}
|
|
42
|
+
const cdSize = data.getUint32(eocdOffset + 12, true);
|
|
43
|
+
const cdOffset = data.getUint32(eocdOffset + 16, true);
|
|
44
|
+
if (cdOffset + cdSize > len) return { totalUncompressed: 0, entryCount };
|
|
45
|
+
let totalUncompressed = 0;
|
|
46
|
+
let pos = cdOffset;
|
|
47
|
+
for (let i = 0; i < entryCount && pos + 46 <= cdOffset + cdSize; i++) {
|
|
48
|
+
if (data.getUint32(pos, true) !== 33639248) break;
|
|
49
|
+
totalUncompressed += data.getUint32(pos + 24, true);
|
|
50
|
+
const nameLen = data.getUint16(pos + 28, true);
|
|
51
|
+
const extraLen = data.getUint16(pos + 30, true);
|
|
52
|
+
const commentLen = data.getUint16(pos + 32, true);
|
|
53
|
+
pos += 46 + nameLen + extraLen + commentLen;
|
|
54
|
+
}
|
|
55
|
+
if (totalUncompressed > maxUncompressedSize) {
|
|
56
|
+
throw new KordocError(`ZIP \uBE44\uC555\uCD95 \uD06C\uAE30 \uCD08\uACFC: ${(totalUncompressed / 1024 / 1024).toFixed(1)}MB (\uCD5C\uB300 ${maxUncompressedSize / 1024 / 1024}MB)`);
|
|
57
|
+
}
|
|
58
|
+
return { totalUncompressed, entryCount };
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (err instanceof KordocError) throw err;
|
|
61
|
+
return { totalUncompressed: 0, entryCount: 0 };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
var SAFE_HREF_RE = /^(?:https?:|mailto:|tel:|#)/i;
|
|
65
|
+
function sanitizeHref(href) {
|
|
66
|
+
const trimmed = href.trim();
|
|
67
|
+
if (!trimmed || !SAFE_HREF_RE.test(trimmed)) return null;
|
|
68
|
+
return trimmed;
|
|
69
|
+
}
|
|
70
|
+
function classifyError(err) {
|
|
71
|
+
if (!(err instanceof Error)) return "PARSE_ERROR";
|
|
72
|
+
const msg = err.message;
|
|
73
|
+
if (msg.includes("\uC554\uD638\uD654")) return "ENCRYPTED";
|
|
74
|
+
if (msg.includes("DRM")) return "DRM_PROTECTED";
|
|
75
|
+
if (msg.includes("ZIP bomb") || msg.includes("ZIP \uBE44\uC555\uCD95 \uD06C\uAE30 \uCD08\uACFC") || msg.includes("ZIP \uC5D4\uD2B8\uB9AC \uC218 \uCD08\uACFC")) return "ZIP_BOMB";
|
|
76
|
+
if (msg.includes("bomb") || msg.includes("\uD06C\uAE30 \uCD08\uACFC") || msg.includes("\uC555\uCD95 \uD574\uC81C")) return "DECOMPRESSION_BOMB";
|
|
77
|
+
if (msg.includes("\uC774\uBBF8\uC9C0 \uAE30\uBC18")) return "IMAGE_BASED_PDF";
|
|
78
|
+
if (msg.includes("\uC139\uC158") && (msg.includes("\uCC3E\uC744 \uC218 \uC5C6") || msg.includes("\uC5C6\uC74C"))) return "NO_SECTIONS";
|
|
79
|
+
if (msg.includes("\uC2DC\uADF8\uB2C8\uCC98") || msg.includes("\uBCF5\uAD6C\uD560 \uC218 \uC5C6")) return "CORRUPTED";
|
|
80
|
+
return "PARSE_ERROR";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
VERSION,
|
|
85
|
+
toArrayBuffer,
|
|
86
|
+
KordocError,
|
|
87
|
+
sanitizeError,
|
|
88
|
+
isPathTraversal,
|
|
89
|
+
precheckZipSize,
|
|
90
|
+
sanitizeHref,
|
|
91
|
+
classifyError
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=chunk-AHW56LNX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts"],"sourcesContent":["/** kordoc 공용 유틸리티 */\n\n/** 빌드 타임에 tsup define으로 주입되는 버전 */\ndeclare const __KORDOC_VERSION__: string\nexport const VERSION: string = typeof __KORDOC_VERSION__ !== \"undefined\" ? __KORDOC_VERSION__ : \"0.0.0-dev\"\n\n/**\n * Node.js Buffer → ArrayBuffer 변환\n * pool Buffer의 공유 ArrayBuffer 문제를 안전하게 처리.\n * offset=0이고 전체 ArrayBuffer를 차지하면 복사 없이 직접 반환.\n */\nexport function toArrayBuffer(buf: Buffer): ArrayBuffer {\n if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {\n return buf.buffer as ArrayBuffer\n }\n return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer\n}\n\n/**\n * kordoc 내부 에러 클래스 — 사용자에게 노출해도 안전한 메시지만 포함.\n * MCP 에러 정제에서 instanceof로 판별하여 allowlist 패턴 매칭 없이 안전하게 통과.\n */\nexport class KordocError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"KordocError\"\n }\n}\n\n/**\n * 에러 메시지 정제 — KordocError는 그대로, 나머지는 일반 메시지로 대체.\n * 파일시스템 경로, 스택 트레이스 등 내부 정보 노출 방지.\n */\nexport function sanitizeError(err: unknown): string {\n if (err instanceof KordocError) return err.message\n return \"문서 처리 중 오류가 발생했습니다\"\n}\n\n/**\n * ZIP 엔트리 경로의 경로 순회 여부 판별.\n * 백슬래시 정규화, .., 절대경로, Windows 드라이브 문자 모두 차단.\n */\nexport function isPathTraversal(name: string): boolean {\n if (name.includes(\"\\x00\")) return true\n const normalized = name.replace(/\\\\/g, \"/\")\n return normalized.includes(\"..\") || normalized.startsWith(\"/\") || /^[A-Za-z]:/.test(normalized)\n}\n\n// ─── ZIP 안전 로딩 (ZIP bomb 방지) ────────────────────\n\n/**\n * ZIP bomb 사전 검사 — Central Directory에서 비압축 합계와 엔트리 수 확인.\n * HWPX/XLSX/DOCX 등 모든 ZIP 기반 포맷에서 공통 사용.\n */\nexport function precheckZipSize(\n buffer: ArrayBuffer,\n maxUncompressedSize = 100 * 1024 * 1024,\n maxEntries = 500,\n): { totalUncompressed: number; entryCount: number } {\n try {\n const data = new DataView(buffer)\n const len = buffer.byteLength\n // EOCD 시그니처 역방향 스캔\n let eocdOffset = -1\n for (let i = len - 22; i >= Math.max(0, len - 65557); i--) {\n if (data.getUint32(i, true) === 0x06054b50) { eocdOffset = i; break }\n }\n if (eocdOffset < 0) return { totalUncompressed: 0, entryCount: 0 }\n\n const entryCount = data.getUint16(eocdOffset + 10, true)\n if (entryCount > maxEntries) {\n throw new KordocError(`ZIP 엔트리 수 초과: ${entryCount} (최대 ${maxEntries})`)\n }\n\n const cdSize = data.getUint32(eocdOffset + 12, true)\n const cdOffset = data.getUint32(eocdOffset + 16, true)\n if (cdOffset + cdSize > len) return { totalUncompressed: 0, entryCount }\n\n let totalUncompressed = 0\n let pos = cdOffset\n for (let i = 0; i < entryCount && pos + 46 <= cdOffset + cdSize; i++) {\n if (data.getUint32(pos, true) !== 0x02014b50) break\n totalUncompressed += data.getUint32(pos + 24, true)\n const nameLen = data.getUint16(pos + 28, true)\n const extraLen = data.getUint16(pos + 30, true)\n const commentLen = data.getUint16(pos + 32, true)\n pos += 46 + nameLen + extraLen + commentLen\n }\n\n if (totalUncompressed > maxUncompressedSize) {\n throw new KordocError(`ZIP 비압축 크기 초과: ${(totalUncompressed / 1024 / 1024).toFixed(1)}MB (최대 ${maxUncompressedSize / 1024 / 1024}MB)`)\n }\n\n return { totalUncompressed, entryCount }\n } catch (err) {\n if (err instanceof KordocError) throw err\n return { totalUncompressed: 0, entryCount: 0 }\n }\n}\n\n/** 하이퍼링크 URL 살균 — javascript: 등 XSS 위험 스킴 차단 */\nconst SAFE_HREF_RE = /^(?:https?:|mailto:|tel:|#)/i\nexport function sanitizeHref(href: string): string | null {\n const trimmed = href.trim()\n if (!trimmed || !SAFE_HREF_RE.test(trimmed)) return null\n return trimmed\n}\n\n// ─── 에러 분류 ──────────────────────────────────────\n\nimport type { ErrorCode } from \"./types.js\"\n\n/** 에러를 구조화된 ErrorCode로 분류 — KordocError 메시지 패턴 매칭 */\nexport function classifyError(err: unknown): ErrorCode {\n if (!(err instanceof Error)) return \"PARSE_ERROR\"\n const msg = err.message\n if (msg.includes(\"암호화\")) return \"ENCRYPTED\"\n if (msg.includes(\"DRM\")) return \"DRM_PROTECTED\"\n if (msg.includes(\"ZIP bomb\") || msg.includes(\"ZIP 비압축 크기 초과\") || msg.includes(\"ZIP 엔트리 수 초과\")) return \"ZIP_BOMB\"\n if (msg.includes(\"bomb\") || msg.includes(\"크기 초과\") || msg.includes(\"압축 해제\")) return \"DECOMPRESSION_BOMB\"\n if (msg.includes(\"이미지 기반\")) return \"IMAGE_BASED_PDF\"\n if (msg.includes(\"섹션\") && (msg.includes(\"찾을 수 없\") || msg.includes(\"없음\"))) return \"NO_SECTIONS\"\n if (msg.includes(\"시그니처\") || msg.includes(\"복구할 수 없\")) return \"CORRUPTED\"\n return \"PARSE_ERROR\"\n}\n"],"mappings":"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","names":[]}
|