python-hwpx 2.11.0__tar.gz → 2.11.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. {python_hwpx-2.11.0/src/python_hwpx.egg-info → python_hwpx-2.11.1}/PKG-INFO +14 -1
  2. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/README.md +13 -0
  3. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/pyproject.toml +1 -1
  4. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/authoring.py +36 -2
  5. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/builder/core.py +20 -0
  6. {python_hwpx-2.11.0 → python_hwpx-2.11.1/src/python_hwpx.egg-info}/PKG-INFO +14 -1
  7. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_builder_core.py +9 -0
  8. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_document_plan.py +48 -0
  9. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/LICENSE +0 -0
  10. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/NOTICE +0 -0
  11. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/setup.cfg +0 -0
  12. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/__init__.py +0 -0
  13. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/builder/__init__.py +0 -0
  14. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/builder/report.py +0 -0
  15. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/data/Skeleton.hwpx +0 -0
  16. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/document.py +0 -0
  17. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/form_fill.py +0 -0
  18. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/opc/package.py +0 -0
  19. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/opc/relationships.py +0 -0
  20. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/opc/security.py +0 -0
  21. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/opc/xml_utils.py +0 -0
  22. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/__init__.py +0 -0
  23. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/_document_impl.py +0 -0
  24. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/body.py +0 -0
  25. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/common.py +0 -0
  26. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/document.py +0 -0
  27. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/header.py +0 -0
  28. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/header_part.py +0 -0
  29. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/memo.py +0 -0
  30. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/namespaces.py +0 -0
  31. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/numbering.py +0 -0
  32. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/objects.py +0 -0
  33. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/paragraph.py +0 -0
  34. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/parser.py +0 -0
  35. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/run.py +0 -0
  36. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/schema.py +0 -0
  37. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/section.py +0 -0
  38. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/simple_parts.py +0 -0
  39. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/table.py +0 -0
  40. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/oxml/utils.py +0 -0
  41. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/package.py +0 -0
  42. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/patch.py +0 -0
  43. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/presets/__init__.py +0 -0
  44. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/presets/proposal.py +0 -0
  45. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/py.typed +0 -0
  46. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/template_formfit.py +0 -0
  47. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/templates.py +0 -0
  48. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/__init__.py +0 -0
  49. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/_schemas/header.xsd +0 -0
  50. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/_schemas/section.xsd +0 -0
  51. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/advanced_generators.py +0 -0
  52. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/archive_cli.py +0 -0
  53. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/doc_diff.py +0 -0
  54. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/exporter.py +0 -0
  55. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/fuzz/__init__.py +0 -0
  56. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/fuzz/__main__.py +0 -0
  57. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/fuzz/catalog.py +0 -0
  58. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/fuzz/generator.py +0 -0
  59. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/fuzz/minimize.py +0 -0
  60. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/fuzz/runner.py +0 -0
  61. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/generic_inventory.py +0 -0
  62. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/id_integrity.py +0 -0
  63. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/layout_preview.py +0 -0
  64. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/mail_merge.py +0 -0
  65. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/markdown_export.py +0 -0
  66. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/object_finder.py +0 -0
  67. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/official_lint.py +0 -0
  68. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/package_validator.py +0 -0
  69. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/page_guard.py +0 -0
  70. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/recover.py +0 -0
  71. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/repair.py +0 -0
  72. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/report_parser.py +0 -0
  73. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/report_utils.py +0 -0
  74. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/roundtrip_diff.py +0 -0
  75. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/style_profile.py +0 -0
  76. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/table_cleanup.py +0 -0
  77. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/table_compute.py +0 -0
  78. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/table_navigation.py +0 -0
  79. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/template_analyzer.py +0 -0
  80. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/text_extract_cli.py +0 -0
  81. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/text_extractor.py +0 -0
  82. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/hwpx/tools/validator.py +0 -0
  83. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/python_hwpx.egg-info/SOURCES.txt +0 -0
  84. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
  85. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/python_hwpx.egg-info/entry_points.txt +0 -0
  86. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/python_hwpx.egg-info/requires.txt +0 -0
  87. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/src/python_hwpx.egg-info/top_level.txt +0 -0
  88. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_advanced_generators.py +0 -0
  89. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_builder_plan_v2.py +0 -0
  90. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_builder_vertical_slice.py +0 -0
  91. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_coverage_promotion.py +0 -0
  92. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_coverage_targets.py +0 -0
  93. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_deviations_registry.py +0 -0
  94. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_doc_diff.py +0 -0
  95. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_document_context_manager.py +0 -0
  96. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_document_formatting.py +0 -0
  97. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_document_plan_computed_fields.py +0 -0
  98. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_document_save_api.py +0 -0
  99. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_existing_document_format_editing.py +0 -0
  100. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_form_fields.py +0 -0
  101. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_form_fill_split_run.py +0 -0
  102. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_fuzz_loop.py +0 -0
  103. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_fuzz_regressions.py +0 -0
  104. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_gap_closure_tools.py +0 -0
  105. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_government_report_preset.py +0 -0
  106. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_government_table_profile.py +0 -0
  107. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_hp_tab_support.py +0 -0
  108. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_hwpxlib_corpus_read.py +0 -0
  109. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_id_generator_range.py +0 -0
  110. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_id_integrity.py +0 -0
  111. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_image_object_workflow.py +0 -0
  112. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_inline_models.py +0 -0
  113. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_integration_hwpx_compatibility.py +0 -0
  114. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_integration_roundtrip.py +0 -0
  115. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_kordoc_absorption.py +0 -0
  116. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_layout_preview.py +0 -0
  117. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_mail_merge_table_compute.py +0 -0
  118. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_markdown_export.py +0 -0
  119. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_memo_and_style_editing.py +0 -0
  120. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_namespace_handling.py +0 -0
  121. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_new_features.py +0 -0
  122. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_official_document_style.py +0 -0
  123. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_opc_package.py +0 -0
  124. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_open_safety_corpus.py +0 -0
  125. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_oxml_parsing.py +0 -0
  126. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_packaging_license_metadata.py +0 -0
  127. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_packaging_py_typed.py +0 -0
  128. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_paragraph_section_management.py +0 -0
  129. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_proposal_preset.py +0 -0
  130. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_recover_broken_zip.py +0 -0
  131. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_repair_repack.py +0 -0
  132. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_report_parser.py +0 -0
  133. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_report_utils.py +0 -0
  134. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_repr_snapshots.py +0 -0
  135. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_roundtrip_fidelity.py +0 -0
  136. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_section_headers.py +0 -0
  137. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_skeleton_template_ids.py +0 -0
  138. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_split_merged_cell.py +0 -0
  139. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_style_profile.py +0 -0
  140. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_table_cleanup.py +0 -0
  141. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_table_navigation.py +0 -0
  142. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_tables_default_border.py +0 -0
  143. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_template_analyzer_enrichment.py +0 -0
  144. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_template_formfit.py +0 -0
  145. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_text_extractor_annotations.py +0 -0
  146. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_validation_severity.py +0 -0
  147. {python_hwpx-2.11.0 → python_hwpx-2.11.1}/tests/test_version_metadata.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx
3
- Version: 2.11.0
3
+ Version: 2.11.1
4
4
  Summary: 한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리
5
5
  Author: python-hwpx Maintainers
6
6
  License-Expression: Apache-2.0
@@ -261,6 +261,19 @@ HWPX 파일은 **ZIP + XML** 구조이므로, 한/글 프로그램 없이 Python
261
261
  | 🧰 **작업 도구** | unpack/pack/분석/비교 | pack-ready 작업 디렉터리 추출과 재구성 점검 |
262
262
  | 🏗️ **저수준 XML** | 데이터클래스 매핑 | OWPML 스키마 ↔ Python 객체 직접 조작 |
263
263
  | 🔄 **네임스페이스 호환** | 자동 정규화 | HWPML 2016 → 2011 자동 변환 |
264
+ | 🏗️ **빌더** | 조립형 생성 | `hwpx.builder` — Section/Heading/Table/Image/Header 조립, 하드게이트 저장 리포트 |
265
+ | ✅ **편집기 오픈 안전** | `validate_editor_open_safety` | 저장/팩/리페어/빌더 출력 게이트, `openSafety` 증거 반환 |
266
+ | 🧪 **퍼징 수렴 루프** | `hwpx.tools.fuzz` | 시드 결정적 시나리오 생성 · 3중 오라클 러너 · 회귀 fixture 박제 |
267
+ | 🖥️ **레이아웃 프리뷰** | `hwpx.tools.layout_preview` | 페이지 박스·표·여백 근사 HTML/PNG (에이전트 자기검증용) |
268
+ | 🧷 **바이트 보존 패치** | `hwpx.patch` | section XML 바이트 splice — 미수정 영역 바이트 보존 |
269
+ | 📐 **기존 문서 서식 편집** | 문단·페이지 | 정렬·줄간격·들여쓰기·문단 간격, 용지·여백·방향, 머리말/쪽번호, 불릿/번호 |
270
+ | 🖊️ **누름틀** | 양식 필드 | 클릭히어 필드 조회·서식 보존 채움 |
271
+ | 🏛️ **공문서 도구** | `official_lint` · 결재란 | 항목기호 위계·"끝." 표시·붙임·날짜 표기 lint, 결재란 프리셋 |
272
+ | 📷 **고급 생성기** | `advanced_generators` | 사진대지(image_grid)·회의 명패·표 기반 조직도 |
273
+ | 🆚 **신구대조** | `doc_diff` | 문단 LCS diff·신구대조표 생성·참조 정합 lint |
274
+ | 📨 **메일머지·표 계산** | `mail_merge` | 템플릿+데이터 N부 대량 생성, 표 합계·평균 |
275
+ | 🪄 **서식 이식** | `style_profile` | 참조 문서 프로파일 추출·적용, 템플릿 레지스트리 |
276
+ | 🛡️ **입력 강건화** | `opc.security` | XML entity 폭탄·ZIP 압축 폭탄 가드 |
264
277
 
265
278
  ## 기능 상세
266
279
 
@@ -225,6 +225,19 @@ HWPX 파일은 **ZIP + XML** 구조이므로, 한/글 프로그램 없이 Python
225
225
  | 🧰 **작업 도구** | unpack/pack/분석/비교 | pack-ready 작업 디렉터리 추출과 재구성 점검 |
226
226
  | 🏗️ **저수준 XML** | 데이터클래스 매핑 | OWPML 스키마 ↔ Python 객체 직접 조작 |
227
227
  | 🔄 **네임스페이스 호환** | 자동 정규화 | HWPML 2016 → 2011 자동 변환 |
228
+ | 🏗️ **빌더** | 조립형 생성 | `hwpx.builder` — Section/Heading/Table/Image/Header 조립, 하드게이트 저장 리포트 |
229
+ | ✅ **편집기 오픈 안전** | `validate_editor_open_safety` | 저장/팩/리페어/빌더 출력 게이트, `openSafety` 증거 반환 |
230
+ | 🧪 **퍼징 수렴 루프** | `hwpx.tools.fuzz` | 시드 결정적 시나리오 생성 · 3중 오라클 러너 · 회귀 fixture 박제 |
231
+ | 🖥️ **레이아웃 프리뷰** | `hwpx.tools.layout_preview` | 페이지 박스·표·여백 근사 HTML/PNG (에이전트 자기검증용) |
232
+ | 🧷 **바이트 보존 패치** | `hwpx.patch` | section XML 바이트 splice — 미수정 영역 바이트 보존 |
233
+ | 📐 **기존 문서 서식 편집** | 문단·페이지 | 정렬·줄간격·들여쓰기·문단 간격, 용지·여백·방향, 머리말/쪽번호, 불릿/번호 |
234
+ | 🖊️ **누름틀** | 양식 필드 | 클릭히어 필드 조회·서식 보존 채움 |
235
+ | 🏛️ **공문서 도구** | `official_lint` · 결재란 | 항목기호 위계·"끝." 표시·붙임·날짜 표기 lint, 결재란 프리셋 |
236
+ | 📷 **고급 생성기** | `advanced_generators` | 사진대지(image_grid)·회의 명패·표 기반 조직도 |
237
+ | 🆚 **신구대조** | `doc_diff` | 문단 LCS diff·신구대조표 생성·참조 정합 lint |
238
+ | 📨 **메일머지·표 계산** | `mail_merge` | 템플릿+데이터 N부 대량 생성, 표 합계·평균 |
239
+ | 🪄 **서식 이식** | `style_profile` | 참조 문서 프로파일 추출·적용, 템플릿 레지스트리 |
240
+ | 🛡️ **입력 강건화** | `opc.security` | XML entity 폭탄·ZIP 압축 폭탄 가드 |
228
241
 
229
242
  ## 기능 상세
230
243
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-hwpx"
7
- version = "2.11.0"
7
+ version = "2.11.1"
8
8
  description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = "Apache-2.0"
@@ -168,16 +168,30 @@ class DocumentStylePreset:
168
168
  heading_bold: bool = True
169
169
  heading_underline: bool = True
170
170
  table_header_bold: bool = True
171
+ title_size: int = 18
172
+ subtitle_size: int = 12
173
+ heading_size: int = 14
174
+ font: str = "함초롬바탕"
171
175
 
172
176
  def ensure_tokens(self, document: HwpxDocument) -> dict[str, str]:
173
177
  """Create/reuse run styles and return semantic token IDs."""
174
178
 
175
179
  return {
176
- "title": document.ensure_run_style(bold=self.title_bold),
177
- "subtitle": document.ensure_run_style(italic=self.subtitle_italic),
180
+ "title": document.ensure_run_style(
181
+ bold=self.title_bold,
182
+ size=self.title_size,
183
+ font=self.font,
184
+ ),
185
+ "subtitle": document.ensure_run_style(
186
+ italic=self.subtitle_italic,
187
+ size=self.subtitle_size,
188
+ font=self.font,
189
+ ),
178
190
  "heading": document.ensure_run_style(
179
191
  bold=self.heading_bold,
180
192
  underline=self.heading_underline,
193
+ size=self.heading_size,
194
+ font=self.font,
181
195
  ),
182
196
  "body": document.ensure_run_style(),
183
197
  "bullet": document.ensure_run_style(),
@@ -186,6 +200,25 @@ class DocumentStylePreset:
186
200
  }
187
201
 
188
202
 
203
+ def _outline_style_refs(document: HwpxDocument, level: int) -> dict[str, str | int]:
204
+ """Return paragraph style refs for a HWP outline heading level, if available."""
205
+
206
+ safe_level = min(10, max(1, int(level)))
207
+ for style in document.styles.values():
208
+ name = str(style.name or "")
209
+ eng_name = str(style.eng_name or "")
210
+ if name == f"개요 {safe_level}" or eng_name == f"Outline {safe_level}":
211
+ refs: dict[str, str | int] = {}
212
+ style_id = style.raw_id if style.raw_id is not None else style.id
213
+ if style_id is None:
214
+ continue
215
+ refs["style_id_ref"] = style_id
216
+ if style.para_pr_id_ref is not None:
217
+ refs["para_pr_id_ref"] = int(style.para_pr_id_ref)
218
+ return refs
219
+ return {}
220
+
221
+
189
222
  def _plan_issue(
190
223
  code: str,
191
224
  path: str,
@@ -1717,6 +1750,7 @@ def _render_block(
1717
1750
  block.text,
1718
1751
  char_pr_id_ref=tokens["heading"],
1719
1752
  inherit_style=False,
1753
+ **_outline_style_refs(document, block.level),
1720
1754
  )
1721
1755
  return
1722
1756
  if isinstance(block, BuilderParagraph):
@@ -25,6 +25,25 @@ _A4_HWP_SIZE = (59528, 84188)
25
25
  # changing default node contracts or the plan-v1 authoring style-token path.
26
26
 
27
27
 
28
+ def _outline_style_refs(document: HwpxDocument, level: int) -> dict[str, str | int]:
29
+ """Return paragraph style refs for the built-in HWP outline level, if present."""
30
+
31
+ safe_level = min(10, max(1, int(level)))
32
+ for style in document.styles.values():
33
+ name = str(style.name or "")
34
+ eng_name = str(style.eng_name or "")
35
+ if name == f"개요 {safe_level}" or eng_name == f"Outline {safe_level}":
36
+ refs: dict[str, str | int] = {}
37
+ style_id = style.raw_id if style.raw_id is not None else style.id
38
+ if style_id is None:
39
+ continue
40
+ refs["style_id_ref"] = style_id
41
+ if style.para_pr_id_ref is not None:
42
+ refs["para_pr_id_ref"] = int(style.para_pr_id_ref)
43
+ return refs
44
+ return {}
45
+
46
+
28
47
  @dataclass(frozen=True)
29
48
  class _BuilderPreset:
30
49
  name: str = "default"
@@ -264,6 +283,7 @@ class Heading:
264
283
  section_index=section_index,
265
284
  char_pr_id_ref=char_pr_id,
266
285
  inherit_style=False,
286
+ **_outline_style_refs(document, self.level),
267
287
  )
268
288
 
269
289
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx
3
- Version: 2.11.0
3
+ Version: 2.11.1
4
4
  Summary: 한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리
5
5
  Author: python-hwpx Maintainers
6
6
  License-Expression: Apache-2.0
@@ -261,6 +261,19 @@ HWPX 파일은 **ZIP + XML** 구조이므로, 한/글 프로그램 없이 Python
261
261
  | 🧰 **작업 도구** | unpack/pack/분석/비교 | pack-ready 작업 디렉터리 추출과 재구성 점검 |
262
262
  | 🏗️ **저수준 XML** | 데이터클래스 매핑 | OWPML 스키마 ↔ Python 객체 직접 조작 |
263
263
  | 🔄 **네임스페이스 호환** | 자동 정규화 | HWPML 2016 → 2011 자동 변환 |
264
+ | 🏗️ **빌더** | 조립형 생성 | `hwpx.builder` — Section/Heading/Table/Image/Header 조립, 하드게이트 저장 리포트 |
265
+ | ✅ **편집기 오픈 안전** | `validate_editor_open_safety` | 저장/팩/리페어/빌더 출력 게이트, `openSafety` 증거 반환 |
266
+ | 🧪 **퍼징 수렴 루프** | `hwpx.tools.fuzz` | 시드 결정적 시나리오 생성 · 3중 오라클 러너 · 회귀 fixture 박제 |
267
+ | 🖥️ **레이아웃 프리뷰** | `hwpx.tools.layout_preview` | 페이지 박스·표·여백 근사 HTML/PNG (에이전트 자기검증용) |
268
+ | 🧷 **바이트 보존 패치** | `hwpx.patch` | section XML 바이트 splice — 미수정 영역 바이트 보존 |
269
+ | 📐 **기존 문서 서식 편집** | 문단·페이지 | 정렬·줄간격·들여쓰기·문단 간격, 용지·여백·방향, 머리말/쪽번호, 불릿/번호 |
270
+ | 🖊️ **누름틀** | 양식 필드 | 클릭히어 필드 조회·서식 보존 채움 |
271
+ | 🏛️ **공문서 도구** | `official_lint` · 결재란 | 항목기호 위계·"끝." 표시·붙임·날짜 표기 lint, 결재란 프리셋 |
272
+ | 📷 **고급 생성기** | `advanced_generators` | 사진대지(image_grid)·회의 명패·표 기반 조직도 |
273
+ | 🆚 **신구대조** | `doc_diff` | 문단 LCS diff·신구대조표 생성·참조 정합 lint |
274
+ | 📨 **메일머지·표 계산** | `mail_merge` | 템플릿+데이터 N부 대량 생성, 표 합계·평균 |
275
+ | 🪄 **서식 이식** | `style_profile` | 참조 문서 프로파일 추출·적용, 템플릿 레지스트리 |
276
+ | 🛡️ **입력 강건화** | `opc.security` | XML entity 폭탄·ZIP 압축 폭탄 가드 |
264
277
 
265
278
  ## 기능 상세
266
279
 
@@ -344,6 +344,15 @@ def test_builder_lowers_headings_with_level_aware_styles(tmp_path) -> None:
344
344
  assert heading_1_run.style is not None
345
345
  assert heading_2_run.style is not None
346
346
  assert int(heading_1_run.style.attributes["height"]) > int(heading_2_run.style.attributes["height"])
347
+ outline_styles = {
348
+ str(style.raw_id if style.raw_id is not None else style.id): int(
349
+ style.name.rsplit(" ", 1)[-1]
350
+ )
351
+ for style in reopened.styles.values()
352
+ if (style.name or "").startswith("개요 ")
353
+ }
354
+ assert outline_styles.get(str(paragraphs[0].style_id_ref)) == 1
355
+ assert outline_styles.get(str(paragraphs[1].style_id_ref)) == 2
347
356
 
348
357
 
349
358
  def test_ensure_numbering_creates_sample_shaped_refs() -> None:
@@ -344,6 +344,54 @@ def test_create_document_from_plan_generates_valid_formatted_hwpx(tmp_path) -> N
344
344
  reopened.close()
345
345
 
346
346
 
347
+ def test_document_plan_headings_use_outline_paragraph_styles() -> None:
348
+ document = create_document_from_plan(_plan())
349
+ try:
350
+ outline_styles = {
351
+ str(style.raw_id if style.raw_id is not None else style.id): int(
352
+ style.name.rsplit(" ", 1)[-1]
353
+ )
354
+ for style in document.styles.values()
355
+ if (style.name or "").startswith("개요 ")
356
+ }
357
+ heading_levels = {
358
+ (paragraph.text or "").strip(): outline_styles.get(str(paragraph.style_id_ref))
359
+ for paragraph in document.paragraphs
360
+ if (paragraph.text or "").strip() in {"Executive Summary", "Budget"}
361
+ }
362
+
363
+ assert heading_levels == {"Executive Summary": 1, "Budget": 2}
364
+ finally:
365
+ document.close()
366
+
367
+
368
+ def test_document_plan_title_and_heading_styles_have_visual_hierarchy() -> None:
369
+ document = create_document_from_plan(_plan())
370
+ try:
371
+ runs_by_text = {
372
+ (paragraph.text or "").strip(): paragraph.runs[0]
373
+ for paragraph in document.paragraphs
374
+ if (paragraph.text or "").strip()
375
+ }
376
+
377
+ title_style = runs_by_text["2026 AI Education Operating Plan"].style
378
+ subtitle_style = runs_by_text["Draft for internal review"].style
379
+ heading_style = runs_by_text["Executive Summary"].style
380
+ body_style = runs_by_text[
381
+ "The program connects classroom practice, teacher training, and evidence review."
382
+ ].style
383
+
384
+ assert title_style is not None
385
+ assert subtitle_style is not None
386
+ assert heading_style is not None
387
+ assert body_style is not None
388
+ assert int(title_style.attributes["height"]) > int(heading_style.attributes["height"])
389
+ assert int(heading_style.attributes["height"]) > int(body_style.attributes["height"])
390
+ assert int(subtitle_style.attributes["height"]) > int(body_style.attributes["height"])
391
+ finally:
392
+ document.close()
393
+
394
+
347
395
  def test_operating_plan_vertical_slice_reports_required_content_and_tables(tmp_path) -> None:
348
396
  output = tmp_path / "operating-plan.hwpx"
349
397
  plan = _operating_plan()
File without changes
File without changes
File without changes