licos-dev-sdk 0.4.3__tar.gz → 0.4.4__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.
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/DOCX.md +15 -1
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/PKG-INFO +1 -1
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/pyproject.toml +1 -1
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/__init__.py +2 -1
- licos_dev_sdk-0.4.4/src/licos_dev_sdk/document_docx_expectations.py +30 -0
- licos_dev_sdk-0.4.4/src/licos_dev_sdk/document_docx_inspect.py +90 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/document_docx_structure.py +3 -6
- licos_dev_sdk-0.4.4/tests/test_docx_inspect.py +113 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/.gitignore +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/_utils.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/archive.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/chart.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/data.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/diagram.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/document.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/document_common.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/document_docx.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/document_docx_edit.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/document_docx_layout.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/document_docx_package.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/document_docx_preservation.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/document_docx_table.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/document_docx_table_actions.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/document_read.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/document_table_export.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/image.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/model.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/observability.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/pdf_inspect.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/ppt_master_runtime.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/presentation.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/presentation_canvas.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/presentation_inspect.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/spreadsheet.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/spreadsheet_edit.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/spreadsheet_html.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/spreadsheet_inspect.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/web.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/tests/test_document_read.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/tests/test_document_spreadsheet.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/tests/test_document_table_export.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/tests/test_docx_structure.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/tests/test_docx_table_edit.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/tests/test_html_docx_features.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/tests/test_model.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/tests/test_observability.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/tests/test_output_paths.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/tests/test_ppt_master_runtime.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/tests/test_presentation.py +0 -0
- {licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/tests/test_spreadsheet_html_edit.py +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Word SDK / CLI 0.4.
|
|
1
|
+
# Word SDK / CLI 0.4.4
|
|
2
2
|
|
|
3
3
|
Cloud document execution stays in `licos-dev-sdk` and `licos-dev`. CLI commands
|
|
4
4
|
only parse arguments and call the SDK; no additional Agent-native Word tools are
|
|
@@ -10,6 +10,7 @@ Use `licos-dev --version` to query the installed document CLI version.
|
|
|
10
10
|
| CLI command | SDK function | Input |
|
|
11
11
|
| --- | --- | --- |
|
|
12
12
|
| `docx-structure` | `read_docx_structure` | File, blocks/paragraphs/tables, page |
|
|
13
|
+
| `docx-inspect` | `inspect_docx` | File, all/fields/images/tables, offset/limit |
|
|
13
14
|
| `docx-target` | `read_docx_target` | File, exact target, text cursor |
|
|
14
15
|
| `docx-replace` | `replace_docx_text` | Source/output, revision, target, search/replacement |
|
|
15
16
|
| `docx-delete-blocks` | `delete_docx_blocks` | Source/output, revision, inclusive block range |
|
|
@@ -62,6 +63,19 @@ Use `licos-dev --version` to query the installed document CLI version.
|
|
|
62
63
|
|
|
63
64
|
## Verification
|
|
64
65
|
|
|
66
|
+
`docx-inspect` reports native field instructions (including split-run TOC fields),
|
|
67
|
+
field completeness, embedded/external/missing image references, and table dimensions
|
|
68
|
+
across document stories, including headers and footers. Each scope has its own
|
|
69
|
+
`next_offset`; counts cover the entire document. `update_fields_on_open` describes
|
|
70
|
+
the Word setting; neither it nor `toc_count` certifies rendered pagination.
|
|
71
|
+
Use `docx-structure` for editable body targets.
|
|
72
|
+
|
|
73
|
+
Batch verification entries require `target`, exactly one of `text` or `text_file`,
|
|
74
|
+
and optional `mode`. JSON `text_file` is a UTF-8 path relative to the JSON file's
|
|
75
|
+
directory. Single CLI `--text-file` is relative to cwd. SDK `base_dir` defaults to
|
|
76
|
+
cwd and can set the file resolution directory explicitly. Empty text is valid;
|
|
77
|
+
unknown keys, conflicting inputs and file errors identify the assertion and field.
|
|
78
|
+
|
|
65
79
|
Run Python tests serially with the current source on `PYTHONPATH`:
|
|
66
80
|
|
|
67
81
|
```text
|
|
@@ -30,6 +30,7 @@ def __getattr__(name: str):
|
|
|
30
30
|
"create_docx_from_template": ("document", "create_docx_from_template"),
|
|
31
31
|
"edit_docx": ("document", "edit_docx"),
|
|
32
32
|
"read_docx_structure": ("document_docx_structure", "read_docx_structure"),
|
|
33
|
+
"inspect_docx": ("document_docx_inspect", "inspect_docx"),
|
|
33
34
|
"read_docx_target": ("document_docx_structure", "read_docx_target"),
|
|
34
35
|
"edit_docx_structure": ("document_docx_structure", "edit_docx_structure"),
|
|
35
36
|
"replace_docx_text": ("document_docx_structure", "replace_docx_text"),
|
|
@@ -108,7 +109,7 @@ def __getattr__(name: str):
|
|
|
108
109
|
|
|
109
110
|
|
|
110
111
|
__all__ = [
|
|
111
|
-
"read_docx_structure", "read_docx_target", "edit_docx_structure",
|
|
112
|
+
"inspect_docx", "read_docx_structure", "read_docx_target", "edit_docx_structure",
|
|
112
113
|
"replace_docx_text", "delete_docx_blocks", "verify_docx", "set_docx_cell", "insert_docx_rows",
|
|
113
114
|
"create_json", "create_xml", "create_yaml",
|
|
114
115
|
"create_zip", "create_tar_gz",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""The shared single/batch verification assertion contract."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def expected_text(expectation, index, base_dir):
|
|
7
|
+
prefix = f"expectation {index}"
|
|
8
|
+
if not isinstance(expectation, dict):
|
|
9
|
+
raise ValueError(f"{prefix} must be an object")
|
|
10
|
+
unknown = set(expectation) - {"target", "text", "text_file", "mode"}
|
|
11
|
+
if unknown:
|
|
12
|
+
raise ValueError(f"{prefix}: unknown fields {', '.join(sorted(unknown))}; allowed: target, text, text_file, mode")
|
|
13
|
+
if "target" not in expectation:
|
|
14
|
+
raise ValueError(f"{prefix}.target is required")
|
|
15
|
+
if ("text" in expectation) == ("text_file" in expectation):
|
|
16
|
+
raise ValueError(f"{prefix}: provide exactly one of text and text_file")
|
|
17
|
+
field = "text_file" if "text_file" in expectation else "text"
|
|
18
|
+
value = expectation[field]
|
|
19
|
+
if not isinstance(value, str) or (field == "text_file" and not value):
|
|
20
|
+
raise ValueError(f"{prefix}.{field} must be {'a non-empty' if field == 'text_file' else 'a'} string")
|
|
21
|
+
mode = expectation.get("mode", "equals")
|
|
22
|
+
if mode not in ("equals", "contains", "absent"):
|
|
23
|
+
raise ValueError(f"{prefix}.mode must be equals, contains or absent")
|
|
24
|
+
if field == "text_file":
|
|
25
|
+
path = Path(base_dir) / Path(value).expanduser()
|
|
26
|
+
try:
|
|
27
|
+
value = path.read_text(encoding="utf-8")
|
|
28
|
+
except (OSError, UnicodeError) as exc:
|
|
29
|
+
raise ValueError(f"{prefix}.text_file: {exc}") from exc
|
|
30
|
+
return value, mode
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Inspect native Word fields, image references and tables without modifying a package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path, PurePosixPath
|
|
6
|
+
import posixpath
|
|
7
|
+
|
|
8
|
+
from lxml import etree
|
|
9
|
+
|
|
10
|
+
from .document_docx_package import DocxPackage
|
|
11
|
+
|
|
12
|
+
W = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}"
|
|
13
|
+
R = "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}"
|
|
14
|
+
A = "{http://schemas.openxmlformats.org/drawingml/2006/main}"
|
|
15
|
+
V = "{urn:schemas-microsoft-com:vml}"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _xml(data):
|
|
19
|
+
return etree.fromstring(data, etree.XMLParser(resolve_entities=False, no_network=True))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _fields(root, part):
|
|
23
|
+
fields, stack = [], []
|
|
24
|
+
for element in root.iter():
|
|
25
|
+
if element.tag == W + "fldSimple":
|
|
26
|
+
fields.append({"part": part, "instruction": element.get(W + "instr", ""), "complete": True})
|
|
27
|
+
elif element.tag == W + "fldChar":
|
|
28
|
+
kind = element.get(W + "fldCharType")
|
|
29
|
+
if kind == "begin":
|
|
30
|
+
field = {"part": part, "instruction": "", "complete": False}
|
|
31
|
+
fields.append(field)
|
|
32
|
+
stack.append((field, True))
|
|
33
|
+
elif kind == "separate" and stack:
|
|
34
|
+
stack[-1] = (stack[-1][0], False)
|
|
35
|
+
elif kind == "end" and stack:
|
|
36
|
+
stack.pop()[0]["complete"] = True
|
|
37
|
+
elif element.tag == W + "instrText" and stack and stack[-1][1]:
|
|
38
|
+
stack[-1][0]["instruction"] += element.text or ""
|
|
39
|
+
for field in fields:
|
|
40
|
+
instruction = field["instruction"].strip()
|
|
41
|
+
field["type"] = instruction.split(maxsplit=1)[0].upper() if instruction else ""
|
|
42
|
+
return fields
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def inspect_docx(source: str | Path, *, scope: str = "all", offset: int = 1, limit: int = 20) -> dict:
|
|
46
|
+
"""Return counts and paginated fields/images/tables. Field presence does not certify pagination."""
|
|
47
|
+
if scope not in {"all", "fields", "images", "tables"}:
|
|
48
|
+
raise ValueError("scope must be all, fields, images or tables")
|
|
49
|
+
if type(offset) is not int or offset < 1 or type(limit) is not int or not 1 <= limit <= 200:
|
|
50
|
+
raise ValueError("offset must be positive; limit must be 1-200")
|
|
51
|
+
package = DocxPackage(source)
|
|
52
|
+
parts = {info.filename: data for info, data in package.parts}
|
|
53
|
+
fields, images, tables = [], [], []
|
|
54
|
+
for part, data in parts.items():
|
|
55
|
+
if not part.startswith("word/") or not part.endswith(".xml"):
|
|
56
|
+
continue
|
|
57
|
+
root = package.root if part == "word/document.xml" else _xml(data)
|
|
58
|
+
fields.extend(_fields(root, part))
|
|
59
|
+
rel_path = str(PurePosixPath(part).parent / "_rels" / (PurePosixPath(part).name + ".rels"))
|
|
60
|
+
relationships = {rel.get("Id"): rel for rel in _xml(parts[rel_path])} if rel_path in parts else {}
|
|
61
|
+
for element in root.iter():
|
|
62
|
+
if element.tag == W + "tbl":
|
|
63
|
+
grid = element.find(W + "tblGrid")
|
|
64
|
+
tables.append({"part": part, "row_count": len(element.findall(W + "tr")),
|
|
65
|
+
"column_count": len(grid) if grid is not None else None})
|
|
66
|
+
if element.tag not in {A + "blip", V + "imagedata"}:
|
|
67
|
+
continue
|
|
68
|
+
rid = element.get(R + "embed") or element.get(R + "link") or element.get(R + "id")
|
|
69
|
+
rel = relationships.get(rid)
|
|
70
|
+
if rel is None:
|
|
71
|
+
images.append({"part": part, "relationship_id": rid, "status": "missing_relationship"})
|
|
72
|
+
continue
|
|
73
|
+
target = rel.get("Target", "")
|
|
74
|
+
external = rel.get("TargetMode") == "External"
|
|
75
|
+
target_part = None if external else posixpath.normpath(posixpath.join(posixpath.dirname(part), target)).lstrip("/")
|
|
76
|
+
images.append({"part": part, "relationship_id": rid, "target": target,
|
|
77
|
+
"external": external, "package_part": target_part,
|
|
78
|
+
"status": "external" if external else "embedded" if target_part in parts else "missing_part"})
|
|
79
|
+
settings = _xml(parts["word/settings.xml"]) if "word/settings.xml" in parts else None
|
|
80
|
+
update = settings.find(W + "updateFields") if settings is not None else None
|
|
81
|
+
report = {"path": str(package.path), "revision": package.revision,
|
|
82
|
+
"field_count": len(fields), "toc_count": sum(field["type"] == "TOC" for field in fields),
|
|
83
|
+
"image_count": len(images), "table_count": len(tables),
|
|
84
|
+
"update_fields_on_open": update is not None and update.get(W + "val", "true") not in {"false", "0", "off"},
|
|
85
|
+
"rendered_layout_verified": False}
|
|
86
|
+
for name, items in (("fields", fields), ("images", images), ("tables", tables)):
|
|
87
|
+
if scope in {"all", name}:
|
|
88
|
+
selected = items[offset - 1:offset - 1 + limit]
|
|
89
|
+
report[name] = {"items": selected, "next_offset": offset + len(selected) if offset - 1 + len(selected) < len(items) else None}
|
|
90
|
+
return report
|
|
@@ -202,7 +202,7 @@ def delete_docx_blocks(source: str | Path, output: str | Path, *, revision: str,
|
|
|
202
202
|
|
|
203
203
|
|
|
204
204
|
def verify_docx(source: str | Path, *, expectations: list[dict] | None = None,
|
|
205
|
-
original: str | Path | None = None) -> dict:
|
|
205
|
+
original: str | Path | None = None, base_dir: str | Path = ".") -> dict:
|
|
206
206
|
"""Check package structure and exact positions; absent assertions do not certify content."""
|
|
207
207
|
package = DocxPackage(source)
|
|
208
208
|
structure = _Structure(package.root)
|
|
@@ -212,12 +212,9 @@ def verify_docx(source: str | Path, *, expectations: list[dict] | None = None,
|
|
|
212
212
|
if not isinstance(expectations, list):
|
|
213
213
|
raise TypeError("expectations must be an array")
|
|
214
214
|
mismatches = []
|
|
215
|
+
from .document_docx_expectations import expected_text
|
|
215
216
|
for index, expectation in enumerate(expectations, 1):
|
|
216
|
-
|
|
217
|
-
raise ValueError(f"expectation {index} requires target,text and optional mode")
|
|
218
|
-
expected, mode = expectation["text"], expectation.get("mode", "equals")
|
|
219
|
-
if not isinstance(expected, str) or mode not in {"equals", "contains", "absent"}:
|
|
220
|
-
raise ValueError(f"expectation {index}: text must be a string; mode must be equals, contains or absent")
|
|
217
|
+
expected, mode = expected_text(expectation, index, base_dir)
|
|
221
218
|
actual = _text(structure.target(expectation["target"]))
|
|
222
219
|
passed = actual == expected if mode == "equals" else expected in actual if mode == "contains" else expected not in actual
|
|
223
220
|
if not passed:
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from io import BytesIO
|
|
3
|
+
from zipfile import ZipFile
|
|
4
|
+
|
|
5
|
+
from docx import Document
|
|
6
|
+
from docx.oxml import OxmlElement
|
|
7
|
+
from docx.oxml.ns import qn
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from licos_dev_sdk import inspect_docx, verify_docx
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def field_run(paragraph, tag, value):
|
|
14
|
+
element = OxmlElement('w:' + tag)
|
|
15
|
+
if tag == 'instrText':
|
|
16
|
+
element.text = value
|
|
17
|
+
else:
|
|
18
|
+
element.set(qn('w:fldCharType'), value)
|
|
19
|
+
paragraph.add_run()._r.append(element)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_inspect_native_fields_images_and_tables_without_rewriting(tmp_path):
|
|
23
|
+
path = tmp_path / 'report.docx'
|
|
24
|
+
doc = Document()
|
|
25
|
+
p = doc.add_paragraph()
|
|
26
|
+
field_run(p, 'fldChar', 'begin')
|
|
27
|
+
field_run(p, 'instrText', ' TO')
|
|
28
|
+
field_run(p, 'instrText', 'C \\o "1-3" ')
|
|
29
|
+
field_run(p, 'fldChar', 'separate')
|
|
30
|
+
p.add_run('Cached table of contents')
|
|
31
|
+
field_run(p, 'fldChar', 'end')
|
|
32
|
+
simple = OxmlElement('w:fldSimple')
|
|
33
|
+
simple.set(qn('w:instr'), 'PAGE')
|
|
34
|
+
doc.sections[0].footer.paragraphs[0]._p.append(simple)
|
|
35
|
+
png = base64.b64decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aTtQAAAAASUVORK5CYII=')
|
|
36
|
+
doc.add_picture(BytesIO(png))
|
|
37
|
+
doc.sections[0].header.paragraphs[0].add_run().add_picture(BytesIO(png))
|
|
38
|
+
doc.add_table(rows=3, cols=2)
|
|
39
|
+
update = OxmlElement('w:updateFields')
|
|
40
|
+
update.set(qn('w:val'), 'true')
|
|
41
|
+
doc.settings.element.append(update)
|
|
42
|
+
doc.save(path)
|
|
43
|
+
original = path.read_bytes()
|
|
44
|
+
report = inspect_docx(path, limit=1)
|
|
45
|
+
assert report['toc_count'] == 1 and report['field_count'] == 2
|
|
46
|
+
assert report['image_count'] == 2 and report['table_count'] == 1
|
|
47
|
+
assert report['fields']['items'][0]['complete']
|
|
48
|
+
assert report['fields']['items'][0]['instruction'] == ' TOC \\o "1-3" '
|
|
49
|
+
assert report['images']['next_offset'] == 2
|
|
50
|
+
assert report['tables']['items'][0] == {'part': 'word/document.xml', 'row_count': 3, 'column_count': 2}
|
|
51
|
+
second = inspect_docx(path, scope='images', offset=2)
|
|
52
|
+
assert second['images']['items'][0]['part'].startswith('word/header')
|
|
53
|
+
assert second['images']['items'][0]['status'] == 'embedded'
|
|
54
|
+
assert 'fields' not in second
|
|
55
|
+
assert report['update_fields_on_open'] and not report['rendered_layout_verified']
|
|
56
|
+
assert path.read_bytes() == original
|
|
57
|
+
with ZipFile(path) as archive:
|
|
58
|
+
parts = {name: archive.read(name) for name in archive.namelist()}
|
|
59
|
+
image_part = report['images']['items'][0]['package_part']
|
|
60
|
+
parts.pop(image_part)
|
|
61
|
+
with ZipFile(path, 'w') as archive:
|
|
62
|
+
for name, data in parts.items():
|
|
63
|
+
archive.writestr(name, data)
|
|
64
|
+
assert inspect_docx(path)['images']['items'][0]['status'] == 'missing_part'
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_incomplete_toc_and_external_or_missing_image_relationships(tmp_path):
|
|
68
|
+
path = tmp_path / 'linked.docx'
|
|
69
|
+
doc = Document()
|
|
70
|
+
p = doc.add_paragraph()
|
|
71
|
+
field_run(p, 'fldChar', 'begin')
|
|
72
|
+
field_run(p, 'instrText', 'TOC')
|
|
73
|
+
rid = doc.part.relate_to('https://example.com/image.png',
|
|
74
|
+
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', is_external=True)
|
|
75
|
+
for relationship in (rid, 'missing'):
|
|
76
|
+
blip = OxmlElement('a:blip')
|
|
77
|
+
blip.set(qn('r:link'), relationship)
|
|
78
|
+
p.add_run()._r.append(blip)
|
|
79
|
+
doc.save(path)
|
|
80
|
+
report = inspect_docx(path)
|
|
81
|
+
assert not report['fields']['items'][0]['complete']
|
|
82
|
+
assert [image['status'] for image in report['images']['items']] == ['external', 'missing_relationship']
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_batch_file_assertions_resolve_from_explicit_base_and_keep_exact_text(tmp_path, monkeypatch):
|
|
86
|
+
path = tmp_path / 'report.docx'
|
|
87
|
+
text = ' 中文🙂\tA\n' * 90 + ' '
|
|
88
|
+
doc = Document()
|
|
89
|
+
doc.add_paragraph(text)
|
|
90
|
+
doc.save(path)
|
|
91
|
+
expected = tmp_path / 'expected.txt'
|
|
92
|
+
expected.write_text(text, encoding='utf-8')
|
|
93
|
+
monkeypatch.chdir(tmp_path.parent)
|
|
94
|
+
assertion = {'target': {'block': 1}, 'text_file': 'expected.txt'}
|
|
95
|
+
assert verify_docx(path, expectations=[assertion], base_dir=tmp_path)['verified']
|
|
96
|
+
assertion['text_file'] = str(expected)
|
|
97
|
+
assert verify_docx(path, expectations=[assertion])['verified']
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@pytest.mark.parametrize(('assertion', 'error'), [
|
|
101
|
+
({'text-file': 'x.txt'}, 'unknown fields text-file'),
|
|
102
|
+
({'text': 'x', 'text_file': 'x.txt'}, 'exactly one'),
|
|
103
|
+
({'text_file': None}, 'text_file must be'),
|
|
104
|
+
({'text_file': 'missing.txt'}, 'expectation 1.text_file'),
|
|
105
|
+
({'text': '', 'mode': []}, 'mode must be'),
|
|
106
|
+
])
|
|
107
|
+
def test_invalid_assertions_report_precise_schema_field(tmp_path, assertion, error):
|
|
108
|
+
path = tmp_path / 'report.docx'
|
|
109
|
+
doc = Document()
|
|
110
|
+
doc.add_paragraph('')
|
|
111
|
+
doc.save(path)
|
|
112
|
+
with pytest.raises(ValueError, match=error):
|
|
113
|
+
verify_docx(path, expectations=[{'target': {'block': 1}, **assertion}], base_dir=tmp_path)
|
|
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
|
{licos_dev_sdk-0.4.3 → licos_dev_sdk-0.4.4}/src/licos_dev_sdk/document_docx_table_actions.py
RENAMED
|
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
|
|
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
|