python-hwpx 3.1.0__tar.gz → 3.2.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.1.0/src/python_hwpx.egg-info → python_hwpx-3.2.0}/PKG-INFO +3 -2
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/README.md +2 -1
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/pyproject.toml +33 -7
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/commands.py +173 -9
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/document.py +36 -4
- python_hwpx-3.2.0/src/hwpx/agent/story.py +207 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/document.py +22 -11
- python_hwpx-3.2.0/src/hwpx/oxml/_document_impl.py +150 -0
- python_hwpx-3.2.0/src/hwpx/oxml/_document_primitives.py +610 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/oxml/document.py +3 -1
- python_hwpx-3.2.0/src/hwpx/oxml/document_parts.py +1093 -0
- python_hwpx-3.2.0/src/hwpx/oxml/header_part.py +1329 -0
- python_hwpx-3.2.0/src/hwpx/oxml/memo.py +290 -0
- python_hwpx-3.2.0/src/hwpx/oxml/numbering.py +30 -0
- python_hwpx-3.2.0/src/hwpx/oxml/objects.py +470 -0
- python_hwpx-3.2.0/src/hwpx/oxml/paragraph.py +919 -0
- python_hwpx-3.2.0/src/hwpx/oxml/run.py +450 -0
- python_hwpx-3.2.0/src/hwpx/oxml/section.py +322 -0
- python_hwpx-3.2.0/src/hwpx/oxml/section_format.py +513 -0
- python_hwpx-3.2.0/src/hwpx/oxml/section_story.py +586 -0
- python_hwpx-3.2.0/src/hwpx/oxml/simple_parts.py +69 -0
- python_hwpx-3.2.0/src/hwpx/oxml/table.py +994 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/package_validator.py +83 -16
- {python_hwpx-3.1.0 → python_hwpx-3.2.0/src/python_hwpx.egg-info}/PKG-INFO +3 -2
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/python_hwpx.egg-info/SOURCES.txt +7 -0
- python_hwpx-3.2.0/tests/test_agent_story_commands.py +602 -0
- python_hwpx-3.2.0/tests/test_oxml_modularization.py +335 -0
- python_hwpx-3.2.0/tests/test_paragraph_section_management.py +419 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_section_headers.py +132 -0
- python_hwpx-3.1.0/src/hwpx/oxml/_document_impl.py +0 -6866
- python_hwpx-3.1.0/src/hwpx/oxml/header_part.py +0 -11
- python_hwpx-3.1.0/src/hwpx/oxml/memo.py +0 -11
- python_hwpx-3.1.0/src/hwpx/oxml/numbering.py +0 -8
- python_hwpx-3.1.0/src/hwpx/oxml/objects.py +0 -8
- python_hwpx-3.1.0/src/hwpx/oxml/paragraph.py +0 -11
- python_hwpx-3.1.0/src/hwpx/oxml/run.py +0 -8
- python_hwpx-3.1.0/src/hwpx/oxml/section.py +0 -11
- python_hwpx-3.1.0/src/hwpx/oxml/simple_parts.py +0 -8
- python_hwpx-3.1.0/src/hwpx/oxml/table.py +0 -11
- python_hwpx-3.1.0/tests/test_paragraph_section_management.py +0 -213
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/LICENSE +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/NOTICE +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/setup.cfg +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/blueprint/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/blueprint/bundle.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/blueprint/catalog.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/blueprint/dump.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/blueprint/mapping.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/blueprint/model.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/blueprint/native.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/blueprint/replay.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/catalog.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/cli.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/form_plan.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/model.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/path.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/agent/query.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/authoring.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/benchmark/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/benchmark/blind_eval.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/body_patch.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/builder/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/builder/core.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/builder/report.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/conformance/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/conformance/badges.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/conformance/corpus/corpus.json +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/conformance/corpus/meeting_summary.hwpx +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/conformance/corpus/notice.hwpx +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/conformance/corpus/report_table.hwpx +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/conformance/corpus.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/conformance/report.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/conformance/roundtrip_batch.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/conformance/runner.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/data/Skeleton.hwpx +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/_support.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/composer.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/harvest.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/plan.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profile.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/application_form/fragments/body.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/application_form/fragments/heading.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/application_form/fragments/info_table.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/application_form/fragments/title.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/application_form/profile.json +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/application_form/template.hwpx +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/home_notice/fragments/body.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/home_notice/fragments/heading.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/home_notice/fragments/title.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/home_notice/profile.json +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/home_notice/template.hwpx +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/official_notice/fragments/body.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/official_notice/fragments/heading.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/official_notice/fragments/info_table.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/official_notice/fragments/title.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/official_notice/profile.json +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/official_notice/template.hwpx +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/report/fragments/body.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/report/fragments/heading.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/report/fragments/info_table.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/report/fragments/title.xml +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/report/profile.json +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/profiles/report/template.hwpx +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/design/validator.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/evalplan_fill.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/exam/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/exam/compose.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/exam/ir.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/exam/measure.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/exam/parser.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/exam/profile.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/fill_residue.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/form_fill.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/form_fit/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/form_fit/apply.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/form_fit/engine.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/form_fit/measure.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/form_fit/policy.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/form_fit/report.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/form_fit/seal.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/form_fit/wordbox.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/formfill_quality.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/guidance_scan.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/ingest/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/ingest/base.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/ingest/hwpx_converter.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/layout/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/layout/lint.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/layout/report.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/opc/package.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/opc/relationships.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/opc/security.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/opc/xml_utils.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/oxml/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/oxml/body.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/oxml/canonical_defaults.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/oxml/common.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/oxml/header.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/oxml/namespaces.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/oxml/parser.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/oxml/schema.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/oxml/utils.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/package.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/patch.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/presets/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/presets/proposal.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/py.typed +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/quality/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/quality/ledger.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/quality/policy.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/quality/report.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/quality/save_pipeline.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/table_patch.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/template_formfit.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/templates.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/_schemas/header.xsd +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/_schemas/section.xsd +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/advanced_generators.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/archive_cli.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/doc_diff.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/exporter.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/fuzz/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/fuzz/__main__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/fuzz/catalog.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/fuzz/generator.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/fuzz/minimize.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/fuzz/runner.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/generic_inventory.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/id_integrity.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/idempotence.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/ir_equality.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/layout_preview.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/mail_merge.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/markdown_export.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/object_finder.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/official_lint.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/package_reconcile.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/page_guard.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/pii.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/read_fidelity.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/recover.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/redline.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/repair.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/report_parser.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/report_utils.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/roundtrip_diff.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/style_profile.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/table_cleanup.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/table_compute.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/table_navigation.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/template_analyzer.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/text_extract_cli.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/text_extractor.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/toc_author.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/toc_fidelity.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/tools/validator.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/__init__.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/_hancom_open_rate.ps1 +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/_refresh_hwpx_mac.applescript +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/_render_hwpx.ps1 +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/_render_hwpx_mac.applescript +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/detectors.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/diff.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/fixture_corpus.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/hancom_worker.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/masks.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/oracle.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/page_qa.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/qa_contracts.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/qa_metrics.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/hwpx/visual/report.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/python_hwpx.egg-info/entry_points.txt +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/python_hwpx.egg-info/requires.txt +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/src/python_hwpx.egg-info/top_level.txt +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_advanced_generators.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_agent_blueprint_contracts.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_agent_blueprint_dump.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_agent_blueprint_replay.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_agent_catalog.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_agent_cli.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_agent_commands.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_agent_contracts.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_agent_document.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_agent_mixed_form.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_agent_path_query.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_authoring_profile_routing.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_authoring_profile_routing_oracle.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_authoring_render_check.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_blind_eval_fixture.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_body_patch.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_builder_core.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_builder_plan_v2.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_builder_vertical_slice.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_cell_line_spacing.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_comment_node_robustness.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_conformance.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_corpus_read_fidelity.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_coverage_promotion.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_coverage_targets.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_design_builder.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_deviations_registry.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_doc_diff.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_document_context_manager.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_document_formatting.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_document_plan.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_document_plan_computed_fields.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_document_save_api.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_evalplan_fill.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_exam_compose.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_exam_compose_oracle.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_exam_fixtures.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_exam_ir.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_exam_measure.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_exam_parser.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_exam_profile.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_existing_document_format_editing.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_fill_residue.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_form_fields.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_form_fill_split_run.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_form_fit.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_form_fit_integration.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_form_fit_seal.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_form_fit_seal_placement.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_form_fit_wordbox.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_formfill_quality.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_fuzz_loop.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_fuzz_regressions.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_gap_closure_tools.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_government_report_preset.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_government_table_profile.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_guidance_scan.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_hancom_render_worker.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_hp_tab_support.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_hwpxlib_corpus_read.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_id_generator_range.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_id_integrity.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_image_object_workflow.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_ingest.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_inline_models.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_integration_hwpx_compatibility.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_integration_roundtrip.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_kordoc_absorption.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_layout_lint.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_layout_preview.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_mail_merge_fit.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_mail_merge_table_compute.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_mail_merge_xlsx.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_markdown_export.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_memo_and_style_editing.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_mixed_form_plan.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_namespace_handling.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_new_features.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_official_document_style.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_official_lint_gongmun_gate.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_official_lint_tableaware.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_opc_package.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_open_safety_corpus.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_openrate.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_oxml_parsing.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_packaging_license_metadata.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_packaging_py_typed.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_paragraph_keep_together.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_pii.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_pii_structural.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_pii_wiring.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_proposal_preset.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_public_runtime_boundary.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_question_split_detector.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_read_fidelity.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_recover_broken_zip.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_redline_authoring.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_redline_verify.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_repair_repack.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_report_parser.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_report_utils.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_repr_snapshots.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_rhwp_t1_gates.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_rhwp_t2_verification.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_roundtrip_fidelity.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_row_heights.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_save_pipeline.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_save_pipeline_no_bypass.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_set_paragraph_format_keep.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_skeleton_template_ids.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_split_cell.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_split_merged_cell.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_style_profile.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_table_cleanup.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_table_navigation.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_table_patch.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_table_patch_dryrun.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_table_patch_m105.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_tables_default_border.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_template_analyzer_enrichment.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_template_formfit.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_text_extractor_annotations.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_toc_author.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_toc_fidelity.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_validation_severity.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_validator_comment_nodes.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_version_metadata.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_visual_fixture_corpus.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_visual_oracle.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_visual_qa_contracts.py +0 -0
- {python_hwpx-3.1.0 → python_hwpx-3.2.0}/tests/test_visual_qa_metrics.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-hwpx
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: 한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리
|
|
5
5
|
Author: python-hwpx Maintainers
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -70,7 +70,8 @@ Dynamic: license-file
|
|
|
70
70
|
`hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동 구성요소입니다.
|
|
71
71
|
“first-party”는 프로젝트 유지보수 관계를 뜻하며, 한컴 또는 제3자의 공식 인증을 뜻하지 않습니다.
|
|
72
72
|
|
|
73
|
-
현재 공개
|
|
73
|
+
현재 PyPI 공개 릴리스는 `python-hwpx 3.2.0`입니다. 일반
|
|
74
|
+
`pip install python-hwpx`로 이 릴리스를 설치할 수 있습니다.
|
|
74
75
|
현재 패키지 분류는 `Development Status :: 3 - Alpha`입니다. 이 분류는 API와 제품의
|
|
75
76
|
성숙도를 나타내며, 공개 버전이나 플러그인의 최소 호환 버전을 대신하지 않습니다.
|
|
76
77
|
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
`hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동 구성요소입니다.
|
|
26
26
|
“first-party”는 프로젝트 유지보수 관계를 뜻하며, 한컴 또는 제3자의 공식 인증을 뜻하지 않습니다.
|
|
27
27
|
|
|
28
|
-
현재 공개
|
|
28
|
+
현재 PyPI 공개 릴리스는 `python-hwpx 3.2.0`입니다. 일반
|
|
29
|
+
`pip install python-hwpx`로 이 릴리스를 설치할 수 있습니다.
|
|
29
30
|
현재 패키지 분류는 `Development Status :: 3 - Alpha`입니다. 이 분류는 API와 제품의
|
|
30
31
|
성숙도를 나타내며, 공개 버전이나 플러그인의 최소 호환 버전을 대신하지 않습니다.
|
|
31
32
|
|
|
@@ -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.2.0"
|
|
8
8
|
description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -109,11 +109,25 @@ python_version = "3.10"
|
|
|
109
109
|
follow_imports = "skip"
|
|
110
110
|
files = [
|
|
111
111
|
"src/hwpx/document.py",
|
|
112
|
+
"src/hwpx/agent/commands.py",
|
|
113
|
+
"src/hwpx/agent/document.py",
|
|
114
|
+
"src/hwpx/agent/story.py",
|
|
115
|
+
"src/hwpx/oxml/_document_impl.py",
|
|
116
|
+
"src/hwpx/oxml/_document_primitives.py",
|
|
112
117
|
"src/hwpx/oxml/document.py",
|
|
118
|
+
"src/hwpx/oxml/document_parts.py",
|
|
119
|
+
"src/hwpx/oxml/header_part.py",
|
|
120
|
+
"src/hwpx/oxml/memo.py",
|
|
121
|
+
"src/hwpx/oxml/numbering.py",
|
|
122
|
+
"src/hwpx/oxml/objects.py",
|
|
113
123
|
"src/hwpx/opc/package.py",
|
|
114
124
|
"src/hwpx/opc/relationships.py",
|
|
115
125
|
"src/hwpx/oxml/paragraph.py",
|
|
116
126
|
"src/hwpx/oxml/run.py",
|
|
127
|
+
"src/hwpx/oxml/section.py",
|
|
128
|
+
"src/hwpx/oxml/section_format.py",
|
|
129
|
+
"src/hwpx/oxml/section_story.py",
|
|
130
|
+
"src/hwpx/oxml/simple_parts.py",
|
|
117
131
|
"src/hwpx/oxml/table.py",
|
|
118
132
|
"src/hwpx/quality/policy.py",
|
|
119
133
|
"src/hwpx/quality/report.py",
|
|
@@ -128,18 +142,30 @@ files = [
|
|
|
128
142
|
]
|
|
129
143
|
ignore_missing_imports = true
|
|
130
144
|
|
|
131
|
-
[[tool.mypy.overrides]]
|
|
132
|
-
module = ["hwpx.document", "hwpx.oxml.document"]
|
|
133
|
-
ignore_errors = true
|
|
134
|
-
|
|
135
145
|
[tool.pyright]
|
|
136
|
-
# Keep basic pyright focused on
|
|
137
|
-
#
|
|
146
|
+
# Keep basic pyright focused on the explicit gradual scope. Feature 025 adds
|
|
147
|
+
# both central facades and every extracted/touched runtime owner to the gate.
|
|
138
148
|
include = [
|
|
149
|
+
"src/hwpx/document.py",
|
|
150
|
+
"src/hwpx/agent/commands.py",
|
|
151
|
+
"src/hwpx/agent/document.py",
|
|
152
|
+
"src/hwpx/agent/story.py",
|
|
153
|
+
"src/hwpx/oxml/_document_impl.py",
|
|
154
|
+
"src/hwpx/oxml/_document_primitives.py",
|
|
155
|
+
"src/hwpx/oxml/document.py",
|
|
156
|
+
"src/hwpx/oxml/document_parts.py",
|
|
157
|
+
"src/hwpx/oxml/header_part.py",
|
|
158
|
+
"src/hwpx/oxml/memo.py",
|
|
159
|
+
"src/hwpx/oxml/numbering.py",
|
|
160
|
+
"src/hwpx/oxml/objects.py",
|
|
139
161
|
"src/hwpx/opc/package.py",
|
|
140
162
|
"src/hwpx/opc/relationships.py",
|
|
141
163
|
"src/hwpx/oxml/paragraph.py",
|
|
142
164
|
"src/hwpx/oxml/run.py",
|
|
165
|
+
"src/hwpx/oxml/section.py",
|
|
166
|
+
"src/hwpx/oxml/section_format.py",
|
|
167
|
+
"src/hwpx/oxml/section_story.py",
|
|
168
|
+
"src/hwpx/oxml/simple_parts.py",
|
|
143
169
|
"src/hwpx/oxml/table.py",
|
|
144
170
|
"src/hwpx/quality/policy.py",
|
|
145
171
|
"src/hwpx/quality/report.py",
|
|
@@ -21,7 +21,7 @@ from pathlib import Path
|
|
|
21
21
|
from typing import Any
|
|
22
22
|
from xml.etree import ElementTree as ET
|
|
23
23
|
|
|
24
|
-
from lxml import etree as LET
|
|
24
|
+
from lxml import etree as LET # type: ignore[reportAttributeAccessIssue] # lxml has no complete bundled typing
|
|
25
25
|
|
|
26
26
|
from hwpx.document import HwpxDocument
|
|
27
27
|
from hwpx.oxml import HwpxOxmlTable
|
|
@@ -38,6 +38,12 @@ from .model import (
|
|
|
38
38
|
validate_agent_batch,
|
|
39
39
|
)
|
|
40
40
|
from .path import parse_path
|
|
41
|
+
from .story import (
|
|
42
|
+
HEADER_STORY_EDITABLE_PROPERTIES,
|
|
43
|
+
HEADER_STORY_KIND,
|
|
44
|
+
HeaderStoryBinding,
|
|
45
|
+
try_parse_header_story_path,
|
|
46
|
+
)
|
|
41
47
|
|
|
42
48
|
_EMPTY_REVISION = "sha256:" + hashlib.sha256(b"").hexdigest()
|
|
43
49
|
_HWP_UNITS_PER_MM = 7200 / 25.4
|
|
@@ -242,6 +248,21 @@ def _validate_property_values(kind: str, properties: Mapping[str, Any], *, creat
|
|
|
242
248
|
raise AgentContractError("unknown_property", f"untyped property: {target}", target=target)
|
|
243
249
|
|
|
244
250
|
|
|
251
|
+
def _validate_header_story_properties(properties: Mapping[str, Any]) -> None:
|
|
252
|
+
unknown = sorted(set(properties) - HEADER_STORY_EDITABLE_PROPERTIES)
|
|
253
|
+
if unknown:
|
|
254
|
+
raise AgentContractError(
|
|
255
|
+
"unknown_property",
|
|
256
|
+
f"header does not support properties: {unknown}",
|
|
257
|
+
target=f"header.{unknown[0]}",
|
|
258
|
+
)
|
|
259
|
+
if "text" not in properties:
|
|
260
|
+
raise AgentContractError(
|
|
261
|
+
"invalid_syntax", "existing header set requires text", target="header.text"
|
|
262
|
+
)
|
|
263
|
+
_require_string(properties["text"], "header.text")
|
|
264
|
+
|
|
265
|
+
|
|
245
266
|
def _preflight(batch: Mapping[str, Any]) -> None:
|
|
246
267
|
"""Reject the complete static command matrix before the first mutation."""
|
|
247
268
|
|
|
@@ -254,6 +275,9 @@ def _preflight(batch: Mapping[str, Any]) -> None:
|
|
|
254
275
|
return alias_kinds[command_id][field]
|
|
255
276
|
except KeyError as exc: # validate_agent_batch already checks ordering
|
|
256
277
|
raise AgentContractError("not_found", f"unknown command alias: {value}") from exc
|
|
278
|
+
story = try_parse_header_story_path(value)
|
|
279
|
+
if story is not None:
|
|
280
|
+
return story.kind
|
|
257
281
|
parsed = parse_path(value)
|
|
258
282
|
return parsed.segments[-1].kind if parsed.segments else "document"
|
|
259
283
|
|
|
@@ -264,6 +288,9 @@ def _preflight(batch: Mapping[str, Any]) -> None:
|
|
|
264
288
|
return alias_kinds[command_id]["parentPath" if field == "path" else field]
|
|
265
289
|
except KeyError:
|
|
266
290
|
return "document"
|
|
291
|
+
story = try_parse_header_story_path(value)
|
|
292
|
+
if story is not None:
|
|
293
|
+
return "section"
|
|
267
294
|
parsed = parse_path(value)
|
|
268
295
|
return parsed.segments[-2].kind if len(parsed.segments) > 1 else "document"
|
|
269
296
|
|
|
@@ -298,6 +325,19 @@ def _preflight(batch: Mapping[str, Any]) -> None:
|
|
|
298
325
|
}
|
|
299
326
|
continue
|
|
300
327
|
source_kind = reference_kind(command["path"])
|
|
328
|
+
if source_kind == HEADER_STORY_KIND:
|
|
329
|
+
if op != "set":
|
|
330
|
+
raise AgentContractError(
|
|
331
|
+
"unsupported_operation",
|
|
332
|
+
f"{op} is unsupported for {source_kind}",
|
|
333
|
+
target=command["commandId"],
|
|
334
|
+
)
|
|
335
|
+
_validate_header_story_properties(command["properties"])
|
|
336
|
+
alias_kinds[command["commandId"]] = {
|
|
337
|
+
"path": source_kind,
|
|
338
|
+
"parentPath": "section",
|
|
339
|
+
}
|
|
340
|
+
continue
|
|
301
341
|
if op not in NODE_PROPERTY_CATALOG_V1[source_kind]["operations"]:
|
|
302
342
|
raise AgentContractError(
|
|
303
343
|
"unsupported_operation",
|
|
@@ -309,11 +349,11 @@ def _preflight(batch: Mapping[str, Any]) -> None:
|
|
|
309
349
|
destination_kind = parent_kind(command["path"])
|
|
310
350
|
if op in {"move", "copy"}:
|
|
311
351
|
destination_kind = reference_kind(command["parent"])
|
|
312
|
-
|
|
313
|
-
if destination_kind !=
|
|
352
|
+
move_parent = _MOVE_COPY_PARENTS.get(source_kind)
|
|
353
|
+
if destination_kind != move_parent:
|
|
314
354
|
raise AgentContractError(
|
|
315
355
|
"incompatible_parent",
|
|
316
|
-
f"{source_kind} requires a {
|
|
356
|
+
f"{source_kind} requires a {move_parent} parent, not {destination_kind}",
|
|
317
357
|
target=command["commandId"],
|
|
318
358
|
)
|
|
319
359
|
alias_kinds[command["commandId"]] = {
|
|
@@ -570,6 +610,42 @@ def _mark_containing_section(document: HwpxDocument, target: Any) -> None:
|
|
|
570
610
|
raise AgentContractError("not_found", "mutated element is detached")
|
|
571
611
|
|
|
572
612
|
|
|
613
|
+
def _apply_header_story_set(
|
|
614
|
+
binding: HeaderStoryBinding, properties: Mapping[str, Any]
|
|
615
|
+
) -> dict[str, Any]:
|
|
616
|
+
"""Apply the bounded existing-header mutation through its OXML owner."""
|
|
617
|
+
|
|
618
|
+
_validate_header_story_properties(properties)
|
|
619
|
+
try:
|
|
620
|
+
binding.native.set_simple_text_preserving(properties["text"])
|
|
621
|
+
except AgentContractError:
|
|
622
|
+
raise
|
|
623
|
+
except ValueError as exc:
|
|
624
|
+
# The OXML owner uses ValueError for rich/control-bearing or otherwise
|
|
625
|
+
# structurally unsafe headers. Do not let it degrade to the generic
|
|
626
|
+
# invariant envelope or fall back to the destructive whole-story setter.
|
|
627
|
+
message = str(exc) or "header content cannot be edited losslessly"
|
|
628
|
+
lowered = message.lower()
|
|
629
|
+
code = (
|
|
630
|
+
"ambiguous_target"
|
|
631
|
+
if "ambiguous" in lowered
|
|
632
|
+
else "not_found"
|
|
633
|
+
if "not found" in lowered
|
|
634
|
+
else "unsupported_content"
|
|
635
|
+
)
|
|
636
|
+
raise AgentContractError(
|
|
637
|
+
code,
|
|
638
|
+
message,
|
|
639
|
+
target=binding.path,
|
|
640
|
+
) from exc
|
|
641
|
+
return {
|
|
642
|
+
"text": {
|
|
643
|
+
"before": binding.text,
|
|
644
|
+
"after": properties["text"],
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
|
|
573
649
|
def _apply_set(document: HwpxDocument, record: NodeRecord, properties: Mapping[str, Any]) -> dict[str, Any]:
|
|
574
650
|
_ensure_operation(record, "set")
|
|
575
651
|
_validate_property_values(record.kind, properties, creating=False)
|
|
@@ -711,7 +787,10 @@ def _add(
|
|
|
711
787
|
if "pageHeightMm" in properties:
|
|
712
788
|
size_kwargs["height"] = round(properties["pageHeightMm"] * _HWP_UNITS_PER_MM)
|
|
713
789
|
if size_kwargs:
|
|
714
|
-
created.properties.set_page_size(
|
|
790
|
+
created.properties.set_page_size(
|
|
791
|
+
width=size_kwargs.get("width"),
|
|
792
|
+
height=size_kwargs.get("height"),
|
|
793
|
+
)
|
|
715
794
|
return created
|
|
716
795
|
if kind == "paragraph":
|
|
717
796
|
created = parent.native.add_paragraph(str(properties.get("text", "")))
|
|
@@ -1081,6 +1160,47 @@ def _member_diff(before: bytes, after: bytes) -> dict[str, Any]:
|
|
|
1081
1160
|
return {"ok": False, "error": f"{type(exc).__name__}: {exc}"}
|
|
1082
1161
|
|
|
1083
1162
|
|
|
1163
|
+
def _verify_header_story_candidates(
|
|
1164
|
+
candidate_data: bytes,
|
|
1165
|
+
candidate_revision: str,
|
|
1166
|
+
expectations: Mapping[str, Mapping[str, str]],
|
|
1167
|
+
) -> dict[str, Any]:
|
|
1168
|
+
receipts: list[dict[str, Any]] = []
|
|
1169
|
+
if expectations:
|
|
1170
|
+
with HwpxDocument.open(candidate_data) as reopened:
|
|
1171
|
+
view = HwpxAgentDocument.from_document(
|
|
1172
|
+
reopened, revision=candidate_revision
|
|
1173
|
+
)
|
|
1174
|
+
for expectation in expectations.values():
|
|
1175
|
+
path = expectation["path"]
|
|
1176
|
+
binding = view._resolve_header_story(path)
|
|
1177
|
+
if (
|
|
1178
|
+
binding.stable_id != expectation["stableId"]
|
|
1179
|
+
or binding.page_type != expectation["pageType"]
|
|
1180
|
+
or binding.text != expectation["text"]
|
|
1181
|
+
):
|
|
1182
|
+
raise AgentContractError(
|
|
1183
|
+
"verification_failed",
|
|
1184
|
+
"reopened header story does not match the committed binding",
|
|
1185
|
+
target=path,
|
|
1186
|
+
)
|
|
1187
|
+
receipts.append(
|
|
1188
|
+
{
|
|
1189
|
+
"commandId": expectation["commandId"],
|
|
1190
|
+
"path": binding.path,
|
|
1191
|
+
"stableId": binding.stable_id,
|
|
1192
|
+
"pageType": binding.page_type,
|
|
1193
|
+
"textMatched": True,
|
|
1194
|
+
}
|
|
1195
|
+
)
|
|
1196
|
+
return {
|
|
1197
|
+
"schemaVersion": "hwpx.agent-story-preservation/v1",
|
|
1198
|
+
"ok": True,
|
|
1199
|
+
"storyCount": len(receipts),
|
|
1200
|
+
"stories": receipts,
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
|
|
1084
1204
|
def _request_hash(batch: Mapping[str, Any]) -> str:
|
|
1085
1205
|
payload = json.dumps(
|
|
1086
1206
|
batch,
|
|
@@ -1192,6 +1312,7 @@ def apply_document_commands(
|
|
|
1192
1312
|
aliases: dict[str, dict[str, str]] = {}
|
|
1193
1313
|
identity_changes: list[Mapping[str, str]] = []
|
|
1194
1314
|
semantic_changes: list[Mapping[str, Any]] = []
|
|
1315
|
+
story_expectations: dict[str, Mapping[str, str]] = {}
|
|
1195
1316
|
_call_fault(fault_injector, "before_open")
|
|
1196
1317
|
with HwpxDocument.open(input_data) as document:
|
|
1197
1318
|
view = HwpxAgentDocument.from_document(document, revision=input_revision)
|
|
@@ -1203,11 +1324,24 @@ def apply_document_commands(
|
|
|
1203
1324
|
parent_path: str | None = None
|
|
1204
1325
|
changed: dict[str, Any] = {}
|
|
1205
1326
|
generated: list[dict[str, str]] = []
|
|
1327
|
+
target_native: Any | None = None
|
|
1328
|
+
story_before: HeaderStoryBinding | None = None
|
|
1206
1329
|
if op == "set":
|
|
1207
1330
|
resolved_path = _resolve_alias(command["path"], aliases)
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1331
|
+
story_path = try_parse_header_story_path(resolved_path)
|
|
1332
|
+
if story_path is not None:
|
|
1333
|
+
story_before = view._resolve_header_story(
|
|
1334
|
+
story_path, expected_revision=input_revision
|
|
1335
|
+
)
|
|
1336
|
+
changed = _apply_header_story_set(
|
|
1337
|
+
story_before, command["properties"]
|
|
1338
|
+
)
|
|
1339
|
+
else:
|
|
1340
|
+
record = view.resolve_record(
|
|
1341
|
+
resolved_path, expected_revision=input_revision
|
|
1342
|
+
)
|
|
1343
|
+
changed = _apply_set(document, record, command["properties"])
|
|
1344
|
+
target_native = record.native
|
|
1211
1345
|
elif op == "add":
|
|
1212
1346
|
parent_path = _resolve_alias(command["parent"], aliases)
|
|
1213
1347
|
parent = view.resolve_record(parent_path, expected_revision=input_revision)
|
|
@@ -1244,7 +1378,26 @@ def apply_document_commands(
|
|
|
1244
1378
|
|
|
1245
1379
|
_call_fault(fault_injector, "after_command", index)
|
|
1246
1380
|
view = HwpxAgentDocument.from_document(document, revision=input_revision)
|
|
1247
|
-
|
|
1381
|
+
result_stable_id: str | None = None
|
|
1382
|
+
if story_before is not None and resolved_path is not None:
|
|
1383
|
+
target_story = view._resolve_header_story(resolved_path)
|
|
1384
|
+
if target_story.stable_id != story_before.stable_id:
|
|
1385
|
+
raise AgentContractError(
|
|
1386
|
+
"invariant_violation",
|
|
1387
|
+
"header story identity changed during text edit",
|
|
1388
|
+
target=resolved_path,
|
|
1389
|
+
)
|
|
1390
|
+
result_path = target_story.path
|
|
1391
|
+
result_parent = target_story.parent_path
|
|
1392
|
+
result_stable_id = target_story.stable_id
|
|
1393
|
+
story_expectations[target_story.binding_key] = {
|
|
1394
|
+
"commandId": command_id,
|
|
1395
|
+
"path": target_story.path,
|
|
1396
|
+
"stableId": target_story.stable_id,
|
|
1397
|
+
"pageType": target_story.page_type,
|
|
1398
|
+
"text": command["properties"]["text"],
|
|
1399
|
+
}
|
|
1400
|
+
elif op == "set" and resolved_path is not None:
|
|
1248
1401
|
# Some native bindings (notably form fields) are request-local
|
|
1249
1402
|
# match dictionaries. A property edit is non-structural, so
|
|
1250
1403
|
# its canonical path is the durable post-edit lookup key.
|
|
@@ -1269,6 +1422,11 @@ def apply_document_commands(
|
|
|
1269
1422
|
"generatedIdentities": generated,
|
|
1270
1423
|
"warnings": [],
|
|
1271
1424
|
}
|
|
1425
|
+
if result_stable_id is not None:
|
|
1426
|
+
# Command results are already an untyped JSON mapping. This
|
|
1427
|
+
# story-only receipt carries the actual stable identity
|
|
1428
|
+
# without changing AgentBatchResult or the ToolSpec schema.
|
|
1429
|
+
result["stableId"] = result_stable_id
|
|
1272
1430
|
command_results.append(result)
|
|
1273
1431
|
semantic_changes.append(
|
|
1274
1432
|
{
|
|
@@ -1285,6 +1443,12 @@ def apply_document_commands(
|
|
|
1285
1443
|
_call_fault(fault_injector, "after_serialize")
|
|
1286
1444
|
|
|
1287
1445
|
candidate_revision = _revision(candidate_data)
|
|
1446
|
+
if story_expectations:
|
|
1447
|
+
verification["storyPreservation"] = _verify_header_story_candidates(
|
|
1448
|
+
candidate_data,
|
|
1449
|
+
candidate_revision,
|
|
1450
|
+
story_expectations,
|
|
1451
|
+
)
|
|
1288
1452
|
semantic_diff = {
|
|
1289
1453
|
"schemaVersion": "hwpx.agent-semantic-diff/v1",
|
|
1290
1454
|
"inputRevision": input_revision,
|
|
@@ -8,7 +8,7 @@ from collections import Counter, defaultdict
|
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
9
|
from os import PathLike
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import Any
|
|
11
|
+
from typing import Any, Sequence, cast
|
|
12
12
|
|
|
13
13
|
from hwpx.document import HwpxDocument
|
|
14
14
|
from hwpx.oxml import HwpxOxmlShape
|
|
@@ -22,7 +22,14 @@ from .model import (
|
|
|
22
22
|
NODE_PROPERTY_CATALOG_V1,
|
|
23
23
|
)
|
|
24
24
|
from .path import SemanticPath, canonicalize_path, identified_segment, indexed_segment
|
|
25
|
+
from .query import QueryRecord as _QueryRecord
|
|
25
26
|
from .query import QueryResult, evaluate_selector, parse_selector
|
|
27
|
+
from .story import (
|
|
28
|
+
HeaderStoryBinding,
|
|
29
|
+
HeaderStoryPath,
|
|
30
|
+
parse_header_story_path,
|
|
31
|
+
resolve_header_story,
|
|
32
|
+
)
|
|
26
33
|
|
|
27
34
|
_HWP_UNITS_PER_MM = 7200 / 25.4
|
|
28
35
|
_SHAPE_KINDS = frozenset({"line", "rect", "ellipse", "arc", "polygon", "curve", "connectLine"})
|
|
@@ -129,7 +136,7 @@ class HwpxAgentDocument:
|
|
|
129
136
|
def __enter__(self) -> "HwpxAgentDocument":
|
|
130
137
|
return self
|
|
131
138
|
|
|
132
|
-
def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> bool:
|
|
139
|
+
def __exit__(self, exc_type: Any, exc: Any, tb: Any) -> bool: # type: ignore[exit-return] # frozen public signature
|
|
133
140
|
self.close()
|
|
134
141
|
return False
|
|
135
142
|
|
|
@@ -689,6 +696,25 @@ class HwpxAgentDocument:
|
|
|
689
696
|
raise AgentContractError("volatile_target", "target has a positional path", target=canonical)
|
|
690
697
|
return record
|
|
691
698
|
|
|
699
|
+
def _resolve_header_story(
|
|
700
|
+
self,
|
|
701
|
+
path: str | HeaderStoryPath,
|
|
702
|
+
*,
|
|
703
|
+
expected_revision: str | None = None,
|
|
704
|
+
) -> HeaderStoryBinding:
|
|
705
|
+
"""Resolve the private command-only existing-header seam.
|
|
706
|
+
|
|
707
|
+
Header stories deliberately remain absent from ``records``, ``get``,
|
|
708
|
+
and ``query`` so the frozen public projection/catalog does not drift.
|
|
709
|
+
"""
|
|
710
|
+
|
|
711
|
+
parsed = parse_header_story_path(path) if isinstance(path, str) else path
|
|
712
|
+
if expected_revision is not None and expected_revision != self.revision:
|
|
713
|
+
raise AgentContractError(
|
|
714
|
+
"stale_revision", "document revision does not match", target=parsed.canonical
|
|
715
|
+
)
|
|
716
|
+
return resolve_header_story(self.document, parsed)
|
|
717
|
+
|
|
692
718
|
def _public_node(self, record: NodeRecord, *, depth: int, child_limit: int) -> AgentNode:
|
|
693
719
|
supported_total = len(record.child_paths)
|
|
694
720
|
selected_paths = record.child_paths[:child_limit] if depth > 0 else []
|
|
@@ -751,7 +777,8 @@ class HwpxAgentDocument:
|
|
|
751
777
|
if expected_revision is not None and expected_revision != self.revision:
|
|
752
778
|
raise AgentContractError("stale_revision", "document revision does not match", target="selector")
|
|
753
779
|
parsed = parse_selector(selector)
|
|
754
|
-
|
|
780
|
+
query_records = cast(Sequence[_QueryRecord], self.records)
|
|
781
|
+
matches, truncated = evaluate_selector(query_records, parsed, limit=limit)
|
|
755
782
|
if not 0 <= node_depth <= MAX_VIEW_DEPTH:
|
|
756
783
|
raise AgentContractError("resource_limit", "nodeDepth is out of bounds", target="nodeDepth")
|
|
757
784
|
if not 1 <= child_limit <= MAX_CHILDREN_PER_NODE:
|
|
@@ -760,7 +787,12 @@ class HwpxAgentDocument:
|
|
|
760
787
|
selector=selector,
|
|
761
788
|
revision=self.revision,
|
|
762
789
|
nodes=tuple(
|
|
763
|
-
self._public_node(
|
|
790
|
+
self._public_node(
|
|
791
|
+
cast(NodeRecord, record),
|
|
792
|
+
depth=node_depth,
|
|
793
|
+
child_limit=child_limit,
|
|
794
|
+
)
|
|
795
|
+
for record in matches
|
|
764
796
|
),
|
|
765
797
|
truncated=truncated,
|
|
766
798
|
)
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Private, contract-neutral bindings for existing non-body stories.
|
|
3
|
+
|
|
4
|
+
The public semantic path grammar intentionally remains frozen at the Feature
|
|
5
|
+
024 catalog. This module recognizes one narrower command-only seam: an
|
|
6
|
+
existing section header selected by its native id or page type. It does not
|
|
7
|
+
project headers into the public view, accept package paths, or expose XML.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import re
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from .model import AgentContractError
|
|
18
|
+
from .path import MAX_PATH_CHARS
|
|
19
|
+
|
|
20
|
+
HEADER_STORY_KIND = "header"
|
|
21
|
+
HEADER_STORY_EDITABLE_PROPERTIES = frozenset({"text"})
|
|
22
|
+
HEADER_PAGE_TYPES = frozenset({"BOTH", "EVEN", "ODD"})
|
|
23
|
+
|
|
24
|
+
_HEADER_STORY_RE = re.compile(
|
|
25
|
+
r"^/section\[(?P<section>0*[1-9][0-9]*)\]/header\["
|
|
26
|
+
r"@(?P<attribute>id|page-type)=(?P<value>\"(?:[^\"\\]|\\.)*\")\]$"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, slots=True)
|
|
31
|
+
class HeaderStoryPath:
|
|
32
|
+
"""Parsed command-only path for one existing section header."""
|
|
33
|
+
|
|
34
|
+
section_index: int
|
|
35
|
+
attribute: str
|
|
36
|
+
value: str
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def kind(self) -> str:
|
|
40
|
+
return HEADER_STORY_KIND
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def canonical(self) -> str:
|
|
44
|
+
value = json.dumps(self.value, ensure_ascii=False, separators=(",", ":"))
|
|
45
|
+
return (
|
|
46
|
+
f"/section[{self.section_index}]/"
|
|
47
|
+
f"header[@{self.attribute}={value}]"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def parent_path(self) -> str:
|
|
52
|
+
return f"/section[{self.section_index}]"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True, slots=True)
|
|
56
|
+
class HeaderStoryBinding:
|
|
57
|
+
"""Request-local native binding for an existing logical header."""
|
|
58
|
+
|
|
59
|
+
path: str
|
|
60
|
+
parent_path: str
|
|
61
|
+
stable_id: str
|
|
62
|
+
native_id: str
|
|
63
|
+
page_type: str
|
|
64
|
+
section_index: int
|
|
65
|
+
native: Any
|
|
66
|
+
text: str
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def kind(self) -> str:
|
|
70
|
+
return HEADER_STORY_KIND
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def binding_key(self) -> str:
|
|
74
|
+
# Native ids are not promised to be document-global. Section scope is
|
|
75
|
+
# part of the logical binding even though the receipt exposes the same
|
|
76
|
+
# kind:id stable-id convention used by projected semantic nodes.
|
|
77
|
+
return f"{self.section_index}:{self.native_id}"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def try_parse_header_story_path(value: object) -> HeaderStoryPath | None:
|
|
81
|
+
"""Return a private header path, or ``None`` for the public path parser.
|
|
82
|
+
|
|
83
|
+
Only exact supported forms are intercepted. Unsupported forms such as
|
|
84
|
+
``header[1]`` deliberately continue through :func:`parse_path` and retain
|
|
85
|
+
the frozen ``unknown_kind`` behavior.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
if not isinstance(value, str) or "/header[@" not in value:
|
|
89
|
+
return None
|
|
90
|
+
if len(value) > MAX_PATH_CHARS:
|
|
91
|
+
raise AgentContractError("resource_limit", "path is too long", target="path")
|
|
92
|
+
match = _HEADER_STORY_RE.fullmatch(value)
|
|
93
|
+
if match is None:
|
|
94
|
+
return None
|
|
95
|
+
try:
|
|
96
|
+
decoded = json.loads(match.group("value"))
|
|
97
|
+
except (TypeError, ValueError, json.JSONDecodeError) as exc:
|
|
98
|
+
raise AgentContractError(
|
|
99
|
+
"invalid_syntax", "invalid header story path string", target="path"
|
|
100
|
+
) from exc
|
|
101
|
+
if not isinstance(decoded, str) or not decoded or len(decoded) > 256:
|
|
102
|
+
raise AgentContractError(
|
|
103
|
+
"resource_limit", "header story selector is invalid", target="path"
|
|
104
|
+
)
|
|
105
|
+
attribute = match.group("attribute")
|
|
106
|
+
if attribute == "page-type" and decoded not in HEADER_PAGE_TYPES:
|
|
107
|
+
raise AgentContractError(
|
|
108
|
+
"invalid_syntax",
|
|
109
|
+
"header page type must be BOTH, EVEN, or ODD",
|
|
110
|
+
target="path",
|
|
111
|
+
)
|
|
112
|
+
return HeaderStoryPath(
|
|
113
|
+
section_index=int(match.group("section")),
|
|
114
|
+
attribute=attribute,
|
|
115
|
+
value=decoded,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def parse_header_story_path(value: str) -> HeaderStoryPath:
|
|
120
|
+
"""Parse an exact private header path for focused internal tests."""
|
|
121
|
+
|
|
122
|
+
parsed = try_parse_header_story_path(value)
|
|
123
|
+
if parsed is None:
|
|
124
|
+
raise AgentContractError(
|
|
125
|
+
"invalid_syntax", "unsupported header story path", target="path"
|
|
126
|
+
)
|
|
127
|
+
return parsed
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def resolve_header_story(document: Any, path: HeaderStoryPath) -> HeaderStoryBinding:
|
|
131
|
+
"""Resolve one unique direct logical header without scanning descendants."""
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
sections = document.sections
|
|
135
|
+
except (AttributeError, TypeError, ValueError) as exc:
|
|
136
|
+
raise AgentContractError(
|
|
137
|
+
"unsupported_content", "document section structure is unavailable", target=path.canonical
|
|
138
|
+
) from exc
|
|
139
|
+
if path.section_index > len(sections):
|
|
140
|
+
raise AgentContractError(
|
|
141
|
+
"not_found", "header story section does not exist", target=path.canonical
|
|
142
|
+
)
|
|
143
|
+
section = sections[path.section_index - 1]
|
|
144
|
+
try:
|
|
145
|
+
headers = tuple(section.properties.headers)
|
|
146
|
+
if path.attribute == "id":
|
|
147
|
+
matches = [header for header in headers if header.id == path.value]
|
|
148
|
+
else:
|
|
149
|
+
matches = [
|
|
150
|
+
header for header in headers if header.apply_page_type == path.value
|
|
151
|
+
]
|
|
152
|
+
except (AttributeError, TypeError, ValueError) as exc:
|
|
153
|
+
raise AgentContractError(
|
|
154
|
+
"unsupported_content", "section header structure is invalid", target=path.canonical
|
|
155
|
+
) from exc
|
|
156
|
+
|
|
157
|
+
if not matches:
|
|
158
|
+
raise AgentContractError(
|
|
159
|
+
"not_found", f"existing header story not found: {path.canonical}", target=path.canonical
|
|
160
|
+
)
|
|
161
|
+
if len(matches) > 1:
|
|
162
|
+
raise AgentContractError(
|
|
163
|
+
"ambiguous_target",
|
|
164
|
+
f"header story selector is not unique: {path.canonical}",
|
|
165
|
+
target=path.canonical,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
header = matches[0]
|
|
169
|
+
try:
|
|
170
|
+
native_id = header.id
|
|
171
|
+
page_type = header.apply_page_type
|
|
172
|
+
text = header.text
|
|
173
|
+
except (AttributeError, TypeError, ValueError) as exc:
|
|
174
|
+
raise AgentContractError(
|
|
175
|
+
"unsupported_content", "header story cannot be read safely", target=path.canonical
|
|
176
|
+
) from exc
|
|
177
|
+
if not native_id or len(native_id) > 256:
|
|
178
|
+
raise AgentContractError(
|
|
179
|
+
"unsupported_content", "header story has no bounded native identity", target=path.canonical
|
|
180
|
+
)
|
|
181
|
+
if page_type not in HEADER_PAGE_TYPES:
|
|
182
|
+
raise AgentContractError(
|
|
183
|
+
"unsupported_content", "header story has an unsupported page type", target=path.canonical
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return HeaderStoryBinding(
|
|
187
|
+
path=path.canonical,
|
|
188
|
+
parent_path=path.parent_path,
|
|
189
|
+
stable_id=f"header:{native_id}",
|
|
190
|
+
native_id=native_id,
|
|
191
|
+
page_type=page_type,
|
|
192
|
+
section_index=path.section_index,
|
|
193
|
+
native=header,
|
|
194
|
+
text=text,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
__all__ = [
|
|
199
|
+
"HEADER_PAGE_TYPES",
|
|
200
|
+
"HEADER_STORY_EDITABLE_PROPERTIES",
|
|
201
|
+
"HEADER_STORY_KIND",
|
|
202
|
+
"HeaderStoryBinding",
|
|
203
|
+
"HeaderStoryPath",
|
|
204
|
+
"parse_header_story_path",
|
|
205
|
+
"resolve_header_story",
|
|
206
|
+
"try_parse_header_story_path",
|
|
207
|
+
]
|