SuperAPI AI Directory

hwpx

General & other

View repository

Updated 10-15 17:31

About this skill

HWPX Document Creation·Editing Skill

Overview

HWPX is the open document format of Hancom Office Hangul. Internally it is ZIP package + XML parts structure, and is based on the KS X 6101 (OWPML) standard. This skill uses the python-hwpx library to programmatically create, edit, and template-substitute HWPX documents.

Installation

pip install python-hwpx --break-system-packages

⚠️⚠️⚠️ Top-priority rule: Form (template) selection policy ⚠️⚠️⚠️

When creating an HWPX document, always follow the order below. No exceptions.

Step 1: Is there a user-uploaded form?

If the user has uploaded a .hwpx form file, always use that file as the template.

  • /mnt/user-data/uploads/ 에 .hwpx check whether the file exists
  • If it exists → copy that file and use it as the template (ignore the default form)
  • If the user uses expressions such as "make it with this form", "based on this file", etc., use that file 100%

Step 2: Use the default provided form

If there is no user-uploaded form, always use the default provided form:

  • Report → assets/report-template.hwpx
  • (This rule also applies to other forms to be added in the future)

Step 3: HwpxDocument.new() is the last resort

HwpxDocument.new()Creating a blank document with is allowed only for very simple memo·list-level documents . Documents that require a form, such as reports, official letters, and draft documents, are never created with new().


⚠️ Required workflow when using a form (applies in all cases)

Whichever form is used (whether user-uploaded or default provided), follow the workflow below:

[1] 양식 파일을 /home/claude/ 로 복사
     ↓
[2] ObjectFinder로 양식 내 텍스트 전수 조사
     ↓
[3] 플레이스홀더 목록 작성 (어떤 텍스트를 뭘로 바꿀지 매핑)
     ↓
[4] ZIP-level 전체 치환 (표 내부 포함)
     ↓  (동일 플레이스홀더가 여러 번 나오면 순차 치환 사용)
[5] 네임스페이스 후처리 (fix_namespaces.py)
     ↓
[6] ObjectFinder로 치환 결과 검증
     ↓
[7] /mnt/user-data/outputs/ 로 복사 → present_files

Key point: Do not use HwpxDocument.open()

python-hwpx Depending on the version, HwpxDocument.open()may not be able to parse complex form files. Using only ZIP-level substitutionis safe.


ZIP-level substitution function (implement directly)

hwpx_replace Since the module does not exist separately, include the function below directly in the code:

Batch substitution (all identical text to the same value)

import zipfile, os

def zip_replace(src_path, dst_path, replacements):
    """HWPX ZIP 내 모든 XML에서 텍스트 치환 (표 내부 포함)"""
    tmp = dst_path + ".tmp"
    with zipfile.ZipFile(src_path, "r") as zin:
        with zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as zout:
            for item in zin.infolist():
                data = zin.read(item.filename)
                if item.filename.startswith("Contents/") and item.filename.endswith(".xml"):
                    text = data.decode("utf-8")
                    for old, new in replacements.items():
                        text = text.replace(old, new)
                    data = text.encode("utf-8")
                zout.writestr(item, data)
    if os.path.exists(dst_path):
        os.remove(dst_path)
    os.rename(tmp, dst_path)

Sequential substitution (the same placeholder to different values in order)

def zip_replace_sequential(src_path, dst_path, old, new_list):
    """section XML에서 old를 순서대로 new_list 값으로 하나씩 치환"""
    tmp = dst_path + ".tmp"
    with zipfile.ZipFile(src_path, "r") as zin:
        with zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as zout:
            for item in zin.infolist():
                data = zin.read(item.filename)
                if "section" in item.filename and item.filename.endswith(".xml"):
                    text = data.decode("utf-8")
                    for new_val in new_list:
                        text = text.replace(old, new_val, 1)  # 1번만 치환
                    data = text.encode("utf-8")
                zout.writestr(item, data)
    if os.path.exists(dst_path):
        os.remove(dst_path)
    os.rename(tmp, dst_path)

How to exhaustively survey text within a form

from hwpx import ObjectFinder

finder = ObjectFinder("양식파일.hwpx")
results = finder.find_all(tag="t")
for r in results:
    if r.text and r.text.strip():
        print(repr(r.text))

After looking at this result and identifying which text is a placeholder, write the substitution mapping.


Guide to using the default form (report-template.hwpx)

Form structure

1쪽: 표지      → 기관명(30pt) + 보고서 제목(25pt) + 작성일(25pt)
2쪽: 목차      → 로마숫자(Ⅰ~Ⅴ) + 제목 + 페이지, 붙임/참고
3쪽~: 본문     → 결재란 + 제목(22pt) + 섹션 바(Ⅰ~Ⅳ) + □○―※ 계층 본문

Body symbol system (completely different from official documents!)

1단계:  □    (HY헤드라인M 16pt, 문단 위 15)
2단계:  ○    (휴먼명조 15pt, 문단 위 10)
3단계:  ―    (휴먼명조 15pt, 문단 위 6)
4단계:  ※    (한양중고딕 13pt, 문단 위 3)

List of substitutable placeholders

Placeholder Location Substitution target Replacement Method
브라더 공기관 Cover Line 1 Organization Name Batch Replacement
기본 보고서 양식 Cover Line 2 Report Title Batch Replacement
2024. 5. 23. Cover Date Actual Date Batch Replacement
제 목 Body Page Title Report Title Batch Replacement
. 개요 등 Table of Contents Item Actual Table of Contents Title Batch Replacement
추진 배경 등 Section Bar Title Actual Section Title Batch Replacement
헤드라인M 폰트 16포인트(문단 위 15) □ Body (8 items) Level 1 Content Sequential Replacement
○ 휴면명조 15포인트(문단위 10) ○ Body (8 items) Level 2 Content Sequential Replacement
― 휴면명조 15포인트(문단 위 6) ― Body (8 items) Level 3 Content Sequential Replacement
※ 중고딕 13포인트(문단 위 3) ※ Notes (7 items) Level 4 Reference Sequential Replacement
1. 세부내용 / 2. 세부내용 Attachment/Reference Attachment List Batch Replacement

Example of Using the Default Template (Full Code)

import shutil, subprocess

# 양식 복사
TEMPLATE = "/mnt/skills/user/hwpx/assets/report-template.hwpx"
WORK = "/home/claude/report.hwpx"
shutil.copy(TEMPLATE, WORK)

# 1. 표지 + 목차 + 섹션 바 + 제목 (일괄 치환)
zip_replace(WORK, WORK, {
    "브라더 공기관": "실제 기관명",
    "기본 보고서 양식": "실제 보고서 제목",
    "2024. 5. 23.": "2026. 2. 13.",
    "제 목": "실제 보고서 제목",
    ". 개요": ". 실제 목차1",
    ". 추진배경": ". 실제 목차2",
    # ... 나머지 목차, 섹션 바 치환
})

# 2. □ 항목 (순차 치환 — 8개)
zip_replace_sequential(WORK, WORK,
    "헤드라인M 폰트 16포인트(문단 위 15)",
    ["첫번째 □ 내용", "두번째 □ 내용", ...]
)

# 3. ○, ―, ※ 항목도 각각 순차 치환
# ...

# 4. 네임스페이스 후처리 (필수!)
subprocess.run(
    ["python", "/mnt/skills/user/hwpx/scripts/fix_namespaces.py", WORK],
    check=True
)

# 5. 결과 검증
from hwpx import ObjectFinder
finder = ObjectFinder(WORK)
for r in finder.find_all(tag="t"):
    if r.text and r.text.strip():
        print(r.text)

Guide to Using User-Uploaded Templates

If the user has uploaded their own .hwpx template:

import shutil, subprocess

# 1. 사용자 양식을 작업 디렉토리로 복사
USER_TEMPLATE = "/mnt/user-data/uploads/사용자양식.hwpx"
WORK = "/home/claude/report.hwpx"
shutil.copy(USER_TEMPLATE, WORK)

# 2. 양식 내 텍스트 전수 조사 (★ 필수 단계!)
from hwpx import ObjectFinder
finder = ObjectFinder(WORK)
for r in finder.find_all(tag="t"):
    if r.text and r.text.strip():
        print(repr(r.text))

# 3. 조사 결과를 바탕으로 치환 매핑 작성
#    (양식마다 플레이스홀더가 다르므로 반드시 조사 후 진행)

# 4. ZIP-level 치환 적용
zip_replace(WORK, WORK, {
    "양식의 기존 텍스트": "실제 내용",
    # ...
})

# 동일 플레이스홀더가 여러 번 → 순차 치환
zip_replace_sequential(WORK, WORK, "반복되는 텍스트", ["값1", "값2", ...])

# 5. 네임스페이스 후처리
subprocess.run(
    ["python", "/mnt/skills/user/hwpx/scripts/fix_namespaces.py", WORK],
    check=True
)

# 6. 치환 결과 검증
finder = ObjectFinder(WORK)
for r in finder.find_all(tag="t"):
    if r.text and r.text.strip():
        print(r.text)

Style Guide by Document Type

When Writing a Report (for Internal Reporting)

→ references/report-style.md Read and follow this first

When Writing an Official Document (Draft)

→ references/official-doc-style.md Read and follow this first

When Low-Level XML Manipulation Is Needed

→ references/xml-internals.md Read this


⚠️ Required Post-Processing: Namespace Fix

The most important step. If omitted, it will display as a blank page in Hangul Viewer.

After ZIP-level replacement or doc.save() Always run after:

subprocess.run(
    ["python", "/mnt/skills/user/hwpx/scripts/fix_namespaces.py", "output.hwpx"],
    check=True
)

Caution: exec(open(...).read()) The method may malfunction due to the script's if __name__ == "__main__" block. Always use the subprocess.run() method.


Quick Reference

Task Approach
Report/Official Document/Template Document Generation Template File + ZIP-level Replacement (★ Recommended)
Very Simple Document HwpxDocument.new() → .save() → Post-Processing
Add Table doc.add_table(rows, cols) → set_cell_text()
Header/Footer doc.set_header_text() / doc.set_footer_text()
Text Search/Extraction ObjectFinder(filepath)
Cell Merge table.merge_cells(row1, col1, row2, col2)

Precautions

  1. Template First: User-Uploaded Template > Default Template > HwpxDocument.new()
  2. ZIP-level Replacement First: ZIP-level replacement is safer and more compatible than HwpxDocument.open()
  3. Namespace Post-Processing Required: After every save/replacement, fix_namespaces.py Run
  4. Template Text Inspection Required: Before replacement, always inspect all text with ObjectFinder
  5. Caution with Sequential Replacement: If the same placeholder appears multiple times, zip_replace_sequential use
  6. Layout fidelity: python-hwpx is not a layout engine. Page breaks are determined by the Hangul app
  7. Font embedding: Fonts are not included in generated HWPX. The viewing environment must have the relevant font
  8. Official document date format: 2026-02-13not 2026. 2. 13. (omit leading zeros for month and day)
  9. HWPX ↔ HWP: python-hwpx handles only HWPX. Legacy .hwprequires a separate tool
  10. How to call fix_namespaces: exec() instead of subprocess.run() use