python-hwpx 5.6.0__tar.gz → 5.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.
Files changed (232) hide show
  1. {python_hwpx-5.6.0/src/python_hwpx.egg-info → python_hwpx-5.7.0}/PKG-INFO +1 -1
  2. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/pyproject.toml +1 -1
  3. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/_document/fields.py +115 -0
  4. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/document.py +62 -0
  5. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/paragraph.py +85 -0
  6. {python_hwpx-5.6.0 → python_hwpx-5.7.0/src/python_hwpx.egg-info}/PKG-INFO +1 -1
  7. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/python_hwpx.egg-info/SOURCES.txt +1 -0
  8. python_hwpx-5.7.0/tests/test_check_box_authoring.py +154 -0
  9. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/LICENSE +0 -0
  10. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/NOTICE +0 -0
  11. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/README.md +0 -0
  12. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/setup.cfg +0 -0
  13. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/__init__.py +0 -0
  14. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/_document/__init__.py +0 -0
  15. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/_document/_units.py +0 -0
  16. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/_document/layout.py +0 -0
  17. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/_document/media.py +0 -0
  18. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/_document/memos.py +0 -0
  19. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/_document/persistence.py +0 -0
  20. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/_document/shapes.py +0 -0
  21. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/_document/tracked.py +0 -0
  22. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/body_patch.py +0 -0
  23. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/capabilities.py +0 -0
  24. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/data/Skeleton.hwpx +0 -0
  25. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/data/contract_docs/known-traps.md +0 -0
  26. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/data/contract_docs/mutation-semantics.md +0 -0
  27. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/data/contract_docs/recipes-traversal.md +0 -0
  28. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/data/contract_docs/support-matrix.md +0 -0
  29. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/equation/__init__.py +0 -0
  30. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/equation/authoring.py +0 -0
  31. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/equation/eqedit.py +0 -0
  32. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/equation/mathml.py +0 -0
  33. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/equation/render.py +0 -0
  34. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/equation/tokens.py +0 -0
  35. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/errors.py +0 -0
  36. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/experimental.py +0 -0
  37. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/form_fit/__init__.py +0 -0
  38. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/form_fit/apply.py +0 -0
  39. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/form_fit/engine.py +0 -0
  40. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/form_fit/measure.py +0 -0
  41. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/form_fit/policy.py +0 -0
  42. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/form_fit/report.py +0 -0
  43. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/ingest/__init__.py +0 -0
  44. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/ingest/base.py +0 -0
  45. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/ingest/hwpx_converter.py +0 -0
  46. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/layout/__init__.py +0 -0
  47. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/layout/lint.py +0 -0
  48. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/layout/report.py +0 -0
  49. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/mutation_report.py +0 -0
  50. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/opc/package.py +0 -0
  51. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/opc/relationships.py +0 -0
  52. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/opc/security.py +0 -0
  53. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/opc/xml_utils.py +0 -0
  54. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/__init__.py +0 -0
  55. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/_document_impl.py +0 -0
  56. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/_document_primitives.py +0 -0
  57. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/body.py +0 -0
  58. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/canonical_defaults.py +0 -0
  59. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/color.py +0 -0
  60. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/common.py +0 -0
  61. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/document.py +0 -0
  62. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/document_parts.py +0 -0
  63. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/header.py +0 -0
  64. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/header_part.py +0 -0
  65. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/memo.py +0 -0
  66. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/namespaces.py +0 -0
  67. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/numbering.py +0 -0
  68. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/objects.py +0 -0
  69. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/parser.py +0 -0
  70. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/run.py +0 -0
  71. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/schema.py +0 -0
  72. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/section.py +0 -0
  73. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/section_format.py +0 -0
  74. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/section_story.py +0 -0
  75. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/simple_parts.py +0 -0
  76. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/table.py +0 -0
  77. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/oxml/utils.py +0 -0
  78. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/package.py +0 -0
  79. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/patch.py +0 -0
  80. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/plan/__init__.py +0 -0
  81. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/plan/_execute.py +0 -0
  82. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/plan/_model.py +0 -0
  83. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/plan/_schema.py +0 -0
  84. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/plan/_validate.py +0 -0
  85. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/py.typed +0 -0
  86. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/quality/__init__.py +0 -0
  87. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/quality/ledger.py +0 -0
  88. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/quality/policy.py +0 -0
  89. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/quality/rendering.py +0 -0
  90. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/quality/report.py +0 -0
  91. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/quality/save_pipeline.py +0 -0
  92. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/table_patch.py +0 -0
  93. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/templates.py +0 -0
  94. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/__init__.py +0 -0
  95. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/_schemas/header.xsd +0 -0
  96. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/_schemas/section.xsd +0 -0
  97. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/archive_cli.py +0 -0
  98. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/doc_diff.py +0 -0
  99. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/document_viewer.py +0 -0
  100. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/exporter.py +0 -0
  101. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/generic_inventory.py +0 -0
  102. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/id_integrity.py +0 -0
  103. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/idempotence.py +0 -0
  104. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/ir_equality.py +0 -0
  105. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/layout_preview.py +0 -0
  106. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/mail_merge.py +0 -0
  107. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/markdown_export.py +0 -0
  108. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/object_finder.py +0 -0
  109. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/package_reconcile.py +0 -0
  110. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/package_validator.py +0 -0
  111. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/page_guard.py +0 -0
  112. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/read_fidelity.py +0 -0
  113. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/recover.py +0 -0
  114. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/redline.py +0 -0
  115. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/repair.py +0 -0
  116. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/report_utils.py +0 -0
  117. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/roundtrip_diff.py +0 -0
  118. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/table_cleanup.py +0 -0
  119. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/table_navigation.py +0 -0
  120. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/template_analyzer.py +0 -0
  121. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/text_extract_cli.py +0 -0
  122. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/text_extractor.py +0 -0
  123. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/toc_author.py +0 -0
  124. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/toc_fidelity.py +0 -0
  125. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/hwpx/tools/validator.py +0 -0
  126. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
  127. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/python_hwpx.egg-info/entry_points.txt +0 -0
  128. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/python_hwpx.egg-info/requires.txt +0 -0
  129. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/src/python_hwpx.egg-info/top_level.txt +0 -0
  130. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_add_control.py +0 -0
  131. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_adoption_metrics_snapshot.py +0 -0
  132. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_authoring_defaults.py +0 -0
  133. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_authoring_quality_corpus.py +0 -0
  134. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_blind_eval_fixture.py +0 -0
  135. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_byte_patch_identity.py +0 -0
  136. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_capabilities_surface.py +0 -0
  137. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_cell_line_spacing.py +0 -0
  138. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_chart_authoring.py +0 -0
  139. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_comment_node_robustness.py +0 -0
  140. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_contract_docs_sync.py +0 -0
  141. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_corpus_read_fidelity.py +0 -0
  142. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_coverage_promotion.py +0 -0
  143. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_coverage_targets.py +0 -0
  144. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_deviations_registry.py +0 -0
  145. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_doc_diff.py +0 -0
  146. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_document_context_manager.py +0 -0
  147. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_document_facade_surface.py +0 -0
  148. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_document_formatting.py +0 -0
  149. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_document_ops_runtime_boundary.py +0 -0
  150. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_document_save_api.py +0 -0
  151. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_document_viewer.py +0 -0
  152. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_documentation_code_fences.py +0 -0
  153. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_edit_plan.py +0 -0
  154. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_equation_authoring.py +0 -0
  155. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_equation_converter.py +0 -0
  156. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_equation_render.py +0 -0
  157. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_error_messages.py +0 -0
  158. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_evalplan_core_primitives.py +0 -0
  159. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_existing_document_format_editing.py +0 -0
  160. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_form_field_authoring.py +0 -0
  161. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_form_fields.py +0 -0
  162. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_form_fill_core_primitives.py +0 -0
  163. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_form_fit.py +0 -0
  164. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_form_fit_integration.py +0 -0
  165. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_fuzz_catalog_derive_expected.py +0 -0
  166. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_fuzz_loop.py +0 -0
  167. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_fuzz_regressions.py +0 -0
  168. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_gap_closure_tools.py +0 -0
  169. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_golden_api.py +0 -0
  170. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_hp_tab_support.py +0 -0
  171. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_hwpxlib_corpus_read.py +0 -0
  172. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_id_generator_range.py +0 -0
  173. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_image_object_workflow.py +0 -0
  174. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_ingest.py +0 -0
  175. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_inline_models.py +0 -0
  176. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_integration_hwpx_compatibility.py +0 -0
  177. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_integration_roundtrip.py +0 -0
  178. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_layout_cache_scope.py +0 -0
  179. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_layout_lint.py +0 -0
  180. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_layout_preview.py +0 -0
  181. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_library_boundary_closure.py +0 -0
  182. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_llms_txt.py +0 -0
  183. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_mail_merge_xlsx.py +0 -0
  184. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_markdown_export.py +0 -0
  185. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_memo_and_style_editing.py +0 -0
  186. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_moved_surface_hints.py +0 -0
  187. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_mutation_report.py +0 -0
  188. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_namespace_handling.py +0 -0
  189. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_new_features.py +0 -0
  190. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_note_authoring_contract.py +0 -0
  191. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_opc_package.py +0 -0
  192. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_open_safety_corpus.py +0 -0
  193. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_oxml_modularization.py +0 -0
  194. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_oxml_parsing.py +0 -0
  195. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_packaging_license_metadata.py +0 -0
  196. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_packaging_py_typed.py +0 -0
  197. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_paragraph_keep_together.py +0 -0
  198. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_paragraph_section_management.py +0 -0
  199. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_product_boundary.py +0 -0
  200. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_public_artifact_hygiene.py +0 -0
  201. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_public_runtime_boundary.py +0 -0
  202. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_read_fidelity.py +0 -0
  203. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_recover_broken_zip.py +0 -0
  204. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_redline_authoring.py +0 -0
  205. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_redline_verify.py +0 -0
  206. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_release_workflow_safety.py +0 -0
  207. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_repair_repack.py +0 -0
  208. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_report_utils.py +0 -0
  209. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_repr_snapshots.py +0 -0
  210. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_roundtrip_fidelity.py +0 -0
  211. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_row_heights.py +0 -0
  212. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_save_pipeline.py +0 -0
  213. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_section_headers.py +0 -0
  214. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_set_paragraph_format_keep.py +0 -0
  215. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_shape_geometry_contract.py +0 -0
  216. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_skeleton_template_ids.py +0 -0
  217. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_split_cell.py +0 -0
  218. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_split_merged_cell.py +0 -0
  219. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_stable_surface.py +0 -0
  220. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_table_cleanup.py +0 -0
  221. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_table_navigation.py +0 -0
  222. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_table_patch.py +0 -0
  223. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_table_patch_dryrun.py +0 -0
  224. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_table_patch_m105.py +0 -0
  225. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_tables_default_border.py +0 -0
  226. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_template_analyzer_enrichment.py +0 -0
  227. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_text_extractor_annotations.py +0 -0
  228. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_toc_author.py +0 -0
  229. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_toc_fidelity.py +0 -0
  230. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_validation_severity.py +0 -0
  231. {python_hwpx-5.6.0 → python_hwpx-5.7.0}/tests/test_validator_comment_nodes.py +0 -0
  232. {python_hwpx-5.6.0 → python_hwpx-5.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: 5.6.0
3
+ Version: 5.7.0
4
4
  Summary: 한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 문서 라이브러리
5
5
  Author: python-hwpx Maintainers
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-hwpx"
7
- version = "5.6.0"
7
+ version = "5.7.0"
8
8
  description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 문서 라이브러리"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = "Apache-2.0"
@@ -386,6 +386,121 @@ def add_form_field(
386
386
  )
387
387
 
388
388
 
389
+ _CHECK_BOX_TAG = f"{_HP}checkBtn"
390
+
391
+
392
+ def _iter_check_boxes(doc: "HwpxDocument") -> list[dict[str, Any]]:
393
+ """Every ``<hp:checkBtn>`` in document order, with its owning element."""
394
+
395
+ found: list[dict[str, Any]] = []
396
+ index = 0
397
+ for section_index, section in enumerate(doc.sections):
398
+ for element in section.element.iter(_CHECK_BOX_TAG):
399
+ found.append(
400
+ {
401
+ "index": index,
402
+ "sectionIndex": section_index,
403
+ "name": element.get("name", ""),
404
+ "caption": element.get("caption", ""),
405
+ "checked": element.get("value", "").upper() == "CHECKED",
406
+ "value": element.get("value", ""),
407
+ "_element": element,
408
+ "_section": section,
409
+ }
410
+ )
411
+ index += 1
412
+ return found
413
+
414
+
415
+ def list_check_boxes(doc: "HwpxDocument") -> list[dict[str, Any]]:
416
+ """Return check-box form objects in document order."""
417
+
418
+ return [
419
+ {key: value for key, value in match.items() if not key.startswith("_")}
420
+ for match in _iter_check_boxes(doc)
421
+ ]
422
+
423
+
424
+ def add_check_box(
425
+ doc: "HwpxDocument",
426
+ caption: str,
427
+ *,
428
+ checked: bool = False,
429
+ name: str | None = None,
430
+ paragraph: Any | None = None,
431
+ section: Any | None = None,
432
+ section_index: int | None = None,
433
+ ) -> dict[str, Any]:
434
+ """Create a check-box form object and return its payload.
435
+
436
+ The emitted XML follows the check-box contract measured against real
437
+ Hancom (specs/060): ``value`` selects ☑/□ and the ``<hp:formCharPr>`` child
438
+ is mandatory — without it Hancom refuses the document. The created object is
439
+ re-read through the standard :func:`list_check_boxes` reader, so creation
440
+ fails loudly if the ordinary consumer would not see it.
441
+ """
442
+
443
+ if not str(caption).strip():
444
+ raise ValueError("check box caption must be a non-empty string")
445
+ if paragraph is None:
446
+ paragraph = doc.add_paragraph(
447
+ "", section=section, section_index=section_index, include_run=False,
448
+ )
449
+
450
+ control = paragraph.add_check_box(caption, checked=checked, name=name)
451
+ _clear_form_field_layout_cache(paragraph.element)
452
+
453
+ created_name = control.element.get("name", "")
454
+ for match in _iter_check_boxes(doc):
455
+ if match["_element"] is control.element:
456
+ return {
457
+ key: value for key, value in match.items() if not key.startswith("_")
458
+ }
459
+ raise RuntimeError(
460
+ f"created check box {created_name!r} was not recognized by the standard reader"
461
+ )
462
+
463
+
464
+ def set_check_box(
465
+ doc: "HwpxDocument",
466
+ checked: bool,
467
+ *,
468
+ index: int | None = None,
469
+ name: str | None = None,
470
+ ) -> dict[str, Any]:
471
+ """Set a check box's state, selecting it by ``index`` or ``name``.
472
+
473
+ Exactly one selector is required — an ambiguous or missing selector is a
474
+ typed refusal, never a guess.
475
+ """
476
+
477
+ if (index is None) == (name is None):
478
+ raise ValueError("provide exactly one of index or name")
479
+ matches = _iter_check_boxes(doc)
480
+ if index is not None:
481
+ chosen = [m for m in matches if m["index"] == index]
482
+ if not chosen:
483
+ raise ValueError(f"check box index not found: {index}")
484
+ else:
485
+ wanted = str(name).strip()
486
+ chosen = [m for m in matches if m["name"] == wanted]
487
+ if not chosen:
488
+ raise ValueError(f"check box name not found: {wanted}")
489
+ if len(chosen) > 1:
490
+ raise ValueError(
491
+ f"check box name is ambiguous ({len(chosen)} matches): {wanted}"
492
+ )
493
+ match = chosen[0]
494
+ match["_element"].set("value", "CHECKED" if checked else "UNCHECKED")
495
+ match["_section"].mark_dirty()
496
+ return {
497
+ key: value
498
+ for key, value in {**match, "checked": bool(checked),
499
+ "value": "CHECKED" if checked else "UNCHECKED"}.items()
500
+ if not key.startswith("_")
501
+ }
502
+
503
+
389
504
  def _select_form_field(
390
505
  doc: "HwpxDocument",
391
506
  matches: Sequence[dict[str, Any]],
@@ -1462,6 +1462,68 @@ class HwpxDocument:
1462
1462
  section_index=section_index,
1463
1463
  )
1464
1464
 
1465
+ def list_check_boxes(self) -> list[dict[str, Any]]:
1466
+ """Return check-box form objects (체크박스) in document order.
1467
+
1468
+ Each entry carries ``index``/``name``/``caption``/``checked``.
1469
+ """
1470
+
1471
+ return _fields.list_check_boxes(self)
1472
+
1473
+ def add_check_box(
1474
+ self,
1475
+ caption: str,
1476
+ *,
1477
+ checked: bool = False,
1478
+ name: str | None = None,
1479
+ paragraph: HwpxOxmlParagraph | None = None,
1480
+ section: HwpxOxmlSection | None = None,
1481
+ section_index: int | None = None,
1482
+ ) -> dict[str, Any]:
1483
+ """Create a check-box form object (체크박스). **Experimental contract.**
1484
+
1485
+ Real Hancom draws ☑ when *checked* and □ otherwise, with *caption* beside
1486
+ the box and present in the rendered text layer. The created object is
1487
+ read back through :meth:`list_check_boxes` with no special-casing.
1488
+
1489
+ Note:
1490
+ Korean government forms specify a text ``[ ]`` + √ convention rather
1491
+ than this form object (시행규칙 별표 4 제10호), so this primitive is
1492
+ for forms that genuinely use Hancom check boxes — not for 공문서.
1493
+
1494
+ Args:
1495
+ caption: Label drawn beside the box (non-empty).
1496
+ checked: Initial state.
1497
+ name: Object name; generated when omitted.
1498
+ paragraph: Target paragraph (e.g. a table cell). A new paragraph is
1499
+ appended to *section* when omitted.
1500
+ """
1501
+
1502
+ return _fields.add_check_box(
1503
+ self,
1504
+ caption,
1505
+ checked=checked,
1506
+ name=name,
1507
+ paragraph=paragraph,
1508
+ section=section,
1509
+ section_index=section_index,
1510
+ )
1511
+
1512
+ def set_check_box(
1513
+ self,
1514
+ checked: bool,
1515
+ *,
1516
+ index: int | None = None,
1517
+ name: str | None = None,
1518
+ ) -> dict[str, Any]:
1519
+ """Set a check box's state, selecting it by ``index`` or ``name``.
1520
+
1521
+ Exactly one selector is required; an ambiguous name is refused rather
1522
+ than guessed.
1523
+ """
1524
+
1525
+ return _fields.set_check_box(self, checked, index=index, name=name)
1526
+
1465
1527
 
1466
1528
  def add_chart(
1467
1529
  self,
@@ -1147,6 +1147,91 @@ class HwpxOxmlParagraph:
1147
1147
  self.section.mark_dirty()
1148
1148
  return HwpxOxmlInlineObject(ctrl1, self)
1149
1149
 
1150
+ def add_check_box(
1151
+ self,
1152
+ caption: str,
1153
+ *,
1154
+ checked: bool = False,
1155
+ name: str | None = None,
1156
+ width: int = 9921,
1157
+ height: int = 1984,
1158
+ char_pr_id_ref: str | int | None = None,
1159
+ ) -> HwpxOxmlInlineObject:
1160
+ """Insert a check-box form object (체크박스) at the end of this paragraph.
1161
+
1162
+ Emits the real-Hancom ``<hp:checkBtn>`` shape. Unlike the click-here
1163
+ field this is **not** wrapped in ``<hp:ctrl>`` — it is a direct child of
1164
+ the run, beside the ``<hp:t>`` node.
1165
+
1166
+ Hancom refuses to open a document whose ``checkBtn`` lacks
1167
+ ``<hp:formCharPr>`` (measured: the same file with the element present
1168
+ opens, without it Hancom reports a read error and produces nothing), so
1169
+ the child block is always emitted. ``caption`` is drawn next to the box
1170
+ and appears in the rendered text layer; ``checked`` selects the
1171
+ ``CHECKED``/``UNCHECKED`` value that draws ☑ or □.
1172
+
1173
+ Args:
1174
+ caption: Label drawn beside the box.
1175
+ checked: Initial state.
1176
+ name: Object name. Defaults to a generated ``CheckBox<N>``.
1177
+ width: Box + caption width in HWPUNIT.
1178
+ height: Box height in HWPUNIT.
1179
+ """
1180
+
1181
+ run = self._create_run_for_object(char_pr_id_ref=char_pr_id_ref)
1182
+ check = _append_child(run, f"{_HP}checkBtn", {
1183
+ "caption": _sanitize_text(caption),
1184
+ "value": "CHECKED" if checked else "UNCHECKED",
1185
+ "radioGroupName": "",
1186
+ "triState": "0",
1187
+ "backStyle": "OPAQUE",
1188
+ "name": _sanitize_text(name) if name else f"CheckBox{_object_id()}",
1189
+ "foreColor": "#000000",
1190
+ "backColor": "#FFFFFF",
1191
+ "groupName": "",
1192
+ "tabStop": "1",
1193
+ "editable": "1",
1194
+ "tabOrder": "0",
1195
+ "enabled": "1",
1196
+ "borderTypeIDRef": "0",
1197
+ "drawFrame": "1",
1198
+ "printable": "1",
1199
+ "command": "",
1200
+ })
1201
+ _append_child(check, f"{_HP}formCharPr", {
1202
+ "charPrIDRef": str(char_pr_id_ref if char_pr_id_ref is not None else 0),
1203
+ "followContext": "0",
1204
+ "autoSz": "0",
1205
+ "wordWrap": "0",
1206
+ })
1207
+ _append_child(check, f"{_HP}sz", {
1208
+ "width": str(int(width)),
1209
+ "widthRelTo": "ABSOLUTE",
1210
+ "height": str(int(height)),
1211
+ "heightRelTo": "ABSOLUTE",
1212
+ "protect": "0",
1213
+ })
1214
+ _append_child(check, f"{_HP}pos", {
1215
+ "treatAsChar": "1",
1216
+ "affectLSpacing": "0",
1217
+ "flowWithText": "1",
1218
+ "allowOverlap": "1",
1219
+ "holdAnchorAndSO": "0",
1220
+ "vertRelTo": "PARA",
1221
+ "horzRelTo": "COLUMN",
1222
+ "vertAlign": "TOP",
1223
+ "horzAlign": "LEFT",
1224
+ "vertOffset": "0",
1225
+ "horzOffset": "0",
1226
+ })
1227
+ _append_child(check, f"{_HP}outMargin", {
1228
+ "left": "0", "right": "0", "top": "0", "bottom": "0",
1229
+ })
1230
+ _append_child(run, f"{_HP}t", {})
1231
+
1232
+ self.section.mark_dirty()
1233
+ return HwpxOxmlInlineObject(check, self)
1234
+
1150
1235
  @property
1151
1236
  def bookmarks(self) -> list[str]:
1152
1237
  """Return the names of all bookmarks in this paragraph."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx
3
- Version: 5.6.0
3
+ Version: 5.7.0
4
4
  Summary: 한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 문서 라이브러리
5
5
  Author: python-hwpx Maintainers
6
6
  License-Expression: Apache-2.0
@@ -133,6 +133,7 @@ tests/test_byte_patch_identity.py
133
133
  tests/test_capabilities_surface.py
134
134
  tests/test_cell_line_spacing.py
135
135
  tests/test_chart_authoring.py
136
+ tests/test_check_box_authoring.py
136
137
  tests/test_comment_node_robustness.py
137
138
  tests/test_contract_docs_sync.py
138
139
  tests/test_corpus_read_fidelity.py
@@ -0,0 +1,154 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """체크박스 양식개체 저작 계약 테스트 (specs/060 P0 실측 계약).
3
+
4
+ 실한컴 실측으로 확정된 불변식을 고정한다:
5
+ - ``value`` 어휘는 ``CHECKED``/``UNCHECKED``(각각 ☑/□로 렌더)
6
+ - **``<hp:formCharPr>``는 필수 자식** — 없으면 한컴이 문서를 거부한다
7
+ (단일 변인 프로브로 확정). 구조 검증(open-safety/ID 무결성)은 이 결함을
8
+ 잡지 못하므로 방출 형상 자체를 테스트로 고정한다.
9
+ - ``<hp:checkBtn>``은 ``<hp:ctrl>`` 래핑 없이 run 직속 자식(누름틀과 다름)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import zipfile
15
+
16
+ import pytest
17
+
18
+ from hwpx.document import HwpxDocument
19
+
20
+ HP = "{http://www.hancom.co.kr/hwpml/2011/paragraph}"
21
+
22
+
23
+ def _section_xml(path) -> str:
24
+ with zipfile.ZipFile(path) as archive:
25
+ return "".join(
26
+ archive.read(name).decode("utf-8")
27
+ for name in sorted(archive.namelist())
28
+ if name.endswith("section0.xml")
29
+ )
30
+
31
+
32
+ def test_add_check_box_emits_the_measured_hancom_shape(tmp_path) -> None:
33
+ doc = HwpxDocument.new()
34
+ doc.add_check_box("동의합니다", checked=True, name="Agree")
35
+ path = tmp_path / "checkbox.hwpx"
36
+ doc.save_to_path(path)
37
+ doc.close()
38
+
39
+ xml = _section_xml(path)
40
+ assert '<hp:checkBtn' in xml
41
+ assert 'value="CHECKED"' in xml
42
+ assert 'caption="동의합니다"' in xml
43
+ assert 'name="Agree"' in xml
44
+ # 필수 자식 3종 — formCharPr 누락은 실한컴 거부 사유다.
45
+ for child in ("formCharPr", "sz", "pos"):
46
+ assert f"<hp:{child}" in xml, f"missing mandatory child: {child}"
47
+ # 누름틀과 달리 ctrl 래핑을 쓰지 않는다.
48
+ assert "<hp:ctrl><hp:checkBtn" not in xml
49
+
50
+
51
+ def test_check_box_children_are_inside_the_element(tmp_path) -> None:
52
+ doc = HwpxDocument.new()
53
+ doc.add_check_box("자식 위치", checked=False)
54
+ path = tmp_path / "children.hwpx"
55
+ doc.save_to_path(path)
56
+ doc.close()
57
+
58
+ reopened = HwpxDocument.open(path)
59
+ checks = [
60
+ element
61
+ for section in reopened.sections
62
+ for element in section.element.iter(f"{HP}checkBtn")
63
+ ]
64
+ assert len(checks) == 1
65
+ names = [child.tag.split("}")[-1] for child in checks[0]]
66
+ assert "formCharPr" in names and "sz" in names and "pos" in names
67
+ reopened.close()
68
+
69
+
70
+ def test_unchecked_is_the_default_and_round_trips(tmp_path) -> None:
71
+ doc = HwpxDocument.new()
72
+ doc.add_check_box("기본 상태")
73
+ path = tmp_path / "default.hwpx"
74
+ doc.save_to_path(path)
75
+ doc.close()
76
+
77
+ assert 'value="UNCHECKED"' in _section_xml(path)
78
+ reopened = HwpxDocument.open(path)
79
+ boxes = reopened.list_check_boxes()
80
+ assert [b["caption"] for b in boxes] == ["기본 상태"]
81
+ assert boxes[0]["checked"] is False
82
+ reopened.close()
83
+
84
+
85
+ def test_list_and_set_by_index_and_name(tmp_path) -> None:
86
+ doc = HwpxDocument.new()
87
+ doc.add_check_box("첫째", name="First")
88
+ doc.add_check_box("둘째", checked=True, name="Second")
89
+
90
+ listed = doc.list_check_boxes()
91
+ assert [(b["index"], b["name"], b["checked"]) for b in listed] == [
92
+ (0, "First", False),
93
+ (1, "Second", True),
94
+ ]
95
+
96
+ assert doc.set_check_box(True, index=0)["checked"] is True
97
+ assert doc.set_check_box(False, name="Second")["value"] == "UNCHECKED"
98
+ assert [b["checked"] for b in doc.list_check_boxes()] == [True, False]
99
+
100
+ path = tmp_path / "toggled.hwpx"
101
+ doc.save_to_path(path)
102
+ doc.close()
103
+ reopened = HwpxDocument.open(path)
104
+ assert [b["checked"] for b in reopened.list_check_boxes()] == [True, False]
105
+ reopened.close()
106
+
107
+
108
+ def test_selector_and_caption_refusals_are_typed() -> None:
109
+ doc = HwpxDocument.new()
110
+ doc.add_check_box("하나", name="Dup")
111
+ doc.add_check_box("둘", name="Dup")
112
+
113
+ with pytest.raises(ValueError, match="exactly one of index or name"):
114
+ doc.set_check_box(True)
115
+ with pytest.raises(ValueError, match="exactly one of index or name"):
116
+ doc.set_check_box(True, index=0, name="Dup")
117
+ with pytest.raises(ValueError, match="ambiguous"):
118
+ doc.set_check_box(True, name="Dup")
119
+ with pytest.raises(ValueError, match="not found"):
120
+ doc.set_check_box(True, name="없는이름")
121
+ with pytest.raises(ValueError, match="index not found"):
122
+ doc.set_check_box(True, index=99)
123
+ with pytest.raises(ValueError, match="non-empty"):
124
+ doc.add_check_box(" ")
125
+ doc.close()
126
+
127
+
128
+ def test_check_box_inside_a_table_cell(tmp_path) -> None:
129
+ doc = HwpxDocument.new()
130
+ table = doc.add_table(2, 2)
131
+ cell_paragraph = table.rows[0].cells[1].paragraphs[0]
132
+ cell_paragraph.add_check_box("셀 안", checked=True)
133
+
134
+ path = tmp_path / "cell.hwpx"
135
+ doc.save_to_path(path)
136
+ doc.close()
137
+
138
+ reopened = HwpxDocument.open(path)
139
+ boxes = reopened.list_check_boxes()
140
+ assert [(b["caption"], b["checked"]) for b in boxes] == [("셀 안", True)]
141
+ reopened.close()
142
+
143
+
144
+ def test_authored_document_passes_open_safety(tmp_path) -> None:
145
+ from hwpx.tools.package_validator import validate_editor_open_safety
146
+
147
+ doc = HwpxDocument.new()
148
+ doc.add_check_box("열림 안전", checked=True)
149
+ path = tmp_path / "safe.hwpx"
150
+ doc.save_to_path(path)
151
+ doc.close()
152
+
153
+ report = validate_editor_open_safety(path).to_dict()
154
+ assert report["ok"] is True
File without changes
File without changes
File without changes
File without changes