모두 파싱해버리겠다 — Parse them all.
Korea's document hell is second to none. Built by a civil servant who survived seven years in it.
HWP 3.x/5.x, HWPX, HWPML, PDF, XLS, XLSX, DOCX — parse, compare, analyze, and generate every document format Korean government offices throw at you.

macOS / Linux / Windows. All you need is Node.js 18+.
npx -y kordoc setup
An interactive wizard:
1. Pick your AI client (Claude Desktop / Cursor / Claude Code / Windsurf / VS Code / Gemini CLI / Zed / Antigravity — installed ones show [detected])
2. Patches the config file automatically → restart the client
Windows gets automatic cmd /c npx wrapping. No manual JSON editing. After restart, 11 document tools (parse_document, parse_table, fill_form, patch_document, generate_document, place_seal, …) are live.
CLI-only usage needs no install at all:
npx kordoc <file>. See CLI below.If you hit
MODULE_NOT_FOUND/Cannot find module ...\dist\cli.js: a broken global install is lingering. Fix with:powershell npm uninstall -g kordoc npx -y kordoc@latest setupIf Windows PowerShell blocks
npx.ps1(PSSecurityException): that's PowerShell's default policy blocking unsigned.ps1scripts (not kordoc). Either run the same command in cmd instead, or relax the policy once from an admin PowerShell:Set-ExecutionPolicy -Scope CurrentUser RemoteSigned.
Prefer a skill (SKILL.md) over MCP registration:
/plugin marketplace add chrisryugj/kordoc
/plugin install kordoc@kordoc
The kordoc skill auto-activates on .hwp/.hwpx mentions and Korean official-document generation/form-filling requests (it calls the npx -y kordoc@^3 CLI internally — no separate install).
Beyond plain text extraction, kordoc automates the entire lifecycle of Korean official documents.
HWP3 (legacy), HWP (5.x), HWPX, HWPML, PDF, XLS, XLSX, and DOCX to Markdown instantly — the ideal shape for LLMs to read and reason about.HWPX. No more copy-paste drudgery.patchHwpx (HWPX) / patchHwp (HWP 5.x binary) — only the changed paragraph/cell text is swapped in place, without touching a single byte of the original formatting. Row insertion/deletion inherits neighboring-row formatting (v3.7); filling originally-empty HWP 5.x cells works too (v3.8).kordoc seal).Claude, Cursor, and friends call kordoc directly to read and produce documents.kordoc seal — finds anchors like "(인)"/"서명 또는 인" and places the stamp PNG as a float in front of text without growing tables/pages (MCP place_seal included). Nested tables, text boxes, and tab/multi-line paragraphs are approximate and reported via warnings — verify in Hancom and fine-tune with --dx/--dy (dx_mm/dy_mm)./plugin marketplace add chrisryugj/kordoc → the kordoc skill auto-activates for .hwp/.hwpx/official-document requests.require_unique), CLI fill -o output, and other "success message, silently wrong output" bugs.<신 설> ("newly inserted") notation inside old-vs-new clause comparison tables for a text box — a 30-page amendment comparison table is restored as one intact table instead of being shredded into paragraphs.Version highlights v3.0 – v3.15 (click — full details in CHANGELOG and the Korean README)
renderHwpxToSvg(buf, { reflow: true }), line-break engine measured 98% match), drawing-shape SVG render, persistent render-worker (stdin NDJSON).pageCount), search-term highlighting (--highlight), line-boundary alignment for control-heavy paragraphs, image-crop misread fix.\frac, \sqrt, scripts, Greek, integrals/limits, matrices), equation input guards, statute roundtrip integrity gate.patchHwpx (formatting inherited from adjacent rows), form-fill accuracy on colspan labels and nested tables, honest partial-application reporting.autoFit), HTML table generation (colspan/rowspan/nested), multi-value fill, tamper-warning fix.generate_document.markdownToHwpx(md, { gongmun }): 8-level Korean item numbering (1. 가. 1) 가) (1) (가) ① ㉮), hanging indents, official margins, presets (official/report/plan/notice/minutes).HwpxSession incremental block-patch API for editors, extractFormSchema (field types/required/empty), CJS build fix.patchHwp: format-preserving patch for HWP 5.x binaries (sector-level container surgery — byte-identical outside the edit).patchHwpx lossless roundtrip + parser leap on a 324-document government corpus: HWPX text 99.998%, table structure 100%, PDF coverage 99.16%.npm install kordoc
# Optional — only if you parse PDFs
npm install pdfjs-dist
import { parse } from "kordoc"
import { readFileSync } from "fs"
const buffer = readFileSync("business-plan.hwpx")
const result = await parse(buffer.buffer)
if (result.success) {
console.log(result.markdown) // markdown text
console.log(result.blocks) // IRBlock[] structured data
console.log(result.metadata) // { title, author, createdAt, ... }
}
import { compare } from "kordoc"
const diff = await compare(oldBuffer, newBuffer)
// diff.stats → { added: 3, removed: 1, modified: 5, unchanged: 42 }
// diff.diffs → BlockDiff[] (tables include cell-level diffs)
Cross-format comparison (HWP vs HWPX) works too.
import { parse, extractFormFields } from "kordoc"
const result = await parse(buffer)
if (result.success) {
const form = extractFormFields(result.blocks)
// form.fields → [{ label: "성명", value: "홍길동", row: 0, col: 0 }, ...]
// form.confidence → 0.85
}
import { fillForm } from "kordoc"
import { readFileSync, writeFileSync } from "fs"
const template = readFileSync("application.hwpx")
// HWPX format-preserving mode — fonts, sizes, alignment 100% intact
const result = await fillForm(template.buffer, {
성명: "홍길동",
주민등록번호: "900101-1234567",
주소: "서울특별시 광진구 능동로 120",
}, { format: "hwpx-preserve" })
writeFileSync("application_filled.hwpx", Buffer.from(result.buffer!))
// result.filled → [{ label: "성명", value: "홍길동" }, ...]
// result.unmatched → keys that failed to match
import { markdownToHwpx } from "kordoc"
const hwpxBuffer = await markdownToHwpx("# Title\n\nBody text\n\n| Name | Rank |\n| --- | --- |\n| 홍길동 | 과장 |")
writeFileSync("out.hwpx", Buffer.from(hwpxBuffer))
// Display math blocks become native HWPX equations (<hp:equation>).
// Supported: a limited LaTeX-like subset — \frac, \sqrt, sub/superscripts,
// Greek, integrals/limits, arrows, relations, matrix family.
const withEquation = await markdownToHwpx("Pythagoras\n\n$$a^2 + b^2 = c^2$$")
// Official-document mode — 8-level Korean item numbering + hanging indent
// + official margins/serif defaults
const gongmun = await markdownToHwpx("1. 추진배경\n - 세부 항목\n2. 추진계획", {
gongmun: { preset: "보고서" }, // official | report | plan | notice | minutes
})
From the CLI: kordoc generate report.md -o report.hwpx --preset 보고서
Draws the typesetting cache Hancom stores in HWPX (line coordinates, cell grids, object anchors) as absolutely-positioned SVG. Fast (no typesetting engine needed) and works on servers without Hancom. Multi-page vertical stack, search-term highlighting, and drawing shapes are supported (v3.14–15). Files without a cache (markdownToHwpx output, AI-generated or edited files) are typeset directly by the pure-TS reflow engine with reflow: true (v3.15). Equation objects are not rendered yet.
import { renderHwpxToSvg } from "kordoc"
const r = await renderHwpxToSvg(readFileSync("approval.hwpx"), { highlights: ["예산"] })
writeFileSync("approval.svg", r.svg)
// r.width/r.height (pt), r.pageCount, r.stats { texts, images, tables }, r.warnings
const g = await renderHwpxToSvg(generatedHwpx, { reflow: true }) // cache-less files
From the CLI: kordoc render approval.hwpx -o approval.svg (--reflow, --highlight 예산,집행) — for continuous rendering use kordoc render-worker (stdin NDJSON).
const result = await parse(buffer, { pages: "1-3" }) // pages 1–3 only
const result = await parse(buffer, { pages: [1, 5, 10] }) // specific pages
const result = await parse(buffer, {
ocr: async (pageImage, pageNumber, mimeType) => {
return await myOcrService.recognize(pageImage)
}
})
PDFs often have a text layer with broken ToUnicode/CMap or control characters mixed in. parsePdf returns per-page quality signals.
const r = await parsePdf(buffer)
if (r.success && r.qualitySummary?.needsOcr) {
// route to your OCR queue (kordoc ships no built-in OCR)
await routeToOcr(buffer, r.qualitySummary.ocrCandidatePages)
}
for (const p of r.pageQuality ?? []) {
if (p.needsOcr) console.log(`p${p.page} needs review: ${p.ocrReason}`)
}
Signal keys: textChars, hangulRatio, controlCharRatio, replacementCharRatio, puaRatio / needsOcr (page & document level) / ocrReason (low_text | high_pua | high_control | high_replacement).
```b
$ claude mcp add kordoc \
-- python -m otcore.mcp_server <graph>