python-hwpx 6.6.0__tar.gz → 6.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_hwpx-6.6.0/src/python_hwpx.egg-info → python_hwpx-6.7.0}/PKG-INFO +7 -22
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/README.md +6 -21
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/pyproject.toml +7 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/__init__.py +11 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/_legacy.py +2 -2
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/fields.py +288 -24
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/layout.py +64 -8
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/media.py +38 -45
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/memos.py +32 -12
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/fields.py +39 -3
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/media.py +16 -2
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/notes.py +2 -2
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/parts.py +31 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/shapes.py +96 -9
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/styles.py +23 -5
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/persistence.py +8 -67
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/shapes.py +96 -7
- python_hwpx-6.7.0/src/hwpx/_moved_modules.py +118 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/data/contract_docs/known-traps.md +25 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/data/contract_docs/mutation-semantics.md +7 -4
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/data/contract_docs/support-matrix.md +5 -5
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/document.py +42 -5
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/authoring.py +25 -18
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/eqedit.py +61 -4
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/measure.py +89 -38
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/tokens.py +14 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/errors.py +13 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/experimental.py +8 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/form_fit/apply.py +7 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/form_fit/engine.py +15 -66
- python_hwpx-6.7.0/src/hwpx/form_fit/measure.py +1546 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/form_fit/policy.py +7 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/layout/lint.py +228 -27
- python_hwpx-6.7.0/src/hwpx/layout/pages.py +2245 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/__init__.py +2 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/form_field.py +51 -38
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/opc/relationships.py +72 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/_document_primitives.py +17 -22
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/_paragraph_text_edit.py +27 -0
- python_hwpx-6.7.0/src/hwpx/oxml/curves.py +231 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/document_parts.py +6 -3
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/drop_cap.py +22 -17
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/dutmal_compose.py +19 -10
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/field_marks.py +38 -7
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/header.py +39 -33
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/header_fonts.py +37 -6
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/header_part.py +20 -24
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/master_page_authoring.py +67 -5
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/memo.py +181 -3
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/note_authoring.py +1 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/numbering_kinds.py +35 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/objects.py +44 -27
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/paragraph.py +20 -13
- python_hwpx-6.7.0/src/hwpx/oxml/paragraph_heading.py +70 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/section.py +23 -5
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/section_format.py +25 -6
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/section_story.py +13 -1
- python_hwpx-6.7.0/src/hwpx/oxml/shape_position.py +282 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/table.py +36 -28
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/table_sizes.py +97 -11
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/quality/save_pipeline.py +4 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/table_patch.py +32 -8
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/document_merge.py +4 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/exporter.py +136 -17
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/id_integrity.py +22 -35
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/layout_preview.py +5 -2
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/package_validator.py +332 -22
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/repair.py +38 -2
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/template_analyzer.py +9 -6
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/toc_author.py +8 -6
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/validator.py +53 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0/src/python_hwpx.egg-info}/PKG-INFO +7 -22
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/python_hwpx.egg-info/SOURCES.txt +21 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_add_control.py +17 -5
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_add_heading.py +74 -8
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_cell_fields.py +47 -0
- python_hwpx-6.7.0/tests/test_cell_margins.py +311 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_cell_text_rewrite_semantics.py +10 -11
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_container_authoring.py +60 -29
- python_hwpx-6.7.0/tests/test_curve_connector_authoring.py +160 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_formatting.py +56 -8
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_merge.py +4 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_documentation_code_fences.py +2 -2
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_drop_cap_authoring.py +34 -5
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_dutmal_compose_authoring.py +34 -5
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_engine_surface_6_0.py +14 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_equation_authoring.py +42 -2
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_equation_converter.py +52 -0
- python_hwpx-6.7.0/tests/test_equation_measure_hancom_saved.py +97 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_equation_render.py +24 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_equation_size.py +12 -2
- python_hwpx-6.7.0/tests/test_export_notes.py +202 -0
- python_hwpx-6.7.0/tests/test_field_type_ids.py +120 -0
- python_hwpx-6.7.0/tests/test_form_fields.py +372 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_form_fit.py +201 -18
- python_hwpx-6.7.0/tests/test_form_fit_hancom_rules.py +721 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_gap_closure_tools.py +36 -16
- python_hwpx-6.7.0/tests/test_header_control_before_field.py +78 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hwp5_save.py +23 -5
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_image_object_workflow.py +64 -3
- python_hwpx-6.7.0/tests/test_instid_attribute_case.py +126 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_integration_hwpx_compatibility.py +1 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_layout_cache_scope.py +48 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_layout_lint.py +190 -2
- python_hwpx-6.7.0/tests/test_layout_page_estimate.py +758 -0
- python_hwpx-6.7.0/tests/test_list_continue.py +112 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_master_page_authoring.py +11 -0
- python_hwpx-6.7.0/tests/test_master_page_pages_taken.py +181 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_memo_and_style_editing.py +65 -0
- python_hwpx-6.7.0/tests/test_memo_fields.py +93 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_merge_cells_keeps_contents.py +4 -3
- python_hwpx-6.7.0/tests/test_moved_module_imports.py +105 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_native_edit_contract.py +6 -6
- python_hwpx-6.7.0/tests/test_ns_shapes.py +198 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_opc_package.py +7 -7
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_oxml_modularization.py +4 -3
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_package_hygiene.py +93 -2
- python_hwpx-6.7.0/tests/test_paragraph_property_lookup.py +51 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_product_boundary.py +8 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_recover_broken_zip.py +1 -1
- python_hwpx-6.7.0/tests/test_remove_unused_charts.py +104 -0
- python_hwpx-6.7.0/tests/test_remove_unused_images.py +84 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_repair_repack.py +4 -4
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_save_pipeline.py +16 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_shape_open_safety_risk_check.py +41 -33
- python_hwpx-6.7.0/tests/test_shape_original_size.py +167 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_stable_surface.py +3 -2
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_style_font_border_reads.py +78 -2
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_style_resolution.py +53 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_tab_definition_version_switch.py +67 -26
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_patch.py +1 -1
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_template_analyzer_enrichment.py +1 -0
- python_hwpx-6.7.0/tests/test_text_box_fields.py +87 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_toc_author.py +9 -1
- python_hwpx-6.7.0/tests/test_validator_form_parameters.py +96 -0
- python_hwpx-6.7.0/tests/test_validator_hancom_open_rules.py +313 -0
- python_hwpx-6.7.0/tests/test_validator_section_ids.py +141 -0
- python_hwpx-6.6.0/src/hwpx/form_fit/measure.py +0 -854
- python_hwpx-6.6.0/src/hwpx/oxml/shape_position.py +0 -128
- python_hwpx-6.6.0/tests/test_cell_margins.py +0 -168
- python_hwpx-6.6.0/tests/test_form_fields.py +0 -101
- python_hwpx-6.6.0/tests/test_form_fit_hancom_rules.py +0 -191
- python_hwpx-6.6.0/tests/test_instid_attribute_case.py +0 -100
- python_hwpx-6.6.0/tests/test_master_page_pages_taken.py +0 -83
- python_hwpx-6.6.0/tests/test_ns_shapes.py +0 -79
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/LICENSE +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/NOTICE +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/setup.cfg +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/__init__.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/_resolve.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/_units.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/headings.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/highlight.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/__init__.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/_base.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/page.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/refs.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/tables.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/text.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/tracking.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/tracked.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/body_patch.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/capabilities.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/data/Skeleton.hwpx +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/data/contract_docs/recipes-traversal.md +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/__init__.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/mathml.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/render.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/form_fit/__init__.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/form_fit/report.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/__init__.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/binary.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/bodytext.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/cfb.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/controls.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/docinfo.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/docinfo_writer.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/errors.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/fileheader.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/header_xml.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/owpml.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/package.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/reader.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/records.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/section_common.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/section_writer.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/section_xml.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/shape_xml.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/shapes.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/summary.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/writer.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/ingest/__init__.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/ingest/base.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/ingest/hwpx_converter.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/layout/__init__.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/layout/report.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/model.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/mutation_report.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/binary_item.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/checkbox.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/highlight.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/results.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/tracked.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/opc/package.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/opc/security.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/opc/xml_utils.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/__init__.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/_document_impl.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/body.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/canonical_defaults.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/color.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/common.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/document.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/document_metadata.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/header_compat.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/history_part.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/hyperlink_form.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/master_page.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/namespaces.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/numbering.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/parser.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/run.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/schema.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/section_layout.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/settings.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/simple_parts.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/table_merge.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/utils.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/version_part.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/package.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/patch.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/plan/__init__.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/plan/_execute.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/plan/_model.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/plan/_schema.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/plan/_validate.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/py.typed +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/quality/__init__.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/quality/ledger.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/quality/policy.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/quality/rendering.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/quality/report.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/templates.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/__init__.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/header.xsd +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/owpml-body.xsd +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/owpml-core.xsd +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/owpml-header.xsd +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/owpml-paralist.xsd +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/owpml-xml.xsd +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/section.xsd +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/archive_cli.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/doc_diff.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/document_viewer.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/generic_inventory.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/idempotence.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/ir_equality.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/mail_merge.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/markdown_export.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/object_finder.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/package_reconcile.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/page_guard.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/read_fidelity.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/recover.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/redline.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/report_utils.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/roundtrip_diff.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/table_cleanup.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/table_navigation.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/text_extract_cli.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/text_extractor.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/toc_fidelity.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/python_hwpx.egg-info/entry_points.txt +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/python_hwpx.egg-info/requires.txt +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/python_hwpx.egg-info/top_level.txt +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_adoption_metrics_snapshot.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_arc_authoring.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_authoring_defaults.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_authoring_quality_corpus.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_blind_eval_fixture.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_body_patch.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_border_fill_image_gradient.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_border_line_types.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_build_element_census.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_byte_patch_identity.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_capabilities_surface.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_cell_border_neighbors.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_cell_line_spacing.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_char_pr_child_order.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_character_width_ratio.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_chart_authoring.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_check_box_authoring.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_color_arguments.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_column_break.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_comment_node_robustness.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_contract_docs_sync.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_corpus_read_fidelity.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_coverage_ledger.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_coverage_promotion.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_coverage_targets.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_deviations_registry.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_doc_diff.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_context_manager.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_facade_surface.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_metadata.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_ops_runtime_boundary.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_save_api.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_viewer.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_edit_fidelity_corpus.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_edit_plan.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_editor_surface_inventory.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_equalize_widths_per_row.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_error_contract.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_error_messages.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_evalplan_core_primitives.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_existing_document_format_editing.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_export_captions_dutmal.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_export_cell_text_spaces.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_export_list_labels.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_export_text_boxes.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_exporter_table_text.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_field_marks.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_fill_image_hancom_modes.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_form_field_authoring.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_form_fill_core_primitives.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_form_fit_integration.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_fuzz_catalog_derive_expected.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_fuzz_loop.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_fuzz_regressions.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_golden_api.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_header_compat_authoring.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_heading_style_shapes.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_highlight_authoring.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hp_tab_support.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hwp5_container.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hwp5_hancom_files.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hwp5_open.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hwpxlib_corpus_read.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hyperlink_hancom_form.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_id_generator_range.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_id_integrity.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_index_mark.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ingest.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_inline_models.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_insert_row_merged_cells.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_integration_roundtrip.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_internal_calls_skip_moved_names.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_layout_preview.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_library_boundary_closure.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_line_width_arguments.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_list_defaults_vs_hancom.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_llms_txt.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_mail_merge_xlsx.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_manifest_resolve_once.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_markdown_export.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_markdown_export_shape_text.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_memo_shape_authoring.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_merge_cells_hancom_files.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_moved_surface_hints.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_mutation_report.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_namespace_handling.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_namespace_hygiene.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_namespace_normalization.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_new_features.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_note_authoring_contract.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_fields.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_media.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_notes.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_page.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_refs.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_tables.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_tracking.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_number_formats.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_open_safety_corpus.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_outline_numbering.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_oxml_parsing.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_packaging_license_metadata.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_packaging_py_typed.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_page_number_control.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_page_number_format.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_page_orientation.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_para_pr_dedup.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_align_distribute_space.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_border.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_format_targets.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_hyperlink_style.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_keep_together.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_margin_default_scale.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_property_version_switch.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_remove_in_containers.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_section_management.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_part_hierarchy_read_models.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_polygon_authoring.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_public_artifact_hygiene.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_public_runtime_boundary.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_read_fidelity.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_redline_authoring.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_redline_verify.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_release_workflow_safety.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_replace_text_barriers.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_report_utils.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_repr_snapshots.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_return_contract.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_roundtrip_fidelity.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_roundtrip_fidelity_corpus.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_row_heights.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_run_choice_atom_authoring.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_run_content_kinds.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_run_font_refs.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_run_style_base.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_safe_edit_example.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_save_keeps_inline_text_caches.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_save_verifies_once.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_section_clear_body.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_section_columns.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_section_headers.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_section_ids_order.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_section_properties_real_roundtrip.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_set_column_widths_uniform_split.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_set_paragraph_format_keep.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_shape_geometry_contract.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_shape_position_frames.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_skeleton_template_ids.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_split_cell.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_split_merged_cell.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_stale_layout_cache_sweep.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_story_edits_reach_control_copy.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_tab_character_writing.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_cell_equalize.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_cleanup.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_label.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_navigation.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_page_flow.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_patch_dryrun.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_patch_m105.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_patch_structure_fixes.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_split_merge.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_tables_default_border.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_text_extractor_annotations.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_text_extractor_nested_once.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_text_extractor_run_choice_atoms.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_text_inline_elements.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_text_replace_everywhere.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_title_mark.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_toc_fidelity.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_tracked_overlap_render_smoke.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_tracked_paragraph_delete_break.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_validation_severity.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_validator_comment_nodes.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_validator_full_schema.py +0 -0
- {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_version_metadata.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-hwpx
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.7.0
|
|
4
4
|
Summary: 한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 문서 라이브러리
|
|
5
5
|
Author: python-hwpx Maintainers
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -68,7 +68,7 @@ Dynamic: license-file
|
|
|
68
68
|
|
|
69
69
|
한컴오피스가 없어도 됩니다. HWPX는 ZIP+XML(OWPML) 포맷이라 순수 파이썬만으로
|
|
70
70
|
읽고, 고치고, 새로 만들 수 있습니다 — Windows·macOS·Linux·CI, 그리고
|
|
71
|
-
**파이썬이 도는 ChatGPT 채팅 안에서도** 그대로 동작합니다.
|
|
71
|
+
**파이썬이 도는 ChatGPT 채팅 안에서도** 그대로 동작합니다.
|
|
72
72
|
새 문서의 한컴 호환성은 아래에 날짜·버전을 명시한 코퍼스로 측정합니다.
|
|
73
73
|
|
|
74
74
|
<p align="center">
|
|
@@ -109,7 +109,7 @@ from hwpx import HwpxDocument
|
|
|
109
109
|
|
|
110
110
|
doc = HwpxDocument.new()
|
|
111
111
|
doc.add_heading("2026 운영계획", level=1)
|
|
112
|
-
doc.add_paragraph("
|
|
112
|
+
doc.add_paragraph("추진 배경", style="개요 2") # 한/글이 "가." 번호를 붙인다
|
|
113
113
|
doc.save_to_path("계획.hwpx")
|
|
114
114
|
```
|
|
115
115
|
|
|
@@ -130,7 +130,7 @@ doc.save_to_path("계획.hwpx")
|
|
|
130
130
|
- **편집** — 문단·표·이미지·머리글/바닥글·메모·각주, 줄간격·여백·쪽번호 같은 서식
|
|
131
131
|
- **양식 채우기** — 라벨로 셀을 찾아 값만 채우기, 행·열 조정 같은 구조 편집도 바이트 보존으로
|
|
132
132
|
- **생성·일괄 처리** — 새 문서 저작, 목차·상호참조, mail merge, 텍스트 diff, 변경추적(redline)
|
|
133
|
-
- **검증·안전** — 패키지 구조 검증 CLI, 열림 안전 게이트,
|
|
133
|
+
- **검증·안전** — 패키지 구조 검증 CLI, 열림 안전 게이트, 저장 결과 보고(`return_report=True`)
|
|
134
134
|
|
|
135
135
|
자세한 내용: [5분 퀵스타트](docs/quickstart.md) · [사용 가이드](docs/usage.md) · [API 레퍼런스](https://airmang.github.io/python-hwpx/) · [예제](docs/examples.md)
|
|
136
136
|
|
|
@@ -152,20 +152,6 @@ report = doc.save_to_path("신청서-작성완료.hwpx", mode="patch", fallback=
|
|
|
152
152
|
바이트 보존을 요구합니다. 수정 파트 내부의 보존과 시각적 배치는 별도 확인이
|
|
153
153
|
필요합니다. 출력 재개봉과 내용 확인까지 포함한 경로는 [안전한 쓰기 계약](docs/safe-write-contract.md)을 따르세요.
|
|
154
154
|
|
|
155
|
-
### 저장에는 영수증이 따라옵니다
|
|
156
|
-
|
|
157
|
-
```python
|
|
158
|
-
report = doc.save_to_path("결과.hwpx", return_report=True)
|
|
159
|
-
print(report.actual_mode) # "patch" 또는 "rebuild" — 실제 저장 등급
|
|
160
|
-
print(report.preservation.untouched_part_payloads.to_dict())
|
|
161
|
-
# {"verified": 17, "changed": 0}
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
`mode="patch", fallback="error"`는 보존 등급 미달 시 출력하지 않습니다.
|
|
165
|
-
기본 `auto`는 달성 가능한 등급을 선택합니다. `report.ok`는 요청 반영이나 시각
|
|
166
|
-
검증 완료를 뜻하지 않습니다.
|
|
167
|
-
전체 규칙: [안전한 쓰기 계약](docs/safe-write-contract.md).
|
|
168
|
-
|
|
169
155
|
*예제 중 **독립 실행 예제** 표시가 없는 블록은 여러분의 기존 문서를 입력으로
|
|
170
156
|
쓰는 조각입니다. 예제별 Python 블록 판정은
|
|
171
157
|
[실행 ledger](docs/python-example-ledger.json)에 동결돼 있습니다.*
|
|
@@ -175,10 +161,9 @@ print(report.preservation.untouched_part_payloads.to_dict())
|
|
|
175
161
|
만든 파일이 실제 한컴오피스에서 열리는지 재서 그대로 공개합니다. 측정마다
|
|
176
162
|
스택과 날짜를 병기합니다:
|
|
177
163
|
|
|
178
|
-
- **한컴 열림 120/120 · 렌더 검증 120/120** —
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
있어 `jq` 한 줄로 재현됩니다
|
|
164
|
+
- **한컴 열림 120/120 · 렌더 검증 120/120** — 기준 문서군 + 저작 기능
|
|
165
|
+
6종(각주·누름틀·수식·차트·체크박스·편집 계획) 축소 측정
|
|
166
|
+
(5.7.0 · 실한컴 12.0.0.3288 · 2026-08-03)
|
|
182
167
|
- **한컴 열림 476/476** — 전수 동결 코퍼스(N=497) 측정
|
|
183
168
|
(3.4.1 · 실한컴 12.0.0.3288 · 2026-07-19)
|
|
184
169
|
- **미수정 영역 바이트 보존 497/497** · 개인정보 유출 0
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
한컴오피스가 없어도 됩니다. HWPX는 ZIP+XML(OWPML) 포맷이라 순수 파이썬만으로
|
|
19
19
|
읽고, 고치고, 새로 만들 수 있습니다 — Windows·macOS·Linux·CI, 그리고
|
|
20
|
-
**파이썬이 도는 ChatGPT 채팅 안에서도** 그대로 동작합니다.
|
|
20
|
+
**파이썬이 도는 ChatGPT 채팅 안에서도** 그대로 동작합니다.
|
|
21
21
|
새 문서의 한컴 호환성은 아래에 날짜·버전을 명시한 코퍼스로 측정합니다.
|
|
22
22
|
|
|
23
23
|
<p align="center">
|
|
@@ -58,7 +58,7 @@ from hwpx import HwpxDocument
|
|
|
58
58
|
|
|
59
59
|
doc = HwpxDocument.new()
|
|
60
60
|
doc.add_heading("2026 운영계획", level=1)
|
|
61
|
-
doc.add_paragraph("
|
|
61
|
+
doc.add_paragraph("추진 배경", style="개요 2") # 한/글이 "가." 번호를 붙인다
|
|
62
62
|
doc.save_to_path("계획.hwpx")
|
|
63
63
|
```
|
|
64
64
|
|
|
@@ -79,7 +79,7 @@ doc.save_to_path("계획.hwpx")
|
|
|
79
79
|
- **편집** — 문단·표·이미지·머리글/바닥글·메모·각주, 줄간격·여백·쪽번호 같은 서식
|
|
80
80
|
- **양식 채우기** — 라벨로 셀을 찾아 값만 채우기, 행·열 조정 같은 구조 편집도 바이트 보존으로
|
|
81
81
|
- **생성·일괄 처리** — 새 문서 저작, 목차·상호참조, mail merge, 텍스트 diff, 변경추적(redline)
|
|
82
|
-
- **검증·안전** — 패키지 구조 검증 CLI, 열림 안전 게이트,
|
|
82
|
+
- **검증·안전** — 패키지 구조 검증 CLI, 열림 안전 게이트, 저장 결과 보고(`return_report=True`)
|
|
83
83
|
|
|
84
84
|
자세한 내용: [5분 퀵스타트](docs/quickstart.md) · [사용 가이드](docs/usage.md) · [API 레퍼런스](https://airmang.github.io/python-hwpx/) · [예제](docs/examples.md)
|
|
85
85
|
|
|
@@ -101,20 +101,6 @@ report = doc.save_to_path("신청서-작성완료.hwpx", mode="patch", fallback=
|
|
|
101
101
|
바이트 보존을 요구합니다. 수정 파트 내부의 보존과 시각적 배치는 별도 확인이
|
|
102
102
|
필요합니다. 출력 재개봉과 내용 확인까지 포함한 경로는 [안전한 쓰기 계약](docs/safe-write-contract.md)을 따르세요.
|
|
103
103
|
|
|
104
|
-
### 저장에는 영수증이 따라옵니다
|
|
105
|
-
|
|
106
|
-
```python
|
|
107
|
-
report = doc.save_to_path("결과.hwpx", return_report=True)
|
|
108
|
-
print(report.actual_mode) # "patch" 또는 "rebuild" — 실제 저장 등급
|
|
109
|
-
print(report.preservation.untouched_part_payloads.to_dict())
|
|
110
|
-
# {"verified": 17, "changed": 0}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
`mode="patch", fallback="error"`는 보존 등급 미달 시 출력하지 않습니다.
|
|
114
|
-
기본 `auto`는 달성 가능한 등급을 선택합니다. `report.ok`는 요청 반영이나 시각
|
|
115
|
-
검증 완료를 뜻하지 않습니다.
|
|
116
|
-
전체 규칙: [안전한 쓰기 계약](docs/safe-write-contract.md).
|
|
117
|
-
|
|
118
104
|
*예제 중 **독립 실행 예제** 표시가 없는 블록은 여러분의 기존 문서를 입력으로
|
|
119
105
|
쓰는 조각입니다. 예제별 Python 블록 판정은
|
|
120
106
|
[실행 ledger](docs/python-example-ledger.json)에 동결돼 있습니다.*
|
|
@@ -124,10 +110,9 @@ print(report.preservation.untouched_part_payloads.to_dict())
|
|
|
124
110
|
만든 파일이 실제 한컴오피스에서 열리는지 재서 그대로 공개합니다. 측정마다
|
|
125
111
|
스택과 날짜를 병기합니다:
|
|
126
112
|
|
|
127
|
-
- **한컴 열림 120/120 · 렌더 검증 120/120** —
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
있어 `jq` 한 줄로 재현됩니다
|
|
113
|
+
- **한컴 열림 120/120 · 렌더 검증 120/120** — 기준 문서군 + 저작 기능
|
|
114
|
+
6종(각주·누름틀·수식·차트·체크박스·편집 계획) 축소 측정
|
|
115
|
+
(5.7.0 · 실한컴 12.0.0.3288 · 2026-08-03)
|
|
131
116
|
- **한컴 열림 476/476** — 전수 동결 코퍼스(N=497) 측정
|
|
132
117
|
(3.4.1 · 실한컴 12.0.0.3288 · 2026-07-19)
|
|
133
118
|
- **미수정 영역 바이트 보존 497/497** · 개인정보 유출 0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-hwpx"
|
|
7
|
-
version = "6.
|
|
7
|
+
version = "6.7.0"
|
|
8
8
|
description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 문서 라이브러리"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -295,6 +295,9 @@ files = [
|
|
|
295
295
|
# Table width/height distribution (distribute cell width row by row) moved out
|
|
296
296
|
# of table.py -- born in the gate, per the WP-A rule.
|
|
297
297
|
"src/hwpx/oxml/table_sizes.py",
|
|
298
|
+
# A paragraph shape's heading (outline levels 8 to 10 in Hancom's hp:switch form),
|
|
299
|
+
# written and read in one place -- born in the gate, per the WP-A rule.
|
|
300
|
+
"src/hwpx/oxml/paragraph_heading.py",
|
|
298
301
|
# Floating-shape reference frames (set_position) and shape-text
|
|
299
302
|
# vertical alignment -- objects.py's overflow module (1600-line cap),
|
|
300
303
|
# born in the gate, per the WP-A rule.
|
|
@@ -501,6 +504,9 @@ include = [
|
|
|
501
504
|
# Table width/height distribution (distribute cell width row by row) moved out
|
|
502
505
|
# of table.py -- born in the gate, per the WP-A rule.
|
|
503
506
|
"src/hwpx/oxml/table_sizes.py",
|
|
507
|
+
# A paragraph shape's heading (outline levels 8 to 10 in Hancom's hp:switch form),
|
|
508
|
+
# written and read in one place -- born in the gate, per the WP-A rule.
|
|
509
|
+
"src/hwpx/oxml/paragraph_heading.py",
|
|
504
510
|
# Floating-shape reference frames (set_position) and shape-text
|
|
505
511
|
# vertical alignment -- objects.py's overflow module (1600-line cap),
|
|
506
512
|
# born in the gate, per the WP-A rule.
|
|
@@ -45,6 +45,10 @@ _EXPERIMENTAL_EXPORTS = {
|
|
|
45
45
|
"LayoutPreview": "hwpx.tools.layout_preview",
|
|
46
46
|
"PreviewPage": "hwpx.tools.layout_preview",
|
|
47
47
|
"render_layout_preview": "hwpx.tools.layout_preview",
|
|
48
|
+
# 쪽 수 추정(한/글 없이, 줄마다 쪽·단·세로 위치). 계약 유동.
|
|
49
|
+
"EstimatedLine": "hwpx.layout.pages",
|
|
50
|
+
"PageEstimate": "hwpx.layout.pages",
|
|
51
|
+
"estimate_pages": "hwpx.layout.pages",
|
|
48
52
|
# 문서 프리뷰 뷰어(3.8.0 신규). 계약 유동.
|
|
49
53
|
"DocumentViewer": "hwpx.tools.document_viewer",
|
|
50
54
|
"render_document_viewer": "hwpx.tools.document_viewer",
|
|
@@ -347,6 +351,13 @@ def __dir__() -> list[str]:
|
|
|
347
351
|
)
|
|
348
352
|
|
|
349
353
|
|
|
354
|
+
# ``import hwpx.builder`` goes through the import system, not ``__getattr__``,
|
|
355
|
+
# so the moved module names get their destination from a finder instead.
|
|
356
|
+
from . import _moved_modules
|
|
357
|
+
|
|
358
|
+
_moved_modules.install(_MOVED_TO_COMPANION)
|
|
359
|
+
|
|
360
|
+
|
|
350
361
|
# --- stable 최상위 표면 (eager import) ------------------------------------------
|
|
351
362
|
from .tools.text_extractor import (
|
|
352
363
|
DEFAULT_NAMESPACES,
|
|
@@ -615,7 +615,7 @@ class _LegacyFacade:
|
|
|
615
615
|
field_id: str | None = None,
|
|
616
616
|
author: str | None = None,
|
|
617
617
|
created: datetime | str | None = None,
|
|
618
|
-
number: int =
|
|
618
|
+
number: int | None = None,
|
|
619
619
|
anchor_char_pr_id_ref: str | int | None = None,
|
|
620
620
|
) -> tuple[HwpxOxmlMemo, HwpxOxmlParagraph, str]:
|
|
621
621
|
"""Create a memo and ensure it is visible by anchoring a MEMO field."""
|
|
@@ -801,7 +801,7 @@ class _LegacyFacade:
|
|
|
801
801
|
field_id: str | None = None,
|
|
802
802
|
author: str | None = None,
|
|
803
803
|
created: datetime | str | None = None,
|
|
804
|
-
number: int =
|
|
804
|
+
number: int | None = None,
|
|
805
805
|
char_pr_id_ref: str | int | None = None,
|
|
806
806
|
) -> str:
|
|
807
807
|
"""Attach a MEMO field control to *paragraph* so Hangul shows *memo*."""
|
|
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any, Iterator, Mapping, Sequence, cast
|
|
|
8
8
|
|
|
9
9
|
from ..errors import HwpxStateError, HwpxValueError
|
|
10
10
|
from ..objects.checkbox import CheckBox
|
|
11
|
-
from ..objects.form_field import CellField, FieldLocation, FieldParameter, FormField
|
|
11
|
+
from ..objects.form_field import CellField, FieldLocation, FieldParameter, FormField, TextBoxField
|
|
12
12
|
from ..objects.results import FieldFillResult
|
|
13
13
|
from ..oxml import HwpxOxmlParagraph
|
|
14
14
|
from ..oxml.namespaces import HP
|
|
@@ -88,6 +88,41 @@ def _sanitize_field_text(value: str) -> str:
|
|
|
88
88
|
return _TEXT_ILLEGAL.sub("", value)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
# Characters an hp:t holds as child elements; they belong to the text they sit in.
|
|
92
|
+
_TEXT_CHARACTER_NAMES = {"tab": "\t", "lineBreak": "\n", "hyphen": "", "nbSpace": "\u00a0", "fwSpace": "\u3000"}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _text_node_value(node: Any) -> str:
|
|
96
|
+
"""The text of an ``hp:t``, with ``hp:lineBreak`` as a newline and ``hp:tab`` as a tab."""
|
|
97
|
+
|
|
98
|
+
parts = [node.text or ""]
|
|
99
|
+
for child in node:
|
|
100
|
+
parts.append(_TEXT_CHARACTER_NAMES.get(_local_name(child), ""))
|
|
101
|
+
parts.append(child.tail or "")
|
|
102
|
+
return "".join(parts)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _write_text_node(node: Any, value: str) -> None:
|
|
106
|
+
"""Put *value* in the ``hp:t`` *node* in place of its text.
|
|
107
|
+
|
|
108
|
+
A line break (CRLF, LF or CR) becomes ``hp:lineBreak``, the element Hancom
|
|
109
|
+
turns such a character into when it opens the file. The characters of the
|
|
110
|
+
old text (tabs, line breaks) go; other marks stay, emptied.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
for child in list(node):
|
|
114
|
+
if _local_name(child) in _TEXT_CHARACTER_NAMES:
|
|
115
|
+
node.remove(child)
|
|
116
|
+
else:
|
|
117
|
+
child.tail = ""
|
|
118
|
+
lines = value.replace("\r\n", "\n").replace("\r", "\n").split("\n")
|
|
119
|
+
node.text = lines[0]
|
|
120
|
+
for line in lines[1:]:
|
|
121
|
+
line_break = node.makeelement(f"{_HP}lineBreak", {})
|
|
122
|
+
node.append(line_break)
|
|
123
|
+
line_break.tail = line
|
|
124
|
+
|
|
125
|
+
|
|
91
126
|
def _field_type_tokens(*values: str | None) -> set[str]:
|
|
92
127
|
tokens: set[str] = set()
|
|
93
128
|
for value in values:
|
|
@@ -125,6 +160,11 @@ def _field_identifier(field_begin: Any) -> str:
|
|
|
125
160
|
|
|
126
161
|
|
|
127
162
|
def _field_end_matches(field_begin: Any, field_end: Any) -> bool:
|
|
163
|
+
# An end that names its begin (beginIDRef) is that begin's end only: a fieldid
|
|
164
|
+
# is shared by every field of a type (Hancom's control id), so it pairs only
|
|
165
|
+
# ends that name no begin.
|
|
166
|
+
if field_begin.get("id") and field_end.get("beginIDRef"):
|
|
167
|
+
return field_begin.get("id") == field_end.get("beginIDRef")
|
|
128
168
|
begin_keys = {
|
|
129
169
|
value
|
|
130
170
|
for value in (
|
|
@@ -160,6 +200,18 @@ def _field_parameters(field_begin: Any) -> list[dict[str, str]]:
|
|
|
160
200
|
return parameters
|
|
161
201
|
|
|
162
202
|
|
|
203
|
+
def _field_name(field_begin: Any, parameters: Sequence[dict[str, str]]) -> str:
|
|
204
|
+
"""The field's name. A ``name`` attribute is the name even when empty: Hancom
|
|
205
|
+
treats such a field as unnamed (it lists and fills fields by name only)."""
|
|
206
|
+
|
|
207
|
+
if field_begin.get("name") is not None:
|
|
208
|
+
return field_begin.get("name", "").strip()
|
|
209
|
+
name = _first_attr(field_begin, _FORM_FIELD_NAME_ATTRS)
|
|
210
|
+
if not name:
|
|
211
|
+
name = _field_parameter_value(parameters, "fieldName", "fieldname", "field_name", "name", "title")
|
|
212
|
+
return name
|
|
213
|
+
|
|
214
|
+
|
|
163
215
|
def _first_attr(element: Any, names: Sequence[str]) -> str:
|
|
164
216
|
for name in names:
|
|
165
217
|
value = (element.get(name) or "").strip()
|
|
@@ -178,6 +230,21 @@ def _field_parameter_value(parameters: Sequence[dict[str, str]], *names: str) ->
|
|
|
178
230
|
return ""
|
|
179
231
|
|
|
180
232
|
|
|
233
|
+
_COMMAND_DIRECTION = re.compile(r"(?:^|[\s:])Direction:wstring:(\d+):")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _command_direction(parameters: Sequence[dict[str, str]]) -> str:
|
|
237
|
+
"""The prompt a field keeps only in its ``Command`` string
|
|
238
|
+
(``Clickhere:set:N:Direction:wstring:<length>:<prompt> HelpState:...``)."""
|
|
239
|
+
|
|
240
|
+
command = _field_parameter_value(parameters, "Command")
|
|
241
|
+
found = _COMMAND_DIRECTION.search(command)
|
|
242
|
+
if found is None:
|
|
243
|
+
return ""
|
|
244
|
+
start = found.end()
|
|
245
|
+
return command[start : start + int(found.group(1))]
|
|
246
|
+
|
|
247
|
+
|
|
181
248
|
def _clear_form_field_layout_cache(paragraph: Any) -> int:
|
|
182
249
|
removed = 0
|
|
183
250
|
for child in list(paragraph):
|
|
@@ -208,6 +275,40 @@ def _find_field_end_position(
|
|
|
208
275
|
return None
|
|
209
276
|
|
|
210
277
|
|
|
278
|
+
def _find_field_end_in_following_paragraphs(
|
|
279
|
+
paragraph_element: Any, field_begin: Any
|
|
280
|
+
) -> tuple[Any, list[Any], int, int, list[Any]] | None:
|
|
281
|
+
"""The end of a field whose content runs past its paragraph: the later
|
|
282
|
+
paragraph beside it that holds the fieldEnd naming this field's id, that
|
|
283
|
+
paragraph's runs, the end's run and child index, and the paragraphs in
|
|
284
|
+
between. Only an end that names the begin's id counts here."""
|
|
285
|
+
|
|
286
|
+
ids = {value for value in (field_begin.get("id"), field_begin.get("fieldid")) if value}
|
|
287
|
+
if not ids:
|
|
288
|
+
return None
|
|
289
|
+
middle: list[Any] = []
|
|
290
|
+
sibling = paragraph_element.getnext()
|
|
291
|
+
while sibling is not None:
|
|
292
|
+
if _local_name(sibling) == "p":
|
|
293
|
+
runs = [child for child in sibling if _local_name(child) == "run"]
|
|
294
|
+
for run_index, run in enumerate(runs):
|
|
295
|
+
for child_index, child in enumerate(run):
|
|
296
|
+
if _local_name(child) != "ctrl":
|
|
297
|
+
continue
|
|
298
|
+
for field_end in child.findall(f"{_HP}fieldEnd"):
|
|
299
|
+
if _field_end_matches(field_begin, field_end) and (
|
|
300
|
+
ids & {field_end.get("beginIDRef"), field_end.get("fieldid")}
|
|
301
|
+
):
|
|
302
|
+
return sibling, runs, run_index, child_index, middle
|
|
303
|
+
middle.append(sibling)
|
|
304
|
+
sibling = sibling.getnext()
|
|
305
|
+
return None
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _run_texts(runs: Sequence[Any]) -> str:
|
|
309
|
+
return "".join(_text_node_value(node) for run in runs for node in run if _local_name(node) == "t")
|
|
310
|
+
|
|
311
|
+
|
|
211
312
|
def _field_text_nodes(
|
|
212
313
|
doc: "HwpxDocument",
|
|
213
314
|
runs: Sequence[Any],
|
|
@@ -244,13 +345,14 @@ def _form_field_payload(
|
|
|
244
345
|
has_end: bool,
|
|
245
346
|
) -> dict[str, Any]:
|
|
246
347
|
parameters = _field_parameters(field_begin)
|
|
247
|
-
name =
|
|
248
|
-
if not name:
|
|
249
|
-
name = _field_parameter_value(parameters, "fieldName", "fieldname", "field_name", "name", "title")
|
|
348
|
+
name = _field_name(field_begin, parameters)
|
|
250
349
|
prompt = _first_attr(field_begin, _FORM_FIELD_PROMPT_ATTRS)
|
|
251
350
|
if not prompt:
|
|
252
351
|
# "Direction" is the real-Hancom 안내문 parameter (P0 gold contract).
|
|
253
352
|
prompt = _field_parameter_value(parameters, "Direction", *_FORM_FIELD_PARAM_NAMES)
|
|
353
|
+
if not prompt:
|
|
354
|
+
# Some fields keep it only inside their Command string.
|
|
355
|
+
prompt = _command_direction(parameters)
|
|
254
356
|
instruction = _field_parameter_value(parameters, "instruction", "guide", "help", "description", "desc")
|
|
255
357
|
if not instruction:
|
|
256
358
|
instruction = prompt
|
|
@@ -325,17 +427,38 @@ def _iter_form_field_matches(doc: "HwpxDocument") -> list[dict[str, Any]]:
|
|
|
325
427
|
)
|
|
326
428
|
end_run_index: int | None = None
|
|
327
429
|
end_child_index: int | None = None
|
|
430
|
+
span = None
|
|
328
431
|
if end_position is not None:
|
|
329
432
|
end_run_index, end_child_index, _field_end = end_position
|
|
433
|
+
else:
|
|
434
|
+
span = _find_field_end_in_following_paragraphs(paragraph_element, field_begin)
|
|
330
435
|
text_nodes = _field_text_nodes(
|
|
331
436
|
doc,
|
|
332
437
|
runs,
|
|
333
438
|
begin_run_index=run_index,
|
|
334
439
|
begin_child_index=child_index,
|
|
335
|
-
end_run_index=end_run_index,
|
|
440
|
+
end_run_index=end_run_index if span is None else len(runs) - 1,
|
|
336
441
|
end_child_index=end_child_index,
|
|
337
442
|
)
|
|
338
|
-
current_value = "".join(
|
|
443
|
+
current_value = "".join(_text_node_value(node) for node in text_nodes)
|
|
444
|
+
if span is not None:
|
|
445
|
+
# the content goes on over paragraphs: their texts, one per line
|
|
446
|
+
end_paragraph, end_runs, span_run, span_child, middle = span
|
|
447
|
+
tail = _field_text_nodes(
|
|
448
|
+
doc,
|
|
449
|
+
end_runs,
|
|
450
|
+
begin_run_index=0,
|
|
451
|
+
begin_child_index=-1,
|
|
452
|
+
end_run_index=span_run,
|
|
453
|
+
end_child_index=span_child,
|
|
454
|
+
)
|
|
455
|
+
current_value = "\n".join(
|
|
456
|
+
[
|
|
457
|
+
current_value,
|
|
458
|
+
*(_run_texts([c for c in p if _local_name(c) == "run"]) for p in middle),
|
|
459
|
+
"".join(_text_node_value(node) for node in tail),
|
|
460
|
+
]
|
|
461
|
+
)
|
|
339
462
|
payload = _form_field_payload(
|
|
340
463
|
doc,
|
|
341
464
|
index=len(matches),
|
|
@@ -347,7 +470,7 @@ def _iter_form_field_matches(doc: "HwpxDocument") -> list[dict[str, Any]]:
|
|
|
347
470
|
ctrl=child,
|
|
348
471
|
field_begin=field_begin,
|
|
349
472
|
current_value=current_value,
|
|
350
|
-
has_end=end_position is not None,
|
|
473
|
+
has_end=end_position is not None or span is not None,
|
|
351
474
|
)
|
|
352
475
|
payload["_paragraph"] = paragraph
|
|
353
476
|
payload["_runs"] = runs
|
|
@@ -356,6 +479,7 @@ def _iter_form_field_matches(doc: "HwpxDocument") -> list[dict[str, Any]]:
|
|
|
356
479
|
payload["_end_run_index"] = end_run_index
|
|
357
480
|
payload["_end_child_index"] = end_child_index
|
|
358
481
|
payload["_text_nodes"] = text_nodes
|
|
482
|
+
payload["_span"] = span
|
|
359
483
|
matches.append(payload)
|
|
360
484
|
paragraph_index += 1
|
|
361
485
|
return matches
|
|
@@ -401,7 +525,13 @@ def _form_field_from_match(doc: "HwpxDocument", match: Mapping[str, Any]) -> For
|
|
|
401
525
|
|
|
402
526
|
|
|
403
527
|
def list_form_fields(doc: "HwpxDocument") -> tuple[FormField, ...]:
|
|
404
|
-
"""Return native form/click-here fields in document order.
|
|
528
|
+
"""Return native form/click-here fields in document order.
|
|
529
|
+
|
|
530
|
+
Every field in the document is listed. An unnamed field (an empty ``name``) is
|
|
531
|
+
listed with an empty name and told apart by its id: :func:`fill_form_field`
|
|
532
|
+
does not find it by name, as Hancom does not, but ``field_index`` and
|
|
533
|
+
``field_id`` reach it.
|
|
534
|
+
"""
|
|
405
535
|
|
|
406
536
|
return tuple(_form_field_from_match(doc, match) for match in _iter_form_field_matches(doc))
|
|
407
537
|
|
|
@@ -442,6 +572,80 @@ def fill_cell_fields(doc: "HwpxDocument", value: str, *, name: str, index: int |
|
|
|
442
572
|
return tuple(fields)
|
|
443
573
|
|
|
444
574
|
|
|
575
|
+
def list_text_box_fields(doc: "HwpxDocument") -> tuple[TextBoxField, ...]:
|
|
576
|
+
"""Named text boxes in document order: Hancom lists a text box whose ``hp:drawText``
|
|
577
|
+
has a name among its fields (and leaves an unnamed one out)."""
|
|
578
|
+
|
|
579
|
+
return tuple(
|
|
580
|
+
TextBoxField(draw_text, section)
|
|
581
|
+
for section in doc.sections
|
|
582
|
+
for draw_text in section.element.iter(f"{_HP}drawText")
|
|
583
|
+
if (draw_text.get("name") or "").strip()
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def fill_text_box_fields(
|
|
588
|
+
doc: "HwpxDocument", value: str, *, name: str, index: int | None = None
|
|
589
|
+
) -> tuple[TextBoxField, ...]:
|
|
590
|
+
"""Set the text of every text box called *name*, or of the *index*-th of them only."""
|
|
591
|
+
|
|
592
|
+
wanted = (name or "").strip()
|
|
593
|
+
fields = [field for field in list_text_box_fields(doc) if wanted and field.name == wanted]
|
|
594
|
+
if index is not None:
|
|
595
|
+
fields = fields[index : index + 1] if index >= 0 else []
|
|
596
|
+
if not fields:
|
|
597
|
+
where = f"{wanted!r}" if index is None else f"{wanted!r} at index {index}"
|
|
598
|
+
raise HwpxValueError(
|
|
599
|
+
f"no text box named {where}",
|
|
600
|
+
code="field-text-box-not-found",
|
|
601
|
+
context={"name": wanted, "index": index},
|
|
602
|
+
suggestion="List doc.fields.text_boxes to see the text box names.",
|
|
603
|
+
)
|
|
604
|
+
for field in fields:
|
|
605
|
+
field.text = value
|
|
606
|
+
return tuple(fields)
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def text_box_text(draw_text: Any) -> str:
|
|
610
|
+
"""The text of a text box, a line per paragraph."""
|
|
611
|
+
|
|
612
|
+
return "\n".join(
|
|
613
|
+
"".join(_text_node_value(node) for node in paragraph.iter(f"{_HP}t"))
|
|
614
|
+
for paragraph in draw_text.findall(f"{_HP}subList/{_HP}p")
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def set_text_box_text(draw_text: Any, section: Any, value: str) -> None:
|
|
619
|
+
"""Put *value* in a text box as Hancom fills a text box field: the box keeps its
|
|
620
|
+
first paragraph and that paragraph's first run, which hold the value alone."""
|
|
621
|
+
|
|
622
|
+
sub_list = draw_text.find(f"{_HP}subList")
|
|
623
|
+
if sub_list is None:
|
|
624
|
+
sub_list = draw_text.makeelement(f"{_HP}subList", {})
|
|
625
|
+
draw_text.insert(0, sub_list)
|
|
626
|
+
paragraphs = sub_list.findall(f"{_HP}p")
|
|
627
|
+
first = paragraphs[0] if paragraphs else sub_list.makeelement(
|
|
628
|
+
f"{_HP}p", {"id": "0", "paraPrIDRef": "0", "styleIDRef": "0", "pageBreak": "0", "columnBreak": "0", "merged": "0"}
|
|
629
|
+
)
|
|
630
|
+
if not paragraphs:
|
|
631
|
+
sub_list.append(first)
|
|
632
|
+
for extra in paragraphs[1:]:
|
|
633
|
+
sub_list.remove(extra)
|
|
634
|
+
runs = first.findall(f"{_HP}run")
|
|
635
|
+
run = runs[0] if runs else first.makeelement(f"{_HP}run", {"charPrIDRef": "0"})
|
|
636
|
+
if not runs:
|
|
637
|
+
first.insert(0, run)
|
|
638
|
+
for extra in runs[1:]:
|
|
639
|
+
first.remove(extra)
|
|
640
|
+
for child in list(run):
|
|
641
|
+
run.remove(child)
|
|
642
|
+
text = run.makeelement(f"{_HP}t", {})
|
|
643
|
+
run.append(text)
|
|
644
|
+
_write_text_node(text, value)
|
|
645
|
+
_clear_form_field_layout_cache(first)
|
|
646
|
+
section.mark_dirty()
|
|
647
|
+
|
|
648
|
+
|
|
445
649
|
_PROMPT_TEXT_COLOR = "#FF0000"
|
|
446
650
|
|
|
447
651
|
|
|
@@ -686,6 +890,7 @@ def _select_form_field(
|
|
|
686
890
|
match
|
|
687
891
|
for match in matches
|
|
688
892
|
if wanted_name
|
|
893
|
+
and match.get("name")
|
|
689
894
|
and wanted_name
|
|
690
895
|
in {
|
|
691
896
|
str(match.get("name", "")).strip().casefold(),
|
|
@@ -735,10 +940,62 @@ def _insert_form_field_text_run(
|
|
|
735
940
|
runs: list[Any] = match["_runs"]
|
|
736
941
|
begin_run = runs[int(match["_begin_run_index"])]
|
|
737
942
|
text_node = begin_run.makeelement(f"{_HP}t", {})
|
|
738
|
-
text_node
|
|
943
|
+
_write_text_node(text_node, _sanitize_field_text(value))
|
|
739
944
|
begin_run.insert(int(match["_begin_child_index"]) + 1, text_node)
|
|
740
945
|
|
|
741
946
|
|
|
947
|
+
def _drop_nested_fields(match: Mapping[str, Any]) -> None:
|
|
948
|
+
"""Hancom's way with a field that holds other fields: the value replaces
|
|
949
|
+
them with the rest of the content, so the begin and end of every field
|
|
950
|
+
lying wholly between this field's begin and end go."""
|
|
951
|
+
|
|
952
|
+
runs: list[Any] = match["_runs"]
|
|
953
|
+
end_run_index = match.get("_end_run_index")
|
|
954
|
+
if end_run_index is None:
|
|
955
|
+
return
|
|
956
|
+
begin_run_index = int(match["_begin_run_index"])
|
|
957
|
+
inside: list[tuple[Any, Any]] = []
|
|
958
|
+
for run_index in range(begin_run_index, int(end_run_index) + 1):
|
|
959
|
+
children = list(runs[run_index])
|
|
960
|
+
start = int(match["_begin_child_index"]) + 1 if run_index == begin_run_index else 0
|
|
961
|
+
stop = int(match["_end_child_index"]) if run_index == end_run_index else len(children)
|
|
962
|
+
inside.extend((runs[run_index], child) for child in children[start:stop] if _local_name(child) == "ctrl")
|
|
963
|
+
begun = {mark.get("id") for _, ctrl in inside for mark in ctrl.findall(f"{_HP}fieldBegin")}
|
|
964
|
+
ended = {mark.get("beginIDRef") for _, ctrl in inside for mark in ctrl.findall(f"{_HP}fieldEnd")}
|
|
965
|
+
nested = (begun & ended) - {None, ""}
|
|
966
|
+
for run, ctrl in inside:
|
|
967
|
+
marks = [(mark.get("id") if _local_name(mark) == "fieldBegin" else mark.get("beginIDRef"))
|
|
968
|
+
for mark in ctrl if _local_name(mark) in ("fieldBegin", "fieldEnd")]
|
|
969
|
+
if marks and all(mark in nested for mark in marks):
|
|
970
|
+
run.remove(ctrl)
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
def _collapse_field_span(match: Mapping[str, Any]) -> None:
|
|
974
|
+
"""Hancom's way with a field whose content runs over paragraphs: the
|
|
975
|
+
content between the begin and the end goes, paragraphs and all, and the
|
|
976
|
+
end with the rest of its paragraph joins the begin's paragraph."""
|
|
977
|
+
|
|
978
|
+
end_paragraph, end_runs, end_run_index, end_child_index, middle = match["_span"]
|
|
979
|
+
paragraph_element = match["_paragraph"].element
|
|
980
|
+
runs: list[Any] = match["_runs"]
|
|
981
|
+
begin_run_index = int(match["_begin_run_index"])
|
|
982
|
+
begin_run = runs[begin_run_index]
|
|
983
|
+
for child in list(begin_run)[int(match["_begin_child_index"]) + 1 :]:
|
|
984
|
+
begin_run.remove(child)
|
|
985
|
+
for run in runs[begin_run_index + 1 :]:
|
|
986
|
+
paragraph_element.remove(run)
|
|
987
|
+
for element in middle:
|
|
988
|
+
element.getparent().remove(element)
|
|
989
|
+
end_run = end_runs[end_run_index]
|
|
990
|
+
moved = end_run.makeelement(end_run.tag, dict(end_run.attrib))
|
|
991
|
+
for child in list(end_run)[end_child_index:]:
|
|
992
|
+
moved.append(child)
|
|
993
|
+
position = paragraph_element.index(begin_run) + 1
|
|
994
|
+
for offset, run in enumerate([moved, *end_runs[end_run_index + 1 :]]):
|
|
995
|
+
paragraph_element.insert(position + offset, run)
|
|
996
|
+
end_paragraph.getparent().remove(end_paragraph)
|
|
997
|
+
|
|
998
|
+
|
|
742
999
|
def fill_form_field(
|
|
743
1000
|
doc: "HwpxDocument",
|
|
744
1001
|
value: str,
|
|
@@ -766,6 +1023,13 @@ def fill_form_field(
|
|
|
766
1023
|
field_id=field_id,
|
|
767
1024
|
name=name,
|
|
768
1025
|
)
|
|
1026
|
+
if not match.get("has_end"):
|
|
1027
|
+
raise HwpxValueError(
|
|
1028
|
+
f"the field {match.get('name') or match.get('index')!r} has no end, so it has no content to replace",
|
|
1029
|
+
code="field-end-missing",
|
|
1030
|
+
context={"name": match.get("name"), "index": match.get("index")},
|
|
1031
|
+
suggestion="Hancom leaves such a field unchanged too; give it an end or remove it.",
|
|
1032
|
+
)
|
|
769
1033
|
paragraph = match["_paragraph"]
|
|
770
1034
|
runs = match["_runs"]
|
|
771
1035
|
before_value = str(match.get("current_value", ""))
|
|
@@ -802,23 +1066,23 @@ def fill_form_field(
|
|
|
802
1066
|
|
|
803
1067
|
text_nodes: list[Any] = match.get("_text_nodes", [])
|
|
804
1068
|
sanitized = _sanitize_field_text(write_value)
|
|
805
|
-
if
|
|
1069
|
+
if match.get("_span") is None:
|
|
1070
|
+
_drop_nested_fields(match)
|
|
1071
|
+
if match.get("_span") is not None:
|
|
1072
|
+
_collapse_field_span(match)
|
|
1073
|
+
_insert_form_field_text_run(doc, match, sanitized)
|
|
1074
|
+
elif text_nodes:
|
|
806
1075
|
primary = text_nodes[0]
|
|
807
|
-
primary
|
|
808
|
-
for child in list(primary):
|
|
809
|
-
child.tail = ""
|
|
1076
|
+
_write_text_node(primary, sanitized)
|
|
810
1077
|
for node in text_nodes[1:]:
|
|
811
|
-
node
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
primary_run = primary.getparent()
|
|
820
|
-
if begin_ref is not None and primary_run is not None:
|
|
821
|
-
primary_run.set("charPrIDRef", begin_ref)
|
|
1078
|
+
_write_text_node(node, "")
|
|
1079
|
+
# Hancom writes the value in the shape of the run holding the field's
|
|
1080
|
+
# begin, whatever shape the old value or the screen-only prompt had.
|
|
1081
|
+
begin_run = runs[int(match["_begin_run_index"])]
|
|
1082
|
+
begin_ref = begin_run.get("charPrIDRef")
|
|
1083
|
+
primary_run = primary.getparent()
|
|
1084
|
+
if begin_ref is not None and primary_run is not None:
|
|
1085
|
+
primary_run.set("charPrIDRef", begin_ref)
|
|
822
1086
|
else:
|
|
823
1087
|
_insert_form_field_text_run(doc, match, sanitized)
|
|
824
1088
|
|