python-hwpx 4.2.0__py3-none-any.whl → 5.0.2__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.
Files changed (152) hide show
  1. hwpx/__init__.py +240 -95
  2. hwpx/_document/__init__.py +1 -1
  3. hwpx/_document/_units.py +1 -1
  4. hwpx/_document/fields.py +1 -1
  5. hwpx/_document/layout.py +1 -1
  6. hwpx/_document/media.py +1 -1
  7. hwpx/_document/memos.py +1 -1
  8. hwpx/_document/persistence.py +1 -1
  9. hwpx/_document/shapes.py +14 -3
  10. hwpx/_document/tracked.py +53 -9
  11. hwpx/body_patch.py +3 -3
  12. hwpx/document.py +35 -3
  13. hwpx/errors.py +9 -2
  14. hwpx/form_fit/engine.py +2 -2
  15. hwpx/form_fit/measure.py +1 -1
  16. hwpx/opc/package.py +51 -11
  17. hwpx/oxml/body.py +56 -0
  18. hwpx/oxml/color.py +60 -0
  19. hwpx/oxml/objects.py +140 -14
  20. hwpx/oxml/paragraph.py +95 -4
  21. hwpx/patch.py +22 -0
  22. hwpx/quality/rendering.py +136 -0
  23. hwpx/quality/report.py +1 -1
  24. hwpx/quality/save_pipeline.py +12 -10
  25. hwpx/table_patch.py +77 -15
  26. hwpx/tools/__init__.py +19 -63
  27. hwpx/tools/doc_diff.py +0 -39
  28. hwpx/tools/exporter.py +20 -7
  29. hwpx/tools/mail_merge.py +16 -12
  30. hwpx/tools/page_guard.py +11 -12
  31. hwpx/tools/read_fidelity.py +1 -1
  32. hwpx/tools/redline.py +81 -42
  33. hwpx/tools/template_analyzer.py +0 -46
  34. hwpx/tools/toc_author.py +1 -1
  35. hwpx/tools/toc_fidelity.py +45 -7
  36. python_hwpx-5.0.2.dist-info/METADATA +238 -0
  37. python_hwpx-5.0.2.dist-info/RECORD +112 -0
  38. {python_hwpx-4.2.0.dist-info → python_hwpx-5.0.2.dist-info}/entry_points.txt +0 -2
  39. hwpx/agent/__init__.py +0 -125
  40. hwpx/agent/_batch_verification.py +0 -383
  41. hwpx/agent/blueprint/__init__.py +0 -58
  42. hwpx/agent/blueprint/bundle.py +0 -282
  43. hwpx/agent/blueprint/catalog.py +0 -136
  44. hwpx/agent/blueprint/dump.py +0 -520
  45. hwpx/agent/blueprint/mapping.py +0 -312
  46. hwpx/agent/blueprint/model.py +0 -722
  47. hwpx/agent/blueprint/native.py +0 -621
  48. hwpx/agent/blueprint/replay.py +0 -622
  49. hwpx/agent/catalog.py +0 -252
  50. hwpx/agent/cli.py +0 -631
  51. hwpx/agent/commands.py +0 -1383
  52. hwpx/agent/document.py +0 -801
  53. hwpx/agent/form_plan.py +0 -1760
  54. hwpx/agent/model.py +0 -808
  55. hwpx/agent/path.py +0 -155
  56. hwpx/agent/query.py +0 -230
  57. hwpx/agent/story.py +0 -207
  58. hwpx/authoring.py +0 -3483
  59. hwpx/benchmark/__init__.py +0 -25
  60. hwpx/benchmark/blind_eval.py +0 -261
  61. hwpx/builder/__init__.py +0 -52
  62. hwpx/builder/core.py +0 -992
  63. hwpx/builder/report.py +0 -195
  64. hwpx/conformance/__init__.py +0 -54
  65. hwpx/conformance/badges.py +0 -198
  66. hwpx/conformance/corpus/corpus.json +0 -53
  67. hwpx/conformance/corpus/meeting_summary.hwpx +0 -0
  68. hwpx/conformance/corpus/notice.hwpx +0 -0
  69. hwpx/conformance/corpus/report_table.hwpx +0 -0
  70. hwpx/conformance/corpus.py +0 -260
  71. hwpx/conformance/report.py +0 -223
  72. hwpx/conformance/roundtrip_batch.py +0 -171
  73. hwpx/conformance/runner.py +0 -395
  74. hwpx/design/__init__.py +0 -30
  75. hwpx/design/_support.py +0 -144
  76. hwpx/design/composer.py +0 -282
  77. hwpx/design/harvest.py +0 -305
  78. hwpx/design/plan.py +0 -69
  79. hwpx/design/profile.py +0 -88
  80. hwpx/design/profiles/application_form/fragments/body.xml +0 -1
  81. hwpx/design/profiles/application_form/fragments/heading.xml +0 -1
  82. hwpx/design/profiles/application_form/fragments/info_table.xml +0 -1
  83. hwpx/design/profiles/application_form/fragments/title.xml +0 -1
  84. hwpx/design/profiles/application_form/profile.json +0 -25
  85. hwpx/design/profiles/application_form/template.hwpx +0 -0
  86. hwpx/design/profiles/home_notice/fragments/body.xml +0 -1
  87. hwpx/design/profiles/home_notice/fragments/heading.xml +0 -1
  88. hwpx/design/profiles/home_notice/fragments/title.xml +0 -1
  89. hwpx/design/profiles/home_notice/profile.json +0 -24
  90. hwpx/design/profiles/home_notice/template.hwpx +0 -0
  91. hwpx/design/profiles/official_notice/fragments/body.xml +0 -1
  92. hwpx/design/profiles/official_notice/fragments/heading.xml +0 -1
  93. hwpx/design/profiles/official_notice/fragments/info_table.xml +0 -1
  94. hwpx/design/profiles/official_notice/fragments/title.xml +0 -1
  95. hwpx/design/profiles/official_notice/profile.json +0 -25
  96. hwpx/design/profiles/official_notice/template.hwpx +0 -0
  97. hwpx/design/profiles/report/fragments/body.xml +0 -1
  98. hwpx/design/profiles/report/fragments/heading.xml +0 -1
  99. hwpx/design/profiles/report/fragments/info_table.xml +0 -1
  100. hwpx/design/profiles/report/fragments/title.xml +0 -1
  101. hwpx/design/profiles/report/profile.json +0 -25
  102. hwpx/design/profiles/report/template.hwpx +0 -0
  103. hwpx/design/validator.py +0 -107
  104. hwpx/evalplan_fill.py +0 -2727
  105. hwpx/exam/__init__.py +0 -22
  106. hwpx/exam/compose.py +0 -237
  107. hwpx/exam/ir.py +0 -41
  108. hwpx/exam/measure.py +0 -147
  109. hwpx/exam/parser.py +0 -145
  110. hwpx/exam/profile.py +0 -116
  111. hwpx/fill_residue.py +0 -200
  112. hwpx/form_fill.py +0 -333
  113. hwpx/form_fit/seal.py +0 -450
  114. hwpx/form_fit/wordbox.py +0 -1238
  115. hwpx/formfill_quality.py +0 -822
  116. hwpx/guidance_scan.py +0 -616
  117. hwpx/presets/__init__.py +0 -22
  118. hwpx/presets/proposal.py +0 -538
  119. hwpx/template_formfit.py +0 -627
  120. hwpx/tools/advanced_generators.py +0 -154
  121. hwpx/tools/fuzz/__init__.py +0 -36
  122. hwpx/tools/fuzz/__main__.py +0 -80
  123. hwpx/tools/fuzz/catalog.py +0 -320
  124. hwpx/tools/fuzz/generator.py +0 -195
  125. hwpx/tools/fuzz/minimize.py +0 -33
  126. hwpx/tools/fuzz/runner.py +0 -502
  127. hwpx/tools/official_lint.py +0 -478
  128. hwpx/tools/pii.py +0 -389
  129. hwpx/tools/report_parser.py +0 -135
  130. hwpx/tools/style_profile.py +0 -437
  131. hwpx/tools/table_compute.py +0 -477
  132. hwpx/visual/__init__.py +0 -115
  133. hwpx/visual/_hancom_open_rate.ps1 +0 -374
  134. hwpx/visual/_refresh_hwpx_mac.applescript +0 -162
  135. hwpx/visual/_render_hwpx.ps1 +0 -72
  136. hwpx/visual/_render_hwpx_mac.applescript +0 -249
  137. hwpx/visual/detectors.py +0 -151
  138. hwpx/visual/diff.py +0 -153
  139. hwpx/visual/fixture_corpus.py +0 -210
  140. hwpx/visual/hancom_worker.py +0 -287
  141. hwpx/visual/masks.py +0 -51
  142. hwpx/visual/oracle.py +0 -942
  143. hwpx/visual/page_qa.py +0 -244
  144. hwpx/visual/qa_contracts.py +0 -293
  145. hwpx/visual/qa_metrics.py +0 -236
  146. hwpx/visual/report.py +0 -47
  147. python_hwpx-4.2.0.dist-info/METADATA +0 -182
  148. python_hwpx-4.2.0.dist-info/RECORD +0 -218
  149. {python_hwpx-4.2.0.dist-info → python_hwpx-5.0.2.dist-info}/WHEEL +0 -0
  150. {python_hwpx-4.2.0.dist-info → python_hwpx-5.0.2.dist-info}/licenses/LICENSE +0 -0
  151. {python_hwpx-4.2.0.dist-info → python_hwpx-5.0.2.dist-info}/licenses/NOTICE +0 -0
  152. {python_hwpx-4.2.0.dist-info → python_hwpx-5.0.2.dist-info}/top_level.txt +0 -0
hwpx/__init__.py CHANGED
@@ -13,13 +13,15 @@
13
13
 
14
14
  import importlib
15
15
  import warnings
16
- from importlib.metadata import PackageNotFoundError, version as _metadata_version
16
+ from dataclasses import dataclass
17
+ from typing import Literal
17
18
 
18
19
 
19
20
  def _resolve_version() -> str:
20
- """패키지 메타데이터에서 현재 배포 버전을 조회합니다."""
21
+ """패키지 메타데이터에서 배포 버전을 조회합니다(import는 함수 스코프 유지)."""
22
+ from importlib.metadata import PackageNotFoundError, version
21
23
  try:
22
- return _metadata_version("python-hwpx")
24
+ return version("python-hwpx")
23
25
  except PackageNotFoundError:
24
26
  return "0+unknown"
25
27
 
@@ -48,12 +50,11 @@ _EXPERIMENTAL_EXPORTS = {
48
50
  "render_document_viewer": "hwpx.tools.document_viewer",
49
51
  }
50
52
 
51
- _DEPRECATED_EXPORTS = {
52
- "analyze_template_formfit": "hwpx.template_formfit",
53
- "apply_template_formfit": "hwpx.template_formfit",
54
- "TEMPLATE_FORMFIT_BASELINE_SCHEMA_VERSION": "hwpx.template_formfit",
55
- "TEMPLATE_FORMFIT_PLAN_SCHEMA_VERSION": "hwpx.template_formfit",
56
- }
53
+ # Emptied in 5.0. The 4.x notice on these names said they would go in the next
54
+ # major, and this is it: analyze_template_formfit, apply_template_formfit and the
55
+ # two schema-version constants are gone with hwpx.template_formfit. Use
56
+ # hwpx.table_patch.fill_cells, or the MCP form-fill trio.
57
+ _DEPRECATED_EXPORTS: dict[str, str] = {}
57
58
 
58
59
  # deprecated formfit 표면의 공통 대체 경로 안내.
59
60
  _FORMFIT_REPLACEMENT = (
@@ -78,6 +79,197 @@ def _deprecated_message(name: str) -> str:
78
79
  )
79
80
 
80
81
 
82
+ # --- 5.0에서 이동한 이름 -------------------------------------------------
83
+ #
84
+ # 응용 워크플로는 companion 패키지가 소유한다. 여기 남는 것은 **표뿐**이다 —
85
+ # 그 패키지를 import하지 않는다. 설치돼 있는지에 따라 core의 동작이 달라지면
86
+ # 의존 방향이 뒤집히고, 그건 5.0이 없애려던 바로 그 결합이다.
87
+ #
88
+ # 표가 하는 일은 오류 메시지 하나다. 기본 Python 오류는 이름이 사라졌다는 것만
89
+ # 알려주고 어디로 갔는지는 말해주지 않는다.
90
+ _SurfaceKind = Literal["symbol", "module", "renamed", "retired"]
91
+
92
+
93
+ @dataclass(frozen=True, slots=True)
94
+ class _CompanionSurface:
95
+ """One executable migration hint without importing the companion."""
96
+
97
+ kind: _SurfaceKind
98
+ target_module: str | None = None
99
+ target_name: str | None = None
100
+ replacement: str | None = None
101
+
102
+ def import_statement(self, legacy_name: str) -> str | None:
103
+ if self.kind == "retired":
104
+ return None
105
+ if self.target_module is None:
106
+ raise AssertionError(f"{legacy_name}: target_module is required")
107
+ if self.kind == "module" or (
108
+ self.kind == "renamed" and self.target_name is None
109
+ ):
110
+ return f"import {self.target_module} as {legacy_name}"
111
+ target_name = self.target_name or legacy_name
112
+ alias = f" as {legacy_name}" if target_name != legacy_name else ""
113
+ return f"from {self.target_module} import {target_name}{alias}"
114
+
115
+
116
+ def _symbol(target_module: str, target_name: str | None = None) -> _CompanionSurface:
117
+ return _CompanionSurface("symbol", target_module, target_name)
118
+
119
+
120
+ def _module(target_module: str) -> _CompanionSurface:
121
+ return _CompanionSurface("module", target_module)
122
+
123
+
124
+ def _renamed(
125
+ target_module: str,
126
+ target_name: str | None = None,
127
+ ) -> _CompanionSurface:
128
+ return _CompanionSurface("renamed", target_module, target_name)
129
+
130
+
131
+ _MOVED_TO_COMPANION: dict[str, _CompanionSurface] = {
132
+ "AUTHORING_REPORT_VERSION": _symbol("hwpx_automation.office.authoring"),
133
+ "DEFAULT_STYLE_PRESET": _symbol("hwpx_automation.office.authoring"),
134
+ "DOCUMENT_PLAN_SCHEMA_VERSION": _symbol("hwpx_automation.office.authoring"),
135
+ "DocumentBlock": _symbol("hwpx_automation.office.authoring"),
136
+ "DocumentPlan": _symbol("hwpx_automation.office.authoring"),
137
+ "DocumentStylePreset": _symbol("hwpx_automation.office.authoring"),
138
+ "OFFICIAL_DOCUMENT_STYLE_REPORT_VERSION": _symbol(
139
+ "hwpx_automation.office.compliance.official_lint"
140
+ ),
141
+ "PlanValidationIssue": _symbol("hwpx_automation.office.authoring"),
142
+ "PlanValidationReport": _symbol("hwpx_automation.office.authoring"),
143
+ "STYLE_PROFILE_COMPARISON_SCHEMA_VERSION": _symbol(
144
+ "hwpx_automation.office.authoring.style_profile"
145
+ ),
146
+ "STYLE_PROFILE_SCHEMA_VERSION": _symbol(
147
+ "hwpx_automation.office.authoring.style_profile"
148
+ ),
149
+ "TABLE_COMPUTE_REPORT_VERSION": _symbol("hwpx_automation.office.utilities"),
150
+ "TEMPLATE_REGISTRY_SCHEMA_VERSION": _symbol(
151
+ "hwpx_automation.office.authoring.style_profile"
152
+ ),
153
+ "agent": _module("hwpx_automation.office.agent"),
154
+ "apply_style_profile_to_plan": _symbol(
155
+ "hwpx_automation.office.authoring.style_profile"
156
+ ),
157
+ "approval_box": _symbol("hwpx_automation.office.authoring.builder"),
158
+ "authoring": _module("hwpx_automation.office.authoring"),
159
+ "build_comparison_table_plan": _symbol(
160
+ "hwpx_automation.office.document_ops"
161
+ ),
162
+ "build_image_grid": _symbol("hwpx_automation.office.authoring"),
163
+ "build_meeting_nameplates": _symbol(
164
+ "hwpx_automation.office.authoring.advanced_generators"
165
+ ),
166
+ "build_organization_chart": _symbol(
167
+ "hwpx_automation.office.authoring.advanced_generators"
168
+ ),
169
+ "builder": _module("hwpx_automation.office.authoring.builder"),
170
+ "compare_style_profiles": _symbol(
171
+ "hwpx_automation.office.authoring.style_profile"
172
+ ),
173
+ "create_document_from_plan": _symbol("hwpx_automation.office.authoring"),
174
+ "describe_template": _symbol(
175
+ "hwpx_automation.office.authoring.style_profile"
176
+ ),
177
+ "design": _module("hwpx_automation.office.authoring.design"),
178
+ "evalplan_fill": _renamed("hwpx_automation.office.evalplan"),
179
+ "exam": _module("hwpx_automation.office.exam"),
180
+ "extract_style_profile": _symbol(
181
+ "hwpx_automation.office.authoring.style_profile"
182
+ ),
183
+ "fill_residue": _module(
184
+ "hwpx_automation.office.form_fill.fill_residue"
185
+ ),
186
+ "form_fill": _module("hwpx_automation.office.form_fill"),
187
+ "formfill_quality": _renamed(
188
+ "hwpx_automation.office.form_fill.quality"
189
+ ),
190
+ "get_document_plan_schema": _symbol("hwpx_automation.office.authoring"),
191
+ "guidance_scan": _renamed(
192
+ "hwpx_automation.office.form_fill.guidance"
193
+ ),
194
+ "inspect_document_authoring_quality": _symbol(
195
+ "hwpx_automation.office.authoring"
196
+ ),
197
+ "inspect_official_document_style": _symbol(
198
+ "hwpx_automation.office.compliance.official_lint"
199
+ ),
200
+ "inspect_operating_plan_quality": _symbol(
201
+ "hwpx_automation.office.authoring"
202
+ ),
203
+ "list_templates": _symbol(
204
+ "hwpx_automation.office.authoring.style_profile"
205
+ ),
206
+ "mail_merge": _renamed(
207
+ "hwpx_automation.office.document_ops",
208
+ "build_mail_merge",
209
+ ),
210
+ "normalize_document_plan": _symbol("hwpx_automation.office.authoring"),
211
+ "placeholder_fill_report": _symbol(
212
+ "hwpx_automation.office.authoring.style_profile"
213
+ ),
214
+ "presets": _module("hwpx_automation.office.authoring.presets"),
215
+ "register_template": _symbol(
216
+ "hwpx_automation.office.authoring.style_profile"
217
+ ),
218
+ "table_compute": _symbol("hwpx_automation.office.utilities"),
219
+ "template_formfit": _module(
220
+ "hwpx_automation.office.form_fill.template_formfit"
221
+ ),
222
+ "validate_document_plan": _symbol("hwpx_automation.office.authoring"),
223
+ "visual": _renamed("hwpx_automation.office.rendering"),
224
+ }
225
+
226
+ _RETIRED_SURFACES: dict[str, _CompanionSurface] = {
227
+ "analyze_template_formfit": _CompanionSurface(
228
+ "retired",
229
+ replacement=(
230
+ "MCP analyze_form_fill → apply_form_fill → verify_form_fill 경로를 "
231
+ "사용하세요."
232
+ ),
233
+ ),
234
+ "apply_template_formfit": _CompanionSurface(
235
+ "retired",
236
+ replacement=(
237
+ "MCP analyze_form_fill → apply_form_fill → verify_form_fill 경로를 "
238
+ "사용하세요."
239
+ ),
240
+ ),
241
+ "TEMPLATE_FORMFIT_BASELINE_SCHEMA_VERSION": _CompanionSurface(
242
+ "retired",
243
+ replacement="새 form-fill ToolSpec 스키마를 사용하세요.",
244
+ ),
245
+ "TEMPLATE_FORMFIT_PLAN_SCHEMA_VERSION": _CompanionSurface(
246
+ "retired",
247
+ replacement="새 form-fill ToolSpec 스키마를 사용하세요.",
248
+ ),
249
+ }
250
+
251
+
252
+ class MovedToCompanion(ImportError):
253
+ """이동한 이름에 접근했다.
254
+
255
+ ImportError인 이유. ``from hwpx import X``는 모듈 ``__getattr__``이 던진
256
+ AttributeError를 잡아 자기만의 밋밋한 ImportError로 바꿔버린다 — 행선지를
257
+ 적어 넣어도 사용자에게 닿지 않는다는 뜻이다. ImportError로 던지면 그대로
258
+ 올라간다. 공개 코드에서 깨지는 형태가 압도적으로 ``from hwpx import X``라
259
+ 그쪽을 택했다.
260
+
261
+ 대가는 ``hasattr(hwpx, "...")``이다. Python의 hasattr은 AttributeError만
262
+ 잡으므로 False 대신 예외가 나간다. 둘 다 상속해서 해결하려 했지만
263
+ ImportError와 AttributeError는 C 레벨 레이아웃이 충돌해 불가능하다.
264
+ 예외가 나가더라도 메시지가 이유를 말해주므로 조용히 False가 되는 것보다는
265
+ 낫다고 판단했다.
266
+ """
267
+
268
+
269
+ class RetiredSurface(ImportError):
270
+ """A removed compatibility surface whose replacement is not another import."""
271
+
272
+
81
273
  def __getattr__(name: str) -> object:
82
274
  """Resolve dynamic module attributes.
83
275
 
@@ -90,14 +282,43 @@ def __getattr__(name: str) -> object:
90
282
 
91
283
  module_name = _EXPERIMENTAL_EXPORTS.get(name)
92
284
  if module_name is not None:
285
+ if module_name != "hwpx" and not module_name.startswith("hwpx."):
286
+ raise AttributeError(
287
+ "hwpx lazy-export targets must remain inside the 'hwpx' package"
288
+ )
93
289
  warnings.warn(_experimental_message(name), DeprecationWarning, stacklevel=2)
94
290
  return getattr(importlib.import_module(module_name), name)
95
291
 
96
292
  module_name = _DEPRECATED_EXPORTS.get(name)
97
293
  if module_name is not None:
294
+ if module_name != "hwpx" and not module_name.startswith("hwpx."):
295
+ raise AttributeError(
296
+ "hwpx lazy-export targets must remain inside the 'hwpx' package"
297
+ )
98
298
  warnings.warn(_deprecated_message(name), DeprecationWarning, stacklevel=2)
99
299
  return getattr(importlib.import_module(module_name), name)
100
300
 
301
+ moved = _MOVED_TO_COMPANION.get(name)
302
+ if moved is not None:
303
+ statement = moved.import_statement(name)
304
+ assert statement is not None
305
+ raise MovedToCompanion(
306
+ f"{name}은(는) python-hwpx 5.0에서 companion 패키지로 이동했습니다.\n"
307
+ f" pip install python-hwpx-automation\n"
308
+ f" {statement}\n"
309
+ f'4.x를 계속 쓰려면: pip install "python-hwpx<5"\n'
310
+ f"전체 대체표: docs/migration-5.0.md"
311
+ )
312
+
313
+ retired = _RETIRED_SURFACES.get(name)
314
+ if retired is not None:
315
+ raise RetiredSurface(
316
+ f"{name}은(는) 예고대로 python-hwpx 5.0에서 제거되었습니다. "
317
+ f"{retired.replacement}\n"
318
+ f'4.x를 계속 쓰려면: pip install "python-hwpx<5"\n'
319
+ f"전체 대체표: docs/migration-5.0.md"
320
+ )
321
+
101
322
  raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
102
323
 
103
324
 
@@ -118,15 +339,9 @@ from .tools.text_extractor import (
118
339
  TextExtractor,
119
340
  )
120
341
  from .tools.object_finder import FoundElement, ObjectFinder
121
- from .tools.advanced_generators import (
122
- build_image_grid,
123
- build_meeting_nameplates,
124
- build_organization_chart,
125
- )
126
342
  from .tools.doc_diff import (
127
343
  DOC_DIFF_REPORT_VERSION,
128
344
  REFERENCE_CONSISTENCY_REPORT_VERSION,
129
- build_comparison_table_plan,
130
345
  diff_paragraphs,
131
346
  doc_diff,
132
347
  inspect_reference_consistency,
@@ -135,15 +350,7 @@ from .tools.mail_merge import (
135
350
  MAIL_MERGE_REPORT_VERSION,
136
351
  inspect_mail_merge_placeholders,
137
352
  load_mail_merge_rows,
138
- mail_merge,
139
- )
140
- from .tools.table_compute import (
141
- TABLE_COMPUTE_REPORT_VERSION,
142
- table_compute,
143
- )
144
- from .tools.official_lint import (
145
- OFFICIAL_DOCUMENT_STYLE_REPORT_VERSION,
146
- inspect_official_document_style,
353
+ merge_template_rows,
147
354
  )
148
355
  from .tools.package_validator import (
149
356
  EditorOpenSafetyReport,
@@ -151,18 +358,6 @@ from .tools.package_validator import (
151
358
  validate_editor_open_safety,
152
359
  validate_package,
153
360
  )
154
- from .tools.style_profile import (
155
- STYLE_PROFILE_COMPARISON_SCHEMA_VERSION,
156
- STYLE_PROFILE_SCHEMA_VERSION,
157
- TEMPLATE_REGISTRY_SCHEMA_VERSION,
158
- apply_style_profile_to_plan,
159
- compare_style_profiles,
160
- describe_template,
161
- extract_style_profile,
162
- list_templates,
163
- placeholder_fill_report,
164
- register_template,
165
- )
166
361
  from .ingest import HwpxMarkdownConverter
167
362
  from .errors import HwpxError
168
363
  from .mutation_report import (
@@ -178,23 +373,6 @@ from .patch import (
178
373
  )
179
374
  from .document import HwpxDocument
180
375
  from .package import HwpxPackage
181
- from .authoring import (
182
- AUTHORING_REPORT_VERSION,
183
- DEFAULT_STYLE_PRESET,
184
- DOCUMENT_PLAN_SCHEMA_VERSION,
185
- DocumentBlock,
186
- DocumentPlan,
187
- DocumentStylePreset,
188
- PlanValidationIssue,
189
- PlanValidationReport,
190
- create_document_from_plan,
191
- get_document_plan_schema,
192
- inspect_document_authoring_quality,
193
- inspect_operating_plan_quality,
194
- normalize_document_plan,
195
- validate_document_plan,
196
- )
197
- from .builder import approval_box
198
376
  from .quality import (
199
377
  QualityPolicy,
200
378
  SavePipeline,
@@ -202,18 +380,20 @@ from .quality import (
202
380
  )
203
381
 
204
382
  __all__ = [
383
+ "inspect_reference_consistency",
384
+ "doc_diff",
385
+ "diff_paragraphs",
386
+ "REFERENCE_CONSISTENCY_REPORT_VERSION",
387
+ "DOC_DIFF_REPORT_VERSION",
388
+ "merge_template_rows",
389
+ "load_mail_merge_rows",
390
+ "inspect_mail_merge_placeholders",
391
+ "MAIL_MERGE_REPORT_VERSION",
205
392
  "QualityPolicy",
206
393
  "SavePipeline",
207
394
  "VisualCompleteReport",
208
395
  "__version__",
209
- "AUTHORING_REPORT_VERSION",
210
396
  "DEFAULT_NAMESPACES",
211
- "DEFAULT_STYLE_PRESET",
212
- "DOCUMENT_PLAN_SCHEMA_VERSION",
213
- "get_document_plan_schema",
214
- "DocumentBlock",
215
- "DocumentPlan",
216
- "DocumentStylePreset",
217
397
  "EditorOpenSafetyReport",
218
398
  "ParagraphInfo",
219
399
  "PackageValidationReport",
@@ -221,7 +401,6 @@ __all__ = [
221
401
  "HwpxError",
222
402
  "MutationReport",
223
403
  "PreservationDowngradeError",
224
- "PlanValidationReport",
225
404
  "ParagraphTextPatch",
226
405
  "PatchApplied",
227
406
  "PatchSkipped",
@@ -230,43 +409,9 @@ __all__ = [
230
409
  "FoundElement",
231
410
  "HwpxMarkdownConverter",
232
411
  "ObjectFinder",
233
- "OFFICIAL_DOCUMENT_STYLE_REPORT_VERSION",
234
- "DOC_DIFF_REPORT_VERSION",
235
- "REFERENCE_CONSISTENCY_REPORT_VERSION",
236
- "MAIL_MERGE_REPORT_VERSION",
237
- "STYLE_PROFILE_COMPARISON_SCHEMA_VERSION",
238
- "STYLE_PROFILE_SCHEMA_VERSION",
239
- "TEMPLATE_REGISTRY_SCHEMA_VERSION",
240
- "TABLE_COMPUTE_REPORT_VERSION",
241
- "apply_style_profile_to_plan",
242
- "build_comparison_table_plan",
243
- "build_image_grid",
244
- "build_meeting_nameplates",
245
- "build_organization_chart",
246
- "diff_paragraphs",
247
- "doc_diff",
248
- "compare_style_profiles",
249
- "PlanValidationIssue",
250
412
  "HwpxDocument",
251
413
  "HwpxPackage",
252
- "create_document_from_plan",
253
- "approval_box",
254
- "describe_template",
255
- "extract_style_profile",
256
- "inspect_document_authoring_quality",
257
- "inspect_mail_merge_placeholders",
258
- "inspect_official_document_style",
259
- "inspect_reference_consistency",
260
- "inspect_operating_plan_quality",
261
- "list_templates",
262
- "load_mail_merge_rows",
263
- "mail_merge",
264
- "normalize_document_plan",
265
- "placeholder_fill_report",
266
- "validate_document_plan",
267
414
  "validate_editor_open_safety",
268
415
  "validate_package",
269
416
  "paragraph_patch",
270
- "register_template",
271
- "table_compute",
272
417
  ]
@@ -1,2 +1,2 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
- """Domain owners behind the HwpxDocument facade (S-084)."""
2
+ """Domain owners behind the HwpxDocument facade."""
hwpx/_document/_units.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
- """Shared HWPUNIT conversion helpers for the HwpxDocument owner modules (S-084)."""
2
+ """Shared HWPUNIT conversion helpers for the HwpxDocument owner modules."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
hwpx/_document/fields.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
- """Form-field domain owner behind the :class:`HwpxDocument` facade (S-084)."""
2
+ """Form-field domain owner behind the :class:`HwpxDocument` facade."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
hwpx/_document/layout.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
- """Formatting and page-layout domain owner behind the HwpxDocument facade (S-084)."""
2
+ """Formatting and page-layout domain owner behind the HwpxDocument facade."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
hwpx/_document/media.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
- """Picture/image domain owner behind the HwpxDocument facade (S-084)."""
2
+ """Picture/image domain owner behind the HwpxDocument facade."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
hwpx/_document/memos.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
- """Memo domain owner behind the :class:`HwpxDocument` facade (S-084)."""
2
+ """Memo domain owner behind the :class:`HwpxDocument` facade."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
@@ -1,5 +1,5 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
- """Save/validate/export domain owner behind the HwpxDocument facade (S-084)."""
2
+ """Save/validate/export domain owner behind the HwpxDocument facade."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
hwpx/_document/shapes.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
- """Shape/control/note domain owner behind the HwpxDocument facade (S-084)."""
2
+ """Shape/control/note domain owner behind the HwpxDocument facade."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
@@ -29,7 +29,13 @@ def add_shape(
29
29
  run_attributes: dict[str, str] | None = None,
30
30
  **extra_attrs: str,
31
31
  ) -> HwpxOxmlInlineObject:
32
- """Insert an inline shape into a new paragraph."""
32
+ """Insert an inline shape into a new paragraph.
33
+
34
+ Low-level escape hatch — writes only the element and attributes it is
35
+ handed, so the result is not openable by Hancom until the required OWPML
36
+ children are supplied. Warns while they are missing. Prefer
37
+ :func:`add_line`, :func:`add_rectangle`, and :func:`add_ellipse`.
38
+ """
33
39
 
34
40
  paragraph = doc.add_paragraph(
35
41
  "",
@@ -62,7 +68,12 @@ def add_control(
62
68
  run_attributes: dict[str, str] | None = None,
63
69
  **extra_attrs: str,
64
70
  ) -> HwpxOxmlInlineObject:
65
- """Insert a control inline object into a new paragraph."""
71
+ """Insert a control inline object into a new paragraph.
72
+
73
+ Low-level escape hatch — an ``<hp:ctrl>`` means nothing without the child
74
+ element that carries the control, and Hancom refuses to open a document
75
+ containing an empty one, so this warns until a child is appended.
76
+ """
66
77
 
67
78
  paragraph = doc.add_paragraph(
68
79
  "",
hwpx/_document/tracked.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
- """Tracked-change authoring owner behind the :class:`HwpxDocument` facade (S-084)."""
2
+ """Tracked-change authoring owner behind the :class:`HwpxDocument` facade."""
3
3
 
4
4
  from __future__ import annotations
5
5
 
@@ -48,6 +48,36 @@ def _paragraph_has_deletable_text(
48
48
  return False
49
49
 
50
50
 
51
+ def _paragraph_has_replaceable_text(
52
+ paragraph: "HwpxOxmlParagraph",
53
+ match: str,
54
+ ) -> bool:
55
+ """Return whether *match* can be wrapped inside one inline text segment.
56
+
57
+ A match that only exists after concatenating text across inline markup is
58
+ present but cannot be rewritten safely. Detect that before allocating
59
+ tracked-change header IDs so a rejected replacement leaves no orphan
60
+ metadata behind.
61
+ """
62
+
63
+ crosses_inline_markup = False
64
+ for run in paragraph.runs:
65
+ model = run.to_model()
66
+ run_text = "".join(span.text for span in model.text_spans)
67
+ if match not in run_text:
68
+ continue
69
+ for span in model.text_spans:
70
+ if match in span.leading_text or any(
71
+ match in markup.trailing_text for markup in span.marks
72
+ ):
73
+ return True
74
+ crosses_inline_markup = True
75
+
76
+ if crosses_inline_markup:
77
+ raise ValueError("match crosses inline markup and cannot be wrapped safely")
78
+ return False
79
+
80
+
51
81
  def add_tracked_insert(
52
82
  doc: "HwpxDocument",
53
83
  paragraph: "HwpxOxmlParagraph",
@@ -111,16 +141,30 @@ def add_tracked_replace(
111
141
  ) -> tuple[int, int]:
112
142
  """Represent a replacement as tracked delete of *old* plus tracked insert of *new*."""
113
143
 
114
- delete_change_id = doc.add_tracked_delete(
115
- paragraph,
116
- match=old,
117
- author=author,
144
+ if old == "":
145
+ raise ValueError("match must be a non-empty string")
146
+ if not _paragraph_has_replaceable_text(paragraph, old):
147
+ raise ValueError("match text was not found in the paragraph")
148
+ sanitized = _sanitize_tracked_text(new)
149
+ if not sanitized:
150
+ raise ValueError("tracked insert text must be non-empty")
151
+
152
+ delete_change_id = doc.add_track_change(
153
+ "Delete",
154
+ author_name=author,
118
155
  date=date,
119
156
  )
120
- insert_change_id = doc.add_tracked_insert(
121
- paragraph,
122
- new,
123
- author=author,
157
+ insert_change_id = doc.add_track_change(
158
+ "Insert",
159
+ author_name=author,
124
160
  date=date,
125
161
  )
162
+ first_mark_id = doc._root.next_track_change_mark_id()
163
+ paragraph._add_tracked_replace(
164
+ old,
165
+ sanitized,
166
+ delete_change_id=delete_change_id,
167
+ insert_change_id=insert_change_id,
168
+ first_mark_id=first_mark_id,
169
+ )
126
170
  return delete_change_id, insert_change_id
hwpx/body_patch.py CHANGED
@@ -451,8 +451,8 @@ def strip_runs_by_color(
451
451
  sections = {n: z.read(n).decode("utf-8") for n in names if re.search(r"section\d+\.xml$", n)}
452
452
 
453
453
  # 계열 매칭(잔존 게이트와 정렬): 대상 색의 _color_family에 드는 모든 charPr.
454
- from .guidance_scan import _color_family
455
- target_families = {_color_family(h) for h in targets}
454
+ from .oxml.color import color_family
455
+ target_families = {color_family(h) for h in targets}
456
456
  ids = set()
457
457
  for cm in re.finditer(r"<(?:[A-Za-z_][\w.-]*:)?charPr\b[^>]*?>", header_xml):
458
458
  tag = cm.group(0)
@@ -461,7 +461,7 @@ def strip_runs_by_color(
461
461
  if not (idm and colm):
462
462
  continue
463
463
  col = colm.group(1).upper()
464
- if col in targets or _color_family(col) in target_families:
464
+ if col in targets or color_family(col) in target_families:
465
465
  ids.add(idm.group(1))
466
466
  transcript: list[dict[str, Any]] = []
467
467
  changed: set[str] = set()