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.
Files changed (25) hide show
  1. {document_adapter-0.7.2/document_adapter.egg-info → document_adapter-0.7.3}/PKG-INFO +1 -1
  2. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/hwpx_adapter.py +15 -5
  3. {document_adapter-0.7.2 → document_adapter-0.7.3/document_adapter.egg-info}/PKG-INFO +1 -1
  4. {document_adapter-0.7.2 → document_adapter-0.7.3}/pyproject.toml +1 -1
  5. {document_adapter-0.7.2 → document_adapter-0.7.3}/tests/test_smoke.py +72 -0
  6. {document_adapter-0.7.2 → document_adapter-0.7.3}/LICENSE +0 -0
  7. {document_adapter-0.7.2 → document_adapter-0.7.3}/NOTICE +0 -0
  8. {document_adapter-0.7.2 → document_adapter-0.7.3}/README.md +0 -0
  9. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/__init__.py +0 -0
  10. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/base.py +0 -0
  11. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/docx_adapter.py +0 -0
  12. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/hwpx_core/__init__.py +0 -0
  13. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/hwpx_core/constants.py +0 -0
  14. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/hwpx_core/grid.py +0 -0
  15. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/hwpx_core/package.py +0 -0
  16. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/hwpx_core/paragraph.py +0 -0
  17. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/mcp_server.py +0 -0
  18. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/pptx_adapter.py +0 -0
  19. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter/tools.py +0 -0
  20. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter.egg-info/SOURCES.txt +0 -0
  21. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter.egg-info/dependency_links.txt +0 -0
  22. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter.egg-info/entry_points.txt +0 -0
  23. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter.egg-info/requires.txt +0 -0
  24. {document_adapter-0.7.2 → document_adapter-0.7.3}/document_adapter.egg-info/top_level.txt +0 -0
  25. {document_adapter-0.7.2 → document_adapter-0.7.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: document-adapter
3
- Version: 0.7.2
3
+ Version: 0.7.3
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
@@ -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
- # 최상위 <hp:tbl> 찾기: root > hp:p > hp:run > hp:tbl
122
- for p in root.findall(HP_P):
123
- for run in p.findall(HP_RUN):
124
- for tbl in run.findall(HP_TBL):
125
- yield from walk(tbl, "", section_name)
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) 반환."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: document-adapter
3
- Version: 0.7.2
3
+ Version: 0.7.3
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "document-adapter"
7
- version = "0.7.2"
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()