python-hwpx 2.10.2__tar.gz → 2.11.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_hwpx-2.10.2/src/python_hwpx.egg-info → python_hwpx-2.11.0}/PKG-INFO +1 -1
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/pyproject.toml +1 -1
- python_hwpx-2.11.0/src/hwpx/__init__.py +177 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/authoring.py +103 -4
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/builder/__init__.py +2 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/builder/core.py +67 -4
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/builder/report.py +17 -1
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/document.py +1082 -26
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/opc/package.py +343 -10
- python_hwpx-2.11.0/src/hwpx/opc/security.py +134 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/opc/xml_utils.py +15 -2
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/oxml/__init__.py +7 -20
- python_hwpx-2.10.2/src/hwpx/oxml/document.py → python_hwpx-2.11.0/src/hwpx/oxml/_document_impl.py +402 -23
- python_hwpx-2.11.0/src/hwpx/oxml/document.py +40 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/oxml/header_part.py +1 -1
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/oxml/memo.py +2 -2
- python_hwpx-2.11.0/src/hwpx/oxml/numbering.py +8 -0
- python_hwpx-2.11.0/src/hwpx/oxml/objects.py +8 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/oxml/paragraph.py +1 -1
- python_hwpx-2.11.0/src/hwpx/oxml/run.py +8 -0
- python_hwpx-2.11.0/src/hwpx/oxml/section.py +11 -0
- python_hwpx-2.11.0/src/hwpx/oxml/simple_parts.py +8 -0
- python_hwpx-2.11.0/src/hwpx/oxml/table.py +11 -0
- python_hwpx-2.11.0/src/hwpx/patch.py +653 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/template_formfit.py +48 -17
- python_hwpx-2.11.0/src/hwpx/tools/__init__.py +167 -0
- python_hwpx-2.11.0/src/hwpx/tools/advanced_generators.py +154 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/archive_cli.py +18 -6
- python_hwpx-2.11.0/src/hwpx/tools/doc_diff.py +349 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/exporter.py +4 -1
- python_hwpx-2.11.0/src/hwpx/tools/fuzz/__init__.py +36 -0
- python_hwpx-2.11.0/src/hwpx/tools/fuzz/__main__.py +80 -0
- python_hwpx-2.11.0/src/hwpx/tools/fuzz/catalog.py +299 -0
- python_hwpx-2.11.0/src/hwpx/tools/fuzz/generator.py +195 -0
- python_hwpx-2.11.0/src/hwpx/tools/fuzz/minimize.py +33 -0
- python_hwpx-2.11.0/src/hwpx/tools/fuzz/runner.py +503 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/id_integrity.py +156 -4
- python_hwpx-2.11.0/src/hwpx/tools/layout_preview.py +573 -0
- python_hwpx-2.11.0/src/hwpx/tools/mail_merge.py +282 -0
- python_hwpx-2.11.0/src/hwpx/tools/official_lint.py +373 -0
- python_hwpx-2.11.0/src/hwpx/tools/package_validator.py +810 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/repair.py +91 -8
- python_hwpx-2.11.0/src/hwpx/tools/style_profile.py +437 -0
- python_hwpx-2.11.0/src/hwpx/tools/table_compute.py +477 -0
- python_hwpx-2.11.0/src/hwpx/tools/template_analyzer.py +656 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/text_extractor.py +10 -5
- {python_hwpx-2.10.2 → python_hwpx-2.11.0/src/python_hwpx.egg-info}/PKG-INFO +1 -1
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/python_hwpx.egg-info/SOURCES.txt +34 -0
- python_hwpx-2.11.0/tests/test_advanced_generators.py +117 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_builder_core.py +5 -0
- python_hwpx-2.11.0/tests/test_doc_diff.py +102 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_document_formatting.py +59 -0
- python_hwpx-2.11.0/tests/test_document_save_api.py +333 -0
- python_hwpx-2.11.0/tests/test_existing_document_format_editing.py +167 -0
- python_hwpx-2.11.0/tests/test_form_fields.py +100 -0
- python_hwpx-2.11.0/tests/test_fuzz_loop.py +118 -0
- python_hwpx-2.11.0/tests/test_fuzz_regressions.py +45 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_gap_closure_tools.py +165 -4
- python_hwpx-2.11.0/tests/test_image_object_workflow.py +124 -0
- python_hwpx-2.11.0/tests/test_kordoc_absorption.py +225 -0
- python_hwpx-2.11.0/tests/test_layout_preview.py +59 -0
- python_hwpx-2.11.0/tests/test_mail_merge_table_compute.py +143 -0
- python_hwpx-2.11.0/tests/test_official_document_style.py +134 -0
- python_hwpx-2.11.0/tests/test_opc_package.py +591 -0
- python_hwpx-2.11.0/tests/test_open_safety_corpus.py +88 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_recover_broken_zip.py +90 -1
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_repair_repack.py +123 -1
- python_hwpx-2.11.0/tests/test_style_profile.py +114 -0
- python_hwpx-2.11.0/tests/test_template_analyzer_enrichment.py +110 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_template_formfit.py +61 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_version_metadata.py +9 -0
- python_hwpx-2.10.2/src/hwpx/__init__.py +0 -79
- python_hwpx-2.10.2/src/hwpx/oxml/section.py +0 -11
- python_hwpx-2.10.2/src/hwpx/oxml/table.py +0 -11
- python_hwpx-2.10.2/src/hwpx/tools/__init__.py +0 -85
- python_hwpx-2.10.2/src/hwpx/tools/package_validator.py +0 -414
- python_hwpx-2.10.2/src/hwpx/tools/template_analyzer.py +0 -235
- python_hwpx-2.10.2/tests/test_document_save_api.py +0 -55
- python_hwpx-2.10.2/tests/test_opc_package.py +0 -126
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/LICENSE +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/NOTICE +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/README.md +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/setup.cfg +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/data/Skeleton.hwpx +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/form_fill.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/opc/relationships.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/oxml/body.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/oxml/common.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/oxml/header.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/oxml/namespaces.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/oxml/parser.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/oxml/schema.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/oxml/utils.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/package.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/presets/__init__.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/presets/proposal.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/py.typed +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/templates.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/_schemas/header.xsd +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/_schemas/section.xsd +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/generic_inventory.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/markdown_export.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/object_finder.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/page_guard.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/recover.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/report_parser.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/report_utils.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/roundtrip_diff.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/table_cleanup.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/table_navigation.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/text_extract_cli.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/hwpx/tools/validator.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/python_hwpx.egg-info/entry_points.txt +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/python_hwpx.egg-info/requires.txt +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/src/python_hwpx.egg-info/top_level.txt +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_builder_plan_v2.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_builder_vertical_slice.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_coverage_promotion.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_coverage_targets.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_deviations_registry.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_document_context_manager.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_document_plan.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_document_plan_computed_fields.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_form_fill_split_run.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_government_report_preset.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_government_table_profile.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_hp_tab_support.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_hwpxlib_corpus_read.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_id_generator_range.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_id_integrity.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_inline_models.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_integration_hwpx_compatibility.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_integration_roundtrip.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_markdown_export.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_memo_and_style_editing.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_namespace_handling.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_new_features.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_oxml_parsing.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_packaging_license_metadata.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_packaging_py_typed.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_paragraph_section_management.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_proposal_preset.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_report_parser.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_report_utils.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_repr_snapshots.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_roundtrip_fidelity.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_section_headers.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_skeleton_template_ids.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_split_merged_cell.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_table_cleanup.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_table_navigation.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_tables_default_border.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_text_extractor_annotations.py +0 -0
- {python_hwpx-2.10.2 → python_hwpx-2.11.0}/tests/test_validation_severity.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-hwpx"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.11.0"
|
|
8
8
|
description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""High-level utilities for working with HWPX documents."""
|
|
3
|
+
|
|
4
|
+
from importlib.metadata import PackageNotFoundError, version as _metadata_version
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _resolve_version() -> str:
|
|
8
|
+
"""패키지 메타데이터에서 현재 배포 버전을 조회합니다."""
|
|
9
|
+
try:
|
|
10
|
+
return _metadata_version("python-hwpx")
|
|
11
|
+
except PackageNotFoundError:
|
|
12
|
+
return "0+unknown"
|
|
13
|
+
|
|
14
|
+
def __getattr__(name: str) -> object:
|
|
15
|
+
"""Resolve dynamic module attributes."""
|
|
16
|
+
|
|
17
|
+
if name == "__version__":
|
|
18
|
+
return _resolve_version()
|
|
19
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
20
|
+
|
|
21
|
+
from .tools.text_extractor import (
|
|
22
|
+
DEFAULT_NAMESPACES,
|
|
23
|
+
ParagraphInfo,
|
|
24
|
+
SectionInfo,
|
|
25
|
+
TextExtractor,
|
|
26
|
+
)
|
|
27
|
+
from .tools.object_finder import FoundElement, ObjectFinder
|
|
28
|
+
from .tools.advanced_generators import (
|
|
29
|
+
build_image_grid,
|
|
30
|
+
build_meeting_nameplates,
|
|
31
|
+
build_organization_chart,
|
|
32
|
+
)
|
|
33
|
+
from .tools.doc_diff import (
|
|
34
|
+
DOC_DIFF_REPORT_VERSION,
|
|
35
|
+
REFERENCE_CONSISTENCY_REPORT_VERSION,
|
|
36
|
+
build_comparison_table_plan,
|
|
37
|
+
diff_paragraphs,
|
|
38
|
+
doc_diff,
|
|
39
|
+
inspect_reference_consistency,
|
|
40
|
+
)
|
|
41
|
+
from .tools.mail_merge import (
|
|
42
|
+
MAIL_MERGE_REPORT_VERSION,
|
|
43
|
+
inspect_mail_merge_placeholders,
|
|
44
|
+
load_mail_merge_rows,
|
|
45
|
+
mail_merge,
|
|
46
|
+
)
|
|
47
|
+
from .tools.table_compute import (
|
|
48
|
+
TABLE_COMPUTE_REPORT_VERSION,
|
|
49
|
+
table_compute,
|
|
50
|
+
)
|
|
51
|
+
from .tools.official_lint import (
|
|
52
|
+
OFFICIAL_DOCUMENT_STYLE_REPORT_VERSION,
|
|
53
|
+
inspect_official_document_style,
|
|
54
|
+
)
|
|
55
|
+
from .tools.package_validator import (
|
|
56
|
+
EditorOpenSafetyReport,
|
|
57
|
+
PackageValidationReport,
|
|
58
|
+
validate_editor_open_safety,
|
|
59
|
+
validate_package,
|
|
60
|
+
)
|
|
61
|
+
from .tools.style_profile import (
|
|
62
|
+
STYLE_PROFILE_COMPARISON_SCHEMA_VERSION,
|
|
63
|
+
STYLE_PROFILE_SCHEMA_VERSION,
|
|
64
|
+
TEMPLATE_REGISTRY_SCHEMA_VERSION,
|
|
65
|
+
apply_style_profile_to_plan,
|
|
66
|
+
compare_style_profiles,
|
|
67
|
+
describe_template,
|
|
68
|
+
extract_style_profile,
|
|
69
|
+
list_templates,
|
|
70
|
+
placeholder_fill_report,
|
|
71
|
+
register_template,
|
|
72
|
+
)
|
|
73
|
+
from .tools.layout_preview import (
|
|
74
|
+
LayoutPreview,
|
|
75
|
+
PreviewPage,
|
|
76
|
+
render_layout_preview,
|
|
77
|
+
)
|
|
78
|
+
from .patch import (
|
|
79
|
+
BytePreservingPatchResult,
|
|
80
|
+
ParagraphTextPatch,
|
|
81
|
+
PatchApplied,
|
|
82
|
+
PatchSkipped,
|
|
83
|
+
paragraph_patch,
|
|
84
|
+
)
|
|
85
|
+
from .document import HwpxDocument
|
|
86
|
+
from .package import HwpxPackage
|
|
87
|
+
from .authoring import (
|
|
88
|
+
AUTHORING_REPORT_VERSION,
|
|
89
|
+
DEFAULT_STYLE_PRESET,
|
|
90
|
+
DOCUMENT_PLAN_SCHEMA_VERSION,
|
|
91
|
+
DocumentBlock,
|
|
92
|
+
DocumentPlan,
|
|
93
|
+
DocumentStylePreset,
|
|
94
|
+
PlanValidationIssue,
|
|
95
|
+
PlanValidationReport,
|
|
96
|
+
create_document_from_plan,
|
|
97
|
+
inspect_document_authoring_quality,
|
|
98
|
+
inspect_operating_plan_quality,
|
|
99
|
+
normalize_document_plan,
|
|
100
|
+
validate_document_plan,
|
|
101
|
+
)
|
|
102
|
+
from .builder import approval_box
|
|
103
|
+
from .template_formfit import (
|
|
104
|
+
TEMPLATE_FORMFIT_BASELINE_SCHEMA_VERSION,
|
|
105
|
+
TEMPLATE_FORMFIT_PLAN_SCHEMA_VERSION,
|
|
106
|
+
analyze_template_formfit,
|
|
107
|
+
apply_template_formfit,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
__all__ = [
|
|
111
|
+
"__version__",
|
|
112
|
+
"AUTHORING_REPORT_VERSION",
|
|
113
|
+
"DEFAULT_NAMESPACES",
|
|
114
|
+
"DEFAULT_STYLE_PRESET",
|
|
115
|
+
"DOCUMENT_PLAN_SCHEMA_VERSION",
|
|
116
|
+
"DocumentBlock",
|
|
117
|
+
"DocumentPlan",
|
|
118
|
+
"DocumentStylePreset",
|
|
119
|
+
"EditorOpenSafetyReport",
|
|
120
|
+
"ParagraphInfo",
|
|
121
|
+
"PackageValidationReport",
|
|
122
|
+
"BytePreservingPatchResult",
|
|
123
|
+
"PlanValidationReport",
|
|
124
|
+
"ParagraphTextPatch",
|
|
125
|
+
"PatchApplied",
|
|
126
|
+
"PatchSkipped",
|
|
127
|
+
"LayoutPreview",
|
|
128
|
+
"PreviewPage",
|
|
129
|
+
"SectionInfo",
|
|
130
|
+
"TEMPLATE_FORMFIT_BASELINE_SCHEMA_VERSION",
|
|
131
|
+
"TEMPLATE_FORMFIT_PLAN_SCHEMA_VERSION",
|
|
132
|
+
"TextExtractor",
|
|
133
|
+
"FoundElement",
|
|
134
|
+
"ObjectFinder",
|
|
135
|
+
"OFFICIAL_DOCUMENT_STYLE_REPORT_VERSION",
|
|
136
|
+
"DOC_DIFF_REPORT_VERSION",
|
|
137
|
+
"REFERENCE_CONSISTENCY_REPORT_VERSION",
|
|
138
|
+
"MAIL_MERGE_REPORT_VERSION",
|
|
139
|
+
"STYLE_PROFILE_COMPARISON_SCHEMA_VERSION",
|
|
140
|
+
"STYLE_PROFILE_SCHEMA_VERSION",
|
|
141
|
+
"TEMPLATE_REGISTRY_SCHEMA_VERSION",
|
|
142
|
+
"TABLE_COMPUTE_REPORT_VERSION",
|
|
143
|
+
"apply_style_profile_to_plan",
|
|
144
|
+
"build_comparison_table_plan",
|
|
145
|
+
"build_image_grid",
|
|
146
|
+
"build_meeting_nameplates",
|
|
147
|
+
"build_organization_chart",
|
|
148
|
+
"diff_paragraphs",
|
|
149
|
+
"doc_diff",
|
|
150
|
+
"compare_style_profiles",
|
|
151
|
+
"PlanValidationIssue",
|
|
152
|
+
"HwpxDocument",
|
|
153
|
+
"HwpxPackage",
|
|
154
|
+
"create_document_from_plan",
|
|
155
|
+
"analyze_template_formfit",
|
|
156
|
+
"apply_template_formfit",
|
|
157
|
+
"approval_box",
|
|
158
|
+
"describe_template",
|
|
159
|
+
"extract_style_profile",
|
|
160
|
+
"inspect_document_authoring_quality",
|
|
161
|
+
"inspect_mail_merge_placeholders",
|
|
162
|
+
"inspect_official_document_style",
|
|
163
|
+
"inspect_reference_consistency",
|
|
164
|
+
"inspect_operating_plan_quality",
|
|
165
|
+
"list_templates",
|
|
166
|
+
"load_mail_merge_rows",
|
|
167
|
+
"mail_merge",
|
|
168
|
+
"normalize_document_plan",
|
|
169
|
+
"placeholder_fill_report",
|
|
170
|
+
"validate_document_plan",
|
|
171
|
+
"validate_editor_open_safety",
|
|
172
|
+
"validate_package",
|
|
173
|
+
"paragraph_patch",
|
|
174
|
+
"render_layout_preview",
|
|
175
|
+
"register_template",
|
|
176
|
+
"table_compute",
|
|
177
|
+
]
|
|
@@ -26,11 +26,13 @@ from .builder import (
|
|
|
26
26
|
Run as BuilderRun,
|
|
27
27
|
Section as BuilderSection,
|
|
28
28
|
Table as BuilderTable,
|
|
29
|
+
approval_box as BuilderApprovalBox,
|
|
29
30
|
)
|
|
30
31
|
from .builder.core import Toc as BuilderToc
|
|
31
32
|
from .document import HwpxDocument
|
|
32
33
|
from .tools.package_validator import validate_package
|
|
33
34
|
from .tools.table_cleanup import normalize_cell_text
|
|
35
|
+
from .tools.advanced_generators import build_image_grid
|
|
34
36
|
from .tools.report_utils import (
|
|
35
37
|
calculate_age,
|
|
36
38
|
calculate_ratios,
|
|
@@ -603,9 +605,13 @@ def _validate_v2_block(raw_block: Any, *, path: str) -> list[PlanValidationIssue
|
|
|
603
605
|
"numberedList",
|
|
604
606
|
"table",
|
|
605
607
|
"image",
|
|
608
|
+
"image_grid",
|
|
609
|
+
"imageGrid",
|
|
606
610
|
"toc",
|
|
607
611
|
"page_break",
|
|
608
612
|
"pageBreak",
|
|
613
|
+
"approval_box",
|
|
614
|
+
"approvalBox",
|
|
609
615
|
}
|
|
610
616
|
if block_type not in supported:
|
|
611
617
|
return [
|
|
@@ -627,6 +633,27 @@ def _validate_v2_block(raw_block: Any, *, path: str) -> list[PlanValidationIssue
|
|
|
627
633
|
suggestion=f"Add non-empty {text_key}.",
|
|
628
634
|
)
|
|
629
635
|
]
|
|
636
|
+
if block_type in {"image_grid", "imageGrid"}:
|
|
637
|
+
images = raw_block.get("images")
|
|
638
|
+
if not isinstance(images, list) or not images:
|
|
639
|
+
return [
|
|
640
|
+
_plan_issue(
|
|
641
|
+
"missing_image_grid_images",
|
|
642
|
+
f"{path}.images",
|
|
643
|
+
f"{path}.images must be a non-empty list",
|
|
644
|
+
suggestion="Add image items with path and optional caption fields.",
|
|
645
|
+
)
|
|
646
|
+
]
|
|
647
|
+
for image_index, image in enumerate(images):
|
|
648
|
+
if not isinstance(image, Mapping) or not str(image.get("path") or "").strip():
|
|
649
|
+
return [
|
|
650
|
+
_plan_issue(
|
|
651
|
+
"missing_image_path",
|
|
652
|
+
f"{path}.images[{image_index}].path",
|
|
653
|
+
f"{path}.images[{image_index}].path is required",
|
|
654
|
+
suggestion="Set a non-empty image path.",
|
|
655
|
+
)
|
|
656
|
+
]
|
|
630
657
|
if block_type in {"bullets", "bullet", "numbered_list", "numberedList"}:
|
|
631
658
|
if not _string_list(raw_block.get("items")):
|
|
632
659
|
return [
|
|
@@ -672,6 +699,14 @@ def _validate_v2_block(raw_block: Any, *, path: str) -> list[PlanValidationIssue
|
|
|
672
699
|
if isinstance(row, (list, tuple)):
|
|
673
700
|
for col_index, value in enumerate(row):
|
|
674
701
|
issues.extend(_computed_field_issues(value, path=f"{path}.rows[{row_index}][{col_index}]"))
|
|
702
|
+
elif block_type in {"image_grid", "imageGrid"}:
|
|
703
|
+
for image_index, image in enumerate(raw_block.get("images") or []):
|
|
704
|
+
if isinstance(image, Mapping):
|
|
705
|
+
issues.extend(_computed_field_issues(image.get("caption"), path=f"{path}.images[{image_index}].caption"))
|
|
706
|
+
elif block_type in {"approval_box", "approvalBox"}:
|
|
707
|
+
for label_index, label in enumerate(raw_block.get("labels") or []):
|
|
708
|
+
issues.extend(_computed_field_issues(label, path=f"{path}.labels[{label_index}]"))
|
|
709
|
+
issues.extend(_computed_field_issues(raw_block.get("delegated"), path=f"{path}.delegated"))
|
|
675
710
|
elif block_type == "toc":
|
|
676
711
|
issues.extend(_computed_field_issues(raw_block.get("title"), path=f"{path}.title"))
|
|
677
712
|
for entry_index, entry in enumerate(raw_block.get("entries") or []):
|
|
@@ -1325,11 +1360,15 @@ def _normalize_v2_section(raw_section: Any, *, index: int) -> BuilderSection:
|
|
|
1325
1360
|
if not isinstance(raw_section, Mapping):
|
|
1326
1361
|
raise TypeError(f"sections[{index}] must be a mapping")
|
|
1327
1362
|
raw_blocks = raw_section.get("blocks", raw_section.get("children"))
|
|
1363
|
+
children: list[Any] = []
|
|
1364
|
+
for block_index, raw_block in enumerate(raw_blocks or []):
|
|
1365
|
+
normalized = _normalize_v2_block(raw_block, path=f"sections[{index}].blocks[{block_index}]")
|
|
1366
|
+
if isinstance(normalized, tuple):
|
|
1367
|
+
children.extend(normalized)
|
|
1368
|
+
else:
|
|
1369
|
+
children.append(normalized)
|
|
1328
1370
|
return BuilderSection(
|
|
1329
|
-
children=tuple(
|
|
1330
|
-
_normalize_v2_block(raw_block, path=f"sections[{index}].blocks[{block_index}]")
|
|
1331
|
-
for block_index, raw_block in enumerate(raw_blocks or [])
|
|
1332
|
-
),
|
|
1371
|
+
children=tuple(children),
|
|
1333
1372
|
page=_normalize_v2_page(raw_section.get("page")),
|
|
1334
1373
|
margins=_normalize_v2_margins(raw_section.get("margins")),
|
|
1335
1374
|
header=_normalize_v2_header_footer(raw_section.get("header"), kind="header"),
|
|
@@ -1455,6 +1494,23 @@ def _normalize_v2_block(raw_block: Any, *, path: str) -> Any:
|
|
|
1455
1494
|
for item in raw_block.get("columnWidths", raw_block.get("column_widths")) or ()
|
|
1456
1495
|
),
|
|
1457
1496
|
)
|
|
1497
|
+
if block_type in {"approval_box", "approvalBox"}:
|
|
1498
|
+
labels = tuple(replace_computed_fields(str(item)) for item in _string_list(raw_block.get("labels")))
|
|
1499
|
+
return BuilderApprovalBox(
|
|
1500
|
+
labels=labels or None,
|
|
1501
|
+
approver_rows=_int_value(
|
|
1502
|
+
raw_block.get("approverRows", raw_block.get("approver_rows")),
|
|
1503
|
+
default=2,
|
|
1504
|
+
),
|
|
1505
|
+
delegated=(
|
|
1506
|
+
replace_computed_fields(str(raw_block.get("delegated")))
|
|
1507
|
+
if raw_block.get("delegated") is not None
|
|
1508
|
+
else None
|
|
1509
|
+
),
|
|
1510
|
+
header_shading=str(raw_block.get("headerShading", raw_block.get("header_shading")) or "EAF1FB"),
|
|
1511
|
+
)
|
|
1512
|
+
if block_type in {"image_grid", "imageGrid"}:
|
|
1513
|
+
return _image_grid_builder_nodes(raw_block)
|
|
1458
1514
|
if block_type == "image":
|
|
1459
1515
|
return BuilderImage(
|
|
1460
1516
|
path=str(raw_block.get("path") or ""),
|
|
@@ -1481,6 +1537,49 @@ def _normalize_v2_block(raw_block: Any, *, path: str) -> Any:
|
|
|
1481
1537
|
raise ValueError(f"{path}.type is unsupported: {block_type!r}")
|
|
1482
1538
|
|
|
1483
1539
|
|
|
1540
|
+
def _image_grid_builder_nodes(raw_block: Mapping[str, Any]) -> tuple[Any, ...]:
|
|
1541
|
+
block = build_image_grid(
|
|
1542
|
+
[
|
|
1543
|
+
{
|
|
1544
|
+
"path": str(image.get("path") or ""),
|
|
1545
|
+
"caption": replace_computed_fields(str(image.get("caption") or "")),
|
|
1546
|
+
}
|
|
1547
|
+
for image in raw_block.get("images") or ()
|
|
1548
|
+
if isinstance(image, Mapping)
|
|
1549
|
+
],
|
|
1550
|
+
columns=_int_value(raw_block.get("columns"), default=2),
|
|
1551
|
+
image_width_mm=_optional_number(raw_block.get("imageWidthMm", raw_block.get("image_width_mm"))),
|
|
1552
|
+
)
|
|
1553
|
+
columns = int(block["columns"])
|
|
1554
|
+
images = list(block["images"])
|
|
1555
|
+
rows: list[tuple[str, ...]] = []
|
|
1556
|
+
for offset in range(0, len(images), columns):
|
|
1557
|
+
row = []
|
|
1558
|
+
for image_index, image in enumerate(images[offset : offset + columns], start=offset + 1):
|
|
1559
|
+
row.append(f"{image_index}. {image['caption']} ({Path(str(image['path'])).name})")
|
|
1560
|
+
row.extend("" for _ in range(columns - len(row)))
|
|
1561
|
+
rows.append(tuple(row))
|
|
1562
|
+
image_width = _optional_number(raw_block.get("imageWidthMm", raw_block.get("image_width_mm")))
|
|
1563
|
+
nodes: list[Any] = [
|
|
1564
|
+
BuilderTable(
|
|
1565
|
+
header=tuple(f"사진 {index + 1}" for index in range(columns)),
|
|
1566
|
+
rows=tuple(rows),
|
|
1567
|
+
header_shading=_optional_str(raw_block.get("headerShading", raw_block.get("header_shading"))) or "EAF1FB",
|
|
1568
|
+
column_widths=tuple(1 for _ in range(columns)),
|
|
1569
|
+
)
|
|
1570
|
+
]
|
|
1571
|
+
for image_index, image in enumerate(images, start=1):
|
|
1572
|
+
nodes.append(
|
|
1573
|
+
BuilderImage(
|
|
1574
|
+
path=str(image["path"]),
|
|
1575
|
+
width_mm=image_width,
|
|
1576
|
+
align=_optional_str(raw_block.get("align")) or "center",
|
|
1577
|
+
caption=f"{image_index}. {image['caption']}",
|
|
1578
|
+
)
|
|
1579
|
+
)
|
|
1580
|
+
return tuple(nodes)
|
|
1581
|
+
|
|
1582
|
+
|
|
1484
1583
|
def _lower_plan_to_builder_document(plan: DocumentPlan) -> BuilderDocument:
|
|
1485
1584
|
"""Lower a normalized document plan to builder nodes.
|
|
1486
1585
|
|
|
@@ -7,6 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
from typing import Any, Mapping, Sequence
|
|
8
8
|
|
|
9
9
|
from hwpx.document import HwpxDocument
|
|
10
|
+
from hwpx.tools.package_validator import validate_editor_open_safety
|
|
10
11
|
from hwpx.tools.package_validator import validate_package
|
|
11
12
|
from hwpx.tools.validator import validate_document
|
|
12
13
|
|
|
@@ -71,8 +72,8 @@ class _BuilderPreset:
|
|
|
71
72
|
if self.is_government_report:
|
|
72
73
|
if run.bold and color is None:
|
|
73
74
|
color = "1F4E79"
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
if (run.bold or run.underline or run.highlight) and font is None:
|
|
76
|
+
font = "함초롬바탕"
|
|
76
77
|
return {
|
|
77
78
|
"bold": run.bold,
|
|
78
79
|
"italic": run.italic,
|
|
@@ -361,6 +362,50 @@ class Table:
|
|
|
361
362
|
table.set_column_widths(self.column_widths)
|
|
362
363
|
|
|
363
364
|
|
|
365
|
+
def approval_box(
|
|
366
|
+
*,
|
|
367
|
+
labels: Sequence[str] | None = None,
|
|
368
|
+
approver_rows: int = 2,
|
|
369
|
+
delegated: str | None = None,
|
|
370
|
+
header_shading: str = "EAF1FB",
|
|
371
|
+
) -> Table:
|
|
372
|
+
"""Return a merged approval/sign-off table for official documents."""
|
|
373
|
+
|
|
374
|
+
normalized_labels = tuple(str(label).strip() for label in (labels or ("기안", "검토", "결재", "전결")) if str(label).strip())
|
|
375
|
+
if not normalized_labels:
|
|
376
|
+
normalized_labels = ("기안", "검토", "결재", "전결")
|
|
377
|
+
delegated_label = str(delegated or "").strip()
|
|
378
|
+
if delegated_label and delegated_label not in normalized_labels:
|
|
379
|
+
normalized_labels = (*normalized_labels, delegated_label)
|
|
380
|
+
row_count = max(int(approver_rows), 1)
|
|
381
|
+
rows = tuple(tuple("" for _ in normalized_labels) for _ in range(row_count))
|
|
382
|
+
if row_count < 2:
|
|
383
|
+
merges: tuple[str, ...] = ()
|
|
384
|
+
else:
|
|
385
|
+
merges = tuple(
|
|
386
|
+
f"{_spreadsheet_column_name(index)}2:{_spreadsheet_column_name(index)}{row_count + 1}"
|
|
387
|
+
for index in range(len(normalized_labels))
|
|
388
|
+
)
|
|
389
|
+
return Table(
|
|
390
|
+
header=normalized_labels,
|
|
391
|
+
rows=rows,
|
|
392
|
+
merges=merges,
|
|
393
|
+
header_shading=header_shading,
|
|
394
|
+
column_widths=tuple(1 for _ in normalized_labels),
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _spreadsheet_column_name(index: int) -> str:
|
|
399
|
+
if index < 0:
|
|
400
|
+
raise ValueError("column index must be non-negative")
|
|
401
|
+
value = index + 1
|
|
402
|
+
letters: list[str] = []
|
|
403
|
+
while value:
|
|
404
|
+
value, remainder = divmod(value - 1, 26)
|
|
405
|
+
letters.append(chr(ord("A") + remainder))
|
|
406
|
+
return "".join(reversed(letters))
|
|
407
|
+
|
|
408
|
+
|
|
364
409
|
@dataclass(frozen=True)
|
|
365
410
|
class Image:
|
|
366
411
|
path: str | PathLike[str] | bytes
|
|
@@ -576,13 +621,24 @@ def _merge_flags(*flag_sets: dict[str, bool]) -> dict[str, bool]:
|
|
|
576
621
|
return merged
|
|
577
622
|
|
|
578
623
|
|
|
579
|
-
def _hard_gates(
|
|
624
|
+
def _hard_gates(
|
|
625
|
+
package_report: object,
|
|
626
|
+
document_report: object,
|
|
627
|
+
reopen_report: ReopenReport,
|
|
628
|
+
editor_open_safety_report: object | None = None,
|
|
629
|
+
) -> dict[str, str]:
|
|
580
630
|
document_warnings = getattr(document_report, "warnings", ())
|
|
631
|
+
editor_open_safety_ok = (
|
|
632
|
+
True
|
|
633
|
+
if editor_open_safety_report is None
|
|
634
|
+
else bool(getattr(editor_open_safety_report, "ok", False))
|
|
635
|
+
)
|
|
581
636
|
return {
|
|
582
637
|
"package_validation": "pass" if getattr(package_report, "ok", False) else "fail",
|
|
583
638
|
"document_errors": "pass" if getattr(document_report, "ok", False) else "fail",
|
|
584
639
|
"schema_lint": "warning" if document_warnings else "pass",
|
|
585
640
|
"reopen": "pass" if reopen_report.ok else "fail",
|
|
641
|
+
"editor_open_safety": "pass" if editor_open_safety_ok else "fail",
|
|
586
642
|
"id_integrity": "unavailable",
|
|
587
643
|
}
|
|
588
644
|
|
|
@@ -696,6 +752,7 @@ class Document:
|
|
|
696
752
|
document.save_to_path(path)
|
|
697
753
|
package_report = validate_package(path)
|
|
698
754
|
document_report = validate_document(path)
|
|
755
|
+
editor_open_safety_report = validate_editor_open_safety(path)
|
|
699
756
|
try:
|
|
700
757
|
reopened_document = HwpxDocument.open(path)
|
|
701
758
|
reopen_report = ReopenReport(ok=True, document=reopened_document)
|
|
@@ -713,8 +770,14 @@ class Document:
|
|
|
713
770
|
validate_document=document_report,
|
|
714
771
|
reopened=reopen_report,
|
|
715
772
|
metadata=self.metadata.as_dict() if self.metadata is not None else {},
|
|
716
|
-
hard_gates=_hard_gates(
|
|
773
|
+
hard_gates=_hard_gates(
|
|
774
|
+
package_report,
|
|
775
|
+
document_report,
|
|
776
|
+
reopen_report,
|
|
777
|
+
editor_open_safety_report,
|
|
778
|
+
),
|
|
717
779
|
visual_review_required=visual_review_required,
|
|
718
780
|
feature_flags=feature_flags,
|
|
781
|
+
editor_open_safety=editor_open_safety_report,
|
|
719
782
|
)
|
|
720
783
|
return report
|
|
@@ -6,7 +6,7 @@ from os import PathLike
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
from hwpx.tools.id_integrity import IdIntegrityReport, check_id_integrity
|
|
9
|
-
from hwpx.tools.package_validator import PackageValidationReport
|
|
9
|
+
from hwpx.tools.package_validator import EditorOpenSafetyReport, PackageValidationReport
|
|
10
10
|
from hwpx.tools.validator import ValidationReport
|
|
11
11
|
|
|
12
12
|
|
|
@@ -32,6 +32,7 @@ class BuilderSaveReport:
|
|
|
32
32
|
visual_review_required: bool = False
|
|
33
33
|
feature_flags: dict[str, bool] = field(default_factory=dict)
|
|
34
34
|
id_integrity: IdIntegrityReport | None = None
|
|
35
|
+
editor_open_safety: EditorOpenSafetyReport | None = None
|
|
35
36
|
|
|
36
37
|
def __post_init__(self) -> None:
|
|
37
38
|
hard_gates = dict(self.hard_gates)
|
|
@@ -52,6 +53,11 @@ class BuilderSaveReport:
|
|
|
52
53
|
"hard_gates": dict(self.hard_gates),
|
|
53
54
|
"visual_review_required": self.visual_review_required,
|
|
54
55
|
"feature_flags": dict(self.feature_flags),
|
|
56
|
+
"editor_open_safety": (
|
|
57
|
+
None
|
|
58
|
+
if self.editor_open_safety is None
|
|
59
|
+
else self.editor_open_safety.to_dict()
|
|
60
|
+
),
|
|
55
61
|
"validate_package": {
|
|
56
62
|
"ok": self.validate_package.ok,
|
|
57
63
|
"checked_parts": list(self.validate_package.checked_parts),
|
|
@@ -76,6 +82,16 @@ class BuilderSaveReport:
|
|
|
76
82
|
else {
|
|
77
83
|
"ok": self.id_integrity.ok,
|
|
78
84
|
"dangling": [str(item) for item in self.id_integrity.dangling],
|
|
85
|
+
"orphan_bin_data": [
|
|
86
|
+
{
|
|
87
|
+
"item_id": item.item_id,
|
|
88
|
+
"path": item.path,
|
|
89
|
+
"aliases": list(item.aliases),
|
|
90
|
+
"sources": list(item.sources),
|
|
91
|
+
"severity": item.severity,
|
|
92
|
+
}
|
|
93
|
+
for item in self.id_integrity.orphan_bin_data
|
|
94
|
+
],
|
|
79
95
|
"ignored": [
|
|
80
96
|
{
|
|
81
97
|
"part": item.part,
|