document-adapter 0.7.2__tar.gz → 0.8.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.7.2/document_adapter.egg-info → document_adapter-0.8.0}/PKG-INFO +1 -1
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter/base.py +61 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter/hwpx_adapter.py +15 -5
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter/pptx_adapter.py +71 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter/tools.py +86 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0/document_adapter.egg-info}/PKG-INFO +1 -1
- {document_adapter-0.7.2 → document_adapter-0.8.0}/pyproject.toml +1 -1
- {document_adapter-0.7.2 → document_adapter-0.8.0}/tests/test_smoke.py +72 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/LICENSE +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/NOTICE +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/README.md +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter/__init__.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter/docx_adapter.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter/hwpx_core/__init__.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter/hwpx_core/constants.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter/hwpx_core/grid.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter/hwpx_core/package.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter/hwpx_core/paragraph.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter/mcp_server.py +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter.egg-info/SOURCES.txt +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter.egg-info/dependency_links.txt +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter.egg-info/entry_points.txt +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter.egg-info/requires.txt +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter.egg-info/top_level.txt +0 -0
- {document_adapter-0.7.2 → document_adapter-0.8.0}/setup.cfg +0 -0
|
@@ -183,6 +183,38 @@ class TableSchema:
|
|
|
183
183
|
return d
|
|
184
184
|
|
|
185
185
|
|
|
186
|
+
@dataclass
|
|
187
|
+
class ShapeInfo:
|
|
188
|
+
"""PPTX 의 표 외 shape (textbox / placeholder / 도형 내 텍스트) 메타.
|
|
189
|
+
|
|
190
|
+
``shape_id`` 는 슬라이드 내 고유 숫자. ``name`` 은 사람 이름 (예: "Title 1").
|
|
191
|
+
``text`` 는 full text. ``text_preview`` 는 40자 컷.
|
|
192
|
+
"""
|
|
193
|
+
slide_index: int
|
|
194
|
+
shape_id: int
|
|
195
|
+
name: str
|
|
196
|
+
kind: str # shape 종류 (placeholder / text_box / group / picture 등)
|
|
197
|
+
has_text: bool
|
|
198
|
+
text: str = ""
|
|
199
|
+
text_preview: str = ""
|
|
200
|
+
placeholder_type: str | None = None
|
|
201
|
+
|
|
202
|
+
def to_dict(self) -> dict[str, Any]:
|
|
203
|
+
d = {
|
|
204
|
+
"slide_index": self.slide_index,
|
|
205
|
+
"shape_id": self.shape_id,
|
|
206
|
+
"name": self.name,
|
|
207
|
+
"kind": self.kind,
|
|
208
|
+
"has_text": self.has_text,
|
|
209
|
+
"text_preview": self.text_preview,
|
|
210
|
+
}
|
|
211
|
+
if self.text and self.text != self.text_preview:
|
|
212
|
+
d["text"] = self.text
|
|
213
|
+
if self.placeholder_type:
|
|
214
|
+
d["placeholder_type"] = self.placeholder_type
|
|
215
|
+
return d
|
|
216
|
+
|
|
217
|
+
|
|
186
218
|
@dataclass
|
|
187
219
|
class CellContent:
|
|
188
220
|
"""단일 셀의 전체 내용 + 병합/중첩 메타 + 크기 힌트.
|
|
@@ -304,6 +336,35 @@ class DocumentAdapter(ABC):
|
|
|
304
336
|
def append_row(self, table_index: int, values: list[str]) -> None:
|
|
305
337
|
"""표 끝에 새 행 추가."""
|
|
306
338
|
|
|
339
|
+
# ---- shape text (v0.8+, PPTX 전용) ----
|
|
340
|
+
def get_shapes(
|
|
341
|
+
self,
|
|
342
|
+
slide_index: int | None = None,
|
|
343
|
+
min_text_len: int = 1,
|
|
344
|
+
max_preview: int = 40,
|
|
345
|
+
) -> list[ShapeInfo]:
|
|
346
|
+
"""표 외 shape (textbox / placeholder / 도형 텍스트) 목록.
|
|
347
|
+
|
|
348
|
+
PPTX 에서만 실질적 의미가 있다. DOCX/HWPX 는 기본 빈 리스트 반환
|
|
349
|
+
(해당 포맷은 표와 paragraph 위주로 편집).
|
|
350
|
+
"""
|
|
351
|
+
return []
|
|
352
|
+
|
|
353
|
+
def set_shape_text(
|
|
354
|
+
self,
|
|
355
|
+
slide_index: int,
|
|
356
|
+
shape_id: int,
|
|
357
|
+
text: str,
|
|
358
|
+
) -> str:
|
|
359
|
+
"""shape 의 텍스트 프레임을 text 로 교체. 기존 run-level 포맷 보존.
|
|
360
|
+
|
|
361
|
+
Returns: 기존 텍스트.
|
|
362
|
+
PPTX 만 지원 — 그 외 포맷은 NotImplementedForFormat.
|
|
363
|
+
"""
|
|
364
|
+
raise NotImplementedForFormat(
|
|
365
|
+
f"{self.format} does not support shape-level text editing"
|
|
366
|
+
)
|
|
367
|
+
|
|
307
368
|
# ---- label-based form filling ----
|
|
308
369
|
def fill_form(
|
|
309
370
|
self,
|
|
@@ -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) 반환."""
|
|
@@ -23,6 +23,7 @@ from .base import (
|
|
|
23
23
|
MergeInfo,
|
|
24
24
|
MergedCellWriteError,
|
|
25
25
|
NotImplementedForFormat,
|
|
26
|
+
ShapeInfo,
|
|
26
27
|
TableIndexError,
|
|
27
28
|
TableSchema,
|
|
28
29
|
)
|
|
@@ -270,6 +271,76 @@ class PptxAdapter(DocumentAdapter):
|
|
|
270
271
|
char_count=len(text),
|
|
271
272
|
)
|
|
272
273
|
|
|
274
|
+
# ---- shape text (v0.8+) ----
|
|
275
|
+
def get_shapes(
|
|
276
|
+
self,
|
|
277
|
+
slide_index: int | None = None,
|
|
278
|
+
min_text_len: int = 1,
|
|
279
|
+
max_preview: int = 40,
|
|
280
|
+
) -> list[ShapeInfo]:
|
|
281
|
+
"""표 외 shape (textbox / placeholder / 도형 텍스트) 수집.
|
|
282
|
+
|
|
283
|
+
``slide_index`` 는 1-based. None 이면 전체.
|
|
284
|
+
``min_text_len`` 미만의 텍스트는 제외 (0 을 주면 빈 shape 도 포함).
|
|
285
|
+
"""
|
|
286
|
+
shapes_out: list[ShapeInfo] = []
|
|
287
|
+
for s_idx, slide in enumerate(self._prs.slides, 1):
|
|
288
|
+
if slide_index is not None and s_idx != slide_index:
|
|
289
|
+
continue
|
|
290
|
+
for shape in slide.shapes:
|
|
291
|
+
if shape.has_table:
|
|
292
|
+
continue # 표는 get_tables 로
|
|
293
|
+
if not shape.has_text_frame:
|
|
294
|
+
continue
|
|
295
|
+
text = (shape.text_frame.text or "").strip()
|
|
296
|
+
if len(text) < min_text_len:
|
|
297
|
+
continue
|
|
298
|
+
ph_type = None
|
|
299
|
+
try:
|
|
300
|
+
ph = shape.placeholder_format
|
|
301
|
+
if ph is not None and ph.type is not None:
|
|
302
|
+
ph_type = str(ph.type).rsplit(".", 1)[-1]
|
|
303
|
+
except ValueError:
|
|
304
|
+
pass
|
|
305
|
+
kind = "placeholder" if ph_type else "text_box"
|
|
306
|
+
shapes_out.append(
|
|
307
|
+
ShapeInfo(
|
|
308
|
+
slide_index=s_idx,
|
|
309
|
+
shape_id=shape.shape_id,
|
|
310
|
+
name=shape.name,
|
|
311
|
+
kind=kind,
|
|
312
|
+
has_text=bool(text),
|
|
313
|
+
text=text,
|
|
314
|
+
text_preview=text[:max_preview],
|
|
315
|
+
placeholder_type=ph_type,
|
|
316
|
+
)
|
|
317
|
+
)
|
|
318
|
+
return shapes_out
|
|
319
|
+
|
|
320
|
+
def set_shape_text(
|
|
321
|
+
self,
|
|
322
|
+
slide_index: int,
|
|
323
|
+
shape_id: int,
|
|
324
|
+
text: str,
|
|
325
|
+
) -> str:
|
|
326
|
+
"""shape 의 텍스트를 text 로 교체. 기존 run-level 포맷 보존."""
|
|
327
|
+
for s_idx, slide in enumerate(self._prs.slides, 1):
|
|
328
|
+
if s_idx != slide_index:
|
|
329
|
+
continue
|
|
330
|
+
for shape in slide.shapes:
|
|
331
|
+
if shape.shape_id != shape_id:
|
|
332
|
+
continue
|
|
333
|
+
if not shape.has_text_frame:
|
|
334
|
+
raise ValueError(
|
|
335
|
+
f"shape {shape_id} on slide {slide_index} has no text frame"
|
|
336
|
+
)
|
|
337
|
+
old = shape.text_frame.text or ""
|
|
338
|
+
_set_text_frame_preserving_format(shape.text_frame, text)
|
|
339
|
+
return old
|
|
340
|
+
raise ValueError(
|
|
341
|
+
f"shape not found: slide_index={slide_index}, shape_id={shape_id}"
|
|
342
|
+
)
|
|
343
|
+
|
|
273
344
|
# ---- editing ----
|
|
274
345
|
def render_template(self, context: dict[str, Any]) -> None:
|
|
275
346
|
"""paragraph 단위로 {{key}}를 치환. run이 쪼개진 경우를 처리하기 위해
|
|
@@ -172,6 +172,53 @@ TOOL_DEFINITIONS: list[dict[str, Any]] = [
|
|
|
172
172
|
"required": ["path", "table_index", "values"],
|
|
173
173
|
},
|
|
174
174
|
},
|
|
175
|
+
{
|
|
176
|
+
"name": "get_shapes",
|
|
177
|
+
"description": (
|
|
178
|
+
"**PPTX 전용**. 표 외 shape (textbox / placeholder / 도형 내 텍스트) 목록을 "
|
|
179
|
+
"반환한다. PPTX 는 17 슬라이드에 걸쳐 표 4 개 외에 수십 개의 textbox / "
|
|
180
|
+
"placeholder 로 내용이 채워져 있으므로, 전체 편집에는 get_tables 만으로 "
|
|
181
|
+
"부족하다. "
|
|
182
|
+
"반환: shapes[] — 각 항목에 slide_index (1-based), shape_id (슬라이드 내 고유 "
|
|
183
|
+
"숫자), name, kind (placeholder/text_box), text/text_preview, placeholder_type. "
|
|
184
|
+
"DOCX/HWPX 는 빈 리스트 반환 (표와 paragraph 중심이라 shape 편집 개념 약함)."
|
|
185
|
+
),
|
|
186
|
+
"input_schema": {
|
|
187
|
+
"type": "object",
|
|
188
|
+
"properties": {
|
|
189
|
+
"path": {"type": "string"},
|
|
190
|
+
"slide_index": {
|
|
191
|
+
"type": "integer",
|
|
192
|
+
"description": "1-based. 생략 시 전체 슬라이드.",
|
|
193
|
+
},
|
|
194
|
+
"min_text_len": {
|
|
195
|
+
"type": "integer",
|
|
196
|
+
"default": 1,
|
|
197
|
+
"description": "이 값 미만 길이의 텍스트 shape 는 제외.",
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
"required": ["path"],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"name": "set_shape_text",
|
|
205
|
+
"description": (
|
|
206
|
+
"**PPTX 전용**. shape (textbox/placeholder) 의 텍스트를 교체한다. run-level "
|
|
207
|
+
"포맷 (폰트/크기/색상) 은 보존. get_shapes 로 slide_index + shape_id 파악 후 "
|
|
208
|
+
"호출. 표 셀은 이 도구 아닌 set_cell 을 쓸 것."
|
|
209
|
+
),
|
|
210
|
+
"input_schema": {
|
|
211
|
+
"type": "object",
|
|
212
|
+
"properties": {
|
|
213
|
+
"path": {"type": "string"},
|
|
214
|
+
"slide_index": {"type": "integer", "description": "1-based"},
|
|
215
|
+
"shape_id": {"type": "integer"},
|
|
216
|
+
"text": {"type": "string"},
|
|
217
|
+
"output_path": {"type": "string"},
|
|
218
|
+
},
|
|
219
|
+
"required": ["path", "slide_index", "shape_id", "text"],
|
|
220
|
+
},
|
|
221
|
+
},
|
|
175
222
|
{
|
|
176
223
|
"name": "fill_form",
|
|
177
224
|
"description": (
|
|
@@ -364,6 +411,43 @@ def append_row(path: str, table_index: int, values: list[str],
|
|
|
364
411
|
}
|
|
365
412
|
|
|
366
413
|
|
|
414
|
+
def get_shapes(path: str, slide_index: int | None = None,
|
|
415
|
+
min_text_len: int = 1) -> dict[str, Any]:
|
|
416
|
+
doc = load(path)
|
|
417
|
+
try:
|
|
418
|
+
shapes = doc.get_shapes(slide_index=slide_index, min_text_len=min_text_len)
|
|
419
|
+
finally:
|
|
420
|
+
doc.close()
|
|
421
|
+
return {
|
|
422
|
+
"format": doc.format,
|
|
423
|
+
"source": str(path),
|
|
424
|
+
"shape_count": len(shapes),
|
|
425
|
+
"shapes": [s.to_dict() for s in shapes],
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def set_shape_text(path: str, slide_index: int, shape_id: int, text: str,
|
|
430
|
+
output_path: str | None = None) -> dict[str, Any]:
|
|
431
|
+
target = Path(output_path) if output_path else Path(path)
|
|
432
|
+
if output_path and Path(path) != target:
|
|
433
|
+
shutil.copy2(path, target)
|
|
434
|
+
|
|
435
|
+
doc = load(target)
|
|
436
|
+
try:
|
|
437
|
+
old = doc.set_shape_text(slide_index, shape_id, text)
|
|
438
|
+
doc.save()
|
|
439
|
+
finally:
|
|
440
|
+
doc.close()
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
"output_path": str(target),
|
|
444
|
+
"slide_index": slide_index,
|
|
445
|
+
"shape_id": shape_id,
|
|
446
|
+
"previous_text": old,
|
|
447
|
+
"new_text": text,
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
|
|
367
451
|
def fill_form(path: str, data: dict[str, str],
|
|
368
452
|
direction: str = "auto", strict: bool = False,
|
|
369
453
|
output_path: str | None = None) -> dict[str, Any]:
|
|
@@ -392,6 +476,8 @@ TOOL_HANDLERS = {
|
|
|
392
476
|
"append_to_cell": append_to_cell,
|
|
393
477
|
"append_row": append_row,
|
|
394
478
|
"fill_form": fill_form,
|
|
479
|
+
"get_shapes": get_shapes,
|
|
480
|
+
"set_shape_text": set_shape_text,
|
|
395
481
|
}
|
|
396
482
|
|
|
397
483
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "document-adapter"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.0"
|
|
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
|
{document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{document_adapter-0.7.2 → document_adapter-0.8.0}/document_adapter.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|