python-hwpx 5.5.0__tar.gz → 5.7.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.5.0/src/python_hwpx.egg-info → python_hwpx-5.7.0}/PKG-INFO +1 -1
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/pyproject.toml +15 -2
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/__init__.py +12 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/_document/fields.py +115 -0
- python_hwpx-5.7.0/src/hwpx/capabilities.py +466 -0
- python_hwpx-5.7.0/src/hwpx/data/contract_docs/known-traps.md +68 -0
- python_hwpx-5.7.0/src/hwpx/data/contract_docs/mutation-semantics.md +59 -0
- python_hwpx-5.7.0/src/hwpx/data/contract_docs/recipes-traversal.md +115 -0
- python_hwpx-5.7.0/src/hwpx/data/contract_docs/support-matrix.md +61 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/document.py +62 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/experimental.py +20 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/paragraph.py +85 -0
- python_hwpx-5.7.0/src/hwpx/plan/__init__.py +48 -0
- python_hwpx-5.7.0/src/hwpx/plan/_execute.py +282 -0
- python_hwpx-5.7.0/src/hwpx/plan/_model.py +269 -0
- python_hwpx-5.7.0/src/hwpx/plan/_schema.py +127 -0
- python_hwpx-5.7.0/src/hwpx/plan/_validate.py +174 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0/src/python_hwpx.egg-info}/PKG-INFO +1 -1
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/python_hwpx.egg-info/SOURCES.txt +14 -0
- python_hwpx-5.7.0/tests/test_capabilities_surface.py +137 -0
- python_hwpx-5.7.0/tests/test_check_box_authoring.py +154 -0
- python_hwpx-5.7.0/tests/test_contract_docs_sync.py +41 -0
- python_hwpx-5.7.0/tests/test_edit_plan.py +356 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_oxml_modularization.py +5 -4
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_product_boundary.py +2 -1
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_public_artifact_hygiene.py +1 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_stable_surface.py +3 -2
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/LICENSE +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/NOTICE +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/README.md +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/setup.cfg +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/_document/__init__.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/_document/_units.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/_document/layout.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/_document/media.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/_document/memos.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/_document/persistence.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/_document/shapes.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/_document/tracked.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/body_patch.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/data/Skeleton.hwpx +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/equation/__init__.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/equation/authoring.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/equation/eqedit.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/equation/mathml.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/equation/render.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/equation/tokens.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/errors.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/form_fit/__init__.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/form_fit/apply.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/form_fit/engine.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/form_fit/measure.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/form_fit/policy.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/form_fit/report.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/ingest/__init__.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/ingest/base.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/ingest/hwpx_converter.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/layout/__init__.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/layout/lint.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/layout/report.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/mutation_report.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/opc/package.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/opc/relationships.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/opc/security.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/opc/xml_utils.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/__init__.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/_document_impl.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/_document_primitives.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/body.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/canonical_defaults.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/color.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/common.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/document.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/document_parts.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/header.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/header_part.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/memo.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/namespaces.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/numbering.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/objects.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/parser.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/run.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/schema.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/section.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/section_format.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/section_story.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/simple_parts.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/table.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/oxml/utils.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/package.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/patch.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/py.typed +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/quality/__init__.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/quality/ledger.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/quality/policy.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/quality/rendering.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/quality/report.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/quality/save_pipeline.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/table_patch.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/templates.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/__init__.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/_schemas/header.xsd +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/_schemas/section.xsd +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/archive_cli.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/doc_diff.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/document_viewer.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/exporter.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/generic_inventory.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/id_integrity.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/idempotence.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/ir_equality.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/layout_preview.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/mail_merge.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/markdown_export.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/object_finder.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/package_reconcile.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/package_validator.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/page_guard.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/read_fidelity.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/recover.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/redline.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/repair.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/report_utils.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/roundtrip_diff.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/table_cleanup.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/table_navigation.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/template_analyzer.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/text_extract_cli.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/text_extractor.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/toc_author.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/toc_fidelity.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/hwpx/tools/validator.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/python_hwpx.egg-info/entry_points.txt +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/python_hwpx.egg-info/requires.txt +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/src/python_hwpx.egg-info/top_level.txt +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_add_control.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_adoption_metrics_snapshot.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_authoring_defaults.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_authoring_quality_corpus.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_blind_eval_fixture.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_byte_patch_identity.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_cell_line_spacing.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_chart_authoring.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_comment_node_robustness.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_corpus_read_fidelity.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_coverage_promotion.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_coverage_targets.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_deviations_registry.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_doc_diff.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_document_context_manager.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_document_facade_surface.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_document_formatting.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_document_ops_runtime_boundary.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_document_save_api.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_document_viewer.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_documentation_code_fences.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_equation_authoring.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_equation_converter.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_equation_render.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_error_messages.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_evalplan_core_primitives.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_existing_document_format_editing.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_form_field_authoring.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_form_fields.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_form_fill_core_primitives.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_form_fit.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_form_fit_integration.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_fuzz_catalog_derive_expected.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_fuzz_loop.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_fuzz_regressions.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_gap_closure_tools.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_golden_api.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_hp_tab_support.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_hwpxlib_corpus_read.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_id_generator_range.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_image_object_workflow.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_ingest.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_inline_models.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_integration_hwpx_compatibility.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_integration_roundtrip.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_layout_cache_scope.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_layout_lint.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_layout_preview.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_library_boundary_closure.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_llms_txt.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_mail_merge_xlsx.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_markdown_export.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_memo_and_style_editing.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_moved_surface_hints.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_mutation_report.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_namespace_handling.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_new_features.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_note_authoring_contract.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_opc_package.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_open_safety_corpus.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_oxml_parsing.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_packaging_license_metadata.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_packaging_py_typed.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_paragraph_keep_together.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_paragraph_section_management.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_public_runtime_boundary.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_read_fidelity.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_recover_broken_zip.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_redline_authoring.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_redline_verify.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_release_workflow_safety.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_repair_repack.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_report_utils.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_repr_snapshots.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_roundtrip_fidelity.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_row_heights.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_save_pipeline.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_section_headers.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_set_paragraph_format_keep.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_shape_geometry_contract.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_skeleton_template_ids.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_split_cell.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_split_merged_cell.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_table_cleanup.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_table_navigation.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_table_patch.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_table_patch_dryrun.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_table_patch_m105.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_tables_default_border.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_template_analyzer_enrichment.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_text_extractor_annotations.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_toc_author.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_toc_fidelity.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_validation_severity.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.0}/tests/test_validator_comment_nodes.py +0 -0
- {python_hwpx-5.5.0 → python_hwpx-5.7.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.7.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
|
|
@@ -386,6 +386,121 @@ def add_form_field(
|
|
|
386
386
|
)
|
|
387
387
|
|
|
388
388
|
|
|
389
|
+
_CHECK_BOX_TAG = f"{_HP}checkBtn"
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _iter_check_boxes(doc: "HwpxDocument") -> list[dict[str, Any]]:
|
|
393
|
+
"""Every ``<hp:checkBtn>`` in document order, with its owning element."""
|
|
394
|
+
|
|
395
|
+
found: list[dict[str, Any]] = []
|
|
396
|
+
index = 0
|
|
397
|
+
for section_index, section in enumerate(doc.sections):
|
|
398
|
+
for element in section.element.iter(_CHECK_BOX_TAG):
|
|
399
|
+
found.append(
|
|
400
|
+
{
|
|
401
|
+
"index": index,
|
|
402
|
+
"sectionIndex": section_index,
|
|
403
|
+
"name": element.get("name", ""),
|
|
404
|
+
"caption": element.get("caption", ""),
|
|
405
|
+
"checked": element.get("value", "").upper() == "CHECKED",
|
|
406
|
+
"value": element.get("value", ""),
|
|
407
|
+
"_element": element,
|
|
408
|
+
"_section": section,
|
|
409
|
+
}
|
|
410
|
+
)
|
|
411
|
+
index += 1
|
|
412
|
+
return found
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def list_check_boxes(doc: "HwpxDocument") -> list[dict[str, Any]]:
|
|
416
|
+
"""Return check-box form objects in document order."""
|
|
417
|
+
|
|
418
|
+
return [
|
|
419
|
+
{key: value for key, value in match.items() if not key.startswith("_")}
|
|
420
|
+
for match in _iter_check_boxes(doc)
|
|
421
|
+
]
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def add_check_box(
|
|
425
|
+
doc: "HwpxDocument",
|
|
426
|
+
caption: str,
|
|
427
|
+
*,
|
|
428
|
+
checked: bool = False,
|
|
429
|
+
name: str | None = None,
|
|
430
|
+
paragraph: Any | None = None,
|
|
431
|
+
section: Any | None = None,
|
|
432
|
+
section_index: int | None = None,
|
|
433
|
+
) -> dict[str, Any]:
|
|
434
|
+
"""Create a check-box form object and return its payload.
|
|
435
|
+
|
|
436
|
+
The emitted XML follows the check-box contract measured against real
|
|
437
|
+
Hancom (specs/060): ``value`` selects ☑/□ and the ``<hp:formCharPr>`` child
|
|
438
|
+
is mandatory — without it Hancom refuses the document. The created object is
|
|
439
|
+
re-read through the standard :func:`list_check_boxes` reader, so creation
|
|
440
|
+
fails loudly if the ordinary consumer would not see it.
|
|
441
|
+
"""
|
|
442
|
+
|
|
443
|
+
if not str(caption).strip():
|
|
444
|
+
raise ValueError("check box caption must be a non-empty string")
|
|
445
|
+
if paragraph is None:
|
|
446
|
+
paragraph = doc.add_paragraph(
|
|
447
|
+
"", section=section, section_index=section_index, include_run=False,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
control = paragraph.add_check_box(caption, checked=checked, name=name)
|
|
451
|
+
_clear_form_field_layout_cache(paragraph.element)
|
|
452
|
+
|
|
453
|
+
created_name = control.element.get("name", "")
|
|
454
|
+
for match in _iter_check_boxes(doc):
|
|
455
|
+
if match["_element"] is control.element:
|
|
456
|
+
return {
|
|
457
|
+
key: value for key, value in match.items() if not key.startswith("_")
|
|
458
|
+
}
|
|
459
|
+
raise RuntimeError(
|
|
460
|
+
f"created check box {created_name!r} was not recognized by the standard reader"
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def set_check_box(
|
|
465
|
+
doc: "HwpxDocument",
|
|
466
|
+
checked: bool,
|
|
467
|
+
*,
|
|
468
|
+
index: int | None = None,
|
|
469
|
+
name: str | None = None,
|
|
470
|
+
) -> dict[str, Any]:
|
|
471
|
+
"""Set a check box's state, selecting it by ``index`` or ``name``.
|
|
472
|
+
|
|
473
|
+
Exactly one selector is required — an ambiguous or missing selector is a
|
|
474
|
+
typed refusal, never a guess.
|
|
475
|
+
"""
|
|
476
|
+
|
|
477
|
+
if (index is None) == (name is None):
|
|
478
|
+
raise ValueError("provide exactly one of index or name")
|
|
479
|
+
matches = _iter_check_boxes(doc)
|
|
480
|
+
if index is not None:
|
|
481
|
+
chosen = [m for m in matches if m["index"] == index]
|
|
482
|
+
if not chosen:
|
|
483
|
+
raise ValueError(f"check box index not found: {index}")
|
|
484
|
+
else:
|
|
485
|
+
wanted = str(name).strip()
|
|
486
|
+
chosen = [m for m in matches if m["name"] == wanted]
|
|
487
|
+
if not chosen:
|
|
488
|
+
raise ValueError(f"check box name not found: {wanted}")
|
|
489
|
+
if len(chosen) > 1:
|
|
490
|
+
raise ValueError(
|
|
491
|
+
f"check box name is ambiguous ({len(chosen)} matches): {wanted}"
|
|
492
|
+
)
|
|
493
|
+
match = chosen[0]
|
|
494
|
+
match["_element"].set("value", "CHECKED" if checked else "UNCHECKED")
|
|
495
|
+
match["_section"].mark_dirty()
|
|
496
|
+
return {
|
|
497
|
+
key: value
|
|
498
|
+
for key, value in {**match, "checked": bool(checked),
|
|
499
|
+
"value": "CHECKED" if checked else "UNCHECKED"}.items()
|
|
500
|
+
if not key.startswith("_")
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
|
|
389
504
|
def _select_form_field(
|
|
390
505
|
doc: "HwpxDocument",
|
|
391
506
|
matches: Sequence[dict[str, Any]],
|
|
@@ -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
|
+
]
|