python-hwpx 5.4.0__tar.gz → 5.6.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-5.4.0/src/python_hwpx.egg-info → python_hwpx-5.6.0}/PKG-INFO +1 -1
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/pyproject.toml +15 -2
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/__init__.py +12 -0
- python_hwpx-5.6.0/src/hwpx/capabilities.py +466 -0
- python_hwpx-5.6.0/src/hwpx/data/contract_docs/known-traps.md +68 -0
- python_hwpx-5.6.0/src/hwpx/data/contract_docs/mutation-semantics.md +59 -0
- python_hwpx-5.6.0/src/hwpx/data/contract_docs/recipes-traversal.md +115 -0
- python_hwpx-5.6.0/src/hwpx/data/contract_docs/support-matrix.md +61 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/experimental.py +20 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/memo.py +26 -6
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/paragraph.py +101 -10
- python_hwpx-5.6.0/src/hwpx/plan/__init__.py +48 -0
- python_hwpx-5.6.0/src/hwpx/plan/_execute.py +282 -0
- python_hwpx-5.6.0/src/hwpx/plan/_model.py +269 -0
- python_hwpx-5.6.0/src/hwpx/plan/_schema.py +127 -0
- python_hwpx-5.6.0/src/hwpx/plan/_validate.py +174 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/markdown_export.py +6 -2
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/text_extractor.py +14 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0/src/python_hwpx.egg-info}/PKG-INFO +1 -1
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/python_hwpx.egg-info/SOURCES.txt +14 -0
- python_hwpx-5.6.0/tests/test_capabilities_surface.py +137 -0
- python_hwpx-5.6.0/tests/test_contract_docs_sync.py +41 -0
- python_hwpx-5.6.0/tests/test_edit_plan.py +356 -0
- python_hwpx-5.6.0/tests/test_note_authoring_contract.py +121 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_oxml_modularization.py +5 -4
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_product_boundary.py +2 -1
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_public_artifact_hygiene.py +1 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_stable_surface.py +3 -2
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/LICENSE +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/NOTICE +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/README.md +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/setup.cfg +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/_document/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/_document/_units.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/_document/fields.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/_document/layout.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/_document/media.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/_document/memos.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/_document/persistence.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/_document/shapes.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/_document/tracked.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/body_patch.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/data/Skeleton.hwpx +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/document.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/equation/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/equation/authoring.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/equation/eqedit.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/equation/mathml.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/equation/render.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/equation/tokens.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/errors.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/form_fit/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/form_fit/apply.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/form_fit/engine.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/form_fit/measure.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/form_fit/policy.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/form_fit/report.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/ingest/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/ingest/base.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/ingest/hwpx_converter.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/layout/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/layout/lint.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/layout/report.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/mutation_report.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/opc/package.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/opc/relationships.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/opc/security.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/opc/xml_utils.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/_document_impl.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/_document_primitives.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/body.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/canonical_defaults.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/color.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/common.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/document.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/document_parts.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/header.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/header_part.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/namespaces.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/numbering.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/objects.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/parser.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/run.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/schema.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/section.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/section_format.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/section_story.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/simple_parts.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/table.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/oxml/utils.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/package.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/patch.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/py.typed +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/quality/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/quality/ledger.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/quality/policy.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/quality/rendering.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/quality/report.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/quality/save_pipeline.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/table_patch.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/templates.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/_schemas/header.xsd +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/_schemas/section.xsd +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/archive_cli.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/doc_diff.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/document_viewer.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/exporter.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/generic_inventory.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/id_integrity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/idempotence.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/ir_equality.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/layout_preview.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/mail_merge.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/object_finder.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/package_reconcile.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/package_validator.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/page_guard.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/read_fidelity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/recover.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/redline.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/repair.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/report_utils.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/roundtrip_diff.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/table_cleanup.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/table_navigation.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/template_analyzer.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/text_extract_cli.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/toc_author.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/toc_fidelity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/hwpx/tools/validator.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/python_hwpx.egg-info/entry_points.txt +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/python_hwpx.egg-info/requires.txt +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/src/python_hwpx.egg-info/top_level.txt +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_add_control.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_adoption_metrics_snapshot.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_authoring_defaults.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_authoring_quality_corpus.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_blind_eval_fixture.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_byte_patch_identity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_cell_line_spacing.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_chart_authoring.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_comment_node_robustness.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_corpus_read_fidelity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_coverage_promotion.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_coverage_targets.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_deviations_registry.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_doc_diff.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_document_context_manager.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_document_facade_surface.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_document_formatting.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_document_ops_runtime_boundary.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_document_save_api.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_document_viewer.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_documentation_code_fences.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_equation_authoring.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_equation_converter.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_equation_render.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_error_messages.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_evalplan_core_primitives.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_existing_document_format_editing.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_form_field_authoring.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_form_fields.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_form_fill_core_primitives.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_form_fit.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_form_fit_integration.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_fuzz_catalog_derive_expected.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_fuzz_loop.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_fuzz_regressions.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_gap_closure_tools.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_golden_api.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_hp_tab_support.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_hwpxlib_corpus_read.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_id_generator_range.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_image_object_workflow.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_ingest.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_inline_models.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_integration_hwpx_compatibility.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_integration_roundtrip.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_layout_cache_scope.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_layout_lint.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_layout_preview.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_library_boundary_closure.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_llms_txt.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_mail_merge_xlsx.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_markdown_export.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_memo_and_style_editing.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_moved_surface_hints.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_mutation_report.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_namespace_handling.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_new_features.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_opc_package.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_open_safety_corpus.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_oxml_parsing.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_packaging_license_metadata.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_packaging_py_typed.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_paragraph_keep_together.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_paragraph_section_management.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_public_runtime_boundary.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_read_fidelity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_recover_broken_zip.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_redline_authoring.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_redline_verify.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_release_workflow_safety.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_repair_repack.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_report_utils.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_repr_snapshots.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_roundtrip_fidelity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_row_heights.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_save_pipeline.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_section_headers.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_set_paragraph_format_keep.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_shape_geometry_contract.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_skeleton_template_ids.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_split_cell.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_split_merged_cell.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_table_cleanup.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_table_navigation.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_table_patch.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_table_patch_dryrun.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_table_patch_m105.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_tables_default_border.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_template_analyzer_enrichment.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_text_extractor_annotations.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_toc_author.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_toc_fidelity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_validation_severity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_validator_comment_nodes.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.6.0}/tests/test_version_metadata.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-hwpx"
|
|
7
|
-
version = "5.
|
|
7
|
+
version = "5.6.0"
|
|
8
8
|
description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 문서 라이브러리"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -109,6 +109,7 @@ packages = [
|
|
|
109
109
|
"hwpx.layout",
|
|
110
110
|
"hwpx.opc",
|
|
111
111
|
"hwpx.oxml",
|
|
112
|
+
"hwpx.plan",
|
|
112
113
|
"hwpx.quality",
|
|
113
114
|
"hwpx.tools",
|
|
114
115
|
"hwpx.tools._schemas",
|
|
@@ -125,7 +126,7 @@ packages = [
|
|
|
125
126
|
[tool.setuptools.package-data]
|
|
126
127
|
"hwpx" = ["py.typed"]
|
|
127
128
|
"hwpx.tools" = ["_schemas/*.xsd"]
|
|
128
|
-
"hwpx.data" = ["Skeleton.hwpx"]
|
|
129
|
+
"hwpx.data" = ["Skeleton.hwpx", "contract_docs/*.md"]
|
|
129
130
|
|
|
130
131
|
[tool.pytest.ini_options]
|
|
131
132
|
# scripts/ carries repository QA harnesses that are not library surface — the
|
|
@@ -145,6 +146,12 @@ files = [
|
|
|
145
146
|
"src/hwpx/document.py",
|
|
146
147
|
"src/hwpx/errors.py",
|
|
147
148
|
"src/hwpx/mutation_report.py",
|
|
149
|
+
"src/hwpx/capabilities.py",
|
|
150
|
+
"src/hwpx/plan/__init__.py",
|
|
151
|
+
"src/hwpx/plan/_model.py",
|
|
152
|
+
"src/hwpx/plan/_validate.py",
|
|
153
|
+
"src/hwpx/plan/_execute.py",
|
|
154
|
+
"src/hwpx/plan/_schema.py",
|
|
148
155
|
"src/hwpx/equation/__init__.py",
|
|
149
156
|
"src/hwpx/equation/tokens.py",
|
|
150
157
|
"src/hwpx/equation/eqedit.py",
|
|
@@ -200,6 +207,12 @@ include = [
|
|
|
200
207
|
"src/hwpx/document.py",
|
|
201
208
|
"src/hwpx/errors.py",
|
|
202
209
|
"src/hwpx/mutation_report.py",
|
|
210
|
+
"src/hwpx/capabilities.py",
|
|
211
|
+
"src/hwpx/plan/__init__.py",
|
|
212
|
+
"src/hwpx/plan/_model.py",
|
|
213
|
+
"src/hwpx/plan/_validate.py",
|
|
214
|
+
"src/hwpx/plan/_execute.py",
|
|
215
|
+
"src/hwpx/plan/_schema.py",
|
|
203
216
|
"src/hwpx/equation/__init__.py",
|
|
204
217
|
"src/hwpx/equation/tokens.py",
|
|
205
218
|
"src/hwpx/equation/eqedit.py",
|
|
@@ -52,6 +52,18 @@ _EXPERIMENTAL_EXPORTS = {
|
|
|
52
52
|
"UnsupportedLatexError": "hwpx.equation.authoring",
|
|
53
53
|
"estimate_equation_size": "hwpx.equation.authoring",
|
|
54
54
|
"latex_to_eqedit": "hwpx.equation.authoring",
|
|
55
|
+
# 편집 계획 실행기(5.6.0 신규, 선언적 다단 편집 -> 원자 실행). 계약 유동.
|
|
56
|
+
"EditPlan": "hwpx.plan",
|
|
57
|
+
"PlanReport": "hwpx.plan",
|
|
58
|
+
"PlanValidationError": "hwpx.plan",
|
|
59
|
+
"apply_edit_plan": "hwpx.plan",
|
|
60
|
+
"validate_edit_plan": "hwpx.plan",
|
|
61
|
+
# 기계가독 자기서술(5.6.0 신규, 드리프트 가드 포함). 계약 유동.
|
|
62
|
+
# 함수명은 describe_capabilities — 모듈 hwpx.capabilities와 이름이 같으면
|
|
63
|
+
# 서브모듈 바인딩이 lazy 알리아스를 그림자로 가려 경고 계약이 깨진다.
|
|
64
|
+
"describe_capabilities": "hwpx.capabilities",
|
|
65
|
+
"contract_document": "hwpx.capabilities",
|
|
66
|
+
"contract_json_schema": "hwpx.capabilities",
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
# Emptied in 5.0. The 4.x notice on these names said they would go in the next
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""``hwpx.capabilities`` — 기계가독 자기서술 (experimental, 5.6.0 신규).
|
|
3
|
+
|
|
4
|
+
동기(실측 드리프트 사례): 사람 손으로 갱신하는 자기서술 문서는 릴리스 트레인
|
|
5
|
+
하루 만에도 실표면과 드리프트한다. 이 모듈이 단일 진실 원천이고, 드리프트
|
|
6
|
+
가드 테스트가 다음 세 방향을 강제한다:
|
|
7
|
+
|
|
8
|
+
1. 여기 등재된 진입점은 전부 실제로 import 가능해야 한다.
|
|
9
|
+
2. ``editPlanOps``는 :data:`hwpx.plan.PLAN_OPS`·실행 디스패치·edit-plan JSON
|
|
10
|
+
Schema enum과 일치해야 한다.
|
|
11
|
+
3. :data:`_CAPABILITY_AREAS`의 매트릭스 행 제목은 ``docs/support-matrix.md``
|
|
12
|
+
표의 행 제목과 집합 일치해야 한다.
|
|
13
|
+
|
|
14
|
+
**core는 환경 변수를 읽지 않는다** — 렌더 오라클 가용성은 여기 없다(자기서술은
|
|
15
|
+
``renderOracle.bundled="none"``으로 그 사실 자체를 말한다). 오라클 탐지·실행은
|
|
16
|
+
automation 계층(python-hwpx-automation)이 보고한다.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import platform
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from .errors import HwpxError
|
|
26
|
+
|
|
27
|
+
CAPABILITIES_SCHEMA = "hwpx.capabilities/v1"
|
|
28
|
+
|
|
29
|
+
_DIST_NAME = "python-hwpx"
|
|
30
|
+
|
|
31
|
+
#: extras 이름 → 그 extra가 끄는 런타임 모듈들. ``visual``은 5.0부터 의도적으로
|
|
32
|
+
#: 빈 extra(설치 호환 유지용)라 백킹 모듈이 없고, 프로브는 공허하게 참이다.
|
|
33
|
+
_EXTRA_MODULES: dict[str, tuple[str, ...]] = {
|
|
34
|
+
"visual": (),
|
|
35
|
+
"xlsx": ("openpyxl",),
|
|
36
|
+
"preview": ("latex2mathml",),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#: 능력 영역 레지스트리. ``matrix_row``는 ``docs/support-matrix.md`` 표 1열
|
|
40
|
+
#: 제목과 **정확히** 일치해야 한다(가드 테스트가 집합 대조). ``entry_points``는
|
|
41
|
+
#: ``"모듈:이름"`` — 전부 import 가능해야 한다(가드 테스트가 해석).
|
|
42
|
+
_CAPABILITY_AREAS: tuple[dict[str, Any], ...] = (
|
|
43
|
+
{
|
|
44
|
+
"area": "paragraph-table-authoring",
|
|
45
|
+
"matrix_row": "문단·표 저작/편집",
|
|
46
|
+
"entry_points": ("hwpx.document:HwpxDocument",),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"area": "table-structure",
|
|
50
|
+
"matrix_row": "표 구조 변경(행·열·표 삭제/삽입, 열 오토핏)",
|
|
51
|
+
"entry_points": ("hwpx.table_patch:apply_table_ops",),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"area": "table-create",
|
|
55
|
+
"matrix_row": "표 생성(병합·중첩 포함)",
|
|
56
|
+
"entry_points": ("hwpx.document:HwpxDocument",),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"area": "form-fill",
|
|
60
|
+
"matrix_row": "양식 채움(byte-splice)",
|
|
61
|
+
"entry_points": (
|
|
62
|
+
"hwpx.patch:paragraph_patch",
|
|
63
|
+
"hwpx.table_patch:fill_cells",
|
|
64
|
+
"hwpx.body_patch:apply_body_ops",
|
|
65
|
+
),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"area": "edit-plan",
|
|
69
|
+
"matrix_row": "편집 계획 실행(edit plan)",
|
|
70
|
+
"entry_points": (
|
|
71
|
+
"hwpx.plan:apply_edit_plan",
|
|
72
|
+
"hwpx.plan:validate_edit_plan",
|
|
73
|
+
),
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"area": "shape-authoring",
|
|
77
|
+
"matrix_row": "도형 저작(선·사각형·타원)",
|
|
78
|
+
"entry_points": ("hwpx.document:HwpxDocument",),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"area": "shape-escape-hatch",
|
|
82
|
+
"matrix_row": "저수준 도형·컨트롤 탈출구",
|
|
83
|
+
"entry_points": ("hwpx.document:HwpxDocument",),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"area": "curve-objects",
|
|
87
|
+
"matrix_row": "arc·polygon·curve·connectLine",
|
|
88
|
+
"entry_points": (),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"area": "picture",
|
|
92
|
+
"matrix_row": "그림 삽입/치환",
|
|
93
|
+
"entry_points": ("hwpx.document:HwpxDocument",),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"area": "chart",
|
|
97
|
+
"matrix_row": "차트",
|
|
98
|
+
"entry_points": ("hwpx.document:HwpxDocument",),
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"area": "equation",
|
|
102
|
+
"matrix_row": "수식",
|
|
103
|
+
"entry_points": ("hwpx.equation.authoring:latex_to_eqedit",),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"area": "redline",
|
|
107
|
+
"matrix_row": "변경추적(redline)",
|
|
108
|
+
"entry_points": ("hwpx.tools.redline:verify_redline",),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"area": "memo",
|
|
112
|
+
"matrix_row": "메모(코멘트)",
|
|
113
|
+
"entry_points": ("hwpx.document:HwpxDocument",),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"area": "footnote-endnote",
|
|
117
|
+
"matrix_row": "각주/미주",
|
|
118
|
+
"entry_points": ("hwpx.document:HwpxDocument",),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"area": "toc-crossref",
|
|
122
|
+
"matrix_row": "네이티브 목차(TOC)/상호참조",
|
|
123
|
+
"entry_points": ("hwpx.tools.toc_author:add_native_toc",),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"area": "encrypted-hwpx",
|
|
127
|
+
"matrix_row": "암호화 HWPX",
|
|
128
|
+
"entry_points": (),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"area": "hwp5-binary",
|
|
132
|
+
"matrix_row": "HWP 5.x 바이너리",
|
|
133
|
+
"entry_points": (),
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"area": "form-field-create",
|
|
137
|
+
"matrix_row": "누름틀(form field) 생성",
|
|
138
|
+
"entry_points": ("hwpx.document:HwpxDocument",),
|
|
139
|
+
},
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
#: 패키지에 동봉되는 계약 문서 이름 → 파일. MCP resources 표면의 원천.
|
|
143
|
+
_CONTRACT_DOCS: dict[str, str] = {
|
|
144
|
+
"support-matrix": "support-matrix.md",
|
|
145
|
+
"recipes-traversal": "recipes-traversal.md",
|
|
146
|
+
"mutation-semantics": "mutation-semantics.md",
|
|
147
|
+
"known-traps": "known-traps.md",
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
_CONTRACT_DOCS_DIR = Path(__file__).resolve().parent / "data" / "contract_docs"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _extra_installed(extra: str) -> bool:
|
|
154
|
+
"""extra 설치 여부 — 함수 스코프 **정적 import** try/except 프로브.
|
|
155
|
+
|
|
156
|
+
core의 importlib 표면은 boundary ratchet상 ``from importlib import
|
|
157
|
+
resources`` 한 형태뿐이라 동적 프로브(find_spec/import_module)를 쓰지
|
|
158
|
+
않는다. 닫힌 extras 집합이므로 하우스 패턴(mail_merge의 지연 import)과
|
|
159
|
+
같은 정적 guarded import면 충분하다.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
if extra == "xlsx":
|
|
163
|
+
try:
|
|
164
|
+
import openpyxl # noqa: F401
|
|
165
|
+
except Exception:
|
|
166
|
+
return False
|
|
167
|
+
return True
|
|
168
|
+
if extra == "preview":
|
|
169
|
+
from .equation.mathml import latex2mathml_available
|
|
170
|
+
|
|
171
|
+
return latex2mathml_available()
|
|
172
|
+
# visual: 5.0부터 의도적 빈 extra(설치 호환 유지) — 공허하게 참.
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _package_version() -> str:
|
|
177
|
+
from . import __version__
|
|
178
|
+
|
|
179
|
+
return str(__version__)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# 진입점 실해석(import_module)은 런타임에 없다 — core의 동적 import 능력은
|
|
183
|
+
# __init__의 핀된 lazy 로더뿐(boundary ratchet). 레지스트리 진입점이 전부
|
|
184
|
+
# 해석되는지는 tests/test_capabilities_surface.py의 드리프트 가드가 실측한다.
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def describe_capabilities() -> dict[str, Any]:
|
|
188
|
+
"""설치된 core의 기계가독 자기서술 — ``hwpx.capabilities/v1``.
|
|
189
|
+
|
|
190
|
+
전부 실측이다: 버전은 배포 메타데이터, extras는 import 프로브, 표면 목록은
|
|
191
|
+
라이브 ``__all__``, op 어휘는 실행기 디스패치와 같은 표. 렌더 오라클은
|
|
192
|
+
core에 없다는 사실 자체를 보고한다(측정 없는 가용성 주장 금지).
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
from . import __all__ as stable_names
|
|
196
|
+
from .experimental import __all__ as experimental_names
|
|
197
|
+
from .plan import PLAN_OPS
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
"schemaVersion": CAPABILITIES_SCHEMA,
|
|
201
|
+
"package": {"name": _DIST_NAME, "version": _package_version()},
|
|
202
|
+
"python": platform.python_version(),
|
|
203
|
+
"extras": {name: _extra_installed(name) for name in _EXTRA_MODULES},
|
|
204
|
+
"renderOracle": {
|
|
205
|
+
"bundled": "none",
|
|
206
|
+
"note": (
|
|
207
|
+
"core는 렌더 백엔드를 동봉하지 않습니다(RenderBackend 주입 seam). "
|
|
208
|
+
"실한컴 오라클 탐지·실행과 그 가용성 보고는 python-hwpx-automation"
|
|
209
|
+
"이 소유합니다."
|
|
210
|
+
),
|
|
211
|
+
},
|
|
212
|
+
"editPlanOps": sorted(PLAN_OPS),
|
|
213
|
+
"schemas": sorted(_SCHEMA_BUILDERS),
|
|
214
|
+
"surfaces": {
|
|
215
|
+
"stable": sorted(stable_names),
|
|
216
|
+
"experimental": sorted(experimental_names),
|
|
217
|
+
},
|
|
218
|
+
"features": [
|
|
219
|
+
{
|
|
220
|
+
"area": row["area"],
|
|
221
|
+
"matrixRow": row["matrix_row"],
|
|
222
|
+
"entryPoints": list(row["entry_points"]),
|
|
223
|
+
}
|
|
224
|
+
for row in _CAPABILITY_AREAS
|
|
225
|
+
],
|
|
226
|
+
"contractDocuments": sorted(_CONTRACT_DOCS),
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def contract_document(name: str) -> str:
|
|
231
|
+
"""패키지에 동봉된 계약 문서(markdown 원문)를 돌려준다.
|
|
232
|
+
|
|
233
|
+
이름 어휘는 ``describe_capabilities()["contractDocuments"]``와 같다. 미지 이름은
|
|
234
|
+
typed 거부(fail-closed).
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
filename = _CONTRACT_DOCS.get(name)
|
|
238
|
+
if filename is None:
|
|
239
|
+
raise HwpxError(
|
|
240
|
+
f"미지 계약 문서 '{name}'입니다.",
|
|
241
|
+
code="unknown-contract-document",
|
|
242
|
+
suggestion=f"가능한 이름: {', '.join(sorted(_CONTRACT_DOCS))}",
|
|
243
|
+
)
|
|
244
|
+
path = _CONTRACT_DOCS_DIR / filename
|
|
245
|
+
try:
|
|
246
|
+
return path.read_text(encoding="utf-8")
|
|
247
|
+
except OSError as exc:
|
|
248
|
+
raise HwpxError(
|
|
249
|
+
f"계약 문서가 패키지에 없습니다: {name}",
|
|
250
|
+
code="contract-document-missing",
|
|
251
|
+
context={"path": str(path)},
|
|
252
|
+
suggestion=(
|
|
253
|
+
"개발 체크아웃이라면 scripts/sync_contract_docs.py를 실행해 "
|
|
254
|
+
"docs/ 원본을 data/contract_docs/로 동기화하세요."
|
|
255
|
+
),
|
|
256
|
+
) from exc
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _mutation_report_json_schema() -> dict[str, Any]:
|
|
260
|
+
"""``hwpx.mutation-report/v1``의 JSON Schema.
|
|
261
|
+
|
|
262
|
+
형태의 진실 원천은 :mod:`hwpx.mutation_report`의 ``to_dict()``들이다 —
|
|
263
|
+
테스트가 실제 리포트 사영을 이 스키마로 검증해 두 표현의 드리프트를 막는다.
|
|
264
|
+
최상위 필수 키 9개는 automation의 동결 계약(FROZEN_MUTATION_REPORT_KEYS)과
|
|
265
|
+
같다.
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
verification_value = {"enum": ["passed", "failed", "not_performed"]}
|
|
269
|
+
counts = {
|
|
270
|
+
"type": "object",
|
|
271
|
+
"required": ["verified", "changed"],
|
|
272
|
+
"properties": {
|
|
273
|
+
"verified": {"type": "integer", "minimum": 0},
|
|
274
|
+
"changed": {"type": "integer", "minimum": 0},
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
279
|
+
"$id": "https://airmang.github.io/python-hwpx/schemas/mutation-report-v1.json",
|
|
280
|
+
"title": "hwpx.mutation-report/v1",
|
|
281
|
+
"type": "object",
|
|
282
|
+
"required": [
|
|
283
|
+
"schemaVersion",
|
|
284
|
+
"ok",
|
|
285
|
+
"path",
|
|
286
|
+
"requestedMode",
|
|
287
|
+
"actualMode",
|
|
288
|
+
"fallbackUsed",
|
|
289
|
+
"changedParts",
|
|
290
|
+
"preservation",
|
|
291
|
+
"verification",
|
|
292
|
+
],
|
|
293
|
+
"properties": {
|
|
294
|
+
"schemaVersion": {"const": "hwpx.mutation-report/v1"},
|
|
295
|
+
"ok": {"type": "boolean"},
|
|
296
|
+
"path": {"type": ["string", "null"]},
|
|
297
|
+
"requestedMode": {"enum": ["patch", "rebuild", "auto"]},
|
|
298
|
+
"actualMode": {"enum": ["patch", "rebuild"]},
|
|
299
|
+
"fallbackUsed": {"type": "boolean"},
|
|
300
|
+
"changedParts": {
|
|
301
|
+
"type": "array",
|
|
302
|
+
"items": {
|
|
303
|
+
"type": "object",
|
|
304
|
+
"required": ["path", "reason", "ranges"],
|
|
305
|
+
"properties": {
|
|
306
|
+
"path": {"type": "string"},
|
|
307
|
+
"reason": {"enum": ["dirty-part", "unexpected"]},
|
|
308
|
+
"ranges": {
|
|
309
|
+
"type": ["array", "null"],
|
|
310
|
+
"items": {
|
|
311
|
+
"type": "object",
|
|
312
|
+
"required": ["start", "end", "coordinateSpace"],
|
|
313
|
+
"properties": {
|
|
314
|
+
"start": {"type": "integer", "minimum": 0},
|
|
315
|
+
"end": {"type": "integer", "minimum": 0},
|
|
316
|
+
"coordinateSpace": {
|
|
317
|
+
"const": "uncompressed-part-bytes"
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
"preservation": {
|
|
326
|
+
"type": "object",
|
|
327
|
+
"required": [
|
|
328
|
+
"untouchedPartPayloads",
|
|
329
|
+
"untouchedLocalZipRecords",
|
|
330
|
+
"wholePackageIdentical",
|
|
331
|
+
],
|
|
332
|
+
"properties": {
|
|
333
|
+
"untouchedPartPayloads": counts,
|
|
334
|
+
"untouchedLocalZipRecords": counts,
|
|
335
|
+
"wholePackageIdentical": {"type": "boolean"},
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
"verification": {
|
|
339
|
+
"type": "object",
|
|
340
|
+
"required": ["package", "openSafety", "reopen", "visual"],
|
|
341
|
+
"properties": {
|
|
342
|
+
"package": verification_value,
|
|
343
|
+
"openSafety": verification_value,
|
|
344
|
+
"reopen": verification_value,
|
|
345
|
+
"visual": verification_value,
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _capabilities_json_schema() -> dict[str, Any]:
|
|
353
|
+
"""``hwpx.capabilities/v1``의 JSON Schema."""
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
357
|
+
"$id": "https://airmang.github.io/python-hwpx/schemas/capabilities-v1.json",
|
|
358
|
+
"title": CAPABILITIES_SCHEMA,
|
|
359
|
+
"type": "object",
|
|
360
|
+
"required": [
|
|
361
|
+
"schemaVersion",
|
|
362
|
+
"package",
|
|
363
|
+
"python",
|
|
364
|
+
"extras",
|
|
365
|
+
"renderOracle",
|
|
366
|
+
"editPlanOps",
|
|
367
|
+
"schemas",
|
|
368
|
+
"surfaces",
|
|
369
|
+
"features",
|
|
370
|
+
"contractDocuments",
|
|
371
|
+
],
|
|
372
|
+
"properties": {
|
|
373
|
+
"schemaVersion": {"const": CAPABILITIES_SCHEMA},
|
|
374
|
+
"package": {
|
|
375
|
+
"type": "object",
|
|
376
|
+
"required": ["name", "version"],
|
|
377
|
+
"properties": {
|
|
378
|
+
"name": {"const": _DIST_NAME},
|
|
379
|
+
"version": {"type": "string"},
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
"python": {"type": "string"},
|
|
383
|
+
"extras": {
|
|
384
|
+
"type": "object",
|
|
385
|
+
"additionalProperties": {"type": "boolean"},
|
|
386
|
+
},
|
|
387
|
+
"renderOracle": {
|
|
388
|
+
"type": "object",
|
|
389
|
+
"required": ["bundled", "note"],
|
|
390
|
+
"properties": {
|
|
391
|
+
"bundled": {"const": "none"},
|
|
392
|
+
"note": {"type": "string"},
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
"editPlanOps": {"type": "array", "items": {"type": "string"}},
|
|
396
|
+
"schemas": {"type": "array", "items": {"type": "string"}},
|
|
397
|
+
"surfaces": {
|
|
398
|
+
"type": "object",
|
|
399
|
+
"required": ["stable", "experimental"],
|
|
400
|
+
"properties": {
|
|
401
|
+
"stable": {"type": "array", "items": {"type": "string"}},
|
|
402
|
+
"experimental": {"type": "array", "items": {"type": "string"}},
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
"features": {
|
|
406
|
+
"type": "array",
|
|
407
|
+
"items": {
|
|
408
|
+
"type": "object",
|
|
409
|
+
"required": ["area", "matrixRow", "entryPoints"],
|
|
410
|
+
"properties": {
|
|
411
|
+
"area": {"type": "string"},
|
|
412
|
+
"matrixRow": {"type": "string"},
|
|
413
|
+
"entryPoints": {
|
|
414
|
+
"type": "array",
|
|
415
|
+
"items": {"type": "string"},
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
"contractDocuments": {"type": "array", "items": {"type": "string"}},
|
|
421
|
+
},
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _edit_plan_schema() -> dict[str, Any]:
|
|
426
|
+
from .plan import edit_plan_json_schema
|
|
427
|
+
|
|
428
|
+
return edit_plan_json_schema()
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _plan_report_schema() -> dict[str, Any]:
|
|
432
|
+
from .plan import plan_report_json_schema
|
|
433
|
+
|
|
434
|
+
return plan_report_json_schema()
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
_SCHEMA_BUILDERS: dict[str, Any] = {
|
|
438
|
+
"hwpx.edit-plan/v1": _edit_plan_schema,
|
|
439
|
+
"hwpx.plan-report/v1": _plan_report_schema,
|
|
440
|
+
"hwpx.mutation-report/v1": _mutation_report_json_schema,
|
|
441
|
+
"hwpx.capabilities/v1": _capabilities_json_schema,
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def contract_json_schema(name: str) -> dict[str, Any]:
|
|
446
|
+
"""계약 스키마를 이름으로 돌려준다(라이브 빌드 — 파일 박제 없음).
|
|
447
|
+
|
|
448
|
+
이름 어휘는 ``describe_capabilities()["schemas"]``와 같다. 미지 이름은 typed 거부.
|
|
449
|
+
"""
|
|
450
|
+
|
|
451
|
+
builder = _SCHEMA_BUILDERS.get(name)
|
|
452
|
+
if builder is None:
|
|
453
|
+
raise HwpxError(
|
|
454
|
+
f"미지 계약 스키마 '{name}'입니다.",
|
|
455
|
+
code="unknown-contract-schema",
|
|
456
|
+
suggestion=f"가능한 이름: {', '.join(sorted(_SCHEMA_BUILDERS))}",
|
|
457
|
+
)
|
|
458
|
+
return builder()
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
__all__ = [
|
|
462
|
+
"CAPABILITIES_SCHEMA",
|
|
463
|
+
"describe_capabilities",
|
|
464
|
+
"contract_document",
|
|
465
|
+
"contract_json_schema",
|
|
466
|
+
]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# 알려진 함정 사전 (Known Traps)
|
|
2
|
+
|
|
3
|
+
에이전트·자동화 코드가 python-hwpx로 문서를 다룰 때 실제로 밟아 본 함정들의
|
|
4
|
+
사전이다. 각 항목은 "무엇이, 왜, 어떻게 피하나"만 말한다. 이 문서는 패키지에
|
|
5
|
+
동봉되어 `hwpx.capabilities.contract_document("known-traps")`와 MCP 리소스로도
|
|
6
|
+
제공된다.
|
|
7
|
+
|
|
8
|
+
## 편집·저장
|
|
9
|
+
|
|
10
|
+
- **바이트-스플라이스 op의 `output_path`는 게이트 실패에도 쓴다.**
|
|
11
|
+
`paragraph_patch`·`fill_cells`·`apply_table_ops`·`apply_body_ops` 계열은
|
|
12
|
+
결과가 skip·불안전이어도 `output_path`를 주면 파일을 쓴다(전 op 스킵 시
|
|
13
|
+
원본 바이트 그대로 기록). 합성 실행이 필요하면 `output_path` 없이 부르고
|
|
14
|
+
쓰기를 직접 소유하거나, 처음부터 `hwpx.plan.apply_edit_plan`(all-or-nothing
|
|
15
|
+
원자 실행)을 쓰라.
|
|
16
|
+
- **결과 `ok`는 "스킵 0 + open-safety 통과"다.** 스킵은 부분 적용이 아니라
|
|
17
|
+
"그 항목은 건드리지 않았다"는 뜻이다 — `ok=False`인데 산출물을 채택하면
|
|
18
|
+
무음 부분 적용이 된다.
|
|
19
|
+
- **`SavePipeline`의 `quality=None` 기본값은 strict다.** 내부 호출은 전부
|
|
20
|
+
`QualityPolicy.transparent()`를 명시한다. 파이프라인을 직접 부를 때 기본값에
|
|
21
|
+
의존하면 의도보다 강한 게이트로 거부될 수 있다.
|
|
22
|
+
- **`apply_table_ops`의 `delete_table` 여러 건은 역순으로.** 표 삭제는 후행
|
|
23
|
+
`table_index`를 시프트한다 — 내림차순으로 배열하라(`hwpx.plan`의 정적
|
|
24
|
+
린트가 위반을 경고한다).
|
|
25
|
+
- **`apply_body_ops` 인덱스는 실행 시점 문서 순서다.** 문단 삭제/삽입 op를
|
|
26
|
+
섞으면 뒤 op의 인덱스가 밀린다 — 인덱스 기반 op는 문서 끝에서 앞으로.
|
|
27
|
+
- **저수준 `add_shape`/`add_control`은 OWPML 필수 하위 요소를 만들지 않는다.**
|
|
28
|
+
그대로 저장하면 실한컴이 문서를 거부한다(경고 `UserWarning` 하나가 유일한
|
|
29
|
+
신호). 도형은 전용 `add_line`/`add_rectangle`/`add_ellipse`를 쓰라.
|
|
30
|
+
- **텍스트를 갈아 끼우면 lineseg 캐시가 무효다.** 스플라이스 계열은 편집 문단의
|
|
31
|
+
`<hp:linesegarray>`를 스스로 떨군다(안 떨구면 한컴이 옛 줄 자리에 새 글자를
|
|
32
|
+
그려 겹침이 생긴다). 직접 XML을 만질 때도 같은 규칙을 지켜야 한다.
|
|
33
|
+
|
|
34
|
+
## 검증·오라클
|
|
35
|
+
|
|
36
|
+
- **렌더 검증 없이 "렌더된다"고 주장하지 말 것.** core는 렌더 백엔드를 동봉하지
|
|
37
|
+
않는다(`RenderBackend` 주입 seam, 기본은 Unavailable). `render_checked=False,
|
|
38
|
+
ok=True`는 "구조만 봤다"는 정직 degrade이지 시각 합격이 아니다. 시각 합격이
|
|
39
|
+
필요하면 `require=True`(fail-closed) + 실한컴 오라클이 있는 환경에서.
|
|
40
|
+
- **`visual_complete_status="unverified"`는 합격이 아니다.** 렌더가 돌지 않은
|
|
41
|
+
것이며, 어떤 경로도 이를 pass로 승격하지 않는다.
|
|
42
|
+
- **기본 테스트 스위트에서 라이브 한컴 렌더를 켜지 말 것.** 오라클은 환경
|
|
43
|
+
의존이라 flake의 근원이다 — 라이브 렌더는 opt-in 게이트로만.
|
|
44
|
+
|
|
45
|
+
## 문서 구조
|
|
46
|
+
|
|
47
|
+
- **네이티브 목차는 `dirty="1"`이 재계산 트리거다.** 목차 삽입/문서 변경 후
|
|
48
|
+
`mark_toc_dirty`를 잊으면 한컴이 옛 페이지 번호를 그대로 보여준다. dirty
|
|
49
|
+
재생성 직후 같은 세션에서 export하면 한컴이 크래시할 수 있다(refresh와
|
|
50
|
+
render는 세션 분리).
|
|
51
|
+
- **본문 문단이 스타일 0(바탕글)이면 목차에 수집될 수 있다.** 한컴 목차 수집은
|
|
52
|
+
스타일 기반이다 — 본문은 본문 스타일, 제목은 개요 스타일로.
|
|
53
|
+
- **메모 필드는 무음 수용 캐비앗이 있다.** `attach_memo_field`가 앵커를 못
|
|
54
|
+
찾아도 조용히 넘어가는 경로가 있다(recipes의 mutation-semantics 표 참조) —
|
|
55
|
+
결과 리포트의 실측 필드를 확인하라.
|
|
56
|
+
- **암호화 HWPX·HWP5 바이너리는 열리지 않는 게 정상이다.** 각각 파싱 예외·
|
|
57
|
+
`BadZipFile`로 fail-closed 거부한다. 우회 API는 없다.
|
|
58
|
+
|
|
59
|
+
## 계획 실행기(hwpx.plan) 자체의 정직 범위
|
|
60
|
+
|
|
61
|
+
- **저널은 재개(resume) 계약이 아니다.** 진단용 JSONL이다. 부분 재실행은
|
|
62
|
+
지원하지 않는다 — 실패하면 계획을 고쳐 처음부터 다시 실행하라(실패 시 파일
|
|
63
|
+
무접촉이므로 안전하다).
|
|
64
|
+
- **`journalPath`는 실패 시에도 쓰이는 유일한 파일 표면이다.** 문서 파일의
|
|
65
|
+
무접촉 보장과 별개다(원하지 않으면 지정하지 말 것).
|
|
66
|
+
- **step `args`의 중첩 op 어휘는 스키마가 깊게 검증하지 않는다.** 실행-시
|
|
67
|
+
해당 op의 fail-closed 검증이 진실 원천이다 — dry_run으로 전체 체인을 미리
|
|
68
|
+
실행해 transcript를 확인하는 것이 가장 확실한 선검증이다.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# 편집 의미론 — 무엇이 돌아오고, 어떻게 실패하고, 다시 실행하면 어떻게 되나
|
|
2
|
+
|
|
3
|
+
stable 편집 표면의 계약을 한 곳에 모았다. 아래 표의 실패 모드와 재실행
|
|
4
|
+
성질은 전부 이 저장소의 테스트·실측으로 확인된 동작이다. 보존 등급 어휘는
|
|
5
|
+
[안전한 쓰기 계약](safe-write-contract.md), stable/experimental 구분은
|
|
6
|
+
[stable API](stable-api.md)를 따른다.
|
|
7
|
+
|
|
8
|
+
> **Python 블록 판정:** [실행 분류 ledger](python-example-ledger.json)가 이
|
|
9
|
+
> current manual의 모든 Python 블록을 동결합니다. 이 문서의 블록은 앞 문맥의
|
|
10
|
+
> `document`가 필요한 조각입니다.
|
|
11
|
+
|
|
12
|
+
## 핵심 편집 호출 계약
|
|
13
|
+
|
|
14
|
+
| 호출 | 반환 | 대표 실패 모드 | 다시 실행하면 |
|
|
15
|
+
|---|---|---|---|
|
|
16
|
+
| `add_paragraph(text)` | `HwpxOxmlParagraph` | 사실상 없음 | 문단이 하나 더 추가된다(append, 비멱등) |
|
|
17
|
+
| `remove_paragraph(p)` | `None` | 섹션의 마지막 단락 삭제 시 `ValueError` | 이미 제거된 문단이면 조용히 무시된다(무해) |
|
|
18
|
+
| `add_table(rows, cols)` | `HwpxOxmlTable` | 사실상 없음 | 표가 하나 더 추가된다(비멱등) |
|
|
19
|
+
| `table.set_cell_text(r, c, text)` | `FitResult \| None` | 범위 밖 좌표는 `IndexError` (`exceed table bounds`) | 같은 값이면 결과 동일(수렴) |
|
|
20
|
+
| `replace_text_in_runs(search, repl)` | `int` (치환 수) | 빈 `search`는 `ValueError` | 치환할 것이 없으면 `0` — 1회차 후 수렴 |
|
|
21
|
+
| `add_memo_with_anchor(...)` | `(memo, paragraph, field_id)` 튜플 | 아래 캐비앗 참고 | 메모가 하나 더 붙는다(비멱등) |
|
|
22
|
+
| `add_footnote(text, paragraph)` | `HwpxOxmlNote` | 사실상 없음 | 각주가 하나 더 붙는다(비멱등) |
|
|
23
|
+
|
|
24
|
+
"사실상 없음"은 정상 인자에서 실패 경로가 없다는 뜻이다 — 타입이 어긋난
|
|
25
|
+
인자는 여느 파이썬 API처럼 `TypeError` 계열로 즉시 드러난다.
|
|
26
|
+
|
|
27
|
+
## 저장 의미론
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
report = document.save_to_path("out.hwpx", return_report=True)
|
|
31
|
+
print(report.actual_mode)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- `save_to_path()`는 원자적이다: 임시 파일에 쓴 뒤 rename하고, editor-open
|
|
35
|
+
안전 검증을 통과한 결과만 대상 경로를 교체한다.
|
|
36
|
+
- 요청한 보존 등급(`mode="patch"` 등)을 지킬 수 없으면
|
|
37
|
+
`PreservationDowngradeError`로 실패하고 **아무것도 쓰지 않는다**
|
|
38
|
+
(fail-closed). 자동 다운그레이드는 없다.
|
|
39
|
+
- `return_report=True`는 `MutationReport`를 돌려준다 — 실제 저장 모드,
|
|
40
|
+
손대지 않은 파트의 바이트 보존 검증 결과까지. 스키마와 전체 규칙은
|
|
41
|
+
[안전한 쓰기 계약](safe-write-contract.md).
|
|
42
|
+
- **직렬화는 결정론적이다**: 같은 문서 상태에서 `to_bytes()`를 두 번 부르면
|
|
43
|
+
바이트가 동일하다(실측). diff·해시 기반 파이프라인에 안전하다.
|
|
44
|
+
|
|
45
|
+
## 알아둘 캐비앗 (정직 고지)
|
|
46
|
+
|
|
47
|
+
- `add_memo_with_anchor(memo_shape_id_ref=...)`는 참조가 실재하는 메모
|
|
48
|
+
모양인지 **검증하지 않고 조용히 수용한다**. 존재하지 않는 ID를 넣으면
|
|
49
|
+
저장은 되지만 편집기 표시가 어긋날 수 있다. `document.memo_shapes`로
|
|
50
|
+
실재 ID를 확인하고 쓰는 것을 권장한다.
|
|
51
|
+
- `add_*` 계열은 전부 append 의미론이다. "없으면 추가"가 필요하면 먼저
|
|
52
|
+
{doc}`recipes-traversal`의 순회로 존재 여부를 확인하라.
|
|
53
|
+
- 편집은 저장 전까지 메모리에만 있다. 저장 경로가 곧 커밋이다.
|
|
54
|
+
|
|
55
|
+
## 다음 단계
|
|
56
|
+
|
|
57
|
+
- 문서에서 원하는 것을 꺼내는 법 → {doc}`recipes-traversal`
|
|
58
|
+
- 보존 등급·영수증 스키마 → [안전한 쓰기 계약](safe-write-contract.md)
|
|
59
|
+
- 표면 안정성 구분(stable/experimental) → [stable API](stable-api.md)
|