νκΈ μμ΄ 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