python-hwpx 3.7.0__tar.gz → 3.8.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-3.7.0 → python_hwpx-3.8.0}/NOTICE +13 -1
- {python_hwpx-3.7.0/src/python_hwpx.egg-info → python_hwpx-3.8.0}/PKG-INFO +20 -5
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/README.md +15 -4
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/pyproject.toml +20 -1
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/__init__.py +6 -0
- python_hwpx-3.8.0/src/hwpx/agent/_batch_verification.py +383 -0
- python_hwpx-3.8.0/src/hwpx/agent/blueprint/replay.py +622 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/commands.py +269 -467
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/form_plan.py +167 -92
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/authoring.py +206 -113
- python_hwpx-3.8.0/src/hwpx/equation/__init__.py +46 -0
- python_hwpx-3.8.0/src/hwpx/equation/eqedit.py +317 -0
- python_hwpx-3.8.0/src/hwpx/equation/mathml.py +69 -0
- python_hwpx-3.8.0/src/hwpx/equation/render.py +83 -0
- python_hwpx-3.8.0/src/hwpx/equation/tokens.py +256 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/evalplan_fill.py +347 -173
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/fill_residue.py +73 -36
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/table.py +216 -128
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/__init__.py +8 -0
- python_hwpx-3.8.0/src/hwpx/tools/document_viewer.py +182 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/catalog.py +105 -84
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/layout_preview.py +95 -16
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/markdown_export.py +76 -54
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/object_finder.py +100 -84
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/toc_fidelity.py +87 -56
- python_hwpx-3.8.0/src/hwpx/visual/fixture_corpus.py +210 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0/src/python_hwpx.egg-info}/PKG-INFO +20 -5
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/SOURCES.txt +12 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/requires.txt +5 -0
- python_hwpx-3.8.0/tests/test_authoring_v2_block_validation.py +180 -0
- python_hwpx-3.8.0/tests/test_document_viewer.py +136 -0
- python_hwpx-3.8.0/tests/test_equation_converter.py +139 -0
- python_hwpx-3.8.0/tests/test_equation_render.py +69 -0
- python_hwpx-3.8.0/tests/test_fuzz_catalog_derive_expected.py +162 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_oxml_modularization.py +3 -1
- python_hwpx-3.7.0/src/hwpx/agent/blueprint/replay.py +0 -452
- python_hwpx-3.7.0/src/hwpx/visual/fixture_corpus.py +0 -175
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/LICENSE +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/setup.cfg +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/_units.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/fields.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/layout.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/media.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/memos.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/persistence.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/shapes.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/tracked.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/bundle.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/catalog.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/dump.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/mapping.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/model.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/native.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/catalog.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/cli.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/document.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/model.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/path.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/query.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/story.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/benchmark/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/benchmark/blind_eval.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/body_patch.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/builder/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/builder/core.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/builder/report.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/badges.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus/corpus.json +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus/meeting_summary.hwpx +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus/notice.hwpx +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus/report_table.hwpx +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/report.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/roundtrip_batch.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/runner.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/data/Skeleton.hwpx +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/_support.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/composer.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/harvest.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/plan.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profile.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/fragments/body.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/fragments/heading.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/fragments/info_table.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/fragments/title.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/profile.json +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/template.hwpx +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/fragments/body.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/fragments/heading.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/fragments/title.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/profile.json +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/template.hwpx +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/fragments/body.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/fragments/heading.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/fragments/info_table.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/fragments/title.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/profile.json +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/template.hwpx +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/fragments/body.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/fragments/heading.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/fragments/info_table.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/fragments/title.xml +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/profile.json +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/template.hwpx +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/validator.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/document.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/exam/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/exam/compose.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/exam/ir.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/exam/measure.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/exam/parser.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/exam/profile.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fill.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/apply.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/engine.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/measure.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/policy.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/report.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/seal.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/wordbox.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/formfill_quality.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/guidance_scan.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/ingest/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/ingest/base.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/ingest/hwpx_converter.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/layout/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/layout/lint.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/layout/report.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/mutation_report.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/opc/package.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/opc/relationships.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/opc/security.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/opc/xml_utils.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/_document_impl.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/_document_primitives.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/body.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/canonical_defaults.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/common.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/document.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/document_parts.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/header.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/header_part.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/memo.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/namespaces.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/numbering.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/objects.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/paragraph.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/parser.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/run.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/schema.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/section.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/section_format.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/section_story.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/simple_parts.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/utils.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/package.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/patch.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/presets/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/presets/proposal.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/py.typed +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/quality/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/quality/ledger.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/quality/policy.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/quality/report.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/quality/save_pipeline.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/table_patch.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/template_formfit.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/templates.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/_schemas/header.xsd +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/_schemas/section.xsd +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/advanced_generators.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/archive_cli.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/doc_diff.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/exporter.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/__main__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/generator.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/minimize.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/runner.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/generic_inventory.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/id_integrity.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/idempotence.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/ir_equality.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/mail_merge.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/official_lint.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/package_reconcile.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/package_validator.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/page_guard.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/pii.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/read_fidelity.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/recover.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/redline.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/repair.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/report_parser.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/report_utils.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/roundtrip_diff.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/style_profile.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/table_cleanup.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/table_compute.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/table_navigation.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/template_analyzer.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/text_extract_cli.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/text_extractor.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/toc_author.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/validator.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/__init__.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/_hancom_open_rate.ps1 +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/_refresh_hwpx_mac.applescript +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/_render_hwpx.ps1 +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/_render_hwpx_mac.applescript +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/detectors.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/diff.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/hancom_worker.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/masks.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/oracle.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/page_qa.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/qa_contracts.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/qa_metrics.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/report.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/entry_points.txt +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/top_level.txt +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_advanced_generators.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_blueprint_contracts.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_blueprint_dump.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_blueprint_replay.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_catalog.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_cli.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_commands.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_contracts.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_document.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_mixed_form.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_path_query.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_story_commands.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_authoring_native_toc.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_authoring_profile_routing.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_authoring_profile_routing_oracle.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_authoring_quality_corpus.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_authoring_render_check.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_blind_eval_fixture.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_body_patch.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_builder_core.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_builder_plan_v2.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_builder_vertical_slice.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_byte_patch_identity.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_cell_line_spacing.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_comment_node_robustness.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_conformance.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_corpus_read_fidelity.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_corpus_toc_verify.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_coverage_promotion.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_coverage_targets.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_design_builder.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_deviations_registry.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_doc_diff.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_document_context_manager.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_document_facade_surface.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_document_formatting.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_document_plan.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_document_plan_computed_fields.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_document_save_api.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_evalplan_fill.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_compose.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_compose_oracle.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_fixtures.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_ir.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_measure.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_parser.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_profile.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_existing_document_format_editing.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_fill_residue.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fields.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fill_split_run.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fit.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fit_integration.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fit_seal.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fit_seal_placement.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fit_wordbox.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_formfill_differential_driver.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_formfill_quality.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_fuzz_loop.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_fuzz_regressions.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_gap_closure_tools.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_government_report_preset.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_government_table_profile.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_guidance_scan.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_hancom_render_worker.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_hp_tab_support.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_hwpxlib_corpus_read.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_id_generator_range.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_id_integrity.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_image_object_workflow.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_ingest.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_inline_models.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_integration_hwpx_compatibility.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_integration_roundtrip.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_kordoc_absorption.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_layout_cache_scope.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_layout_lint.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_layout_preview.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_m9_p0_spike_tools.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_mail_merge_fit.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_mail_merge_table_compute.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_mail_merge_xlsx.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_markdown_export.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_memo_and_style_editing.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_mixed_form_plan.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_mutation_report.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_mutation_report_projections.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_namespace_handling.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_new_features.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_official_document_style.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_official_lint_gongmun_gate.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_official_lint_tableaware.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_opc_package.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_open_safety_corpus.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_openrate.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_oxml_parsing.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_packaging_license_metadata.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_packaging_py_typed.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_paragraph_keep_together.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_paragraph_section_management.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_pii.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_pii_leak_sweep.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_pii_structural.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_pii_wiring.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_proposal_preset.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_public_runtime_boundary.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_question_split_detector.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_read_fidelity.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_recover_broken_zip.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_redline_authoring.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_redline_verify.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_repair_repack.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_report_parser.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_report_utils.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_repr_snapshots.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_rhwp_t1_gates.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_rhwp_t2_verification.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_roundtrip_fidelity.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_row_heights.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_save_pipeline.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_save_pipeline_no_bypass.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_section_headers.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_set_paragraph_format_keep.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_skeleton_template_ids.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_split_cell.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_split_merged_cell.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_style_profile.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_table_cleanup.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_table_navigation.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_table_patch.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_table_patch_dryrun.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_table_patch_m105.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_tables_default_border.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_template_analyzer_enrichment.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_template_formfit.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_text_extractor_annotations.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_toc_author.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_toc_fidelity.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_validation_severity.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_validator_comment_nodes.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_version_metadata.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_visual_fixture_corpus.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_visual_oracle.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_visual_oracle_budget.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_visual_qa_contracts.py +0 -0
- {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_visual_qa_metrics.py +0 -0
|
@@ -29,10 +29,22 @@ reimplemented from public references, without copying source code:
|
|
|
29
29
|
from public model headers only. hwpxlib sample packages were used only as
|
|
30
30
|
structure and roundtrip fixtures.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
- OpenBapul/hml-equation-parser (Apache-2.0): HULK-style EqEdit <-> LaTeX token
|
|
33
|
+
mapping, referenced for reader-direction <hp:equation> script rendering. The
|
|
34
|
+
token maps are a clean-room re-derivation from the public EqEdit grammar; no
|
|
35
|
+
source code was copied, translated, or vendored.
|
|
36
|
+
|
|
37
|
+
These form-fill and equation references were used as behavior evidence only. The
|
|
33
38
|
implementation is a clean-room Python implementation; no source code was
|
|
34
39
|
copied, translated, vendored, or linked.
|
|
35
40
|
|
|
41
|
+
## Optional dependency: latex2mathml
|
|
42
|
+
|
|
43
|
+
The optional ``preview`` extra depends on roniemartinez/latex2mathml (MIT), used
|
|
44
|
+
to convert LaTeX to MathML for the document preview viewer. It is an unmodified
|
|
45
|
+
third-party dependency installed via pip; its MIT license travels with the
|
|
46
|
+
package.
|
|
47
|
+
|
|
36
48
|
## Test fixtures: hwpxlib sample corpus
|
|
37
49
|
|
|
38
50
|
tests/fixtures/hwpxlib_corpus/ contains .hwpx sample files vendored from
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-hwpx
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.8.0
|
|
4
4
|
Summary: 한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리
|
|
5
5
|
Author: python-hwpx Maintainers
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -27,10 +27,13 @@ Requires-Dist: pillow>=10.0; extra == "visual"
|
|
|
27
27
|
Requires-Dist: numpy>=1.26; extra == "visual"
|
|
28
28
|
Provides-Extra: xlsx
|
|
29
29
|
Requires-Dist: openpyxl>=3.1; extra == "xlsx"
|
|
30
|
+
Provides-Extra: preview
|
|
31
|
+
Requires-Dist: latex2mathml>=3.77; extra == "preview"
|
|
30
32
|
Provides-Extra: dev
|
|
31
33
|
Requires-Dist: build>=1.0; extra == "dev"
|
|
32
34
|
Requires-Dist: twine>=4.0; extra == "dev"
|
|
33
35
|
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
36
|
+
Requires-Dist: latex2mathml>=3.77; extra == "dev"
|
|
34
37
|
Provides-Extra: test
|
|
35
38
|
Requires-Dist: build>=1.0; extra == "test"
|
|
36
39
|
Requires-Dist: numpy>=1.26; extra == "test"
|
|
@@ -38,6 +41,7 @@ Requires-Dist: pillow>=10.0; extra == "test"
|
|
|
38
41
|
Requires-Dist: pytest>=7.4; extra == "test"
|
|
39
42
|
Requires-Dist: pytest-cov>=5.0; extra == "test"
|
|
40
43
|
Requires-Dist: ruff>=0.12; extra == "test"
|
|
44
|
+
Requires-Dist: latex2mathml>=3.77; extra == "test"
|
|
41
45
|
Provides-Extra: typecheck
|
|
42
46
|
Requires-Dist: mypy>=1.10; extra == "typecheck"
|
|
43
47
|
Requires-Dist: pyright>=1.1.390; extra == "typecheck"
|
|
@@ -53,6 +57,8 @@ Dynamic: license-file
|
|
|
53
57
|
<a href="https://pypi.org/project/python-hwpx/"><img src="https://img.shields.io/pypi/pyversions/python-hwpx" alt="Python"></a>
|
|
54
58
|
<a href="https://github.com/airmang/python-hwpx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License"></a>
|
|
55
59
|
<a href="https://airmang.github.io/python-hwpx/"><img src="https://img.shields.io/badge/docs-Sphinx-8CA1AF" alt="Docs"></a>
|
|
60
|
+
<a href="https://github.com/airmang/python-hwpx/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/airmang/python-hwpx/tests.yml?branch=main&label=tests" alt="Tests"></a>
|
|
61
|
+
<a href="https://airmang.github.io/python-hwpx/corpus-metrics.html"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fairmang.github.io%2Fpython-hwpx%2F_static%2Fbadge-hancom-open.json" alt="Hancom open"></a>
|
|
56
62
|
</p>
|
|
57
63
|
</p>
|
|
58
64
|
|
|
@@ -83,11 +89,10 @@ Dynamic: license-file
|
|
|
83
89
|
| 🔌 MCP 서버 | [`hwpx-mcp-server`](https://github.com/airmang/hwpx-mcp-server) | MCP 클라이언트(Claude Desktop, VS Code 등)에서 HWPX 조작 |
|
|
84
90
|
| 🎯 에이전트 스킬 | [`hwpx-plugin`](https://github.com/airmang/hwpx-plugins) | 에이전트가 HWPX를 바로 쓰게 해주는 first-party 플러그인·스킬 번들 |
|
|
85
91
|
|
|
86
|
-
`
|
|
87
|
-
|
|
88
|
-
“first-party”는 프로젝트 유지보수 관계를 뜻하며, 한컴 또는 제3자의 공식 인증을 뜻하지 않습니다.
|
|
92
|
+
`hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동
|
|
93
|
+
구성요소입니다(유지보수 관계를 뜻하며, 한컴 등 외부 기관의 인증을 뜻하지 않습니다).
|
|
89
94
|
|
|
90
|
-
현재 PyPI 공개 릴리스는 `python-hwpx 3.
|
|
95
|
+
현재 PyPI 공개 릴리스는 `python-hwpx 3.8.0`입니다. 일반
|
|
91
96
|
`pip install python-hwpx`로 이 릴리스를 설치할 수 있습니다.
|
|
92
97
|
현재 패키지 분류는 `Development Status :: 3 - Alpha`입니다. 이 분류는 API와 제품의
|
|
93
98
|
성숙도를 나타내며, 공개 버전이나 플러그인의 최소 호환 버전을 대신하지 않습니다.
|
|
@@ -109,6 +114,11 @@ Dynamic: license-file
|
|
|
109
114
|
export는 한컴 자체가 거부 — 실측 한계로 발행) + 미검증 17건
|
|
110
115
|
- wild 공개 양식 채움은 구조결함 픽스 후 **무음 서식파괴 16.7%**(판정 66조합, 못 담는 타깃은 typed 거부 35건·산출분 pass 17/28) — **낮은 숫자도 그대로 발행**하고 잔여(페이지 리플·표 shape)를 명명합니다
|
|
111
116
|
|
|
117
|
+
<p align="center">
|
|
118
|
+
<img src="https://raw.githubusercontent.com/airmang/python-hwpx/main/docs/images/redline-hancom.png" alt="python-hwpx가 남긴 변경추적과 AI 에이전트 메모가 실제 한/글에서 열린 화면" width="720">
|
|
119
|
+
</p>
|
|
120
|
+
<p align="center"><sub>python-hwpx로 작성한 변경추적(취소선·삽입)과 AI 에이전트 메모 — 실제 한/글에서 연 화면입니다.</sub></p>
|
|
121
|
+
|
|
112
122
|
> 이 숫자들은 *생성물 수용률* 축입니다(우리가 만든 파일을 실제 한컴이 받아들이는가).
|
|
113
123
|
> 문서 *파싱 recall*과는 다른 축이므로 파서 프로젝트 수치와 병치 비교하지 마세요.
|
|
114
124
|
|
|
@@ -301,6 +311,11 @@ Unsupported-but-preserved / Unsupported-and-rejected**.
|
|
|
301
311
|
|
|
302
312
|
버그 리포트, 기능 제안, PR 모두 환영합니다.
|
|
303
313
|
|
|
314
|
+
- **[help wanted 이슈](https://github.com/airmang/python-hwpx/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)** — 지금 들어오기 좋은 입구입니다. 시작 전에 이슈에 코멘트로 방향을 남겨주세요.
|
|
315
|
+
- **[마일스톤](https://github.com/airmang/python-hwpx/milestones)** — 공개 로드맵입니다. 프로젝트가 어디로 가는지 여기서 보입니다.
|
|
316
|
+
- **[Discussions](https://github.com/airmang/python-hwpx/discussions)** — 질문·아이디어는 이슈 대신 여기로.
|
|
317
|
+
- **[내부 실전 가이드](docs/internals/)** — HWPX 내부 구조가 처음이라면 여기부터. 개발 흐름은 [CONTRIBUTING.md](CONTRIBUTING.md)에 있습니다.
|
|
318
|
+
|
|
304
319
|
```bash
|
|
305
320
|
git clone https://github.com/airmang/python-hwpx.git
|
|
306
321
|
cd python-hwpx
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
<a href="https://pypi.org/project/python-hwpx/"><img src="https://img.shields.io/pypi/pyversions/python-hwpx" alt="Python"></a>
|
|
9
9
|
<a href="https://github.com/airmang/python-hwpx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License"></a>
|
|
10
10
|
<a href="https://airmang.github.io/python-hwpx/"><img src="https://img.shields.io/badge/docs-Sphinx-8CA1AF" alt="Docs"></a>
|
|
11
|
+
<a href="https://github.com/airmang/python-hwpx/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/airmang/python-hwpx/tests.yml?branch=main&label=tests" alt="Tests"></a>
|
|
12
|
+
<a href="https://airmang.github.io/python-hwpx/corpus-metrics.html"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fairmang.github.io%2Fpython-hwpx%2F_static%2Fbadge-hancom-open.json" alt="Hancom open"></a>
|
|
11
13
|
</p>
|
|
12
14
|
</p>
|
|
13
15
|
|
|
@@ -38,11 +40,10 @@
|
|
|
38
40
|
| 🔌 MCP 서버 | [`hwpx-mcp-server`](https://github.com/airmang/hwpx-mcp-server) | MCP 클라이언트(Claude Desktop, VS Code 등)에서 HWPX 조작 |
|
|
39
41
|
| 🎯 에이전트 스킬 | [`hwpx-plugin`](https://github.com/airmang/hwpx-plugins) | 에이전트가 HWPX를 바로 쓰게 해주는 first-party 플러그인·스킬 번들 |
|
|
40
42
|
|
|
41
|
-
`
|
|
42
|
-
|
|
43
|
-
“first-party”는 프로젝트 유지보수 관계를 뜻하며, 한컴 또는 제3자의 공식 인증을 뜻하지 않습니다.
|
|
43
|
+
`hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동
|
|
44
|
+
구성요소입니다(유지보수 관계를 뜻하며, 한컴 등 외부 기관의 인증을 뜻하지 않습니다).
|
|
44
45
|
|
|
45
|
-
현재 PyPI 공개 릴리스는 `python-hwpx 3.
|
|
46
|
+
현재 PyPI 공개 릴리스는 `python-hwpx 3.8.0`입니다. 일반
|
|
46
47
|
`pip install python-hwpx`로 이 릴리스를 설치할 수 있습니다.
|
|
47
48
|
현재 패키지 분류는 `Development Status :: 3 - Alpha`입니다. 이 분류는 API와 제품의
|
|
48
49
|
성숙도를 나타내며, 공개 버전이나 플러그인의 최소 호환 버전을 대신하지 않습니다.
|
|
@@ -64,6 +65,11 @@
|
|
|
64
65
|
export는 한컴 자체가 거부 — 실측 한계로 발행) + 미검증 17건
|
|
65
66
|
- wild 공개 양식 채움은 구조결함 픽스 후 **무음 서식파괴 16.7%**(판정 66조합, 못 담는 타깃은 typed 거부 35건·산출분 pass 17/28) — **낮은 숫자도 그대로 발행**하고 잔여(페이지 리플·표 shape)를 명명합니다
|
|
66
67
|
|
|
68
|
+
<p align="center">
|
|
69
|
+
<img src="https://raw.githubusercontent.com/airmang/python-hwpx/main/docs/images/redline-hancom.png" alt="python-hwpx가 남긴 변경추적과 AI 에이전트 메모가 실제 한/글에서 열린 화면" width="720">
|
|
70
|
+
</p>
|
|
71
|
+
<p align="center"><sub>python-hwpx로 작성한 변경추적(취소선·삽입)과 AI 에이전트 메모 — 실제 한/글에서 연 화면입니다.</sub></p>
|
|
72
|
+
|
|
67
73
|
> 이 숫자들은 *생성물 수용률* 축입니다(우리가 만든 파일을 실제 한컴이 받아들이는가).
|
|
68
74
|
> 문서 *파싱 recall*과는 다른 축이므로 파서 프로젝트 수치와 병치 비교하지 마세요.
|
|
69
75
|
|
|
@@ -256,6 +262,11 @@ Unsupported-but-preserved / Unsupported-and-rejected**.
|
|
|
256
262
|
|
|
257
263
|
버그 리포트, 기능 제안, PR 모두 환영합니다.
|
|
258
264
|
|
|
265
|
+
- **[help wanted 이슈](https://github.com/airmang/python-hwpx/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)** — 지금 들어오기 좋은 입구입니다. 시작 전에 이슈에 코멘트로 방향을 남겨주세요.
|
|
266
|
+
- **[마일스톤](https://github.com/airmang/python-hwpx/milestones)** — 공개 로드맵입니다. 프로젝트가 어디로 가는지 여기서 보입니다.
|
|
267
|
+
- **[Discussions](https://github.com/airmang/python-hwpx/discussions)** — 질문·아이디어는 이슈 대신 여기로.
|
|
268
|
+
- **[내부 실전 가이드](docs/internals/)** — HWPX 내부 구조가 처음이라면 여기부터. 개발 흐름은 [CONTRIBUTING.md](CONTRIBUTING.md)에 있습니다.
|
|
269
|
+
|
|
259
270
|
```bash
|
|
260
271
|
git clone https://github.com/airmang/python-hwpx.git
|
|
261
272
|
cd python-hwpx
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-hwpx"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.8.0"
|
|
8
8
|
description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -38,10 +38,16 @@ visual = [
|
|
|
38
38
|
xlsx = [
|
|
39
39
|
"openpyxl>=3.1",
|
|
40
40
|
]
|
|
41
|
+
# Document preview viewer equation rendering: LaTeX -> MathML. Optional; without
|
|
42
|
+
# it the preview fails closed to a LaTeX code block instead of dropping equations.
|
|
43
|
+
preview = [
|
|
44
|
+
"latex2mathml>=3.77",
|
|
45
|
+
]
|
|
41
46
|
dev = [
|
|
42
47
|
"build>=1.0",
|
|
43
48
|
"twine>=4.0",
|
|
44
49
|
"pytest>=7.4",
|
|
50
|
+
"latex2mathml>=3.77",
|
|
45
51
|
]
|
|
46
52
|
test = [
|
|
47
53
|
"build>=1.0",
|
|
@@ -50,6 +56,7 @@ test = [
|
|
|
50
56
|
"pytest>=7.4",
|
|
51
57
|
"pytest-cov>=5.0",
|
|
52
58
|
"ruff>=0.12",
|
|
59
|
+
"latex2mathml>=3.77",
|
|
53
60
|
]
|
|
54
61
|
typecheck = [
|
|
55
62
|
"mypy>=1.10",
|
|
@@ -110,6 +117,12 @@ follow_imports = "skip"
|
|
|
110
117
|
files = [
|
|
111
118
|
"src/hwpx/document.py",
|
|
112
119
|
"src/hwpx/mutation_report.py",
|
|
120
|
+
"src/hwpx/equation/__init__.py",
|
|
121
|
+
"src/hwpx/equation/tokens.py",
|
|
122
|
+
"src/hwpx/equation/eqedit.py",
|
|
123
|
+
"src/hwpx/equation/mathml.py",
|
|
124
|
+
"src/hwpx/equation/render.py",
|
|
125
|
+
"src/hwpx/tools/document_viewer.py",
|
|
113
126
|
"src/hwpx/_document/_units.py",
|
|
114
127
|
"src/hwpx/_document/fields.py",
|
|
115
128
|
"src/hwpx/_document/layout.py",
|
|
@@ -158,6 +171,12 @@ ignore_missing_imports = true
|
|
|
158
171
|
include = [
|
|
159
172
|
"src/hwpx/document.py",
|
|
160
173
|
"src/hwpx/mutation_report.py",
|
|
174
|
+
"src/hwpx/equation/__init__.py",
|
|
175
|
+
"src/hwpx/equation/tokens.py",
|
|
176
|
+
"src/hwpx/equation/eqedit.py",
|
|
177
|
+
"src/hwpx/equation/mathml.py",
|
|
178
|
+
"src/hwpx/equation/render.py",
|
|
179
|
+
"src/hwpx/tools/document_viewer.py",
|
|
161
180
|
"src/hwpx/_document/_units.py",
|
|
162
181
|
"src/hwpx/_document/fields.py",
|
|
163
182
|
"src/hwpx/_document/layout.py",
|
|
@@ -75,6 +75,10 @@ from .tools.layout_preview import (
|
|
|
75
75
|
PreviewPage,
|
|
76
76
|
render_layout_preview,
|
|
77
77
|
)
|
|
78
|
+
from .tools.document_viewer import (
|
|
79
|
+
DocumentViewer,
|
|
80
|
+
render_document_viewer,
|
|
81
|
+
)
|
|
78
82
|
from .ingest import (
|
|
79
83
|
ConversionAttempt,
|
|
80
84
|
DocumentConverter,
|
|
@@ -206,6 +210,8 @@ __all__ = [
|
|
|
206
210
|
"validate_package",
|
|
207
211
|
"paragraph_patch",
|
|
208
212
|
"render_layout_preview",
|
|
213
|
+
"render_document_viewer",
|
|
214
|
+
"DocumentViewer",
|
|
209
215
|
"register_template",
|
|
210
216
|
"table_compute",
|
|
211
217
|
]
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Verification/orchestration primitives for :func:`hwpx.agent.commands.apply_document_commands`.
|
|
3
|
+
|
|
4
|
+
Split out of ``commands.py`` (S-088 P3, agent/commands.py:apply_document_commands
|
|
5
|
+
complexity decomposition) to keep that module under its enforced line-count
|
|
6
|
+
ratchet. Everything here is self-contained -- it depends only on
|
|
7
|
+
``hwpx.agent.model``/``hwpx.agent.document`` and absolute ``hwpx`` imports, never
|
|
8
|
+
on ``commands.py`` itself, so ``commands.py`` can import from this module
|
|
9
|
+
without creating an import cycle.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import hashlib
|
|
15
|
+
import json
|
|
16
|
+
from collections.abc import Callable, Mapping, MutableMapping
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from hwpx.document import HwpxDocument
|
|
21
|
+
from hwpx.mutation_report import member_diff_bytes
|
|
22
|
+
from hwpx.quality import QualityPolicy, SavePipeline
|
|
23
|
+
from hwpx.tools.package_validator import validate_editor_open_safety
|
|
24
|
+
|
|
25
|
+
from .document import HwpxAgentDocument
|
|
26
|
+
from .model import AgentBatchResult, AgentContractError, AgentError
|
|
27
|
+
|
|
28
|
+
_EMPTY_REVISION = "sha256:" + hashlib.sha256(b"").hexdigest()
|
|
29
|
+
|
|
30
|
+
IdempotencyStore = MutableMapping[str, Any]
|
|
31
|
+
FaultInjector = Callable[[str, int | None], None]
|
|
32
|
+
DomainVerifier = Callable[[bytes, Mapping[str, Any]], Mapping[str, Any]]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _revision(data: bytes) -> str:
|
|
36
|
+
return "sha256:" + hashlib.sha256(data).hexdigest()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _error_from_exception(exc: BaseException, *, target: str | None = None) -> AgentError:
|
|
40
|
+
if isinstance(exc, AgentContractError):
|
|
41
|
+
code = exc.code
|
|
42
|
+
message = str(exc)
|
|
43
|
+
target = exc.target or target
|
|
44
|
+
elif isinstance(exc, (KeyError, IndexError, TypeError, ValueError)):
|
|
45
|
+
code = "invariant_violation"
|
|
46
|
+
message = str(exc) or type(exc).__name__
|
|
47
|
+
else:
|
|
48
|
+
code = "verification_failed"
|
|
49
|
+
message = f"{type(exc).__name__}: {exc}"
|
|
50
|
+
recoverability = "retryable" if code in {"stale_revision", "idempotency_conflict"} else "terminal"
|
|
51
|
+
if code in {"ambiguous_target", "volatile_target", "unsupported_content"}:
|
|
52
|
+
recoverability = "needs-review"
|
|
53
|
+
suggestions = {
|
|
54
|
+
"stale_revision": "Read the document again and retry with its current revision.",
|
|
55
|
+
"ambiguous_target": "Resolve a unique canonical path before mutation.",
|
|
56
|
+
"volatile_target": "Refresh the positional path from the current document revision.",
|
|
57
|
+
"unknown_property": "Use the node capability catalog to choose an editable property.",
|
|
58
|
+
"incompatible_parent": "Choose a compatible semantic parent and position.",
|
|
59
|
+
"verification_failed": "Inspect verificationReport and do not publish the candidate.",
|
|
60
|
+
}
|
|
61
|
+
return AgentError(
|
|
62
|
+
code=code,
|
|
63
|
+
message=message[:4096],
|
|
64
|
+
target=target,
|
|
65
|
+
recoverability=recoverability,
|
|
66
|
+
suggestion=suggestions.get(code),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _failure_result(
|
|
71
|
+
*,
|
|
72
|
+
exc: BaseException,
|
|
73
|
+
batch: Mapping[str, Any] | None,
|
|
74
|
+
input_revision: str = _EMPTY_REVISION,
|
|
75
|
+
command_results: list[Mapping[str, Any]] | None = None,
|
|
76
|
+
verification: Mapping[str, Any] | None = None,
|
|
77
|
+
) -> AgentBatchResult:
|
|
78
|
+
raw = batch or {}
|
|
79
|
+
output = raw.get("output") if isinstance(raw, Mapping) else None
|
|
80
|
+
output_filename = ""
|
|
81
|
+
if isinstance(output, Mapping):
|
|
82
|
+
output_filename = str(output.get("filename") or "")
|
|
83
|
+
return AgentBatchResult(
|
|
84
|
+
ok=False,
|
|
85
|
+
rolled_back=True,
|
|
86
|
+
dry_run=bool(raw.get("dryRun", False)),
|
|
87
|
+
input_revision=input_revision,
|
|
88
|
+
document_revision=input_revision,
|
|
89
|
+
output_filename=output_filename,
|
|
90
|
+
command_results=tuple(command_results or ()),
|
|
91
|
+
verification_report=dict(verification or {}),
|
|
92
|
+
error=_error_from_exception(exc),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _quality_policy(value: str | Mapping[str, Any] | None) -> QualityPolicy:
|
|
97
|
+
if value is None or value == "transparent":
|
|
98
|
+
return QualityPolicy.transparent()
|
|
99
|
+
if value == "strict":
|
|
100
|
+
return QualityPolicy.strict()
|
|
101
|
+
if not isinstance(value, Mapping): # already contract-validated
|
|
102
|
+
raise AgentContractError("invalid_syntax", "quality is invalid", target="batch.quality")
|
|
103
|
+
mode = value.get("mode", "transparent")
|
|
104
|
+
policy = QualityPolicy.strict() if mode == "strict" else QualityPolicy.transparent()
|
|
105
|
+
mapping = {
|
|
106
|
+
"renderCheck": "render_check",
|
|
107
|
+
"xsdMode": "xsd_mode",
|
|
108
|
+
"overflowPolicy": "overflow_policy",
|
|
109
|
+
"layoutLint": "layout_lint",
|
|
110
|
+
"preserveUnmodifiedParts": "preserve_unmodified_parts",
|
|
111
|
+
"requireReferenceIntegrity": "require_reference_integrity",
|
|
112
|
+
}
|
|
113
|
+
changes = {mapping[name]: setting for name, setting in value.items() if name in mapping}
|
|
114
|
+
return policy.with_(**changes)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _member_diff(before: bytes, after: bytes) -> dict[str, Any]:
|
|
118
|
+
# Shared with the Safe Write Contract's MutationReport spine: one uncompressed
|
|
119
|
+
# member comparison, one home for the diff shape (mutation_report.py).
|
|
120
|
+
return member_diff_bytes(before, after)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _verify_header_story_candidates(
|
|
124
|
+
candidate_data: bytes,
|
|
125
|
+
candidate_revision: str,
|
|
126
|
+
expectations: Mapping[str, Mapping[str, str]],
|
|
127
|
+
) -> dict[str, Any]:
|
|
128
|
+
receipts: list[dict[str, Any]] = []
|
|
129
|
+
if expectations:
|
|
130
|
+
with HwpxDocument.open(candidate_data) as reopened:
|
|
131
|
+
view = HwpxAgentDocument.from_document(
|
|
132
|
+
reopened, revision=candidate_revision
|
|
133
|
+
)
|
|
134
|
+
for expectation in expectations.values():
|
|
135
|
+
path = expectation["path"]
|
|
136
|
+
binding = view._resolve_header_story(path)
|
|
137
|
+
if (
|
|
138
|
+
binding.stable_id != expectation["stableId"]
|
|
139
|
+
or binding.page_type != expectation["pageType"]
|
|
140
|
+
or binding.text != expectation["text"]
|
|
141
|
+
):
|
|
142
|
+
raise AgentContractError(
|
|
143
|
+
"verification_failed",
|
|
144
|
+
"reopened header story does not match the committed binding",
|
|
145
|
+
target=path,
|
|
146
|
+
)
|
|
147
|
+
receipts.append(
|
|
148
|
+
{
|
|
149
|
+
"commandId": expectation["commandId"],
|
|
150
|
+
"path": binding.path,
|
|
151
|
+
"stableId": binding.stable_id,
|
|
152
|
+
"pageType": binding.page_type,
|
|
153
|
+
"textMatched": True,
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
return {
|
|
157
|
+
"schemaVersion": "hwpx.agent-story-preservation/v1",
|
|
158
|
+
"ok": True,
|
|
159
|
+
"storyCount": len(receipts),
|
|
160
|
+
"stories": receipts,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _request_hash(batch: Mapping[str, Any]) -> str:
|
|
165
|
+
payload = json.dumps(
|
|
166
|
+
batch,
|
|
167
|
+
ensure_ascii=False,
|
|
168
|
+
sort_keys=True,
|
|
169
|
+
separators=(",", ":"),
|
|
170
|
+
).encode("utf-8")
|
|
171
|
+
return _revision(payload)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _call_fault(injector: FaultInjector | None, stage: str, index: int | None = None) -> None:
|
|
175
|
+
if injector is not None:
|
|
176
|
+
injector(stage, index)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _apply_commands_idempotency_lookup(
|
|
180
|
+
verification: dict[str, Any],
|
|
181
|
+
idempotency_store: IdempotencyStore | None,
|
|
182
|
+
*,
|
|
183
|
+
key: str | None,
|
|
184
|
+
request_hash: str,
|
|
185
|
+
) -> AgentBatchResult | None:
|
|
186
|
+
verification["idempotency"] = {
|
|
187
|
+
"keyProvided": key is not None,
|
|
188
|
+
"requestHash": request_hash,
|
|
189
|
+
"replayed": False,
|
|
190
|
+
"store": "caller-owned" if idempotency_store is not None else "none",
|
|
191
|
+
}
|
|
192
|
+
if key is not None and idempotency_store is not None and key in idempotency_store:
|
|
193
|
+
cached = idempotency_store[key]
|
|
194
|
+
if not isinstance(cached, Mapping) or cached.get("requestHash") != request_hash:
|
|
195
|
+
raise AgentContractError(
|
|
196
|
+
"idempotency_conflict",
|
|
197
|
+
"idempotency key was used for a different request",
|
|
198
|
+
target="batch.idempotencyKey",
|
|
199
|
+
)
|
|
200
|
+
prior = cached.get("result")
|
|
201
|
+
if not isinstance(prior, AgentBatchResult):
|
|
202
|
+
raise AgentContractError(
|
|
203
|
+
"invariant_violation", "idempotency store contains an invalid result", target="batch.idempotencyKey"
|
|
204
|
+
)
|
|
205
|
+
replay_report = dict(prior.verification_report)
|
|
206
|
+
replay_idempotency = dict(replay_report.get("idempotency", {}))
|
|
207
|
+
replay_idempotency["replayed"] = True
|
|
208
|
+
replay_report["idempotency"] = replay_idempotency
|
|
209
|
+
return AgentBatchResult(
|
|
210
|
+
ok=prior.ok,
|
|
211
|
+
rolled_back=prior.rolled_back,
|
|
212
|
+
dry_run=prior.dry_run,
|
|
213
|
+
input_revision=prior.input_revision,
|
|
214
|
+
document_revision=prior.document_revision,
|
|
215
|
+
output_filename=prior.output_filename,
|
|
216
|
+
command_results=prior.command_results,
|
|
217
|
+
semantic_diff=prior.semantic_diff,
|
|
218
|
+
verification_report=replay_report,
|
|
219
|
+
error=prior.error,
|
|
220
|
+
)
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _validate_apply_commands_input(
|
|
225
|
+
normalized: Mapping[str, Any],
|
|
226
|
+
verification: dict[str, Any],
|
|
227
|
+
input_data: bytes,
|
|
228
|
+
input_revision: str,
|
|
229
|
+
output_path: Path,
|
|
230
|
+
) -> None:
|
|
231
|
+
verification["revision"] = {
|
|
232
|
+
"expected": normalized["expectedRevision"],
|
|
233
|
+
"actual": input_revision,
|
|
234
|
+
"matched": normalized["expectedRevision"] in {None, input_revision},
|
|
235
|
+
}
|
|
236
|
+
if normalized["expectedRevision"] not in {None, input_revision}:
|
|
237
|
+
raise AgentContractError(
|
|
238
|
+
"stale_revision", "expectedRevision does not match input bytes", target="batch.expectedRevision"
|
|
239
|
+
)
|
|
240
|
+
input_safety = validate_editor_open_safety(input_data)
|
|
241
|
+
verification["inputOpenSafety"] = input_safety.to_dict()
|
|
242
|
+
if not input_safety.ok:
|
|
243
|
+
raise AgentContractError(
|
|
244
|
+
"verification_failed",
|
|
245
|
+
"input failed package/reopen/openSafety verification",
|
|
246
|
+
target="batch.input.filename",
|
|
247
|
+
)
|
|
248
|
+
if output_path.exists() and not normalized["output"]["overwrite"] and not normalized["dryRun"]:
|
|
249
|
+
raise AgentContractError(
|
|
250
|
+
"invariant_violation", "output exists and overwrite is false", target="batch.output.filename"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _apply_commands_build_candidate_report(
|
|
255
|
+
input_data: bytes,
|
|
256
|
+
candidate_data: bytes,
|
|
257
|
+
input_revision: str,
|
|
258
|
+
semantic_changes: list[Mapping[str, Any]],
|
|
259
|
+
identity_changes: list[Mapping[str, str]],
|
|
260
|
+
story_expectations: Mapping[str, Mapping[str, str]],
|
|
261
|
+
verification: dict[str, Any],
|
|
262
|
+
) -> tuple[str, dict[str, Any], Any, dict[str, Any]]:
|
|
263
|
+
"""Compute the candidate revision/semantic diff and write byte/package safety
|
|
264
|
+
into ``verification``.
|
|
265
|
+
|
|
266
|
+
Does not raise on unsafe candidates -- the original ordering runs domain
|
|
267
|
+
verification before that check, so callers must still inspect the
|
|
268
|
+
returned ``safety``/``byte_report``.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
candidate_revision = _revision(candidate_data)
|
|
272
|
+
if story_expectations:
|
|
273
|
+
verification["storyPreservation"] = _verify_header_story_candidates(
|
|
274
|
+
candidate_data,
|
|
275
|
+
candidate_revision,
|
|
276
|
+
story_expectations,
|
|
277
|
+
)
|
|
278
|
+
semantic_diff = {
|
|
279
|
+
"schemaVersion": "hwpx.agent-semantic-diff/v1",
|
|
280
|
+
"inputRevision": input_revision,
|
|
281
|
+
"candidateRevision": candidate_revision,
|
|
282
|
+
"changes": semantic_changes,
|
|
283
|
+
"identityMap": identity_changes,
|
|
284
|
+
}
|
|
285
|
+
byte_report = _member_diff(input_data, candidate_data)
|
|
286
|
+
safety = validate_editor_open_safety(candidate_data)
|
|
287
|
+
safety_dict = safety.to_dict()
|
|
288
|
+
verification.update(
|
|
289
|
+
{
|
|
290
|
+
"candidateRevision": candidate_revision,
|
|
291
|
+
"package": safety_dict["validatePackage"],
|
|
292
|
+
"reopen": safety_dict["reopen"],
|
|
293
|
+
"openSafety": safety_dict,
|
|
294
|
+
"semanticDiff": {"ok": True, "changeCount": len(semantic_changes)},
|
|
295
|
+
"bytePreservation": byte_report,
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
return candidate_revision, semantic_diff, safety, byte_report
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _apply_commands_domain_verification(
|
|
302
|
+
candidate_data: bytes,
|
|
303
|
+
normalized: Mapping[str, Any],
|
|
304
|
+
requirements: set[str],
|
|
305
|
+
domain_verifier: DomainVerifier | None,
|
|
306
|
+
verification: dict[str, Any],
|
|
307
|
+
) -> None:
|
|
308
|
+
if "domain" in requirements:
|
|
309
|
+
if domain_verifier is None:
|
|
310
|
+
verification["domain"] = {"ok": False, "error": "required verifier unavailable"}
|
|
311
|
+
raise AgentContractError(
|
|
312
|
+
"verification_failed", "required domain verifier is unavailable", target="domain"
|
|
313
|
+
)
|
|
314
|
+
domain_report = dict(domain_verifier(candidate_data, normalized))
|
|
315
|
+
try:
|
|
316
|
+
json.dumps(domain_report, ensure_ascii=False, sort_keys=True)
|
|
317
|
+
except (TypeError, ValueError) as exc:
|
|
318
|
+
raise AgentContractError(
|
|
319
|
+
"verification_failed",
|
|
320
|
+
"domain verifier returned a non-JSON report",
|
|
321
|
+
target="domain",
|
|
322
|
+
) from exc
|
|
323
|
+
verification["domain"] = domain_report
|
|
324
|
+
if not domain_report.get("ok"):
|
|
325
|
+
raise AgentContractError("verification_failed", "domain verification failed", target="domain")
|
|
326
|
+
else:
|
|
327
|
+
verification["domain"] = {"ok": None, "status": "not-requested"}
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _require_candidate_structural_safety(safety: Any, byte_report: Mapping[str, Any]) -> None:
|
|
331
|
+
if not safety.ok or not byte_report.get("ok"):
|
|
332
|
+
raise AgentContractError(
|
|
333
|
+
"verification_failed", "candidate failed package/reopen/openSafety verification"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _apply_commands_run_save_pipeline(
|
|
338
|
+
candidate_data: bytes,
|
|
339
|
+
input_path: Path,
|
|
340
|
+
output_path: Path,
|
|
341
|
+
document: HwpxDocument,
|
|
342
|
+
normalized: Mapping[str, Any],
|
|
343
|
+
requirements: set[str],
|
|
344
|
+
save_pipeline: SavePipeline | None,
|
|
345
|
+
verification: dict[str, Any],
|
|
346
|
+
) -> Any:
|
|
347
|
+
pipeline = save_pipeline or SavePipeline()
|
|
348
|
+
quality_policy = _quality_policy(normalized["quality"])
|
|
349
|
+
if "realHancom" in requirements:
|
|
350
|
+
# Make the required oracle part of the atomic gate itself. A
|
|
351
|
+
# post-publication provenance check would be too late to roll
|
|
352
|
+
# back an otherwise structurally valid file.
|
|
353
|
+
quality_policy = quality_policy.with_(
|
|
354
|
+
require_visual_complete=True,
|
|
355
|
+
render_check="required",
|
|
356
|
+
)
|
|
357
|
+
quality_report = pipeline.run(
|
|
358
|
+
candidate_data,
|
|
359
|
+
output_path=None if normalized["dryRun"] else output_path,
|
|
360
|
+
quality=quality_policy,
|
|
361
|
+
before=input_path,
|
|
362
|
+
reference_document=document,
|
|
363
|
+
publish="never" if normalized["dryRun"] else "on_pass",
|
|
364
|
+
source_label="agent.apply_document_commands",
|
|
365
|
+
)
|
|
366
|
+
verification["savePipeline"] = quality_report.to_dict()
|
|
367
|
+
verification["realHancom"] = {
|
|
368
|
+
"required": "realHancom" in requirements,
|
|
369
|
+
"ok": quality_report.visual_complete,
|
|
370
|
+
"status": quality_report.visual_complete_status,
|
|
371
|
+
"renderChecked": quality_report.render_checked,
|
|
372
|
+
}
|
|
373
|
+
if "realHancom" in requirements and not quality_report.visual_complete:
|
|
374
|
+
raise AgentContractError(
|
|
375
|
+
"verification_failed",
|
|
376
|
+
"required real-Hancom visual verification is unavailable or failed",
|
|
377
|
+
target="realHancom",
|
|
378
|
+
)
|
|
379
|
+
if not quality_report.ok:
|
|
380
|
+
raise AgentContractError(
|
|
381
|
+
"verification_failed", "SavePipeline rejected the candidate", target="savePipeline"
|
|
382
|
+
)
|
|
383
|
+
return quality_report
|