python-hwpx 5.4.0__tar.gz → 5.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_hwpx-5.4.0/src/python_hwpx.egg-info → python_hwpx-5.5.0}/PKG-INFO +1 -1
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/pyproject.toml +1 -1
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/memo.py +26 -6
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/paragraph.py +101 -10
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/markdown_export.py +6 -2
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/text_extractor.py +14 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0/src/python_hwpx.egg-info}/PKG-INFO +1 -1
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/python_hwpx.egg-info/SOURCES.txt +1 -0
- python_hwpx-5.5.0/tests/test_note_authoring_contract.py +121 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/LICENSE +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/NOTICE +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/README.md +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/setup.cfg +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/_document/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/_document/_units.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/_document/fields.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/_document/layout.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/_document/media.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/_document/memos.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/_document/persistence.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/_document/shapes.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/_document/tracked.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/body_patch.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/data/Skeleton.hwpx +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/document.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/equation/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/equation/authoring.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/equation/eqedit.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/equation/mathml.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/equation/render.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/equation/tokens.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/errors.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/experimental.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/form_fit/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/form_fit/apply.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/form_fit/engine.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/form_fit/measure.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/form_fit/policy.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/form_fit/report.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/ingest/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/ingest/base.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/ingest/hwpx_converter.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/layout/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/layout/lint.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/layout/report.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/mutation_report.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/opc/package.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/opc/relationships.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/opc/security.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/opc/xml_utils.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/_document_impl.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/_document_primitives.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/body.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/canonical_defaults.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/color.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/common.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/document.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/document_parts.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/header.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/header_part.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/namespaces.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/numbering.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/objects.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/parser.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/run.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/schema.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/section.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/section_format.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/section_story.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/simple_parts.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/table.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/oxml/utils.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/package.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/patch.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/py.typed +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/quality/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/quality/ledger.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/quality/policy.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/quality/rendering.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/quality/report.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/quality/save_pipeline.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/table_patch.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/templates.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/__init__.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/_schemas/header.xsd +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/_schemas/section.xsd +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/archive_cli.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/doc_diff.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/document_viewer.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/exporter.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/generic_inventory.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/id_integrity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/idempotence.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/ir_equality.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/layout_preview.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/mail_merge.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/object_finder.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/package_reconcile.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/package_validator.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/page_guard.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/read_fidelity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/recover.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/redline.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/repair.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/report_utils.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/roundtrip_diff.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/table_cleanup.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/table_navigation.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/template_analyzer.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/text_extract_cli.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/toc_author.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/toc_fidelity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/hwpx/tools/validator.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/python_hwpx.egg-info/entry_points.txt +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/python_hwpx.egg-info/requires.txt +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/src/python_hwpx.egg-info/top_level.txt +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_add_control.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_adoption_metrics_snapshot.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_authoring_defaults.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_authoring_quality_corpus.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_blind_eval_fixture.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_byte_patch_identity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_cell_line_spacing.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_chart_authoring.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_comment_node_robustness.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_corpus_read_fidelity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_coverage_promotion.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_coverage_targets.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_deviations_registry.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_doc_diff.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_document_context_manager.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_document_facade_surface.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_document_formatting.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_document_ops_runtime_boundary.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_document_save_api.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_document_viewer.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_documentation_code_fences.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_equation_authoring.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_equation_converter.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_equation_render.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_error_messages.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_evalplan_core_primitives.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_existing_document_format_editing.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_form_field_authoring.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_form_fields.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_form_fill_core_primitives.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_form_fit.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_form_fit_integration.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_fuzz_catalog_derive_expected.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_fuzz_loop.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_fuzz_regressions.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_gap_closure_tools.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_golden_api.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_hp_tab_support.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_hwpxlib_corpus_read.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_id_generator_range.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_image_object_workflow.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_ingest.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_inline_models.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_integration_hwpx_compatibility.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_integration_roundtrip.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_layout_cache_scope.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_layout_lint.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_layout_preview.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_library_boundary_closure.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_llms_txt.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_mail_merge_xlsx.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_markdown_export.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_memo_and_style_editing.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_moved_surface_hints.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_mutation_report.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_namespace_handling.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_new_features.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_opc_package.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_open_safety_corpus.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_oxml_modularization.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_oxml_parsing.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_packaging_license_metadata.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_packaging_py_typed.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_paragraph_keep_together.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_paragraph_section_management.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_product_boundary.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_public_artifact_hygiene.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_public_runtime_boundary.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_read_fidelity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_recover_broken_zip.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_redline_authoring.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_redline_verify.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_release_workflow_safety.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_repair_repack.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_report_utils.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_repr_snapshots.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_roundtrip_fidelity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_row_heights.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_save_pipeline.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_section_headers.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_set_paragraph_format_keep.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_shape_geometry_contract.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_skeleton_template_ids.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_split_cell.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_split_merged_cell.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_stable_surface.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_table_cleanup.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_table_navigation.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_table_patch.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_table_patch_dryrun.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_table_patch_m105.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_tables_default_border.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_template_analyzer_enrichment.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_text_extractor_annotations.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_toc_author.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_toc_fidelity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_validation_severity.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_validator_comment_nodes.py +0 -0
- {python_hwpx-5.4.0 → python_hwpx-5.5.0}/tests/test_version_metadata.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-hwpx"
|
|
7
|
-
version = "5.
|
|
7
|
+
version = "5.5.0"
|
|
8
8
|
description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 문서 라이브러리"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -14,7 +14,6 @@ from ._document_primitives import (
|
|
|
14
14
|
_default_sublist_attributes,
|
|
15
15
|
_element_local_name,
|
|
16
16
|
_memo_id,
|
|
17
|
-
_paragraph_id,
|
|
18
17
|
_sanitize_text,
|
|
19
18
|
)
|
|
20
19
|
|
|
@@ -213,14 +212,35 @@ class HwpxOxmlNote:
|
|
|
213
212
|
|
|
214
213
|
@text.setter
|
|
215
214
|
def text(self, value: str) -> None:
|
|
216
|
-
"""Replace the note body text.
|
|
215
|
+
"""Replace the note body text.
|
|
216
|
+
|
|
217
|
+
The gold-shaped body (각주/미주 style paragraph with the leading
|
|
218
|
+
``<hp:autoNum>`` control real Hancom requires to render the note) is
|
|
219
|
+
preserved: only text content is replaced.
|
|
220
|
+
"""
|
|
217
221
|
sublist = self.element.find(f"{_HP}subList")
|
|
218
222
|
if sublist is None:
|
|
219
|
-
|
|
220
|
-
|
|
223
|
+
attrs = _default_sublist_attributes()
|
|
224
|
+
attrs["vertAlign"] = "TOP"
|
|
225
|
+
sublist = _append_child(self.element, f"{_HP}subList", attrs)
|
|
226
|
+
paragraphs = sublist.findall(f"{_HP}p")
|
|
227
|
+
template = paragraphs[0] if paragraphs else None
|
|
228
|
+
p_attrs = (
|
|
229
|
+
dict(template.attrib)
|
|
230
|
+
if template is not None
|
|
231
|
+
else {"id": "0", **_DEFAULT_PARAGRAPH_ATTRS}
|
|
232
|
+
)
|
|
233
|
+
first_run = template.find(f"{_HP}run") if template is not None else None
|
|
234
|
+
run_attrs = dict(first_run.attrib) if first_run is not None else {"charPrIDRef": "0"}
|
|
235
|
+
auto_num = (
|
|
236
|
+
first_run.find(f"{_HP}ctrl") if first_run is not None else None
|
|
237
|
+
)
|
|
238
|
+
for p in paragraphs:
|
|
221
239
|
sublist.remove(p)
|
|
222
|
-
paragraph = _append_child(sublist, f"{_HP}p",
|
|
223
|
-
run = _append_child(paragraph, f"{_HP}run",
|
|
240
|
+
paragraph = _append_child(sublist, f"{_HP}p", p_attrs)
|
|
241
|
+
run = _append_child(paragraph, f"{_HP}run", run_attrs)
|
|
242
|
+
if auto_num is not None:
|
|
243
|
+
run.append(auto_num)
|
|
224
244
|
t = _append_child(run, f"{_HP}t", {})
|
|
225
245
|
t.text = _sanitize_text(value)
|
|
226
246
|
self.paragraph.section.mark_dirty()
|
|
@@ -23,7 +23,6 @@ from ._document_primitives import (
|
|
|
23
23
|
_default_sublist_attributes,
|
|
24
24
|
_is_tab_control_element,
|
|
25
25
|
_object_id,
|
|
26
|
-
_paragraph_id,
|
|
27
26
|
_sanitize_text,
|
|
28
27
|
)
|
|
29
28
|
from .memo import HwpxOxmlNote
|
|
@@ -1183,6 +1182,61 @@ class HwpxOxmlParagraph:
|
|
|
1183
1182
|
# Footnote / Endnote helpers
|
|
1184
1183
|
# ------------------------------------------------------------------
|
|
1185
1184
|
|
|
1185
|
+
_NOTE_STYLE_NAMES = {"footNote": ("각주", "Footnote"), "endNote": ("미주", "Endnote")}
|
|
1186
|
+
_NOTE_STYLE_FALLBACK = {"footNote": ("15", "10", "3"), "endNote": ("16", "10", "3")}
|
|
1187
|
+
|
|
1188
|
+
def _note_style_refs(self, tag: str) -> tuple[str, str, str]:
|
|
1189
|
+
"""(styleIDRef, paraPrIDRef, charPrIDRef) for the note body paragraph.
|
|
1190
|
+
|
|
1191
|
+
Real Hancom puts note bodies on the 각주/미주 paragraph styles. Resolve
|
|
1192
|
+
by style name from the header so documents with re-numbered styles stay
|
|
1193
|
+
correct; fall back to the fixed template coordinates.
|
|
1194
|
+
"""
|
|
1195
|
+
|
|
1196
|
+
korean, english = self._NOTE_STYLE_NAMES[tag]
|
|
1197
|
+
document = self.section.document
|
|
1198
|
+
headers = cast(
|
|
1199
|
+
"Sequence[object]",
|
|
1200
|
+
getattr(document, "_headers", []) if document is not None else [],
|
|
1201
|
+
)
|
|
1202
|
+
for header in headers:
|
|
1203
|
+
styles = getattr(header, "_styles_element", None)
|
|
1204
|
+
container = cast(
|
|
1205
|
+
"ET.Element | None", styles() if callable(styles) else None
|
|
1206
|
+
)
|
|
1207
|
+
if container is None:
|
|
1208
|
+
continue
|
|
1209
|
+
for style in container:
|
|
1210
|
+
if style.get("name") == korean or style.get("engName") == english:
|
|
1211
|
+
return (
|
|
1212
|
+
style.get("id") or self._NOTE_STYLE_FALLBACK[tag][0],
|
|
1213
|
+
style.get("paraPrIDRef") or self._NOTE_STYLE_FALLBACK[tag][1],
|
|
1214
|
+
style.get("charPrIDRef") or self._NOTE_STYLE_FALLBACK[tag][2],
|
|
1215
|
+
)
|
|
1216
|
+
return self._NOTE_STYLE_FALLBACK[tag]
|
|
1217
|
+
|
|
1218
|
+
def _note_suffix_char(self, tag: str) -> str:
|
|
1219
|
+
"""The note suffix character from secPr, defaulting to ")"."""
|
|
1220
|
+
|
|
1221
|
+
properties = self.section._section_properties_element()
|
|
1222
|
+
if properties is not None:
|
|
1223
|
+
pr = properties.find(f"{_HP}{tag}Pr")
|
|
1224
|
+
if pr is not None:
|
|
1225
|
+
fmt = pr.find(f"{_HP}autoNumFormat")
|
|
1226
|
+
if fmt is not None and fmt.get("suffixChar"):
|
|
1227
|
+
return fmt.get("suffixChar", ")")
|
|
1228
|
+
return ")"
|
|
1229
|
+
|
|
1230
|
+
def _next_note_number(self, tag: str) -> int:
|
|
1231
|
+
"""Document-continuous note number, counted per note type."""
|
|
1232
|
+
|
|
1233
|
+
document = self.section.document
|
|
1234
|
+
sections = document.sections if document is not None else [self.section]
|
|
1235
|
+
count = 0
|
|
1236
|
+
for section in sections:
|
|
1237
|
+
count += sum(1 for _ in section.element.iter(f"{_HP}{tag}"))
|
|
1238
|
+
return count + 1
|
|
1239
|
+
|
|
1186
1240
|
def _add_note(
|
|
1187
1241
|
self,
|
|
1188
1242
|
tag: str,
|
|
@@ -1191,17 +1245,54 @@ class HwpxOxmlParagraph:
|
|
|
1191
1245
|
run_attributes: dict[str, str] | None = None,
|
|
1192
1246
|
char_pr_id_ref: str | int | None = None,
|
|
1193
1247
|
) -> HwpxOxmlNote:
|
|
1194
|
-
"""Insert a ``<hp:footNote>`` or ``<hp:endNote>`` element.
|
|
1248
|
+
"""Insert a ``<hp:footNote>`` or ``<hp:endNote>`` element.
|
|
1195
1249
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1250
|
+
Emits the real-Hancom shape (gold-reversed): the note element is
|
|
1251
|
+
wrapped in ``<hp:ctrl>`` inside a body run, carries ``number`` and
|
|
1252
|
+
``suffixChar``, and its body paragraph uses the 각주/미주 style with a
|
|
1253
|
+
leading ``<hp:autoNum>`` control — without these real Hancom does not
|
|
1254
|
+
render the note at all.
|
|
1255
|
+
"""
|
|
1256
|
+
|
|
1257
|
+
number = self._next_note_number(tag)
|
|
1258
|
+
suffix = self._note_suffix_char(tag)
|
|
1259
|
+
style_ref, para_pr_ref, note_char_ref = self._note_style_refs(tag)
|
|
1260
|
+
if char_pr_id_ref is not None:
|
|
1261
|
+
note_char_ref = str(char_pr_id_ref)
|
|
1262
|
+
|
|
1263
|
+
runs = self._run_elements()
|
|
1264
|
+
if run_attributes is None and runs:
|
|
1265
|
+
run = runs[-1]
|
|
1266
|
+
else:
|
|
1267
|
+
run = self._create_run_for_object(run_attributes, char_pr_id_ref=char_pr_id_ref)
|
|
1268
|
+
ctrl = _append_child(run, f"{_HP}ctrl", {})
|
|
1269
|
+
note_element = _append_child(
|
|
1270
|
+
ctrl,
|
|
1271
|
+
f"{_HP}{tag}",
|
|
1272
|
+
{
|
|
1273
|
+
"number": str(number),
|
|
1274
|
+
"suffixChar": str(ord(suffix[0])) if suffix else "41",
|
|
1275
|
+
"instId": _object_id(),
|
|
1276
|
+
},
|
|
1277
|
+
)
|
|
1278
|
+
sublist_attrs = _default_sublist_attributes()
|
|
1279
|
+
sublist_attrs["vertAlign"] = "TOP"
|
|
1280
|
+
sublist = _append_child(note_element, f"{_HP}subList", sublist_attrs)
|
|
1281
|
+
p_attrs = dict(_DEFAULT_PARAGRAPH_ATTRS)
|
|
1282
|
+
p_attrs.update({"id": "0", "paraPrIDRef": para_pr_ref, "styleIDRef": style_ref})
|
|
1200
1283
|
paragraph = _append_child(sublist, f"{_HP}p", p_attrs)
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1284
|
+
note_run = _append_child(paragraph, f"{_HP}run", {"charPrIDRef": note_char_ref})
|
|
1285
|
+
num_ctrl = _append_child(note_run, f"{_HP}ctrl", {})
|
|
1286
|
+
auto_num = _append_child(
|
|
1287
|
+
num_ctrl,
|
|
1288
|
+
f"{_HP}autoNum",
|
|
1289
|
+
{"num": str(number), "numType": "FOOTNOTE" if tag == "footNote" else "ENDNOTE"},
|
|
1290
|
+
)
|
|
1291
|
+
_append_child(
|
|
1292
|
+
auto_num,
|
|
1293
|
+
f"{_HP}autoNumFormat",
|
|
1294
|
+
{"type": "DIGIT", "userChar": "", "prefixChar": "", "suffixChar": suffix, "supscript": "0"},
|
|
1295
|
+
)
|
|
1205
1296
|
t = _append_child(note_run, f"{_HP}t", {})
|
|
1206
1297
|
t.text = _sanitize_text(text)
|
|
1207
1298
|
self.section.mark_dirty()
|
|
@@ -228,7 +228,7 @@ def _md_push_text(state: _MdParagraphState, cpr, text) -> None:
|
|
|
228
228
|
state.items.append((cpr, text))
|
|
229
229
|
|
|
230
230
|
|
|
231
|
-
def _md_handle_ctrl_child(child, state: _MdParagraphState, base_cp, chars) -> None:
|
|
231
|
+
def _md_handle_ctrl_child(child, state: _MdParagraphState, base_cp, chars, doc=None, notes_out=None) -> None:
|
|
232
232
|
for gc in child:
|
|
233
233
|
gctag = _local_name(gc)
|
|
234
234
|
if gctag == "fieldBegin" and gc.get("type") == "HYPERLINK":
|
|
@@ -236,6 +236,9 @@ def _md_handle_ctrl_child(child, state: _MdParagraphState, base_cp, chars) -> No
|
|
|
236
236
|
state.link_url = gc.get("name", "")
|
|
237
237
|
elif gctag == "fieldEnd":
|
|
238
238
|
_md_flush_link(state, base_cp, chars)
|
|
239
|
+
elif gctag in ("footNote", "endNote"):
|
|
240
|
+
# 실한컴 계약: 각주/미주는 run 안 <hp:ctrl>에 래핑되어 있다.
|
|
241
|
+
_md_handle_note_child(gc, gctag, state, base_cp, chars, doc, notes_out)
|
|
239
242
|
|
|
240
243
|
|
|
241
244
|
def _md_handle_note_child(
|
|
@@ -271,8 +274,9 @@ def _p_element_to_md(p_el, doc, notes_out: list | None = None) -> str:
|
|
|
271
274
|
if child.text:
|
|
272
275
|
_md_push_text(state, cpr, child.text)
|
|
273
276
|
elif tag == "ctrl":
|
|
274
|
-
_md_handle_ctrl_child(child, state, base_cp, chars)
|
|
277
|
+
_md_handle_ctrl_child(child, state, base_cp, chars, doc, notes_out)
|
|
275
278
|
elif tag in ("footNote", "endNote"):
|
|
279
|
+
# 구식(5.4 이전 자사 방출) 호환: run 직속 각주도 계속 읽는다.
|
|
276
280
|
_md_handle_note_child(child, tag, state, base_cp, chars, doc, notes_out)
|
|
277
281
|
|
|
278
282
|
_md_flush_items(state, base_cp, chars)
|
|
@@ -483,6 +483,20 @@ class TextExtractor:
|
|
|
483
483
|
annotations: Optional[AnnotationOptions],
|
|
484
484
|
preserve_breaks: bool,
|
|
485
485
|
) -> None:
|
|
486
|
+
# 실한컴 계약: 각주/미주는 <hp:ctrl> 래핑으로 나타난다 — annotations
|
|
487
|
+
# 유무와 무관하게 노트 분기로 위임한다(구식 run 직속도 별도 분기 유지).
|
|
488
|
+
for note_tag in ("footNote", "endNote"):
|
|
489
|
+
note = _first_child_by_local(element, note_tag)
|
|
490
|
+
if note is not None:
|
|
491
|
+
self._handle_note(
|
|
492
|
+
note,
|
|
493
|
+
fragments,
|
|
494
|
+
note_tag,
|
|
495
|
+
annotations=annotations,
|
|
496
|
+
preserve_breaks=preserve_breaks,
|
|
497
|
+
)
|
|
498
|
+
return
|
|
499
|
+
|
|
486
500
|
if annotations is None:
|
|
487
501
|
return
|
|
488
502
|
|
|
@@ -171,6 +171,7 @@ tests/test_moved_surface_hints.py
|
|
|
171
171
|
tests/test_mutation_report.py
|
|
172
172
|
tests/test_namespace_handling.py
|
|
173
173
|
tests/test_new_features.py
|
|
174
|
+
tests/test_note_authoring_contract.py
|
|
174
175
|
tests/test_opc_package.py
|
|
175
176
|
tests/test_open_safety_corpus.py
|
|
176
177
|
tests/test_oxml_modularization.py
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
|
|
5
|
+
from hwpx import HwpxDocument
|
|
6
|
+
from hwpx.tools.markdown_export import export_markdown
|
|
7
|
+
|
|
8
|
+
HP = "{http://www.hancom.co.kr/hwpml/2011/paragraph}"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _roundtrip(doc: HwpxDocument) -> HwpxDocument:
|
|
12
|
+
buffer = io.BytesIO()
|
|
13
|
+
doc.save_to_stream(buffer)
|
|
14
|
+
buffer.seek(0)
|
|
15
|
+
return HwpxDocument.open(buffer)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestGoldShapedEmission:
|
|
19
|
+
"""Real-Hancom footnote contract (specs/058 gold reverse): without the
|
|
20
|
+
ctrl wrapper, autoNum, and note styles Hancom does not render notes."""
|
|
21
|
+
|
|
22
|
+
def test_note_is_ctrl_wrapped_inside_the_host_run(self) -> None:
|
|
23
|
+
doc = HwpxDocument.new()
|
|
24
|
+
para = doc.add_paragraph("본문 앵커 문장")
|
|
25
|
+
para.add_footnote("각주 본문")
|
|
26
|
+
host_run = para.element.findall(f"{HP}run")[-1]
|
|
27
|
+
ctrl = host_run.find(f"{HP}ctrl")
|
|
28
|
+
assert ctrl is not None
|
|
29
|
+
assert ctrl.find(f"{HP}footNote") is not None
|
|
30
|
+
assert host_run.find(f"{HP}footNote") is None
|
|
31
|
+
|
|
32
|
+
def test_note_numbering_is_continuous_per_type(self) -> None:
|
|
33
|
+
doc = HwpxDocument.new()
|
|
34
|
+
p1 = doc.add_paragraph("첫 문단")
|
|
35
|
+
p2 = doc.add_paragraph("둘째 문단")
|
|
36
|
+
f1 = p1.add_footnote("하나")
|
|
37
|
+
f2 = p2.add_footnote("둘")
|
|
38
|
+
e1 = p2.add_endnote("미주 하나")
|
|
39
|
+
assert f1.element.get("number") == "1"
|
|
40
|
+
assert f2.element.get("number") == "2"
|
|
41
|
+
assert e1.element.get("number") == "1"
|
|
42
|
+
|
|
43
|
+
def test_note_body_carries_style_autonum_and_suffix(self) -> None:
|
|
44
|
+
doc = HwpxDocument.new()
|
|
45
|
+
para = doc.add_paragraph("앵커")
|
|
46
|
+
note = para.add_footnote("각주 텍스트")
|
|
47
|
+
el = note.element
|
|
48
|
+
assert el.get("suffixChar") == "41"
|
|
49
|
+
sub = el.find(f"{HP}subList")
|
|
50
|
+
assert sub.get("vertAlign") == "TOP"
|
|
51
|
+
body_p = sub.find(f"{HP}p")
|
|
52
|
+
assert body_p.get("styleIDRef") == "15"
|
|
53
|
+
assert body_p.get("paraPrIDRef") == "10"
|
|
54
|
+
run = body_p.find(f"{HP}run")
|
|
55
|
+
assert run.get("charPrIDRef") == "3"
|
|
56
|
+
auto = run.find(f"{HP}ctrl/{HP}autoNum")
|
|
57
|
+
assert auto.get("numType") == "FOOTNOTE"
|
|
58
|
+
assert auto.get("num") == "1"
|
|
59
|
+
fmt = auto.find(f"{HP}autoNumFormat")
|
|
60
|
+
assert fmt.get("suffixChar") == ")"
|
|
61
|
+
|
|
62
|
+
def test_endnote_uses_endnote_style_and_numtype(self) -> None:
|
|
63
|
+
doc = HwpxDocument.new()
|
|
64
|
+
note = doc.add_paragraph("앵커").add_endnote("미주")
|
|
65
|
+
body_p = note.element.find(f"{HP}subList/{HP}p")
|
|
66
|
+
assert body_p.get("styleIDRef") == "16"
|
|
67
|
+
auto = body_p.find(f"{HP}run/{HP}ctrl/{HP}autoNum")
|
|
68
|
+
assert auto.get("numType") == "ENDNOTE"
|
|
69
|
+
|
|
70
|
+
def test_text_setter_preserves_the_gold_body(self) -> None:
|
|
71
|
+
doc = HwpxDocument.new()
|
|
72
|
+
note = doc.add_paragraph("앵커").add_footnote("원래 텍스트")
|
|
73
|
+
note.text = "고친 텍스트"
|
|
74
|
+
body_p = note.element.find(f"{HP}subList/{HP}p")
|
|
75
|
+
assert body_p.get("styleIDRef") == "15"
|
|
76
|
+
run = body_p.find(f"{HP}run")
|
|
77
|
+
assert run.find(f"{HP}ctrl/{HP}autoNum") is not None
|
|
78
|
+
assert run.find(f"{HP}t").text == "고친 텍스트"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class TestReaderCoversBothShapes:
|
|
82
|
+
def test_markdown_reads_gold_shaped_notes(self) -> None:
|
|
83
|
+
doc = HwpxDocument.new()
|
|
84
|
+
para = doc.add_paragraph("누리호 발사 성공")
|
|
85
|
+
para.add_footnote("한국형 발사체")
|
|
86
|
+
md = export_markdown(_roundtrip(doc))
|
|
87
|
+
assert "[^fn" in md
|
|
88
|
+
assert "한국형 발사체" in md
|
|
89
|
+
|
|
90
|
+
def test_markdown_still_reads_legacy_run_direct_notes(self) -> None:
|
|
91
|
+
# 5.4 이전 자사 방출(run 직속 footNote) 호환 유지
|
|
92
|
+
doc = HwpxDocument.new()
|
|
93
|
+
para = doc.add_paragraph("본문")
|
|
94
|
+
run = para.element.makeelement(f"{HP}run", {"charPrIDRef": "0"})
|
|
95
|
+
para.element.append(run)
|
|
96
|
+
note = run.makeelement(f"{HP}footNote", {"instId": "9999"})
|
|
97
|
+
run.append(note)
|
|
98
|
+
sub = note.makeelement(f"{HP}subList", {"id": "", "vertAlign": "TOP"})
|
|
99
|
+
note.append(sub)
|
|
100
|
+
body_p = sub.makeelement(f"{HP}p", {"id": "0", "paraPrIDRef": "0", "styleIDRef": "0"})
|
|
101
|
+
sub.append(body_p)
|
|
102
|
+
body_run = body_p.makeelement(f"{HP}run", {"charPrIDRef": "0"})
|
|
103
|
+
body_p.append(body_run)
|
|
104
|
+
t = body_run.makeelement(f"{HP}t", {})
|
|
105
|
+
t.text = "구식 각주 본문"
|
|
106
|
+
body_run.append(t)
|
|
107
|
+
md = export_markdown(_roundtrip(doc))
|
|
108
|
+
assert "구식 각주 본문" in md
|
|
109
|
+
|
|
110
|
+
def test_notes_survive_roundtrip_and_are_discoverable(self) -> None:
|
|
111
|
+
doc = HwpxDocument.new()
|
|
112
|
+
para = doc.add_paragraph("앵커 문장")
|
|
113
|
+
para.add_footnote("왕복 각주")
|
|
114
|
+
reopened = _roundtrip(doc)
|
|
115
|
+
notes = [
|
|
116
|
+
el
|
|
117
|
+
for section in reopened.sections
|
|
118
|
+
for el in section.element.iter(f"{HP}footNote")
|
|
119
|
+
]
|
|
120
|
+
assert len(notes) == 1
|
|
121
|
+
assert notes[0].get("number") == "1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|