airmang/python-hwpx
Pure Python HWPX automation: read, edit, generate, and validate documents without Hancom Office.
16 Releases
Latest: yesterday
v2.15.0Latest
๐ฆ ์ถ๊ฐ
- `HwpxDocument.set_paragraph_format(keep_with_next=, keep_lines=, page_break_before=)` โ ๋ฌธ๋จ keep-together ํ๋๊ทธ๋ฅผ ์์ง `ensure_paragraph_format(break_setting=)`๋ก ์ ๋ฌํ๋ค(์ paraPr ๋ฐํ, ๊ธฐ์กด paraPr ๋ฏธ์์ = ๋ฌด์์ค). ์ํ์ง ์กฐํ ๋ฑ์์ ํ ๋ฌธํญ์ด ๋จ/์ชฝ ๊ฒฝ๊ณ์์ ์๋ฆฌ์ง ์๊ฒ ๋ฌถ์ ๋ ์ด๋ค.
- `hwpx.exam`: re-typeset an authored exam (Markdown) into a school form `.hwpx`
- โ Exam IR + strict md parser, form profiler (roleโexisting form style),
- keep-together body composition (insert into the form's body region, never
- append; ๊ด๋ฆฌ๋ฐ์ค + footer preserved byte-identical), and an oracle convergence
- driver `compose_exam_into_form`. The driver renders via Hancom and, when the
- composed ๋ฌธํญ are in the extractable text layer, verifies ๋ฌธํญ-split / overflow
- / placeholder integrity (inserting column/page breaks to converge); when they
- + 4 more
๐ฆ ์์
- `paragraph.add_picture` โ `treat_as_char=True`(inline)์ธ๋ฐ `pos_overrides`(PAPER relTo/offset)๋ฅผ ์ฃผ๋ฉด ๋ชจ์๋ inline/floating `<hp:pos>`๋ฅผ ๋ฐฉ์ถํ๋ ๊ฒ์ `ValueError`๋ก fail-fast. floating ๋ฐฐ์น๋ `treat_as_char=False`์์๋ง.
v2.13.0
๐ฆ ์ถ๊ฐ
- `hwpx.conformance` โ VisualComplete ์ ํฉ์ฑ ์ฝํผ์ค + ๋ฐฐ์ง ๋ฑ๊ธ(plan ยง2 Phase G). `hwpx-conformance run`์ด ์ฝํผ์ค๋ฅผ 4๊ฐ ๋ฐฐ์ง ๋ฑ๊ธ(Open-Safe/Semantic-Safe/Form-Safe/VisualComplete)์ผ๋ก ์ฑ์ ํ๊ณ ๋ฑ๊ธ๋ณ ํต๊ณผ์จ์ ์ฐ์ถํฉ๋๋ค. ์๊ณ๊ฐ์ ์๊ฒฉ ๊ธฐ๋ณธ๊ฐ(๊ตฌ์กฐ ๋ฑ๊ธ 100%, ํผ์ overflow 0%, VisualComplete โฅ95%). golden ๋ฒ ์ด์ค๋ผ์ธ(`tests/conformance/golden/structural.json`) ๋๋น ํ๊ท๋ฅผ ์ซ์๋ก ๊ฐ์งํ๋ฉฐ(`--check`), CI๊ฐ ๊ตฌ์กฐ ๋ฑ๊ธ์ ์ถ์ ํฉ๋๋ค. ์ด์์ด๋ฐ์ค ๋ฑ๊ธ์ ์ ๋ ์์ง ์์ต๋๋ค(ยง0.0): ํ์ปด์ด ์๋ ๊ตฌ์กฐ ์คํ์ VisualComplete๋ฅผ `unverified`๋ก ๋ณด๊ณ ํ๊ณ , ์ค๋ผํด ์คํ(๋๋ฌ ๊ฐ๋ฅํ ํ์ปด ๋ฐฑ์๋)๋ง VisualComplete๋ฅผ ๊ฒ์ฆํฉ๋๋ค. ์ผ์ด์ค์ `before`(+์ ํ์ `editMask`)๋ฅผ ์ ์ธํ๋ฉด VisualComplete๊ฐ ์ค๋ผํด์ before/after diff ๊ฒฝ๋ก๋ก ๊ฒ์ดํธ๋ฉ๋๋ค(๋ง์คํฌ ๋ฐ ๋ณ๊ฒฝยท๊ธ์ ๊ฒน์นจ์ ์ก์). `expectVisualDefect`๋ ์ผ๋ถ๋ฌ ๊นจ๋จ๋ฆฐ ์์ positive control๋ก ์ผ์ ๊ฒ์ดํธ๊ฐ ๊ฒฐํจ์ ์ค์ ๋ก ์ก๋์ง ๊ฒ์ฆํฉ๋๋ค. (์ค์ธก: ์ค์ ํ์ปด-์ ์ฅ ์ฝํผ์ค์์ clean ์์ ํต๊ณผ, out-of-slot ๋ณ๊ฒฝ์ catch.)
v2.11.1
๐ฆ ์์
- `create_document_from_plan()`์ `heading` block๊ณผ builder `Heading`์ด ๊ธฐ๋ณธ ํ ํ๋ฆฟ์ `๊ฐ์ N`/`Outline N` ๋ฌธ๋จ ์คํ์ผ์ ์ค์ ๋ก ์ ์ฉํ๋๋ก ์์ ํ์ต๋๋ค. ์์ฑ ๋ฌธ์๊ฐ ํ์ปด ๊ฐ์/๋ฌธ์ ํ์๊ณผ MCP outline readback์์ ๊ตฌ์กฐํ๋ ์ ๋ชฉ์ผ๋ก ์ธ์๋ฉ๋๋ค.
- document-plan ๊ธฐ๋ณธ ์คํ์ผ preset์ ์ ๋ชฉ 18pt, ๋ถ์ 12pt, ์ฅ ์ ๋ชฉ 14pt ๊ธ์ ํฌ๊ธฐ์ ํจ์ด๋กฌ๋ฐํ ํฐํธ๋ฅผ ์ ์ฉํด ๋ณด๊ณ ์ ์์ฑ ์ ์ ๋ชฉ/๋ณธ๋ฌธ ์๊ฐ ์๊ณ๊ฐ ๋ช ํํ๊ฒ ๋ณด์ด๋๋ก ํ์ต๋๋ค.
v2.11.0
๐ฆ ์ถ๊ฐ
- ์๋ ๊ฒฐ์ ์ ํผ์ง ์๋ ด ๋ฃจํ `hwpx.tools.fuzz`(์๋๋ฆฌ์ค ์นดํ๋ก๊ทธยท์์ฑ๊ธฐยท3์ค ์ค๋ผํด ๋ฌ๋ยท์ต์ํ)์ `tests/fixtures/fuzz_regressions` ํ๊ท ๋ฐ์ ์ํธ๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
- ๋ ์ด์์ ๊ทผ์ฌ ํ๋ฆฌ๋ทฐ ๋ ๋๋ฌ `hwpx.tools.layout_preview`๋ฅผ ์ถ๊ฐํ์ต๋๋ค(ํ์ด์ง ๋ฐ์คยทํยท์ฌ๋ฐฑ ๊ทผ์ฌ HTML/PNG โ ์์ด์ ํธ ์๊ธฐ๊ฒ์ฆ์ฉ).
- section XML ๋ฐ์ดํธ splice ๊ธฐ๋ฐ ๋ฌธ๋จ ํจ์น ๊ฒฝ๋ก `hwpx.patch`๋ฅผ ์ถ๊ฐํ์ต๋๋ค(๋ฏธ์์ ์์ญ ๋ฐ์ดํธ ๋ณด์กด).
- ๊ทธ๋ฆผ ์์ฐ ์์ ์ฝ์ ยท์นํ API(`add_picture` ๋ฐ ์นํ ์ํฌํ๋ก)์ manifest ๊ฒ์ฆ์ ์ถ๊ฐํ์ต๋๋ค.
- ๊ธฐ์กด ๋ฌธ์ ์์ ํธ์ง API๋ฅผ ์ถ๊ฐํ์ต๋๋ค: ๋ฌธ๋จ ์ ๋ ฌยท์ค๊ฐ๊ฒฉยท๋ค์ฌ์ฐ๊ธฐยท๋ฌธ๋จ ๊ฐ๊ฒฉ, ์ฉ์งยท์ฌ๋ฐฑยท๋ฐฉํฅ, ๋จธ๋ฆฌ๋ง/๊ผฌ๋ฆฌ๋งยท์ชฝ๋ฒํธ, ๋ถ๋ฆฟ/๋ฒํธ ํ์.
- ๋๋ฆํ(ํด๋ฆญํ์ด ํ๋) 1๊ธ ์กฐํยท์ฑ์ API๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
- ๊ณต๋ฌธ์ ์์ฑ๊ท์ lint `hwpx.tools.official_lint`(ํญ๋ชฉ๊ธฐํธ ์๊ณยท"๋." ํ์ยท๋ถ์ยท๋ ์ง ํ๊ธฐ)์ ๊ฒฐ์ฌ๋ ํ๋ฆฌ์ ์ ์ถ๊ฐํ์ต๋๋ค.
- ๊ณ ๊ธ ์์ฑ๊ธฐ `hwpx.tools.advanced_generators`๋ฅผ ์ถ๊ฐํ์ต๋๋ค: ์ฌ์ง๋์ง(`build_image_grid`)ยทํ์ ๋ช ํจ(`build_meeting_nameplates`)ยทํ ๊ธฐ๋ฐ ์กฐ์ง๋.
- + 4 more
๐ฆ ๋ณ๊ฒฝ
- `hwpx.oxml.document` ๋ชจ๋๋ฆฌ์ค(5,700์ฌ ์ค)๋ฅผ ์์๋ณ ๋ชจ๋(`_document_impl` ์ธ 18๊ฐ)๋ก ๋ถํ ํ์ต๋๋ค. ๊ณต๊ฐ API๋ ๋ณํ์ง ์์ต๋๋ค.
๐ฆ ์์
- ์ ๋ขฐํ ์ ์๋ ์ ๋ ฅ ํ์ฑ์ ๊ฐ๊ฑดํํ์ต๋๋ค(`hwpx.opc.security`): XML entity ์ ์ธ ๊ฑฐ๋ถ์ ๊น์ด/ํฌ๊ธฐ ํ๋, ZIP ์์ถ๋นยท๋ฉค๋ฒ ์ ํ๋๋ฅผ ์ ์ฉํด entity ํญํยท์์ถ ํญํ ์ ๋ ฅ์ ์์ ํ๊ฒ ๊ฑฐ๋ถํฉ๋๋ค.
v2.10.3
๐ฆ ์ถ๊ฐ
- `hwpx.tools.validate_editor_open_safety()`์ `EditorOpenSafetyReport`๋ฅผ ์ถ๊ฐํด package validation, document validation, ์ฌ์คํ ๊ฒ์ฆ์ ํ ๊ณณ์์ ํ์ธํ ์ ์๊ฒ ํ์ต๋๋ค.
๐ฆ ์์
- ํ ์คํธ๋ฅผ ์ค์ด๋ ์ ์์ค ํธ์ง ๋ค stale `hp:linesegarray`๊ฐ ๋จ์ ํ์ปด ํธ์ง๊ธฐ์์ ์ด๋ฆฌ์ง ์์ ์ ์๋ ๋ฌธ์ ๋ฅผ ๋ง๊ธฐ ์ํด, ์ ์ฅ ์ง์ plain-text ๋ฌธ๋จ์ ๋ฌดํจํ layout cache๋ฅผ ์ ๊ฑฐํฉ๋๋ค.
- ํธ์ง๋ section๊ณผ public ์ ์์ค section write ๊ฒฝ๋ก๋ ๋ชจ๋ `hp:lineSegArray` layout cache๋ฅผ ์ ๊ฑฐํด, ๋ณตํฉ ๋ฌธ๋จ์ฒ๋ผ stale ์ฌ๋ถ๋ฅผ ์์ ํ๊ฒ ๊ณ์ฐํ๊ธฐ ์ด๋ ค์ด ๊ฒฝ์ฐ๋ ํธ์ง๊ธฐ๊ฐ ๋ค์ ๊ณ์ฐํ๋๋ก ํ์ต๋๋ค.
- public ์ ์์ค section/header XML write ๊ฒฝ๋ก๋ Hancom-compatible root namespace ์ ์ธ๊ณผ `standalone="yes"` XML declaration์ ๋ณด์ ํด generic XML serializer ์ถ๋ ฅ์ด ๊ทธ๋๋ก ์ ์ฅ๋์ง ์๋๋ก ํ์ต๋๋ค.
- `HwpxDocument.to_bytes()`, `save_to_path()`, `save_to_stream()`์ด ์์ฑ๋ ํจํค์ง์ editor-open safety๋ฅผ ํ์ธํ ๋ค์๋ง ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๊ฑฐ๋ ์ฐ๋๋ก ๋ณด๊ฐํ์ต๋๋ค. `save_to_path()`๋ safety ์คํจ ์ ๊ธฐ์กด ๋์ ํ์ผ์ ๊ต์ฒดํ์ง ์์ต๋๋ค.
- `HwpxPackage.save()`๋ editor-open safety๋ฅผ ๊ธฐ๋ณธ ๊ฒ์ฆํด ์ ์์ค package ์ง์ ํธ์ง์ด unsafe HWPX๋ฅผ bytes/path/stream์ผ๋ก ๋ด๋ณด๋ด์ง ์๋๋ก ๋ง์ต๋๋ค.
- public `HwpxPackage.save()`์์ editor-open safety ๊ฒ์ฆ์ ์ฐํํ๋ ํ๋ผ๋ฏธํฐ๋ฅผ ์ ๊ณตํ์ง ์๋๋ก ์ ๋ฆฌํ์ต๋๋ค. ๊ฒ์ฆ ์คํจ ์ํ์ bytes snapshot์ package ๋ด๋ถ ์ง๋จ ํ ํฐ์ด ์๋ ๊ฒฝ๋ก์์๋ง ์์ฑํ๋ฉฐ, unchecked ๊ฒฝ๋ก๋ caller-provided file path/stream์ ์ง์ ์ธ ์ ์์ต๋๋ค.
- `HwpxDocument._to_bytes_raw()`์ open-safety bypass ์ธ์๋ฅผ ์ ๊ฑฐํด document ๊ฐ์ฒด์์ unchecked bytes๋ฅผ ์ป๋ ์ค์์ฑ ์ฐํ ๊ฒฝ๋ก๋ฅผ ๋ ์ขํ์ต๋๋ค.
- private archive writer๋ save ๋ด๋ถ ์ปจํ ์คํธ์์๋ง ๋์ํ๊ฒ ํด, `_write_archive()`/`_write_zip_entry()` ์ง์ ํธ์ถ๋ก editor-open safety ๊ฒ์ฆ์ ๊ฑด๋๋ด ZIP์ ๋ง๋๋ ์ค์์ฑ ์ฐํ ๊ฒฝ๋ก๋ฅผ ๋ง์์ต๋๋ค.
- + 10 more
v2.10.2
๐ฆ ์ถ๊ฐ
- `hwpx.tools.markdown_export.export_markdown()`์ `HwpxDocument.export_rich_markdown()`์ ์ถ๊ฐํด ํ๋ถํ Markdown ๋ณํ์ ์ง์ํฉ๋๋ค. ์ธ๋ผ์ธ ์์(๊ตต๊ฒ/๊ธฐ์ธ์/์ทจ์์ /์์/ํ์ด๋ผ์ดํธ), ํ ๋ณํฉ ์ (colspan/rowspan HTML), ์ค์ฒฉ ํ ์ฌ๊ท, `rect`/`ellipse`/`polygon` ๋ํ ๋ด๋ถ paragraph, BinData ์ด๋ฏธ์ง ์ถ์ถ, `โ .`/`1.` ํจํด ๊ธฐ๋ฐ ํค๋ฉ ๊ฐ์ง(`# `/`## `), ๊ฐ์ฃผยท๋ฏธ์ฃผ(์ ํ ์์น ๋ง์ปค + `fn1`/`en1` ์ผ๋ จ๋ฒํธ + ๋ณธ๋ฌธ ์ธ๋ผ์ธ ์์), ํ์ดํผ๋งํฌ(`[text](url)`) ๋ณด์กด์ ํ ๋ฒ์ ์ฒ๋ฆฌํฉ๋๋ค. ๊ธฐ์กด `HwpxDocument.export_markdown()`์ ๊ทธ๋๋ก ์ ์ง๋ฉ๋๋ค.
- `HwpxOxmlNote`์ ๋ณธ๋ฌธ paragraph ์ ๊ทผ/ํธ์ง helper๋ฅผ ์ถ๊ฐํ์ต๋๋ค: `body_paragraph` property, `add_run(text, *, char_pr_id_ref=..., bold=..., italic=..., underline=..., color=..., font=..., size=..., highlight=..., strike=..., attributes=...)`, `add_hyperlink(url, display_text, *, char_pr_id_ref=...)`. XML ์ง์ ์กฐ์ ์์ด ๊ฐ์ฃผ ๋ณธ๋ฌธ์ ํผํฉ ์์ run๊ณผ ํ์ดํผ๋งํฌ๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
- `get_table_map()` ๊ฒฐ๊ณผ์ ๋ณธ๋ฌธ ํ anchor `location`, ์ ๋ฌธ๋จ๋ณ `table_cell_paragraph` location, `caption_text`, `preceding_paragraph_text`๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
- ์ ์ปจ๋ฒํฐ์ helper์ ๋ํ ํ๊ท ํ ์คํธ๋ฅผ `tests/test_markdown_export.py`์ ์ถ๊ฐํ์ต๋๋ค.
๐ฆ ๋ณ๊ฒฝ
- `HwpxOxmlTableCell.text`๊ฐ ์ ๋ด๋ถ ์ฌ๋ฌ ๋ฌธ๋จ์ ์ค๋ฐ๊ฟ์ผ๋ก ๋ณด์กดํ๊ณ , `set_text(..., preserve_format=True, split_paragraphs=True)` ๊ฒฝ๋ก์์ ๊ธฐ์กด run `charPrIDRef`๋ฅผ ์ ์งํ๋๋ก ๊ฐ์ ํ์ต๋๋ค.
๐ฆ ์์
- `HwpxOxmlParagraph.add_footnote()`/`add_endnote()`์ `char_pr_id_ref` ์ธ์๊ฐ ์ธ๋ถ ํธ์คํ run์๋ง ์ ์ฉ๋๊ณ ๊ฐ์ฃผ ๋ณธ๋ฌธ run์ ํญ์ `charPrIDRef="0"`์ผ๋ก ํ๋์ฝ๋ฉ๋๋ ๋ฌธ์ ๋ฅผ ์์ ํ์ต๋๋ค. ์ธ์๊ฐ ์ฌ์ฉ์ ์๋๋๋ก ๋ณธ๋ฌธ run์๋ ์ ์ฉ๋ฉ๋๋ค.
v2.10.1
๐ฆ ์ถ๊ฐ
- `document_plan` authoring์ builder lowering ์ค์ฌ์ผ๋ก ํ์ฅํ๊ณ v2 builder node, TOC, government_report preset์ ์ง์ํฉ๋๋ค.
- ์ ๋ถ๋ณด๊ณ ์ ๊ณ์ฐ/ํ์ฑ ์ ํธ๋ฆฌํฐ(`hwpx.tools.report_utils`, `hwpx.tools.report_parser`)์ computed field ์นํ์ ์ถ๊ฐํ์ต๋๋ค.
- generic element coverage inventory, table cleanup, table profile/caption/unit preservation, id reference integrity checker๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
- `linesegarray`, `transMatrix`, `scaMatrix`, `rotMatrix`, edit/combo box control์ first-class OXML ๋ชจ๋ธ๋ก ์น๊ฒฉํ์ต๋๋ค.
๐ฆ ๋ณ๊ฒฝ
- builder save report์ hard gate๊ฐ id integrity๋ฅผ ์ค์ ๊ฒ์ฌ ๊ฒฐ๊ณผ๋ก ๋ฐ์ํ๋๋ก ๊ฐํํ์ต๋๋ค.
- ํจํค์ง rewrite ์ `mimetype` ์ํธ๋ฆฌ๋ฅผ ๋ณด์กดํ๋๋ก OPC ์ ์ฅ ๊ฒฝ๋ก๋ฅผ ์ ๋ฆฌํ์ต๋๋ค.
v2.10.0
๐ฆ ์ถ๊ฐ
- `hwpx.builder` ๊ณต๊ฐ ํจํค์ง๋ฅผ ์ถ๊ฐํ์ต๋๋ค. `Document`, `Section`, `Paragraph`, `Run`, `Heading`, `Bullet`, `NumberedList`, `Table`, `Image`, `Header`, `Footer`, `PageNumber`, `PageBreak`, `Metadata`, `PageSize`, `Margins` ๋ ธ๋๋ก ์กฐ๋ฆฝํ HWPX ์์ฑ์ ์ง์ํฉ๋๋ค.
- `BuilderSaveReport`์ `ReopenReport`๋ฅผ ์ถ๊ฐํด builder ์ ์ฅ ํ package validation, document error/lint, reopen, feature flags, visual review ํ์ ์ฌ๋ถ๋ฅผ ํ์ธํ ์ ์๊ฒ ํ์ต๋๋ค.
- ๋จธ๋ฆฌ๊ธ/๋ฐ๋ฅ๊ธ ๋ฆฌ์น content, ์๋ ์ชฝ๋ฒํธ, ๋ฆฌ์น ๋ฐ ์์(color/font/size/highlight/strike), ๋ค๋จ๊ณ ๋ชฉ๋ก, ํ ๋ณํฉ/์์/์ด๋๋น, ์ด๋ฏธ์ง ๋ฐฐ์น๋ฅผ ์ํ `HwpxDocument` facade ๋ฐ OXML wrapper ๋ฉ์๋๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
- `hwpx.document_plan.v1`, ์ด์ ๊ณํ์ ํ์ง ํ๋กํ, template form-fit authoring, proposal/form-fill ํ์ง ๊ฒ์ฆ ํ๋ฆ์ ๊ฐํํ์ต๋๋ค.
- hwpxlib sample corpus ๊ธฐ๋ฐ oracle fixture์ builder vertical slice ํตํฉ ํ ์คํธ๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
- `src/hwpx/tools/_schemas/owpml/`์ 2011 Hancom ๋ค์์คํ์ด์ค์ฉ subset XSD ๋ฒ๋ค์ ์ถ๊ฐํ์ต๋๋ค (`header.xsd`, `body.xsd`, `paralist.xsd`, `core.xsd`, `xml.xsd`, `NOTICE`).
- `hwpx.oxml.load_compound_schema()`์ `SchemaImportError`๋ฅผ ์ถ๊ฐํด offline compound XSD ๋ก๋ฉ์ ์ง์ํฉ๋๋ค.
- fixture matrix ๊ธฐ๋ฐ Phase 1 validation ๋ฆฌํฌํธ(`shared/hwpx/HWPX_STACK_VALIDATION_2026-04-20_pre-phase1.md`, `..._post-phase1.md`)์ ํ๊ท ํ ์คํธ๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
๐ฆ ๋ณ๊ฒฝ
- `validate_document().ok`๋ error ๊ธฐ์ค์ผ๋ก ์ ์งํ๊ณ schema warning์ lint/warning์ผ๋ก ๋ถ๋ฆฌํด ๊ฐ์ํํฉ๋๋ค.
- `HwpxDocument.save_to_path()` ๊ธฐ๋ฐ ์ ์ฅ/์ฌ์คํ ๊ฒ์ฆ ๊ฒฝ๋ก๋ฅผ builder์ authoring workflow์์ ์ผ๊ด๋๊ฒ ์ฌ์ฉํ๋๋ก ์ ๋ฆฌํ์ต๋๋ค.
- `hwpx-validate`๋ ์ด์ ๊ธฐ๋ณธ strict ๋ชจ๋๋ก Phase 1 subset schema bundle์ ์ฌ์ฉํฉ๋๋ค. `--no-strict`๋ก warning-only ๋ถ๋ฅ๋ฅผ ์ง์ํฉ๋๋ค.
- `HwpxDocument.validate()`๋ ๊ธฐ๋ณธ `strict=False`๋ก ๋์ํ๋ฉฐ, `validate_on_save_strict` ์ต์ ์ผ๋ก ์ ์ฅ ์ strict ๊ฒ์ฆ์ ์ ์ดํ ์ ์์ต๋๋ค.
- ํจํค์ง ๋ฐฐํฌ๋ฌผ(sdist/wheel)์ OWPML subset schema bundle์ด ํฌํจ๋๋๋ก package-data๋ฅผ ํ์ฅํ์ต๋๋ค.
๐ฆ ์์
- split-run placeholder, template form-fit, proposal/document-plan ์์ฑ ๊ฒฝ๋ก์ ํ๊ท๋ฅผ ๋ณด๊ฐํ์ต๋๋ค.
- builder vertical slice์์ Hancom Office HWP ์ฌ์คํ๊ณผ ๊ตฌ์กฐ hard gate๊ฐ ํต๊ณผํ๋๋ก ๋จธ๋ฆฌ๊ธ/๋ฐ๋ฅ๊ธ lowering๊ณผ page number control ๋ฐฐ์น๋ฅผ ์ ๋ ฌํ์ต๋๋ค.
v2.9.1
๐ฆ [2.9.1] - 2026-04-27
- ์ํธ์ด์ฉ์ฑ(interop) ๋ฒ๊ทธ ๋ฌถ์ ๋ฆด๋ฆฌ์ฆ์ ๋๋ค. ์ธ๋ถ ๊ธฐ์ฌ์๋ค์ด ๋ณด๊ณ ํ๊ณ ์์ ํ ์ธ ๊ฐ์ง ๋ฌธ์ ๋ฅผ ์ ๋ฆฌํฉ๋๋ค.
๐ฆ ์์
- `HwpxOxmlTableCell._ensure_text_element`์ `ensure_run_style` ๋ด modifier๊ฐ lxml ์๋ฆฌ๋จผํธ ์์์ ๋ํ `ET.SubElement`๋ฅผ ํธ์ถํด `TypeError`๋ฅผ ๋ฐ์์ํค๋ ๊ฒฝ๋ก๋ฅผ ๊ธฐ๋ณธ ํฌํผ `_append_child`๋ก ์ ๋ฆฌํ์ต๋๋ค. ์ด์ `cell.text = ...`์ `paragraph.add_run(..., bold=True)`๊ฐ monkey-patch ์์ด ์ ์ ๋์ํฉ๋๋ค (#30, [@hhy827](https://github.com/hhy827)).
- `_paragraph_id` / `_object_id` / `_memo_id`๊ฐ `uuid4().int & 0xFFFFFFFF`๋ก๋ถํฐ signed int32 ๋ฒ์๋ฅผ ๋ฒ์ด๋๋ ๊ฐ์ ์ฝ 50% ํ๋ฅ ๋ก ์์ฑํ๋ ๋ฌธ์ ๋ฅผ ์์ ํ์ต๋๋ค. id ๊ฐ์ signed 32-bit ์์ ๋ฒ์(`0 <= x < 2^31`)๋ก ํด๋จํํด downstream ์๋น์์์ ์ํธ์ด์ฉ์ฑ์ ํ๋ณดํ์ต๋๋ค (#34, [@seonghoony](https://github.com/seonghoony)).
- `HwpxDocument.new()`์ seed๋ก ์ฐ์ด๋ ๋ฒ๋ค `Skeleton.hwpx`์ signed int32 ๋ฒ์๋ฅผ ๋ฒ์ด๋๋ `<hp:p id="3121190098">`๊ฐ ํฌํจ๋ผ ์๋ ๋ฌธ์ ๋ฅผ ์์ ํ์ต๋๋ค (#35, [@seonghoony](https://github.com/seonghoony)).
- `pyproject.toml`์ PEP 639 `license` expression๊ณผ ๊ฐ์ด ๋จ์ ์๋ legacy `License :: OSI Approved :: Apache Software License` classifier๋ฅผ ์ ๊ฑฐํด `setuptools>=77`์์์ ์์ค ์ค์น/๋ฐ์ด๋๋ฆฌ ๋น๋ ์คํจ๋ฅผ ํด์ํ์ต๋๋ค.
๐ฆ ์ถ๊ฐ
- ์ ์ธ ๋ฒ๊ทธ์ ๋ํ ํ๊ท ํ ์คํธ๋ฅผ ์ถ๊ฐํ์ต๋๋ค (`tests/test_document_formatting.py`, `tests/test_id_generator_range.py`, `tests/test_skeleton_template_ids.py`).
- ๋จธ์ง๋ ๊ธฐ์ฌ๋ฅผ ์ธ์ ํ๋ `CONTRIBUTORS.md`๋ฅผ ์ถ๊ฐํ๊ณ `README.md` / `CONTRIBUTING.md`์์ ์ฐ๊ฒฐํ์ต๋๋ค.
๐ฆ ๋ณ๊ฒฝ
- License relicensed to Apache-2.0 (sole author, full consent). Previous license terms no longer apply to future releases.
v2.9.0
๐ฆ ์ถ๊ฐ
- `HwpxDocument.get_table_map()`, `find_cell_by_label()`, `fill_by_path()`๋ฅผ ์ถ๊ฐํด HWPX ์์/ํ ํ๋ฆฟ ํ๋ฅผ ๋ฌธ์ ์์ ๊ธฐ๋ฐ์ผ๋ก ํ์ํ๊ณ ์ฑ์ธ ์ ์๊ฒ ํ์ต๋๋ค.
- `hwpx.tools.table_navigation` ๋ชจ๋์ ์ถ๊ฐํด ์์ง ๋ ๋ฒจ์์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ํ ํ์, ๋ผ๋ฒจ ์ ๊ทํ, ๋ฐฉํฅ ์ด๋, ๋ฐฐ์น ์ฑ์ฐ๊ธฐ helper๋ฅผ ๊ณต๊ฐํ์ต๋๋ค.
๐ฆ ๋ณ๊ฒฝ
- ๋ผ๋ฒจ ๋งค์นญ์ด ๊ณต๋ฐฑ ์ถ์ฝ, ๋์๋ฌธ์ ๋ฌด์, ํํ ์ฝ๋ก ํ์ฉ ๊ท์น์ ๋ฐ๋ฅด๋๋ก ์ ๊ทํ ๋ก์ง์ ์ถ๊ฐํ์ต๋๋ค.
- ํ ์๋ํ API์ ๋ํ ํ๊ท ํ ์คํธ์ README/API ๋ ํผ๋ฐ์ค ๋ฌธ์๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
v2.8.3
๐ฆ ๋ณ๊ฒฝ
- ์ ์ฅ์์ ๋ฐฐํฌ ๋ฉํ๋ฐ์ดํฐ์ ๋ผ์ด์ ์ค ํ๊ธฐ๋ฅผ ์ค์ `LICENSE` ํ์ผ๊ณผ ์ผ์นํ๋๋ก ์ ๋ ฌํ์ต๋๋ค.
- `pyproject.toml`์ PEP 639 ๋ฐฉ์์ `LicenseRef-python-hwpx-NonCommercial` + `license-files` ๊ตฌ์ฑ์ผ๋ก ๊ฐฑ์ ํ๊ณ , ์๋ชป๋ MIT ๋ถ๋ฅ์๋ฅผ ์ ๊ฑฐํ์ต๋๋ค.
- README ๋ผ์ด์ ์ค ๋ฐฐ์ง/์น์ ์ ์ปค์คํ ๋น์์ ์ ๋ผ์ด์ ์ค ๊ธฐ์ค์ผ๋ก ์์ ํ๊ณ , wheel/sdist ์ฐ์ถ๋ฌผ์ ๋ผ์ด์ ์ค ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํ๋ ํ๊ท ํ ์คํธ๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
v2.8.2
๐ฆ ๋ณ๊ฒฝ
- README๋ฅผ ํ์ฌ ๊ณต๊ฐ API์ CLI ๋ฒ์์ ๋ง์ถฐ ์ ๋ฆฌํ์ต๋๋ค. Quick start, ํ ์คํธ ์ถ์ถ, ๊ฐ์ฒด ๊ฒ์ ์์๋ฅผ ์ค์ ํธ์ถ ๋ฐฉ์ ๊ธฐ์ค์ผ๋ก ์์ ํ์ต๋๋ค.
- `add_memo()`/`add_memo_with_anchor()`๊ฐ `HwpxDocument.new()`๋ก ๋ง๋ ์ค์ `lxml` ๊ธฐ๋ฐ ๋ฌธ์์์๋ ๋์ํ๋๋ก memo XML ์์ฑ ๊ฒฝ๋ก๋ฅผ ์์ง ํธํ ๋ฐฉ์์ผ๋ก ์ ๋ฆฌํ์ต๋๋ค.
- ์ค์ ๋น ๋ฌธ์ ํ ํ๋ฆฟ์์ ๋ฉ๋ชจ ์ถ๊ฐ ํ roundtrip ๋๋ ํ๊ท ํ ์คํธ๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
v2.8
๐ฆ ๋ณ๊ฒฝ
- `HwpxPackage`์ OXML ๋ก๋ฉ/์ ์ฅ์ด rootfile/manifest-relative ๊ฒฝ๋ก๋ฅผ ์ค์ ๋ก ๋ฐ๋ฅด๋๋ก ์ ๋ ฌํ์ต๋๋ค.
- `hwpx-analyze-template --extract-dir`๊ฐ pack-ready ์์ ๋๋ ํฐ๋ฆฌ์ `.hwpx-pack-metadata.json`์ ์์ฑํ๋๋ก ํ์ฅํ์ต๋๋ค.
- `hwpx-validate-package`๋ฅผ ์์ง ์ ํฉ ๊ธฐ์ค์ผ๋ก ์ฌ์์ฑํด dynamic rootfile/manifest ๊ด๊ณ, CRC, fallback warning์ ๊ตฌ๋ถํ๋๋ก ํ์ต๋๋ค.
- `hwpx-unpack` ๊ธฐ๋ณธ๊ฐ์ raw-byte preserving์ผ๋ก ๋ฐ๊พธ๊ณ `--pretty-xml` opt-in์ ์ถ๊ฐํ์ต๋๋ค.
- tooling/OPC ํ๊ท ํ ์คํธ๋ฅผ ํ๋ํ๊ณ , coverage threshold๋ฅผ 60์ผ๋ก ์ฌ๋ ธ์ผ๋ฉฐ, pyright๋ touched OPC/tooling ๋ฒ์์์ `basic`์ผ๋ก ์ํฅํ์ต๋๋ค.
v2.7.1
๐ฆ ๋ณ๊ฒฝ
- ๊ณต๊ฐ ์ ์ฅ์์ ๋ฐฐํฌ ์ฐ์ถ๋ฌผ์์ ๋ด๋ถ ๊ฐ์ฌ ๋ฌธ์๋ฅผ ์ ๊ฑฐํ์ต๋๋ค.
v2.7
๐ฆ ์ถ๊ฐ
- `hwpx-unpack`, `hwpx-pack`, `hwpx-analyze-template` CLI๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
- `src/hwpx/tools/archive_cli.py`๋ฅผ ์ถ๊ฐํด unpack/pack ์ํฌํ๋ก๋ฅผ ํจํค์ง ๋ ๋ฒจ ๋๊ตฌ๋ก ์น๊ฒฉํ์ต๋๋ค.
- unpack ์ `.hwpx-pack-metadata.json`์ ๊ธฐ๋กํ๊ณ , pack ์ ์ด๋ฅผ ์ฌ์ฉํด ์๋ณธ ZIP ์ํธ๋ฆฌ ์์/์์ถ ๋ฐฉ์์ ๊ฐ๋ฅํ ๋ฒ์์์ ๋ณด์กดํ๋๋ก ํ์ต๋๋ค.
- `src/hwpx/tools/template_analyzer.py`์ `DevDoc/hwpxskill_gap_audit.md`๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
๐ฆ ๋ณ๊ฒฝ
- `scripts/office/unpack.py`, `scripts/office/pack.py`, `scripts/analyze_template.py`๋ฅผ ํจํค์ง ๋๊ตฌ ๋ํผ๋ก ์ ๋ฆฌํ์ต๋๋ค.
- `page_guard`์ shape/control count ๋ฐ ํ์คํ ๊ทธ๋จ ๋น๊ต๋ฅผ ์ถ๊ฐํ๊ณ , rendered page count๊ฐ ์๋ layout-drift proxy์์ ๋ฌธ์์ CLI ์ค๋ช ์ ๋ช ์ํ์ต๋๋ค.
- README์ `docs/usage.md`์ ์ CLI ์ฌ์ฉ ์์๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
- ์ tooling์ ๋ํ CLI/์ถ์ถ/overwrite/page-guard ํ๊ท ํ ์คํธ๋ฅผ ๊ฐํํ์ต๋๋ค.
v2.6
๐ฆ ์ถ๊ฐ
- `hwpx-validate-package` CLI์ `hwpx.tools.package_validator`๋ฅผ ์ถ๊ฐํด ZIP/OPC/HWPX ํจํค์ง ๊ตฌ์กฐ, `mimetype`, `container.xml`, manifest/spine ์ฐธ์กฐ, XML well-formedness๋ฅผ ์ ๊ฒํ ์ ์๊ฒ ํ์ต๋๋ค.
- `hwpx-page-guard` CLI์ `hwpx.tools.page_guard`๋ฅผ ์ถ๊ฐํด ์น์ ์, ๋จ๋ฝ ์, page/column break, ํ ๊ตฌ์กฐ, ํ ์คํธ ๊ธธ์ด ๋ณํ๋์ ๊ธฐ์ค์ผ๋ก ๋ฌธ์ ๋๋ฆฌํํธ๋ฅผ ๋น๊ตํ ์ ์๊ฒ ํ์ต๋๋ค.
- `hwpx-text-extract` CLI๋ฅผ ์ถ๊ฐํด ๊ธฐ์กด `TextExtractor` ๊ธฐ๋ฅ์ plain/markdown ํํ๋ก ๋ฐ๋ก ์ฌ์ฉํ ์ ์๊ฒ ํ์ต๋๋ค.
- `scripts/office/unpack.py`, `scripts/office/pack.py`, `scripts/analyze_template.py`๋ฅผ ์ถ๊ฐํด XML-first HWPX ์์ ํ๋ฆ์ ์ง์ํฉ๋๋ค.
- gap-closure ๋ฐ์๋ถ์ ๋ํ ํ ์คํธ๋ฅผ ์ถ๊ฐํ์ต๋๋ค (`tests/test_gap_closure_tools.py`).
๐ฆ ์์
- `HwpxDocument.validate()`๊ฐ ๋ด๋ถ ์ง๋ ฌํ ๊ณผ์ ์์ dirty ์ํ๋ฅผ ์ง์ ๋ฒ๋ฆฌ๋ ๋ถ์์ฉ์ ์ ๊ฑฐํด, ๊ฒ์ฆ ์ดํ์๋ ์ ์ฅ ํ์ ์ํ๊ฐ ์ ์ง๋๋๋ก ์์ ํ์ต๋๋ค.
