ํ๊ธ ์์ด HWPX ๋ฌธ์๋ฅผ Python์ผ๋ก ์ฝ๊ณ , ํธ์งํ๊ณ , ์์ฑํ๊ณ , ๊ฒ์ฆํฉ๋๋ค.
| ๊ณ์ธต | ๋ ํฌ | ์ญํ |
|---|---|---|
| ๐ฆ ๋ผ์ด๋ธ๋ฌ๋ฆฌ | python-hwpx |
์์ ํ์ด์ฌ HWPX ํ์ฑยทํธ์งยท์์ฑ ์ฝ์ด |
| ๐ MCP ์๋ฒ | hwpx-mcp-server |
MCP ํด๋ผ์ด์ธํธ(Claude Desktop, VS Code ๋ฑ)์์ HWPX ์กฐ์ |
| ๐ฏ ์์ด์ ํธ ์คํฌ | hwpx-skill |
์์ด์ ํธ๊ฐ HWPX๋ฅผ ๋ฐ๋ก ์ฐ๊ฒ ํด์ฃผ๋ ๊ณต์ ์จ๋ณด๋ฉ ์คํฌ |
- ํ์ปด์คํผ์ค ์ค์น ๋ถํ์ โ ์์ ํ์ด์ฌ์ผ๋ก ์ด๋์๋ ๋์
- XML-first ์ํฌํ๋ก โ ์คํค๋ง ๊ฒ์ฆยทunpack/pack๊น์ง ํฌํจ
- ์์ด์ ํธยท์๋ํ ์นํ โ MCP ์๋ฒยทSkill์ด ๊ฐ์ ์คํ ์์์ ์ง๊ฒฐ
| ํญ๋ชฉ | python-hwpx | pyhwp(x) ๋ฅ | ole+bin ์์์ |
|---|---|---|---|
| HWPX Open XML ์ง์ | โ | โ | |
| ํ์ปด์คํผ์ค ์ค์น ๋ถํ์ | โ | โ | โ |
| ํธ์ง/์์ฑ API | โ | โ ๋๋ถ๋ถ ์ฝ๊ธฐ | โ |
| ์คํค๋ง ๊ฒ์ฆ | โ | โ | โ |
| AI ์์ด์ ํธ ์ฐ๋ (MCP) | โ (hwpx-mcp-server) | โ | โ |
from hwpx import HwpxDocument
document = HwpxDocument.open("๋ณด๊ณ ์.hwpx")
document.add_paragraph("์๋ํ๋ก ์ถ๊ฐํ ๋ฌธ๋จ์
๋๋ค.")
document.save_to_path("๋ณด๊ณ ์-์์ .hwpx")from hwpx import HwpxDocument
doc = HwpxDocument.open("์ ์ฒญ์.hwpx")
result = doc.fill_by_path({
"์ฑ๋ช
> right": "ํ๊ธธ๋",
"์์ > right": "ํ๋ซํผํ",
})
doc.save_to_path("์ ์ฒญ์-์์ฑ์๋ฃ.hwpx")
print(result["applied_count"], result["failed_count"])from hwpx import HwpxDocument
text = HwpxDocument.open("๋ณด๊ณ ์.hwpx").export_markdown()
print(text[:500])hwpx-validate-package ๋ณด๊ณ ์.hwpx
hwpx-analyze-template ๋ณด๊ณ ์.hwpx์ฒ์์๋ open/new -> edit/extract -> save_to_path ํ๋ฆ๋ง ์ก์ผ๋ฉด ๋๋ค. ํจํค์ง ๊ตฌ์กฐ, XML ํํธ, ํ
ํ๋ฆฟ ํ๊ท ์ ๊ฒ์ ํ์ํ ๋๋ง ํ์ฅํ๋ฉด ๋๋ค.
ํ์ํ ์์ ๋ถํฐ ๋ฐ๋ก ๋ค์ด๊ฐ๋ฉด ๋๋ค.
- ์ฒซ ํ์ผ์ ์ด๊ณ ์ ์ฅํ๋ ์ต์ ๊ฒฝ๋ก โ
docs/quickstart.md - ๋ฌธ๋จ, ํ, ๋ฉ๋ชจ, ์น์
ํธ์ง ํจํด โ
docs/usage.md - ํ
์คํธ ์ถ์ถ, ๊ตฌ์กฐ ์กฐํ, ๊ฒ์ฆ/ํจํค์ง ์์
โ
docs/usage.md - ์คํ ๊ฐ๋ฅํ ์์ ๋ชจ์ โ
docs/examples.md - ํจํค์ง ๊ตฌ์กฐ์ ์คํค๋ง ์ฌํ โ
docs/schema-overview.md - ์ค์น ๊ฒ์ฆ๊ณผ ๊ฐ๋ฐ ํ๊ฒฝ ํ์ธ โ
docs/installation.md
|
build_release_checklist.py ๋ฉ๋ชจ์ ์คํ์ผ ํธ์ง์ด ํฌํจ๋ ๋ฆด๋ฆฌ์ค ์ฒดํฌ๋ฆฌ์คํธ์ฉ HWPX๋ฅผ ์์ฑํ๋ค. |
extract_text.py ๋ณธ๋ฌธ๊ณผ ์ค์ฒฉ ๊ฐ์ฒด ํ ์คํธ๋ฅผ CLI๋ก ๋น ๋ฅด๊ฒ ์ถ์ถํ๋ค. |
find_objects.py ํ๊ทธยท์์ฑ ๊ธฐ์ค์ผ๋ก OWPML XML ๋ ธ๋๋ฅผ ์ถ์ ํ๋ค. |
์ ๋ฌธ์๋ฅผ ๋ฐ๋ก ๋ง๋ค๊ณ ์ถ๋ค๋ฉด ์ด๋ ๊ฒ ์์ํ๋ฉด ๋๋ค.
from hwpx import HwpxDocument
document = HwpxDocument.new()
document.add_paragraph("python-hwpx๋ก ๋ง๋ ์ ๋ฌธ์")
document.save_to_path("์๋ฌธ์.hwpx")๐ก ์ปจํ ์คํธ ๋งค๋์ ๋ ์ง์ํฉ๋๋ค:
with HwpxDocument.open("๋ณด๊ณ ์.hwpx") as doc: doc.add_paragraph("์๋์ผ๋ก ๋ฆฌ์์ค๊ฐ ์ ๋ฆฌ๋ฉ๋๋ค.") doc.save_to_path("๊ฒฐ๊ณผ๋ฌผ.hwpx")
ํ, ๋ฉ๋ชจ, ํ
์คํธ ์ถ์ถ, ๊ฒ์ฆ, ํจํค์ง/XML ์ฌํ๋ docs/quickstart.md์ docs/usage.md์์ ๋ฐ๋ก ์ด์ด์ง๋ค.
pyhwpx / pyhwp์ ๋ค๋ฅธ ์ ?
python-hwpx pyhwpx pyhwp ๋์ ํฌ๋งท .hwpx(OWPML/OPC).hwpx.hwp(v5 ๋ฐ์ด๋๋ฆฌ)ํ/๊ธ ์ค์น ๋ถํ์ ํ์ (Windows COM) ๋ถํ์ ํฌ๋ก์ค ํ๋ซํผ โ Linux / macOS / Windows / CI โ Windows ์ ์ฉ โ ๋ฐฉ์ ์ง์ XML ํ์ฑ COM ์๋ํ OLE ํ์ฑ
HWPX ํ์ผ์ ZIP + XML ๊ตฌ์กฐ์ด๋ฏ๋ก, ํ/๊ธ ํ๋ก๊ทธ๋จ ์์ด Python๋ง์ผ๋ก ์ฝ๊ณ ํธ์งํ๋ ์ํฌํ๋ก๋ฅผ ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
| ํ๋ซํผ | ์ฝ๊ธฐ | ์ฐ๊ธฐ | ๋น๊ณ |
|---|---|---|---|
| โ Windows | โ | โ | ํ์ปด์คํผ์ค |
| โ macOS | โ | โ | ํ์ปด์คํผ์ค Mac |
| โ Linux | โ | โ | ํ์ปด์คํผ์ค Linux |
| โ CI/CD | โ | โ | Docker, GitHub Actions ๋ฑ |
| ์นดํ ๊ณ ๋ฆฌ | ๊ธฐ๋ฅ | ์ค๋ช |
|---|---|---|
| ๐ ๋ฌธ์ I/O | ์ด๊ธฐ/์ ์ฅ/์์ฑ | ํ์ผ, ๋ฐ์ดํธ, ์คํธ๋ฆผ ์ ์ถ๋ ฅ ยท ์์์ ์ ์ฅ ยท ZIP ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ |
| ๐ ๋จ๋ฝ | ์ถ๊ฐ/์ญ์ /ํธ์ง/์์ | ํ
์คํธ ์ค์ , ๋จ๋ฝ ์ญ์ (remove_paragraph), ์คํ์ผ ์ฐธ์กฐ |
| โ๏ธ Run | ํ ์คํธ ์กฐ๊ฐ | ์ถ๊ฐ, ๊ต์ฒด, ๋ณผ๋/์ดํค๋ฆญ/๋ฐ์ค/์์ ์์ |
| ๐ ํ(Table) | ์์ฑ/ํธ์ง/๋ณํฉ | NรM ํ ์์ฑ, ์ ํ ์คํธ, ์ ๋ณํฉ/๋ถํ , ์ค์ฒฉ ํ ์ด๋ธ |
| ๐งญ ํ ์๋ํ | ํ์/์ฑ์ฐ๊ธฐ | ํ ์ด๋ธ ๋งต, ๋ผ๋ฒจ ๊ธฐ๋ฐ ์ ํ์, ๊ฒฝ๋ก ๊ธฐ๋ฐ ๋ฐฐ์น ์ฑ์ฐ๊ธฐ |
| ๐ ์น์ | ์ถ๊ฐ/์ญ์ | add_section(after=), remove_section(), manifest ์๋ ๊ด๋ฆฌ |
| ๐ผ๏ธ ์ด๋ฏธ์ง | ์๋ฒ ๋/์ญ์ | ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ ๊ด๋ฆฌ, manifest ์๋ ๋ฑ๋ก |
| โ๏ธ ๋ํ | ์ /์ฌ๊ฐํ/ํ์ | OWPML ๋ช ์ธ ์ค์ ๋ํ ์ฝ์ |
| ๐ ๋จธ๋ฆฌ๊ธ/๋ฐ๋ฅ๊ธ | ์ค์ /์ ๊ฑฐ | ํ์/์ง์/์์ชฝ ํ์ด์ง ๊ตฌ๋ถ |
| ๐ฌ ๋ฉ๋ชจ | ์ถ๊ฐ/์ญ์ | ์ต์ปค ๊ธฐ๋ฐ ๋ฉ๋ชจ, ๋ฉ๋ชจ ์ ฐ์ดํ ์ฐธ์กฐ |
| ๐ ๊ฐ์ฃผ/๋ฏธ์ฃผ | ์ถ๊ฐ | ํ ์คํธ ์ ๊ทผ |
| ๐ ๋ถ๋งํฌ/ํ์ดํผ๋งํฌ | ์ฝ์ /์กฐํ | URL ๋งํฌ, ๋ด๋ถ ๋ถ๋งํฌ |
| ๐ฐ ๋ค๋จ ํธ์ง | ์ปฌ๋ผ ์ ์ | ๋ค๋จ ๋ ์ด์์ ์ ์ด |
| ๐ ํ ์คํธ ์ถ์ถ | ํ์ดํ๋ผ์ธ | ์น์ /๋จ๋ฝ ์ํ, ์ฃผ์ ๋ ๋๋ง, ์ค์ฒฉ ๊ฐ์ฒด ์ ์ด |
| ๐ ๊ฐ์ฒด ๊ฒ์ | ํ๊ทธ/์์ฑ/XPath | ํน์ ์์ ํ์, ์ฃผ์ ์ดํฐ๋ ์ดํฐ |
| ๐จ ์คํ์ผ ์นํ | ์์ ๊ธฐ๋ฐ ํํฐ | ์์/๋ฐ์ค/charPrIDRef ๊ธฐ๋ฐ Run ๊ฒ์ ๋ฐ ๊ต์ฒด |
| ๐ค ๋ด๋ณด๋ด๊ธฐ | ํ ์คํธ/HTML/Markdown | ๋ฌธ์ ๋ณํ ์ถ๋ ฅ |
| โ ์ ํจ์ฑ ๊ฒ์ฌ | XSD + ํจํค์ง ๊ตฌ์กฐ | CLI(hwpx-validate, hwpx-validate-package) ๋ฐ API |
| ๐งฐ ์์ ๋๊ตฌ | unpack/pack/๋ถ์/๋น๊ต | pack-ready ์์ ๋๋ ํฐ๋ฆฌ ์ถ์ถ๊ณผ ์ฌ๊ตฌ์ฑ ์ ๊ฒ |
| ๐๏ธ ์ ์์ค XML | ๋ฐ์ดํฐํด๋์ค ๋งคํ | OWPML ์คํค๋ง โ Python ๊ฐ์ฒด ์ง์ ์กฐ์ |
| ๐ ๋ค์์คํ์ด์ค ํธํ | ์๋ ์ ๊ทํ | HWPML 2016 โ 2011 ์๋ ๋ณํ |
๋ฌธ๋จ, ํ, ๋ฉ๋ชจ, ๋จธ๋ฆฌ๊ธ/๋ฐ๋ฅ๊ธ์ Python ๊ฐ์ฒด๋ก ๋ค๋ฃน๋๋ค.
# ๋จ๋ฝ ์ถ๊ฐยท์ญ์
doc.add_paragraph("์ ๋ฌธ๋จ")
doc.remove_paragraph(doc.paragraphs[-1]) # ๋ง์ง๋ง ๋จ๋ฝ ์ญ์
# ์น์
์ถ๊ฐยท์ญ์
new_sec = doc.add_section() # ๋ฌธ์ ๋์ ์น์
์ถ๊ฐ
new_sec.add_paragraph("๋ ๋ฒ์งธ ์น์
๋ด์ฉ")
doc.remove_section(1) # ์ธ๋ฑ์ค๋ก ์น์
์ญ์
# ๋จธ๋ฆฌ๊ธยท๋ฐ๋ฅ๊ธ
doc.set_header_text("๊ธฐ๋ฐ ๋ฌธ์", page_type="BOTH")
doc.set_footer_text("1 / 10", page_type="BOTH")
# ํ ์
๋ณํฉยท๋ถํ
table.merge_cells(0, 0, 1, 1) # (0,0)~(1,1) ๋ณํฉ
table.set_cell_text(0, 0, "๋ณํฉ๋ ์
", logical=True, split_merged=True)
# ์์ํ ํ ์๋ ์ฑ์ฐ๊ธฐ
form = doc.add_table(2, 2)
form.cell(0, 0).text = "์ฑ๋ช
:"
form.cell(1, 0).text = "์์"
doc.find_cell_by_label("์ฑ๋ช
") # {"matches": [...], "count": 1}
doc.fill_by_path({
"์ฑ๋ช
> right": "ํ๊ธธ๋",
"์์ > right": "ํ๋ซํผํ",
})from hwpx import TextExtractor, ObjectFinder
# ํ
์คํธ ์ถ์ถ
with TextExtractor("๋ฌธ์.hwpx") as extractor:
for section in extractor.iter_sections():
for para in extractor.iter_paragraphs(section):
print(para.text())
# ํน์ ๊ฐ์ฒด ํ์
for obj in ObjectFinder("๋ฌธ์.hwpx").find_all(tag="tbl"):
print(obj.tag, obj.path)hp:tab๊ณผ ctrl id="tab"์ ํญ ๋ฌธ์(\t)๋ก ๋ณด์กด๋ฉ๋๋ค. ๋ฐ๋ผ์ Paragraph.text, TextExtractor, export_text()/export_html()/export_markdown() ๊ฒฝ๋ก์์ ๊ฐ์ ํญ ์๋ฏธ๋ฅผ ์ ์งํ ์ฑ roundtrip ํ ์ ์์ต๋๋ค. ํ์ํ๋ฉด preserve_breaks=False๋ก ์ค๋ฐ๊ฟ/ํญ์ ๊ณต๋ฐฑ ๊ธฐ๋ฐ์ผ๋ก ํํํํ ์ ์์ต๋๋ค.
์์(์์, ๋ฐ์ค, charPrIDRef)์ผ๋ก ๋ฐ์ ํํฐ๋งํด ์ ํ์ ์ผ๋ก ๊ต์ฒดํฉ๋๋ค.
# ๋นจ๊ฐ์ ํ
์คํธ๋ง ์ฐพ์์ ์นํ
doc.replace_text_in_runs(
"์์", "ํ์ ",
text_color="#FF0000",
)
# ํน์ ์์์ ๋ฐ ๊ฒ์
runs = doc.find_runs_by_style(underline_type="SINGLE")# ํ
์คํธ, HTML, Markdown์ผ๋ก ๋ณํ
text = doc.export_text()
html = doc.export_html()
md = doc.export_markdown()OWPML ์คํค๋ง์ ๋งคํ๋ ๋ฐ์ดํฐํด๋์ค๋ก XML ๊ตฌ์กฐ๋ฅผ ์ง์ ๋ค๋ฃน๋๋ค.
# ํค๋ ์ฐธ์กฐ ๋ชฉ๋ก
doc.border_fills # ํ
๋๋ฆฌ ์ฑ์ฐ๊ธฐ
doc.bullets # ๊ธ๋จธ๋ฆฌํ
doc.styles # ์คํ์ผ
doc.track_changes # ๋ณ๊ฒฝ ์ถ์
# ๋ฐํ์ชฝยท์ด๋ ฅยท๋ฒ์ ํํธ
doc.master_pages
doc.histories
doc.versionpython-hwpx
โโโ hwpx.document # ๊ณ ์์ค ํธ์ง API (HwpxDocument)
โโโ hwpx.opc # OPC ์ปจํ
์ด๋ ์ฝ๊ธฐ/์ฐ๊ธฐ (์์์ ์ ์ฅ, ZIP ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ)
โโโ hwpx.oxml # OWPML XML โ ๋ฐ์ดํฐํด๋์ค ๋งคํ
โ โโโ document.py # ์น์
, ๋ฌธ๋จ, ํ, ๋ฐ, ๋ฉ๋ชจ, ๋ํ, ๋
ธํธ
โ โโโ header.py # ํค๋ ์ฐธ์กฐ ๋ชฉ๋ก (์คํ์ผ, ๊ธ๋จธ๋ฆฌํ, ๋ณ๊ฒฝ์ถ์ ๋ฑ)
โ โโโ body.py # ํ์
์ด ์ง์ ๋ ๋ณธ๋ฌธ ๋ชจ๋ธ
โ โโโ common.py # ๋ฒ์ฉ XML โ ๋ฐ์ดํฐํด๋์ค
โโโ hwpx.tools
โ โโโ archive_cli # unpack/pack CLI ๋ฐ ์ฌํจํน ๋ฉํ๋ฐ์ดํฐ
โ โโโ text_extractor # ํ
์คํธ ์ถ์ถ ํ์ดํ๋ผ์ธ
โ โโโ text_extract_cli # ํ
์คํธ ์ถ์ถ CLI
โ โโโ object_finder # ๊ฐ์ฒด ํ์ ์ ํธ๋ฆฌํฐ
โ โโโ exporter # ํ
์คํธ/HTML/Markdown ๋ด๋ณด๋ด๊ธฐ
โ โโโ validator # ์คํค๋ง ์ ํจ์ฑ ๊ฒ์ฌ (hwpx-validate CLI)
โ โโโ package_validator# ZIP/OPC/HWPX ๊ตฌ์กฐ ๊ฒ์ฌ
โ โโโ page_guard # ๊ตฌ์กฐ ๋ณํ ์งํ ์ ๊ฒ
โ โโโ template_analyzer# ๋ ํผ๋ฐ์ค ๋ฌธ์ ๋ถ์/์ถ์ถ
โโโ hwpx.templates # ๋ด์ฅ ๋น ๋ฌธ์ ํ
ํ๋ฆฟ
| ๐ ์ ์ฒด ๋ฌธ์ | Sphinx ๊ธฐ๋ฐ API ๋ ํผ๋ฐ์ค, ์ฌ์ฉ ๊ฐ์ด๋, FAQ |
| ๐ ๋น ๋ฅธ ์์ | 5๋ถ ์์ HWPX ๋ฌธ์ ๋ค๋ฃจ๊ธฐ |
| ๐ ์ฌ์ฉ ๊ฐ์ด๋ | 50+ ์ค์ ์ฌ์ฉ ํจํด |
| ๐ง API ๋ ํผ๋ฐ์ค | ํด๋์คยท๋ฉ์๋ ์์ธ ๋ช ์ธ |
| ๐ ์คํค๋ง ๊ฐ์ | OWPML ์คํค๋ง ๊ตฌ์กฐ ์ค๋ช |
| ๐งช ์คํ ํตํฉ ์๋ฃ | fixture, smoke, validation, compatibility ์ด์ ์๋ฃ |
| ํฌ๋งท | ํ์ฅ์ | ์ฝ๊ธฐ | ์ฐ๊ธฐ |
|---|---|---|---|
| HWPX | .hwpx |
โ | โ |
| HWP | .hwp |
โ | โ |
Note: HWP(v5 ๋ฐ์ด๋๋ฆฌ) ํ์ผ์ ์ง์ํ์ง ์์ต๋๋ค. ํ์ปด์คํผ์ค์์ HWPX๋ก ๋ณํ ํ ์ฌ์ฉํ์ธ์.
- Python 3.10+
- lxml โฅ 4.9
add_shape()/add_control()์ ํ/๊ธ์ด ์๊ตฌํ๋ ๋ชจ๋ ํ์ ์์๋ฅผ ์์ฑํ์ง ์์ต๋๋ค. ๋ณต์กํ ๊ฐ์ฒด๋ฅผ ์ถ๊ฐํ ๋๋ ํ/๊ธ์์ ์ด์ด ๊ฒ์ฆํด ์ฃผ์ธ์.- ์ด๋ฏธ์ง ์ฝ์
์ ๋ฐ์ด๋๋ฆฌ ์๋ฒ ๋๋ ์ง์ํ์ง๋ง,
<hp:pic>์์์ ์์ ํ ์๋ ์์ฑ์ ์ ๊ณตํ์ง ์์ต๋๋ค. - ์ํธํ๋ HWPX ํ์ผ์ ์๋ณตํธํ๋ ์ง์ํ์ง ์์ต๋๋ค.
๋ฒ๊ทธ ๋ฆฌํฌํธ, ๊ธฐ๋ฅ ์ ์, PR ๋ชจ๋ ํ์ํฉ๋๋ค. ๊ฐ๋ฐ ํ๊ฒฝ ์ค์ ๊ณผ ํ ์คํธ ๋ฐฉ๋ฒ์ CONTRIBUTING.md๋ฅผ ์ฐธ๊ณ ํ์ธ์.
git clone https://github.com/airmang/python-hwpx.git
cd python-hwpx
pip install -e ".[dev]"
pytest๋จธ์ง๋ ๊ธฐ์ฌ์ ๋ชฉ๋ก์ CONTRIBUTORS.md์์ ํ์ธํ ์ ์์ต๋๋ค.
Apache License 2.0. See LICENSE and NOTICE.
Primary maintainer/contact: ๊ณ ๊ทํ โ ๊ด๊ต๊ณ ๋ฑํ๊ต ์ ๋ณดยท์ปดํจํฐ ๊ต์ฌ
- โ๏ธ kokyuhyun@hotmail.com
- ๐ @airmang