hwpx
한글(HWPX) 보고서 생성/편집/읽기 스킬. .hwpx 파일, 한글 문서, Hancom, OWPML 관련 요청 시 사용.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
한글(HWPX) 보고서 생성/편집/읽기 스킬. .hwpx 파일, 한글 문서, Hancom, OWPML 관련 요청 시 사용.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
| name | hwpx |
| description | 한글(HWPX) 보고서 생성/편집/읽기 스킬. .hwpx 파일, 한글 문서, Hancom, OWPML 관련 요청 시 사용. |
한글(Hancom Office)의 HWPX 보고서를 XML 직접 작성 중심으로 생성, 편집, 읽기할 수 있는 스킬. HWPX는 ZIP 기반 XML 컨테이너(OWPML 표준)이다. python-hwpx API의 서식 버그를 완전히 우회하며, 세밀한 서식 제어가 가능하다.
이 스킬은 templates/report/에 정의된 report 보고서 양식 한 종류를 기준으로 동작한다. 사용자가 .hwpx 파일을 첨부하더라도 그 구조를 그대로 복원하지는 않는다 — 첨부 파일은 내용 참고용으로만 활용하고, 출력은 항상 report 양식으로 생성한다.
이 스킬은 비개발자 사용자를 위해 배포된다. 따라서 의존성 누락 시 사용자에게
pip install을 요구하지 말고, 아래 절차로 스킬이 직접 설치한다.
본격적인 빌드 작업 전에 다음을 순서대로 수행한다:
필수 의존성 확인 (lxml)
python3 -c "import lxml" 2>/dev/null
성공하면 다음 단계로. 실패하면 2번으로 이동.
자동 설치 (실패 시 1회 안내 후 진행) 사용자에게 한 줄로 안내한다: "한글파일 스킬 첫 사용 — 의존성 자동 설치 중입니다(약 10~30초)…" 그 다음 다음 명령을 실행한다:
python3 -m pip install --user --quiet -r "$SKILL_DIR/requirements.txt"
설치 후 다시 python3 -c "import lxml"로 확인. 여전히 실패하면 사용자에게 환경 문제(파이썬/네트워크/권한)를 보고하고 작업을 중단한다.
텍스트 추출(text_extract.py)이 필요한 요청에 한해서만 python-hwpx 확인
requirements.txt에 이미 포함되어 있어 2번에서 함께 설치된다. 일반적인 보고서 생성 흐름에서는 별도 확인이 불필요하다.
재확인 후 본 작업 계속 환경 점검이 한 번 통과하면 같은 세션에서는 다시 점검하지 않는다(이미 import 가능 상태).
참고: 설치 명령은
--user플래그로 사용자 홈에 설치하므로 시스템 권한이 필요 없고,--quiet로 출력을 최소화한다.requirements.txt가 패키지 목록의 단일 출처(single source of truth)다.
SKILL_DIR는 이 SKILL.md가 위치한 디렉터리의 절대 경로다. 설치 방식에 따라 다음과 같이 결정한다:
| 설치 방식 | SKILL_DIR 값 |
|---|---|
| 사용자 전역 설치 | $HOME/.claude/skills/hwpx |
| 프로젝트 전용 설치 | $(pwd)/.claude/skills/hwpx (프로젝트 루트 기준) |
| 리포 클론 직접 사용 | $(pwd) (리포 루트 자체가 곧 스킬) |
Claude agent는 이 SKILL.md를 읽어 들인 실제 위치를 기준으로 SKILL_DIR를 자연스럽게 결정한다. 아래 모든 예시의 $SKILL_DIR는 이 값으로 치환해 사용한다.
Python 명령은 현재 agent 환경에서 사용 가능한 python3로 실행한다. 필수 의존성은 위 "0. 환경 점검" 절차로 자동 처리되므로, 사용자가 별도로 pip install할 필요는 없다. 의존성 목록의 정의는 requirements.txt에 있다.
이 스킬이 생성하는 모든 .hwpx 결과물은 사용자의 현재 작업 디렉터리($(pwd)) 아래 output/ 폴더에 저장한다.
--output output/result.hwpx, --output output/2026_보고서.hwpxoutput/ 폴더가 없으면 build_hwpx.py(및 office/pack.py)가 자동 생성한다 — 사용자에게 미리 만들도록 요구하지 않는다.output/업무추진계획.hwpx).output/ 대신 tempfile//tmp에 두고 사용 후 삭제한다.hwpx/
├── SKILL.md # 이 파일
├── scripts/
│ ├── office/
│ │ ├── unpack.py # HWPX → 디렉토리 (XML pretty-print)
│ │ └── pack.py # 디렉토리 → HWPX
│ ├── build_hwpx.py # report 템플릿 + XML → .hwpx 조립 (핵심)
│ ├── table_builder.py # 표 템플릿 → XML 생성
│ ├── preview_table.py # 표 템플릿 + 샘플 데이터 → 미리보기 .hwpx
│ ├── extract_table_templates.py # 표 라이브러리 HWPX → 표 템플릿 .xml 재생성
│ ├── validate.py # HWPX 구조 검증
│ ├── style_check.py # 개조식 문체 규칙 검사 (□/❍ 길이·종결)
│ └── text_extract.py # 텍스트 추출 (python-hwpx 필요)
├── templates/
│ ├── base/ # 내부 스켈레톤 (mimetype, META-INF, content.hpf 등 — build_hwpx.py가 자동 사용)
│ ├── report/ # 보고서 양식 (header.xml, section0.xml) — 모든 출력의 기본 오버레이
│ └── tables/ # 표 템플릿 (table_builder.py 사용)
│ ├── basic.xml # 기본 표 (4열: 연번/구분/내용/비고)
│ ├── status.xml # 현황표 (4열: 번호/항목/추진현황/진행률)
│ ├── budget.xml # 예산표 (5열, 합계행 포함)
│ ├── schedule.xml # 일정표 (3열: 일자/추진내용/담당)
│ └── checklist.xml # 점검표 (5열: 번호/점검항목/담당/확인/비고)
├── assets/
│ ├── report-template.hwpx # report 템플릿 시각 기준 샘플 (런타임 미사용)
│ └── all_tables_preview.hwpx # 표 라이브러리 — extract_table_templates.py의 추출 소스
└── references/
└── hwpx-format.md # OWPML XML 요소 레퍼런스
templates/base/는 HWPX 컨테이너에 반드시 들어가야 하는 파일 골격(mimetype, META-INF, Preview, content.hpf 등)을 제공하는 내부 스켈레톤이다. build_hwpx.py가 자동으로 사용하며, 사용자가 직접 의식할 필요는 없다.
assets/는 최종 문서 생성에 직접 투입되는 런타임 템플릿이 아니라, 필요할 때 한글에서 열어 레이아웃을 확인하는 기준 샘플을 보관하는 위치다.
templates/report/header.xml을 그대로 사용한다 (스타일 정의: 폰트, 크기, 색상, 문단 간격 등)templates/report/section0.xml의 secPr(페이지 설정) 첫 문단만 가져오고, 본문은 내용에 맞게 새로 작성한다□/❍ 본문은 공공기관 보고서의 개조식 문체로 작성한다. 줄의 성격에 따라 길이와 종결을 다르게 한다.
□ 뒤 기준으로 공백·괄호·문장부호 포함 30자 이상 35자 이내로 쓴다. "□ 연구환경"처럼 내용 없는 단순 라벨로 두지 말고, 핵심을 압축한 한 줄 범주 문장으로 작성한다 — 세부 내용은 아래 ❍ 줄로 내린다라벨 : 값 형식의 개요·항목 줄은 30자 하한을 적용하지 않는다 — 자연스러운 길이로 두고 억지로 늘리지 않는다 (35자 상한은 동일 적용) ❍ ...). □ 줄은 들여쓰기 없이 가장 왼쪽에서 시작한다. style_check는 들여쓰기 무관하게 길이를 보지만, 양식 일관성을 위해 들여쓰기는 반드시 넣는다pp=14, cp=9, 양식 고정) — templates/report/section0.xml의 paras[:3]까지 그대로 복사하면 자동 포함, (2) 섹션 사이 구분 빈 줄(pp=2, cp=13, 11pt 한양중고딕). 일반 pp=0, cp=0 빈 줄은 양식과 어긋난다~ 전환, ~ 발전, ~ 구축, ~ 확대, ~ 강화, ~ 고도화, ~ 확보, ~ 운영, ~ 추진 등~합니다, ~습니다, ~한다, ~된다, ~있다, ~큼, ~함생성한
.hwpx는scripts/style_check.py로 위 규칙(□ 길이·❍ 길이·종결) 준수 여부를 점검한다.
templates/report/header.xml 읽기)templates/report/section0.xml에서 복사, 본문은 내용 분량에 맞게 자유롭게 구성--template 지정 없이 호출하면 report 양식이 자동 적용된다templates/report/section0.xml의 상단에는 다음 자리표시자가 들어 있다. 빌드 전에 반드시 실제 값으로 교체해야 한다 — 그대로 두면 결과 문서에 YY, 보고자료 제목 같은 더미 텍스트가 그대로 노출된다.
| 위치 | 자리표시자 원문 | 교체 대상 |
|---|---|---|
| 첫 문단 머리말 | (’YY. MM. DD., 부서명) | 보고일 + 작성 부서 |
| 제목 도형(rect) drawText | 보고자료 제목 | 실제 보고서 제목 |
날짜는 빌드 시점의 시스템 날짜를 기본값으로 사용한다 (datetime.date.today()). 사용자가 다른 날짜를 명시한 경우만 예외.
원본 템플릿에서는 YY / . MM. DD., 부서명) / 보고자료 제목이 각각 별개의 <hp:t> 노드로 들어 있으므로, 텍스트 노드를 순회하며 정확한 일치(또는 부분 일치)로 치환한다:
from datetime import date
from lxml import etree
HP_T = "{http://www.hancom.co.kr/hwpml/2011/paragraph}t"
today = date.today()
yy = f"{today.year % 100:02d}"
mm_dd = f"{today.month}. {today.day}."
for t in root.iter(HP_T):
if t.text == "YY":
t.text = yy
elif t.text == "보고자료 제목":
t.text = "2026년 상반기 AI 활용 교육 추진계획 보고서"
elif t.text and ". MM. DD., 부서명)" in t.text:
t.text = t.text.replace(
". MM. DD., 부서명)", f". {mm_dd}, 인재개발팀)"
)
--title/--creatorCLI 옵션은content.hpf의 메타데이터(파일 속성)만 갱신하며, 문서 본문에 보이는 제목 도형 텍스트는 위 절차로 별도 치환해야 한다.
# 기본 보고서 (커스텀 section 없이 — 양식만 들어있는 빈 보고서)
python3 "$SKILL_DIR/scripts/build_hwpx.py" --output output/result.hwpx
# 커스텀 section0.xml로 본문 채우기
python3 "$SKILL_DIR/scripts/build_hwpx.py" --section my_section0.xml --output output/result.hwpx
# header도 오버라이드 (드물게 사용)
python3 "$SKILL_DIR/scripts/build_hwpx.py" --header my_header.xml --section my_section0.xml --output output/result.hwpx
# 메타데이터 설정
python3 "$SKILL_DIR/scripts/build_hwpx.py" --section my.xml \
--title "제목" --creator "작성자" --output output/result.hwpx
Step 1: templates/report/section0.xml의 secPr 포함 첫 문단을 복사 (페이지 설정)
Step 2: 나머지 본문은 내용 분량에 맞게 자유롭게 작성 (charPrIDRef/paraPrIDRef만 header.xml 참조)
Step 3: 빌드
# 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 포함 첫 문단 (report template 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 output/result.hwpx
# 3. 정리
rm -f "$SECTION"
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/report/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>
1000000001부터 순차 증가1000000099 등 별도 범위 사용 권장templates/tables/ 에 저장된 표 양식을 기반으로, 데이터만 넣으면 완성된 표 XML을 생성한다.
| 템플릿 | 열 구성 | 합계행 | 용도 |
|---|---|---|---|
basic | 연번/구분/내용/비고 (4열) | - | 범용 표 |
status | 번호/항목/추진현황/진행률 (4열) | - | 현황 보고 |
budget | 연번/사업명/내용/예산(백만원)/비고 (5열) | ✓ | 예산 내역 |
schedule | 일자/추진내용/담당 (3열) | - | 일정 계획 |
checklist | 번호/점검항목/담당/확인/비고 (5열) | - | 점검·체크리스트 |
위 열 구성은 현재
templates/tables/*.xml기준이며extract_table_templates.py로 재생성하면 바뀔 수 있다. 최신 목록은table_builder.py --list로 확인한다.
import sys; sys.path.insert(0, f"{SKILL_DIR}/scripts")
from table_builder import build_table, build_table_paragraph
# 표 XML만 생성 (hp:tbl)
table_xml = build_table(
template="basic",
data=[["1", "교원연수", "AI 직무연수 운영", "상반기"],
["2", "수업모델", "수업안 개발", "5개 교과"]],
headers=["연번", "구분", "내용", "비고"], # 선택: 헤더 텍스트 오버라이드
start_id=1000000050,
)
# hp:p로 감싼 버전 (section0.xml에 바로 삽입 가능)
para_xml = build_table_paragraph(
template="budget",
data=[["1", "인건비", "개발자 3명", "500", ""],
["2", "장비", "서버 구매", "200", ""]],
summary=["", "", "합 계", "700", ""], # 합계행 (budget 전용)
start_id=1000000050,
)
# 템플릿 목록 확인
python3 "$SKILL_DIR/scripts/table_builder.py" --list
# 표 XML 생성
python3 "$SKILL_DIR/scripts/table_builder.py" --template basic \
--data '[["1","교원연수","AI 직무연수","상반기"]]' \
--start-id 1000000050
# hp:p 포함 출력 (--paragraph)
python3 "$SKILL_DIR/scripts/table_builder.py" --template basic \
--data '[["1","A","B","C"]]' --paragraph --start-id 1000000050
summary 파라미터는 budget처럼 summary row가 정의된 템플릿에서만 동작headers 미지정 시 템플릿의 기본 헤더 텍스트 사용‘YY.MM.DD 형식으로 작성한다 (예: ‘26.05.01). 기간은 ‘26.05.01 ~ ‘26.06.30처럼 쓴다. "3월 2주", "상반기" 같은 모호한 표기는 피한다표 템플릿에 샘플 데이터를 채워 한글에서 바로 열어볼 수 있는 .hwpx를 생성한다 (디자인 확인용).
# 사용 가능한 표 템플릿 목록
python3 "$SKILL_DIR/scripts/preview_table.py" --list
# 단일 템플릿 미리보기 (output/budget_preview.hwpx 로 저장)
python3 "$SKILL_DIR/scripts/preview_table.py" budget
# 전체 템플릿을 한 번에 미리보기 생성 (모두 output/ 에 저장)
python3 "$SKILL_DIR/scripts/preview_table.py" --all
새 표 양식을 추가하거나 기존 표를 수정할 때는, 표를 모아 둔 표 라이브러리 HWPX(assets/all_tables_preview.hwpx)를 한글에서 편집한 뒤 이 스크립트로 templates/tables/*.xml을 다시 추출한다. XML을 직접 손대지 않고 한글에서 표를 디자인할 수 있다.
# 표 라이브러리 HWPX → templates/tables/*.xml 재생성
python3 "$SKILL_DIR/scripts/extract_table_templates.py"
# 다른 HWPX를 소스로 지정 / 미리보기만 / 사라진 템플릿 정리
python3 "$SKILL_DIR/scripts/extract_table_templates.py" assets/my_tables.hwpx
python3 "$SKILL_DIR/scripts/extract_table_templates.py" --dry-run
python3 "$SKILL_DIR/scripts/extract_table_templates.py" --prune
표 인식 규칙 — 각 표 바로 앞 문단에 N. 이름 - 설명 형식 라벨을 둔다(예: 4. schedule - 일정표 (일자/추진내용/담당)). 이름은 출력 파일명과 <meta><name>, 설명은 <meta><description>가 된다. 1행은 머리행, 표 끝에서 머리행과 같은 셀 서식이 연속되는 행은 합계행, 나머지는 본문행으로 인식한다.
스타일 정규화 규칙 (필수) — 추출 결과의 모든 charPrIDRef·paraPrIDRef·borderFillIDRef는 소스 한글파일이 아니라 templates/report/header.xml의 표준 표 스타일로 자동 재매핑된다 — 머리행 26/18/9, 본문행 27/18·22/10, 합계행 26/18/9, 컨테이너 4. 보고서는 항상 report 헤더와 함께 빌드되므로, 이 재매핑이 있어야 어떤 한글파일에서 뽑은 표든 글꼴·테두리가 깨지지 않는다. 표의 구조(열 수·너비·정렬·합계행)는 소스 그대로 유지되고 색·글꼴만 보고서 표준 표 서식으로 통일된다.
추출 표 너비가 보고서 본문폭(
48190)을 넘으면 경고가 출력된다 — 한글에서 열 너비를 줄여 다시 추출한다.
셀 여백 주의 — 셀 안쪽 여백은 한글에서 표/셀 속성 → 안 여백으로 준다. 문단 모양 → 여백으로 준 값은
paraPr(스타일)에 저장돼 정규화 때 사라진다. 구조 속성인<hp:cellMargin>만 추출 시 보존된다.
[design] 마커)스타일 정규화는 보통 합리적이지만, 마일스톤·로드맵·카드형 표처럼 셀마다 다른 borderFill·색·폰트로 시각화하는 표는 정규화하면 디자인이 평준화되어 의도가 사라진다(spacer 셀까지 헤더 회색으로 칠해지거나, 본문 N행이 1행으로 합쳐지는 등). 그런 표는 라벨 끝에 [design] (또는 [preserve]) 마커를 둔다.
6. roadmap - 일자별 진행일정 로드맵 [design]
마커가 있으면 추출 결과가 달라진다:
meta/preserve-design이 true로 기록본문 N->1 정규화 안 함)borderFill·charPr·paraPr 정의를 라이브러리 header.xml에서 자동 추출해 meta/extra-styles에 함께 저장sample은 라이브러리 원본 ID 그대로 — 스타일 평준화 없음빌드 시점에는 table_builder.merge_template_styles_into_header()가 이 extra-styles를 보고서 header.xml에 새 ID로 머지하고, build_table*(template, ..., id_mapping=mapping)이 sample의 ID 참조를 매핑된 ID로 치환한다. 보고서 빌더 패턴:
from lxml import etree
from table_builder import build_table_paragraph, merge_template_styles_into_header
# 1) 보고서 header.xml에 디자인 보존 표의 의존 스타일을 머지
rep_header = etree.parse("$SKILL_DIR/templates/report/header.xml")
mapping = merge_template_styles_into_header("roadmap", rep_header)
rep_header.write("/tmp/patched_header.xml", xml_declaration=True,
encoding="UTF-8", standalone=True)
# 2) 표 빌드 (preserve-design 모드는 build_table 안에서 자동 처리)
# headers: header-row 셀 수만큼(스페이서 포함, 빈 문자열로) 채운 list
# data: body 행 수만큼, 각 row는 그 행의 셀 텍스트 list
table_xml = build_table_paragraph(
template="roadmap",
headers=["1단계", "", "2단계", "", "3단계", "", "4단계", "", "5단계"],
data=[
["계획 수립", "공모·접수", "심사·평가", "본선·시상", "성과 확산"],
["’26. 6월", "’26. 7~8월", "’26. 9~10월", "’26. 11월", "’26. 12월"],
],
id_mapping=mapping,
start_id=1000000200,
)
# 3) build_hwpx.py에 --header /tmp/patched_header.xml 로 전달
preview_table.py는 preserve-design=true 템플릿을 자동 감지해 위 단계를 알아서 수행한다. 디자인 보존 표의 미리보기 데이터는 SAMPLE_DATA 딕셔너리에서 "headers": [...], "data": [[...], ...] 형태로 정의한다.
사용 시 주의 (preserve-design 모드 한정):
merge_template_styles_into_header()는 같은 header tree에 한 번만 호출한다(멱등하지 않음). 두 번 호출하면 lib 정의가 중복 추가된다. 한 보고서 빌드 흐름에서 한 번만 부르면 안전.data/headers 행·셀 수는 sample 구조와 정확히 일치시킨다. 부족하면 해당 셀은 빈 셀로 출력된다(원본 placeholder 텍스트가 새지 않도록 안전 디폴트). 행 수를 일부러 줄이고 싶으면 한글에서 라이브러리 표를 수정한 뒤 재추출한다.[design] 마커는 한글에서 라이브러리 편집 시에도 유지해야 한다. 마커가 지워지면 다음 extract 때 정규화 경로로 분류돼 디자인이 평준화된다.templates/report/header.xml 복사itemCnt 속성 업데이트<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 idhangul="0" → 굴림, "1" → 궁서, "2" → 맑은고딕, "3" → 함초롬돋움hangul="4" → 휴먼명조, "5" → HY헤드라인M, "7" → 한양중고딕, "8" → 바탕
templates/report/header.xml기준.
| 용도 | charPrIDRef | paraPrIDRef | 설명 |
|---|---|---|---|
| 소제목 (1. 2. 3.) | 10 | 2 | 16pt HY헤드라인M |
| 소제목 후 빈줄 | 12 | 2 | 4pt 스페이서 |
| □/❍ 본문 항목 | 25 | 22 | 13pt 휴먼명조 |
| ※ 참고 사항 | 20 | 21 | 13pt 한양중고딕 |
| 섹션 구분 빈줄 | 13 | 2 | 11pt 한양중고딕 |
| 제목 아래 빈줄 | 9 | 14 | (양식 고정) |
| 용도 | charPrIDRef | paraPrIDRef | borderFillIDRef |
|---|---|---|---|
| 표 헤더 셀 | 26 (12pt 휴먼명조 bold) | 18 (CENTER) | 9 (회색 #D9D9D9) |
| 표 본문 셀 | 27 (12pt 휴먼명조) | 18 (CENTER) 또는 22 (JUSTIFY) | 10 (테두리만) |
| 표 합계행 | 26 | 18 | 9 |
| 표 컨테이너 | - | - | 4 (SOLID 테두리) |
| 용도 | charPrIDRef | paraPrIDRef | 비고 |
|---|---|---|---|
| 날짜·부서 문단 | 21, 9, 22, 18 | 19 | secPr 포함, 다중 run |
| 제목 drawText | 24 (내부: 25) | 2 | rect 도형 안 텍스트 |
# 1. HWPX → 디렉토리 (XML pretty-print)
python3 "$SKILL_DIR/scripts/office/unpack.py" document.hwpx ./unpacked/
# 2. XML 직접 편집 (agent가 파일 편집 도구로)
# 본문: ./unpacked/Contents/section0.xml
# 스타일: ./unpacked/Contents/header.xml
# 3. 다시 HWPX로 패키징
python3 "$SKILL_DIR/scripts/office/pack.py" ./unpacked/ output/edited.hwpx
# 4. 검증
python3 "$SKILL_DIR/scripts/validate.py" output/edited.hwpx
# 순수 텍스트
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
from hwpx import TextExtractor
with TextExtractor("document.hwpx") as ext:
text = ext.extract_text(include_nested=True, object_behavior="nested")
print(text)
python3 "$SKILL_DIR/scripts/validate.py" document.hwpx
검증 항목: ZIP 유효성, 필수 파일 존재, mimetype 내용/위치/압축방식, XML well-formedness
| 스크립트 | 용도 |
|---|---|
scripts/build_hwpx.py | 핵심 — report 템플릿 + XML → HWPX 조립 |
scripts/table_builder.py | 표 템플릿 → 데이터 주입 → 표 XML 생성 |
scripts/preview_table.py | 표 템플릿 + 샘플 데이터 → 미리보기 .hwpx 생성 |
scripts/extract_table_templates.py | 표 라이브러리 HWPX → 표 템플릿 .xml 재생성 |
scripts/office/unpack.py | HWPX → 디렉토리 (XML pretty-print) |
scripts/office/pack.py | 디렉토리 → HWPX (mimetype first) |
scripts/validate.py | HWPX 파일 구조 검증 |
scripts/style_check.py | 개조식 문체 규칙 검사 (□/❍ 길이·종결) |
scripts/text_extract.py | HWPX 텍스트 추출 (python-hwpx 필요) |
| 값 | HWPUNIT | 의미 |
|---|---|---|
| 1pt | 100 | 기본 단위 |
| 10pt | 1000 | 기본 글자크기 |
| 1mm | 283.5 | 밀리미터 |
| 1cm | 2835 | 센티미터 |
| A4 폭 | 59528 | 210mm |
| A4 높이 | 84186 | 297mm |
| 좌우여백 | 8504 | 30mm |
| 본문폭 | 42520 | 150mm (A4-좌우여백) |
.hwp(바이너리) 파일은 지원하지 않는다. 사용자가 .hwp 파일을 제공하면 한글 오피스에서 .hwpx로 다시 저장하도록 안내할 것. (파일 → 다른 이름으로 저장 → 파일 형식: HWPX)hp:, hs:, hh:, hc: 접두사 유지python3 사용 (lxml 필요, 일부 보조 스크립트는 hwpx 패키지 필요)validate.py로 무결성 확인$SKILL_DIR/references/hwpx-format.md 참조<hp:t/> 사용 (self-closing tag).hwpx 결과는 $(pwd)/output/ 폴더에 저장한다. 폴더 미존재 시 자동 생성. 사용자가 다른 경로를 명시한 경우만 예외. ([결과 저장 위치] 섹션 참조)YY / . MM. DD., 부서명) / 보고자료 제목 자리표시자는 빌드 전에 반드시 실제 값으로 교체한다. 날짜는 datetime.date.today()를 기본값으로 사용한다(사용자가 다른 날짜를 명시한 경우만 예외). ([머리말·제목 자리표시자 교체] 섹션 참조)