python-hwpx-automation 6.0.3__py3-none-any.whl
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.
- hwpx_automation/__init__.py +61 -0
- hwpx_automation/__init__.pyi +27 -0
- hwpx_automation/__main__.py +8 -0
- hwpx_automation/agent_document.py +392 -0
- hwpx_automation/api.py +136 -0
- hwpx_automation/blind_eval.py +407 -0
- hwpx_automation/capabilities.py +110 -0
- hwpx_automation/compat.py +48 -0
- hwpx_automation/configuration.py +60 -0
- hwpx_automation/core/__init__.py +2 -0
- hwpx_automation/core/content.py +762 -0
- hwpx_automation/core/context.py +111 -0
- hwpx_automation/core/diff.py +53 -0
- hwpx_automation/core/document.py +37 -0
- hwpx_automation/core/formatting.py +513 -0
- hwpx_automation/core/handles.py +24 -0
- hwpx_automation/core/locations.py +205 -0
- hwpx_automation/core/locator.py +162 -0
- hwpx_automation/core/plan.py +680 -0
- hwpx_automation/core/resources.py +42 -0
- hwpx_automation/core/search.py +296 -0
- hwpx_automation/core/transactions.py +434 -0
- hwpx_automation/core/txn.py +48 -0
- hwpx_automation/document_state.py +95 -0
- hwpx_automation/errors.py +174 -0
- hwpx_automation/execution_lock.py +15 -0
- hwpx_automation/fastmcp_adapter.py +672 -0
- hwpx_automation/form_fill.py +1177 -0
- hwpx_automation/form_output_models.py +223 -0
- hwpx_automation/handlers/__init__.py +2 -0
- hwpx_automation/handlers/_shared.py +377 -0
- hwpx_automation/handlers/agent_document.py +257 -0
- hwpx_automation/handlers/authoring.py +750 -0
- hwpx_automation/handlers/content_edit.py +1078 -0
- hwpx_automation/handlers/form_fill.py +607 -0
- hwpx_automation/handlers/layout_style.py +660 -0
- hwpx_automation/handlers/quality_render.py +566 -0
- hwpx_automation/handlers/read_export.py +1295 -0
- hwpx_automation/handlers/specialized.py +624 -0
- hwpx_automation/handlers/tracked_changes.py +589 -0
- hwpx_automation/handlers/workflow.py +105 -0
- hwpx_automation/hwp_converter.py +227 -0
- hwpx_automation/hwp_support.py +94 -0
- hwpx_automation/hwpx_ops.py +1439 -0
- hwpx_automation/identity.json +263 -0
- hwpx_automation/identity.py +18 -0
- hwpx_automation/ingest_adapters.py +85 -0
- hwpx_automation/markdown_plan.py +216 -0
- hwpx_automation/mcp_cli.py +29 -0
- hwpx_automation/metadata/tools_meta.py +40 -0
- hwpx_automation/mixed_form.py +3007 -0
- hwpx_automation/mutation_models.py +401 -0
- hwpx_automation/network_policy.py +232 -0
- hwpx_automation/office/__init__.py +14 -0
- hwpx_automation/office/agent/__init__.py +125 -0
- hwpx_automation/office/agent/_batch_verification.py +383 -0
- hwpx_automation/office/agent/blueprint/__init__.py +58 -0
- hwpx_automation/office/agent/blueprint/bundle.py +282 -0
- hwpx_automation/office/agent/blueprint/catalog.py +136 -0
- hwpx_automation/office/agent/blueprint/dump.py +520 -0
- hwpx_automation/office/agent/blueprint/mapping.py +312 -0
- hwpx_automation/office/agent/blueprint/model.py +722 -0
- hwpx_automation/office/agent/blueprint/native.py +621 -0
- hwpx_automation/office/agent/blueprint/replay.py +622 -0
- hwpx_automation/office/agent/catalog.py +252 -0
- hwpx_automation/office/agent/cli.py +647 -0
- hwpx_automation/office/agent/commands.py +1383 -0
- hwpx_automation/office/agent/document.py +801 -0
- hwpx_automation/office/agent/form_plan.py +1760 -0
- hwpx_automation/office/agent/model.py +808 -0
- hwpx_automation/office/agent/path.py +155 -0
- hwpx_automation/office/agent/query.py +230 -0
- hwpx_automation/office/agent/story.py +207 -0
- hwpx_automation/office/authoring/__init__.py +3542 -0
- hwpx_automation/office/authoring/advanced_generators.py +154 -0
- hwpx_automation/office/authoring/builder/__init__.py +52 -0
- hwpx_automation/office/authoring/builder/core.py +996 -0
- hwpx_automation/office/authoring/builder/report.py +195 -0
- hwpx_automation/office/authoring/design/__init__.py +30 -0
- hwpx_automation/office/authoring/design/_support.py +144 -0
- hwpx_automation/office/authoring/design/composer.py +282 -0
- hwpx_automation/office/authoring/design/harvest.py +305 -0
- hwpx_automation/office/authoring/design/plan.py +69 -0
- hwpx_automation/office/authoring/design/profile.py +88 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/application_form/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/application_form/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/profile.json +24 -0
- hwpx_automation/office/authoring/design/profiles/home_notice/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/official_notice/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/body.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/heading.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/info_table.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/fragments/title.xml +1 -0
- hwpx_automation/office/authoring/design/profiles/report/profile.json +25 -0
- hwpx_automation/office/authoring/design/profiles/report/template.hwpx +0 -0
- hwpx_automation/office/authoring/design/validator.py +107 -0
- hwpx_automation/office/authoring/presets/__init__.py +22 -0
- hwpx_automation/office/authoring/presets/proposal.py +538 -0
- hwpx_automation/office/authoring/report_parser.py +141 -0
- hwpx_automation/office/authoring/style_profile.py +437 -0
- hwpx_automation/office/authoring/template_analyzer.py +657 -0
- hwpx_automation/office/compliance/__init__.py +38 -0
- hwpx_automation/office/compliance/official_lint.py +478 -0
- hwpx_automation/office/compliance/pii.py +388 -0
- hwpx_automation/office/document_ops/__init__.py +13 -0
- hwpx_automation/office/document_ops/comparison.py +62 -0
- hwpx_automation/office/document_ops/mail_merge.py +73 -0
- hwpx_automation/office/document_ops/redline.py +35 -0
- hwpx_automation/office/evalplan/__init__.py +36 -0
- hwpx_automation/office/evalplan/runtime.py +2762 -0
- hwpx_automation/office/exam/__init__.py +44 -0
- hwpx_automation/office/exam/compose.py +282 -0
- hwpx_automation/office/exam/ir.py +44 -0
- hwpx_automation/office/exam/measure.py +163 -0
- hwpx_automation/office/exam/parser.py +150 -0
- hwpx_automation/office/exam/profile.py +123 -0
- hwpx_automation/office/form_fill/__init__.py +66 -0
- hwpx_automation/office/form_fill/classification.py +108 -0
- hwpx_automation/office/form_fill/fill_residue.py +242 -0
- hwpx_automation/office/form_fill/fit/__init__.py +36 -0
- hwpx_automation/office/form_fill/fit/apply.py +24 -0
- hwpx_automation/office/form_fill/fit/engine.py +24 -0
- hwpx_automation/office/form_fill/fit/measure.py +50 -0
- hwpx_automation/office/form_fill/fit/policy.py +28 -0
- hwpx_automation/office/form_fill/fit/report.py +28 -0
- hwpx_automation/office/form_fill/fit/seal.py +457 -0
- hwpx_automation/office/form_fill/fit/wordbox.py +1343 -0
- hwpx_automation/office/form_fill/guidance.py +704 -0
- hwpx_automation/office/form_fill/quality.py +961 -0
- hwpx_automation/office/form_fill/split_run.py +333 -0
- hwpx_automation/office/form_fill/template_formfit.py +656 -0
- hwpx_automation/office/house_style/__init__.py +196 -0
- hwpx_automation/office/house_style/composition.py +68 -0
- hwpx_automation/office/house_style/data/bank.json +625 -0
- hwpx_automation/office/house_style/data/genres.json +43 -0
- hwpx_automation/office/quality/__init__.py +14 -0
- hwpx_automation/office/quality/page_guard.py +277 -0
- hwpx_automation/office/rendering/__init__.py +145 -0
- hwpx_automation/office/rendering/_hancom_open_rate.ps1 +374 -0
- hwpx_automation/office/rendering/_refresh_hwpx_mac.applescript +162 -0
- hwpx_automation/office/rendering/_render_hwpx.ps1 +72 -0
- hwpx_automation/office/rendering/_render_hwpx_mac.applescript +249 -0
- hwpx_automation/office/rendering/block_splits.py +76 -0
- hwpx_automation/office/rendering/detectors.py +151 -0
- hwpx_automation/office/rendering/diff.py +153 -0
- hwpx_automation/office/rendering/fixture_corpus.py +215 -0
- hwpx_automation/office/rendering/oracle.py +909 -0
- hwpx_automation/office/rendering/page_qa.py +245 -0
- hwpx_automation/office/rendering/qa_contracts.py +293 -0
- hwpx_automation/office/rendering/qa_metrics.py +241 -0
- hwpx_automation/office/rendering/worker.py +290 -0
- hwpx_automation/office/utilities/__init__.py +12 -0
- hwpx_automation/office/utilities/table_compute.py +477 -0
- hwpx_automation/ops_services/__init__.py +1 -0
- hwpx_automation/ops_services/_border_fill.py +283 -0
- hwpx_automation/ops_services/composition.py +55 -0
- hwpx_automation/ops_services/content_layout.py +322 -0
- hwpx_automation/ops_services/context.py +213 -0
- hwpx_automation/ops_services/form_fields.py +557 -0
- hwpx_automation/ops_services/media.py +178 -0
- hwpx_automation/ops_services/memo_style.py +477 -0
- hwpx_automation/ops_services/package_validation.py +166 -0
- hwpx_automation/ops_services/planning.py +201 -0
- hwpx_automation/ops_services/preview_export.py +585 -0
- hwpx_automation/ops_services/read_query.py +601 -0
- hwpx_automation/ops_services/save_policy.py +604 -0
- hwpx_automation/ops_services/tables.py +539 -0
- hwpx_automation/ops_services/transactions.py +616 -0
- hwpx_automation/preview_output_models.py +69 -0
- hwpx_automation/public-modules.json +206 -0
- hwpx_automation/py.typed +1 -0
- hwpx_automation/quality.py +351 -0
- hwpx_automation/quality_generation.py +725 -0
- hwpx_automation/runtime.py +321 -0
- hwpx_automation/runtime_services.py +100 -0
- hwpx_automation/server.py +259 -0
- hwpx_automation/storage.py +747 -0
- hwpx_automation/tool_bindings.py +170 -0
- hwpx_automation/tool_contract.py +982 -0
- hwpx_automation/upstream.py +755 -0
- hwpx_automation/utils/__init__.py +2 -0
- hwpx_automation/utils/helpers.py +29 -0
- hwpx_automation/visual_qa.py +667 -0
- hwpx_automation/workflow/__init__.py +55 -0
- hwpx_automation/workflow/adapters.py +482 -0
- hwpx_automation/workflow/dispatcher.py +213 -0
- hwpx_automation/workflow/models.py +243 -0
- hwpx_automation/workflow/policy.py +198 -0
- hwpx_automation/workflow/render_contracts.py +173 -0
- hwpx_automation/workflow/render_metrics.py +196 -0
- hwpx_automation/workflow/render_queue.py +482 -0
- hwpx_automation/workflow/render_security.py +172 -0
- hwpx_automation/workflow/render_transport.py +369 -0
- hwpx_automation/workflow/rendering.py +206 -0
- hwpx_automation/workflow/service.py +758 -0
- hwpx_automation/workflow/state_machine.py +65 -0
- hwpx_automation/workflow/store.py +747 -0
- hwpx_automation/workspace.py +1694 -0
- python_hwpx_automation-6.0.3.dist-info/METADATA +279 -0
- python_hwpx_automation-6.0.3.dist-info/RECORD +217 -0
- python_hwpx_automation-6.0.3.dist-info/WHEEL +5 -0
- python_hwpx_automation-6.0.3.dist-info/entry_points.txt +3 -0
- python_hwpx_automation-6.0.3.dist-info/licenses/LICENSE +178 -0
- python_hwpx_automation-6.0.3.dist-info/licenses/NOTICE +14 -0
- python_hwpx_automation-6.0.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Narrow typed native bridge used only by blueprint replay.
|
|
3
|
+
|
|
4
|
+
This module accepts validated blueprint records. It deliberately exposes no
|
|
5
|
+
XML, namespace, package-part, or arbitrary-attribute input.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, cast
|
|
12
|
+
|
|
13
|
+
from hwpx.document import HwpxDocument
|
|
14
|
+
from hwpx.oxml.namespaces import HH, HP
|
|
15
|
+
|
|
16
|
+
from ..commands import _add, _insert_direct_child, _insert_inline, _remove_inline_element, _table_caption
|
|
17
|
+
from ..document import HwpxAgentDocument, NodeRecord
|
|
18
|
+
from ..model import AgentContractError
|
|
19
|
+
|
|
20
|
+
_HWP_UNITS_PER_MM = 7200 / 25.4
|
|
21
|
+
_INLINE_KINDS = frozenset({"table", "picture", "shape", "footnote", "endnote"})
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True, slots=True)
|
|
25
|
+
class CreatedBinding:
|
|
26
|
+
kind: str
|
|
27
|
+
native: Any
|
|
28
|
+
native_id: str | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _local_name(element: Any) -> str:
|
|
32
|
+
return str(getattr(element, "tag", "")).rsplit("}", 1)[-1]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _direct_child(element: Any, kind: str) -> Any | None:
|
|
36
|
+
return next((child for child in element if _local_name(child) == kind), None)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _mm_units(value: object, default: int) -> int:
|
|
40
|
+
if value is None:
|
|
41
|
+
return default
|
|
42
|
+
return max(1, round(float(cast(Any, value)) * _HWP_UNITS_PER_MM))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _bool(value: object, default: bool = False) -> bool:
|
|
46
|
+
return value if isinstance(value, bool) else default
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def create_paragraph_style(
|
|
50
|
+
document: HwpxDocument, properties: dict[str, Any], *, source_key: str
|
|
51
|
+
) -> dict[str, str]:
|
|
52
|
+
"""Create one allow-listed paragraph style with a collision-free name."""
|
|
53
|
+
|
|
54
|
+
if not document._root.headers: # type: ignore[attr-defined]
|
|
55
|
+
raise AgentContractError("unsupported_content", "target has no style header", target=source_key)
|
|
56
|
+
header = document._root.headers[0] # type: ignore[attr-defined]
|
|
57
|
+
styles = header._styles_element() # type: ignore[attr-defined]
|
|
58
|
+
if styles is None:
|
|
59
|
+
raise AgentContractError("unsupported_content", "target has no style registry", target=source_key)
|
|
60
|
+
base_name = str(properties.get("name") or "Blueprint Style")[:200]
|
|
61
|
+
existing_names = {str(style.name) for style in document.styles.values() if style.name}
|
|
62
|
+
target_name = base_name
|
|
63
|
+
if target_name in existing_names:
|
|
64
|
+
target_name = f"{base_name}__bp_{source_key.rsplit(':', 1)[-1][:8]}"
|
|
65
|
+
para_pr_id = header.ensure_paragraph_format(
|
|
66
|
+
base_para_pr_id="0",
|
|
67
|
+
alignment=properties.get("alignment"),
|
|
68
|
+
line_spacing_percent=properties.get("lineSpacingPercent"),
|
|
69
|
+
break_setting={
|
|
70
|
+
key: value
|
|
71
|
+
for key, value in {
|
|
72
|
+
"keep_with_next": properties.get("keepWithNext"),
|
|
73
|
+
"page_break_before": properties.get("breakBefore"),
|
|
74
|
+
}.items()
|
|
75
|
+
if isinstance(value, bool)
|
|
76
|
+
}
|
|
77
|
+
or None,
|
|
78
|
+
)
|
|
79
|
+
style_id = header._allocate_ref_id(styles, f"{HH}style") # type: ignore[attr-defined]
|
|
80
|
+
attrs = {
|
|
81
|
+
"id": style_id,
|
|
82
|
+
"type": str(properties.get("type") or "PARA"),
|
|
83
|
+
"name": target_name,
|
|
84
|
+
"engName": str(properties.get("englishName") or target_name)[:200],
|
|
85
|
+
"paraPrIDRef": para_pr_id,
|
|
86
|
+
"charPrIDRef": "0",
|
|
87
|
+
"nextStyleIDRef": style_id,
|
|
88
|
+
"langID": "0",
|
|
89
|
+
"lockForm": "1" if properties.get("lockForm") else "0",
|
|
90
|
+
}
|
|
91
|
+
styles.append(styles.makeelement(f"{HH}style", attrs))
|
|
92
|
+
header._update_item_count(styles, f"{HH}style") # type: ignore[attr-defined]
|
|
93
|
+
header.mark_dirty()
|
|
94
|
+
return {"styleIDRef": style_id, "paraPrIDRef": para_pr_id, "name": target_name}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def create_character_format(document: HwpxDocument, properties: dict[str, Any]) -> str:
|
|
98
|
+
return document.ensure_run_style(
|
|
99
|
+
bold=_bool(properties.get("bold")),
|
|
100
|
+
italic=_bool(properties.get("italic")),
|
|
101
|
+
underline=_bool(properties.get("underline")),
|
|
102
|
+
color=properties.get("color"),
|
|
103
|
+
size=properties.get("fontSizePt"),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def create_numbering(document: HwpxDocument, properties: dict[str, Any]) -> str:
|
|
108
|
+
raw_type = str(properties.get("type") or "NUMBER").upper()
|
|
109
|
+
kind = "bullet" if raw_type == "BULLET" else "number"
|
|
110
|
+
level = max(0, int(properties.get("level") or 0))
|
|
111
|
+
refs = document.ensure_numbering(kind=kind, levels=[{} for _ in range(level + 1)])
|
|
112
|
+
return refs[level]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _move_to_host_run(paragraph: Any, native: Any, host_run: Any) -> None:
|
|
116
|
+
element = getattr(native, "element", native)
|
|
117
|
+
temporary_run = None
|
|
118
|
+
for run in list(paragraph.element):
|
|
119
|
+
if _local_name(run) == "run" and any(child is element for child in run):
|
|
120
|
+
temporary_run = run
|
|
121
|
+
break
|
|
122
|
+
if temporary_run is not None and temporary_run is not host_run.element:
|
|
123
|
+
temporary_run.remove(element)
|
|
124
|
+
if len(temporary_run) == 0:
|
|
125
|
+
paragraph.element.remove(temporary_run)
|
|
126
|
+
host_run.element.append(element)
|
|
127
|
+
paragraph.section.mark_dirty()
|
|
128
|
+
_remove_empty_text_placeholders(host_run)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _remove_empty_text_placeholders(run: Any) -> None:
|
|
132
|
+
"""Drop builder placeholders before attaching native controls.
|
|
133
|
+
|
|
134
|
+
Hancom ignores a table or inline control that follows an empty ``t`` in the
|
|
135
|
+
same run even though the semantic reader can still see it. Preserve real
|
|
136
|
+
run text, but remove empty builder-created text nodes from control runs.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
for child in list(run.element):
|
|
140
|
+
if _local_name(child) == "t" and not "".join(child.itertext()) and not child.tail:
|
|
141
|
+
run.element.remove(child)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TypedNativeBridge:
|
|
145
|
+
"""Construct validated blueprint nodes through existing typed primitives."""
|
|
146
|
+
|
|
147
|
+
def __init__(
|
|
148
|
+
self,
|
|
149
|
+
document: HwpxDocument,
|
|
150
|
+
manifest: dict[str, Any],
|
|
151
|
+
dependency_targets: dict[str, dict[str, Any]],
|
|
152
|
+
) -> None:
|
|
153
|
+
self.document = document
|
|
154
|
+
self.manifest = manifest
|
|
155
|
+
self.nodes = {str(node["blueprintId"]): node for node in manifest["nodes"]}
|
|
156
|
+
self.dependency_targets = dependency_targets
|
|
157
|
+
self.created: dict[str, CreatedBinding] = {}
|
|
158
|
+
self.references = {
|
|
159
|
+
(str(item["from"]), str(item["field"])): str(item["to"])
|
|
160
|
+
for item in manifest["references"]
|
|
161
|
+
}
|
|
162
|
+
self._identity_seed = self._max_numeric_identity()
|
|
163
|
+
|
|
164
|
+
def _max_numeric_identity(self) -> int:
|
|
165
|
+
maximum = 0
|
|
166
|
+
for section in self.document.sections:
|
|
167
|
+
for element in section.element.iter():
|
|
168
|
+
for name in ("id", "instid", "instId"):
|
|
169
|
+
value = str(element.get(name) or "")
|
|
170
|
+
if value.isdigit():
|
|
171
|
+
maximum = max(maximum, int(value))
|
|
172
|
+
return maximum
|
|
173
|
+
|
|
174
|
+
def _identity(self) -> str:
|
|
175
|
+
self._identity_seed += 1
|
|
176
|
+
return str(self._identity_seed)
|
|
177
|
+
|
|
178
|
+
def _dependency(self, node: dict[str, Any], field: str) -> dict[str, Any] | None:
|
|
179
|
+
refs = list(node[field])
|
|
180
|
+
if not refs:
|
|
181
|
+
return None
|
|
182
|
+
if len(refs) != 1:
|
|
183
|
+
raise AgentContractError(
|
|
184
|
+
"invariant_violation", f"{node['kind']} requires at most one {field}", target=node["blueprintId"]
|
|
185
|
+
)
|
|
186
|
+
return self.dependency_targets[str(refs[0])]
|
|
187
|
+
|
|
188
|
+
def _apply_paragraph_dependencies(self, paragraph: Any, node: dict[str, Any]) -> None:
|
|
189
|
+
style = self._dependency(node, "styleRefs")
|
|
190
|
+
if style is not None:
|
|
191
|
+
paragraph.style_id_ref = style["identity"]["styleIDRef"]
|
|
192
|
+
paragraph.para_pr_id_ref = style["identity"]["paraPrIDRef"]
|
|
193
|
+
numbering = self._dependency(node, "numberingRefs")
|
|
194
|
+
if numbering is not None:
|
|
195
|
+
definition = self.document.paragraph_property(numbering["identity"]["paraPrIDRef"])
|
|
196
|
+
heading = definition.heading if definition is not None else None
|
|
197
|
+
if heading is None or not self.document._root.headers: # type: ignore[attr-defined]
|
|
198
|
+
raise AgentContractError("invariant_violation", "mapped numbering has no heading")
|
|
199
|
+
paragraph.para_pr_id_ref = self.document._root.headers[0].ensure_paragraph_format( # type: ignore[attr-defined]
|
|
200
|
+
base_para_pr_id=paragraph.para_pr_id_ref,
|
|
201
|
+
heading={"type": heading.type or "NONE", "idRef": heading.id_ref or 0, "level": heading.level or 0},
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def _populate_run(self, run: Any, node: dict[str, Any]) -> None:
|
|
205
|
+
character = self._dependency(node, "styleRefs")
|
|
206
|
+
if character is not None:
|
|
207
|
+
run.char_pr_id_ref = character["identity"]["charPrIDRef"]
|
|
208
|
+
run.text = str(node["properties"].get("text") or "")
|
|
209
|
+
self.created[str(node["blueprintId"])] = CreatedBinding("run", run)
|
|
210
|
+
|
|
211
|
+
def _create_runs(self, paragraph: Any, child_ids: list[str], *, reuse_existing: bool) -> None:
|
|
212
|
+
run_ids = [child_id for child_id in child_ids if self.nodes[child_id]["kind"] == "run"]
|
|
213
|
+
existing = list(paragraph.runs) if reuse_existing else []
|
|
214
|
+
for index, run_id in enumerate(run_ids):
|
|
215
|
+
node = self.nodes[run_id]
|
|
216
|
+
if index < len(existing):
|
|
217
|
+
run = existing[index]
|
|
218
|
+
else:
|
|
219
|
+
character = self._dependency(node, "styleRefs")
|
|
220
|
+
char_ref = character["identity"]["charPrIDRef"] if character is not None else None
|
|
221
|
+
run = paragraph.add_run("", char_pr_id_ref=char_ref)
|
|
222
|
+
self._populate_run(run, node)
|
|
223
|
+
for run in existing[len(run_ids) :]:
|
|
224
|
+
# Newly created section/cell paragraphs may carry one structural
|
|
225
|
+
# control run. Preserve it only when it has non-text children.
|
|
226
|
+
if all(_local_name(child) in {"t", "tab"} for child in run.element):
|
|
227
|
+
paragraph.element.remove(run.element)
|
|
228
|
+
|
|
229
|
+
def _host_run(self, node_id: str, paragraph: Any) -> Any:
|
|
230
|
+
target_id = self.references.get((node_id, "hostRun"))
|
|
231
|
+
if target_id is not None:
|
|
232
|
+
try:
|
|
233
|
+
return self.created[target_id].native
|
|
234
|
+
except KeyError as exc:
|
|
235
|
+
raise AgentContractError("invariant_violation", "host run was not constructed", target=node_id) from exc
|
|
236
|
+
return paragraph.add_run("")
|
|
237
|
+
|
|
238
|
+
def _create_paragraph(
|
|
239
|
+
self,
|
|
240
|
+
node_id: str,
|
|
241
|
+
parent: Any,
|
|
242
|
+
*,
|
|
243
|
+
existing: Any | None = None,
|
|
244
|
+
root_view: HwpxAgentDocument | None = None,
|
|
245
|
+
root_parent: NodeRecord | None = None,
|
|
246
|
+
position: dict[str, Any] | None = None,
|
|
247
|
+
) -> Any:
|
|
248
|
+
node = self.nodes[node_id]
|
|
249
|
+
if existing is not None:
|
|
250
|
+
paragraph = existing
|
|
251
|
+
paragraph.clear_text()
|
|
252
|
+
reuse_existing = True
|
|
253
|
+
elif hasattr(parent, "add_paragraph") and _local_name(parent.element) == "tc":
|
|
254
|
+
paragraph = parent.add_paragraph("")
|
|
255
|
+
paragraph.clear_text()
|
|
256
|
+
reuse_existing = True
|
|
257
|
+
else:
|
|
258
|
+
paragraph = parent.add_paragraph("", include_run=False, inherit_style=False)
|
|
259
|
+
reuse_existing = False
|
|
260
|
+
self._apply_paragraph_dependencies(paragraph, node)
|
|
261
|
+
if root_view is not None and root_parent is not None and position and position["mode"] != "append":
|
|
262
|
+
paragraph.element.getparent().remove(paragraph.element)
|
|
263
|
+
_insert_direct_child(root_view, root_parent, "paragraph", paragraph.element, position)
|
|
264
|
+
root_parent.native.mark_dirty()
|
|
265
|
+
self.created[node_id] = CreatedBinding("paragraph", paragraph)
|
|
266
|
+
child_ids = list(node["children"])
|
|
267
|
+
self._create_runs(paragraph, child_ids, reuse_existing=reuse_existing)
|
|
268
|
+
non_runs = [child_id for child_id in child_ids if self.nodes[child_id]["kind"] != "run"]
|
|
269
|
+
if not child_ids:
|
|
270
|
+
paragraph.text = str(node["properties"].get("text") or "")
|
|
271
|
+
for child_id in non_runs:
|
|
272
|
+
self._create_inline_or_table(child_id, paragraph)
|
|
273
|
+
return paragraph
|
|
274
|
+
|
|
275
|
+
def _create_table(self, node_id: str, paragraph: Any, host_run: Any) -> Any:
|
|
276
|
+
node = self.nodes[node_id]
|
|
277
|
+
properties = node["properties"]
|
|
278
|
+
table = paragraph.add_table(
|
|
279
|
+
int(properties["rowCount"]),
|
|
280
|
+
int(properties["columnCount"]),
|
|
281
|
+
width=_mm_units(properties.get("widthMm"), 7200 * int(properties["columnCount"])),
|
|
282
|
+
)
|
|
283
|
+
_move_to_host_run(paragraph, table, host_run)
|
|
284
|
+
if properties.get("caption") is not None:
|
|
285
|
+
_table_caption(table, str(properties["caption"] or ""))
|
|
286
|
+
pos = _direct_child(table.element, "pos")
|
|
287
|
+
if pos is not None and properties.get("alignment"):
|
|
288
|
+
pos.set("horzAlign", str(properties["alignment"]).upper())
|
|
289
|
+
self.created[node_id] = CreatedBinding("table", table)
|
|
290
|
+
row_ids = [child for child in node["children"] if self.nodes[child]["kind"] == "row"]
|
|
291
|
+
if len(row_ids) != len(table.rows):
|
|
292
|
+
raise AgentContractError("invariant_violation", "table row count does not match graph", target=node_id)
|
|
293
|
+
cell_layout = self._table_cell_layout(row_ids, int(properties["columnCount"]))
|
|
294
|
+
for cell_id, row_index, column_index, row_span, column_span in cell_layout:
|
|
295
|
+
if row_span > 1 or column_span > 1:
|
|
296
|
+
table.merge_cells(
|
|
297
|
+
row_index,
|
|
298
|
+
column_index,
|
|
299
|
+
row_index + row_span - 1,
|
|
300
|
+
column_index + column_span - 1,
|
|
301
|
+
)
|
|
302
|
+
for row_id, row in zip(row_ids, table.rows, strict=True):
|
|
303
|
+
self._bind_row(row_id, row)
|
|
304
|
+
for cell_id, row_index, column_index, _row_span, _column_span in cell_layout:
|
|
305
|
+
self._populate_cell(cell_id, table.cell(row_index, column_index))
|
|
306
|
+
return table
|
|
307
|
+
|
|
308
|
+
def _table_cell_layout(
|
|
309
|
+
self,
|
|
310
|
+
row_ids: list[str],
|
|
311
|
+
column_count: int,
|
|
312
|
+
) -> list[tuple[str, int, int, int, int]]:
|
|
313
|
+
occupancy = [[False for _ in range(column_count)] for _ in row_ids]
|
|
314
|
+
layout: list[tuple[str, int, int, int, int]] = []
|
|
315
|
+
for row_index, row_id in enumerate(row_ids):
|
|
316
|
+
column_index = 0
|
|
317
|
+
cell_ids = [
|
|
318
|
+
child
|
|
319
|
+
for child in self.nodes[row_id]["children"]
|
|
320
|
+
if self.nodes[child]["kind"] == "cell"
|
|
321
|
+
]
|
|
322
|
+
for cell_id in cell_ids:
|
|
323
|
+
while column_index < column_count and occupancy[row_index][column_index]:
|
|
324
|
+
column_index += 1
|
|
325
|
+
properties = self.nodes[cell_id]["properties"]
|
|
326
|
+
row_span = int(properties.get("rowSpan") or 1)
|
|
327
|
+
column_span = int(properties.get("columnSpan") or 1)
|
|
328
|
+
if (
|
|
329
|
+
column_index >= column_count
|
|
330
|
+
or row_index + row_span > len(row_ids)
|
|
331
|
+
or column_index + column_span > column_count
|
|
332
|
+
):
|
|
333
|
+
raise AgentContractError(
|
|
334
|
+
"invariant_violation",
|
|
335
|
+
"table cell spans exceed the declared grid",
|
|
336
|
+
target=cell_id,
|
|
337
|
+
)
|
|
338
|
+
for occupied_row in range(row_index, row_index + row_span):
|
|
339
|
+
for occupied_column in range(column_index, column_index + column_span):
|
|
340
|
+
if occupancy[occupied_row][occupied_column]:
|
|
341
|
+
raise AgentContractError(
|
|
342
|
+
"invariant_violation",
|
|
343
|
+
"table cell spans overlap",
|
|
344
|
+
target=cell_id,
|
|
345
|
+
)
|
|
346
|
+
occupancy[occupied_row][occupied_column] = True
|
|
347
|
+
layout.append((cell_id, row_index, column_index, row_span, column_span))
|
|
348
|
+
column_index += column_span
|
|
349
|
+
if any(not occupied for row in occupancy for occupied in row):
|
|
350
|
+
raise AgentContractError(
|
|
351
|
+
"invariant_violation",
|
|
352
|
+
"table graph does not cover the declared grid",
|
|
353
|
+
)
|
|
354
|
+
return layout
|
|
355
|
+
|
|
356
|
+
def _bind_row(self, node_id: str, row: Any) -> None:
|
|
357
|
+
node = self.nodes[node_id]
|
|
358
|
+
self.created[node_id] = CreatedBinding("row", row)
|
|
359
|
+
height = node["properties"].get("heightMm")
|
|
360
|
+
if height is not None:
|
|
361
|
+
for cell in row.cells:
|
|
362
|
+
cell.set_size(height=_mm_units(height, cell.height))
|
|
363
|
+
|
|
364
|
+
def _populate_row(self, node_id: str, row: Any) -> None:
|
|
365
|
+
node = self.nodes[node_id]
|
|
366
|
+
self._bind_row(node_id, row)
|
|
367
|
+
cell_ids = [child for child in node["children"] if self.nodes[child]["kind"] == "cell"]
|
|
368
|
+
if len(cell_ids) != len(row.cells):
|
|
369
|
+
raise AgentContractError("invariant_violation", "table cell count does not match graph", target=node_id)
|
|
370
|
+
for cell_id, cell in zip(cell_ids, row.cells, strict=True):
|
|
371
|
+
self._populate_cell(cell_id, cell)
|
|
372
|
+
|
|
373
|
+
def _populate_cell(self, node_id: str, cell: Any) -> None:
|
|
374
|
+
node = self.nodes[node_id]
|
|
375
|
+
properties = node["properties"]
|
|
376
|
+
cell.set_span(int(properties.get("rowSpan") or 1), int(properties.get("columnSpan") or 1))
|
|
377
|
+
sublist = _direct_child(cell.element, "subList")
|
|
378
|
+
if sublist is not None and properties.get("verticalAlignment"):
|
|
379
|
+
sublist.set("vertAlign", str(properties["verticalAlignment"]).upper())
|
|
380
|
+
self.created[node_id] = CreatedBinding("cell", cell)
|
|
381
|
+
paragraph_ids = [child for child in node["children"] if self.nodes[child]["kind"] == "paragraph"]
|
|
382
|
+
existing = list(cell.paragraphs)
|
|
383
|
+
for index, paragraph_id in enumerate(paragraph_ids):
|
|
384
|
+
paragraph = existing[index] if index < len(existing) else None
|
|
385
|
+
self._create_paragraph(paragraph_id, cell, existing=paragraph)
|
|
386
|
+
for paragraph in existing[len(paragraph_ids) :]:
|
|
387
|
+
if sublist is not None:
|
|
388
|
+
sublist.remove(paragraph.element)
|
|
389
|
+
cell.table.mark_dirty()
|
|
390
|
+
if not paragraph_ids:
|
|
391
|
+
cell.text = str(properties.get("text") or "")
|
|
392
|
+
|
|
393
|
+
def _create_picture(self, node_id: str, paragraph: Any, host_run: Any) -> Any:
|
|
394
|
+
node = self.nodes[node_id]
|
|
395
|
+
resource = self._dependency(node, "resourceRefs")
|
|
396
|
+
if resource is None:
|
|
397
|
+
raise AgentContractError("invariant_violation", "picture has no mapped resource", target=node_id)
|
|
398
|
+
properties = node["properties"]
|
|
399
|
+
picture = paragraph.add_picture(
|
|
400
|
+
resource["identity"]["binaryItemIDRef"],
|
|
401
|
+
width=_mm_units(properties.get("widthMm"), 14400),
|
|
402
|
+
height=_mm_units(properties.get("heightMm"), 14400),
|
|
403
|
+
align=properties.get("alignment"),
|
|
404
|
+
treat_as_char=_bool(properties.get("treatAsChar"), True),
|
|
405
|
+
)
|
|
406
|
+
_move_to_host_run(paragraph, picture, host_run)
|
|
407
|
+
if properties.get("altText"):
|
|
408
|
+
picture.element.set("altText", str(properties["altText"]))
|
|
409
|
+
if properties.get("name"):
|
|
410
|
+
picture.element.set("name", str(properties["name"]))
|
|
411
|
+
self.created[node_id] = CreatedBinding("picture", picture)
|
|
412
|
+
return picture
|
|
413
|
+
|
|
414
|
+
def _create_shape(self, node_id: str, paragraph: Any, host_run: Any) -> Any:
|
|
415
|
+
node = self.nodes[node_id]
|
|
416
|
+
properties = node["properties"]
|
|
417
|
+
shape_type = str(properties.get("shapeType") or "")
|
|
418
|
+
kwargs = {
|
|
419
|
+
"width": _mm_units(properties.get("widthMm"), 14400),
|
|
420
|
+
"height": _mm_units(properties.get("heightMm"), 7200),
|
|
421
|
+
"line_color": str(properties.get("lineColor") or "#000000"),
|
|
422
|
+
"line_width": str(properties.get("lineWidth") or "283"),
|
|
423
|
+
"fill_color": properties.get("fillColor"),
|
|
424
|
+
"treat_as_char": True,
|
|
425
|
+
}
|
|
426
|
+
if shape_type == "rect":
|
|
427
|
+
shape = paragraph.add_rectangle(ratio=int(properties.get("ratio") or 0), **kwargs)
|
|
428
|
+
elif shape_type == "ellipse":
|
|
429
|
+
shape = paragraph.add_ellipse(**kwargs)
|
|
430
|
+
else:
|
|
431
|
+
raise AgentContractError("unsupported_content", "shape type is not replayable", target=node_id)
|
|
432
|
+
_move_to_host_run(paragraph, shape, host_run)
|
|
433
|
+
if properties.get("altText"):
|
|
434
|
+
shape.element.set("altText", str(properties["altText"]))
|
|
435
|
+
if properties.get("name"):
|
|
436
|
+
shape.element.set("name", str(properties["name"]))
|
|
437
|
+
self.created[node_id] = CreatedBinding("shape", shape)
|
|
438
|
+
return shape
|
|
439
|
+
|
|
440
|
+
def _create_note(self, node_id: str, paragraph: Any, host_run: Any) -> Any:
|
|
441
|
+
node = self.nodes[node_id]
|
|
442
|
+
text = str(node["properties"].get("text") or "")
|
|
443
|
+
note = paragraph.add_footnote(text) if node["kind"] == "footnote" else paragraph.add_endnote(text)
|
|
444
|
+
_move_to_host_run(paragraph, note, host_run)
|
|
445
|
+
self.created[node_id] = CreatedBinding(str(node["kind"]), note)
|
|
446
|
+
return note
|
|
447
|
+
|
|
448
|
+
def _create_form_field(self, node_id: str, paragraph: Any) -> None:
|
|
449
|
+
node = self.nodes[node_id]
|
|
450
|
+
properties = node["properties"]
|
|
451
|
+
field_type = str(properties.get("fieldType") or "ClickHere")
|
|
452
|
+
normalized_type = field_type.replace("_", "").replace("-", "").upper()
|
|
453
|
+
if normalized_type not in {"FORM", "CLICKHERE", "NURUMTUL", "누름틀"}:
|
|
454
|
+
raise AgentContractError("unsupported_content", "only FORM fields are replayable", target=node_id)
|
|
455
|
+
host_id = self.references.get((node_id, "hostRun"))
|
|
456
|
+
end_id = self.references.get((node_id, "endRun"))
|
|
457
|
+
if host_id is None or end_id is None:
|
|
458
|
+
raise AgentContractError("invariant_violation", "form field run references are incomplete", target=node_id)
|
|
459
|
+
host_run = self.created[host_id].native
|
|
460
|
+
end_run = self.created[end_id].native
|
|
461
|
+
begin_id = self._identity()
|
|
462
|
+
field_id = self._identity()
|
|
463
|
+
direction = str(properties.get("value") or "")[:4096]
|
|
464
|
+
command_payload = (
|
|
465
|
+
f"Direction:wstring:{len(direction)}:{direction} HelpState:wstring:0: "
|
|
466
|
+
)
|
|
467
|
+
ctrl = host_run.element.makeelement(f"{HP}ctrl", {})
|
|
468
|
+
begin = ctrl.makeelement(
|
|
469
|
+
f"{HP}fieldBegin",
|
|
470
|
+
{
|
|
471
|
+
"id": begin_id,
|
|
472
|
+
"fieldid": field_id,
|
|
473
|
+
"name": str(properties.get("name") or "")[:512],
|
|
474
|
+
"type": "CLICK_HERE" if normalized_type in {"FORM", "CLICKHERE"} else field_type,
|
|
475
|
+
"editable": "0" if properties.get("readOnly") else "1",
|
|
476
|
+
"dirty": "0",
|
|
477
|
+
"zorder": "-1",
|
|
478
|
+
"metaTag": "",
|
|
479
|
+
},
|
|
480
|
+
)
|
|
481
|
+
parameters = begin.makeelement(f"{HP}parameters", {"cnt": "3", "name": ""})
|
|
482
|
+
prop = parameters.makeelement(f"{HP}integerParam", {"name": "Prop"})
|
|
483
|
+
prop.text = "9"
|
|
484
|
+
command = parameters.makeelement(f"{HP}stringParam", {"name": "Command"})
|
|
485
|
+
command.text = f"Clickhere:set:{max(0, len(command_payload) - 1)}:{command_payload}"
|
|
486
|
+
direction_param = parameters.makeelement(f"{HP}stringParam", {"name": "Direction"})
|
|
487
|
+
direction_param.text = direction
|
|
488
|
+
parameters.extend((prop, command, direction_param))
|
|
489
|
+
begin.append(parameters)
|
|
490
|
+
ctrl.append(begin)
|
|
491
|
+
_remove_empty_text_placeholders(host_run)
|
|
492
|
+
host_run.element.append(ctrl)
|
|
493
|
+
end_ctrl = end_run.element.makeelement(f"{HP}ctrl", {"type": "FORM"})
|
|
494
|
+
end_ctrl.append(
|
|
495
|
+
end_ctrl.makeelement(
|
|
496
|
+
f"{HP}fieldEnd", {"beginIDRef": begin_id, "fieldid": field_id}
|
|
497
|
+
)
|
|
498
|
+
)
|
|
499
|
+
_remove_empty_text_placeholders(end_run)
|
|
500
|
+
end_run.element.append(end_ctrl)
|
|
501
|
+
paragraph.section.mark_dirty()
|
|
502
|
+
self.created[node_id] = CreatedBinding("form-field", None, begin_id)
|
|
503
|
+
|
|
504
|
+
def _create_inline_or_table(self, node_id: str, paragraph: Any) -> Any:
|
|
505
|
+
node = self.nodes[node_id]
|
|
506
|
+
kind = str(node["kind"])
|
|
507
|
+
if kind == "form-field":
|
|
508
|
+
self._create_form_field(node_id, paragraph)
|
|
509
|
+
return None
|
|
510
|
+
host_run = self._host_run(node_id, paragraph)
|
|
511
|
+
if kind == "table":
|
|
512
|
+
return self._create_table(node_id, paragraph, host_run)
|
|
513
|
+
if kind == "picture":
|
|
514
|
+
return self._create_picture(node_id, paragraph, host_run)
|
|
515
|
+
if kind == "shape":
|
|
516
|
+
return self._create_shape(node_id, paragraph, host_run)
|
|
517
|
+
if kind in {"footnote", "endnote"}:
|
|
518
|
+
return self._create_note(node_id, paragraph, host_run)
|
|
519
|
+
raise AgentContractError("unsupported_content", f"unsupported paragraph child: {kind}", target=node_id)
|
|
520
|
+
|
|
521
|
+
def _create_memo(self, node_id: str, section: Any) -> Any:
|
|
522
|
+
node = self.nodes[node_id]
|
|
523
|
+
properties = node["properties"]
|
|
524
|
+
attrs = {"author": str(properties["author"])} if properties.get("author") else None
|
|
525
|
+
memo = section.add_memo(str(properties.get("text") or ""), attributes=attrs)
|
|
526
|
+
self.created[node_id] = CreatedBinding("memo", memo)
|
|
527
|
+
return memo
|
|
528
|
+
|
|
529
|
+
def _create_section(self, node_id: str) -> Any:
|
|
530
|
+
node = self.nodes[node_id]
|
|
531
|
+
section = self.document.add_section()
|
|
532
|
+
properties = node["properties"]
|
|
533
|
+
size: dict[str, int] = {}
|
|
534
|
+
if properties.get("pageWidthMm") is not None:
|
|
535
|
+
size["width"] = _mm_units(properties["pageWidthMm"], 59528)
|
|
536
|
+
if properties.get("pageHeightMm") is not None:
|
|
537
|
+
size["height"] = _mm_units(properties["pageHeightMm"], 84189)
|
|
538
|
+
if size:
|
|
539
|
+
section.properties.set_page_size(width=size.get("width"), height=size.get("height"))
|
|
540
|
+
self.created[node_id] = CreatedBinding("section", section)
|
|
541
|
+
paragraph_ids = [child for child in node["children"] if self.nodes[child]["kind"] == "paragraph"]
|
|
542
|
+
existing = list(section.paragraphs)
|
|
543
|
+
for index, paragraph_id in enumerate(paragraph_ids):
|
|
544
|
+
paragraph = existing[index] if index < len(existing) else None
|
|
545
|
+
self._create_paragraph(paragraph_id, section, existing=paragraph)
|
|
546
|
+
for child_id in node["children"]:
|
|
547
|
+
if self.nodes[child_id]["kind"] == "memo":
|
|
548
|
+
self._create_memo(child_id, section)
|
|
549
|
+
return section
|
|
550
|
+
|
|
551
|
+
def create_root(
|
|
552
|
+
self,
|
|
553
|
+
parent: NodeRecord,
|
|
554
|
+
position: dict[str, Any],
|
|
555
|
+
view: HwpxAgentDocument,
|
|
556
|
+
) -> dict[str, CreatedBinding]:
|
|
557
|
+
root_id = str(self.manifest["root"]["blueprintId"])
|
|
558
|
+
root = self.nodes[root_id]
|
|
559
|
+
kind = str(root["kind"])
|
|
560
|
+
if kind == "document":
|
|
561
|
+
self.created[root_id] = CreatedBinding("document", self.document)
|
|
562
|
+
for child_id in root["children"]:
|
|
563
|
+
self._create_section(child_id)
|
|
564
|
+
elif kind == "section":
|
|
565
|
+
if position["mode"] != "append":
|
|
566
|
+
raise AgentContractError("unsupported_operation", "section replay is append-only")
|
|
567
|
+
self._create_section(root_id)
|
|
568
|
+
elif kind == "paragraph":
|
|
569
|
+
self._create_paragraph(
|
|
570
|
+
root_id,
|
|
571
|
+
parent.native,
|
|
572
|
+
root_view=view,
|
|
573
|
+
root_parent=parent,
|
|
574
|
+
position=position,
|
|
575
|
+
)
|
|
576
|
+
elif kind == "run":
|
|
577
|
+
character = self._dependency(root, "styleRefs")
|
|
578
|
+
char_ref = character["identity"]["charPrIDRef"] if character is not None else None
|
|
579
|
+
run = parent.native.add_run(str(root["properties"].get("text") or ""), char_pr_id_ref=char_ref)
|
|
580
|
+
if position["mode"] != "append":
|
|
581
|
+
parent.native.element.remove(run.element)
|
|
582
|
+
_insert_direct_child(view, parent, "run", run.element, position)
|
|
583
|
+
self.created[root_id] = CreatedBinding("run", run)
|
|
584
|
+
elif kind in _INLINE_KINDS:
|
|
585
|
+
native = self._create_inline_or_table(root_id, parent.native)
|
|
586
|
+
if position["mode"] != "append":
|
|
587
|
+
_remove_inline_element(native.element, parent.native)
|
|
588
|
+
_insert_inline(view, parent, kind, native.element, position)
|
|
589
|
+
elif kind == "memo":
|
|
590
|
+
if position["mode"] != "append":
|
|
591
|
+
raise AgentContractError("unsupported_operation", "memo replay is append-only")
|
|
592
|
+
self._create_memo(root_id, parent.native)
|
|
593
|
+
elif kind == "row":
|
|
594
|
+
properties = root["properties"]
|
|
595
|
+
created_element = _add(
|
|
596
|
+
self.document,
|
|
597
|
+
view,
|
|
598
|
+
parent,
|
|
599
|
+
"row",
|
|
600
|
+
{"cellCount": int(properties["cellCount"]), "heightMm": properties.get("heightMm")},
|
|
601
|
+
position,
|
|
602
|
+
)
|
|
603
|
+
row = next(
|
|
604
|
+
(candidate for candidate in parent.native.rows if candidate.element is created_element),
|
|
605
|
+
None,
|
|
606
|
+
)
|
|
607
|
+
if row is None:
|
|
608
|
+
raise AgentContractError("not_found", "replayed row was not materialized", target=root_id)
|
|
609
|
+
self._populate_row(root_id, row)
|
|
610
|
+
else:
|
|
611
|
+
raise AgentContractError("unsupported_content", f"root kind is not replayable: {kind}")
|
|
612
|
+
return self.created
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
__all__ = [
|
|
616
|
+
"CreatedBinding",
|
|
617
|
+
"TypedNativeBridge",
|
|
618
|
+
"create_character_format",
|
|
619
|
+
"create_numbering",
|
|
620
|
+
"create_paragraph_style",
|
|
621
|
+
]
|