document-adapter 0.7.2__tar.gz → 0.7.3__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.7.2/document_adapter.egg-info → document_adapter-0.7.3}/PKG-INFO +1 -1
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/hwpx_adapter.py +15 -5
- {document_adapter-0.7.2 → document_adapter-0.7.3/document_adapter.egg-info}/PKG-INFO +1 -1
- {document_adapter-0.7.2 → document_adapter-0.7.3}/pyproject.toml +1 -1
- {document_adapter-0.7.2 → document_adapter-0.7.3}/tests/test_smoke.py +72 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/LICENSE +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/NOTICE +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/README.md +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/__init__.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/base.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/docx_adapter.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/hwpx_core/__init__.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/hwpx_core/constants.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/hwpx_core/grid.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/hwpx_core/package.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/hwpx_core/paragraph.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/mcp_server.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/pptx_adapter.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/tools.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter.egg-info/SOURCES.txt +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter.egg-info/dependency_links.txt +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter.egg-info/entry_points.txt +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter.egg-info/requires.txt +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter.egg-info/top_level.txt +0 -0
- {document_adapter-0.7.2 → document_adapter-0.7.3}/setup.cfg +0 -0
|
@@ -118,11 +118,21 @@ class HwpxAdapter(DocumentAdapter):
|
|
|
118
118
|
yield from walk(child_tbl, child_parent, section_name)
|
|
119
119
|
|
|
120
120
|
for section_name, root in self._pkg.iter_section_roots():
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
# 섹션의 모든 <hp:tbl> descendant 중 "top-level" 만 선별.
|
|
122
|
+
# top-level = <hp:tc> (cell) 의 descendant 가 아닌 것.
|
|
123
|
+
# 이러면 <hp:p>/<hp:run>/<hp:ctrl>/<hp:header>/<hp:footer>/도형 등
|
|
124
|
+
# 어디에 놓여있든 표를 누락 없이 발견 (xgen-doc2chunk 의 _process_ctrl
|
|
125
|
+
# 가 처리하던 범위 포함). cell 내부 <hp:tbl> 은 walk 재귀에서 처리.
|
|
126
|
+
for tbl in root.iter(HP_TBL):
|
|
127
|
+
parent = tbl.getparent()
|
|
128
|
+
is_in_cell = False
|
|
129
|
+
while parent is not None:
|
|
130
|
+
if parent.tag == HP_TC:
|
|
131
|
+
is_in_cell = True
|
|
132
|
+
break
|
|
133
|
+
parent = parent.getparent()
|
|
134
|
+
if not is_in_cell:
|
|
135
|
+
yield from walk(tbl, "", section_name)
|
|
126
136
|
|
|
127
137
|
def _get_table(self, table_index: int) -> tuple[etree._Element, str]:
|
|
128
138
|
"""flat_index로 (tbl_element, section_part_name) 반환."""
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "document-adapter"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.3"
|
|
8
8
|
description = "LLM-friendly document template editing (DOCX/PPTX/HWPX) with MCP server and Claude API tool-use support"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -1207,3 +1207,75 @@ def test_fill_form_label_normalization(tmp_path: Path) -> None:
|
|
|
1207
1207
|
|
|
1208
1208
|
assert result["not_found"] == []
|
|
1209
1209
|
assert len(result["filled"]) == 1
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
# ---- v0.7.3: <hp:ctrl> 내부 테이블 탐지 회귀 방지 ----
|
|
1213
|
+
|
|
1214
|
+
def _make_hwpx_with_ctrl_embedded_table(path: Path) -> None:
|
|
1215
|
+
"""기본 2x2 표 1 개에 더해 <hp:ctrl> 내부에 동일 구조 표를 1 개 추가.
|
|
1216
|
+
|
|
1217
|
+
HWPX 양식에서 header/footer/footNote 등은 <hp:ctrl> 하위에 들어가므로
|
|
1218
|
+
LLM 이 편집해야 할 표가 ctrl 내부에 숨어있을 수 있다. adapter 가 이를
|
|
1219
|
+
top-level 로 인식하고 편집 가능해야 한다.
|
|
1220
|
+
"""
|
|
1221
|
+
import zipfile
|
|
1222
|
+
from copy import deepcopy
|
|
1223
|
+
from lxml import etree
|
|
1224
|
+
|
|
1225
|
+
# 1. 기본 HWPX 생성
|
|
1226
|
+
doc = HwpxDocument.new()
|
|
1227
|
+
doc.add_paragraph("before")
|
|
1228
|
+
doc.add_table(2, 2)
|
|
1229
|
+
doc.add_paragraph("after")
|
|
1230
|
+
doc.save_to_path(path)
|
|
1231
|
+
doc.close()
|
|
1232
|
+
|
|
1233
|
+
HP = "{http://www.hancom.co.kr/hwpml/2011/paragraph}"
|
|
1234
|
+
|
|
1235
|
+
# 2. section0.xml 에 <hp:ctrl> 래핑된 tbl 추가
|
|
1236
|
+
with zipfile.ZipFile(path, "r") as zin:
|
|
1237
|
+
section_xml = zin.read("Contents/section0.xml")
|
|
1238
|
+
other = {n: zin.read(n) for n in zin.namelist() if n != "Contents/section0.xml"}
|
|
1239
|
+
infos = {n: zin.getinfo(n) for n in zin.namelist()}
|
|
1240
|
+
|
|
1241
|
+
root = etree.fromstring(section_xml)
|
|
1242
|
+
existing_tbl = next(root.iter(f"{HP}tbl"))
|
|
1243
|
+
cloned = deepcopy(existing_tbl)
|
|
1244
|
+
new_p = etree.SubElement(root, f"{HP}p")
|
|
1245
|
+
new_run = etree.SubElement(new_p, f"{HP}run")
|
|
1246
|
+
new_ctrl = etree.SubElement(new_run, f"{HP}ctrl")
|
|
1247
|
+
new_ctrl.append(cloned)
|
|
1248
|
+
|
|
1249
|
+
modified_xml = etree.tostring(
|
|
1250
|
+
root, xml_declaration=True, encoding="UTF-8", standalone=True
|
|
1251
|
+
)
|
|
1252
|
+
with zipfile.ZipFile(path, "w") as zout:
|
|
1253
|
+
for name, info in infos.items():
|
|
1254
|
+
data = modified_xml if name == "Contents/section0.xml" else other[name]
|
|
1255
|
+
new_info = zipfile.ZipInfo(filename=name, date_time=info.date_time)
|
|
1256
|
+
new_info.compress_type = info.compress_type
|
|
1257
|
+
zout.writestr(new_info, data)
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
def test_hwpx_ctrl_embedded_table_is_found(tmp_path: Path) -> None:
|
|
1261
|
+
"""<hp:ctrl> 내부 tbl 이 top-level 로 발견되고 편집 가능해야 한다 (v0.7.3 회귀 방지)."""
|
|
1262
|
+
src = tmp_path / "ctrl_embedded.hwpx"
|
|
1263
|
+
_make_hwpx_with_ctrl_embedded_table(src)
|
|
1264
|
+
|
|
1265
|
+
adapter = load(src)
|
|
1266
|
+
try:
|
|
1267
|
+
tables = adapter.get_tables()
|
|
1268
|
+
# 2 개 (top-level + ctrl 내부) 전부 발견
|
|
1269
|
+
assert len(tables) == 2, f"expected 2 tables (top-level + ctrl inner), got {len(tables)}"
|
|
1270
|
+
# 두 번째 (ctrl 내부) 표 편집도 가능해야
|
|
1271
|
+
adapter.set_cell(1, 0, 0, "__CTRL_INNER_EDIT__")
|
|
1272
|
+
adapter.save()
|
|
1273
|
+
finally:
|
|
1274
|
+
adapter.close()
|
|
1275
|
+
|
|
1276
|
+
adapter2 = load(src)
|
|
1277
|
+
try:
|
|
1278
|
+
cell = adapter2.get_cell(1, 0, 0)
|
|
1279
|
+
assert cell.text.strip() == "__CTRL_INNER_EDIT__"
|
|
1280
|
+
finally:
|
|
1281
|
+
adapter2.close()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|