ํ๊ธ ์์ด HWPX ๋ฌธ์๋ฅผ Python์ผ๋ก ์ฝ๊ณ , ํธ์งํ๊ณ , ์์ฑํ๊ณ , ๊ฒ์ฆํฉ๋๋ค.
| ๊ณ์ธต | ๋ ํฌ | ์ญํ |
|---|---|---|
| ๐ฆ ๋ผ์ด๋ธ๋ฌ๋ฆฌ | python-hwpx |
์์ ํ์ด์ฌ HWPX ํ์ฑยทํธ์งยท์์ฑ ์ฝ์ด |
| ๐ MCP ์๋ฒ | hwpx-mcp-server |
MCP ํด๋ผ์ด์ธํธ(Claude Desktop, VS Code ๋ฑ)์์ HWPX ์กฐ์ |
| ๐ฏ ์์ด์ ํธ ์คํฌ | hwpx-skill |
์์ด์ ํธ๊ฐ HWPX๋ฅผ ๋ฐ๋ก ์ฐ๊ฒ ํด์ฃผ๋ ๊ณต์ ์จ๋ณด๋ฉ ์คํฌ |
- ํ์ปด์คํผ์ค ์ค์น ๋ถํ์ โ HWPX๋ ZIP+XML(OWPML/OPC) ๊ตฌ์กฐ๋ผ, ์์ ํ์ด์ฌ์ผ๋ก WindowsยทmacOSยทLinuxยทCI ์ด๋์๋ ์ฝ๊ณ ์๋๋ค.
- ์ฝ๊ธฐ๋ถํฐ ์์ฑ๊น์ง ํ ์ฝ์ด โ ํ ์คํธ/์์ ์ถ์ถ, ๋ฌธ๋จยทํยท์์ ํธ์ง, ์ ๋ฌธ์ ์์ฑ, XSD ์คํค๋ง ๊ฒ์ฆ์ ํ๋์ API๋ก ์ฒ๋ฆฌํฉ๋๋ค.
- ์์ด์ ํธยท์๋ํ ์นํ โ ๊ฐ์ ์คํ ์์์
hwpx-mcp-server์ ๊ณต์ ์คํฌ์ด ์ง๊ฒฐ๋ฉ๋๋ค.
pip install python-hwpx # Python 3.10+ ยท lxml โฅ 4.9from hwpx import HwpxDocument
# ๊ธฐ์กด ๋ฌธ์ ์ด๊ธฐ โ ํธ์ง โ ์ ์ฅ
doc = HwpxDocument.open("๋ณด๊ณ ์.hwpx")
doc.add_paragraph("์๋ํ๋ก ์ถ๊ฐํ ๋ฌธ๋จ์
๋๋ค.")
doc.save_to_path("๋ณด๊ณ ์-์์ .hwpx")
# ์ ๋ฌธ์ ๋ง๋ค๊ธฐ
new = HwpxDocument.new()
new.add_paragraph("python-hwpx๋ก ๋ง๋ ์ ๋ฌธ์")
new.save_to_path("์๋ฌธ์.hwpx")๐ก ์ปจํ ์คํธ ๋งค๋์ ๋ ์ง์ํฉ๋๋ค โ
with๋ธ๋ก์ ๋ฒ์ด๋๋ฉด ๋ฆฌ์์ค๊ฐ ์๋ ์ ๋ฆฌ๋ฉ๋๋ค:with HwpxDocument.open("๋ณด๊ณ ์.hwpx") as doc: doc.add_paragraph("์๋์ผ๋ก ๋ฆฌ์์ค๊ฐ ์ ๋ฆฌ๋ฉ๋๋ค.") doc.save_to_path("๊ฒฐ๊ณผ๋ฌผ.hwpx")
open/new โ edit/extract โ save_to_path ํ๋ฆ๋ง ์ก์ผ๋ฉด ๋๋จธ์ง๋ ํ์ํ ๋ ํ์ฅํ๋ฉด ๋ฉ๋๋ค.
- ํ
์คํธ/HTML/Markdown ๋ด๋ณด๋ด๊ธฐ โ
export_text()ยทexport_html()ยทexport_markdown() - ํ๋ถํ Markdown โ
export_rich_markdown()์ ์ธ๋ผ์ธ ์์(**๊ตต๊ฒ**ยท*๊ธฐ์ธ์*ยท~~์ทจ์์ ~~), ์ค์ฒฉ ํ(colspan/rowspan ์์ ), ๋ํ ํ ์คํธ, ์ด๋ฏธ์ง, ๊ฐ์ฃผ/๋ฏธ์ฃผ, ํ์ดํผ๋งํฌ, ์ ๋ชฉ(#/##) ์๋ ๊ฐ์ง๊น์ง ๋ณด์กด - ๋ฌธ์ ingest ๊ฒ์ดํธ์จ์ด โ
hwpx.ingest.DocumentIngestor๊ฐ HWPX๋ฅผ ๊ฐ์งํด rich Markdown๊ณผ ์น์ /ํ ๋ฉํ๋ฐ์ดํฐ๋ก ์ ๊ทํ TextExtractor/ObjectFinderโ ์น์ ยท๋ฌธ๋จ ์ํ, ํ๊ทธยท์์ฑยทXPath๋ก ๊ฐ์ฒด ํ์ (hp:tab์\t๋ก ๋ณด์กด, roundtrip ์์ )
doc = HwpxDocument.open("๋ณด๊ณ ์.hwpx")
md = doc.export_rich_markdown(
image_dir="out/images", # BinData ์ด๋ฏธ์ง๋ฅผ ๋์คํฌ์ ์ถ์ถ
image_ref_prefix="images/", # ๋งํฌ๋ค์ด ๋ด  ๊ฒฝ๋ก ์ ๋
detect_headings=True, # โ
./1. ํจํด ๊ธฐ๋ฐ #/## ์๋
)- ๋ฌธ๋จ ์ถ๊ฐ/์ญ์ /์์, Run ๋จ์ ๋ณผ๋ยท์ดํค๋ฆญยท๋ฐ์คยท์์
- ์น์
์ถ๊ฐ/์ญ์ (
add_section(after=)ยทremove_section(), manifest ์๋ ๊ด๋ฆฌ) - ํ ์์ฑยท์ ํ ์คํธยท๋ณํฉ/๋ถํ ยท์ค์ฒฉ ํ ์ด๋ธ, ์ด๋ฏธ์ง ์๋ฒ ๋, ๋จธ๋ฆฌ๊ธ/๋ฐ๋ฅ๊ธ, ๋ฉ๋ชจ(์ต์ปค ๊ธฐ๋ฐ), ๊ฐ์ฃผ/๋ฏธ์ฃผ, ๋ถ๋งํฌ/ํ์ดํผ๋งํฌ, ๋ค๋จ ํธ์ง
- ๊ธฐ์กด ๋ฌธ์ ์์ ํธ์ง โ ์ ๋ ฌยท์ค๊ฐ๊ฒฉยท๋ค์ฌ์ฐ๊ธฐยท๋ฌธ๋จ ๊ฐ๊ฒฉ, ์ฉ์งยท์ฌ๋ฐฑยท๋ฐฉํฅ, ์ชฝ๋ฒํธ, ๋ถ๋ฆฟ/๋ฒํธ
- ์คํ์ผ ๊ธฐ๋ฐ ์นํ โ ์์ยท๋ฐ์คยท
charPrIDRef๋ก Run์ ํํฐ๋งํด ์ ํ ๊ต์ฒด(replace_text_in_runsยทfind_runs_by_style)
# ๋นจ๊ฐ์ ํ
์คํธ๋ง ์ฐพ์์ ์นํ
doc.replace_text_in_runs("์์", "ํ์ ", text_color="#FF0000")- ๋๋ฆํ(ํด๋ฆญํ์ด) ํ๋ ์กฐํยท์์ ๋ณด์กด ์ฑ์, ๋ผ๋ฒจ ๊ธฐ๋ฐ ์
ํ์(
find_cell_by_label)ยท๊ฒฝ๋ก ์ฑ์ฐ๊ธฐ(fill_by_path) - ๋ฐ์ดํธ ๋ณด์กด ๊ตฌ์กฐ ํธ์ง โ ์
์ฑ์ฐ๊ธฐ / ํยท์ดยทํ ์ญ์ ยท์ฝ์
/ ์ด ๋๋น ์คํ ํ / ํฐํธ shrink-to-fit ์ ๋ฌธ์ ์ฌ์กฐ๋ฆฝ ์์ด ์ํํด ์์ ์์์ ๊ทธ๋๋ก ๋ณด์กด. ๋ฏธ์์ ์์ญ์
hwpx.patch๊ฐ section XML ๋ฐ์ดํธ๋ฅผ spliceํด ์๋์ง ์์
doc = HwpxDocument.open("์ ์ฒญ์.hwpx")
result = doc.fill_by_path({
"์ฑ๋ช
> right": "ํ๊ธธ๋",
"์์ > right": "ํ๋ซํผํ",
})
doc.save_to_path("์ ์ฒญ์-์์ฑ์๋ฃ.hwpx")
print(result["applied_count"], result["failed_count"])hwpx.builderโ Section/Heading/Table/Image/Header ์กฐ๋ฆฝํ ์์ฑ + ํ๋๊ฒ์ดํธ ์ ์ฅ ๋ฆฌํฌํธ- ๊ณต๋ฌธ์ ๋๊ตฌ โ
official_lint(ํญ๋ชฉ๊ธฐํธ ์๊ณยท"๋." ํ์ยท๋ถ์ยท๋ ์ง lint), ๊ฒฐ์ฌ๋ ํ๋ฆฌ์ advanced_generatorsโ ์ฌ์ง๋์ง(image_grid)ยทํ์ ๋ช ํจยทํ ๊ธฐ๋ฐ ์กฐ์ง๋mail_mergeโ ํ ํ๋ฆฟ+๋ฐ์ดํฐ N๋ถ ๋๋ ์์ฑ, ํ ํฉ๊ณยทํ๊ท ๊ณ์ฐdoc_diffโ ๋ฌธ๋จ LCS diffยท์ ๊ตฌ๋์กฐํยท์ฐธ์กฐ ์ ํฉ lintstyle_profileโ ์ฐธ์กฐ ๋ฌธ์ ํ๋กํ์ผ ์ถ์ถยท์ ์ฉ, ํ ํ๋ฆฟ ๋ ์ง์คํธ๋ฆฌ
- XSD ์คํค๋ง + ํจํค์ง ๊ตฌ์กฐ ๊ฒ์ฆ โ CLI
hwpx-validateยทhwpx-validate-package,hwpx-analyze-template validate_editor_open_safetyโ ์ ์ฅ/ํฉ/๋ฆฌํ์ด/๋น๋ ์ถ๋ ฅ ๊ฒ์ดํธ,openSafety์ฆ๊ฑฐ ๋ฐํhwpx.tools.fuzz(์๋ ๊ฒฐ์ ์ ์๋๋ฆฌ์คยท3์ค ์ค๋ผํด) ยทhwpx.tools.layout_preview(ํ์ด์ง ๋ฐ์ค ๊ทผ์ฌ HTML/PNG ์๊ธฐ๊ฒ์ฆ) ยทopc.security(XML entityยทZIP ์์ถ ํญํ ๊ฐ๋)hwpx.oxml๋ฐ์ดํฐํด๋์ค๋ก OWPML ์คํค๋ง โ Python ๊ฐ์ฒด ์ง์ ์กฐ์, HWPML 2016โ2011 ๋ค์์คํ์ด์ค ์๋ ์ ๊ทํ
hwpx-validate-package ๋ณด๊ณ ์.hwpx
hwpx-analyze-template ๋ณด๊ณ ์.hwpx์ ์ฒด ๊ธฐ๋ฅยทํด๋์คยท๋ฉ์๋ ๋ชฉ๋ก์ ์ฌ์ฉ ๊ฐ์ด๋์ API ๋ ํผ๋ฐ์ค๋ฅผ ์ฐธ๊ณ ํ์ธ์.
| python-hwpx | pyhwpx | pyhwp | |
|---|---|---|---|
| ๋์ ํฌ๋งท | .hwpx (OWPML/OPC) |
.hwpx |
.hwp (v5 ๋ฐ์ด๋๋ฆฌ) |
| ํ/๊ธ ์ค์น | ๋ถํ์ | ํ์ (Windows COM) | ๋ถํ์ |
| ํฌ๋ก์ค ํ๋ซํผ | โ Linux / macOS / Windows / CI | โ Windows ์ ์ฉ | โ |
| ํธ์ง/์์ฑ API | โ | โ (COM) | โ ๋๋ถ๋ถ ์ฝ๊ธฐ |
| ์คํค๋ง ๊ฒ์ฆ | โ | โ | โ |
| AI ์์ด์ ํธ ์ฐ๋ (MCP) | โ
hwpx-mcp-server |
โ | โ |
HWP(v5 ๋ฐ์ด๋๋ฆฌ) ํ์ผ์ ์ง์ํ์ง ์์ต๋๋ค. ํ์ปด์คํผ์ค์์ HWPX๋ก ๋ณํ ํ ์ฌ์ฉํ์ธ์.
add_shape()/add_control()์ ํ/๊ธ์ด ์๊ตฌํ๋ ๋ชจ๋ ํ์ ์์๋ฅผ ์์ฑํ์ง ์์ต๋๋ค. ๋ณต์กํ ๊ฐ์ฒด ์ถ๊ฐ ์ ํ/๊ธ์์ ์ด์ด ๊ฒ์ฆํ์ธ์.- ์ด๋ฏธ์ง ๋ฐ์ด๋๋ฆฌ ์๋ฒ ๋๋ ์ง์ํ์ง๋ง
<hp:pic>์์์ ์์ ์๋ ์์ฑ์ ์ ๊ณตํ์ง ์์ต๋๋ค. - ์ํธํ๋ HWPX ํ์ผ์ ์๋ณตํธํ๋ ์ง์ํ์ง ์์ต๋๋ค.
- ๐ ๋น ๋ฅธ ์์ ยท ๐ ์ฌ์ฉ ๊ฐ์ด๋ โ ์ฒซ ํ์ผ ์ด๊ธฐ๋ถํฐ ๋ฌธ๋จยทํยท๋ฉ๋ชจยท์น์ ํธ์ง, ํ ์คํธ ์ถ์ถยท๊ฒ์ฆ๊น์ง
- ๐ก ์์ ๋ชจ์ ยท
examples/โbuild_release_checklist.py(๋ฉ๋ชจยท์คํ์ผ ํธ์ง HWPX ์์ฑ),extract_text.py(CLI ํ ์คํธ ์ถ์ถ),find_objects.py(OWPML ๋ ธ๋ ์ถ์ ) ๋ฑ - ๐ ์คํค๋ง ๊ฐ์ ยท ๐ง ์ค์น ๊ฒ์ฆ
- ๐ ์ ์ฒด ๋ฌธ์ (Sphinx) โ API ๋ ํผ๋ฐ์คยท50+ ์ค์ ํจํดยทFAQ
- ๐ CHANGELOG ยท ๐ค CONTRIBUTING ยท ๐ฅ CONTRIBUTORS
๋ฒ๊ทธ ๋ฆฌํฌํธ, ๊ธฐ๋ฅ ์ ์, PR ๋ชจ๋ ํ์ํฉ๋๋ค.
git clone https://github.com/airmang/python-hwpx.git
cd python-hwpx
pip install -e ".[dev]"
pytest์๋ ๊ณต๊ฐ ํ์คยทํ๋ก์ ํธ์ ๋น์ง๊ณ ์์ต๋๋ค.
- OWPML โ ๊ฐ๋ฐฉํ ์๋ํ๋ก์ธ์ ๋งํฌ์ ์ธ์ด (KS X 6101) โ HWPX๊ฐ ๊ธฐ๋ฐํ๋ ํ๊ตญ ์ฐ์ ํ์ค
- hancom-io/hwpx-owpml-model โ OWPML ์์ ๊ตฌ์กฐ ์ฐธ์กฐ ๋ชจ๋ธ ยท neolord0/hwpxlib โ ์ค๋ผํด ์ํ ์ฝํผ์ค
- edwardkim/rhwp โ ๋ฉฑ๋ฑ์ฑยท๊ฒ์ฆ ๊ฒ์ดํธ ์ค๊ณ ์๊ฐ
- ๋ฒ์ ๋ถ์คํผ์ค โ ๊ณต๋ฌด ๋ฌธ์ ํธ์ง ์ํฌํ๋ก ์์ด๋์ด
Apache License 2.0. See LICENSE and NOTICE.
Primary maintainer/contact: ๊ณ ๊ทํ โ ๊ด๊ต๊ณ ๋ฑํ๊ต ์ ๋ณดยท์ปดํจํฐ ๊ต์ฌ
- โ๏ธ kokyuhyun@hotmail.com
- ๐ @airmang