| name | hwpx |
| description | 한글(HWPX) 문서 생성/읽기/편집 스킬. .hwpx 파일, 한글 문서, Hancom, OWPML 관련 요청 시 사용. |
원본: Canine89/hwpxskill
커스텀 (본 레포): HWP 바이너리 파일 자동 변환 워크플로우 추가
HWPX 문서 스킬 — XML-first 워크플로우
한글(Hancom Office)의 HWPX 파일을 XML 직접 작성 중심으로 생성, 편집, 읽기할 수 있는 스킬.
HWPX는 ZIP 기반 XML 컨테이너(OWPML 표준)이다. python-hwpx API의 서식 버그를 완전히 우회하며, 세밀한 서식 제어가 가능하다.
환경
# SKILL_DIR는 이 SKILL.md가 위치한 디렉토리의 절대 경로로 설정
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)" # 스크립트 내에서
# 또는 Claude Code가 자동으로 주입하는 base directory 경로를 사용
# Python 가상환경 (프로젝트에 맞게 설정)
VENV="<프로젝트>/.venv/bin/activate"
모든 Python 실행 시:
source "$VENV"
디렉토리 구조
.claude/skills/hwpx/
├── SKILL.md # 이 파일
├── scripts/
│ ├── office/
│ │ ├── unpack.py # HWPX → 디렉토리 (XML pretty-print)
│ │ └── pack.py # 디렉토리 → HWPX
│ ├── build_hwpx.py # 템플릿 + XML → .hwpx 조립 (핵심)
│ ├── analyze_template.py # HWPX 심층 분석 (레퍼런스 기반 생성용)
│ ├── validate.py # HWPX 구조 검증
│ └── 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/ # 제안서/사업개요 오버레이 (색상 헤더바, 번호 배지)
├── examples/
│ ├── 01_basic_document.sh # XML로 기본 문서 빌드
│ ├── 02_gonmun_example.sh # 공문 템플릿 사용
│ ├── 03_report_with_table.sh # 표 포함 보고서
│ ├── 04_read_and_extract.py # 기존 문서 읽기/추출
│ ├── 05_edit_existing.sh # unpack→편집→pack
│ ├── sample_section0.xml # 주석 달린 section0 예제
│ └── sample_header.xml # 주석 달린 header 예제
└── references/
└── hwpx-format.md # OWPML XML 요소 레퍼런스
워크플로우 1: XML-first 문서 생성 (주 워크플로우)
흐름
- 템플릿 선택 (base/gonmun/report/minutes/proposal)
- section0.xml 작성 (본문 내용)
- (선택) header.xml 수정 (새 스타일 추가 필요 시)
- build_hwpx.py로 빌드
- validate.py로 검증
기본 사용법
source "$VENV"
python3 "$SKILL_DIR/scripts/build_hwpx.py" --output result.hwpx
python3 "$SKILL_DIR/scripts/build_hwpx.py" --template gonmun --output result.hwpx
python3 "$SKILL_DIR/scripts/build_hwpx.py" --template gonmun --section my_section0.xml --output result.hwpx
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
실전 패턴: 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
python3 "$SKILL_DIR/scripts/build_hwpx.py" --section "$SECTION" --output result.hwpx
rm -f "$SECTION"
XML Guardrails
레퍼런스 기반 재기안에서 XML을 직접 치환할 때는 아래 규칙을 기본값으로 둔다.
hp:tbl 또는 중첩 hp:p를 품은 컨테이너 문단은 일반 문단처럼 치환하지 않는다.
-, *, **, ***, ○, □, 번호형 prefix로 시작하는 문단은 원래 prefix run을 보존한 채 본문만 바꾼다.
- 치환이 끝나면
hp:linesegarray를 제거해 한글이 줄바꿈과 자간을 다시 계산하도록 한다.
- 표지, 목차, 중첩 표 블록은 형식 특화 구역으로 보고 바깥 wrapper 문단을 건드리지 않는다.
공통 처리 로직은 scripts/rekian_xml.py를 사용한다.
section0.xml 작성 가이드
필수 구조
section0.xml의 첫 문단(<hp:p>)의 첫 런(<hp:run>)에 반드시 <hp:secPr>과 <hp:colPr> 포함:
<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 의 첫 문단을 그대로 복사하면 된다.
문단
<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>
서식 혼합 런 (한 문단에 여러 스타일)
<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>
표 작성법
<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>
표 크기 계산
- A4 본문폭: 42520 HWPUNIT = 59528(용지) - 8504×2(좌우여백)
- 열 너비 합 = 본문폭 (42520)
- 예: 3열 균등 → 14173 + 14173 + 14174 = 42520
- 예: 2열 (라벨:내용 = 1:4) → 8504 + 34016 = 42520
- 행 높이: 셀당 보통 2400~3600 HWPUNIT
ID 규칙
- 문단 id:
1000000001부터 순차 증가
- 표 id:
1000000099 등 별도 범위 사용 권장
- 모든 id는 문서 내 고유해야 함
header.xml 수정 가이드
커스텀 스타일 추가 방법
templates/base/Contents/header.xml 복사
- 필요한 charPr/paraPr/borderFill 추가
- 각 그룹의
itemCnt 속성 업데이트
charPr 추가 예시 (볼드 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>
폰트 참조 체계
fontRef 값은 fontfaces에 정의된 font id
hangul="0" → 함초롬돋움 (고딕)
hangul="1" → 함초롬바탕 (명조)
- 7개 언어 모두 동일하게 설정
paraPr 추가 시 주의
- 반드시
hp:switch 구조 포함 (hp:case + hp:default)
hp:case와 hp:default의 값은 보통 동일 (또는 default가 2배)
borderFillIDRef="2" 유지
템플릿별 스타일 ID 맵
base (기본)
| ID | 유형 | 설명 |
|---|
| charPr 0 | 글자 | 10pt 함초롬바탕, 기본 |
| charPr 1 | 글자 | 10pt 함초롬돋움 |
| charPr 2~6 | 글자 | Skeleton 기본 스타일 |
| paraPr 0 | 문단 | JUSTIFY, 160% 줄간격 |
| paraPr 1~19 | 문단 | Skeleton 기본 (개요, 각주 등) |
| borderFill 1 | 테두리 | 없음 (페이지 보더) |
| borderFill 2 | 테두리 | 없음 + 투명배경 (참조용) |
gonmun (공문) — base + 추가
| ID | 유형 | 설명 |
|---|
| charPr 7 | 글자 | 22pt 볼드 함초롬바탕 (기관명/제목) |
| charPr 8 | 글자 | 16pt 볼드 함초롬바탕 (서명자) |
| charPr 9 | 글자 | 8pt 함초롬바탕 (하단 연락처) |
| charPr 10 | 글자 | 10pt 볼드 함초롬바탕 (표 헤더) |
| paraPr 20 | 문단 | CENTER, 160% 줄간격 |
| paraPr 21 | 문단 | CENTER, 130% (표 셀) |
| paraPr 22 | 문단 | JUSTIFY, 130% (표 셀) |
| borderFill 3 | 테두리 | SOLID 0.12mm 4면 |
| borderFill 4 | 테두리 | SOLID 0.12mm + #D6DCE4 배경 |
report (보고서) — base + 추가
| ID | 유형 | 설명 |
|---|
| charPr 7 | 글자 | 20pt 볼드 (문서 제목) |
| charPr 8 | 글자 | 14pt 볼드 (소제목) |
| charPr 9 | 글자 | 10pt 볼드 (표 헤더) |
| charPr 10 | 글자 | 10pt 볼드+밑줄 (강조 텍스트) |
| charPr 11 | 글자 | 9pt 함초롬바탕 (소형/각주) |
| charPr 12 | 글자 | 16pt 볼드 함초롬바탕 (1줄 제목) |
| charPr 13 | 글자 | 12pt 볼드 함초롬돋움 (섹션 헤더) |
| paraPr 20~22 | 문단 | CENTER/JUSTIFY 변형 |
| paraPr 23 | 문단 | RIGHT 정렬, 160% 줄간격 |
| paraPr 24 | 문단 | JUSTIFY, left 600 (□ 체크항목 들여쓰기) |
| paraPr 25 | 문단 | JUSTIFY, left 1200 (하위항목 ①②③ 들여쓰기) |
| paraPr 26 | 문단 | JUSTIFY, left 1800 (깊은 하위항목 - 들여쓰기) |
| paraPr 27 | 문단 | LEFT, 상하단 테두리선 (섹션 헤더용), prev 400 |
| borderFill 3 | 테두리 | SOLID 0.12mm 4면 |
| borderFill 4 | 테두리 | SOLID 0.12mm + #DAEEF3 배경 |
| borderFill 5 | 테두리 | 상단 0.4mm 굵은선 + 하단 0.12mm 얇은선 (섹션 헤더) |
들여쓰기 규칙: 공백 문자가 아닌 반드시 paraPr의 left margin 사용. □ 항목은 paraPr 24, 하위 ①②③ 는 paraPr 25, 깊은 - 항목은 paraPr 26.
섹션 헤더 규칙: paraPr 27 + charPr 13 조합. 문단 테두리(borderFillIDRef="5")로 상단 굵은선 + 하단 얇은선 자동 표시.
minutes (회의록) — base + 추가
| ID | 유형 | 설명 |
|---|
| charPr 7 | 글자 | 18pt 볼드 (제목) |
| charPr 8 | 글자 | 12pt 볼드 (섹션 라벨) |
| charPr 9 | 글자 | 10pt 볼드 (표 헤더) |
| paraPr 20~22 | 문단 | CENTER/JUSTIFY 변형 |
| borderFill 3 | 테두리 | SOLID 0.12mm 4면 |
| borderFill 4 | 테두리 | SOLID 0.12mm + #E2EFDA 배경 |
proposal (제안서/사업개요) — base + 추가
시각적 구분이 필요한 공식 문서용. 색상 배경 헤더바와 번호 배지를 표(table) 기반 레이아웃으로 구현.
| ID | 유형 | 설명 |
|---|
| charPr 7 | 글자 | 20pt 볼드 함초롬바탕 (문서 제목) |
| charPr 8 | 글자 | 14pt 볼드 함초롬바탕 (소제목) |
| charPr 9 | 글자 | 10pt 볼드 함초롬바탕 (표 헤더) |
| charPr 10 | 글자 | 14pt 볼드 흰색 함초롬돋움 (대항목 번호, 녹색 배경) |
| charPr 11 | 글자 | 11pt 볼드 흰색 함초롬돋움 (소항목 번호, 파란 배경) |
| paraPr 20 | 문단 | CENTER, 160% 줄간격 |
| paraPr 21 | 문단 | CENTER, 130% (표 셀) |
| paraPr 22 | 문단 | JUSTIFY, 130% (표 셀) |
| borderFill 3 | 테두리 | SOLID 0.12mm 4면 |
| borderFill 4 | 테두리 | SOLID 0.12mm + #DAEEF3 배경 |
| borderFill 5 | 테두리 | 올리브녹색 배경 #7B8B3D (대항목 번호 셀) |
| borderFill 6 | 테두리 | 연한 회색 배경 #F2F2F2 + 회색 테두리 (대항목 제목 셀) |
| borderFill 7 | 테두리 | 파란색 배경 #4472C4 (소항목 번호 배지) |
| borderFill 8 | 테두리 | 하단 테두리만 #D0D0D0 (소항목 제목 영역) |
proposal 레이아웃 패턴
대항목 헤더 (2셀 표: 번호 + 제목):
소항목 헤더 (2셀 표: 번호배지 + 제목):
워크플로우 2: 기존 문서 편집 (unpack → Edit → pack)
source "$VENV"
python3 "$SKILL_DIR/scripts/office/unpack.py" document.hwpx ./unpacked/
python3 "$SKILL_DIR/scripts/office/pack.py" ./unpacked/ edited.hwpx
python3 "$SKILL_DIR/scripts/validate.py" edited.hwpx
워크플로우 3: 읽기/텍스트 추출
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)
워크플로우 4: 검증
source "$VENV"
python3 "$SKILL_DIR/scripts/validate.py" document.hwpx
검증 항목: ZIP 유효성, 필수 파일 존재, mimetype 내용/위치/압축방식, XML well-formedness
워크플로우 5: 레퍼런스 기반 문서 생성
사용자가 제공한 HWPX 파일을 분석하여 동일한 레이아웃의 문서를 생성하는 워크플로우.
흐름
- 분석 —
analyze_template.py로 레퍼런스 문서 심층 분석
- header.xml 추출 — 레퍼런스의 스타일 정의를 그대로 사용
- section0.xml 작성 — 분석 결과의 구조를 따라 새 내용으로 작성
- 빌드 — 추출한 header.xml + 새 section0.xml로 빌드
- 검증
사용법
source "$VENV"
python3 "$SKILL_DIR/scripts/analyze_template.py" reference.hwpx
python3 "$SKILL_DIR/scripts/analyze_template.py" reference.hwpx \
--extract-header /tmp/ref_header.xml \
--extract-section /tmp/ref_section.xml
python3 "$SKILL_DIR/scripts/build_hwpx.py" \
--header /tmp/ref_header.xml \
--section /tmp/new_section0.xml \
--output result.hwpx
python3 "$SKILL_DIR/scripts/validate.py" result.hwpx
분석 출력 항목
| 항목 | 설명 |
|---|
| 폰트 정의 | hangul/latin 폰트 매핑 |
| borderFill | 테두리 타입/두께 + 배경색 (각 면별 상세) |
| charPr | 글꼴 크기(pt), 폰트명, 색상, 볼드/이탤릭/밑줄/취소선, fontRef |
| paraPr | 정렬, 줄간격, 여백(left/right/prev/next/intent), heading, borderFillIDRef |
| 문서 구조 | 페이지 크기, 여백, 페이지 테두리, 본문폭 |
| 본문 상세 | 모든 문단의 id/paraPr/charPr + 텍스트 내용 |
| 표 상세 | 행×열, 열너비 배열, 셀별 span/margin/borderFill/vertAlign + 내용 |
핵심 원칙
- charPrIDRef/paraPrIDRef를 그대로 사용: 추출한 header.xml의 스타일 ID를 변경하지 말 것
- 열 너비 합계 = 본문폭: 분석 결과의 열너비 배열을 그대로 복제
- rowSpan/colSpan 패턴 유지: 분석된 셀 병합 구조를 정확히 재현
- cellMargin 보존: 분석된 셀 여백 값을 동일하게 적용
스크립트 요약
| 스크립트 | 용도 |
|---|
scripts/build_hwpx.py | 핵심 — 템플릿 + XML → HWPX 조립 |
scripts/analyze_template.py | HWPX 심층 분석 (레퍼런스 기반 생성의 청사진) |
scripts/office/unpack.py | HWPX → 디렉토리 (XML pretty-print) |
scripts/office/pack.py | 디렉토리 → HWPX (mimetype first) |
scripts/validate.py | HWPX 파일 구조 검증 |
scripts/text_extract.py | HWPX 텍스트 추출 |
단위 변환
| 값 | HWPUNIT | 의미 |
|---|
| 1pt | 100 | 기본 단위 |
| 10pt | 1000 | 기본 글자크기 |
| 1mm | 283.5 | 밀리미터 |
| 1cm | 2835 | 센티미터 |
| A4 폭 | 59528 | 210mm |
| A4 높이 | 84186 | 297mm |
| 좌우여백 | 8504 | 30mm |
| 본문폭 | 42520 | 150mm (A4-좌우여백) |
HWP → HWPX 자동 변환
.hwp 파일이 입력으로 들어오면, 읽기/편집 전에 반드시 아래 방법으로 먼저 .hwpx로 변환한다.
사전 준비: HWPX 변환기 애드인 설치
변환기는 한컴 공식 다운로드 센터에서 무료로 받을 수 있다.
- 한컴 다운로드 센터 접속
- 추가 기능(Add-In) 카테고리 선택
- HWPX 변환기 항목 다운로드 및 설치
설치 후 변환기 경로:
C:\Program Files (x86)\HNC\HwpxConverter\HwpxConverter.exe
변환 방법 (파일 인자 방식)
"/c/Program Files (x86)/HNC/HwpxConverter/HwpxConverter.exe" "경로/파일명.hwp"
sleep 4
Python에서 사용 패턴
import subprocess, time
from pathlib import Path
def ensure_hwpx(path: str) -> str:
"""HWP이면 HWPX로 변환 후 경로 반환. HWPX면 그대로 반환."""
p = Path(path)
if p.suffix.lower() == ".hwpx":
return path
if p.suffix.lower() == ".hwp":
converter = r"C:\Program Files (x86)\HNC\HwpxConverter\HwpxConverter.exe"
hwpx_path = p.with_suffix(".hwpx")
subprocess.Popen([converter, str(p)])
time.sleep(4)
if hwpx_path.exists():
return str(hwpx_path)
raise FileNotFoundError(f"변환 실패: {hwpx_path}")
raise ValueError(f"지원하지 않는 형식: {p.suffix}")
주의사항
- 변환기가 백그라운드로 실행되므로
sleep 4 이상 대기 필요
- 변환 결과는 원본
.hwp와 같은 폴더에 생성됨
.hwpx 파일이 이미 존재하면 덮어씀
Critical Rules
- [커스텀] HWP 자동 변환:
.hwp(바이너리) 파일은 직접 읽지 않는다. 파일을 읽기 전에 HwpxConverter.exe로 자동 변환 후 처리한다. (아래 "HWP → HWPX 자동 변환" 섹션 참조)
- secPr 필수: section0.xml 첫 문단의 첫 run에 반드시 secPr + colPr 포함
- mimetype 순서: HWPX 패키징 시 mimetype은 첫 번째 ZIP 엔트리, ZIP_STORED
- 네임스페이스 보존: XML 편집 시
hp:, hs:, hh:, hc: 접두사 유지
- itemCnt 정합성: header.xml의 charProperties/paraProperties/borderFills itemCnt가 실제 자식 수와 일치
- ID 참조 정합성: section0.xml의 charPrIDRef/paraPrIDRef가 header.xml 정의와 일치
- venv 사용: 프로젝트의
.venv/bin/python3 (lxml 패키지 필요)
- 검증: 생성 후 반드시
validate.py로 무결성 확인
- 레퍼런스: 상세 XML 구조는
$SKILL_DIR/references/hwpx-format.md 참조
- build_hwpx.py 우선: 새 문서 생성은 build_hwpx.py 사용 (python-hwpx API 직접 호출 지양)
- 빈 줄:
<hp:t/> 사용 (self-closing tag)
워크플로 3: 레퍼런스 보존형 재조립
템플릿을 새로 만드는 대신, 이미 잘 만들어진 참고 HWPX의 상단 구조와 스타일 정의를 유지하고 본문만 교체하는 방식이다.
기관별 결재문서, 보고전, 방침결정처럼 레이아웃은 유지하고 문안만 새로 써야 하는 경우에 특히 유효하다.
핵심 원칙
- 참고 HWPX의
Contents/section0.xml을 직접 읽는다.
- 상단 표, 그림, 제목 테이블, 요약 박스 등 유지해야 할 구간은 그대로 둔다.
- 본문 시작 마커 이후 문단만 새로 생성해서 교체한다.
- 줄바꿈 기준이 중요한 문장은 한 문단 한 문자열로 넣지 말고
run으로 분해한다.
run 분해가 필요한 대표 패턴
항목명 : 설명
- (항목명) 설명
▸ KPI : 값
이 방식은 단순히 글자를 예쁘게 넣는 문제가 아니라, 줄바꿈 시 둘째 줄이 어디에 맞춰질지를 통제하기 위한 것이다.
권장 절차
python3 "$SKILL_DIR/scripts/analyze_template.py" reference.hwpx \
--extract-header /tmp/ref_header.xml \
--extract-section /tmp/ref_section.xml
python3 "$SKILL_DIR/scripts/validate.py" result.hwpx
실무 판단 기준
- 같은 레벨 문장이라도
콜론형, 괄호형, 일반 서술형은 별개 패턴으로 본다.
- 참고 문서의
charPrIDRef, paraPrIDRef는 가능하면 그대로 유지한다.
- 스타일을 새로 만드는 것보다, 기존 스타일을 재사용하면서 run만 나누는 쪽이 안정적이다.
- 본문 교체형 작업은 생성 후 반드시
validate.py를 돌린다.
Workflow 6: XML Triage and Escalation
Use this decision rule before editing a complex reference document.
Stay in XML-first mode when
- the reference body is mostly plain paragraphs
- tables are simple and text lives in normal paragraph runs
- visual fidelity is important but exact on-screen placement is not critical
Escalate to hwp-com-writer when
- text lives inside table cells, text boxes, or positioned controls
- a single visible line is split across many
run or t nodes
- the cover/header contains indexed boxes that must stay in place
validate.py passes but line wrapping or box placement is still broken in Hancom
Non-negotiable XML safety rules
- Do not insert new paragraphs ahead of indexed cover/header objects. Overwrite in place.
- After changing paragraph text, remove
linesegarray so Hancom can recalculate line layout.
- Preserve the reference
run and t node shape when replacing text.
- If a target
run has no hp:t, create one before writing text.
- Summary boxes often use one paragraph with many text nodes. Fill the slots; do not collapse them into one long string.
- Headings may be a single
run with multiple text nodes. Replace at node level, not just run level.
Practical red flags from reference-preserving work
- If a title like
I Heading loses everything except the roman numeral, you edited the wrong node granularity.
- If text overlaps on one line, you likely kept stale
linesegarray or collapsed multi-run content into one run.
- If a department box becomes blank after adding a date or stamp line, you shifted paragraph indexes instead of editing in place.
Public Release Note
For public distribution, omit binary .hwpx examples or scrub their metadata first.