SuperAPI AI Directory

hwpx

Office & productivity

View repository

Updated 10-15 22:36

About this skill

HWPX Document Skill — Reference Restoration-First (XML-first) Workflow

A skill for creating, editing, and reading Hangul (Hancom Office) HWPX files with a focus on direct XML authoring . HWPX is a ZIP-based XML container (OWPML standard). For edits where format preservation is important, it directly handles OWPML XML to finely control charPr, paraPr, and table structures.

Rules for Refusing HWP Binary Requests (Required)

This skill is HWPX-only. If the user requests the writing, creation, saving, editing, filling, or generation of conversion output for a .hwp binary file, that task is not performed.

  • .hwpRequests to produce a result file as are refused.
  • .hwp Requests to directly modify or fill in the original are refused.
  • .hwpReading and referencing is possible, but the output is produced as only if the user explicitly permits HWPX. .hwpx.
  • If the user requested the creation of .hwp but did not explicitly permit an HWPX alternative deliverable, do not arbitrarily create a .hwpx file and treat it as completed instead.
  • Keep the guidance text simple: "This skill only supports HWPX, so it cannot create .hwp. If you provide a file saved as HWPX in Hangul, or if you receive the result as .hwpx, it can be processed."

Default Operation Mode (Required): Analyze attached HWPX → Restore unique XML (99% close) → Rewrite reflecting the request

If the user attached .hwpx, this skill follows the order below as the default.

  1. Secure reference: Use the attached HWPX as the reference document
  2. Slot extraction: hwpx_slots.pycreates a JSON list of editable paragraph/cell slots
  3. Map user values: Map new values to slot keys (p:12, cell:0:2:1)
  4. Structure-preserving editing: edit_hwpx.py --slot-jsonclones the original package and modifies only the slot text
  5. Build/validate: edit_hwpx.py result or build_hwpx.py result as validate.pyto verify integrity
  6. Finalization/layout warnings: fix_namespaces.py, finalize_hwpx.py --strip-linesegarray --layout, validate.py --layoutchecks namespaces and rendering risks
  7. Character budget/page count guard (Required): page_guard.pychecks the character budget per paragraph/cell and the risk of page drift compared with the reference
  8. Content completeness guard (Required): content_guard.pychecks for leftover original text, placeholders, and missing required keywords

99% Close Restoration Criteria (Practical Checklist)

  • charPrIDRef, paraPrIDRef, borderFillIDRef reference system identical
  • Table rowCnt, colCnt, colSpan, rowSpan, cellSz, cellMargin identical
  • Paragraph order, paragraph count, and major blank line/section positions identical
  • Page/margins/section (secPr) identical
  • Changes are limited to the scope of the user's request (body text, values, item names, etc.)
  • If the content change request is a shift in the document's overall perspective (e.g., Korean institution → US institution, specific company → another company), it fails if remnants of the previous institution name/contact person/phone number/policy name remain

Same page count (100%) is a mandatory criterion

  • If the user provided a reference, the final page count of the resulting document must be the same as the reference
  • If the page count appears likely to increase, first compress/summarize the text to fit the existing layout
  • Without an explicit user request, hp:p, hp:tbl, rowCnt, colCnt, pageBreak, secPris not changed
  • validate.py Do not treat passing only as completion. It must also pass page_guard.py와 content_guard.pyas well
  • page_guard.py or content_guard.py If it fails, do not submit the result as complete; fix the cause (excessive length/structural change/source remnants/missing required keywords) and rebuild
  • If possible, check the final page count value based on Hangul (or the user's confirmation) and re-verify whether it matches the reference

Default execution command (when an attached reference exists)

source "$VENV"

# 1) 레퍼런스 분석 + XML 추출
python3 "$SKILL_DIR/scripts/analyze_template.py" reference.hwpx \
  --extract-header /tmp/ref_header.xml \
  --extract-section /tmp/ref_section.xml

# 2) 편집 가능한 슬롯 추출
python3 "$SKILL_DIR/scripts/hwpx_slots.py" reference.hwpx \
  --output /tmp/reference.slots.json

# 3) 원본 양식의 문단/셀별 글자 예산 저장
python3 "$SKILL_DIR/scripts/page_guard.py" \
  --reference reference.hwpx \
  --write-budget /tmp/reference.budget.json \
  --write-structure /tmp/reference.structure.json

# 3-1) 내용 완성도 규칙 작성
# 예: 한국 고용노동부 보도자료를 미국노동부 문서로 바꾸는 경우
cat > /tmp/content.rules.json <<'JSON'
{
  "require": ["미국노동부"],
  "forbid": ["고용노동부", "김영훈", "044-"],
  "forbid_regex": ["○○+", "\\.\\s*\\.\\s*\\."]
}
JSON

# 4) 양식 보존 편집
python3 "$SKILL_DIR/scripts/edit_hwpx.py" reference.hwpx \
  --output result.hwpx \
  --slot-json values.json

# 5) 검증 + 최종화
python3 "$SKILL_DIR/scripts/validate.py" result.hwpx
python3 "$SKILL_DIR/scripts/fix_namespaces.py" result.hwpx
python3 "$SKILL_DIR/scripts/finalize_hwpx.py" result.hwpx --strip-linesegarray --layout
python3 "$SKILL_DIR/scripts/validate.py" result.hwpx --layout

# 6) 글자 예산 + 쪽수 드리프트 가드 (필수)
python3 "$SKILL_DIR/scripts/page_guard.py" \
  --reference reference.hwpx \
  --output result.hwpx \
  --budget-profile /tmp/reference.budget.json \
  --structure-profile /tmp/reference.structure.json \
  --no-strict-paragraph-budget \
  --skip-text-drift \
  --allow-empty-fill

# 7) 내용 완성도 가드 (필수)
python3 "$SKILL_DIR/scripts/content_guard.py" result.hwpx \
  --rules /tmp/content.rules.json

# 문서 전체 관점 전환/전면 재작성인 경우 원본 긴 문장 잔존율도 제한
python3 "$SKILL_DIR/scripts/content_guard.py" result.hwpx \
  --reference reference.hwpx \
  --rules /tmp/content.rules.json \
  --max-unchanged-ratio 0.35

# 공문서라면 날짜/시간/금액/붙임 표기도 점검
python3 "$SKILL_DIR/scripts/gonmun_lint.py" --hwpx result.hwpx --format text

hwpx_slots.pyexposes actually editable paragraphs and table cells as slots, excluding container paragraphs that hold tables/images/text boxes. The user only needs to put values into the slot keys.

edit_hwpx.py는 mimetype, content.hpf, header.xml, settings.xml, Preview/*, BinData/*is copied verbatim from the original. The modification target is basically only Contents/section0.xml the slot text inside. Therefore, if the user provided an existing form and only text/cell filling is needed, build_hwpx.pythan edit_hwpx.py --slot-jsonis preferred.

Packaging must not use ordinary ZIP recompression. Copy the original ZIP's local headers and compressed data as-is, and replace only the target entries. For unmodified entries, the CRC, compressed size, file size, flag bits, compression method, date, creating system, and external/internal attributes must be the same as the original.

edit_hwpx.py Usage rules

# 전체 텍스트 치환
python3 "$SKILL_DIR/scripts/edit_hwpx.py" form.hwpx \
  -o out.hwpx \
  --replace "기존문구=새문구"

# JSON 매핑 치환
python3 "$SKILL_DIR/scripts/edit_hwpx.py" form.hwpx \
  -o out.hwpx \
  --replace-json values.json

# 표 셀 채우기: row,col은 0부터 시작, table 생략 시 첫 번째 표
python3 "$SKILL_DIR/scripts/edit_hwpx.py" form.hwpx \
  -o out.hwpx \
  --cell "0,2,1=테스트 법인"

# 본문 문단 재작성: 문장 품질을 우선하고 원본 문단 예산 이하로만 제한
python3 "$SKILL_DIR/scripts/edit_hwpx.py" form.hwpx \
  -o out.hwpx \
  --paragraph-json paragraphs.json

# 권장: 슬롯 키 기반 채우기
python3 "$SKILL_DIR/scripts/hwpx_slots.py" form.hwpx -o slots.json
python3 "$SKILL_DIR/scripts/edit_hwpx.py" form.hwpx \
  -o out.hwpx \
  --slot-json values.json
  • --replaceis first replaced within individual hp:t to preserve run formatting as much as possible.
  • If the replacement target is split across multiple runs, merge at the paragraph level, but do not unconditionally use the first run. header.xml의 charPrCheck and select the main run that has less emphasis such as bold/color/underline and whose character height is closest to the most-used non-emphasized text height within that paragraph.
  • --cellpreserves the formatting of the first run within the corresponding hp:tc and inserts only the text.
  • --paragraph/--paragraph-jsonis for rewriting practical body text. Rather than mechanically splitting existing sentences across multiple runs, insert natural prose into one main text run of the paragraph. The main run is chosen not just by character count but by charPrIDRef's bold/color/underline and the dominant body height within the paragraph.
  • hh:strikeout shape="3D" Values such as these are attached to ordinary body charPr in some HWPX files. If actual strikethrough rendering is not clear, do not use it alone as a criterion for emphasis/exclusion.
  • --slot/--slot-jsonis the default interface. Slot keys use only those generated by hwpx_slots.py.
  • --paragraph The target is directly hp:run/hp:tIt must be an actual text paragraph with . Container paragraphs that contain tables/images/text boxes are not modified directly. Modifying a container causes the internal text and the new text to be rendered overlapping.
  • Like an empty input cell, hp:runexists but hp:tis absent, add only hp:tinside the existing run.
  • hp:t inside hp:fwSpace, hp:lineBreak, and other child controls are never deleted. Even when clearing the text, keep the child tags and clear only text/tail.
  • Of a paragraph whose text was changed, hp:linesegarrayis removed. This tag is a paragraph line layout cache, so if it is preserved after modifying the body, Hancom may issue a corruption/tampering warning.
  • hp:tbl, hp:tr, hp:tc, cellAddr, cellSpan, cellSz, cellMargin, borderFillIDRefis not changed.

Practical input quality rules

For body paragraphs, spelling and spacing take priority. To preserve the form, 정부는현장점검과공개보고...do not remove spaces or cut sentences in the middle as in .

  • Body rewriting uses --paragraph-json.
  • When choosing a paragraph index, analyze_template.py or from the text extraction result, choose the internal actual text paragraph, not the container paragraph. For example, modify the institution name/title paragraph inside a table cell, not the entire top table paragraph.
  • The body is rewritten shorter than or equal to the original paragraph's non-space character count. Do not force-pad it to exactly the same character count as the original.
  • Strictly apply the exact length/cell budget only to fields such as names, dates, phone numbers, job titles, amounts, and short table cells.
  • Excessively long Korean strings without spaces edit_hwpx.pyfail in the default quality guard of . If it is not a normal sentence, do not submit the result.
  • If the original run's bold/emphasis gets buried in the middle of a new body sentence, it is a failure. The body is put into a single main run of the --paragraph family, and only intended emphasis such as titles/subheadings retains the original style.
  • If the font size looks different from the original, header.xmlfirst compare whether changed, or whether the new text went into a different charPrIDRef. header.xmlEven if is the same, choosing a 12pt parenthesis/footnote run instead of 14pt body text changes the displayed typeface.

Character budget guard rules

page_guard.py --write-budgetcalculates the following budget from the original form.

  • Normal paragraph: directly hp:run/hp:t the non-space character count of the text
  • Table cell: the larger of the non-space character count of the entire hp:t text inside the cell and the estimated capacity based on cell width/font size
  • Empty input cell: using the existing cell width and the first run's charPrIDRef font size, calculate a conservative capacity

When validating the result, --budget-profilemust be used. For practical body rewriting results, --no-strict-paragraph-budget --skip-text-driftis also used to check not for an exact match of the original character count but for being within budget and preserving structure. Table cells and short input fields fail if they exceed the budget. Paragraphs inside a cell are checked against the cell budget, so empty-cell input is possible.

Full structure fingerprint guard rules

page_guard.py --write-structurestores the following in the original form.

  • ZIP package file list and order
  • compression method, date, creating system, version, flag bits, external/internal attributes of each entry
  • SHA-256 hash of binary/attached files
  • tag names, attributes, hierarchy, child order of all XML files
  • hp:tdirect text of and hp:t tail text of child controls is treated as input and excluded
  • hp:t child control tags/order such as inside hp:fwSpace, hp:lineBreak are included in the structure fingerprint
  • hp:linesegarrayis treated as a line placement cache and excluded from the structure fingerprint

When verifying results, --structure-profilemust be used. hp:t text input and hp:linesegarray If the tag/attribute/file/image/meta structure changes other than cache removal, it fails. In particular, hp:t If an internal control is deleted, the possibility of corruption is high, so it must fail.

ZIP entry's flag_bitsmust also be the same as the original. Python zipfile.writestr()If you rewrite the entire entry with flag_bitsand the compressed bytes may change, so do not use it. Always replace only the target entry using the raw ZIP copy method.

edit_hwpx.pybasically checks the pre-input character count budget and basic sentence quality. --paragraph value must be less than or equal to the original paragraph budget, and --cell value must not exceed the original cell budget. --replaceIf the new value is longer than the existing slot, it fails before writing. --allow-over-budgetis used only when the user explicitly takes the risk.

Content completeness guard rules

content_guard.pychecks whether the result text matches the work intent regardless of whether the document opens. In the following cases, a rule file must be created and executed.

  • When changing the document's institution/country/company/brand: put the previous name, previous person in charge, previous phone number, and previous policy name into forbid.
  • When filling form placeholders: ○○, {{name}}, ... . . prohibit leftover placeholders such as .
  • When key terms from the new document perspective must be present: put the new institution name, new corporate name, new department in charge, etc. into require.
  • In documents with multiple attachments/references/person-in-charge tables, do not change only the front part; run content_guard.pyon the entire text extraction result.
  • For full rewrite requests such as "change the content completely" or "change it so it looks like it was written in the US," use --reference와 --max-unchanged-ratiotogether. If many long sentences from the original remain, it fails even if the structure is normal.

Example:

{
  "require": ["미국노동부", "좋은 일자리"],
  "forbid": ["고용노동부", "김영훈", "최영범", "장지훈", "044-"],
  "forbid_regex": ["○○+", "\\(\\s*\\.\\s*\\.\\s*\\.\\s*\\)"]
}

content_guard.pyIf fails, do not submit the result as complete no matter how normal the structure is. Review the failure list and further modify the remaining slots, or expand the scope of the full document rewrite and build again.

Environment

# SKILL_DIR는 이 SKILL.md가 위치한 디렉토리의 절대 경로로 설정
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"   # 스크립트 내에서
# 또는 Claude Code가 자동으로 주입하는 base directory 경로를 사용

# Python 가상환경 (프로젝트에 맞게 설정)
VENV="<프로젝트>/.venv/bin/activate"

For every Python execution:

# 프로젝트의 .venv를 활성화 (pip install lxml 필요)
source "$VENV"

Directory structure

.claude/skills/hwpx/
├── SKILL.md                              # 이 파일
├── scripts/
│   ├── office/
│   │   ├── unpack.py                     # HWPX → 디렉토리 (기본 XML 바이트 보존, --pretty는 검사 전용)
│   │   └── pack.py                       # 디렉토리 → HWPX
│   ├── build_hwpx.py                     # 템플릿 + XML → .hwpx 조립 (핵심)
│   ├── edit_hwpx.py                      # 원본 패키지 보존 + 텍스트/셀 최소 수정
│   ├── analyze_template.py               # HWPX 심층 분석 (레퍼런스 기반 생성용)
│   ├── validate.py                       # HWPX 구조 검증
│   ├── fix_namespaces.py                 # 표준 네임스페이스 프리픽스/header itemCnt 보정
│   ├── finalize_hwpx.py                  # 줄 배치 캐시 제거 + 레이아웃 위험 경고
│   ├── page_guard.py                     # 레퍼런스 대비 페이지 드리프트 위험 검사
│   ├── content_guard.py                  # 원문 잔재/placeholder/필수 키워드 검사
│   ├── gonmun_lint.py                    # 공문서 날짜/시간/금액/붙임 표기 검수
│   └── text_extract.py                   # 텍스트 추출
├── templates/
│   ├── base/                             # 베이스 템플릿 (Skeleton 기반)
│   │   ├── mimetype, META-INF/*, version.xml, settings.xml, Preview/*
│   │   └── Contents/ (header.xml, section0.xml, content.hpf)
│   ├── gonmun/                           # 공문 오버레이 (header.xml, section0.xml)
│   ├── report/                           # 보고서 오버레이
│   ├── minutes/                          # 회의록 오버레이
│   └── proposal/                         # 제안서/사업개요 오버레이 (색상 헤더바, 번호 배지)
└── references/
    └── hwpx-format.md                    # OWPML XML 요소 레퍼런스

Workflow 1: XML-first document generation (secondary workflow, only when there is no reference file)

Flow

  1. Select template (base/gonmun/report/minutes/proposal)
  2. Write section0.xml (body content)
  3. (Optional) Modify header.xml (when new styles need to be added)
  4. Build with build_hwpx.py
  5. Validate with validate.py

Principle: If the user provides a reference HWPX, use the "Default behavior mode (reference restoration first)" above instead of this workflow.

Basic usage

source "$VENV"

# 빈 문서 (base 템플릿)
python3 "$SKILL_DIR/scripts/build_hwpx.py" --output result.hwpx

# 템플릿 사용
python3 "$SKILL_DIR/scripts/build_hwpx.py" --template gonmun --output result.hwpx

# 커스텀 section0.xml 오버라이드
python3 "$SKILL_DIR/scripts/build_hwpx.py" --template gonmun --section my_section0.xml --output result.hwpx

# header도 오버라이드
python3 "$SKILL_DIR/scripts/build_hwpx.py" --header my_header.xml --section my_section0.xml --output result.hwpx

# 메타데이터 설정
python3 "$SKILL_DIR/scripts/build_hwpx.py" --template report --section my.xml \
  --title "제목" --creator "작성자" --output result.hwpx

Practical pattern: Write section0.xml inline → build

# 1. section0.xml을 임시파일로 작성
SECTION=$(mktemp /tmp/section0_XXXX.xml)
cat > "$SECTION" << 'XMLEOF'
<?xml version='1.0' encoding='UTF-8'?>
<hs:sec xmlns:hp="http://www.hancom.co.kr/hwpml/2011/paragraph"
        xmlns:hs="http://www.hancom.co.kr/hwpml/2011/section">
  <!-- secPr 포함 첫 문단 (base/section0.xml에서 복사) -->
  <!-- ... -->
  <hp:p id="1000000002" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">
    <hp:run charPrIDRef="0">
      <hp:t>본문 내용</hp:t>
    </hp:run>
  </hp:p>
</hs:sec>
XMLEOF

# 2. 빌드
python3 "$SKILL_DIR/scripts/build_hwpx.py" --section "$SECTION" --output result.hwpx

# 3. 정리
rm -f "$SECTION"

section0.xml writing guide

Required structure

In the first paragraph of section0.xml (<hp:p>)의 첫 런(<hp:run>) must <hp:secPr>과 <hp:colPr> include:

<hp:p id="1000000001" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">
  <hp:run charPrIDRef="0">
    <hp:secPr ...>
      <!-- 페이지 크기, 여백, 각주/미주 설정 등 -->
    </hp:secPr>
    <hp:ctrl>
      <hp:colPr id="" type="NEWSPAPER" layout="LEFT" colCount="1" sameSz="1" sameGap="0"/>
    </hp:ctrl>
  </hp:run>
  <hp:run charPrIDRef="0"><hp:t/></hp:run>
</hp:p>

Tip: templates/base/Contents/section0.xml You can simply copy the first paragraph of .

Paragraph

<hp:p id="고유ID" paraPrIDRef="문단스타일ID" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">
  <hp:run charPrIDRef="글자스타일ID">
    <hp:t>텍스트 내용</hp:t>
  </hp:run>
</hp:p>

빈 줄

<hp:p id="고유ID" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">
  <hp:run charPrIDRef="0"><hp:t/></hp:run>
</hp:p>

Mixed-format runs (multiple styles in one paragraph)

<hp:p id="고유ID" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">
  <hp:run charPrIDRef="0"><hp:t>일반 텍스트 </hp:t></hp:run>
  <hp:run charPrIDRef="7"><hp:t>볼드 텍스트</hp:t></hp:run>
  <hp:run charPrIDRef="0"><hp:t> 다시 일반</hp:t></hp:run>
</hp:p>

How to create tables

<hp:p id="고유ID" paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">
  <hp:run charPrIDRef="0">
    <hp:tbl id="고유ID" zOrder="0" numberingType="TABLE" textWrap="TOP_AND_BOTTOM"
            textFlow="BOTH_SIDES" lock="0" dropcapstyle="None" pageBreak="CELL"
            repeatHeader="0" rowCnt="행수" colCnt="열수" cellSpacing="0"
            borderFillIDRef="3" noAdjust="0">
      <hp:sz width="42520" widthRelTo="ABSOLUTE" height="전체높이" heightRelTo="ABSOLUTE" protect="0"/>
      <hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="1" allowOverlap="0"
              holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="COLUMN" vertAlign="TOP"
              horzAlign="LEFT" vertOffset="0" horzOffset="0"/>
      <hp:outMargin left="0" right="0" top="0" bottom="0"/>
      <hp:inMargin left="0" right="0" top="0" bottom="0"/>
      <hp:tr>
        <hp:tc name="" header="0" hasMargin="0" protect="0" editable="0" dirty="1" borderFillIDRef="4">
          <hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="CENTER"
                     linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0"
                     hasTextRef="0" hasNumRef="0">
            <hp:p paraPrIDRef="21" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0" id="고유ID">
              <hp:run charPrIDRef="9"><hp:t>헤더 셀</hp:t></hp:run>
            </hp:p>
          </hp:subList>
          <hp:cellAddr colAddr="0" rowAddr="0"/>
          <hp:cellSpan colSpan="1" rowSpan="1"/>
          <hp:cellSz width="열너비" height="행높이"/>
          <hp:cellMargin left="0" right="0" top="0" bottom="0"/>
        </hp:tc>
        <!-- 나머지 셀... -->
      </hp:tr>
    </hp:tbl>
  </hp:run>
</hp:p>

Table size calculation

  • A4 body width: 42520 HWPUNIT = 59528 (paper) - 8504×2 (left/right margins)
  • Sum of column widths = body width (42520)
  • Example: 3 equal columns → 14173 + 14173 + 14174 = 42520
  • Example: 2 columns (label:content = 1:4) → 8504 + 34016 = 42520
  • Row height: usually 2400~3600 HWPUNIT per cell

ID rules

  • Paragraph id: 1000000001increasing sequentially from
  • 표 id: 1000000099 It is recommended to use separate ranges such as
  • All ids must be unique within the document

header.xml modification guide

How to add a custom style

  1. templates/base/Contents/header.xml copy
  2. Add the necessary charPr/paraPr/borderFill
  3. For each group, itemCnt update the attributes

Example of adding charPr (bold 14pt)

<hh:charPr id="8" height="1400" textColor="#000000" shadeColor="none"
           useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="2">
  <hh:fontRef hangul="1" latin="1" hanja="1" japanese="1" other="1" symbol="1" user="1"/>
  <hh:ratio hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
  <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
  <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
  <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
  <hh:bold/>
  <hh:underline type="NONE" shape="SOLID" color="#000000"/>
  <hh:strikeout shape="NONE" color="#000000"/>
  <hh:outline type="NONE"/>
  <hh:shadow type="NONE" color="#C0C0C0" offsetX="10" offsetY="10"/>
</hh:charPr>

Font reference system

  • fontRef The value is fontfacesthe font id defined in
  • hangul="0" → Hamchorom Dotum (Gothic)
  • hangul="1" → Hamchorom Batang (Myeongjo)
  • Set identically for all 7 languages

Cautions when adding paraPr

  • Must hp:switch include structure (hp:case + hp:default)
  • hp:case와 hp:defaultThe values of are usually the same (or default is 2x)
  • borderFillIDRef="2" maintain

Style ID map by template

base (default)

ID Type Description
charPr 0 Text 10pt Hamchorom Batang, default
charPr 1 Text 10pt Hamchorom Dotum
charPr 2~6 Text Skeleton default style
paraPr 0 Paragraph JUSTIFY, 160% line spacing
paraPr 1~19 Paragraph Skeleton default (outline, footnotes, etc.)
borderFill 1 Border None (page border)
borderFill 2 Border None + transparent background (for reference)

gonmun (official document) — base + additions

ID Type Description
charPr 7 Text 22pt bold Hamchorom Batang (organization name/title)
charPr 8 Text 16pt bold Hamchorom Batang (signer)
charPr 9 Text 8pt Hamchorom Batang (bottom contact info)
charPr 10 Text 10pt bold Hamchorom Batang (table header)
paraPr 20 Paragraph CENTER, 160% line spacing
paraPr 21 Paragraph CENTER, 130% (표 셀)
paraPr 22 Paragraph JUSTIFY, 130% (표 셀)
borderFill 3 Border SOLID 0.12mm 4면
borderFill 4 Border SOLID 0.12mm + #D6DCE4 background

report — base + additions

ID Type Description
charPr 7 Text 20pt bold (document title)
charPr 8 Text 14pt bold (subheading)
charPr 9 Text 10pt bold (table header)
charPr 10 Text 10pt bold+underline (emphasized text)
charPr 11 Text 9pt Hamchorom Batang (small/footnote)
charPr 12 Text 16pt bold Hamchorom Batang (one-line title)
charPr 13 Text 12pt bold Hamchorom Dotum (section header)
paraPr 20~22 Paragraph CENTER/JUSTIFY variants
paraPr 23 Paragraph RIGHT aligned, 160% line spacing
paraPr 24 Paragraph JUSTIFY, left 600 (□ checklist item indent)
paraPr 25 Paragraph JUSTIFY, left 1200 (sub-item ①②③ indent)
paraPr 26 Paragraph JUSTIFY, left 1800 (deep sub-item - indent)
paraPr 27 Paragraph LEFT, top/bottom border lines (for section header), prev 400
borderFill 3 Border SOLID 0.12mm 4면
borderFill 4 Border SOLID 0.12mm + #DAEEF3 background
borderFill 5 Border Top 0.4mm thick line + bottom 0.12mm thin line (section header)

Indentation rules: Must use paraPr's left margin, not space characters. □ items use paraPr 24, sub-items ①②③ use paraPr 25, deep - items use paraPr 26.

Section header rules: paraPr 27 + charPr 13 combination. Paragraph border (borderFillIDRef="5") automatically displays top thick line + bottom thin line.

minutes — base + additions

ID Type Description
charPr 7 Text 18pt bold (title)
charPr 8 Text 12pt bold (section label)
charPr 9 Text 10pt bold (table header)
paraPr 20~22 Paragraph CENTER/JUSTIFY variants
borderFill 3 Border SOLID 0.12mm 4면
borderFill 4 Border SOLID 0.12mm + #E2EFDA background

proposal — base + additions

For official documents requiring visual distinction. Color background header bars and number badges implemented with table-based layout.

ID Type Description
charPr 7 Text 20pt bold Hamchorom Batang (document title)
charPr 8 Text 14pt bold Hamchorom Batang (subheading)
charPr 9 Text 10pt bold Hamchorom Batang (table header)
charPr 10 Text 14pt bold white Hamchorom Dotum (major item number, green background)
charPr 11 Text 11pt bold white Hamchorom Dotum (minor item number, blue background)
paraPr 20 Paragraph CENTER, 160% line spacing
paraPr 21 Paragraph CENTER, 130% (표 셀)
paraPr 22 Paragraph JUSTIFY, 130% (표 셀)
borderFill 3 Border SOLID 0.12mm 4면
borderFill 4 Border SOLID 0.12mm + #DAEEF3 background
borderFill 5 Border Olive green background #7B8B3D (major item number cell)
borderFill 6 Border Light gray background #F2F2F2 + gray border (major item title cell)
borderFill 7 Border Blue background #4472C4 (minor item number badge)
borderFill 8 Border Bottom border only #D0D0D0 (minor item title area)

proposal layout pattern

Major item header (2-cell table: number + title):

<!-- borderFillIDRef="5" + charPrIDRef="10" → 녹색배경 흰색 로마숫자 -->
<!-- borderFillIDRef="6" + charPrIDRef="8"  → 회색배경 검정 볼드 제목 -->

Minor item header (2-cell table: number badge + title):

<!-- borderFillIDRef="7" + charPrIDRef="11" → 파란배경 흰색 아라비아숫자 -->
<!-- borderFillIDRef="8" + charPrIDRef="8"  → 하단선만 검정 볼드 제목 -->

Workflow 2: Editing an existing document (unpack → Edit → pack)

source "$VENV"

# 1. HWPX → 디렉토리 (기본값은 XML 바이트 보존)
python3 "$SKILL_DIR/scripts/office/unpack.py" document.hwpx ./unpacked/

# 2. XML 직접 편집 (Claude가 Read/Edit 도구로)
#    본문: ./unpacked/Contents/section0.xml
#    스타일: ./unpacked/Contents/header.xml

# 3. 다시 HWPX로 패키징
python3 "$SKILL_DIR/scripts/office/pack.py" ./unpacked/ edited.hwpx

# 4. 검증
python3 "$SKILL_DIR/scripts/validate.py" edited.hwpx

unpack.py --prettyis used only when a person wants to inspect the XML in a readable way. HWPX's hp:tmay have mixed content in which text and child controls such as hp:fwSpace, hp:lineBreak are mixed, and line breaks/spaces inserted by pretty-print may be rendered as actual text by Hancom. Therefore, --prettydo not use the directory unpacked with pack.py as input again.


Workflow 3: Reading/text extraction

source "$VENV"

# 순수 텍스트
python3 "$SKILL_DIR/scripts/text_extract.py" document.hwpx

# 테이블 포함
python3 "$SKILL_DIR/scripts/text_extract.py" document.hwpx --include-tables

# 마크다운 형식
python3 "$SKILL_DIR/scripts/text_extract.py" document.hwpx --format markdown

Python API

from hwpx import TextExtractor
with TextExtractor("document.hwpx") as ext:
    text = ext.extract_text(include_nested=True, object_behavior="nested")
    print(text)

Workflow 4: Validation

source "$VENV"
python3 "$SKILL_DIR/scripts/validate.py" document.hwpx

Validation items: ZIP validity, required files existence, mimetype content/location/compression method, XML well-formedness


Workflow 5: Reference-based document generation (applied by default when an attached HWPX exists)

A workflow that analyzes a user-provided HWPX file and generates a document with the same layout. In this skill, if an attached reference exists, this workflow is used by default.

Flow

  1. Analysis — analyze_template.pyin-depth analysis of the reference document with
  2. Extract header.xml — use the reference's style definitions as they are
  3. Write section0.xml — write new content following the structure of the analysis result
  4. Build — build with the extracted header.xml + new section0.xml
  5. Validation — validate.py
  6. Page count guard — page_guard.py (revise again if it fails)

Usage

source "$VENV"

# 1. 심층 분석 (구조 청사진 출력)
python3 "$SKILL_DIR/scripts/analyze_template.py" reference.hwpx

# 2. header.xml과 section0.xml을 추출하여 참고용으로 보관
python3 "$SKILL_DIR/scripts/analyze_template.py" reference.hwpx \
  --extract-header /tmp/ref_header.xml \
  --extract-section /tmp/ref_section.xml

# 3. 분석 결과를 보고 새 section0.xml 작성
#    - 동일한 charPrIDRef, paraPrIDRef 사용
#    - 동일한 테이블 구조 (열 수, 열 너비, 행 수, rowSpan/colSpan)
#    - 동일한 borderFillIDRef, cellMargin

# 4. 추출한 header.xml + 새 section0.xml로 빌드
python3 "$SKILL_DIR/scripts/build_hwpx.py" \
  --header /tmp/ref_header.xml \
  --section /tmp/new_section0.xml \
  --output result.hwpx

# 5. 검증
python3 "$SKILL_DIR/scripts/validate.py" result.hwpx

# 6. 쪽수 드리프트 가드 (필수)
python3 "$SKILL_DIR/scripts/page_guard.py" \
  --reference reference.hwpx \
  --output result.hwpx

Analysis output items

Item Description
Font definition hangul/latin font mapping
borderFill Border type/thickness + background color (details for each side)
charPr Font size (pt), font name, color, bold/italic/underline/strikethrough, fontRef
paraPr Alignment, line spacing, margins (left/right/prev/next/intent), heading, borderFillIDRef
Document structure Page size, margins, page borders, body width
Body details id/paraPr/charPr of all paragraphs + text content
Table details rows×columns, column width array, per-cell span/margin/borderFill/vertAlign + content

Core principles

  • Use charPrIDRef/paraPrIDRef as is: Do not change the style IDs of the extracted header.xml
  • Sum of column widths = body width: Duplicate the column width array from the analysis results as is
  • Maintain rowSpan/colSpan patterns: Accurately reproduce the analyzed cell merge structure
  • Preserve cellMargin: Apply the analyzed cell margin values identically
  • Do not increase pages: Do not increase the resulting page count without explicit user approval
  • Replacement-first editing: Prioritize replacing existing text nodes over adding new paragraphs/tables

Script summary

Script Purpose
scripts/build_hwpx.py Key — Template + XML → HWPX assembly
scripts/analyze_template.py HWPX in-depth analysis (blueprint for reference-based generation)
scripts/office/unpack.py HWPX → directory (preserve raw XML bytes, --prettyis for inspection only)
scripts/office/pack.py Directory → HWPX (mimetype first)
scripts/validate.py Validate HWPX file structure
scripts/fix_namespaces.py Standard namespace prefixes and header itemCnt correction
scripts/finalize_hwpx.py Remove line layout cache, layout risk warning, Windows Hancom open check
scripts/page_guard.py Check page drift risk against reference (required gate)
scripts/content_guard.py Check for original text remnants, placeholders, and missing required keywords
scripts/gonmun_lint.py Review official document date/time/amount/attachment notation
scripts/text_extract.py HWPX text extraction

Unit conversion

값 HWPUNIT Meaning
1pt 100 Base unit
10pt 1000 Base font size
1mm 283.5 Millimeters
1cm 2835 Centimeters
A4 폭 59528 210mm
A4 height 84186 297mm
Left/right margins 8504 30mm
Body width 42520 150mm (A4 - left/right margins)

Critical Rules

  1. HWPX only supported / HWP creation refused: .hwp(binary) file creation, generation, saving, direct editing, and filling requests are refused. Only when the user explicitly permits a HWPX alternative output, work with .hwpxWhen the user provides a .hwp file, In Hancom Office, .hwpxsave again asguide them to do so. (File → Save As → File type: HWPX)
  2. secPr required: The first run of the first paragraph in section0.xml must include secPr + colPr
  3. mimetype order: When packaging HWPX, mimetype is the first ZIP entry, ZIP_STORED
  4. Preserve namespaces: When editing XML, hp:, hs:, hh:, hc: keep the prefix
  5. itemCnt consistency: charProperties/paraProperties/borderFills itemCnt in header.xml matches the actual number of children
  6. ID reference consistency: charPrIDRef/paraPrIDRef in section0.xml matches the header.xml definition
  7. use venv: of the project .venv/bin/python3 (lxml package required)
  8. Validation: after generation, always validate.pyto verify integrity
  9. Reference: for detailed XML structure, see $SKILL_DIR/references/hwpx-format.md refer to
  10. build_hwpx.py preferred: for creating new documents, use build_hwpx.py (avoid calling the python-hwpx API directly)
  11. 빈 줄: <hp:t/> use (self-closing tag)
  12. reference-first enforcement: when the user attaches an HWPX, always analyze_template.py + restore/rewrite based on the extracted XML
  13. do not use the examples folder: during work, .claude/skills/hwpx/examples/* files must not be used for reading/reference/copying
  14. page count must be identical: in reference-based work, keep the final result's page count identical to the reference
  15. no unauthorized page increase: do not make structural changes that cause page count increases without the user's explicit request/approval
  16. restrict structural changes: unless requested by the user, do not add/delete/split/merge paragraphs or tables (replacement-focused editing)
  17. page_guard must pass: validate.pyand separately from page_guard.pymust be passed to mark as complete