hwpx
한글(HWPX) 보고서 생성/편집/읽기 스킬. .hwpx 파일, 한글 문서, Hancom, OWPML 관련 요청 시 사용.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
한글(HWPX) 보고서 생성/편집/읽기 스킬. .hwpx 파일, 한글 문서, Hancom, OWPML 관련 요청 시 사용.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
| 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()를 기본값으로 사용한다(사용자가 다른 날짜를 명시한 경우만 예외). ([머리말·제목 자리표시자 교체] 섹션 참조)