document-adapter 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Plateer Lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,252 @@
1
+ Metadata-Version: 2.4
2
+ Name: document-adapter
3
+ Version: 0.1.0
4
+ Summary: LLM-friendly document template editing (DOCX/PPTX/HWPX) with MCP server and Claude API tool-use support
5
+ Author-email: Son Seongjun <sonsj97@plateer.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/PlateerLab/document-adapter
8
+ Project-URL: Repository, https://github.com/PlateerLab/document-adapter
9
+ Keywords: mcp,llm,document,docx,pptx,hwpx,template,claude
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: python-docx>=1.1
20
+ Requires-Dist: docxtpl>=0.20
21
+ Requires-Dist: python-pptx>=1.0
22
+ Requires-Dist: python-hwpx>=2.9
23
+ Requires-Dist: mcp>=1.0
24
+ Provides-Extra: claude
25
+ Requires-Dist: anthropic>=0.40; extra == "claude"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # document-adapter
31
+
32
+ **LLM이 DOCX / PPTX / HWPX 문서를 직접 편집할 수 있게 해주는 통합 어댑터 + MCP 서버.**
33
+
34
+ 세 가지 오피스 포맷을 하나의 파이썬 인터페이스로 추상화하고, Claude Desktop / Claude Code / Anthropic API Tool Use에서 바로 호출할 수 있는 MCP 도구로 노출합니다. 양식 문서의 빈 셀을 자동으로 채우거나, 템플릿의 `{{key}}`를 치환하거나, 기존 표의 내용을 수정하는 작업을 LLM 에이전트가 수행할 수 있습니다.
35
+
36
+ ## 지원 포맷
37
+
38
+ | 포맷 | 백엔드 | 템플릿 렌더 | 표 읽기 | 셀 수정 | 행 추가 |
39
+ |---|---|---|---|---|---|
40
+ | `.docx` | `docxtpl` + `python-docx` | Jinja2 (`{%tr%}` loop 포함) | ✅ | ✅ | ✅ |
41
+ | `.pptx` | `python-pptx` | `{{key}}` 치환 | ✅ (슬라이드 위치 포함) | ✅ | ❌ (미지원) |
42
+ | `.hwpx` | `python-hwpx` (Pure Python) | `{{key}}` 치환 | ✅ | ✅ | ❌ (미지원) |
43
+
44
+ - HWPX는 한컴오피스 설치가 **불필요**합니다 (macOS/Linux 서버에서 그대로 동작).
45
+ - 구버전 `.hwp`(바이너리 포맷)는 지원하지 않습니다 — `.hwpx`로 변환 후 사용하세요.
46
+
47
+ ## 설치
48
+
49
+ ```bash
50
+ pip install -e .
51
+
52
+ # Claude API 예시 스크립트까지 쓰려면
53
+ pip install -e ".[claude]"
54
+ ```
55
+
56
+ Python 3.10+ 필요.
57
+
58
+ ## 빠른 시작 — 파이썬 API
59
+
60
+ ```python
61
+ from document_adapter import load
62
+
63
+ doc = load("report_template.docx")
64
+
65
+ # 1. 구조 파악
66
+ schema = doc.get_schema()
67
+ print(schema.placeholders) # ['author', 'date', 'title']
68
+ print(schema.tables) # [TableSchema(index=0, rows=7, cols=2, ...), ...]
69
+
70
+ # 2. 템플릿 렌더
71
+ doc.render_template({
72
+ "title": "Q1 운영 리포트",
73
+ "author": "손성준",
74
+ "date": "2026-04-15",
75
+ })
76
+ doc.save("report_filled.docx")
77
+
78
+ # 3. 기존 양식 파일의 표 셀 수정
79
+ doc = load("checklist.docx")
80
+ old = doc.set_cell(table_index=1, row=1, col=1, value="○○전자")
81
+ doc.append_row(1, ["새 항목", "값"]) # DOCX만 지원
82
+ doc.save("checklist_filled.docx")
83
+ doc.close()
84
+ ```
85
+
86
+ 확장자로 자동 분기되므로 `.pptx` / `.hwpx`도 동일한 API를 사용합니다.
87
+
88
+ ## MCP 서버로 사용 — Claude Desktop / Claude Code
89
+
90
+ ### 실행
91
+
92
+ ```bash
93
+ python -m document_adapter.mcp_server
94
+ # 또는 설치 후
95
+ document-adapter-mcp
96
+ ```
97
+
98
+ ### Claude Desktop 설정
99
+
100
+ `~/Library/Application Support/Claude/claude_desktop_config.json`:
101
+
102
+ ```json
103
+ {
104
+ "mcpServers": {
105
+ "document-adapter": {
106
+ "command": "/absolute/path/to/venv/bin/python",
107
+ "args": ["-m", "document_adapter.mcp_server"]
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ 재시작하면 Claude Desktop에서 아래 4개 도구를 사용할 수 있습니다.
114
+
115
+ ### Claude Code 설정
116
+
117
+ ```bash
118
+ claude mcp add document-adapter \
119
+ /absolute/path/to/venv/bin/python -m document_adapter.mcp_server
120
+ ```
121
+
122
+ ## Anthropic API Tool Use로 사용
123
+
124
+ `document_adapter.tools`가 Claude API의 tool schema 형식과 그대로 호환됩니다.
125
+
126
+ ```python
127
+ import anthropic
128
+ from document_adapter.tools import TOOL_DEFINITIONS, call_tool
129
+
130
+ client = anthropic.Anthropic()
131
+
132
+ resp = client.messages.create(
133
+ model="claude-opus-4-6",
134
+ max_tokens=4096,
135
+ tools=[{
136
+ "name": t["name"],
137
+ "description": t["description"],
138
+ "input_schema": t["input_schema"],
139
+ } for t in TOOL_DEFINITIONS],
140
+ messages=[{
141
+ "role": "user",
142
+ "content": "report_template.docx의 표 구조를 확인하고 빈 셀을 적절히 채워줘",
143
+ }],
144
+ )
145
+
146
+ # tool_use 블록을 받으면 call_tool(name, args)로 실행 후 결과 반환
147
+ ```
148
+
149
+ 전체 agent loop 예시는 [`examples/claude_api_example.py`](examples/claude_api_example.py) 참고.
150
+
151
+ ## 노출되는 4개 도구
152
+
153
+ | 도구 | 설명 |
154
+ |---|---|
155
+ | `inspect_document` | 문서 구조(placeholders, tables)를 JSON으로 반환. **항상 첫 호출로 사용** |
156
+ | `render_template` | `{{key}}`를 context dict 값으로 치환해 새 파일 저장 |
157
+ | `set_cell` | 특정 표의 `(row, col)` 셀 값 교체 |
158
+ | `append_row` | 표 끝에 새 행 추가 (DOCX 전용) |
159
+
160
+ ### `inspect_document` 반환 예시
161
+
162
+ ```json
163
+ {
164
+ "format": "docx",
165
+ "source": "/path/to/checklist.docx",
166
+ "placeholders": [],
167
+ "tables": [
168
+ {
169
+ "index": 1,
170
+ "rows": 7,
171
+ "cols": 2,
172
+ "location": null,
173
+ "preview": [
174
+ {"row": 0, "cells": ["항목", "기입 내용"]},
175
+ {"row": 1, "cells": ["고객사 / 조직", ""]},
176
+ {"row": 2, "cells": ["현업 담당부서 / 책임자", ""]}
177
+ ]
178
+ }
179
+ ]
180
+ }
181
+ ```
182
+
183
+ LLM은 이 preview를 보고 **"빈 셀이 어디 있는지 / 어떤 값을 넣어야 하는지"** 를 판단하여 `set_cell`을 호출합니다.
184
+
185
+ ## 템플릿 작성 규칙
186
+
187
+ ### DOCX — Jinja2 전체 문법 사용 가능
188
+
189
+ ```
190
+ {{ report_title }}
191
+ 작성자: {{ author }}
192
+
193
+ {% for item in items %}- {{ item.name }}: {{ item.value }}
194
+ {% endfor %}
195
+ ```
196
+
197
+ **표 행 반복은 `{%tr for ... %}` / `{%tr endfor %}`를 각각 별도 행에 두어야 합니다.**
198
+ 같은 행에 두 태그를 넣으면 `<w:tr>` 전체가 `{% for %}`로 교체되어 `endfor`가 손실됩니다.
199
+
200
+ ```
201
+ ┌─────────────────────┬─────┬─────┐
202
+ │ 항목 │ 목표 │ 실적 │ <- 헤더
203
+ ├─────────────────────┼─────┼─────┤
204
+ │ {%tr for r in rows %} │ <- for 행
205
+ ├─────────────────────┼─────┼─────┤
206
+ │ {{ r.name }} │ {{ r.target }} │ {{ r.actual }} │ <- 반복 본문
207
+ ├─────────────────────┼─────┼─────┤
208
+ │ {%tr endfor %} │ <- endfor 행
209
+ └─────────────────────┴─────┴─────┘
210
+ ```
211
+
212
+ ### PPTX / HWPX — 단순 `{{key}}` 치환만
213
+
214
+ loop / if / filter는 지원하지 않습니다. PPTX는 placeholder가 여러 `run`으로 쪼개질 수 있어, 어댑터가 paragraph 전체 텍스트를 재조립한 뒤 첫 `run`에 다시 담는 방식으로 처리합니다 (서식 일부 손실 가능).
215
+
216
+ ## 내장된 버그 회피
217
+
218
+ | 포맷 | 문제 | 어댑터의 처리 |
219
+ |---|---|---|
220
+ | HWPX | `python-hwpx 2.9.0`의 `set_cell_text()`가 빈 셀에서 lxml/ElementTree 혼용 `TypeError` 발생 | `paragraphs[0].text = value` 직접 할당으로 우회 |
221
+ | HWPX | `replace_text_in_runs()`가 한글 공백이 run으로 쪼개진 경우 매칭 실패 | 위치 기반 API만 사용 |
222
+ | HWPX | `manifest fallback` 경고 로그가 과도하게 출력됨 | `logging.getLogger("hwpx")` 레벨을 `ERROR`로 조정 |
223
+ | PPTX | placeholder가 여러 `run`으로 쪼개져 단순 `run.text` 치환이 실패 | paragraph 전체 재조립 |
224
+ | DOCX | `docxtpl`의 `{%tr%}`를 같은 행에 두면 파싱 에러 | README에 배치 규칙 명시 |
225
+
226
+ ## 프로젝트 구조
227
+
228
+ ```
229
+ document_adapter/
230
+ ├── __init__.py # load() dispatcher
231
+ ├── base.py # DocumentAdapter ABC, TableSchema, DocumentSchema
232
+ ├── docx_adapter.py # DocxAdapter
233
+ ├── pptx_adapter.py # PptxAdapter
234
+ ├── hwpx_adapter.py # HwpxAdapter (버그 회피 포함)
235
+ ├── tools.py # Tool 정의 + call_tool dispatcher
236
+ └── mcp_server.py # MCP stdio server
237
+
238
+ examples/
239
+ └── claude_api_example.py
240
+ ```
241
+
242
+ ## 라이선스
243
+
244
+ MIT
245
+
246
+ ## Credits
247
+
248
+ - [`python-docx`](https://github.com/python-openxml/python-docx)
249
+ - [`docxtpl`](https://github.com/elapouya/python-docx-template)
250
+ - [`python-pptx`](https://github.com/scanny/python-pptx)
251
+ - [`python-hwpx`](https://github.com/airmang/python-hwpx)
252
+ - [`mcp`](https://github.com/modelcontextprotocol/python-sdk)
@@ -0,0 +1,223 @@
1
+ # document-adapter
2
+
3
+ **LLM이 DOCX / PPTX / HWPX 문서를 직접 편집할 수 있게 해주는 통합 어댑터 + MCP 서버.**
4
+
5
+ 세 가지 오피스 포맷을 하나의 파이썬 인터페이스로 추상화하고, Claude Desktop / Claude Code / Anthropic API Tool Use에서 바로 호출할 수 있는 MCP 도구로 노출합니다. 양식 문서의 빈 셀을 자동으로 채우거나, 템플릿의 `{{key}}`를 치환하거나, 기존 표의 내용을 수정하는 작업을 LLM 에이전트가 수행할 수 있습니다.
6
+
7
+ ## 지원 포맷
8
+
9
+ | 포맷 | 백엔드 | 템플릿 렌더 | 표 읽기 | 셀 수정 | 행 추가 |
10
+ |---|---|---|---|---|---|
11
+ | `.docx` | `docxtpl` + `python-docx` | Jinja2 (`{%tr%}` loop 포함) | ✅ | ✅ | ✅ |
12
+ | `.pptx` | `python-pptx` | `{{key}}` 치환 | ✅ (슬라이드 위치 포함) | ✅ | ❌ (미지원) |
13
+ | `.hwpx` | `python-hwpx` (Pure Python) | `{{key}}` 치환 | ✅ | ✅ | ❌ (미지원) |
14
+
15
+ - HWPX는 한컴오피스 설치가 **불필요**합니다 (macOS/Linux 서버에서 그대로 동작).
16
+ - 구버전 `.hwp`(바이너리 포맷)는 지원하지 않습니다 — `.hwpx`로 변환 후 사용하세요.
17
+
18
+ ## 설치
19
+
20
+ ```bash
21
+ pip install -e .
22
+
23
+ # Claude API 예시 스크립트까지 쓰려면
24
+ pip install -e ".[claude]"
25
+ ```
26
+
27
+ Python 3.10+ 필요.
28
+
29
+ ## 빠른 시작 — 파이썬 API
30
+
31
+ ```python
32
+ from document_adapter import load
33
+
34
+ doc = load("report_template.docx")
35
+
36
+ # 1. 구조 파악
37
+ schema = doc.get_schema()
38
+ print(schema.placeholders) # ['author', 'date', 'title']
39
+ print(schema.tables) # [TableSchema(index=0, rows=7, cols=2, ...), ...]
40
+
41
+ # 2. 템플릿 렌더
42
+ doc.render_template({
43
+ "title": "Q1 운영 리포트",
44
+ "author": "손성준",
45
+ "date": "2026-04-15",
46
+ })
47
+ doc.save("report_filled.docx")
48
+
49
+ # 3. 기존 양식 파일의 표 셀 수정
50
+ doc = load("checklist.docx")
51
+ old = doc.set_cell(table_index=1, row=1, col=1, value="○○전자")
52
+ doc.append_row(1, ["새 항목", "값"]) # DOCX만 지원
53
+ doc.save("checklist_filled.docx")
54
+ doc.close()
55
+ ```
56
+
57
+ 확장자로 자동 분기되므로 `.pptx` / `.hwpx`도 동일한 API를 사용합니다.
58
+
59
+ ## MCP 서버로 사용 — Claude Desktop / Claude Code
60
+
61
+ ### 실행
62
+
63
+ ```bash
64
+ python -m document_adapter.mcp_server
65
+ # 또는 설치 후
66
+ document-adapter-mcp
67
+ ```
68
+
69
+ ### Claude Desktop 설정
70
+
71
+ `~/Library/Application Support/Claude/claude_desktop_config.json`:
72
+
73
+ ```json
74
+ {
75
+ "mcpServers": {
76
+ "document-adapter": {
77
+ "command": "/absolute/path/to/venv/bin/python",
78
+ "args": ["-m", "document_adapter.mcp_server"]
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ 재시작하면 Claude Desktop에서 아래 4개 도구를 사용할 수 있습니다.
85
+
86
+ ### Claude Code 설정
87
+
88
+ ```bash
89
+ claude mcp add document-adapter \
90
+ /absolute/path/to/venv/bin/python -m document_adapter.mcp_server
91
+ ```
92
+
93
+ ## Anthropic API Tool Use로 사용
94
+
95
+ `document_adapter.tools`가 Claude API의 tool schema 형식과 그대로 호환됩니다.
96
+
97
+ ```python
98
+ import anthropic
99
+ from document_adapter.tools import TOOL_DEFINITIONS, call_tool
100
+
101
+ client = anthropic.Anthropic()
102
+
103
+ resp = client.messages.create(
104
+ model="claude-opus-4-6",
105
+ max_tokens=4096,
106
+ tools=[{
107
+ "name": t["name"],
108
+ "description": t["description"],
109
+ "input_schema": t["input_schema"],
110
+ } for t in TOOL_DEFINITIONS],
111
+ messages=[{
112
+ "role": "user",
113
+ "content": "report_template.docx의 표 구조를 확인하고 빈 셀을 적절히 채워줘",
114
+ }],
115
+ )
116
+
117
+ # tool_use 블록을 받으면 call_tool(name, args)로 실행 후 결과 반환
118
+ ```
119
+
120
+ 전체 agent loop 예시는 [`examples/claude_api_example.py`](examples/claude_api_example.py) 참고.
121
+
122
+ ## 노출되는 4개 도구
123
+
124
+ | 도구 | 설명 |
125
+ |---|---|
126
+ | `inspect_document` | 문서 구조(placeholders, tables)를 JSON으로 반환. **항상 첫 호출로 사용** |
127
+ | `render_template` | `{{key}}`를 context dict 값으로 치환해 새 파일 저장 |
128
+ | `set_cell` | 특정 표의 `(row, col)` 셀 값 교체 |
129
+ | `append_row` | 표 끝에 새 행 추가 (DOCX 전용) |
130
+
131
+ ### `inspect_document` 반환 예시
132
+
133
+ ```json
134
+ {
135
+ "format": "docx",
136
+ "source": "/path/to/checklist.docx",
137
+ "placeholders": [],
138
+ "tables": [
139
+ {
140
+ "index": 1,
141
+ "rows": 7,
142
+ "cols": 2,
143
+ "location": null,
144
+ "preview": [
145
+ {"row": 0, "cells": ["항목", "기입 내용"]},
146
+ {"row": 1, "cells": ["고객사 / 조직", ""]},
147
+ {"row": 2, "cells": ["현업 담당부서 / 책임자", ""]}
148
+ ]
149
+ }
150
+ ]
151
+ }
152
+ ```
153
+
154
+ LLM은 이 preview를 보고 **"빈 셀이 어디 있는지 / 어떤 값을 넣어야 하는지"** 를 판단하여 `set_cell`을 호출합니다.
155
+
156
+ ## 템플릿 작성 규칙
157
+
158
+ ### DOCX — Jinja2 전체 문법 사용 가능
159
+
160
+ ```
161
+ {{ report_title }}
162
+ 작성자: {{ author }}
163
+
164
+ {% for item in items %}- {{ item.name }}: {{ item.value }}
165
+ {% endfor %}
166
+ ```
167
+
168
+ **표 행 반복은 `{%tr for ... %}` / `{%tr endfor %}`를 각각 별도 행에 두어야 합니다.**
169
+ 같은 행에 두 태그를 넣으면 `<w:tr>` 전체가 `{% for %}`로 교체되어 `endfor`가 손실됩니다.
170
+
171
+ ```
172
+ ┌─────────────────────┬─────┬─────┐
173
+ │ 항목 │ 목표 │ 실적 │ <- 헤더
174
+ ├─────────────────────┼─────┼─────┤
175
+ │ {%tr for r in rows %} │ <- for 행
176
+ ├─────────────────────┼─────┼─────┤
177
+ │ {{ r.name }} │ {{ r.target }} │ {{ r.actual }} │ <- 반복 본문
178
+ ├─────────────────────┼─────┼─────┤
179
+ │ {%tr endfor %} │ <- endfor 행
180
+ └─────────────────────┴─────┴─────┘
181
+ ```
182
+
183
+ ### PPTX / HWPX — 단순 `{{key}}` 치환만
184
+
185
+ loop / if / filter는 지원하지 않습니다. PPTX는 placeholder가 여러 `run`으로 쪼개질 수 있어, 어댑터가 paragraph 전체 텍스트를 재조립한 뒤 첫 `run`에 다시 담는 방식으로 처리합니다 (서식 일부 손실 가능).
186
+
187
+ ## 내장된 버그 회피
188
+
189
+ | 포맷 | 문제 | 어댑터의 처리 |
190
+ |---|---|---|
191
+ | HWPX | `python-hwpx 2.9.0`의 `set_cell_text()`가 빈 셀에서 lxml/ElementTree 혼용 `TypeError` 발생 | `paragraphs[0].text = value` 직접 할당으로 우회 |
192
+ | HWPX | `replace_text_in_runs()`가 한글 공백이 run으로 쪼개진 경우 매칭 실패 | 위치 기반 API만 사용 |
193
+ | HWPX | `manifest fallback` 경고 로그가 과도하게 출력됨 | `logging.getLogger("hwpx")` 레벨을 `ERROR`로 조정 |
194
+ | PPTX | placeholder가 여러 `run`으로 쪼개져 단순 `run.text` 치환이 실패 | paragraph 전체 재조립 |
195
+ | DOCX | `docxtpl`의 `{%tr%}`를 같은 행에 두면 파싱 에러 | README에 배치 규칙 명시 |
196
+
197
+ ## 프로젝트 구조
198
+
199
+ ```
200
+ document_adapter/
201
+ ├── __init__.py # load() dispatcher
202
+ ├── base.py # DocumentAdapter ABC, TableSchema, DocumentSchema
203
+ ├── docx_adapter.py # DocxAdapter
204
+ ├── pptx_adapter.py # PptxAdapter
205
+ ├── hwpx_adapter.py # HwpxAdapter (버그 회피 포함)
206
+ ├── tools.py # Tool 정의 + call_tool dispatcher
207
+ └── mcp_server.py # MCP stdio server
208
+
209
+ examples/
210
+ └── claude_api_example.py
211
+ ```
212
+
213
+ ## 라이선스
214
+
215
+ MIT
216
+
217
+ ## Credits
218
+
219
+ - [`python-docx`](https://github.com/python-openxml/python-docx)
220
+ - [`docxtpl`](https://github.com/elapouya/python-docx-template)
221
+ - [`python-pptx`](https://github.com/scanny/python-pptx)
222
+ - [`python-hwpx`](https://github.com/airmang/python-hwpx)
223
+ - [`mcp`](https://github.com/modelcontextprotocol/python-sdk)
@@ -0,0 +1,45 @@
1
+ """Document template editing — 통합 어댑터.
2
+
3
+ 사용법:
4
+ from document_adapter import load
5
+ doc = load("report.docx")
6
+ schema = doc.get_schema()
7
+ doc.set_cell(0, 1, 1, "홍길동")
8
+ doc.save("report_filled.docx")
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+
14
+ from .base import DocumentAdapter, DocumentSchema, TableSchema
15
+ from .docx_adapter import DocxAdapter
16
+ from .hwpx_adapter import HwpxAdapter
17
+ from .pptx_adapter import PptxAdapter
18
+
19
+ __all__ = [
20
+ "load",
21
+ "DocumentAdapter",
22
+ "DocumentSchema",
23
+ "TableSchema",
24
+ "DocxAdapter",
25
+ "PptxAdapter",
26
+ "HwpxAdapter",
27
+ ]
28
+
29
+ _ADAPTERS: dict[str, type[DocumentAdapter]] = {
30
+ ".docx": DocxAdapter,
31
+ ".pptx": PptxAdapter,
32
+ ".hwpx": HwpxAdapter,
33
+ }
34
+
35
+
36
+ def load(path: str | Path) -> DocumentAdapter:
37
+ """확장자로 적절한 어댑터를 선택해 문서를 연다."""
38
+ p = Path(path)
39
+ suffix = p.suffix.lower()
40
+ cls = _ADAPTERS.get(suffix)
41
+ if cls is None:
42
+ raise ValueError(
43
+ f"지원하지 않는 포맷: {suffix}. 지원: {sorted(_ADAPTERS.keys())}"
44
+ )
45
+ return cls(p)
@@ -0,0 +1,108 @@
1
+ """DocumentAdapter 공통 인터페이스.
2
+
3
+ 세 포맷(DOCX/PPTX/HWPX)의 공통 작업을 추상화:
4
+ - 템플릿 렌더링 ({{key}} 치환)
5
+ - 표 스키마 추출 (LLM 입력용)
6
+ - 셀 수정 / 행 추가
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+
16
+ @dataclass
17
+ class TableSchema:
18
+ """표 한 개의 구조 (LLM에게 넘길 형태)."""
19
+ index: int
20
+ rows: int
21
+ cols: int
22
+ preview: list[list[str]]
23
+ location: str | None = None
24
+
25
+ def to_dict(self) -> dict[str, Any]:
26
+ return {
27
+ "index": self.index,
28
+ "rows": self.rows,
29
+ "cols": self.cols,
30
+ "location": self.location,
31
+ "preview": self.preview,
32
+ }
33
+
34
+
35
+ @dataclass
36
+ class DocumentSchema:
37
+ """문서 전체 스키마."""
38
+ format: str
39
+ source: str
40
+ placeholders: list[str] = field(default_factory=list)
41
+ tables: list[TableSchema] = field(default_factory=list)
42
+
43
+ def to_dict(self) -> dict[str, Any]:
44
+ return {
45
+ "format": self.format,
46
+ "source": self.source,
47
+ "placeholders": self.placeholders,
48
+ "tables": [t.to_dict() for t in self.tables],
49
+ }
50
+
51
+
52
+ class DocumentAdapter(ABC):
53
+ """모든 포맷 어댑터의 공통 부모."""
54
+
55
+ format: str = ""
56
+
57
+ def __init__(self, path: Path) -> None:
58
+ self.path = Path(path)
59
+ self._open()
60
+
61
+ # ---- lifecycle ----
62
+ @abstractmethod
63
+ def _open(self) -> None: ...
64
+
65
+ @abstractmethod
66
+ def save(self, path: Path | str | None = None) -> Path: ...
67
+
68
+ def close(self) -> None:
69
+ """일부 포맷(HWPX)은 명시적 close 필요."""
70
+ pass
71
+
72
+ # ---- inspection ----
73
+ @abstractmethod
74
+ def get_placeholders(self) -> list[str]:
75
+ """본문에서 사용된 {{key}} 목록 반환."""
76
+
77
+ @abstractmethod
78
+ def get_tables(self, min_rows: int = 1, min_cols: int = 1,
79
+ preview_rows: int = 4, max_cell_len: int = 40) -> list[TableSchema]:
80
+ """필터 조건을 만족하는 표 스키마 목록."""
81
+
82
+ def get_schema(self) -> DocumentSchema:
83
+ return DocumentSchema(
84
+ format=self.format,
85
+ source=str(self.path),
86
+ placeholders=self.get_placeholders(),
87
+ tables=self.get_tables(),
88
+ )
89
+
90
+ # ---- editing ----
91
+ @abstractmethod
92
+ def render_template(self, context: dict[str, Any]) -> None:
93
+ """템플릿의 {{key}}를 context 값으로 치환."""
94
+
95
+ @abstractmethod
96
+ def set_cell(self, table_index: int, row: int, col: int, value: str) -> str:
97
+ """셀 값 교체. 원래 값 반환."""
98
+
99
+ @abstractmethod
100
+ def append_row(self, table_index: int, values: list[str]) -> None:
101
+ """표 끝에 새 행 추가."""
102
+
103
+ # ---- context manager ----
104
+ def __enter__(self) -> "DocumentAdapter":
105
+ return self
106
+
107
+ def __exit__(self, exc_type, exc, tb) -> None:
108
+ self.close()