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,1439 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
""":mod:`python-hwpx` 위에 구축한 고수준 연산 호환 facade."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, cast
|
|
10
|
+
|
|
11
|
+
from .core.plan import PlanManager
|
|
12
|
+
from .core.locator import RegisteredHandle
|
|
13
|
+
from .storage import DocumentStorage, LocalDocumentStorage
|
|
14
|
+
from .workspace import WorkspaceMissingParentGuard, WorkspaceOutputGuard
|
|
15
|
+
from .upstream import HH_NS as HH_NS
|
|
16
|
+
from .upstream import HP_NS as HP_NS
|
|
17
|
+
from .upstream import (
|
|
18
|
+
HwpxDocument,
|
|
19
|
+
HwpxOxmlMemo,
|
|
20
|
+
HwpxOxmlParagraph,
|
|
21
|
+
HwpxOxmlTable,
|
|
22
|
+
)
|
|
23
|
+
from .errors import build_error_payload
|
|
24
|
+
from .ops_services.composition import HwpxOpsServices, build_hwpx_ops_services
|
|
25
|
+
from .ops_services.context import DocumentContext
|
|
26
|
+
from .ops_services.save_policy import (
|
|
27
|
+
SavePolicy,
|
|
28
|
+
_ExactBackupResult,
|
|
29
|
+
_ExactRecoveryPublication,
|
|
30
|
+
_ExactSidecarMutation,
|
|
31
|
+
)
|
|
32
|
+
from .ops_services.tables import DEFAULT_PAGING_PARAGRAPH_LIMIT
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class HwpxOperationError(RuntimeError):
|
|
38
|
+
"""문서 단위 작업이 실패했을 때 사용하는 예외."""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
message: str,
|
|
43
|
+
*,
|
|
44
|
+
code: str = "OPERATION_FAILED",
|
|
45
|
+
details: Optional[Dict[str, Any]] = None,
|
|
46
|
+
hint: Optional[str] = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
super().__init__(message)
|
|
49
|
+
self.code = code
|
|
50
|
+
self.message = message
|
|
51
|
+
self.details = details
|
|
52
|
+
self.hint = hint
|
|
53
|
+
|
|
54
|
+
def to_payload(self) -> Dict[str, Any]:
|
|
55
|
+
return build_error_payload(
|
|
56
|
+
code=self.code,
|
|
57
|
+
message=self.message,
|
|
58
|
+
details=self.details,
|
|
59
|
+
hint=self.hint,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class HwpxHandleNotFoundError(HwpxOperationError):
|
|
64
|
+
"""등록되지 않은 핸들 조회 시 사용하는 예외."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class HwpxOps:
|
|
68
|
+
"""MCP 도구에서 활용하는 안전한 고수준 헬퍼 모음."""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
*,
|
|
73
|
+
base_directory: Path | None = None,
|
|
74
|
+
paging_paragraph_limit: int = DEFAULT_PAGING_PARAGRAPH_LIMIT,
|
|
75
|
+
auto_backup: bool = False,
|
|
76
|
+
storage: DocumentStorage | None = None,
|
|
77
|
+
) -> None:
|
|
78
|
+
if storage is not None and (base_directory is not None or auto_backup):
|
|
79
|
+
logger.debug(
|
|
80
|
+
"Ignoring base_directory/auto_backup parameters because explicit storage was provided",
|
|
81
|
+
extra={
|
|
82
|
+
"base_directory": str(base_directory) if base_directory else None
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if storage is None:
|
|
87
|
+
storage = LocalDocumentStorage(
|
|
88
|
+
base_directory=base_directory,
|
|
89
|
+
auto_backup=auto_backup,
|
|
90
|
+
logger=logger,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
context = DocumentContext(
|
|
94
|
+
storage=storage,
|
|
95
|
+
paging_paragraph_limit=paging_paragraph_limit,
|
|
96
|
+
error_type=HwpxOperationError,
|
|
97
|
+
handle_error_type=HwpxHandleNotFoundError,
|
|
98
|
+
)
|
|
99
|
+
self._services: HwpxOpsServices = build_hwpx_ops_services(context)
|
|
100
|
+
self.storage = storage
|
|
101
|
+
self.base_directory = storage.base_directory
|
|
102
|
+
self.paging_limit = context.paging_limit
|
|
103
|
+
self._plan_manager = self._services.planning.plan_manager
|
|
104
|
+
self._registered_handles = context.registered_handles
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def plan_manager(self) -> PlanManager:
|
|
108
|
+
return self._services.planning.plan_manager
|
|
109
|
+
|
|
110
|
+
def _new_error(
|
|
111
|
+
self,
|
|
112
|
+
code: str,
|
|
113
|
+
message: str,
|
|
114
|
+
*,
|
|
115
|
+
details: Optional[Dict[str, Any]] = None,
|
|
116
|
+
hint: Optional[str] = None,
|
|
117
|
+
) -> HwpxOperationError:
|
|
118
|
+
return cast(HwpxOperationError, self._services.context._new_error(
|
|
119
|
+
code, message, details=details, hint=hint
|
|
120
|
+
))
|
|
121
|
+
|
|
122
|
+
def _resolve_path(self, path: str, *, must_exist: bool = True) -> Path:
|
|
123
|
+
return self._services.context._resolve_path(path, must_exist=must_exist)
|
|
124
|
+
|
|
125
|
+
def _make_handle_id(self, path: str, backend: Optional[str] = None) -> str:
|
|
126
|
+
return self._services.context._make_handle_id(path, backend)
|
|
127
|
+
|
|
128
|
+
def _register_handle(self, path: str, resolved: Path) -> RegisteredHandle:
|
|
129
|
+
return self._services.context._register_handle(path, resolved)
|
|
130
|
+
|
|
131
|
+
def list_registered_handles(self) -> List[RegisteredHandle]:
|
|
132
|
+
return self._services.context.list_registered_handles()
|
|
133
|
+
|
|
134
|
+
def open_document_handle(self, path: str) -> Dict[str, Any]:
|
|
135
|
+
return self._services.context.open_document_handle(path)
|
|
136
|
+
|
|
137
|
+
def list_open_documents(self) -> Dict[str, Any]:
|
|
138
|
+
return self._services.context.list_open_documents()
|
|
139
|
+
|
|
140
|
+
def close_document_handle(self, handle_id: str) -> Dict[str, Any]:
|
|
141
|
+
return self._services.context.close_document_handle(handle_id)
|
|
142
|
+
|
|
143
|
+
def get_registered_handle(self, handle_id: str) -> RegisteredHandle:
|
|
144
|
+
return self._services.context.get_registered_handle(handle_id)
|
|
145
|
+
|
|
146
|
+
def resolve_document_path(
|
|
147
|
+
self,
|
|
148
|
+
*,
|
|
149
|
+
path: Optional[str] = None,
|
|
150
|
+
handle_id: Optional[str] = None,
|
|
151
|
+
) -> str:
|
|
152
|
+
return self._services.context.resolve_document_path(
|
|
153
|
+
path=path, handle_id=handle_id
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def get_metadata_by_handle(self, handle_id: str) -> Dict[str, Any]:
|
|
157
|
+
return self._services.read_query.get_metadata_by_handle(handle_id)
|
|
158
|
+
|
|
159
|
+
def get_paragraphs_by_handle(self, handle_id: str) -> Dict[str, Any]:
|
|
160
|
+
return self._services.read_query.get_paragraphs_by_handle(handle_id)
|
|
161
|
+
|
|
162
|
+
def get_tables_by_handle(self, handle_id: str) -> Dict[str, Any]:
|
|
163
|
+
return self._services.read_query.get_tables_by_handle(handle_id)
|
|
164
|
+
|
|
165
|
+
def _resolve_output_path(self, path: str) -> Path:
|
|
166
|
+
return self._services.context._resolve_output_path(path)
|
|
167
|
+
|
|
168
|
+
def _ensure_backup(self, path: Path) -> Optional[Path]:
|
|
169
|
+
return self._services.save._ensure_backup(path)
|
|
170
|
+
|
|
171
|
+
def _relative_path(self, path: Path) -> str:
|
|
172
|
+
return self._services.context._relative_path(path)
|
|
173
|
+
|
|
174
|
+
def _maybe_backup(self, path: Path) -> None:
|
|
175
|
+
return self._services.save._maybe_backup(path)
|
|
176
|
+
|
|
177
|
+
def _open_document(self, path: str) -> Tuple[HwpxDocument, Path]:
|
|
178
|
+
return self._services.context._open_document(path)
|
|
179
|
+
|
|
180
|
+
def _read_only_hwp_paragraphs(self, path: str) -> Tuple[List[str], Path, str]:
|
|
181
|
+
return self._services.context._read_only_hwp_paragraphs(path)
|
|
182
|
+
|
|
183
|
+
def _ensure_planner_document(self, doc_id: str, path: str) -> None:
|
|
184
|
+
return self._services.planning._ensure_planner_document(doc_id, path)
|
|
185
|
+
|
|
186
|
+
def _save_document(
|
|
187
|
+
self, document: HwpxDocument, target: Path, *, quality: Any = None
|
|
188
|
+
) -> Dict[str, Any]:
|
|
189
|
+
return self._services.save._save_document(document, target, quality=quality)
|
|
190
|
+
|
|
191
|
+
def _save_transaction_document(
|
|
192
|
+
self, document: HwpxDocument, target: Path, *, quality: Any = None
|
|
193
|
+
) -> Dict[str, Any]:
|
|
194
|
+
return self._services.save._save_transaction_document(
|
|
195
|
+
document, target, quality=quality
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
@staticmethod
|
|
199
|
+
def _report_for_bytes(data: bytes, *, file_path: Path) -> Dict[str, Any]:
|
|
200
|
+
return SavePolicy._report_for_bytes(data, file_path=file_path)
|
|
201
|
+
|
|
202
|
+
@staticmethod
|
|
203
|
+
def _semantic_diff_bytes(before: bytes, after: bytes) -> Dict[str, Any]:
|
|
204
|
+
return SavePolicy._semantic_diff_bytes(before, after)
|
|
205
|
+
|
|
206
|
+
def _capture_exact_sidecar_guard(self, path: Path) -> WorkspaceOutputGuard:
|
|
207
|
+
"""Authorize one derived sidecar name without following a final alias."""
|
|
208
|
+
return self._services.save._capture_exact_sidecar_guard(path)
|
|
209
|
+
|
|
210
|
+
@staticmethod
|
|
211
|
+
def _absent_publication_guard(
|
|
212
|
+
guard: WorkspaceOutputGuard,
|
|
213
|
+
) -> WorkspaceOutputGuard:
|
|
214
|
+
"""Represent the exact absent state produced by a guarded deletion."""
|
|
215
|
+
return SavePolicy._absent_publication_guard(guard)
|
|
216
|
+
|
|
217
|
+
def _assert_exact_sidecar_publication(
|
|
218
|
+
self,
|
|
219
|
+
guard: WorkspaceOutputGuard,
|
|
220
|
+
) -> None:
|
|
221
|
+
"""Revalidate either an exact file publication or guarded absence."""
|
|
222
|
+
return self._services.save._assert_exact_sidecar_publication(guard)
|
|
223
|
+
|
|
224
|
+
def _publish_exact_recovery(
|
|
225
|
+
self,
|
|
226
|
+
base_path: Path,
|
|
227
|
+
data: bytes,
|
|
228
|
+
*,
|
|
229
|
+
mode: int | None,
|
|
230
|
+
marker: str,
|
|
231
|
+
max_candidates: int = 32,
|
|
232
|
+
) -> _ExactRecoveryPublication:
|
|
233
|
+
"""Publish recovery bytes without overwriting an existing sidecar."""
|
|
234
|
+
return self._services.save._publish_exact_recovery(
|
|
235
|
+
base_path, data, mode=mode, marker=marker, max_candidates=max_candidates
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def _preserve_exact_preimages(
|
|
239
|
+
self,
|
|
240
|
+
preimages: Sequence[tuple[Path, bytes, int | None]],
|
|
241
|
+
*,
|
|
242
|
+
marker: str,
|
|
243
|
+
) -> tuple[_ExactRecoveryPublication, ...] | None:
|
|
244
|
+
"""Preserve every preimage before any destructive mutation begins."""
|
|
245
|
+
return self._services.save._preserve_exact_preimages(preimages, marker=marker)
|
|
246
|
+
|
|
247
|
+
def _cleanup_exact_recoveries(
|
|
248
|
+
self,
|
|
249
|
+
recoveries: Sequence[_ExactRecoveryPublication],
|
|
250
|
+
) -> tuple[bool, bool]:
|
|
251
|
+
"""Remove proven recoveries, republishing all if any cleanup loses CAS."""
|
|
252
|
+
return self._services.save._cleanup_exact_recoveries(recoveries)
|
|
253
|
+
|
|
254
|
+
def _republish_exact_recoveries(
|
|
255
|
+
self,
|
|
256
|
+
recoveries: Sequence[_ExactRecoveryPublication],
|
|
257
|
+
) -> bool:
|
|
258
|
+
"""Recreate immutable recovery copies after cleanup or claim loss."""
|
|
259
|
+
return self._services.save._republish_exact_recoveries(recoveries)
|
|
260
|
+
|
|
261
|
+
def _rotate_and_backup_exact(
|
|
262
|
+
self,
|
|
263
|
+
target: Path,
|
|
264
|
+
*,
|
|
265
|
+
target_guard: WorkspaceOutputGuard | None = None,
|
|
266
|
+
target_bytes: bytes | None = None,
|
|
267
|
+
max_backups: int = 5,
|
|
268
|
+
) -> _ExactBackupResult:
|
|
269
|
+
"""Rotate local sidecars from no-follow guards and an exact preimage."""
|
|
270
|
+
return self._services.save._rotate_and_backup_exact(
|
|
271
|
+
target,
|
|
272
|
+
target_guard=target_guard,
|
|
273
|
+
target_bytes=target_bytes,
|
|
274
|
+
max_backups=max_backups,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def _rollback_exact_backup_mutations(
|
|
278
|
+
self,
|
|
279
|
+
mutations: Sequence[_ExactSidecarMutation],
|
|
280
|
+
*,
|
|
281
|
+
preimages_preserved: bool = False,
|
|
282
|
+
) -> None:
|
|
283
|
+
"""Restore every sidecar candidate that is still exactly ours."""
|
|
284
|
+
return self._services.save._rollback_exact_backup_mutations(
|
|
285
|
+
mutations, preimages_preserved=preimages_preserved
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def _decode_image_base64(self, image_base64: str) -> bytes:
|
|
289
|
+
return self._services.media._decode_image_base64(image_base64)
|
|
290
|
+
|
|
291
|
+
def _id_integrity_payload(self, document: HwpxDocument) -> Dict[str, Any]:
|
|
292
|
+
return self._services.media._id_integrity_payload(document)
|
|
293
|
+
|
|
294
|
+
def _with_transaction_verification(
|
|
295
|
+
self,
|
|
296
|
+
result: Dict[str, Any],
|
|
297
|
+
document: HwpxDocument,
|
|
298
|
+
target: Path,
|
|
299
|
+
*,
|
|
300
|
+
dry_run: bool,
|
|
301
|
+
quality: Any = None,
|
|
302
|
+
) -> Dict[str, Any]:
|
|
303
|
+
return self._services.transactions._with_transaction_verification(
|
|
304
|
+
result, document, target, dry_run=dry_run, quality=quality
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
def _operation_value(
|
|
308
|
+
self, operation: Dict[str, Any], *names: str, default: Any = None
|
|
309
|
+
) -> Any:
|
|
310
|
+
return self._services.transactions._operation_value(
|
|
311
|
+
operation, *names, default=default
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def _apply_transaction_operation(
|
|
315
|
+
self,
|
|
316
|
+
document: HwpxDocument,
|
|
317
|
+
operation: Dict[str, Any],
|
|
318
|
+
index: int,
|
|
319
|
+
) -> Dict[str, Any]:
|
|
320
|
+
return self._services.transactions._apply_transaction_operation(
|
|
321
|
+
document, operation, index
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def apply_edits(
|
|
325
|
+
self,
|
|
326
|
+
path: str,
|
|
327
|
+
operations: Sequence[Dict[str, Any]],
|
|
328
|
+
*,
|
|
329
|
+
dry_run: bool = False,
|
|
330
|
+
quality: Any = None,
|
|
331
|
+
) -> Dict[str, Any]:
|
|
332
|
+
return self._services.transactions.apply_edits(
|
|
333
|
+
path, operations, dry_run=dry_run, quality=quality
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def undo_last_edit(self, path: str) -> Dict[str, Any]:
|
|
337
|
+
return self._services.transactions.undo_last_edit(path)
|
|
338
|
+
|
|
339
|
+
def _iter_paragraphs(self, document: HwpxDocument) -> List[HwpxOxmlParagraph]:
|
|
340
|
+
return self._services.context._iter_paragraphs(document)
|
|
341
|
+
|
|
342
|
+
def _iter_tables(self, document: HwpxDocument) -> List[HwpxOxmlTable]:
|
|
343
|
+
return self._services.context._iter_tables(document)
|
|
344
|
+
|
|
345
|
+
def _auto_fit_table_columns(self, table: HwpxOxmlTable) -> List[int]:
|
|
346
|
+
return self._services.tables._auto_fit_table_columns(table)
|
|
347
|
+
|
|
348
|
+
def _normalize_color(self, color: str | None) -> Optional[str]:
|
|
349
|
+
return self._services.memo_style._normalize_color(color)
|
|
350
|
+
|
|
351
|
+
def _ensure_char_style(
|
|
352
|
+
self,
|
|
353
|
+
document: HwpxDocument,
|
|
354
|
+
run_style: Optional[Dict[str, Any]],
|
|
355
|
+
) -> Optional[str]:
|
|
356
|
+
return self._services.memo_style._ensure_char_style(document, run_style)
|
|
357
|
+
|
|
358
|
+
def _ensure_table_border_fill(
|
|
359
|
+
self,
|
|
360
|
+
document: HwpxDocument,
|
|
361
|
+
*,
|
|
362
|
+
border_style: Optional[str] = None,
|
|
363
|
+
border_color: Optional[str] = None,
|
|
364
|
+
border_width: Optional[str | float | int] = None,
|
|
365
|
+
fill_color: Optional[str] = None,
|
|
366
|
+
) -> str:
|
|
367
|
+
return self._services.tables._ensure_table_border_fill(
|
|
368
|
+
document,
|
|
369
|
+
border_style=border_style,
|
|
370
|
+
border_color=border_color,
|
|
371
|
+
border_width=border_width,
|
|
372
|
+
fill_color=fill_color,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
def open_info(self, path: str) -> Dict[str, Any]:
|
|
376
|
+
return self._services.read_query.open_info(path)
|
|
377
|
+
|
|
378
|
+
def list_sections(self, path: str) -> Dict[str, Any]:
|
|
379
|
+
return self._services.read_query.list_sections(path)
|
|
380
|
+
|
|
381
|
+
def list_headers(self, path: str) -> Dict[str, Any]:
|
|
382
|
+
return self._services.read_query.list_headers(path)
|
|
383
|
+
|
|
384
|
+
def package_parts(self, path: str) -> Dict[str, Any]:
|
|
385
|
+
return self._services.package_validation.package_parts(path)
|
|
386
|
+
|
|
387
|
+
def package_get_text(
|
|
388
|
+
self, path: str, part_name: str, encoding: str | None = None
|
|
389
|
+
) -> Dict[str, Any]:
|
|
390
|
+
return self._services.package_validation.package_get_text(
|
|
391
|
+
path, part_name, encoding
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
def repair_hwpx(
|
|
395
|
+
self,
|
|
396
|
+
source: str,
|
|
397
|
+
output: str,
|
|
398
|
+
*,
|
|
399
|
+
recover: bool = False,
|
|
400
|
+
overwrite: bool = False,
|
|
401
|
+
max_entry_size: int = 64 * 1024 * 1024,
|
|
402
|
+
max_total_size: int = 512 * 1024 * 1024,
|
|
403
|
+
max_source_size: int = 512 * 1024 * 1024,
|
|
404
|
+
) -> Dict[str, Any]:
|
|
405
|
+
return self._services.package_validation.repair_hwpx(
|
|
406
|
+
source,
|
|
407
|
+
output,
|
|
408
|
+
recover=recover,
|
|
409
|
+
overwrite=overwrite,
|
|
410
|
+
max_entry_size=max_entry_size,
|
|
411
|
+
max_total_size=max_total_size,
|
|
412
|
+
max_source_size=max_source_size,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
def read_text(
|
|
416
|
+
self,
|
|
417
|
+
path: str,
|
|
418
|
+
*,
|
|
419
|
+
offset: int = 0,
|
|
420
|
+
limit: Optional[int] = None,
|
|
421
|
+
with_highlights: bool = False,
|
|
422
|
+
with_footnotes: bool = False,
|
|
423
|
+
) -> Dict[str, Any]:
|
|
424
|
+
return self._services.read_query.read_text(
|
|
425
|
+
path,
|
|
426
|
+
offset=offset,
|
|
427
|
+
limit=limit,
|
|
428
|
+
with_highlights=with_highlights,
|
|
429
|
+
with_footnotes=with_footnotes,
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
def get_paragraphs(
|
|
433
|
+
self,
|
|
434
|
+
path: str,
|
|
435
|
+
paragraph_indexes: Sequence[int],
|
|
436
|
+
*,
|
|
437
|
+
with_highlights: bool = False,
|
|
438
|
+
with_footnotes: bool = False,
|
|
439
|
+
) -> Dict[str, Any]:
|
|
440
|
+
return self._services.read_query.get_paragraphs(
|
|
441
|
+
path,
|
|
442
|
+
paragraph_indexes,
|
|
443
|
+
with_highlights=with_highlights,
|
|
444
|
+
with_footnotes=with_footnotes,
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
def text_extract_report(self, path: str, mode: str = "plain") -> Dict[str, Any]:
|
|
448
|
+
return self._services.read_query.text_extract_report(path, mode)
|
|
449
|
+
|
|
450
|
+
def analyze_template_structure(
|
|
451
|
+
self,
|
|
452
|
+
path: str,
|
|
453
|
+
*,
|
|
454
|
+
placeholder_patterns: Optional[Sequence[str]] = None,
|
|
455
|
+
lock_keywords: Optional[Sequence[str]] = None,
|
|
456
|
+
) -> Dict[str, Any]:
|
|
457
|
+
return self._services.read_query.analyze_template_structure(
|
|
458
|
+
path, placeholder_patterns=placeholder_patterns, lock_keywords=lock_keywords
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
def find(
|
|
462
|
+
self,
|
|
463
|
+
path: str,
|
|
464
|
+
query: str,
|
|
465
|
+
*,
|
|
466
|
+
is_regex: bool = False,
|
|
467
|
+
max_results: int = 100,
|
|
468
|
+
context_radius: int = 80,
|
|
469
|
+
) -> Dict[str, Any]:
|
|
470
|
+
return self._services.read_query.find(
|
|
471
|
+
path,
|
|
472
|
+
query,
|
|
473
|
+
is_regex=is_regex,
|
|
474
|
+
max_results=max_results,
|
|
475
|
+
context_radius=context_radius,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
def find_runs_by_style(
|
|
479
|
+
self,
|
|
480
|
+
path: str,
|
|
481
|
+
*,
|
|
482
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
483
|
+
max_results: int = 200,
|
|
484
|
+
) -> Dict[str, Any]:
|
|
485
|
+
return self._services.memo_style.find_runs_by_style(
|
|
486
|
+
path, filters=filters, max_results=max_results
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
def replace_text_in_runs(
|
|
490
|
+
self,
|
|
491
|
+
path: str,
|
|
492
|
+
search: str,
|
|
493
|
+
replacement: str,
|
|
494
|
+
*,
|
|
495
|
+
style_filter: Optional[Dict[str, Any]] = None,
|
|
496
|
+
limit_per_run: Optional[int] = None,
|
|
497
|
+
dry_run: bool = False,
|
|
498
|
+
) -> Dict[str, Any]:
|
|
499
|
+
return self._services.content_layout.replace_text_in_runs(
|
|
500
|
+
path,
|
|
501
|
+
search,
|
|
502
|
+
replacement,
|
|
503
|
+
style_filter=style_filter,
|
|
504
|
+
limit_per_run=limit_per_run,
|
|
505
|
+
dry_run=dry_run,
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
def add_paragraph(
|
|
509
|
+
self,
|
|
510
|
+
path: str,
|
|
511
|
+
text: str = "",
|
|
512
|
+
*,
|
|
513
|
+
section_index: Optional[int] = None,
|
|
514
|
+
run_style: Optional[Dict[str, Any]] = None,
|
|
515
|
+
) -> Dict[str, Any]:
|
|
516
|
+
return self._services.content_layout.add_paragraph(
|
|
517
|
+
path, text, section_index=section_index, run_style=run_style
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
def insert_paragraphs_bulk(
|
|
521
|
+
self,
|
|
522
|
+
path: str,
|
|
523
|
+
paragraphs: Sequence[str],
|
|
524
|
+
*,
|
|
525
|
+
section_index: Optional[int] = None,
|
|
526
|
+
run_style: Optional[Dict[str, Any]] = None,
|
|
527
|
+
dry_run: bool = False,
|
|
528
|
+
) -> Dict[str, Any]:
|
|
529
|
+
return self._services.content_layout.insert_paragraphs_bulk(
|
|
530
|
+
path,
|
|
531
|
+
paragraphs,
|
|
532
|
+
section_index=section_index,
|
|
533
|
+
run_style=run_style,
|
|
534
|
+
dry_run=dry_run,
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
def set_paragraph_format(
|
|
538
|
+
self,
|
|
539
|
+
path: str,
|
|
540
|
+
*,
|
|
541
|
+
paragraph_index: Optional[int] = None,
|
|
542
|
+
paragraph_indexes: Optional[Sequence[int]] = None,
|
|
543
|
+
alignment: Optional[str] = None,
|
|
544
|
+
line_spacing_percent: Optional[float] = None,
|
|
545
|
+
indent_left_mm: Optional[float] = None,
|
|
546
|
+
indent_right_mm: Optional[float] = None,
|
|
547
|
+
first_line_indent_mm: Optional[float] = None,
|
|
548
|
+
spacing_before_pt: Optional[float] = None,
|
|
549
|
+
spacing_after_pt: Optional[float] = None,
|
|
550
|
+
outline_level: Optional[int] = None,
|
|
551
|
+
keep_with_next: Optional[bool] = None,
|
|
552
|
+
keep_lines: Optional[bool] = None,
|
|
553
|
+
page_break_before: Optional[bool] = None,
|
|
554
|
+
dry_run: bool = False,
|
|
555
|
+
) -> Dict[str, Any]:
|
|
556
|
+
return self._services.content_layout.set_paragraph_format(
|
|
557
|
+
path,
|
|
558
|
+
paragraph_index=paragraph_index,
|
|
559
|
+
paragraph_indexes=paragraph_indexes,
|
|
560
|
+
alignment=alignment,
|
|
561
|
+
line_spacing_percent=line_spacing_percent,
|
|
562
|
+
indent_left_mm=indent_left_mm,
|
|
563
|
+
indent_right_mm=indent_right_mm,
|
|
564
|
+
first_line_indent_mm=first_line_indent_mm,
|
|
565
|
+
spacing_before_pt=spacing_before_pt,
|
|
566
|
+
spacing_after_pt=spacing_after_pt,
|
|
567
|
+
outline_level=outline_level,
|
|
568
|
+
keep_with_next=keep_with_next,
|
|
569
|
+
keep_lines=keep_lines,
|
|
570
|
+
page_break_before=page_break_before,
|
|
571
|
+
dry_run=dry_run,
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
def set_page_setup(
|
|
575
|
+
self,
|
|
576
|
+
path: str,
|
|
577
|
+
*,
|
|
578
|
+
paper_size: Optional[str] = None,
|
|
579
|
+
width_mm: Optional[float] = None,
|
|
580
|
+
height_mm: Optional[float] = None,
|
|
581
|
+
orientation: Optional[str] = None,
|
|
582
|
+
margins_mm: Optional[Dict[str, float]] = None,
|
|
583
|
+
margin_left_mm: Optional[float] = None,
|
|
584
|
+
margin_right_mm: Optional[float] = None,
|
|
585
|
+
margin_top_mm: Optional[float] = None,
|
|
586
|
+
margin_bottom_mm: Optional[float] = None,
|
|
587
|
+
header_margin_mm: Optional[float] = None,
|
|
588
|
+
footer_margin_mm: Optional[float] = None,
|
|
589
|
+
gutter_mm: Optional[float] = None,
|
|
590
|
+
columns: Optional[int] = None,
|
|
591
|
+
column_gap_mm: Optional[float] = None,
|
|
592
|
+
section_index: Optional[int] = None,
|
|
593
|
+
dry_run: bool = False,
|
|
594
|
+
) -> Dict[str, Any]:
|
|
595
|
+
return self._services.content_layout.set_page_setup(
|
|
596
|
+
path,
|
|
597
|
+
paper_size=paper_size,
|
|
598
|
+
width_mm=width_mm,
|
|
599
|
+
height_mm=height_mm,
|
|
600
|
+
orientation=orientation,
|
|
601
|
+
margins_mm=margins_mm,
|
|
602
|
+
margin_left_mm=margin_left_mm,
|
|
603
|
+
margin_right_mm=margin_right_mm,
|
|
604
|
+
margin_top_mm=margin_top_mm,
|
|
605
|
+
margin_bottom_mm=margin_bottom_mm,
|
|
606
|
+
header_margin_mm=header_margin_mm,
|
|
607
|
+
footer_margin_mm=footer_margin_mm,
|
|
608
|
+
gutter_mm=gutter_mm,
|
|
609
|
+
columns=columns,
|
|
610
|
+
column_gap_mm=column_gap_mm,
|
|
611
|
+
section_index=section_index,
|
|
612
|
+
dry_run=dry_run,
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
def _header_footer_payload(
|
|
616
|
+
self,
|
|
617
|
+
wrapper: Any,
|
|
618
|
+
*,
|
|
619
|
+
kind: str,
|
|
620
|
+
page_type: str,
|
|
621
|
+
) -> Dict[str, Any]:
|
|
622
|
+
return self._services.content_layout._header_footer_payload(
|
|
623
|
+
wrapper, kind=kind, page_type=page_type
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
def set_header_footer(
|
|
627
|
+
self,
|
|
628
|
+
path: str,
|
|
629
|
+
*,
|
|
630
|
+
kind: str,
|
|
631
|
+
text: Optional[str] = None,
|
|
632
|
+
content: Optional[Sequence[Dict[str, Any]]] = None,
|
|
633
|
+
section_index: Optional[int] = None,
|
|
634
|
+
page_type: str = "BOTH",
|
|
635
|
+
dry_run: bool = False,
|
|
636
|
+
) -> Dict[str, Any]:
|
|
637
|
+
return self._services.content_layout.set_header_footer(
|
|
638
|
+
path,
|
|
639
|
+
kind=kind,
|
|
640
|
+
text=text,
|
|
641
|
+
content=content,
|
|
642
|
+
section_index=section_index,
|
|
643
|
+
page_type=page_type,
|
|
644
|
+
dry_run=dry_run,
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
def set_page_number(
|
|
648
|
+
self,
|
|
649
|
+
path: str,
|
|
650
|
+
*,
|
|
651
|
+
target: str = "footer",
|
|
652
|
+
page_type: str = "BOTH",
|
|
653
|
+
format: str = "page",
|
|
654
|
+
align: str = "CENTER",
|
|
655
|
+
position: str = "BOTTOM_CENTER",
|
|
656
|
+
prefix: str = "",
|
|
657
|
+
suffix: str = "",
|
|
658
|
+
format_type: Optional[str] = None,
|
|
659
|
+
section_index: Optional[int] = None,
|
|
660
|
+
dry_run: bool = False,
|
|
661
|
+
) -> Dict[str, Any]:
|
|
662
|
+
return self._services.content_layout.set_page_number(
|
|
663
|
+
path,
|
|
664
|
+
target=target,
|
|
665
|
+
page_type=page_type,
|
|
666
|
+
format=format,
|
|
667
|
+
align=align,
|
|
668
|
+
position=position,
|
|
669
|
+
prefix=prefix,
|
|
670
|
+
suffix=suffix,
|
|
671
|
+
format_type=format_type,
|
|
672
|
+
section_index=section_index,
|
|
673
|
+
dry_run=dry_run,
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
def set_list_format(
|
|
677
|
+
self,
|
|
678
|
+
path: str,
|
|
679
|
+
*,
|
|
680
|
+
paragraph_index: Optional[int] = None,
|
|
681
|
+
paragraph_indexes: Optional[Sequence[int]] = None,
|
|
682
|
+
kind: str = "bullet",
|
|
683
|
+
level: int = 1,
|
|
684
|
+
bullet_char: Optional[str] = None,
|
|
685
|
+
number_format: Optional[str] = None,
|
|
686
|
+
start: Optional[int] = None,
|
|
687
|
+
dry_run: bool = False,
|
|
688
|
+
) -> Dict[str, Any]:
|
|
689
|
+
return self._services.content_layout.set_list_format(
|
|
690
|
+
path,
|
|
691
|
+
paragraph_index=paragraph_index,
|
|
692
|
+
paragraph_indexes=paragraph_indexes,
|
|
693
|
+
kind=kind,
|
|
694
|
+
level=level,
|
|
695
|
+
bullet_char=bullet_char,
|
|
696
|
+
number_format=number_format,
|
|
697
|
+
start=start,
|
|
698
|
+
dry_run=dry_run,
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
def list_form_fields(
|
|
702
|
+
self,
|
|
703
|
+
path: str,
|
|
704
|
+
) -> Dict[str, Any]:
|
|
705
|
+
return self._services.form_fields.list_form_fields(path)
|
|
706
|
+
|
|
707
|
+
def fill_form_field(
|
|
708
|
+
self,
|
|
709
|
+
path: str,
|
|
710
|
+
*,
|
|
711
|
+
value: str,
|
|
712
|
+
field_index: Optional[int] = None,
|
|
713
|
+
field_id: Optional[str] = None,
|
|
714
|
+
name: Optional[str] = None,
|
|
715
|
+
dry_run: bool = False,
|
|
716
|
+
) -> Dict[str, Any]:
|
|
717
|
+
return self._services.form_fields.fill_form_field(
|
|
718
|
+
path,
|
|
719
|
+
value=value,
|
|
720
|
+
field_index=field_index,
|
|
721
|
+
field_id=field_id,
|
|
722
|
+
name=name,
|
|
723
|
+
dry_run=dry_run,
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
def add_table(
|
|
727
|
+
self,
|
|
728
|
+
path: str,
|
|
729
|
+
rows: int,
|
|
730
|
+
cols: int,
|
|
731
|
+
*,
|
|
732
|
+
section_index: Optional[int] = None,
|
|
733
|
+
border_style: str | None = None,
|
|
734
|
+
border_color: Optional[str] = None,
|
|
735
|
+
border_width: Optional[str | float | int] = None,
|
|
736
|
+
fill_color: Optional[str] = None,
|
|
737
|
+
auto_fit: bool = False,
|
|
738
|
+
) -> Dict[str, Any]:
|
|
739
|
+
return self._services.tables.add_table(
|
|
740
|
+
path,
|
|
741
|
+
rows,
|
|
742
|
+
cols,
|
|
743
|
+
section_index=section_index,
|
|
744
|
+
border_style=border_style,
|
|
745
|
+
border_color=border_color,
|
|
746
|
+
border_width=border_width,
|
|
747
|
+
fill_color=fill_color,
|
|
748
|
+
auto_fit=auto_fit,
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
def set_table_border_fill(
|
|
752
|
+
self,
|
|
753
|
+
path: str,
|
|
754
|
+
table_index: int,
|
|
755
|
+
*,
|
|
756
|
+
border_style: str | None = None,
|
|
757
|
+
border_color: Optional[str] = None,
|
|
758
|
+
border_width: Optional[str | float | int] = None,
|
|
759
|
+
fill_color: Optional[str] = None,
|
|
760
|
+
) -> Dict[str, Any]:
|
|
761
|
+
return self._services.tables.set_table_border_fill(
|
|
762
|
+
path,
|
|
763
|
+
table_index,
|
|
764
|
+
border_style=border_style,
|
|
765
|
+
border_color=border_color,
|
|
766
|
+
border_width=border_width,
|
|
767
|
+
fill_color=fill_color,
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
def get_table_cell_map(
|
|
771
|
+
self,
|
|
772
|
+
path: str,
|
|
773
|
+
table_index: int,
|
|
774
|
+
) -> Dict[str, Any]:
|
|
775
|
+
return self._services.tables.get_table_cell_map(path, table_index)
|
|
776
|
+
|
|
777
|
+
def set_table_cell_text(
|
|
778
|
+
self,
|
|
779
|
+
path: str,
|
|
780
|
+
table_index: int,
|
|
781
|
+
row: int,
|
|
782
|
+
col: int,
|
|
783
|
+
text: str,
|
|
784
|
+
*,
|
|
785
|
+
dry_run: bool = False,
|
|
786
|
+
logical: Optional[bool] = None,
|
|
787
|
+
split_merged: Optional[bool] = None,
|
|
788
|
+
auto_fit: bool = False,
|
|
789
|
+
) -> Dict[str, Any]:
|
|
790
|
+
return self._services.tables.set_table_cell_text(
|
|
791
|
+
path,
|
|
792
|
+
table_index,
|
|
793
|
+
row,
|
|
794
|
+
col,
|
|
795
|
+
text,
|
|
796
|
+
dry_run=dry_run,
|
|
797
|
+
logical=logical,
|
|
798
|
+
split_merged=split_merged,
|
|
799
|
+
auto_fit=auto_fit,
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
def replace_table_region(
|
|
803
|
+
self,
|
|
804
|
+
path: str,
|
|
805
|
+
table_index: int,
|
|
806
|
+
start_row: int,
|
|
807
|
+
start_col: int,
|
|
808
|
+
values: Sequence[Sequence[str]],
|
|
809
|
+
*,
|
|
810
|
+
dry_run: bool = False,
|
|
811
|
+
logical: Optional[bool] = None,
|
|
812
|
+
split_merged: Optional[bool] = None,
|
|
813
|
+
auto_fit: bool = False,
|
|
814
|
+
) -> Dict[str, Any]:
|
|
815
|
+
return self._services.tables.replace_table_region(
|
|
816
|
+
path,
|
|
817
|
+
table_index,
|
|
818
|
+
start_row,
|
|
819
|
+
start_col,
|
|
820
|
+
values,
|
|
821
|
+
dry_run=dry_run,
|
|
822
|
+
logical=logical,
|
|
823
|
+
split_merged=split_merged,
|
|
824
|
+
auto_fit=auto_fit,
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
def byte_preserving_patch(
|
|
828
|
+
self,
|
|
829
|
+
path: str,
|
|
830
|
+
patches: Sequence[Dict[str, Any]],
|
|
831
|
+
*,
|
|
832
|
+
output: Optional[str] = None,
|
|
833
|
+
) -> Dict[str, Any]:
|
|
834
|
+
return self._services.transactions.byte_preserving_patch(
|
|
835
|
+
path, patches, output=output
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
def _write_patched(
|
|
839
|
+
self,
|
|
840
|
+
target_path,
|
|
841
|
+
data: bytes,
|
|
842
|
+
payload: Dict[str, Any],
|
|
843
|
+
*,
|
|
844
|
+
output_guard: (
|
|
845
|
+
WorkspaceOutputGuard | WorkspaceMissingParentGuard | None
|
|
846
|
+
) = None,
|
|
847
|
+
output_precondition: (
|
|
848
|
+
WorkspaceOutputGuard | WorkspaceMissingParentGuard | None
|
|
849
|
+
) = None,
|
|
850
|
+
publication_sink: Callable[[WorkspaceOutputGuard], None] | None = None,
|
|
851
|
+
) -> Dict[str, Any]:
|
|
852
|
+
"""Atomic temp-write + open-safety gate for a byte-preserving result
|
|
853
|
+
(shared by byte_preserving_patch / apply_table_ops)."""
|
|
854
|
+
return self._services.save._write_patched(
|
|
855
|
+
target_path,
|
|
856
|
+
data,
|
|
857
|
+
payload,
|
|
858
|
+
output_guard=output_guard,
|
|
859
|
+
output_precondition=output_precondition,
|
|
860
|
+
publication_sink=publication_sink,
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
def apply_table_ops(
|
|
864
|
+
self,
|
|
865
|
+
path: str,
|
|
866
|
+
ops: Sequence[Dict[str, Any]],
|
|
867
|
+
*,
|
|
868
|
+
output: Optional[str] = None,
|
|
869
|
+
render_check: str = "off",
|
|
870
|
+
dry_run: bool = False,
|
|
871
|
+
output_guard: (
|
|
872
|
+
WorkspaceOutputGuard | WorkspaceMissingParentGuard | None
|
|
873
|
+
) = None,
|
|
874
|
+
publication_sink: Callable[[WorkspaceOutputGuard], None] | None = None,
|
|
875
|
+
) -> Dict[str, Any]:
|
|
876
|
+
"""Byte-preserving structural form-fill: apply cell fills + table structure
|
|
877
|
+
ops (delete_column/row/table, insert_row_by_clone, insert_block_by_clone)
|
|
878
|
+
preserving every untouched byte. Cells/tables may be addressed by
|
|
879
|
+
tableAnchor/cellAnchor (unique-or-skip) as well as index. Optional
|
|
880
|
+
real-Hancom render gate.
|
|
881
|
+
|
|
882
|
+
dryRun=true: identical pipeline (resolution/validation/fail-closed all
|
|
883
|
+
real) but writes NOTHING — returns transcript (per-op resolution + before/
|
|
884
|
+
after dims) and applied old→new texts as approval evidence for the user
|
|
885
|
+
consult loop. renderCheck still works on the would-be bytes."""
|
|
886
|
+
return self._services.form_fields.apply_table_ops(
|
|
887
|
+
path,
|
|
888
|
+
ops,
|
|
889
|
+
output=output,
|
|
890
|
+
render_check=render_check,
|
|
891
|
+
dry_run=dry_run,
|
|
892
|
+
output_guard=output_guard,
|
|
893
|
+
publication_sink=publication_sink,
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
def verify_form_fill(
|
|
897
|
+
self,
|
|
898
|
+
path: str,
|
|
899
|
+
before_path: str,
|
|
900
|
+
*,
|
|
901
|
+
require: bool = False,
|
|
902
|
+
) -> Dict[str, Any]:
|
|
903
|
+
"""Render before/after in REAL Hancom and judge overflow/overlap/layout.
|
|
904
|
+
Honest degrade (renderChecked=false) with no oracle unless require=true."""
|
|
905
|
+
return self._services.form_fields.verify_form_fill(
|
|
906
|
+
path, before_path, require=require
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
def score_form_fill(
|
|
910
|
+
self,
|
|
911
|
+
path: str,
|
|
912
|
+
gold_path: str,
|
|
913
|
+
blank_path: str,
|
|
914
|
+
*,
|
|
915
|
+
run_render: bool = True,
|
|
916
|
+
expected_pages: Optional[int] = None,
|
|
917
|
+
) -> Dict[str, Any]:
|
|
918
|
+
"""Score a filled evaluation-plan form against a gold reference on 5 axes
|
|
919
|
+
(A render cleanliness / B byte fidelity vs blank / C structure vs gold /
|
|
920
|
+
D content completeness / E compliance) -> weighted 0-100 + per-axis gap
|
|
921
|
+
report. The fitness function of the form-fill quality loop.
|
|
922
|
+
|
|
923
|
+
``path`` = produced fill, ``goldPath`` = accepted reference of the same
|
|
924
|
+
form family, ``blankPath`` = the empty province form. A requires a real
|
|
925
|
+
Hancom render (renderCheck); with no oracle A is ``unverified`` (never a
|
|
926
|
+
silent pass). Set runRender=false for a fast structural-only pass."""
|
|
927
|
+
return self._services.form_fields.score_form_fill(
|
|
928
|
+
path,
|
|
929
|
+
gold_path,
|
|
930
|
+
blank_path,
|
|
931
|
+
run_render=run_render,
|
|
932
|
+
expected_pages=expected_pages,
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
def apply_body_ops(
|
|
936
|
+
self,
|
|
937
|
+
path: str,
|
|
938
|
+
ops: Sequence[Dict[str, Any]],
|
|
939
|
+
*,
|
|
940
|
+
output: Optional[str] = None,
|
|
941
|
+
dry_run: bool = False,
|
|
942
|
+
output_guard: (
|
|
943
|
+
WorkspaceOutputGuard | WorkspaceMissingParentGuard | None
|
|
944
|
+
) = None,
|
|
945
|
+
publication_sink: Callable[[WorkspaceOutputGuard], None] | None = None,
|
|
946
|
+
) -> Dict[str, Any]:
|
|
947
|
+
"""Byte-preserving BODY(표 밖 직속 문단) ops — Stage 2 결정표의 본문 어휘.
|
|
948
|
+
|
|
949
|
+
ops: replace_text{find,replace,count=1: <hp:t> 텍스트 안에서만, 개수 불일치
|
|
950
|
+
refuse} · delete_paragraph{index: 표 품은 문단 refuse} ·
|
|
951
|
+
insert_paragraph_by_clone{ref_index,count,texts?: 참조 문단 서식 verbatim
|
|
952
|
+
상속} · reorder_paragraphs{start,end,order}. index는 op 실행 시점 기준.
|
|
953
|
+
dryRun=true면 아무것도 쓰지 않고 transcript만(승인 근거)."""
|
|
954
|
+
return self._services.form_fields.apply_body_ops(
|
|
955
|
+
path,
|
|
956
|
+
ops,
|
|
957
|
+
output=output,
|
|
958
|
+
dry_run=dry_run,
|
|
959
|
+
output_guard=output_guard,
|
|
960
|
+
publication_sink=publication_sink,
|
|
961
|
+
)
|
|
962
|
+
|
|
963
|
+
def inspect_fill_residue(
|
|
964
|
+
self,
|
|
965
|
+
path: str,
|
|
966
|
+
*,
|
|
967
|
+
blank_path: Optional[str] = None,
|
|
968
|
+
) -> Dict[str, Any]:
|
|
969
|
+
"""채움본 잔존물 zero-체크(비변형) — 제출 전 기계 게이트(Stage 3).
|
|
970
|
+
|
|
971
|
+
blank를 주면 범례 기반 신호까지: 삭제색 잔존·미수정 샘플(수정색이 blank와
|
|
972
|
+
동일=prose 샘플 미교체) = ERROR. placeholder ◯◯◯/□□□=ERROR, **=각주와
|
|
973
|
+
중의적이라 needs_review, 고아 마커=needs_review. ok=true는 필요조건일 뿐 —
|
|
974
|
+
제출 확언은 렌더 PDF를 사람이 전 페이지 본 뒤에만."""
|
|
975
|
+
return self._services.form_fields.inspect_fill_residue(
|
|
976
|
+
path, blank_path=blank_path
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
def scan_form_guidance(self, path: str, *, max_items: int = 60) -> Dict[str, Any]:
|
|
980
|
+
"""Recon an unfamiliar form (NON-MUTATING) — universal form-fill Stage 1.
|
|
981
|
+
|
|
982
|
+
Walks every run INCLUDING table-cell interiors and table captions, parses
|
|
983
|
+
the form's own colour legend (e.g. "검정 유지/파랑 수정/빨강 삭제"), and
|
|
984
|
+
reports candidates with table_patch-compatible addresses: delete candidates
|
|
985
|
+
(legend-delete colour + guidance keywords), modify targets, placeholder
|
|
986
|
+
tokens (◯◯◯/**/□□□), conditional-choice blocks, empty cells with neighbour
|
|
987
|
+
label + charPr format context, and an honest question list. Candidates are
|
|
988
|
+
proposals — destructive ops still require user approval."""
|
|
989
|
+
return self._services.form_fields.scan_form_guidance(path, max_items=max_items)
|
|
990
|
+
|
|
991
|
+
def apply_evalplan_fill(
|
|
992
|
+
self,
|
|
993
|
+
path: str,
|
|
994
|
+
review_md: str,
|
|
995
|
+
*,
|
|
996
|
+
phase: str = "all",
|
|
997
|
+
output: Optional[str] = None,
|
|
998
|
+
render_check: str = "off",
|
|
999
|
+
score_gold_path: Optional[str] = None,
|
|
1000
|
+
expected_pages: Optional[int] = None,
|
|
1001
|
+
output_guard: (
|
|
1002
|
+
WorkspaceOutputGuard | WorkspaceMissingParentGuard | None
|
|
1003
|
+
) = None,
|
|
1004
|
+
publication_sink: Callable[[WorkspaceOutputGuard], None] | None = None,
|
|
1005
|
+
) -> Dict[str, Any]:
|
|
1006
|
+
"""Whole-form 평가계획 fill: {blank province form + review markdown} ->
|
|
1007
|
+
byte-preserving gold-quality 채움본 in ONE call. Runs the structure-driven
|
|
1008
|
+
recipe (delete red/optional tables + 정기시험 column, fill 운영계획/성취기준/
|
|
1009
|
+
성취수준/성취율/반영비율/rubrics incl. 채점기준 ladder/prose sections),
|
|
1010
|
+
preserving the blank's formatting (never regenerates). Works on BOTH the
|
|
1011
|
+
2015-개정 and 2022-개정 form families (auto-detected from the blank + review).
|
|
1012
|
+
|
|
1013
|
+
``path`` = blank form, ``reviewMd`` = the structured review markdown
|
|
1014
|
+
(Ⅰ 운영계획 + [1]~[11]). ``phase`` = ``"structural"`` | ``"all"`` (default) |
|
|
1015
|
+
``"clean"``; ``"clean"`` additionally runs core's deterministic post-fill
|
|
1016
|
+
cleanup so a submittable 채움본 comes back in one call (report under
|
|
1017
|
+
``contentReport.finalize``). Returns the produced path + per-region
|
|
1018
|
+
contentReport with rubricNeedsReview (honest-defer count, never silent). Set
|
|
1019
|
+
renderCheck='required' to gate on a real Hancom render; pass scoreGoldPath
|
|
1020
|
+
(an accepted form of the same family) to also return the 5-axis scorecard."""
|
|
1021
|
+
return self._services.form_fields.apply_evalplan_fill(
|
|
1022
|
+
path,
|
|
1023
|
+
review_md,
|
|
1024
|
+
phase=phase,
|
|
1025
|
+
output=output,
|
|
1026
|
+
render_check=render_check,
|
|
1027
|
+
score_gold_path=score_gold_path,
|
|
1028
|
+
expected_pages=expected_pages,
|
|
1029
|
+
output_guard=output_guard,
|
|
1030
|
+
publication_sink=publication_sink,
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
def split_table_cell(
|
|
1034
|
+
self,
|
|
1035
|
+
path: str,
|
|
1036
|
+
table_index: int,
|
|
1037
|
+
row: int,
|
|
1038
|
+
col: int,
|
|
1039
|
+
) -> Dict[str, Any]:
|
|
1040
|
+
return self._services.tables.split_table_cell(path, table_index, row, col)
|
|
1041
|
+
|
|
1042
|
+
def copy_table_between_documents(
|
|
1043
|
+
self,
|
|
1044
|
+
source_path: str,
|
|
1045
|
+
source_table_index: int,
|
|
1046
|
+
target_path: str,
|
|
1047
|
+
*,
|
|
1048
|
+
target_section_index: Optional[int] = None,
|
|
1049
|
+
auto_fit: bool = False,
|
|
1050
|
+
) -> Dict[str, Any]:
|
|
1051
|
+
return self._services.tables.copy_table_between_documents(
|
|
1052
|
+
source_path,
|
|
1053
|
+
source_table_index,
|
|
1054
|
+
target_path,
|
|
1055
|
+
target_section_index=target_section_index,
|
|
1056
|
+
auto_fit=auto_fit,
|
|
1057
|
+
)
|
|
1058
|
+
|
|
1059
|
+
def add_shape(
|
|
1060
|
+
self,
|
|
1061
|
+
path: str,
|
|
1062
|
+
*,
|
|
1063
|
+
shape_type: str = "RECTANGLE",
|
|
1064
|
+
section_index: Optional[int] = None,
|
|
1065
|
+
dry_run: bool = True,
|
|
1066
|
+
) -> Dict[str, Any]:
|
|
1067
|
+
return self._services.media.add_shape(
|
|
1068
|
+
path, shape_type=shape_type, section_index=section_index, dry_run=dry_run
|
|
1069
|
+
)
|
|
1070
|
+
|
|
1071
|
+
def add_control(
|
|
1072
|
+
self,
|
|
1073
|
+
path: str,
|
|
1074
|
+
*,
|
|
1075
|
+
control_type: str = "TEXTBOX",
|
|
1076
|
+
section_index: Optional[int] = None,
|
|
1077
|
+
dry_run: bool = True,
|
|
1078
|
+
) -> Dict[str, Any]:
|
|
1079
|
+
return self._services.media.add_control(
|
|
1080
|
+
path,
|
|
1081
|
+
control_type=control_type,
|
|
1082
|
+
section_index=section_index,
|
|
1083
|
+
dry_run=dry_run,
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
def insert_picture(
|
|
1087
|
+
self,
|
|
1088
|
+
path: str,
|
|
1089
|
+
image_base64: str,
|
|
1090
|
+
*,
|
|
1091
|
+
image_format: str = "png",
|
|
1092
|
+
width: Optional[int] = None,
|
|
1093
|
+
height: Optional[int] = None,
|
|
1094
|
+
width_mm: Optional[float] = None,
|
|
1095
|
+
height_mm: Optional[float] = None,
|
|
1096
|
+
section_index: Optional[int] = None,
|
|
1097
|
+
align: Optional[str] = None,
|
|
1098
|
+
output: Optional[str] = None,
|
|
1099
|
+
dry_run: bool = False,
|
|
1100
|
+
) -> Dict[str, Any]:
|
|
1101
|
+
return self._services.media.insert_picture(
|
|
1102
|
+
path,
|
|
1103
|
+
image_base64,
|
|
1104
|
+
image_format=image_format,
|
|
1105
|
+
width=width,
|
|
1106
|
+
height=height,
|
|
1107
|
+
width_mm=width_mm,
|
|
1108
|
+
height_mm=height_mm,
|
|
1109
|
+
section_index=section_index,
|
|
1110
|
+
align=align,
|
|
1111
|
+
output=output,
|
|
1112
|
+
dry_run=dry_run,
|
|
1113
|
+
)
|
|
1114
|
+
|
|
1115
|
+
def replace_picture(
|
|
1116
|
+
self,
|
|
1117
|
+
path: str,
|
|
1118
|
+
image_base64: str,
|
|
1119
|
+
*,
|
|
1120
|
+
image_format: str = "png",
|
|
1121
|
+
picture_index: int = 0,
|
|
1122
|
+
binary_item_id_ref: Optional[str] = None,
|
|
1123
|
+
remove_orphaned: bool = True,
|
|
1124
|
+
output: Optional[str] = None,
|
|
1125
|
+
dry_run: bool = False,
|
|
1126
|
+
) -> Dict[str, Any]:
|
|
1127
|
+
return self._services.media.replace_picture(
|
|
1128
|
+
path,
|
|
1129
|
+
image_base64,
|
|
1130
|
+
image_format=image_format,
|
|
1131
|
+
picture_index=picture_index,
|
|
1132
|
+
binary_item_id_ref=binary_item_id_ref,
|
|
1133
|
+
remove_orphaned=remove_orphaned,
|
|
1134
|
+
output=output,
|
|
1135
|
+
dry_run=dry_run,
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1138
|
+
def add_memo(
|
|
1139
|
+
self,
|
|
1140
|
+
path: str,
|
|
1141
|
+
text: str,
|
|
1142
|
+
*,
|
|
1143
|
+
section_index: Optional[int] = None,
|
|
1144
|
+
author: str | None = None,
|
|
1145
|
+
timestamp: str | None = None,
|
|
1146
|
+
) -> Dict[str, Any]:
|
|
1147
|
+
return self._services.memo_style.add_memo(
|
|
1148
|
+
path, text, section_index=section_index, author=author, timestamp=timestamp
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
def attach_memo_field(
|
|
1152
|
+
self,
|
|
1153
|
+
path: str,
|
|
1154
|
+
paragraph_index: int,
|
|
1155
|
+
memo_id: str,
|
|
1156
|
+
) -> Dict[str, Any]:
|
|
1157
|
+
return self._services.memo_style.attach_memo_field(
|
|
1158
|
+
path, paragraph_index, memo_id
|
|
1159
|
+
)
|
|
1160
|
+
|
|
1161
|
+
def add_memo_with_anchor(
|
|
1162
|
+
self,
|
|
1163
|
+
path: str,
|
|
1164
|
+
*,
|
|
1165
|
+
text: str,
|
|
1166
|
+
section_index: Optional[int] = None,
|
|
1167
|
+
memo_shape_id_ref: str | None = None,
|
|
1168
|
+
) -> Dict[str, Any]:
|
|
1169
|
+
return self._services.memo_style.add_memo_with_anchor(
|
|
1170
|
+
path,
|
|
1171
|
+
text=text,
|
|
1172
|
+
section_index=section_index,
|
|
1173
|
+
memo_shape_id_ref=memo_shape_id_ref,
|
|
1174
|
+
)
|
|
1175
|
+
|
|
1176
|
+
def remove_memo(
|
|
1177
|
+
self,
|
|
1178
|
+
path: str,
|
|
1179
|
+
memo_id: str,
|
|
1180
|
+
*,
|
|
1181
|
+
dry_run: bool = True,
|
|
1182
|
+
) -> Dict[str, Any]:
|
|
1183
|
+
return self._services.memo_style.remove_memo(path, memo_id, dry_run=dry_run)
|
|
1184
|
+
|
|
1185
|
+
def _find_memo(
|
|
1186
|
+
self, document: HwpxDocument, memo_id: str
|
|
1187
|
+
) -> Optional[HwpxOxmlMemo]:
|
|
1188
|
+
return self._services.memo_style._find_memo(document, memo_id)
|
|
1189
|
+
|
|
1190
|
+
def ensure_run_style(self, path: str, **run_style: Any) -> Dict[str, Any]:
|
|
1191
|
+
return self._services.memo_style.ensure_run_style(path, **run_style)
|
|
1192
|
+
|
|
1193
|
+
def list_styles_and_bullets(self, path: str) -> Dict[str, Any]:
|
|
1194
|
+
return self._services.memo_style.list_styles_and_bullets(path)
|
|
1195
|
+
|
|
1196
|
+
def apply_style_to_text_ranges(
|
|
1197
|
+
self,
|
|
1198
|
+
path: str,
|
|
1199
|
+
spans: Sequence[Dict[str, int]],
|
|
1200
|
+
char_pr_id_ref: str,
|
|
1201
|
+
*,
|
|
1202
|
+
dry_run: bool = True,
|
|
1203
|
+
) -> Dict[str, Any]:
|
|
1204
|
+
return self._services.memo_style.apply_style_to_text_ranges(
|
|
1205
|
+
path, spans, char_pr_id_ref, dry_run=dry_run
|
|
1206
|
+
)
|
|
1207
|
+
|
|
1208
|
+
def apply_style_to_paragraphs(
|
|
1209
|
+
self,
|
|
1210
|
+
path: str,
|
|
1211
|
+
paragraph_indexes: Sequence[int],
|
|
1212
|
+
char_pr_id_ref: str,
|
|
1213
|
+
*,
|
|
1214
|
+
dry_run: bool = True,
|
|
1215
|
+
) -> Dict[str, Any]:
|
|
1216
|
+
return self._services.memo_style.apply_style_to_paragraphs(
|
|
1217
|
+
path, paragraph_indexes, char_pr_id_ref, dry_run=dry_run
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
def save(self, path: str) -> Dict[str, Any]:
|
|
1221
|
+
return self._services.save.save(path)
|
|
1222
|
+
|
|
1223
|
+
def save_as(self, path: str, out: str) -> Dict[str, Any]:
|
|
1224
|
+
return self._services.save.save_as(path, out)
|
|
1225
|
+
|
|
1226
|
+
def fill_template(
|
|
1227
|
+
self,
|
|
1228
|
+
source: str,
|
|
1229
|
+
output: str,
|
|
1230
|
+
replacements: Dict[str, str],
|
|
1231
|
+
*,
|
|
1232
|
+
preserve_style: bool = True,
|
|
1233
|
+
split_newlines: bool = True,
|
|
1234
|
+
) -> Dict[str, Any]:
|
|
1235
|
+
return self._services.save.fill_template(
|
|
1236
|
+
source,
|
|
1237
|
+
output,
|
|
1238
|
+
replacements,
|
|
1239
|
+
preserve_style=preserve_style,
|
|
1240
|
+
split_newlines=split_newlines,
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1243
|
+
def export_text(self, path: str) -> Dict[str, Any]:
|
|
1244
|
+
"""Export document content as plain text."""
|
|
1245
|
+
return self._services.preview_export.export_text(path)
|
|
1246
|
+
|
|
1247
|
+
def export_html(self, path: str) -> Dict[str, Any]:
|
|
1248
|
+
"""Export document content as HTML."""
|
|
1249
|
+
return self._services.preview_export.export_html(path)
|
|
1250
|
+
|
|
1251
|
+
def export_markdown(self, path: str) -> Dict[str, Any]:
|
|
1252
|
+
"""Export document content as Markdown."""
|
|
1253
|
+
return self._services.preview_export.export_markdown(path)
|
|
1254
|
+
|
|
1255
|
+
def _preview_output_dir(self, source_path: Path, output_dir: Optional[str]) -> Path:
|
|
1256
|
+
return self._services.preview_export._preview_output_dir(
|
|
1257
|
+
source_path, output_dir
|
|
1258
|
+
)
|
|
1259
|
+
|
|
1260
|
+
def _embed_screenshot_image(
|
|
1261
|
+
self,
|
|
1262
|
+
item: dict[str, Any],
|
|
1263
|
+
png_path: Path,
|
|
1264
|
+
*,
|
|
1265
|
+
embed_images: bool,
|
|
1266
|
+
max_image_bytes: int | None,
|
|
1267
|
+
) -> None:
|
|
1268
|
+
"""Attach a base64 PNG payload to a screenshot item, bounded by a byte cap.
|
|
1269
|
+
|
|
1270
|
+
Keeps the on-disk artifact regardless; only the inline payload is gated so
|
|
1271
|
+
an oversized page degrades to "path only" instead of bloating the response.
|
|
1272
|
+
"""
|
|
1273
|
+
return self._services.preview_export._embed_screenshot_image(
|
|
1274
|
+
item, png_path, embed_images=embed_images, max_image_bytes=max_image_bytes
|
|
1275
|
+
)
|
|
1276
|
+
|
|
1277
|
+
def _capture_preview_pages(
|
|
1278
|
+
self,
|
|
1279
|
+
*,
|
|
1280
|
+
page_html_paths: Sequence[Path],
|
|
1281
|
+
pages: Sequence[Dict[str, Any]],
|
|
1282
|
+
output_dir: Path,
|
|
1283
|
+
max_pages: int | None,
|
|
1284
|
+
embed_images: bool = False,
|
|
1285
|
+
max_image_bytes: int | None = None,
|
|
1286
|
+
) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
|
1287
|
+
return self._services.preview_export._capture_preview_pages(
|
|
1288
|
+
page_html_paths=page_html_paths,
|
|
1289
|
+
pages=pages,
|
|
1290
|
+
output_dir=output_dir,
|
|
1291
|
+
max_pages=max_pages,
|
|
1292
|
+
embed_images=embed_images,
|
|
1293
|
+
max_image_bytes=max_image_bytes,
|
|
1294
|
+
)
|
|
1295
|
+
|
|
1296
|
+
def render_preview(
|
|
1297
|
+
self,
|
|
1298
|
+
path: str,
|
|
1299
|
+
output_dir: Optional[str] = None,
|
|
1300
|
+
mode: str = "pages",
|
|
1301
|
+
screenshot: str = "auto",
|
|
1302
|
+
max_pages: Optional[int] = None,
|
|
1303
|
+
embed_images: bool = False,
|
|
1304
|
+
max_image_bytes: Optional[int] = None,
|
|
1305
|
+
viewer: bool = False,
|
|
1306
|
+
) -> Dict[str, Any]:
|
|
1307
|
+
"""Generate layout-aware HTML and optional PNG preview artifacts.
|
|
1308
|
+
|
|
1309
|
+
When *embed_images* is true, each captured screenshot item also carries
|
|
1310
|
+
an ``imageBase64``/``imageMime`` payload (bounded by *max_image_bytes*,
|
|
1311
|
+
defaulting to ``_DEFAULT_MAX_PREVIEW_IMAGE_BYTES``) so a caller can return
|
|
1312
|
+
the page as an inline image content block.
|
|
1313
|
+
|
|
1314
|
+
When *viewer* is true, the manifest also carries a self-contained
|
|
1315
|
+
scrollable document viewer under ``viewer`` (equations as native MathML
|
|
1316
|
+
with ``python-hwpx[preview]``).
|
|
1317
|
+
"""
|
|
1318
|
+
return self._services.preview_export.render_preview(
|
|
1319
|
+
path,
|
|
1320
|
+
output_dir,
|
|
1321
|
+
mode,
|
|
1322
|
+
screenshot,
|
|
1323
|
+
max_pages,
|
|
1324
|
+
embed_images,
|
|
1325
|
+
max_image_bytes,
|
|
1326
|
+
viewer,
|
|
1327
|
+
)
|
|
1328
|
+
|
|
1329
|
+
def make_blank(self, out: str) -> Dict[str, Any]:
|
|
1330
|
+
return self._services.save.make_blank(out)
|
|
1331
|
+
|
|
1332
|
+
def convert_hwp_to_hwpx(
|
|
1333
|
+
self, source: str, output: Optional[str] = None
|
|
1334
|
+
) -> Dict[str, Any]:
|
|
1335
|
+
return self._services.preview_export.convert_hwp_to_hwpx(source, output)
|
|
1336
|
+
|
|
1337
|
+
def list_master_pages_histories_versions(self, path: str) -> Dict[str, Any]:
|
|
1338
|
+
return self._services.package_validation.list_master_pages_histories_versions(
|
|
1339
|
+
path
|
|
1340
|
+
)
|
|
1341
|
+
|
|
1342
|
+
def object_find_by_tag(
|
|
1343
|
+
self,
|
|
1344
|
+
path: str,
|
|
1345
|
+
tag_name: str,
|
|
1346
|
+
*,
|
|
1347
|
+
max_results: int = 200,
|
|
1348
|
+
) -> Dict[str, Any]:
|
|
1349
|
+
return self._services.read_query.object_find_by_tag(
|
|
1350
|
+
path, tag_name, max_results=max_results
|
|
1351
|
+
)
|
|
1352
|
+
|
|
1353
|
+
def object_find_by_attr(
|
|
1354
|
+
self,
|
|
1355
|
+
path: str,
|
|
1356
|
+
element_type: str | None,
|
|
1357
|
+
attr: str,
|
|
1358
|
+
value: str | None,
|
|
1359
|
+
*,
|
|
1360
|
+
max_results: int = 200,
|
|
1361
|
+
) -> Dict[str, Any]:
|
|
1362
|
+
return self._services.read_query.object_find_by_attr(
|
|
1363
|
+
path, element_type, attr, value, max_results=max_results
|
|
1364
|
+
)
|
|
1365
|
+
|
|
1366
|
+
def validate_structure(self, path: str, level: str = "basic") -> Dict[str, Any]:
|
|
1367
|
+
return self._services.package_validation.validate_structure(path, level)
|
|
1368
|
+
|
|
1369
|
+
def lint_text_conventions(
|
|
1370
|
+
self,
|
|
1371
|
+
path: str,
|
|
1372
|
+
*,
|
|
1373
|
+
max_line_len: Optional[int] = None,
|
|
1374
|
+
forbid_patterns: Optional[Sequence[str]] = None,
|
|
1375
|
+
) -> Dict[str, Any]:
|
|
1376
|
+
return self._services.package_validation.lint_text_conventions(
|
|
1377
|
+
path, max_line_len=max_line_len, forbid_patterns=forbid_patterns
|
|
1378
|
+
)
|
|
1379
|
+
|
|
1380
|
+
def plan_edit(
|
|
1381
|
+
self,
|
|
1382
|
+
*,
|
|
1383
|
+
path: str,
|
|
1384
|
+
operations: Sequence[Dict[str, Any]],
|
|
1385
|
+
trace_id: Optional[str] = None,
|
|
1386
|
+
) -> Dict[str, Any]:
|
|
1387
|
+
return self._services.planning.plan_edit(
|
|
1388
|
+
path=path, operations=operations, trace_id=trace_id
|
|
1389
|
+
)
|
|
1390
|
+
|
|
1391
|
+
def preview_edit(
|
|
1392
|
+
self,
|
|
1393
|
+
*,
|
|
1394
|
+
plan_id: str,
|
|
1395
|
+
trace_id: Optional[str] = None,
|
|
1396
|
+
) -> Dict[str, Any]:
|
|
1397
|
+
return self._services.planning.preview_edit(plan_id=plan_id, trace_id=trace_id)
|
|
1398
|
+
|
|
1399
|
+
def apply_edit(
|
|
1400
|
+
self,
|
|
1401
|
+
*,
|
|
1402
|
+
plan_id: str,
|
|
1403
|
+
confirm: bool,
|
|
1404
|
+
idempotency_key: Optional[str] = None,
|
|
1405
|
+
trace_id: Optional[str] = None,
|
|
1406
|
+
) -> Dict[str, Any]:
|
|
1407
|
+
return self._services.planning.apply_edit(
|
|
1408
|
+
plan_id=plan_id,
|
|
1409
|
+
confirm=confirm,
|
|
1410
|
+
idempotency_key=idempotency_key,
|
|
1411
|
+
trace_id=trace_id,
|
|
1412
|
+
)
|
|
1413
|
+
|
|
1414
|
+
def search(
|
|
1415
|
+
self,
|
|
1416
|
+
*,
|
|
1417
|
+
path: str,
|
|
1418
|
+
pattern: str,
|
|
1419
|
+
scope: Optional[str] = None,
|
|
1420
|
+
is_regex: bool = False,
|
|
1421
|
+
limit: int = 20,
|
|
1422
|
+
) -> Dict[str, Any]:
|
|
1423
|
+
return self._services.planning.search(
|
|
1424
|
+
path=path, pattern=pattern, scope=scope, is_regex=is_regex, limit=limit
|
|
1425
|
+
)
|
|
1426
|
+
|
|
1427
|
+
def get_context(
|
|
1428
|
+
self,
|
|
1429
|
+
*,
|
|
1430
|
+
path: str,
|
|
1431
|
+
target: Dict[str, Any],
|
|
1432
|
+
window: int = 1,
|
|
1433
|
+
) -> Dict[str, Any]:
|
|
1434
|
+
return self._services.planning.get_context(
|
|
1435
|
+
path=path, target=target, window=window
|
|
1436
|
+
)
|
|
1437
|
+
|
|
1438
|
+
def package_get_xml(self, path: str, part_name: str) -> Dict[str, Any]:
|
|
1439
|
+
return self._services.package_validation.package_get_xml(path, part_name)
|