Skip to content

airmang/python-hwpx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

346 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

python-hwpx

ํ•œ๊ธ€ ์—†์ด HWPX ๋ฌธ์„œ๋ฅผ Python์œผ๋กœ ์ฝ๊ณ , ํŽธ์ง‘ํ•˜๊ณ , ์ƒ์„ฑํ•˜๊ณ , ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

PyPI Python License Docs


๐Ÿงฉ HWPX Stack (3์ข…)

๊ณ„์ธต ๋ ˆํฌ ์—ญํ• 
๐Ÿ“ฆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ python-hwpx ์ˆœ์ˆ˜ ํŒŒ์ด์ฌ HWPX ํŒŒ์‹ฑยทํŽธ์ง‘ยท์ƒ์„ฑ ์ฝ”์–ด
๐Ÿ”Œ MCP ์„œ๋ฒ„ hwpx-mcp-server MCP ํด๋ผ์ด์–ธํŠธ(Claude Desktop, VS Code ๋“ฑ)์—์„œ HWPX ์กฐ์ž‘
๐ŸŽฏ ์—์ด์ „ํŠธ ์Šคํ‚ฌ hwpx-skill ์—์ด์ „ํŠธ๊ฐ€ HWPX๋ฅผ ๋ฐ”๋กœ ์“ฐ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ณต์‹ ์˜จ๋ณด๋”ฉ ์Šคํ‚ฌ

์™œ python-hwpx์ธ๊ฐ€

  • ํ•œ์ปด์˜คํ”ผ์Šค ์„ค์น˜ ๋ถˆํ•„์š” โ€” HWPX๋Š” ZIP+XML(OWPML/OPC) ๊ตฌ์กฐ๋ผ, ์ˆœ์ˆ˜ ํŒŒ์ด์ฌ์œผ๋กœ WindowsยทmacOSยทLinuxยทCI ์–ด๋””์„œ๋‚˜ ์ฝ๊ณ  ์”๋‹ˆ๋‹ค.
  • ์ฝ๊ธฐ๋ถ€ํ„ฐ ์ƒ์„ฑ๊นŒ์ง€ ํ•œ ์ฝ”์–ด โ€” ํ…์ŠคํŠธ/์„œ์‹ ์ถ”์ถœ, ๋ฌธ๋‹จยทํ‘œยท์–‘์‹ ํŽธ์ง‘, ์ƒˆ ๋ฌธ์„œ ์ƒ์„ฑ, XSD ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ์„ ํ•˜๋‚˜์˜ API๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ์—์ด์ „ํŠธยท์ž๋™ํ™” ์นœํ™” โ€” ๊ฐ™์€ ์Šคํƒ ์œ„์—์„œ hwpx-mcp-server์™€ ๊ณต์‹ ์Šคํ‚ฌ์ด ์ง๊ฒฐ๋ฉ๋‹ˆ๋‹ค.

๋น ๋ฅธ ์‹œ์ž‘

pip install python-hwpx      # Python 3.10+ ยท lxml โ‰ฅ 4.9
from 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/",   # ๋งˆํฌ๋‹ค์šด ๋‚ด ![](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")

๐Ÿ–Š๏ธ ์–‘์‹ ์ฑ„์šฐ๊ธฐ (byte-preserving)

  • ๋ˆ„๋ฆ„ํ‹€(ํด๋ฆญํžˆ์–ด) ํ•„๋“œ ์กฐํšŒยท์„œ์‹ ๋ณด์กด ์ฑ„์›€, ๋ผ๋ฒจ ๊ธฐ๋ฐ˜ ์…€ ํƒ์ƒ‰(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ยท์‹ ๊ตฌ๋Œ€์กฐํ‘œยท์ฐธ์กฐ ์ •ํ•ฉ lint
  • style_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 ํŒŒ์ผ์˜ ์•”๋ณตํ˜ธํ™”๋Š” ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋” ๋ณด๊ธฐ

๊ธฐ์—ฌํ•˜๊ธฐ

๋ฒ„๊ทธ ๋ฆฌํฌํŠธ, ๊ธฐ๋Šฅ ์ œ์•ˆ, PR ๋ชจ๋‘ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.

git clone https://github.com/airmang/python-hwpx.git
cd python-hwpx
pip install -e ".[dev]"
pytest

๊ฐ์‚ฌ์˜ ๋ง

์•„๋ž˜ ๊ณต๊ฐœ ํ‘œ์ค€ยทํ”„๋กœ์ ํŠธ์— ๋นš์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

License

Apache License 2.0. See LICENSE and NOTICE.

Maintainer

Primary maintainer/contact: ๊ณ ๊ทœํ˜„ โ€” ๊ด‘๊ต๊ณ ๋“ฑํ•™๊ต ์ •๋ณดยท์ปดํ“จํ„ฐ ๊ต์‚ฌ

About

Pure Python HWPX automation: read, edit, generate, and validate documents without Hancom Office.

Topics

Resources

License

Contributing

Stars

90 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors