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,305 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Harvest a profile from a real Hancom-saved document (plan §2 Phase E, task 1).
|
|
3
|
+
|
|
4
|
+
Given a genuine ``.hwpx`` it writes, under ``profiles/<id>/``:
|
|
5
|
+
|
|
6
|
+
* ``template.hwpx`` — a **body-stripped skeleton**: the real document with its
|
|
7
|
+
section body reduced to a single ``secPr``-carrying paragraph. ``header.xml``
|
|
8
|
+
(every style/font/borderFill) and the page setup survive untouched, so the
|
|
9
|
+
committed artifact is small and carries no document content (no PII), yet is
|
|
10
|
+
genuinely Hancom-derived — never imagined XML.
|
|
11
|
+
* ``fragments/{title,heading,body,info_table}.xml`` — real ``<hp:p>`` / ``<hp:tbl>``
|
|
12
|
+
elements harvested from the source, with their text **sanitised to placeholders**
|
|
13
|
+
(the style is kept, the content dropped).
|
|
14
|
+
* ``profile.json`` — the manifest the loader reads.
|
|
15
|
+
|
|
16
|
+
This is an authoring-time tool (run once per profile); the committed artifacts are
|
|
17
|
+
what production uses.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
from collections import Counter
|
|
23
|
+
from copy import deepcopy
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from lxml import etree # pyright: ignore[reportAttributeAccessIssue]
|
|
27
|
+
|
|
28
|
+
from hwpx.document import HwpxDocument
|
|
29
|
+
|
|
30
|
+
import hwpx_automation.office.authoring.design._support as S
|
|
31
|
+
|
|
32
|
+
PROFILE_SCHEMA = "hwpx.design.profile.v1"
|
|
33
|
+
|
|
34
|
+
_PLACEHOLDER = {
|
|
35
|
+
"title": "{{title}}",
|
|
36
|
+
"heading": "{{heading}}",
|
|
37
|
+
"body": "{{body}}",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _font_height(doc, ref) -> int:
|
|
42
|
+
style = doc.char_property(ref)
|
|
43
|
+
if style is None:
|
|
44
|
+
return 0
|
|
45
|
+
try:
|
|
46
|
+
return int(style.attributes.get("height") or 0)
|
|
47
|
+
except ValueError:
|
|
48
|
+
return 0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _text_paragraphs(doc):
|
|
52
|
+
out = []
|
|
53
|
+
for index, para in enumerate(doc.sections[0].paragraphs):
|
|
54
|
+
if getattr(para, "tables", []):
|
|
55
|
+
continue
|
|
56
|
+
text = (para.text or "").strip()
|
|
57
|
+
if not text:
|
|
58
|
+
continue
|
|
59
|
+
runs = para.runs
|
|
60
|
+
cref = runs[0].char_pr_id_ref if runs else "0"
|
|
61
|
+
out.append(
|
|
62
|
+
{
|
|
63
|
+
"index": index,
|
|
64
|
+
"para": para.element.get("paraPrIDRef"),
|
|
65
|
+
"char": cref,
|
|
66
|
+
"h": _font_height(doc, cref),
|
|
67
|
+
"len": len(text),
|
|
68
|
+
"element": para.element,
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
return out
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _pick_fragments(doc) -> dict[str, etree._Element]:
|
|
75
|
+
tps = _text_paragraphs(doc)
|
|
76
|
+
if not tps:
|
|
77
|
+
raise ValueError("no text paragraphs to harvest")
|
|
78
|
+
prose = [t for t in tps if t["len"] >= 8] or tps
|
|
79
|
+
|
|
80
|
+
title = max(tps, key=lambda t: t["h"])
|
|
81
|
+
combo = Counter((t["para"], t["char"]) for t in prose)
|
|
82
|
+
body_key = combo.most_common(1)[0][0]
|
|
83
|
+
body = next(t for t in prose if (t["para"], t["char"]) == body_key)
|
|
84
|
+
head_pool = [
|
|
85
|
+
t for t in tps
|
|
86
|
+
if (t["para"], t["char"]) != body_key
|
|
87
|
+
and Counter((x["para"], x["char"]) for x in tps)[(t["para"], t["char"])] >= 2
|
|
88
|
+
and t["h"] >= body["h"]
|
|
89
|
+
]
|
|
90
|
+
heading = max(head_pool, key=lambda t: t["h"], default=title)
|
|
91
|
+
|
|
92
|
+
picks = {"title": title, "heading": heading, "body": body}
|
|
93
|
+
fragments = {role: deepcopy(p["element"]) for role, p in picks.items()}
|
|
94
|
+
for role, frag in fragments.items():
|
|
95
|
+
S.set_paragraph_text(frag, _PLACEHOLDER[role])
|
|
96
|
+
|
|
97
|
+
info = _pick_info_table(doc)
|
|
98
|
+
if info is not None:
|
|
99
|
+
fragments["info_table"] = info
|
|
100
|
+
return fragments
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _references_binary(element: etree._Element) -> bool:
|
|
104
|
+
"""True if *element* embeds an image / binary (so harvesting would dangle)."""
|
|
105
|
+
|
|
106
|
+
for node in element.iter():
|
|
107
|
+
if S.ln(node.tag).lower() in ("pic", "img", "image", "ole", "drawing"):
|
|
108
|
+
return True
|
|
109
|
+
for key in node.attrib:
|
|
110
|
+
if "binaryitemidref" in S.ln(key).lower() or "bindata" in S.ln(key).lower():
|
|
111
|
+
return True
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _is_simple_grid(table_element: etree._Element) -> bool:
|
|
116
|
+
"""True if every cell is 1×1 (no row/col merges) — safe to row-clone."""
|
|
117
|
+
|
|
118
|
+
for tc in S.iter_local(table_element, "tc"):
|
|
119
|
+
span = next((c for c in tc if S.ln(c.tag) == "cellSpan"), None)
|
|
120
|
+
if span is not None:
|
|
121
|
+
if int(span.get("rowSpan", "1")) > 1 or int(span.get("colSpan", "1")) > 1:
|
|
122
|
+
return False
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _pick_info_table(doc) -> etree._Element | None:
|
|
127
|
+
best = None
|
|
128
|
+
for para in doc.sections[0].paragraphs:
|
|
129
|
+
for table in getattr(para, "tables", []):
|
|
130
|
+
rows, cols = table.row_count, table.column_count
|
|
131
|
+
if not (2 <= cols <= 4 and 2 <= rows <= 12):
|
|
132
|
+
continue
|
|
133
|
+
if _references_binary(table.element):
|
|
134
|
+
continue # text-only key-value tables, never image grids
|
|
135
|
+
if not _is_simple_grid(table.element):
|
|
136
|
+
continue # merge-free grids only — the composer row-clones them
|
|
137
|
+
score = rows * cols
|
|
138
|
+
if best is None or score < best[0]:
|
|
139
|
+
best = (score, table)
|
|
140
|
+
if best is None:
|
|
141
|
+
return None
|
|
142
|
+
# Harvest the *paragraph* that wraps the table — a table is not a valid
|
|
143
|
+
# section child on its own; it must ride inside an <hp:p>.
|
|
144
|
+
frag = deepcopy(best[1].paragraph.element)
|
|
145
|
+
# Sanitise every cell to a neutral placeholder; structure is what we keep.
|
|
146
|
+
for tc in S.iter_local(frag, "tc"):
|
|
147
|
+
sublist = _subList(tc)
|
|
148
|
+
for para in S.children_local(sublist if sublist is not None else tc, "p"):
|
|
149
|
+
S.set_paragraph_text(para, "{{cell}}")
|
|
150
|
+
S.strip_lineseg(frag)
|
|
151
|
+
return frag
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _subList(tc: etree._Element):
|
|
155
|
+
for child in tc:
|
|
156
|
+
if S.ln(child.tag) == "subList":
|
|
157
|
+
return child
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _build_skeleton(source: Path, out_path: Path) -> dict:
|
|
162
|
+
doc = HwpxDocument.open(source)
|
|
163
|
+
section = doc.sections[0]
|
|
164
|
+
secpr = S.find_secpr(section.element)
|
|
165
|
+
if secpr is None:
|
|
166
|
+
doc.close()
|
|
167
|
+
raise ValueError("template has no secPr (page setup)")
|
|
168
|
+
secpr = deepcopy(secpr)
|
|
169
|
+
|
|
170
|
+
empty = etree.SubElement(section.element, S.hp(section.element, "p"))
|
|
171
|
+
run = etree.SubElement(empty, S.hp(section.element, "run"), {"charPrIDRef": "0"})
|
|
172
|
+
etree.SubElement(run, S.hp(section.element, "t")).text = ""
|
|
173
|
+
section.element.remove(empty) # detach; replace_section_body re-inserts
|
|
174
|
+
S.replace_section_body(section.element, [empty])
|
|
175
|
+
S.move_secpr_into(empty, secpr)
|
|
176
|
+
section.mark_dirty()
|
|
177
|
+
|
|
178
|
+
stripped = _strip_binaries(doc)
|
|
179
|
+
_scrub_metadata(doc)
|
|
180
|
+
|
|
181
|
+
page = {}
|
|
182
|
+
try:
|
|
183
|
+
ps, pm = section.properties.page_size, section.properties.page_margins
|
|
184
|
+
page = {
|
|
185
|
+
"width": ps.width, "height": ps.height, "orientation": ps.orientation,
|
|
186
|
+
"margins": {"left": pm.left, "right": pm.right, "top": pm.top, "bottom": pm.bottom},
|
|
187
|
+
}
|
|
188
|
+
except Exception: # pragma: no cover - diagnostics only
|
|
189
|
+
page = {}
|
|
190
|
+
|
|
191
|
+
report = doc.save_report(out_path)
|
|
192
|
+
style_count = len(doc.char_properties)
|
|
193
|
+
doc.close()
|
|
194
|
+
return {
|
|
195
|
+
"page": page,
|
|
196
|
+
"char_pr_count": style_count,
|
|
197
|
+
"open_safe": report.open_safety.ok,
|
|
198
|
+
"stripped_parts": stripped,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _strip_binaries(doc) -> list[str]:
|
|
203
|
+
"""Drop embedded images + the source preview (size + PII) from the skeleton.
|
|
204
|
+
|
|
205
|
+
The body is already reduced to one empty paragraph, so nothing references
|
|
206
|
+
these parts; removing them (and their manifest items) keeps the committed
|
|
207
|
+
skeleton small and content-free while leaving every style in header.xml.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
package = doc._package
|
|
211
|
+
removed: list[str] = []
|
|
212
|
+
|
|
213
|
+
for item in list(package._manifest_items()):
|
|
214
|
+
href = item.get("href", "") or ""
|
|
215
|
+
media = item.get("media-type", "") or ""
|
|
216
|
+
base = href.rsplit("/", 1)[-1]
|
|
217
|
+
if (
|
|
218
|
+
"BinData/" in href
|
|
219
|
+
or base.startswith("PrvImage")
|
|
220
|
+
or media.lower().startswith("image/")
|
|
221
|
+
):
|
|
222
|
+
item_id = item.get("id")
|
|
223
|
+
if item_id:
|
|
224
|
+
package.remove_manifest_item(item_id)
|
|
225
|
+
|
|
226
|
+
for name in list(package.part_names()):
|
|
227
|
+
if name.startswith("BinData/") or name.startswith("Preview/PrvImage"):
|
|
228
|
+
try:
|
|
229
|
+
package.delete(name)
|
|
230
|
+
removed.append(name)
|
|
231
|
+
except Exception: # pragma: no cover - defensive
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
if package.has_part("Preview/PrvText.txt"):
|
|
235
|
+
package.set_part("Preview/PrvText.txt", b"") # clear source text (PII)
|
|
236
|
+
return removed
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# Manifest (content.hpf) metadata that carries source PII — author, the person who
|
|
240
|
+
# last saved, the source title, and timestamps. Cleared so generated docs inherit
|
|
241
|
+
# nothing identifying.
|
|
242
|
+
_SCRUB_META_NAMES = {
|
|
243
|
+
"creator", "lastsaveby", "subject", "description", "keyword",
|
|
244
|
+
"date", "CreatedDate", "ModifiedDate",
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _scrub_metadata(doc) -> None:
|
|
249
|
+
"""Blank source-identifying OPF metadata in Contents/content.hpf (PII)."""
|
|
250
|
+
|
|
251
|
+
package = doc._package
|
|
252
|
+
try:
|
|
253
|
+
tree = package.manifest_tree()
|
|
254
|
+
except Exception: # pragma: no cover - defensive
|
|
255
|
+
return
|
|
256
|
+
for element in tree.iter():
|
|
257
|
+
name = S.ln(element.tag)
|
|
258
|
+
if name == "title":
|
|
259
|
+
element.text = ""
|
|
260
|
+
elif name == "meta" and element.get("name") in _SCRUB_META_NAMES:
|
|
261
|
+
element.text = ""
|
|
262
|
+
package.set_part(package.main_content.full_path, tree)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def harvest_profile(source: str | Path, profile_id: str, out_root: str | Path) -> dict:
|
|
266
|
+
"""Harvest *source* into ``<out_root>/<profile_id>/`` and return the manifest."""
|
|
267
|
+
|
|
268
|
+
source = Path(source)
|
|
269
|
+
out_dir = Path(out_root) / profile_id
|
|
270
|
+
(out_dir / "fragments").mkdir(parents=True, exist_ok=True)
|
|
271
|
+
|
|
272
|
+
# Fragments first (reads the full source), then the skeleton (rewrites body).
|
|
273
|
+
doc = HwpxDocument.open(source)
|
|
274
|
+
fragments = _pick_fragments(doc)
|
|
275
|
+
doc.close()
|
|
276
|
+
|
|
277
|
+
roles = []
|
|
278
|
+
for role, frag in fragments.items():
|
|
279
|
+
path = out_dir / "fragments" / f"{role}.xml"
|
|
280
|
+
path.write_bytes(etree.tostring(frag, encoding="UTF-8"))
|
|
281
|
+
roles.append(role)
|
|
282
|
+
|
|
283
|
+
skel = _build_skeleton(source, out_dir / "template.hwpx")
|
|
284
|
+
|
|
285
|
+
manifest = {
|
|
286
|
+
"schemaVersion": PROFILE_SCHEMA,
|
|
287
|
+
"id": profile_id,
|
|
288
|
+
"template": "template.hwpx",
|
|
289
|
+
"fragments": {role: f"fragments/{role}.xml" for role in roles},
|
|
290
|
+
"page": skel["page"],
|
|
291
|
+
"char_pr_count": skel["char_pr_count"],
|
|
292
|
+
"style_coverage_threshold": 0.98,
|
|
293
|
+
"source_basename": source.name,
|
|
294
|
+
}
|
|
295
|
+
(out_dir / "profile.json").write_text(
|
|
296
|
+
json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8"
|
|
297
|
+
)
|
|
298
|
+
return {
|
|
299
|
+
**manifest,
|
|
300
|
+
"skeleton_open_safe": skel["open_safe"],
|
|
301
|
+
"stripped_parts": skel.get("stripped_parts", []),
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
__all__ = ["harvest_profile", "PROFILE_SCHEMA"]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""``DocumentPlan`` — the model-facing description of a new document (Appendix B).
|
|
3
|
+
|
|
4
|
+
The LLM emits a plan (``hwpx.document_plan.v1``), never XML. Phase E lowers it
|
|
5
|
+
natively onto a verified Hancom-saved template + harvested fragments
|
|
6
|
+
(:mod:`hwpx.design.composer`).
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any, Literal
|
|
12
|
+
|
|
13
|
+
DOCUMENT_PLAN_SCHEMA = "hwpx.document_plan.v1"
|
|
14
|
+
|
|
15
|
+
BlockType = Literal["paragraph", "table"]
|
|
16
|
+
# Paragraph roles map to harvested fragments; table → the info-table fragment.
|
|
17
|
+
BlockRole = Literal["title", "heading", "subheading", "body", "info"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(slots=True)
|
|
21
|
+
class Block:
|
|
22
|
+
"""One block of a :class:`DocumentPlan`."""
|
|
23
|
+
|
|
24
|
+
type: BlockType
|
|
25
|
+
role: BlockRole = "body"
|
|
26
|
+
text: str = ""
|
|
27
|
+
columns: list[str] = field(default_factory=list)
|
|
28
|
+
rows: list[list[str]] = field(default_factory=list)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_dict(cls, data: dict[str, Any]) -> "Block":
|
|
32
|
+
return cls(
|
|
33
|
+
type=data.get("type", "paragraph"),
|
|
34
|
+
role=data.get("role", "body"),
|
|
35
|
+
text=str(data.get("text", "")),
|
|
36
|
+
columns=[str(c) for c in data.get("columns", [])],
|
|
37
|
+
rows=[[str(c) for c in row] for row in data.get("rows", [])],
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(slots=True)
|
|
42
|
+
class DocumentPlan:
|
|
43
|
+
"""A new-document plan (Appendix B ``hwpx.document_plan.v1``)."""
|
|
44
|
+
|
|
45
|
+
profile: str
|
|
46
|
+
title: str = ""
|
|
47
|
+
blocks: list[Block] = field(default_factory=list)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_dict(cls, data: dict[str, Any]) -> "DocumentPlan":
|
|
51
|
+
if not data.get("profile"):
|
|
52
|
+
raise ValueError("DocumentPlan requires a 'profile'")
|
|
53
|
+
return cls(
|
|
54
|
+
profile=str(data["profile"]),
|
|
55
|
+
title=str(data.get("title", "")),
|
|
56
|
+
blocks=[Block.from_dict(b) for b in data.get("blocks", [])],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def iter_blocks(self) -> list[Block]:
|
|
60
|
+
"""Blocks with the title promoted to a leading title block."""
|
|
61
|
+
|
|
62
|
+
blocks: list[Block] = []
|
|
63
|
+
if self.title:
|
|
64
|
+
blocks.append(Block(type="paragraph", role="title", text=self.title))
|
|
65
|
+
blocks.extend(self.blocks)
|
|
66
|
+
return blocks
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__all__ = ["DocumentPlan", "Block", "BlockType", "BlockRole", "DOCUMENT_PLAN_SCHEMA"]
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Profile loader + registry (plan §2 Phase E, task 1).
|
|
3
|
+
|
|
4
|
+
A :class:`Profile` bundles a verified Hancom-saved skeleton ``template.hwpx`` with
|
|
5
|
+
the harvested ``<hp:p>``/``<hp:tbl>`` fragments keyed by role. The default registry
|
|
6
|
+
loads the profiles committed under ``hwpx/design/profiles/``.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from functools import lru_cache
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from lxml import etree # pyright: ignore[reportAttributeAccessIssue]
|
|
17
|
+
|
|
18
|
+
PROFILES_DIRNAME = "profiles"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(slots=True)
|
|
22
|
+
class Profile:
|
|
23
|
+
"""A verified template skeleton + its harvested fragments."""
|
|
24
|
+
|
|
25
|
+
id: str
|
|
26
|
+
root: Path
|
|
27
|
+
manifest: dict[str, Any]
|
|
28
|
+
template_bytes: bytes
|
|
29
|
+
_fragments: dict[str, bytes] = field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def style_coverage_threshold(self) -> float:
|
|
33
|
+
return float(self.manifest.get("style_coverage_threshold", 0.98))
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def roles(self) -> list[str]:
|
|
37
|
+
return list(self._fragments)
|
|
38
|
+
|
|
39
|
+
def has_role(self, role: str) -> bool:
|
|
40
|
+
return role in self._fragments
|
|
41
|
+
|
|
42
|
+
def fragment(self, role: str) -> etree._Element:
|
|
43
|
+
"""Return a fresh (deep, detached) clone of the *role* fragment element."""
|
|
44
|
+
|
|
45
|
+
if role not in self._fragments:
|
|
46
|
+
raise KeyError(f"profile {self.id!r} has no fragment for role {role!r}")
|
|
47
|
+
return etree.fromstring(self._fragments[role])
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def load(cls, root: str | Path) -> "Profile":
|
|
51
|
+
root = Path(root)
|
|
52
|
+
manifest = json.loads((root / "profile.json").read_text(encoding="utf-8"))
|
|
53
|
+
template_bytes = (root / manifest["template"]).read_bytes()
|
|
54
|
+
fragments: dict[str, bytes] = {}
|
|
55
|
+
for role, rel in manifest.get("fragments", {}).items():
|
|
56
|
+
fragments[role] = (root / rel).read_bytes()
|
|
57
|
+
return cls(
|
|
58
|
+
id=manifest["id"],
|
|
59
|
+
root=root,
|
|
60
|
+
manifest=manifest,
|
|
61
|
+
template_bytes=template_bytes,
|
|
62
|
+
_fragments=fragments,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@lru_cache(maxsize=1)
|
|
67
|
+
def _registry_root() -> Path:
|
|
68
|
+
return Path(__file__).resolve().parent / PROFILES_DIRNAME
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def available_profiles() -> list[str]:
|
|
72
|
+
root = _registry_root()
|
|
73
|
+
return sorted(p.name for p in root.iterdir() if (p / "profile.json").exists())
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@lru_cache(maxsize=None)
|
|
77
|
+
def load_profile(profile_id: str) -> Profile:
|
|
78
|
+
"""Load a committed profile by id (cached)."""
|
|
79
|
+
|
|
80
|
+
root = _registry_root() / profile_id
|
|
81
|
+
if not (root / "profile.json").exists():
|
|
82
|
+
raise KeyError(
|
|
83
|
+
f"unknown profile {profile_id!r}; available: {available_profiles()}"
|
|
84
|
+
)
|
|
85
|
+
return Profile.load(root)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
__all__ = ["Profile", "load_profile", "available_profiles", "PROFILES_DIRNAME"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<hp:p xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" id="2147483648" paraPrIDRef="107" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="142"><hp:t>{{body}}</hp:t></hp:run><hp:run charPrIDRef="32"><hp:t></hp:t></hp:run><hp:run charPrIDRef="143"><hp:t></hp:t></hp:run><hp:run charPrIDRef="160"><hp:t></hp:t></hp:run><hp:run charPrIDRef="174"><hp:t></hp:t></hp:run><hp:run charPrIDRef="163"><hp:t></hp:t></hp:run><hp:run charPrIDRef="145"><hp:t></hp:t></hp:run><hp:run charPrIDRef="146"><hp:t></hp:t></hp:run></hp:p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<hp:p xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" id="0" paraPrIDRef="1" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="141"><hp:t>{{heading}}</hp:t></hp:run></hp:p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<hp:p xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" id="0" paraPrIDRef="93" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="161"><hp:t> </hp:t><hp:tbl id="2052787869" zOrder="32" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="1" rowCnt="3" colCnt="2" cellSpacing="0" borderFillIDRef="4" noAdjust="0"><hp:sz width="46946" widthRelTo="ABSOLUTE" height="19347" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="0" right="0" top="0" bottom="0"/><hp:inMargin left="510" right="510" top="141" bottom="141"/><hp:tr><hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="0" borderFillIDRef="238"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0"><hp:p id="2147483648" paraPrIDRef="66" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="79"><hp:t>{{cell}}</hp:t></hp:run></hp:p></hp:subList><hp:cellAddr colAddr="0" rowAddr="0"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="5253" height="1639"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/></hp:tc><hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="0" borderFillIDRef="238"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0"><hp:p id="2147483648" paraPrIDRef="6" styleIDRef="21" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="79"><hp:t>{{cell}}</hp:t></hp:run></hp:p></hp:subList><hp:cellAddr colAddr="1" rowAddr="0"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="41693" height="1639"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/></hp:tc></hp:tr><hp:tr><hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="0" borderFillIDRef="258"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0"><hp:p id="2147483648" paraPrIDRef="66" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="69"><hp:t>{{cell}}</hp:t></hp:run></hp:p></hp:subList><hp:cellAddr colAddr="0" rowAddr="1"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="5253" height="8854"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/></hp:tc><hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="0" borderFillIDRef="258"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0"><hp:p id="2147483648" paraPrIDRef="103" styleIDRef="21" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="80"><hp:t>{{cell}}</hp:t></hp:run></hp:p><hp:p id="0" paraPrIDRef="103" styleIDRef="21" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="80"><hp:t>{{cell}}</hp:t></hp:run></hp:p><hp:p id="0" paraPrIDRef="103" styleIDRef="21" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="80"><hp:t>{{cell}}</hp:t></hp:run></hp:p><hp:p id="0" paraPrIDRef="103" styleIDRef="21" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="80"><hp:t>{{cell}}</hp:t></hp:run></hp:p><hp:p id="0" paraPrIDRef="103" styleIDRef="21" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="80"><hp:t>{{cell}}</hp:t></hp:run></hp:p></hp:subList><hp:cellAddr colAddr="1" rowAddr="1"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="41693" height="8854"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/></hp:tc></hp:tr><hp:tr><hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="0" borderFillIDRef="259"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0"><hp:p id="0" paraPrIDRef="66" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="69"><hp:t>{{cell}}</hp:t></hp:run></hp:p></hp:subList><hp:cellAddr colAddr="0" rowAddr="2"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="5253" height="8854"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/></hp:tc><hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="0" borderFillIDRef="259"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0"><hp:p id="0" paraPrIDRef="103" styleIDRef="21" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="80"><hp:t>{{cell}}</hp:t></hp:run></hp:p><hp:p id="0" paraPrIDRef="103" styleIDRef="21" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="80"><hp:t>{{cell}}</hp:t></hp:run></hp:p><hp:p id="0" paraPrIDRef="103" styleIDRef="21" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="80"><hp:t>{{cell}}</hp:t></hp:run></hp:p><hp:p id="0" paraPrIDRef="103" styleIDRef="21" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="80"><hp:t>{{cell}}</hp:t></hp:run></hp:p></hp:subList><hp:cellAddr colAddr="1" rowAddr="2"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="41693" height="8854"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/></hp:tc></hp:tr></hp:tbl><hp:t/></hp:run></hp:p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<hp:p xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" id="2147483648" paraPrIDRef="100" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="141"><hp:t>{{title}}</hp:t></hp:run></hp:p>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "hwpx.design.profile.v1",
|
|
3
|
+
"id": "application_form",
|
|
4
|
+
"template": "template.hwpx",
|
|
5
|
+
"fragments": {
|
|
6
|
+
"title": "fragments/title.xml",
|
|
7
|
+
"heading": "fragments/heading.xml",
|
|
8
|
+
"body": "fragments/body.xml",
|
|
9
|
+
"info_table": "fragments/info_table.xml"
|
|
10
|
+
},
|
|
11
|
+
"page": {
|
|
12
|
+
"width": 59528,
|
|
13
|
+
"height": 84188,
|
|
14
|
+
"orientation": "WIDELY",
|
|
15
|
+
"margins": {
|
|
16
|
+
"left": 5669,
|
|
17
|
+
"right": 5669,
|
|
18
|
+
"top": 4251,
|
|
19
|
+
"bottom": 4251
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"char_pr_count": 249,
|
|
23
|
+
"style_coverage_threshold": 0.98,
|
|
24
|
+
"source_basename": "2026%EB%85%84+AI+%EC%A4%91%EC%A0%90%ED%95%99%EA%B5%90+%EC%8B%A0%EC%B2%AD%EC%84%9C+%EB%B0%8F+%EC%9A%B4%EC%98%81%EA%B3%84%ED%9A%8D%EC%84%9C%28%EC%9C%A0%ED%98%951_%EB%A7%A4%EC%9B%90%EA%B3%A0%29_%EC%97%90%EB%93%80%EB%84%B7+%ED%83%91%EC%9E%AC%EC%9A%A9.hwpx"
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<hp:p xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" id="2147483648" paraPrIDRef="1" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="26"><hp:t>{{body}}</hp:t></hp:run></hp:p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<hp:p xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" id="2147483648" paraPrIDRef="1" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="25"><hp:t>{{heading}}</hp:t></hp:run></hp:p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<hp:p xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" id="2147483648" paraPrIDRef="25" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="17"><hp:t>{{title}}</hp:t></hp:run></hp:p>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "hwpx.design.profile.v1",
|
|
3
|
+
"id": "home_notice",
|
|
4
|
+
"template": "template.hwpx",
|
|
5
|
+
"fragments": {
|
|
6
|
+
"title": "fragments/title.xml",
|
|
7
|
+
"heading": "fragments/heading.xml",
|
|
8
|
+
"body": "fragments/body.xml"
|
|
9
|
+
},
|
|
10
|
+
"page": {
|
|
11
|
+
"width": 59528,
|
|
12
|
+
"height": 84186,
|
|
13
|
+
"orientation": "WIDELY",
|
|
14
|
+
"margins": {
|
|
15
|
+
"left": 4251,
|
|
16
|
+
"right": 4251,
|
|
17
|
+
"top": 1417,
|
|
18
|
+
"bottom": 0
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"char_pr_count": 54,
|
|
22
|
+
"style_coverage_threshold": 0.98,
|
|
23
|
+
"source_basename": "ganghwa_records.hwpx"
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<hp:p xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" id="2147483648" paraPrIDRef="33" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="18"><hp:t>{{body}}</hp:t></hp:run></hp:p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<hp:p xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" id="0" paraPrIDRef="25" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="13"><hp:t>{{heading}}</hp:t></hp:run></hp:p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<hp:p xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" id="0" paraPrIDRef="28" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="15"><hp:t> </hp:t></hp:run><hp:run charPrIDRef="14"><hp:tbl id="1465524190" zOrder="10" numberingType="TABLE" textWrap="TOP_AND_BOTTOM" textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL" repeatHeader="1" rowCnt="2" colCnt="3" cellSpacing="0" borderFillIDRef="12" noAdjust="0"><hp:sz width="46490" widthRelTo="ABSOLUTE" height="7274" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="141" right="141" top="141" bottom="141"/><hp:inMargin left="510" right="510" top="141" bottom="141"/><hp:tr><hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="0" borderFillIDRef="15"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0"><hp:p id="0" paraPrIDRef="22" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="42"><hp:t>{{cell}}</hp:t></hp:run></hp:p></hp:subList><hp:cellAddr colAddr="0" rowAddr="0"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="13610" height="2414"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/></hp:tc><hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="0" borderFillIDRef="15"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0"><hp:p id="0" paraPrIDRef="22" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="42"><hp:t>{{cell}}</hp:t></hp:run></hp:p></hp:subList><hp:cellAddr colAddr="1" rowAddr="0"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="13610" height="2414"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/></hp:tc><hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="0" borderFillIDRef="15"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0"><hp:p id="0" paraPrIDRef="22" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="52"><hp:t>{{cell}}</hp:t></hp:run></hp:p></hp:subList><hp:cellAddr colAddr="2" rowAddr="0"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="19270" height="2414"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/></hp:tc></hp:tr><hp:tr><hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="0" borderFillIDRef="12"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0"><hp:p id="0" paraPrIDRef="58" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="42"><hp:t>{{cell}}</hp:t></hp:run></hp:p><hp:p id="0" paraPrIDRef="58" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="42"><hp:t>{{cell}}</hp:t></hp:run></hp:p></hp:subList><hp:cellAddr colAddr="0" rowAddr="1"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="13610" height="4860"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/></hp:tc><hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="0" borderFillIDRef="12"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0"><hp:p id="0" paraPrIDRef="58" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="42"><hp:t>{{cell}}</hp:t></hp:run></hp:p><hp:p id="0" paraPrIDRef="58" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="42"><hp:t>{{cell}}</hp:t></hp:run></hp:p></hp:subList><hp:cellAddr colAddr="1" rowAddr="1"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="13610" height="4860"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/></hp:tc><hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="0" borderFillIDRef="12"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0"><hp:p id="0" paraPrIDRef="58" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="52"><hp:t>{{cell}}</hp:t></hp:run></hp:p><hp:p id="0" paraPrIDRef="58" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="52"><hp:t>{{cell}}</hp:t></hp:run></hp:p></hp:subList><hp:cellAddr colAddr="2" rowAddr="1"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="19270" height="4860"/><hp:cellMargin left="510" right="510" top="141" bottom="141"/></hp:tc></hp:tr></hp:tbl><hp:t/></hp:run></hp:p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<hp:p xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" id="0" paraPrIDRef="25" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="13"><hp:t>{{title}}</hp:t></hp:run></hp:p>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "hwpx.design.profile.v1",
|
|
3
|
+
"id": "official_notice",
|
|
4
|
+
"template": "template.hwpx",
|
|
5
|
+
"fragments": {
|
|
6
|
+
"title": "fragments/title.xml",
|
|
7
|
+
"heading": "fragments/heading.xml",
|
|
8
|
+
"body": "fragments/body.xml",
|
|
9
|
+
"info_table": "fragments/info_table.xml"
|
|
10
|
+
},
|
|
11
|
+
"page": {
|
|
12
|
+
"width": 59528,
|
|
13
|
+
"height": 84188,
|
|
14
|
+
"orientation": "WIDELY",
|
|
15
|
+
"margins": {
|
|
16
|
+
"left": 5669,
|
|
17
|
+
"right": 5669,
|
|
18
|
+
"top": 4251,
|
|
19
|
+
"bottom": 2834
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"char_pr_count": 65,
|
|
23
|
+
"style_coverage_threshold": 0.98,
|
|
24
|
+
"source_basename": "synthetic_official_notice_source.hwpx"
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<hp:p xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" id="0" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="52"><hp:t>{{body}}</hp:t></hp:run></hp:p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<hp:p xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph" id="0" paraPrIDRef="22" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0"><hp:run charPrIDRef="26"><hp:t>{{heading}}</hp:t></hp:run><hp:run charPrIDRef="26"/></hp:p>
|