document-adapter 0.2.0__tar.gz → 0.4.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.
- document_adapter-0.4.0/NOTICE +29 -0
- {document_adapter-0.2.0 → document_adapter-0.4.0}/PKG-INFO +74 -23
- {document_adapter-0.2.0 → document_adapter-0.4.0}/README.md +68 -19
- {document_adapter-0.2.0 → document_adapter-0.4.0}/document_adapter/base.py +64 -1
- document_adapter-0.4.0/document_adapter/docx_adapter.py +352 -0
- document_adapter-0.4.0/document_adapter/hwpx_adapter.py +348 -0
- document_adapter-0.4.0/document_adapter/hwpx_core/__init__.py +67 -0
- document_adapter-0.4.0/document_adapter/hwpx_core/constants.py +27 -0
- document_adapter-0.4.0/document_adapter/hwpx_core/grid.py +145 -0
- document_adapter-0.4.0/document_adapter/hwpx_core/package.py +146 -0
- document_adapter-0.4.0/document_adapter/hwpx_core/paragraph.py +106 -0
- document_adapter-0.4.0/document_adapter/pptx_adapter.py +354 -0
- {document_adapter-0.2.0 → document_adapter-0.4.0}/document_adapter/tools.py +106 -17
- {document_adapter-0.2.0 → document_adapter-0.4.0}/document_adapter.egg-info/PKG-INFO +74 -23
- {document_adapter-0.2.0 → document_adapter-0.4.0}/document_adapter.egg-info/SOURCES.txt +6 -0
- {document_adapter-0.2.0 → document_adapter-0.4.0}/document_adapter.egg-info/requires.txt +3 -2
- {document_adapter-0.2.0 → document_adapter-0.4.0}/pyproject.toml +7 -5
- {document_adapter-0.2.0 → document_adapter-0.4.0}/tests/test_smoke.py +325 -0
- document_adapter-0.2.0/document_adapter/docx_adapter.py +0 -133
- document_adapter-0.2.0/document_adapter/hwpx_adapter.py +0 -246
- document_adapter-0.2.0/document_adapter/pptx_adapter.py +0 -178
- {document_adapter-0.2.0 → document_adapter-0.4.0}/LICENSE +0 -0
- {document_adapter-0.2.0 → document_adapter-0.4.0}/document_adapter/__init__.py +0 -0
- {document_adapter-0.2.0 → document_adapter-0.4.0}/document_adapter/mcp_server.py +0 -0
- {document_adapter-0.2.0 → document_adapter-0.4.0}/document_adapter.egg-info/dependency_links.txt +0 -0
- {document_adapter-0.2.0 → document_adapter-0.4.0}/document_adapter.egg-info/entry_points.txt +0 -0
- {document_adapter-0.2.0 → document_adapter-0.4.0}/document_adapter.egg-info/top_level.txt +0 -0
- {document_adapter-0.2.0 → document_adapter-0.4.0}/setup.cfg +0 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
document-adapter
|
|
2
|
+
Copyright 2026 Son Seongjun / PlateerLab
|
|
3
|
+
|
|
4
|
+
This product includes software developed at PlateerLab
|
|
5
|
+
(https://github.com/PlateerLab).
|
|
6
|
+
|
|
7
|
+
===============================================================================
|
|
8
|
+
Third-party code attributions
|
|
9
|
+
===============================================================================
|
|
10
|
+
|
|
11
|
+
HWPX table grid parsing logic
|
|
12
|
+
-----------------------------
|
|
13
|
+
|
|
14
|
+
Portions of the HWPX table grid construction in
|
|
15
|
+
`document_adapter/hwpx_core/grid.py` (cell position / cell span parsing, and
|
|
16
|
+
the skip-map approach for merged non-anchor cells) are adapted from:
|
|
17
|
+
|
|
18
|
+
PlateerLab/xgen-doc2chunk
|
|
19
|
+
https://github.com/PlateerLab/xgen-doc2chunk
|
|
20
|
+
Licensed under the Apache License, Version 2.0
|
|
21
|
+
|
|
22
|
+
The adapted logic is limited to read-side grid construction. All edit-side
|
|
23
|
+
functionality (cell writing, row insertion, ZIP round-trip preservation) is
|
|
24
|
+
original to this project.
|
|
25
|
+
|
|
26
|
+
You may obtain a copy of the Apache License 2.0 at:
|
|
27
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
28
|
+
|
|
29
|
+
===============================================================================
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: document-adapter
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: LLM-friendly document template editing (DOCX/PPTX/HWPX) with MCP server and Claude API tool-use support
|
|
5
5
|
Author-email: Son Seongjun <sonsj97@plateer.com>
|
|
6
6
|
License: MIT
|
|
@@ -9,7 +9,7 @@ Project-URL: Repository, https://github.com/PlateerLab/document-adapter
|
|
|
9
9
|
Project-URL: Issues, https://github.com/PlateerLab/document-adapter/issues
|
|
10
10
|
Project-URL: PyPI, https://pypi.org/project/document-adapter/
|
|
11
11
|
Project-URL: Changelog, https://github.com/PlateerLab/document-adapter/releases
|
|
12
|
-
Keywords: mcp,model-context-protocol,llm,claude,anthropic,tool-use,agent,document,docx,pptx,hwpx,hwp,template,template-engine,office,office-automation,word,powerpoint,hancom,korean,docxtpl,python-docx,python-pptx,
|
|
12
|
+
Keywords: mcp,model-context-protocol,llm,claude,anthropic,tool-use,agent,document,docx,pptx,hwpx,hwp,template,template-engine,office,office-automation,word,powerpoint,hancom,korean,docxtpl,python-docx,python-pptx,lxml
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -26,15 +26,17 @@ Classifier: Environment :: Console
|
|
|
26
26
|
Requires-Python: >=3.10
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
License-File: LICENSE
|
|
29
|
-
|
|
29
|
+
License-File: NOTICE
|
|
30
|
+
Requires-Dist: python-docx>=1.2
|
|
30
31
|
Requires-Dist: docxtpl>=0.20
|
|
31
32
|
Requires-Dist: python-pptx>=1.0
|
|
32
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: lxml>=5.0
|
|
33
34
|
Requires-Dist: mcp>=1.0
|
|
34
35
|
Provides-Extra: claude
|
|
35
36
|
Requires-Dist: anthropic>=0.40; extra == "claude"
|
|
36
37
|
Provides-Extra: dev
|
|
37
38
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
39
|
+
Requires-Dist: python-hwpx>=2.9; extra == "dev"
|
|
38
40
|
Dynamic: license-file
|
|
39
41
|
|
|
40
42
|
# document-adapter
|
|
@@ -53,14 +55,21 @@ Dynamic: license-file
|
|
|
53
55
|
|
|
54
56
|
## 지원 포맷
|
|
55
57
|
|
|
56
|
-
| 포맷 | 백엔드 | 템플릿 렌더 | 표 읽기 | 셀 수정 | 행 추가 |
|
|
57
|
-
|
|
58
|
-
| `.docx` | `docxtpl` + `python-docx` | Jinja2 (`{%tr%}` loop 포함) | ✅ | ✅ | ✅ |
|
|
59
|
-
| `.pptx` | `python-pptx` | `{{key}}` 치환 | ✅ (슬라이드 위치 포함) | ✅ | ❌ (미지원) |
|
|
60
|
-
| `.hwpx` | `
|
|
58
|
+
| 포맷 | 백엔드 | 템플릿 렌더 | 표 읽기 | 병합 셀 인지 | 중첩 테이블 | 셀 수정 | 행 추가 |
|
|
59
|
+
|---|---|---|---|---|---|---|---|
|
|
60
|
+
| `.docx` | `docxtpl` + `python-docx` | Jinja2 (`{%tr%}` loop 포함) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
61
|
+
| `.pptx` | `python-pptx` | `{{key}}` 치환 | ✅ (슬라이드 위치 포함) | ✅ | — (포맷 미지원) | ✅ | ❌ (미지원) |
|
|
62
|
+
| `.hwpx` | 자체 `hwpx_core` (lxml + zipfile) | `{{key}}` 치환 | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
61
63
|
|
|
62
64
|
- HWPX는 한컴오피스 설치가 **불필요**합니다 (macOS/Linux 서버에서 그대로 동작).
|
|
63
65
|
- 구버전 `.hwp`(바이너리 포맷)는 지원하지 않습니다 — `.hwpx`로 변환 후 사용하세요.
|
|
66
|
+
- 병합 셀: 3개 포맷 모두 preview에 `null` 슬롯 + `merges` 메타로 구조 노출. non-anchor 좌표에 쓰기는 `MergedCellWriteError`로 거부.
|
|
67
|
+
|
|
68
|
+
## 라이선스 (상용 사용 가능)
|
|
69
|
+
|
|
70
|
+
- 본 프로젝트: **MIT License**
|
|
71
|
+
- 런타임 의존성(`python-docx`, `docxtpl`, `python-pptx`, `lxml`, `mcp`): 전부 **허용형 OSS** (MIT/BSD/Apache-2.0/LGPL-2.1). 상용·내부 서비스에 그대로 포함 가능.
|
|
72
|
+
- v0.3 이하에서 사용했던 `python-hwpx` (Non-Commercial License) 는 v0.4.0부터 **dev 환경(테스트 fixture 생성) 전용**으로 이동. HWPX 편집은 자체 `hwpx_core` 모듈이 수행합니다.
|
|
64
73
|
|
|
65
74
|
## 설치
|
|
66
75
|
|
|
@@ -106,14 +115,50 @@ doc.save("report_filled.docx")
|
|
|
106
115
|
|
|
107
116
|
# 3. 기존 양식 파일의 표 셀 수정
|
|
108
117
|
doc = load("checklist.docx")
|
|
118
|
+
|
|
119
|
+
# 빈 셀 값 교체
|
|
109
120
|
old = doc.set_cell(table_index=1, row=1, col=1, value="○○전자")
|
|
110
|
-
|
|
121
|
+
|
|
122
|
+
# 라벨이 있는 셀 ("성 명")에 값 추가 → "성 명 홍길동"
|
|
123
|
+
doc.append_to_cell(table_index=2, row=0, col=0, value="홍길동")
|
|
124
|
+
|
|
125
|
+
# 셀 전체 텍스트 + 병합 메타 조회 (preview의 40자 잘림 없이)
|
|
126
|
+
cell = doc.get_cell(table_index=1, row=3, col=2)
|
|
127
|
+
print(cell.text, cell.is_anchor, cell.span, cell.nested_table_indices)
|
|
128
|
+
|
|
129
|
+
# DOCX/HWPX는 행 추가 지원
|
|
130
|
+
doc.append_row(1, ["새 항목", "값"])
|
|
131
|
+
|
|
111
132
|
doc.save("checklist_filled.docx")
|
|
112
133
|
doc.close()
|
|
113
134
|
```
|
|
114
135
|
|
|
115
136
|
확장자로 자동 분기되므로 `.pptx` / `.hwpx`도 동일한 API를 사용합니다.
|
|
116
137
|
|
|
138
|
+
### 병합 셀 인지 동작 (v0.2+)
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
schema = doc.get_schema()
|
|
142
|
+
t = schema.tables[0]
|
|
143
|
+
|
|
144
|
+
# preview는 logical grid. 병합된 non-anchor 슬롯은 None.
|
|
145
|
+
# [['HEADER', None, None], ['A1', 'A2', 'A3']]
|
|
146
|
+
print(t.preview)
|
|
147
|
+
|
|
148
|
+
# merges는 span>1x1인 anchor 목록
|
|
149
|
+
# [MergeInfo(anchor=(0,0), span=(1,3))]
|
|
150
|
+
print(t.merges)
|
|
151
|
+
|
|
152
|
+
# non-anchor에 쓰기 시도하면 MergedCellWriteError (ValueError 서브클래스)
|
|
153
|
+
try:
|
|
154
|
+
doc.set_cell(0, 0, 2, "X")
|
|
155
|
+
except ValueError as e:
|
|
156
|
+
print(e) # "cell (0,2) is part of a merged region anchored at (0,0)..."
|
|
157
|
+
|
|
158
|
+
# 의도적으로 앵커로 리디렉트하고 싶다면
|
|
159
|
+
doc.set_cell(0, 0, 2, "X", allow_merge_redirect=True) # 경고 + 실제로 (0,0) 수정
|
|
160
|
+
```
|
|
161
|
+
|
|
117
162
|
## MCP 서버로 사용 — Claude Desktop / Claude Code
|
|
118
163
|
|
|
119
164
|
### 실행
|
|
@@ -177,39 +222,45 @@ resp = client.messages.create(
|
|
|
177
222
|
|
|
178
223
|
전체 agent loop 예시는 [`examples/claude_api_example.py`](examples/claude_api_example.py) 참고.
|
|
179
224
|
|
|
180
|
-
## 노출되는
|
|
225
|
+
## 노출되는 도구
|
|
181
226
|
|
|
182
227
|
| 도구 | 설명 |
|
|
183
228
|
|---|---|
|
|
184
229
|
| `inspect_document` | 문서 구조(placeholders, tables)를 JSON으로 반환. **항상 첫 호출로 사용** |
|
|
185
230
|
| `render_template` | `{{key}}`를 context dict 값으로 치환해 새 파일 저장 |
|
|
186
|
-
| `
|
|
187
|
-
| `
|
|
231
|
+
| `get_cell` | 셀 전체 텍스트 + 병합/중첩 메타 반환 (preview의 40자 잘림 없이) |
|
|
232
|
+
| `set_cell` | 특정 표의 `(row, col)` 셀 값 교체 (병합 anchor만) |
|
|
233
|
+
| `append_to_cell` | 기존 텍스트 뒤에 값 덧붙임 (라벨 유지용, 예: `"성 명"` → `"성 명 홍길동"`) |
|
|
234
|
+
| `append_row` | 표 끝에 새 행 추가 (DOCX/HWPX 지원) |
|
|
188
235
|
|
|
189
|
-
### `inspect_document` 반환 예시
|
|
236
|
+
### `inspect_document` 반환 예시 (v0.2+)
|
|
190
237
|
|
|
191
238
|
```json
|
|
192
239
|
{
|
|
193
|
-
"format": "
|
|
194
|
-
"source": "/path/to/
|
|
240
|
+
"format": "hwpx",
|
|
241
|
+
"source": "/path/to/form.hwpx",
|
|
195
242
|
"placeholders": [],
|
|
196
243
|
"tables": [
|
|
197
244
|
{
|
|
198
|
-
"index":
|
|
199
|
-
"rows":
|
|
200
|
-
"cols":
|
|
245
|
+
"index": 0,
|
|
246
|
+
"rows": 28,
|
|
247
|
+
"cols": 16,
|
|
201
248
|
"location": null,
|
|
249
|
+
"parent_path": null,
|
|
202
250
|
"preview": [
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
251
|
+
["포상금 지급신청서", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
|
|
252
|
+
["접수번호", null, null, "", "접수일자", null, null, "", ...]
|
|
253
|
+
],
|
|
254
|
+
"merges": [
|
|
255
|
+
{"anchor": [0, 0], "span": [1, 16]},
|
|
256
|
+
{"anchor": [1, 0], "span": [1, 3]}
|
|
206
257
|
]
|
|
207
258
|
}
|
|
208
259
|
]
|
|
209
260
|
}
|
|
210
261
|
```
|
|
211
262
|
|
|
212
|
-
LLM은 이 preview를 보고 **"빈 셀이 어디 있는지 / 어떤 값을 넣어야 하는지"** 를 판단하여 `set_cell`을 호출합니다.
|
|
263
|
+
LLM은 이 preview를 보고 **"빈 셀이 어디 있는지 / 어떤 값을 넣어야 하는지"** 를 판단하여 `set_cell` / `append_to_cell`을 호출합니다. `null` 슬롯은 병합된 영역이며 `merges`의 anchor 좌표로만 쓸 수 있습니다.
|
|
213
264
|
|
|
214
265
|
## 템플릿 작성 규칙
|
|
215
266
|
|
|
@@ -14,14 +14,21 @@
|
|
|
14
14
|
|
|
15
15
|
## 지원 포맷
|
|
16
16
|
|
|
17
|
-
| 포맷 | 백엔드 | 템플릿 렌더 | 표 읽기 | 셀 수정 | 행 추가 |
|
|
18
|
-
|
|
19
|
-
| `.docx` | `docxtpl` + `python-docx` | Jinja2 (`{%tr%}` loop 포함) | ✅ | ✅ | ✅ |
|
|
20
|
-
| `.pptx` | `python-pptx` | `{{key}}` 치환 | ✅ (슬라이드 위치 포함) | ✅ | ❌ (미지원) |
|
|
21
|
-
| `.hwpx` | `
|
|
17
|
+
| 포맷 | 백엔드 | 템플릿 렌더 | 표 읽기 | 병합 셀 인지 | 중첩 테이블 | 셀 수정 | 행 추가 |
|
|
18
|
+
|---|---|---|---|---|---|---|---|
|
|
19
|
+
| `.docx` | `docxtpl` + `python-docx` | Jinja2 (`{%tr%}` loop 포함) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
20
|
+
| `.pptx` | `python-pptx` | `{{key}}` 치환 | ✅ (슬라이드 위치 포함) | ✅ | — (포맷 미지원) | ✅ | ❌ (미지원) |
|
|
21
|
+
| `.hwpx` | 자체 `hwpx_core` (lxml + zipfile) | `{{key}}` 치환 | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
22
22
|
|
|
23
23
|
- HWPX는 한컴오피스 설치가 **불필요**합니다 (macOS/Linux 서버에서 그대로 동작).
|
|
24
24
|
- 구버전 `.hwp`(바이너리 포맷)는 지원하지 않습니다 — `.hwpx`로 변환 후 사용하세요.
|
|
25
|
+
- 병합 셀: 3개 포맷 모두 preview에 `null` 슬롯 + `merges` 메타로 구조 노출. non-anchor 좌표에 쓰기는 `MergedCellWriteError`로 거부.
|
|
26
|
+
|
|
27
|
+
## 라이선스 (상용 사용 가능)
|
|
28
|
+
|
|
29
|
+
- 본 프로젝트: **MIT License**
|
|
30
|
+
- 런타임 의존성(`python-docx`, `docxtpl`, `python-pptx`, `lxml`, `mcp`): 전부 **허용형 OSS** (MIT/BSD/Apache-2.0/LGPL-2.1). 상용·내부 서비스에 그대로 포함 가능.
|
|
31
|
+
- v0.3 이하에서 사용했던 `python-hwpx` (Non-Commercial License) 는 v0.4.0부터 **dev 환경(테스트 fixture 생성) 전용**으로 이동. HWPX 편집은 자체 `hwpx_core` 모듈이 수행합니다.
|
|
25
32
|
|
|
26
33
|
## 설치
|
|
27
34
|
|
|
@@ -67,14 +74,50 @@ doc.save("report_filled.docx")
|
|
|
67
74
|
|
|
68
75
|
# 3. 기존 양식 파일의 표 셀 수정
|
|
69
76
|
doc = load("checklist.docx")
|
|
77
|
+
|
|
78
|
+
# 빈 셀 값 교체
|
|
70
79
|
old = doc.set_cell(table_index=1, row=1, col=1, value="○○전자")
|
|
71
|
-
|
|
80
|
+
|
|
81
|
+
# 라벨이 있는 셀 ("성 명")에 값 추가 → "성 명 홍길동"
|
|
82
|
+
doc.append_to_cell(table_index=2, row=0, col=0, value="홍길동")
|
|
83
|
+
|
|
84
|
+
# 셀 전체 텍스트 + 병합 메타 조회 (preview의 40자 잘림 없이)
|
|
85
|
+
cell = doc.get_cell(table_index=1, row=3, col=2)
|
|
86
|
+
print(cell.text, cell.is_anchor, cell.span, cell.nested_table_indices)
|
|
87
|
+
|
|
88
|
+
# DOCX/HWPX는 행 추가 지원
|
|
89
|
+
doc.append_row(1, ["새 항목", "값"])
|
|
90
|
+
|
|
72
91
|
doc.save("checklist_filled.docx")
|
|
73
92
|
doc.close()
|
|
74
93
|
```
|
|
75
94
|
|
|
76
95
|
확장자로 자동 분기되므로 `.pptx` / `.hwpx`도 동일한 API를 사용합니다.
|
|
77
96
|
|
|
97
|
+
### 병합 셀 인지 동작 (v0.2+)
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
schema = doc.get_schema()
|
|
101
|
+
t = schema.tables[0]
|
|
102
|
+
|
|
103
|
+
# preview는 logical grid. 병합된 non-anchor 슬롯은 None.
|
|
104
|
+
# [['HEADER', None, None], ['A1', 'A2', 'A3']]
|
|
105
|
+
print(t.preview)
|
|
106
|
+
|
|
107
|
+
# merges는 span>1x1인 anchor 목록
|
|
108
|
+
# [MergeInfo(anchor=(0,0), span=(1,3))]
|
|
109
|
+
print(t.merges)
|
|
110
|
+
|
|
111
|
+
# non-anchor에 쓰기 시도하면 MergedCellWriteError (ValueError 서브클래스)
|
|
112
|
+
try:
|
|
113
|
+
doc.set_cell(0, 0, 2, "X")
|
|
114
|
+
except ValueError as e:
|
|
115
|
+
print(e) # "cell (0,2) is part of a merged region anchored at (0,0)..."
|
|
116
|
+
|
|
117
|
+
# 의도적으로 앵커로 리디렉트하고 싶다면
|
|
118
|
+
doc.set_cell(0, 0, 2, "X", allow_merge_redirect=True) # 경고 + 실제로 (0,0) 수정
|
|
119
|
+
```
|
|
120
|
+
|
|
78
121
|
## MCP 서버로 사용 — Claude Desktop / Claude Code
|
|
79
122
|
|
|
80
123
|
### 실행
|
|
@@ -138,39 +181,45 @@ resp = client.messages.create(
|
|
|
138
181
|
|
|
139
182
|
전체 agent loop 예시는 [`examples/claude_api_example.py`](examples/claude_api_example.py) 참고.
|
|
140
183
|
|
|
141
|
-
## 노출되는
|
|
184
|
+
## 노출되는 도구
|
|
142
185
|
|
|
143
186
|
| 도구 | 설명 |
|
|
144
187
|
|---|---|
|
|
145
188
|
| `inspect_document` | 문서 구조(placeholders, tables)를 JSON으로 반환. **항상 첫 호출로 사용** |
|
|
146
189
|
| `render_template` | `{{key}}`를 context dict 값으로 치환해 새 파일 저장 |
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
190
|
+
| `get_cell` | 셀 전체 텍스트 + 병합/중첩 메타 반환 (preview의 40자 잘림 없이) |
|
|
191
|
+
| `set_cell` | 특정 표의 `(row, col)` 셀 값 교체 (병합 anchor만) |
|
|
192
|
+
| `append_to_cell` | 기존 텍스트 뒤에 값 덧붙임 (라벨 유지용, 예: `"성 명"` → `"성 명 홍길동"`) |
|
|
193
|
+
| `append_row` | 표 끝에 새 행 추가 (DOCX/HWPX 지원) |
|
|
149
194
|
|
|
150
|
-
### `inspect_document` 반환 예시
|
|
195
|
+
### `inspect_document` 반환 예시 (v0.2+)
|
|
151
196
|
|
|
152
197
|
```json
|
|
153
198
|
{
|
|
154
|
-
"format": "
|
|
155
|
-
"source": "/path/to/
|
|
199
|
+
"format": "hwpx",
|
|
200
|
+
"source": "/path/to/form.hwpx",
|
|
156
201
|
"placeholders": [],
|
|
157
202
|
"tables": [
|
|
158
203
|
{
|
|
159
|
-
"index":
|
|
160
|
-
"rows":
|
|
161
|
-
"cols":
|
|
204
|
+
"index": 0,
|
|
205
|
+
"rows": 28,
|
|
206
|
+
"cols": 16,
|
|
162
207
|
"location": null,
|
|
208
|
+
"parent_path": null,
|
|
163
209
|
"preview": [
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
210
|
+
["포상금 지급신청서", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
|
|
211
|
+
["접수번호", null, null, "", "접수일자", null, null, "", ...]
|
|
212
|
+
],
|
|
213
|
+
"merges": [
|
|
214
|
+
{"anchor": [0, 0], "span": [1, 16]},
|
|
215
|
+
{"anchor": [1, 0], "span": [1, 3]}
|
|
167
216
|
]
|
|
168
217
|
}
|
|
169
218
|
]
|
|
170
219
|
}
|
|
171
220
|
```
|
|
172
221
|
|
|
173
|
-
LLM은 이 preview를 보고 **"빈 셀이 어디 있는지 / 어떤 값을 넣어야 하는지"** 를 판단하여 `set_cell`을 호출합니다.
|
|
222
|
+
LLM은 이 preview를 보고 **"빈 셀이 어디 있는지 / 어떤 값을 넣어야 하는지"** 를 판단하여 `set_cell` / `append_to_cell`을 호출합니다. `null` 슬롯은 병합된 영역이며 `merges`의 anchor 좌표로만 쓸 수 있습니다.
|
|
174
223
|
|
|
175
224
|
## 템플릿 작성 규칙
|
|
176
225
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
세 포맷(DOCX/PPTX/HWPX)의 공통 작업을 추상화:
|
|
4
4
|
- 템플릿 렌더링 ({{key}} 치환)
|
|
5
5
|
- 표 스키마 추출 (LLM 입력용)
|
|
6
|
-
- 셀 수정 / 행 추가
|
|
6
|
+
- 셀 수정 / 값 추가 / 행 추가
|
|
7
7
|
"""
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
@@ -13,6 +13,28 @@ from pathlib import Path
|
|
|
13
13
|
from typing import Any
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
# ---- custom exceptions -----------------------------------------------------
|
|
17
|
+
# 표준 예외를 상속해 기존 ``except ValueError/IndexError`` 흐름과 호환.
|
|
18
|
+
|
|
19
|
+
class MergedCellWriteError(ValueError):
|
|
20
|
+
"""병합 영역의 non-anchor 좌표에 쓰기를 시도했을 때."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CellOutOfBoundsError(IndexError):
|
|
24
|
+
"""(row, col)이 표 경계를 벗어남."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TableIndexError(IndexError):
|
|
28
|
+
"""table_index로 표를 찾지 못함."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class NotImplementedForFormat(NotImplementedError):
|
|
32
|
+
"""특정 포맷이 지원하지 않는 연산."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ---- dataclasses -----------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
|
|
16
38
|
@dataclass
|
|
17
39
|
class MergeInfo:
|
|
18
40
|
"""병합 셀 정보. anchor=(row,col)에서 span=(rows,cols)만큼 병합."""
|
|
@@ -51,6 +73,34 @@ class TableSchema:
|
|
|
51
73
|
}
|
|
52
74
|
|
|
53
75
|
|
|
76
|
+
@dataclass
|
|
77
|
+
class CellContent:
|
|
78
|
+
"""단일 셀의 전체 내용 + 병합/중첩 메타.
|
|
79
|
+
|
|
80
|
+
프리뷰가 max_cell_len으로 잘리는 것과 달리 ``text``는 전체 본문을 보유.
|
|
81
|
+
"""
|
|
82
|
+
row: int
|
|
83
|
+
col: int
|
|
84
|
+
text: str # 셀 전체 평문 (자르지 않음)
|
|
85
|
+
paragraphs: list[str] # paragraph 단위 분리
|
|
86
|
+
is_anchor: bool # (row,col)이 병합 앵커인지
|
|
87
|
+
anchor: tuple[int, int] # 이 logical 위치의 앵커 좌표
|
|
88
|
+
span: tuple[int, int] # (row_span, col_span)
|
|
89
|
+
nested_table_indices: list[int] = field(default_factory=list)
|
|
90
|
+
|
|
91
|
+
def to_dict(self) -> dict[str, Any]:
|
|
92
|
+
return {
|
|
93
|
+
"row": self.row,
|
|
94
|
+
"col": self.col,
|
|
95
|
+
"text": self.text,
|
|
96
|
+
"paragraphs": self.paragraphs,
|
|
97
|
+
"is_anchor": self.is_anchor,
|
|
98
|
+
"anchor": list(self.anchor),
|
|
99
|
+
"span": list(self.span),
|
|
100
|
+
"nested_table_indices": self.nested_table_indices,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
54
104
|
@dataclass
|
|
55
105
|
class DocumentSchema:
|
|
56
106
|
"""문서 전체 스키마."""
|
|
@@ -98,6 +148,10 @@ class DocumentAdapter(ABC):
|
|
|
98
148
|
preview_rows: int = 4, max_cell_len: int = 40) -> list[TableSchema]:
|
|
99
149
|
"""필터 조건을 만족하는 표 스키마 목록."""
|
|
100
150
|
|
|
151
|
+
@abstractmethod
|
|
152
|
+
def get_cell(self, table_index: int, row: int, col: int) -> CellContent:
|
|
153
|
+
"""셀 단건 조회. preview로 잘린 전체 텍스트와 병합/중첩 메타 반환."""
|
|
154
|
+
|
|
101
155
|
def get_schema(self) -> DocumentSchema:
|
|
102
156
|
return DocumentSchema(
|
|
103
157
|
format=self.format,
|
|
@@ -115,6 +169,15 @@ class DocumentAdapter(ABC):
|
|
|
115
169
|
def set_cell(self, table_index: int, row: int, col: int, value: str) -> str:
|
|
116
170
|
"""셀 값 교체. 원래 값 반환."""
|
|
117
171
|
|
|
172
|
+
@abstractmethod
|
|
173
|
+
def append_to_cell(self, table_index: int, row: int, col: int, value: str,
|
|
174
|
+
separator: str = " ") -> str:
|
|
175
|
+
"""기존 셀 텍스트 뒤에 ``separator + value``를 덧붙임.
|
|
176
|
+
|
|
177
|
+
라벨(예: "성 명")을 유지한 채 사용자 입력을 추가하는 용도.
|
|
178
|
+
빈 셀이면 separator 없이 value만 기록. 원래 값 반환.
|
|
179
|
+
"""
|
|
180
|
+
|
|
118
181
|
@abstractmethod
|
|
119
182
|
def append_row(self, table_index: int, values: list[str]) -> None:
|
|
120
183
|
"""표 끝에 새 행 추가."""
|