{"id":49030362,"url":"https://github.com/plateerlab/document-adapter","last_synced_at":"2026-04-21T11:01:01.615Z","repository":{"id":351472071,"uuid":"1210979516","full_name":"PlateerLab/document-adapter","owner":"PlateerLab","description":"LLM이 DOCX/PPTX/HWPX 문서를 직접 편집할 수 있게 해주는 통합 어댑터 + MCP 서버. Claude Desktop / Claude Code / Anthropic API Tool Use 호환. pip install document-adapter","archived":false,"fork":false,"pushed_at":"2026-04-16T22:58:37.000Z","size":418,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-19T09:37:02.604Z","etag":null,"topics":["ai-agent","anthropic","claude","claude-desktop","document-generation","docx","hancom","hwpx","korean","llm","mcp","mcp-server","model-context-protocol","office-automation","powerpoint","pptx","python","template-engine","tool-use","word"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/document-adapter/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PlateerLab.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-15T00:34:11.000Z","updated_at":"2026-04-16T22:59:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/PlateerLab/document-adapter","commit_stats":null,"previous_names":["plateerlab/document-adapter"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/PlateerLab/document-adapter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PlateerLab%2Fdocument-adapter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PlateerLab%2Fdocument-adapter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PlateerLab%2Fdocument-adapter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PlateerLab%2Fdocument-adapter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PlateerLab","download_url":"https://codeload.github.com/PlateerLab/document-adapter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PlateerLab%2Fdocument-adapter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32042293,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-20T00:18:06.643Z","status":"online","status_checked_at":"2026-04-20T02:00:06.527Z","response_time":94,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agent","anthropic","claude","claude-desktop","document-generation","docx","hancom","hwpx","korean","llm","mcp","mcp-server","model-context-protocol","office-automation","powerpoint","pptx","python","template-engine","tool-use","word"],"created_at":"2026-04-19T09:04:05.891Z","updated_at":"2026-04-20T10:01:10.567Z","avatar_url":"https://github.com/PlateerLab.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# document-adapter\n\n[![PyPI version](https://img.shields.io/pypi/v/document-adapter.svg)](https://pypi.org/project/document-adapter/)\n[![Python versions](https://img.shields.io/pypi/pyversions/document-adapter.svg)](https://pypi.org/project/document-adapter/)\n[![License](https://img.shields.io/pypi/l/document-adapter.svg)](https://github.com/PlateerLab/document-adapter/blob/main/LICENSE)\n[![MCP](https://img.shields.io/badge/MCP-compatible-blue.svg)](https://modelcontextprotocol.io)\n\n**LLM이 DOCX / PPTX / HWPX 문서를 직접 편집할 수 있게 해주는 통합 어댑터 + MCP 서버.**\n\n세 가지 오피스 포맷을 하나의 파이썬 인터페이스로 추상화하고, Claude Desktop / Claude Code / Anthropic API Tool Use에서 바로 호출할 수 있는 MCP 도구로 노출합니다. 양식 문서의 빈 셀을 자동으로 채우거나, 템플릿의 `{{key}}`를 치환하거나, 기존 표의 내용을 수정하는 작업을 LLM 에이전트가 수행할 수 있습니다.\n\n- 📦 PyPI: https://pypi.org/project/document-adapter/\n- 🔗 Repo: https://github.com/PlateerLab/document-adapter\n\n## 지원 포맷\n\n| 포맷 | 백엔드 | 템플릿 렌더 | 표 읽기 | 병합 셀 인지 | 중첩 테이블 | 셀 수정 | 행 추가 |\n|---|---|---|---|---|---|---|---|\n| `.docx` | `docxtpl` + `python-docx` | Jinja2 (`{%tr%}` loop 포함) | ✅ | ✅ | ✅ | ✅ | ✅ |\n| `.pptx` | `python-pptx` + 자체 lxml 확장 | `{{key}}` 치환 | ✅ (슬라이드 위치 포함) | ✅ | — (포맷 미지원) | ✅ | ✅ (v0.5+) |\n| `.hwpx` | 자체 `hwpx_core` (lxml + zipfile) | `{{key}}` 치환 | ✅ | ✅ | ✅ | ✅ | ✅ |\n\n- HWPX는 한컴오피스 설치가 **불필요**합니다 (macOS/Linux 서버에서 그대로 동작).\n- 구버전 `.hwp`(바이너리 포맷)는 지원하지 않습니다 — `.hwpx`로 변환 후 사용하세요.\n- 병합 셀: 3개 포맷 모두 preview에 `null` 슬롯 + `merges` 메타로 구조 노출. non-anchor 좌표에 쓰기는 `MergedCellWriteError`로 거부.\n- **셀 크기 메타 (v0.6+)**: `get_tables`는 `column_widths_cm` / `row_heights_cm`, `get_cell`은 `width_cm` / `height_cm` / `char_count`를 반환합니다. LLM이 좁은 셀(예: 1.7×0.7cm 배지)에 긴 텍스트를 넣어 오버플로 되는 것을 사전에 판단할 수 있습니다.\n\n## 라이선스 (상용 사용 가능)\n\n- 본 프로젝트: **MIT License**\n- 런타임 의존성(`python-docx`, `docxtpl`, `python-pptx`, `lxml`, `mcp`): 전부 **허용형 OSS** (MIT/BSD/Apache-2.0/LGPL-2.1). 상용·내부 서비스에 그대로 포함 가능.\n- v0.3 이하에서 사용했던 `python-hwpx` (Non-Commercial License) 는 v0.4.0부터 **dev 환경(테스트 fixture 생성) 전용**으로 이동. HWPX 편집은 자체 `hwpx_core` 모듈이 수행합니다.\n\n## 설치\n\n```bash\npip install document-adapter\n```\n\nClaude API 예시 스크립트까지 포함:\n\n```bash\npip install \"document-adapter[claude]\"\n```\n\n개발 환경에서 소스로 설치:\n\n```bash\ngit clone https://github.com/PlateerLab/document-adapter.git\ncd document-adapter\npip install -e \".[dev]\"\n```\n\nPython 3.10+ 필요.\n\n## 빠른 시작 — 파이썬 API\n\n```python\nfrom document_adapter import load\n\ndoc = load(\"report_template.docx\")\n\n# 1. 구조 파악\nschema = doc.get_schema()\nprint(schema.placeholders)   # ['author', 'date', 'title']\nprint(schema.tables)         # [TableSchema(index=0, rows=7, cols=2, ...), ...]\n\n# 2. 템플릿 렌더\ndoc.render_template({\n    \"title\": \"Q1 운영 리포트\",\n    \"author\": \"손성준\",\n    \"date\": \"2026-04-15\",\n})\ndoc.save(\"report_filled.docx\")\n\n# 3. 기존 양식 파일의 표 셀 수정\ndoc = load(\"checklist.docx\")\n\n# 빈 셀 값 교체\nold = doc.set_cell(table_index=1, row=1, col=1, value=\"○○전자\")\n\n# 라벨이 있는 셀 (\"성 명\")에 값 추가 → \"성 명  홍길동\"\ndoc.append_to_cell(table_index=2, row=0, col=0, value=\"홍길동\")\n\n# 셀 전체 텍스트 + 병합 메타 조회 (preview의 40자 잘림 없이)\ncell = doc.get_cell(table_index=1, row=3, col=2)\nprint(cell.text, cell.is_anchor, cell.span, cell.nested_table_indices)\n\n# DOCX/PPTX/HWPX 전부 행 추가 지원 (v0.5+)\ndoc.append_row(1, [\"새 항목\", \"값\"])\n\ndoc.save(\"checklist_filled.docx\")\ndoc.close()\n```\n\n### 라벨 기반 일괄 채우기 (v0.7+)\n\nLLM 이 좌표 `(table_index, row, col)` 를 직접 계산하지 않고 \"접수번호\", \"성명\" 같은 사람이 읽는 라벨 key-value 로 양식을 채울 수 있습니다.\n\n```python\ndoc = load(\"form.hwpx\")\nresult = doc.fill_form({\n    \"접수번호\": \"2026-0001\",\n    \"성 명\":   \"홍길동\",\n    \"주 소\":   \"서울시 강남구\",\n    \"금융회사\": \"국민은행\",\n})\n# → {\"filled\": [...], \"not_found\": [...], \"ambiguous\": [...]}\ndoc.save()\ndoc.close()\n```\n\n- `auto` (기본): 라벨 셀 오른쪽 → 아래 → 같은 셀 순으로 값 셀 탐색. 보수적이라 기존 값 있는 셀은 다른 라벨로 간주하고 skip.\n- `direction=\"right\"` 명시: 라벨 오른쪽 셀을 **덮어쓰기** (예시값 있는 PPTX 템플릿 등).\n- **Dot-path 섹션 지정**: 동일 라벨이 여러 섹션에 있으면 `\"피해자.금액\"`, `\"지급정지요청계좌.금액\"` 처럼 섹션 힌트 부여. `ambiguous` 반환 시 `hint` 필드에 예시 제공.\n- **팁**: 한 양식의 관련 라벨을 한 번에 dict 로 넘기면 라벨끼리 서로 보호되어 오염을 방지합니다.\n\n### 셀 크기 메타 (v0.6+)\n\n`get_tables()`가 `column_widths_cm` / `row_heights_cm` 를, `get_cell()`이 `width_cm` / `height_cm` / `char_count` 를 반환해 LLM이 **좁은 셀에 긴 텍스트를 넣어 오버플로 되는 것을 사전에 판단**할 수 있습니다.\n\n```python\ncell = doc.get_cell(table_index=0, row=0, col=0)\nprint(cell.width_cm, cell.char_count)  # 1.7cm, 4자 — 작은 배지\n```\n\nDOCX/PPTX는 EMU → cm, HWPX는 HU → cm 자동 환산 (1자리 반올림).\n\n확장자로 자동 분기되므로 `.pptx` / `.hwpx`도 동일한 API를 사용합니다.\n\n### 병합 셀 인지 동작 (v0.2+)\n\n```python\nschema = doc.get_schema()\nt = schema.tables[0]\n\n# preview는 logical grid. 병합된 non-anchor 슬롯은 None.\n# [['HEADER', None, None], ['A1', 'A2', 'A3']]\nprint(t.preview)\n\n# merges는 span\u003e1x1인 anchor 목록\n# [MergeInfo(anchor=(0,0), span=(1,3))]\nprint(t.merges)\n\n# non-anchor에 쓰기 시도하면 MergedCellWriteError (ValueError 서브클래스)\ntry:\n    doc.set_cell(0, 0, 2, \"X\")\nexcept ValueError as e:\n    print(e)  # \"cell (0,2) is part of a merged region anchored at (0,0)...\"\n\n# 의도적으로 앵커로 리디렉트하고 싶다면\ndoc.set_cell(0, 0, 2, \"X\", allow_merge_redirect=True)  # 경고 + 실제로 (0,0) 수정\n```\n\n## MCP 서버로 사용 — Claude Desktop / Claude Code\n\n### 실행\n\n```bash\npython -m document_adapter.mcp_server\n# 또는 설치 후\ndocument-adapter-mcp\n```\n\n### Claude Desktop 설정\n\n`~/Library/Application Support/Claude/claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"document-adapter\": {\n      \"command\": \"/absolute/path/to/venv/bin/python\",\n      \"args\": [\"-m\", \"document_adapter.mcp_server\"]\n    }\n  }\n}\n```\n\n재시작하면 Claude Desktop에서 아래 7개 도구를 사용할 수 있습니다.\n\n### Claude Code 설정\n\n```bash\nclaude mcp add document-adapter \\\n  /absolute/path/to/venv/bin/python -m document_adapter.mcp_server\n```\n\n## Anthropic API Tool Use로 사용\n\n`document_adapter.tools`가 Claude API의 tool schema 형식과 그대로 호환됩니다.\n\n```python\nimport anthropic\nfrom document_adapter.tools import TOOL_DEFINITIONS, call_tool\n\nclient = anthropic.Anthropic()\n\nresp = client.messages.create(\n    model=\"claude-opus-4-6\",\n    max_tokens=4096,\n    tools=[{\n        \"name\": t[\"name\"],\n        \"description\": t[\"description\"],\n        \"input_schema\": t[\"input_schema\"],\n    } for t in TOOL_DEFINITIONS],\n    messages=[{\n        \"role\": \"user\",\n        \"content\": \"report_template.docx의 표 구조를 확인하고 빈 셀을 적절히 채워줘\",\n    }],\n)\n\n# tool_use 블록을 받으면 call_tool(name, args)로 실행 후 결과 반환\n```\n\n전체 agent loop 예시는 [`examples/claude_api_example.py`](examples/claude_api_example.py) 참고.\n\n## 노출되는 도구\n\n| 도구 | 설명 |\n|---|---|\n| `inspect_document` | 문서 구조(placeholders, tables + `column_widths_cm`/`row_heights_cm`)를 JSON으로 반환. **항상 첫 호출로 사용** |\n| `render_template` | `{{key}}`를 context dict 값으로 치환해 새 파일 저장 |\n| `get_cell` | 셀 전체 텍스트 + 병합/중첩 메타 + `width_cm`/`height_cm`/`char_count` 반환 |\n| `set_cell` | 특정 표의 `(row, col)` 셀 값 교체 (병합 anchor만) |\n| `append_to_cell` | 기존 텍스트 뒤에 값 덧붙임 (라벨 유지용, 예: `\"성 명\"` → `\"성 명  홍길동\"`) |\n| `fill_form` (v0.7+) | **라벨 이름**으로 일괄 채우기. 좌표 계산 없이 `{\"접수번호\": \"...\", \"성명\": \"...\"}` dict. dot-path 섹션 해소 지원 |\n| `append_row` | 표 끝에 새 행 추가 (DOCX/PPTX/HWPX 전부 지원, v0.5+) |\n\n### `inspect_document` 반환 예시 (v0.2+)\n\n```json\n{\n  \"format\": \"hwpx\",\n  \"source\": \"/path/to/form.hwpx\",\n  \"placeholders\": [],\n  \"tables\": [\n    {\n      \"index\": 0,\n      \"rows\": 28,\n      \"cols\": 16,\n      \"location\": null,\n      \"parent_path\": null,\n      \"preview\": [\n        [\"포상금 지급신청서\", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],\n        [\"접수번호\", null, null, \"\", \"접수일자\", null, null, \"\", ...]\n      ],\n      \"merges\": [\n        {\"anchor\": [0, 0], \"span\": [1, 16]},\n        {\"anchor\": [1, 0], \"span\": [1, 3]}\n      ]\n    }\n  ]\n}\n```\n\nLLM은 이 preview를 보고 **\"빈 셀이 어디 있는지 / 어떤 값을 넣어야 하는지\"** 를 판단하여 `set_cell` / `append_to_cell`을 호출합니다. `null` 슬롯은 병합된 영역이며 `merges`의 anchor 좌표로만 쓸 수 있습니다.\n\n## 템플릿 작성 규칙\n\n### DOCX — Jinja2 전체 문법 사용 가능\n\n```\n{{ report_title }}\n작성자: {{ author }}\n\n{% for item in items %}- {{ item.name }}: {{ item.value }}\n{% endfor %}\n```\n\n**표 행 반복은 `{%tr for ... %}` / `{%tr endfor %}`를 각각 별도 행에 두어야 합니다.**\n같은 행에 두 태그를 넣으면 `\u003cw:tr\u003e` 전체가 `{% for %}`로 교체되어 `endfor`가 손실됩니다.\n\n```\n┌─────────────────────┬─────┬─────┐\n│ 항목                │ 목표 │ 실적 │    \u003c- 헤더\n├─────────────────────┼─────┼─────┤\n│ {%tr for r in rows %}          │    \u003c- for 행\n├─────────────────────┼─────┼─────┤\n│ {{ r.name }}        │ {{ r.target }} │ {{ r.actual }} │  \u003c- 반복 본문\n├─────────────────────┼─────┼─────┤\n│ {%tr endfor %}                 │    \u003c- endfor 행\n└─────────────────────┴─────┴─────┘\n```\n\n### PPTX / HWPX — 단순 `{{key}}` 치환만\n\nloop / if / filter는 지원하지 않습니다. PPTX는 placeholder가 여러 `run`으로 쪼개질 수 있어, 어댑터가 paragraph 전체 텍스트를 재조립한 뒤 첫 `run`에 다시 담는 방식으로 처리합니다 (서식 일부 손실 가능).\n\n## 내장된 버그 회피 / 백엔드 선택\n\n| 포맷 | 문제 | 어댑터의 처리 |\n|---|---|---|\n| HWPX | `python-hwpx` 가 Non-Commercial License → 상용 배포 블로커 | **v0.4.0 부터 자체 `hwpx_core` 모듈** (zipfile + lxml) 로 교체. 런타임에 `python-hwpx` 불필요. 테스트 fixture 생성에만 사용 (dev extras) |\n| PPTX | `python-pptx` 에 공식 `add_row` API 없음 (issue #86, 2014년부터 open) | **v0.5.0 부터 자체 lxml 구현** (`\u003ca:tr\u003e` deepcopy 패턴) |\n| PPTX | placeholder가 여러 `run`으로 쪼개져 단순 `run.text` 치환이 실패 | paragraph 전체 재조립 |\n| DOCX | `docxtpl`의 `{%tr%}`를 같은 행에 두면 파싱 에러 | README에 배치 규칙 명시 |\n\n## 프로젝트 구조\n\n```\ndocument_adapter/\n├── __init__.py        # load() dispatcher\n├── base.py            # DocumentAdapter ABC + fill_form + dataclasses\n├── docx_adapter.py    # DocxAdapter\n├── pptx_adapter.py    # PptxAdapter (append_row 자체 구현 포함)\n├── hwpx_adapter.py    # HwpxAdapter (hwpx_core 기반)\n├── hwpx_core/         # 자체 HWPX 패키지 (v0.4+)\n│   ├── constants.py\n│   ├── package.py     # ZIP + dirty XML 관리\n│   ├── grid.py        # iter_grid, table_shape\n│   └── paragraph.py   # run-level 편집 헬퍼\n├── tools.py           # 7개 MCP 도구 정의 + call_tool dispatcher\n└── mcp_server.py      # MCP stdio server\n\nexamples/\n└── claude_api_example.py    # Claude API Tool Use 에이전트 루프\n```\n\n## 라이선스\n\nMIT\n\n## Credits\n\n**런타임 의존성** (전부 허용형 OSS):\n- [`python-docx`](https://github.com/python-openxml/python-docx) — MIT\n- [`docxtpl`](https://github.com/elapouya/python-docx-template) — LGPL-2.1\n- [`python-pptx`](https://github.com/scanny/python-pptx) — MIT\n- [`lxml`](https://lxml.de/) — BSD\n- [`mcp`](https://github.com/modelcontextprotocol/python-sdk) — MIT\n\n**코드 참조**:\n- [`xgen-doc2chunk`](https://github.com/PlateerLab/xgen-doc2chunk) (Apache-2.0) — HWPX table grid 파싱 로직 차용 (`NOTICE` 참조)\n\n**Dev 전용** (fixture 생성에만 사용):\n- [`python-hwpx`](https://github.com/airmang/python-hwpx) — Non-Commercial License (v0.4.0 부터 런타임 의존성 제거)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplateerlab%2Fdocument-adapter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplateerlab%2Fdocument-adapter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplateerlab%2Fdocument-adapter/lists"}