python-hwpx-automation 7.3.4__tar.gz → 7.4.1__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_automation-7.3.4/src/python_hwpx_automation.egg-info → python_hwpx_automation-7.4.1}/PKG-INFO +3 -3
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/README.md +2 -2
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/pyproject.toml +1 -1
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/identity.json +2 -2
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/commands.py +36 -1
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/composer.py +51 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/harvest.py +44 -2
- python_hwpx_automation-7.4.1/src/hwpx_automation/office/authoring/design/marked.py +273 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/plan.py +3 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/fit/wordbox.py +29 -4
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/_render_hwpx_mac.applescript +89 -4
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/cli.py +10 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/mac_session.py +20 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/oracle.py +119 -4
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/public-modules.json +5 -4
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1/src/python_hwpx_automation.egg-info}/PKG-INFO +3 -3
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/python_hwpx_automation.egg-info/SOURCES.txt +1 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/LICENSE +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/MANIFEST.in +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/NOTICE +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/setup.cfg +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/__init__.pyi +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/__main__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/agent_document.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/api.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/blind_eval.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/capabilities.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/compat.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/configuration.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/content.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/context.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/diff.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/document.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/formatting.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/handles.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/locations.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/locator.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/plan.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/resources.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/search.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/transactions.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/core/txn.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/document_state.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/errors.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/execution_lock.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/fastmcp_adapter.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/form_fill.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/form_output_models.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/handlers/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/handlers/_shared.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/handlers/agent_document.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/handlers/authoring.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/handlers/content_edit.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/handlers/form_fill.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/handlers/layout_style.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/handlers/quality_render.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/handlers/read_export.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/handlers/specialized.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/handlers/tracked_changes.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/handlers/workflow.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/hwp_converter.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/hwp_support.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/hwpx_ops.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/identity.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ingest_adapters.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/markdown_plan.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/mcp_cli.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/metadata/tools_meta.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/mixed_form.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/mutation_models.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/network_policy.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/_batch_publication.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/_batch_verification.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/_text_scope.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/blueprint/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/blueprint/bundle.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/blueprint/catalog.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/blueprint/dump.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/blueprint/mapping.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/blueprint/model.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/blueprint/native.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/blueprint/replay.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/catalog.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/cli.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/document.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/form_plan.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/model.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/path.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/query.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/agent/story.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/advanced_generators.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/builder/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/builder/core.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/builder/report.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/_support.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profile.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/body.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/heading.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/info_table.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/title.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/profile.json +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/template.hwpx +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/fragments/body.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/fragments/heading.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/fragments/title.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/profile.json +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/template.hwpx +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/body.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/heading.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/info_table.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/title.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/profile.json +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/template.hwpx +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/body.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/heading.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/info_table.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/title.xml +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/report/profile.json +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/profiles/report/template.hwpx +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/design/validator.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/presets/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/presets/proposal.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/report_parser.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/style_profile.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/authoring/template_analyzer.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/charting/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/compliance/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/compliance/official_lint.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/compliance/pii.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/document_ops/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/document_ops/comparison.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/document_ops/mail_merge.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/document_ops/redline.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/evalplan/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/evalplan/runtime.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/exam/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/exam/compose.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/exam/ir.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/exam/measure.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/exam/parser.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/exam/profile.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/classification.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/fill_residue.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/fit/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/fit/apply.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/fit/engine.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/fit/measure.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/fit/policy.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/fit/report.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/fit/seal.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/guidance.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/quality.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/split_run.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/form_fill/template_formfit.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/house_style/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/house_style/composition.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/house_style/data/bank.json +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/house_style/data/genres.json +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/house_style/gianmun.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/house_style/org_chart.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/quality/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/quality/page_guard.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/__main__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/_hancom_open_rate.ps1 +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/_refresh_hwpx_mac.applescript +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/_render_hwpx.ps1 +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/block_splits.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/detectors.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/diff.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/fixture_corpus.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/page_qa.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/qa_contracts.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/qa_metrics.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/rendering/worker.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/utilities/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/office/utilities/table_compute.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/_border_fill.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/composition.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/content_layout.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/context.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/form_fields.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/media.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/memo_style.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/package_validation.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/planning.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/preview_export.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/read_query.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/save_policy.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/tables.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/ops_services/transactions.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/preview_output_models.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/py.typed +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/quality.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/quality_generation.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/resources.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/runtime.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/runtime_services.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/server.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/storage.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/tool_bindings.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/tool_contract.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/upstream.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/utils/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/utils/helpers.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/utils/read_budget.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/visual_qa.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/__init__.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/adapters.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/dispatcher.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/evidence_lineage.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/models.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/policy.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/render_contracts.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/render_metrics.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/render_queue.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/render_security.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/render_transport.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/rendering.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/service.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/state_machine.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workflow/store.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/workspace.py +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/python_hwpx_automation.egg-info/dependency_links.txt +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/python_hwpx_automation.egg-info/entry_points.txt +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/python_hwpx_automation.egg-info/requires.txt +0 -0
- {python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/python_hwpx_automation.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-hwpx-automation
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.4.1
|
|
4
4
|
Summary: Task-oriented HWPX document automation for Python, with an optional MCP adapter.
|
|
5
5
|
Author: Kohkyuhyun
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -74,7 +74,7 @@ Dynamic: license-file
|
|
|
74
74
|
|
|
75
75
|
<!-- release-state: release-approved -->
|
|
76
76
|
> [!NOTE]
|
|
77
|
-
> 발행이 승인된 7.
|
|
77
|
+
> 발행이 승인된 7.4.1 후보 (release-approved; 원격 발행 대기): `python-hwpx 6.6.0 → python-hwpx-automation 7.4.1 → hwpx-plugin 2.4.0` (`5e5c23651f92785a`).
|
|
78
78
|
> 공개 트레인: `python-hwpx 6.6.0 → python-hwpx-automation 7.3.1 → hwpx-plugin 2.4.0`.
|
|
79
79
|
> 공개 좌표는 core·automation PyPI와 plugin GitHub
|
|
80
80
|
> Release·marketplace·실제 marketplace 설치 관찰 후에만 승격합니다.
|
|
@@ -162,7 +162,7 @@ PDF와 쪽 PNG로 렌더합니다. 별도 환경에서 실행하므로 호출하
|
|
|
162
162
|
고정 버전을 건드리지 않습니다.
|
|
163
163
|
|
|
164
164
|
```bash
|
|
165
|
-
uvx --from "python-hwpx-automation[oracle]==7.
|
|
165
|
+
uvx --from "python-hwpx-automation[oracle]==7.4.1" \
|
|
166
166
|
hwpx render-pdf in.hwpx out.pdf --png out/page --json
|
|
167
167
|
```
|
|
168
168
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
<!-- release-state: release-approved -->
|
|
16
16
|
> [!NOTE]
|
|
17
|
-
> 발행이 승인된 7.
|
|
17
|
+
> 발행이 승인된 7.4.1 후보 (release-approved; 원격 발행 대기): `python-hwpx 6.6.0 → python-hwpx-automation 7.4.1 → hwpx-plugin 2.4.0` (`5e5c23651f92785a`).
|
|
18
18
|
> 공개 트레인: `python-hwpx 6.6.0 → python-hwpx-automation 7.3.1 → hwpx-plugin 2.4.0`.
|
|
19
19
|
> 공개 좌표는 core·automation PyPI와 plugin GitHub
|
|
20
20
|
> Release·marketplace·실제 marketplace 설치 관찰 후에만 승격합니다.
|
|
@@ -102,7 +102,7 @@ PDF와 쪽 PNG로 렌더합니다. 별도 환경에서 실행하므로 호출하
|
|
|
102
102
|
고정 버전을 건드리지 않습니다.
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
|
-
uvx --from "python-hwpx-automation[oracle]==7.
|
|
105
|
+
uvx --from "python-hwpx-automation[oracle]==7.4.1" \
|
|
106
106
|
hwpx render-pdf in.hwpx out.pdf --png out/page --json
|
|
107
107
|
```
|
|
108
108
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-hwpx-automation"
|
|
7
|
-
version = "7.
|
|
7
|
+
version = "7.4.1"
|
|
8
8
|
description = "Task-oriented HWPX document automation for Python, with an optional MCP adapter."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Kohkyuhyun" }]
|
{python_hwpx_automation-7.3.4 → python_hwpx_automation-7.4.1}/src/hwpx_automation/identity.json
RENAMED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
"candidate": {
|
|
8
8
|
"pythonHwpx": "6.6.0",
|
|
9
9
|
"canonicalDistribution": "python-hwpx-automation",
|
|
10
|
-
"canonicalAutomation": "7.
|
|
10
|
+
"canonicalAutomation": "7.4.1",
|
|
11
11
|
"compatibilityDistribution": "hwpx-mcp-server",
|
|
12
|
-
"compatibility": "7.
|
|
12
|
+
"compatibility": "7.4.1",
|
|
13
13
|
"plugin": "2.4.0",
|
|
14
14
|
"contractHash": "5e5c23651f92785a"
|
|
15
15
|
},
|
|
@@ -438,6 +438,32 @@ def _insert_direct_child(
|
|
|
438
438
|
container.insert(list(container).index(siblings[index]), element)
|
|
439
439
|
|
|
440
440
|
|
|
441
|
+
def _drop_layout_cache(paragraph_element: Any | None) -> None:
|
|
442
|
+
"""Remove a paragraph's line layout cache (``hp:linesegarray``) after its content changed.
|
|
443
|
+
|
|
444
|
+
The cache records where each line starts in the paragraph's content. Once an object or a run has left or
|
|
445
|
+
joined the paragraph, the cache no longer matches: Hancom reports a document whose cached lines point past
|
|
446
|
+
the remaining content as damaged, while without the cache it lays the paragraph out again. Only the
|
|
447
|
+
paragraph that changed is touched; every other paragraph keeps the cache Hancom wrote. ``None`` (an
|
|
448
|
+
element no paragraph holds) changes nothing.
|
|
449
|
+
"""
|
|
450
|
+
|
|
451
|
+
if paragraph_element is None:
|
|
452
|
+
return
|
|
453
|
+
for child in list(paragraph_element):
|
|
454
|
+
if _local_name(child).lower() == "linesegarray":
|
|
455
|
+
paragraph_element.remove(child)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _paragraph_element_of(element: Any) -> Any | None:
|
|
459
|
+
"""The nearest ``hp:p`` holding *element*, or ``None`` when it is detached."""
|
|
460
|
+
|
|
461
|
+
current = element
|
|
462
|
+
while current is not None and _local_name(current) != "p":
|
|
463
|
+
current = current.getparent() if hasattr(current, "getparent") else None
|
|
464
|
+
return current
|
|
465
|
+
|
|
466
|
+
|
|
441
467
|
def _insert_inline(
|
|
442
468
|
view: HwpxAgentDocument,
|
|
443
469
|
parent: NodeRecord,
|
|
@@ -468,6 +494,7 @@ def _insert_inline(
|
|
|
468
494
|
if _local_name(anchor_run) == "ctrl":
|
|
469
495
|
anchor_run = anchor_run.getparent()
|
|
470
496
|
paragraph.element.insert(list(paragraph.element).index(anchor_run), run)
|
|
497
|
+
_drop_layout_cache(paragraph.element)
|
|
471
498
|
paragraph.section.mark_dirty()
|
|
472
499
|
return run
|
|
473
500
|
|
|
@@ -482,6 +509,7 @@ def _remove_inline_element(element: Any, paragraph: Any) -> None:
|
|
|
482
509
|
run.remove(control)
|
|
483
510
|
if not list(run) and run in list(paragraph.element):
|
|
484
511
|
paragraph.element.remove(run)
|
|
512
|
+
_drop_layout_cache(paragraph.element)
|
|
485
513
|
paragraph.section.mark_dirty()
|
|
486
514
|
|
|
487
515
|
|
|
@@ -834,7 +862,8 @@ def _add(
|
|
|
834
862
|
if position["mode"] != "append":
|
|
835
863
|
parent.native.element.remove(created.element)
|
|
836
864
|
_insert_direct_child(view, parent, kind, created.element, position)
|
|
837
|
-
|
|
865
|
+
_drop_layout_cache(parent.native.element)
|
|
866
|
+
parent.native.section.mark_dirty()
|
|
838
867
|
return created
|
|
839
868
|
if kind == "table":
|
|
840
869
|
created = parent.native.add_table(
|
|
@@ -896,7 +925,9 @@ def _remove(document: HwpxDocument, record: NodeRecord) -> None:
|
|
|
896
925
|
if record.kind == "paragraph":
|
|
897
926
|
native.remove()
|
|
898
927
|
elif record.kind == "run":
|
|
928
|
+
paragraph_element = native.paragraph.element
|
|
899
929
|
native.remove()
|
|
930
|
+
_drop_layout_cache(paragraph_element)
|
|
900
931
|
elif record.kind == "table":
|
|
901
932
|
_remove_inline_element(native.element, native.paragraph)
|
|
902
933
|
elif record.kind == "row":
|
|
@@ -914,6 +945,7 @@ def _remove(document: HwpxDocument, record: NodeRecord) -> None:
|
|
|
914
945
|
if parent is None:
|
|
915
946
|
raise AgentContractError("not_found", "picture is detached", target=record.path)
|
|
916
947
|
parent.remove(_element(record.native))
|
|
948
|
+
_drop_layout_cache(_paragraph_element_of(parent))
|
|
917
949
|
_mark_containing_section(document, parent)
|
|
918
950
|
elif record.kind == "shape":
|
|
919
951
|
_remove_inline_element(native.element, native.paragraph)
|
|
@@ -934,6 +966,7 @@ def _detach(view: HwpxAgentDocument, record: NodeRecord) -> Any:
|
|
|
934
966
|
record.native.section.mark_dirty()
|
|
935
967
|
elif record.kind == "run":
|
|
936
968
|
record.native.paragraph.element.remove(element)
|
|
969
|
+
_drop_layout_cache(record.native.paragraph.element)
|
|
937
970
|
record.native.paragraph.section.mark_dirty()
|
|
938
971
|
elif record.kind in _INLINE_KINDS:
|
|
939
972
|
paragraph = record.native.paragraph if hasattr(record.native, "paragraph") else None
|
|
@@ -1002,6 +1035,7 @@ def _move(
|
|
|
1002
1035
|
if source.kind == "paragraph":
|
|
1003
1036
|
parent.native.mark_dirty()
|
|
1004
1037
|
elif source.kind == "run":
|
|
1038
|
+
_drop_layout_cache(parent.native.element)
|
|
1005
1039
|
parent.native.section.mark_dirty()
|
|
1006
1040
|
else:
|
|
1007
1041
|
_refresh_table_rows(source.native.table)
|
|
@@ -1157,6 +1191,7 @@ def _copy_node(
|
|
|
1157
1191
|
if source.kind == "paragraph":
|
|
1158
1192
|
parent.native.mark_dirty()
|
|
1159
1193
|
elif source.kind == "run":
|
|
1194
|
+
_drop_layout_cache(parent.native.element)
|
|
1160
1195
|
parent.native.section.mark_dirty()
|
|
1161
1196
|
else:
|
|
1162
1197
|
_refresh_table_rows(parent.native)
|
|
@@ -22,6 +22,7 @@ from hwpx.document import HwpxDocument
|
|
|
22
22
|
from hwpx.quality import QualityPolicy
|
|
23
23
|
|
|
24
24
|
import hwpx_automation.office.authoring.design._support as S
|
|
25
|
+
from .marked import OBJECT_TAGS, fill_placeholders, fragment_paragraphs, is_marked_profile
|
|
25
26
|
from .plan import Block, DocumentPlan
|
|
26
27
|
from .profile import Profile, load_profile
|
|
27
28
|
from .validator import StyleCoverage, style_coverage
|
|
@@ -82,7 +83,11 @@ def compose(
|
|
|
82
83
|
secpr = deepcopy(secpr)
|
|
83
84
|
|
|
84
85
|
new_paragraphs: list[etree._Element] = []
|
|
86
|
+
marked = is_marked_profile(prof.manifest)
|
|
85
87
|
for block in plan.iter_blocks():
|
|
88
|
+
if marked:
|
|
89
|
+
new_paragraphs.extend(_marked_fragment_for(prof, block, production, warnings))
|
|
90
|
+
continue
|
|
86
91
|
frag = _fragment_for(prof, block, production, warnings)
|
|
87
92
|
if frag is None:
|
|
88
93
|
continue
|
|
@@ -96,6 +101,7 @@ def compose(
|
|
|
96
101
|
S.move_secpr_into(new_paragraphs[0], secpr)
|
|
97
102
|
S.replace_section_body(section.element, new_paragraphs)
|
|
98
103
|
_reassign_paragraph_ids(new_paragraphs)
|
|
104
|
+
_reassign_object_ids(new_paragraphs)
|
|
99
105
|
section.mark_dirty()
|
|
100
106
|
|
|
101
107
|
coverage = style_coverage(doc, new_paragraphs)
|
|
@@ -188,6 +194,35 @@ def _fragment_for(
|
|
|
188
194
|
return frag
|
|
189
195
|
|
|
190
196
|
|
|
197
|
+
def _marked_fragment_for(
|
|
198
|
+
prof: Profile, block: Block, production: bool, warnings: list[str]
|
|
199
|
+
) -> list[etree._Element]:
|
|
200
|
+
"""Clone the author-named role and fill its slots (marked-template profiles)."""
|
|
201
|
+
|
|
202
|
+
if not prof.has_role(block.role):
|
|
203
|
+
_forbid_fallback(
|
|
204
|
+
production, f"marked profile {prof.id!r} has no role {block.role!r}", warnings
|
|
205
|
+
)
|
|
206
|
+
return []
|
|
207
|
+
values = dict(block.values)
|
|
208
|
+
slots = prof.manifest.get("slots", {}).get(block.role, [])
|
|
209
|
+
# Plain ``text`` fills the unnamed slot, or the only slot a role has.
|
|
210
|
+
if block.text and "" in slots and "" not in values:
|
|
211
|
+
values[""] = block.text
|
|
212
|
+
elif block.text and len(slots) == 1 and slots[0] not in values:
|
|
213
|
+
values[slots[0]] = block.text
|
|
214
|
+
paragraphs = fragment_paragraphs(prof.fragment(block.role))
|
|
215
|
+
unfilled = fill_placeholders(paragraphs, block.role, values)
|
|
216
|
+
if not values:
|
|
217
|
+
# A block with no text and no values asks for the role as is (a page
|
|
218
|
+
# break line, a separator) -- blank slots are what it wants.
|
|
219
|
+
return paragraphs
|
|
220
|
+
for slot in unfilled:
|
|
221
|
+
name = f"{block.role}.{slot}" if slot else block.role
|
|
222
|
+
warnings.append(f"MARKED_SLOT_EMPTY: no value for {{{{{name}}}}}; left blank")
|
|
223
|
+
return paragraphs
|
|
224
|
+
|
|
225
|
+
|
|
191
226
|
def _forbid_fallback(production: bool, message: str, warnings: list[str]) -> None:
|
|
192
227
|
if production:
|
|
193
228
|
raise ProfileRequiredError("PROFILE_REQUIRED: " + message)
|
|
@@ -279,4 +314,20 @@ def _reassign_paragraph_ids(paragraphs: list[etree._Element]) -> None:
|
|
|
279
314
|
para.set("id", str(uuid4().int % (2**31)))
|
|
280
315
|
|
|
281
316
|
|
|
317
|
+
# Composed bodies are made only of clones, so object ids restart from a fixed
|
|
318
|
+
# base — deterministic output, and no clone shares the id of another.
|
|
319
|
+
_OBJECT_ID_BASE = 1_000_000_000
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _reassign_object_ids(paragraphs: list[etree._Element]) -> None:
|
|
323
|
+
"""Give every table/drawing object a unique id (fragments may repeat)."""
|
|
324
|
+
|
|
325
|
+
next_id = _OBJECT_ID_BASE
|
|
326
|
+
for root in paragraphs:
|
|
327
|
+
for element in root.iter():
|
|
328
|
+
if S.ln(element.tag) in OBJECT_TAGS and element.get("id") is not None:
|
|
329
|
+
element.set("id", str(next_id))
|
|
330
|
+
next_id += 1
|
|
331
|
+
|
|
332
|
+
|
|
282
333
|
__all__ = ["compose", "compose_bytes", "ComposeResult", "ProfileRequiredError"]
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
Given a genuine ``.hwpx`` it writes, under ``profiles/<id>/``:
|
|
5
5
|
|
|
6
|
-
* ``template.hwpx`` — a **body-stripped skeleton**: the real document
|
|
7
|
-
section body
|
|
6
|
+
* ``template.hwpx`` — a **body-stripped skeleton**: the real document reduced to
|
|
7
|
+
its first section, whose body is a single ``secPr``-carrying paragraph. ``header.xml``
|
|
8
8
|
(every style/font/borderFill) and the page setup survive untouched, so the
|
|
9
9
|
committed artifact is small and carries no document content (no PII), yet is
|
|
10
10
|
genuinely Hancom-derived — never imagined XML.
|
|
@@ -160,6 +160,9 @@ def _subList(tc: etree._Element):
|
|
|
160
160
|
|
|
161
161
|
def _build_skeleton(source: Path, out_path: Path) -> dict:
|
|
162
162
|
doc = HwpxDocument.open(source)
|
|
163
|
+
# Only the first section's page setup is reused; later sections would ride
|
|
164
|
+
# into every composed document with the source's text.
|
|
165
|
+
_drop_later_sections(doc)
|
|
163
166
|
section = doc.sections[0]
|
|
164
167
|
secpr = S.find_secpr(section.element)
|
|
165
168
|
if secpr is None:
|
|
@@ -199,6 +202,26 @@ def _build_skeleton(source: Path, out_path: Path) -> dict:
|
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
|
|
205
|
+
def _drop_later_sections(doc) -> None:
|
|
206
|
+
"""Remove every section after the first, part and manifest item included.
|
|
207
|
+
|
|
208
|
+
Older cores remove the section from the document tree but keep its part,
|
|
209
|
+
which would still be saved with the source text in it.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
dropped = [section.part_name for section in doc.sections[1:]]
|
|
213
|
+
for extra in reversed(doc.sections[1:]):
|
|
214
|
+
doc.remove_section(extra)
|
|
215
|
+
package = doc._package
|
|
216
|
+
for item in list(package._manifest_items()):
|
|
217
|
+
href = item.get("href", "") or ""
|
|
218
|
+
if any(name.endswith(href) for name in dropped if href) and item.get("id"):
|
|
219
|
+
package.remove_manifest_item(item.get("id"))
|
|
220
|
+
for name in dropped:
|
|
221
|
+
if package.has_part(name):
|
|
222
|
+
package.delete(name)
|
|
223
|
+
|
|
224
|
+
|
|
202
225
|
def _strip_binaries(doc) -> list[str]:
|
|
203
226
|
"""Drop embedded images + the source preview (size + PII) from the skeleton.
|
|
204
227
|
|
|
@@ -209,6 +232,10 @@ def _strip_binaries(doc) -> list[str]:
|
|
|
209
232
|
|
|
210
233
|
package = doc._package
|
|
211
234
|
removed: list[str] = []
|
|
235
|
+
# Image fills defined in header.xml (borderFill imgBrush) are styles, not
|
|
236
|
+
# body content: dropping their binaries would leave dangling references.
|
|
237
|
+
kept_ids = _header_binary_refs(package)
|
|
238
|
+
kept_parts: set[str] = set()
|
|
212
239
|
|
|
213
240
|
for item in list(package._manifest_items()):
|
|
214
241
|
href = item.get("href", "") or ""
|
|
@@ -220,10 +247,15 @@ def _strip_binaries(doc) -> list[str]:
|
|
|
220
247
|
or media.lower().startswith("image/")
|
|
221
248
|
):
|
|
222
249
|
item_id = item.get("id")
|
|
250
|
+
if item_id in kept_ids:
|
|
251
|
+
kept_parts.add(href)
|
|
252
|
+
continue
|
|
223
253
|
if item_id:
|
|
224
254
|
package.remove_manifest_item(item_id)
|
|
225
255
|
|
|
226
256
|
for name in list(package.part_names()):
|
|
257
|
+
if any(name.endswith(href) for href in kept_parts):
|
|
258
|
+
continue
|
|
227
259
|
if name.startswith("BinData/") or name.startswith("Preview/PrvImage"):
|
|
228
260
|
try:
|
|
229
261
|
package.delete(name)
|
|
@@ -236,6 +268,16 @@ def _strip_binaries(doc) -> list[str]:
|
|
|
236
268
|
return removed
|
|
237
269
|
|
|
238
270
|
|
|
271
|
+
def _header_binary_refs(package) -> set[str]:
|
|
272
|
+
refs: set[str] = set()
|
|
273
|
+
for path in package.header_paths():
|
|
274
|
+
for node in package.get_xml(path).iter():
|
|
275
|
+
for key, value in node.attrib.items():
|
|
276
|
+
if S.ln(key) == "binaryItemIDRef":
|
|
277
|
+
refs.add(value)
|
|
278
|
+
return refs
|
|
279
|
+
|
|
280
|
+
|
|
239
281
|
# Manifest (content.hpf) metadata that carries source PII — author, the person who
|
|
240
282
|
# last saved, the source title, and timestamps. Cleared so generated docs inherit
|
|
241
283
|
# nothing identifying.
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Marked-template profiles — fragments named by the template author.
|
|
3
|
+
|
|
4
|
+
:func:`~hwpx_automation.office.authoring.design.harvest.harvest_profile` guesses
|
|
5
|
+
roles from font size and keeps a single info table, so it cannot tell a coloured
|
|
6
|
+
chapter-band table from a data table, or one bullet level from another. Here the
|
|
7
|
+
author decides instead: in a Hancom-saved template they type placeholders where
|
|
8
|
+
content goes —
|
|
9
|
+
|
|
10
|
+
* ``□ {{1단계}}`` — a paragraph role ``1단계`` with one unnamed slot;
|
|
11
|
+
* ``{{띠.번호}}`` / ``{{띠.제목}}`` in the cells of a band table — a role ``띠``
|
|
12
|
+
with the slots ``번호`` and ``제목``.
|
|
13
|
+
|
|
14
|
+
Every top-level paragraph that carries placeholders becomes one role. Text-less
|
|
15
|
+
paragraphs directly above it (the spacer lines Hancom users put between items)
|
|
16
|
+
belong to the same role, so spacing is cloned with it. Fills, borders, fonts and
|
|
17
|
+
indents are never interpreted: the composer clones the saved XML and swaps only
|
|
18
|
+
the placeholder text.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import re
|
|
24
|
+
from copy import deepcopy
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Mapping
|
|
27
|
+
|
|
28
|
+
from lxml import etree # pyright: ignore[reportAttributeAccessIssue]
|
|
29
|
+
|
|
30
|
+
from hwpx.document import HwpxDocument
|
|
31
|
+
|
|
32
|
+
import hwpx_automation.office.authoring.design._support as S
|
|
33
|
+
from .harvest import PROFILE_SCHEMA, _build_skeleton, _references_binary
|
|
34
|
+
|
|
35
|
+
MARKED_PROFILE_KIND = "marked"
|
|
36
|
+
# Wrapper for a multi-paragraph fragment file; never written into a document.
|
|
37
|
+
FRAGMENT_WRAPPER_TAG = "fragment"
|
|
38
|
+
|
|
39
|
+
PLACEHOLDER_RE = re.compile(r"\{\{\s*([^{}.\s]+)(?:\.([^{}\s]+))?\s*\}\}")
|
|
40
|
+
|
|
41
|
+
# Objects whose ``id`` must stay unique when a fragment is cloned more than once.
|
|
42
|
+
OBJECT_TAGS = frozenset(
|
|
43
|
+
{
|
|
44
|
+
"tbl", "pic", "rect", "ellipse", "arc", "polygon", "curve", "line",
|
|
45
|
+
"connectLine", "container", "ole", "equation", "textart",
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class MarkedTemplateError(ValueError):
|
|
51
|
+
"""The template's placeholders cannot be turned into a profile."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def is_marked_profile(manifest: Mapping[str, object]) -> bool:
|
|
55
|
+
return manifest.get("kind") == MARKED_PROFILE_KIND
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _text_pieces(element: etree._Element) -> list[tuple[etree._Element, str]]:
|
|
59
|
+
"""(owner, attr) pairs for every text piece of every ``<hp:t>`` under *element*.
|
|
60
|
+
|
|
61
|
+
``attr`` is ``"text"`` for ``t.text`` and ``"tail"`` for the tail of an inline
|
|
62
|
+
child (tab, line break) inside the ``<hp:t>``.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
pieces: list[tuple[etree._Element, str]] = []
|
|
66
|
+
for t in S.iter_local(element, "t"):
|
|
67
|
+
pieces.append((t, "text"))
|
|
68
|
+
for child in t:
|
|
69
|
+
pieces.append((child, "tail"))
|
|
70
|
+
return pieces
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _get(piece: tuple[etree._Element, str]) -> str:
|
|
74
|
+
owner, attr = piece
|
|
75
|
+
return (owner.text if attr == "text" else owner.tail) or ""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _set(piece: tuple[etree._Element, str], value: str) -> None:
|
|
79
|
+
owner, attr = piece
|
|
80
|
+
if attr == "text":
|
|
81
|
+
owner.text = value
|
|
82
|
+
else:
|
|
83
|
+
owner.tail = value
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def placeholders_in(element: etree._Element) -> list[tuple[str, str]]:
|
|
87
|
+
"""(role, slot) for every placeholder under *element*; slot ``""`` if unnamed."""
|
|
88
|
+
|
|
89
|
+
found: list[tuple[str, str]] = []
|
|
90
|
+
for para in S.iter_local(element, "p"):
|
|
91
|
+
joined = "".join(_get(p) for p in _direct_pieces(para))
|
|
92
|
+
found.extend((m.group(1), m.group(2) or "") for m in PLACEHOLDER_RE.finditer(joined))
|
|
93
|
+
return list(dict.fromkeys(found))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _direct_pieces(paragraph: etree._Element) -> list[tuple[etree._Element, str]]:
|
|
97
|
+
"""Text pieces of *paragraph* itself, excluding paragraphs nested in its tables."""
|
|
98
|
+
|
|
99
|
+
pieces: list[tuple[etree._Element, str]] = []
|
|
100
|
+
for run in S.children_local(paragraph, "run"):
|
|
101
|
+
for t in S.children_local(run, "t"):
|
|
102
|
+
pieces.append((t, "text"))
|
|
103
|
+
for child in t:
|
|
104
|
+
pieces.append((child, "tail"))
|
|
105
|
+
return pieces
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _is_blank(paragraph: etree._Element) -> bool:
|
|
109
|
+
if any(_get(p).strip() for p in _text_pieces(paragraph)):
|
|
110
|
+
return False
|
|
111
|
+
for node in paragraph.iter():
|
|
112
|
+
if S.ln(node.tag) in OBJECT_TAGS:
|
|
113
|
+
return False
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _strip_section_setup(fragment: etree._Element) -> None:
|
|
118
|
+
"""Drop the page setup a template's first paragraph carries (secPr + colPr ctrl)."""
|
|
119
|
+
|
|
120
|
+
for run in S.children_local(fragment, "run"):
|
|
121
|
+
for secpr in S.children_local(run, "secPr"):
|
|
122
|
+
run.remove(secpr)
|
|
123
|
+
for ctrl in S.children_local(run, "ctrl"):
|
|
124
|
+
if any(S.ln(c.tag) == "colPr" for c in ctrl):
|
|
125
|
+
run.remove(ctrl)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def fill_placeholders(
|
|
129
|
+
paragraphs: list[etree._Element], role: str, values: Mapping[str, str]
|
|
130
|
+
) -> list[str]:
|
|
131
|
+
"""Replace ``{{role}}``/``{{role.slot}}`` text in place; return unfilled slots.
|
|
132
|
+
|
|
133
|
+
A placeholder inside one text piece keeps that piece's run (and its style).
|
|
134
|
+
One that Hancom split across runs is rebuilt in the paragraph's first piece.
|
|
135
|
+
Missing values become empty text so a placeholder never ships.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
unfilled: list[str] = []
|
|
139
|
+
|
|
140
|
+
def replace(match: re.Match[str]) -> str:
|
|
141
|
+
if match.group(1) != role:
|
|
142
|
+
return match.group(0)
|
|
143
|
+
slot = match.group(2) or ""
|
|
144
|
+
if slot not in values:
|
|
145
|
+
unfilled.append(slot)
|
|
146
|
+
return ""
|
|
147
|
+
return str(values[slot])
|
|
148
|
+
|
|
149
|
+
for root in paragraphs:
|
|
150
|
+
for para in S.iter_local(root, "p"):
|
|
151
|
+
pieces = _direct_pieces(para)
|
|
152
|
+
for piece in pieces:
|
|
153
|
+
text = _get(piece)
|
|
154
|
+
if "{{" in text:
|
|
155
|
+
_set(piece, PLACEHOLDER_RE.sub(replace, text))
|
|
156
|
+
joined = "".join(_get(p) for p in pieces)
|
|
157
|
+
if any(m.group(1) == role for m in PLACEHOLDER_RE.finditer(joined)):
|
|
158
|
+
_set(pieces[0], PLACEHOLDER_RE.sub(replace, joined))
|
|
159
|
+
for piece in pieces[1:]:
|
|
160
|
+
_set(piece, "")
|
|
161
|
+
S.strip_lineseg(root)
|
|
162
|
+
return list(dict.fromkeys(unfilled))
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _pick_marked_fragments(
|
|
166
|
+
section_element: etree._Element,
|
|
167
|
+
) -> tuple[dict[str, list[etree._Element]], dict[str, list[str]], int]:
|
|
168
|
+
fragments: dict[str, list[etree._Element]] = {}
|
|
169
|
+
slots: dict[str, list[str]] = {}
|
|
170
|
+
pending: list[etree._Element] = []
|
|
171
|
+
ignored = 0
|
|
172
|
+
for para in S.children_local(section_element, "p"):
|
|
173
|
+
marks = placeholders_in(para)
|
|
174
|
+
if not marks:
|
|
175
|
+
if _is_blank(para):
|
|
176
|
+
pending.append(para)
|
|
177
|
+
else:
|
|
178
|
+
ignored += 1 # static content outside any role
|
|
179
|
+
pending = []
|
|
180
|
+
continue
|
|
181
|
+
roles = list(dict.fromkeys(role for role, _ in marks))
|
|
182
|
+
if len(roles) > 1:
|
|
183
|
+
raise MarkedTemplateError(
|
|
184
|
+
f"one paragraph carries placeholders of several roles {roles}; "
|
|
185
|
+
"put each role in its own paragraph or table"
|
|
186
|
+
)
|
|
187
|
+
role = roles[0]
|
|
188
|
+
if role in fragments:
|
|
189
|
+
raise MarkedTemplateError(f"role {role!r} appears more than once in the template")
|
|
190
|
+
group = [deepcopy(p) for p in (*pending, para)]
|
|
191
|
+
pending = []
|
|
192
|
+
for frag in group:
|
|
193
|
+
if _references_binary(frag):
|
|
194
|
+
raise MarkedTemplateError(
|
|
195
|
+
f"role {role!r} contains an image or embedded object; "
|
|
196
|
+
"fragments must be text, tables and shapes only"
|
|
197
|
+
)
|
|
198
|
+
_strip_section_setup(frag)
|
|
199
|
+
S.strip_lineseg(frag)
|
|
200
|
+
fragments[role] = group
|
|
201
|
+
slots[role] = [slot for _, slot in marks]
|
|
202
|
+
if not fragments:
|
|
203
|
+
raise MarkedTemplateError("template has no {{placeholder}} paragraphs")
|
|
204
|
+
return fragments, slots, ignored
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def harvest_marked_profile(source: str | Path, profile_id: str, out_root: str | Path) -> dict:
|
|
208
|
+
"""Harvest a placeholder-marked template into ``<out_root>/<profile_id>/``.
|
|
209
|
+
|
|
210
|
+
Only the first section is read. Returns the written manifest plus
|
|
211
|
+
``skeleton_open_safe`` and ``stripped_parts`` diagnostics.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
source = Path(source)
|
|
215
|
+
out_dir = Path(out_root) / profile_id
|
|
216
|
+
(out_dir / "fragments").mkdir(parents=True, exist_ok=True)
|
|
217
|
+
|
|
218
|
+
doc = HwpxDocument.open(source)
|
|
219
|
+
try:
|
|
220
|
+
fragments, slots, ignored = _pick_marked_fragments(doc.sections[0].element)
|
|
221
|
+
finally:
|
|
222
|
+
doc.close()
|
|
223
|
+
|
|
224
|
+
paths: dict[str, str] = {}
|
|
225
|
+
for index, (role, group) in enumerate(fragments.items()):
|
|
226
|
+
wrapper = etree.Element(FRAGMENT_WRAPPER_TAG)
|
|
227
|
+
wrapper.extend(group)
|
|
228
|
+
rel = f"fragments/{index:02d}.xml"
|
|
229
|
+
(out_dir / rel).write_bytes(etree.tostring(wrapper, encoding="UTF-8"))
|
|
230
|
+
paths[role] = rel
|
|
231
|
+
|
|
232
|
+
skel = _build_skeleton(source, out_dir / "template.hwpx")
|
|
233
|
+
manifest = {
|
|
234
|
+
"schemaVersion": PROFILE_SCHEMA,
|
|
235
|
+
"kind": MARKED_PROFILE_KIND,
|
|
236
|
+
"id": profile_id,
|
|
237
|
+
"template": "template.hwpx",
|
|
238
|
+
"fragments": paths,
|
|
239
|
+
"slots": slots,
|
|
240
|
+
"page": skel["page"],
|
|
241
|
+
"char_pr_count": skel["char_pr_count"],
|
|
242
|
+
"style_coverage_threshold": 0.98,
|
|
243
|
+
"source_basename": source.name,
|
|
244
|
+
"ignored_static_paragraphs": ignored,
|
|
245
|
+
}
|
|
246
|
+
(out_dir / "profile.json").write_text(
|
|
247
|
+
json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8"
|
|
248
|
+
)
|
|
249
|
+
return {
|
|
250
|
+
**manifest,
|
|
251
|
+
"skeleton_open_safe": skel["open_safe"],
|
|
252
|
+
"stripped_parts": skel.get("stripped_parts", []),
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def fragment_paragraphs(fragment: etree._Element) -> list[etree._Element]:
|
|
257
|
+
"""The ``<hp:p>`` list a (possibly wrapped) fragment element stands for."""
|
|
258
|
+
|
|
259
|
+
if fragment.tag == FRAGMENT_WRAPPER_TAG:
|
|
260
|
+
return list(fragment)
|
|
261
|
+
return [fragment]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
__all__ = [
|
|
265
|
+
"MARKED_PROFILE_KIND",
|
|
266
|
+
"PLACEHOLDER_RE",
|
|
267
|
+
"MarkedTemplateError",
|
|
268
|
+
"harvest_marked_profile",
|
|
269
|
+
"fill_placeholders",
|
|
270
|
+
"placeholders_in",
|
|
271
|
+
"fragment_paragraphs",
|
|
272
|
+
"is_marked_profile",
|
|
273
|
+
]
|
|
@@ -26,6 +26,8 @@ class Block:
|
|
|
26
26
|
text: str = ""
|
|
27
27
|
columns: list[str] = field(default_factory=list)
|
|
28
28
|
rows: list[list[str]] = field(default_factory=list)
|
|
29
|
+
#: Slot values for a marked-template role (``{{role.slot}}`` → value).
|
|
30
|
+
values: dict[str, str] = field(default_factory=dict)
|
|
29
31
|
|
|
30
32
|
@classmethod
|
|
31
33
|
def from_dict(cls, data: dict[str, Any]) -> "Block":
|
|
@@ -35,6 +37,7 @@ class Block:
|
|
|
35
37
|
text=str(data.get("text", "")),
|
|
36
38
|
columns=[str(c) for c in data.get("columns", [])],
|
|
37
39
|
rows=[[str(c) for c in row] for row in data.get("rows", [])],
|
|
40
|
+
values={str(k): str(v) for k, v in (data.get("values") or {}).items()},
|
|
38
41
|
)
|
|
39
42
|
|
|
40
43
|
|