Imported from Canine89/hwpxskill (
SKILL.md). Install upstream withnpx skills add Canine89/hwpxskill. Copyright stays with the author.
HWPX ๋ฌธ์ ์คํฌ โ ๋ ํผ๋ฐ์ค ๋ณต์ ์ฐ์ (XML-first) ์ํฌํ๋ก์ฐ
ํ๊ธ(Hancom Office)์ HWPX ํ์ผ์ XML ์ง์ ์์ฑ ์ค์ฌ์ผ๋ก ์์ฑ, ํธ์ง, ์ฝ๊ธฐํ ์ ์๋ ์คํฌ. HWPX๋ ZIP ๊ธฐ๋ฐ XML ์ปจํ ์ด๋(OWPML ํ์ค)์ด๋ค. ์์ ๋ณด์กด์ด ์ค์ํ ํธ์ง์์๋ OWPML XML์ ์ง์ ๋ค๋ค charPr, paraPr, ํ ๊ตฌ์กฐ๋ฅผ ์ธ๋ฐํ๊ฒ ์ ์ดํ๋ค.
HWP ๋ฐ์ด๋๋ฆฌ ์์ฒญ ๊ฑฐ๋ถ ๊ท์น (ํ์)
์ด ์คํฌ์ HWPX ์ ์ฉ์ด๋ค. ์ฌ์ฉ์๊ฐ .hwp ๋ฐ์ด๋๋ฆฌ ํ์ผ์ ์์ฑ, ์์ฑ, ์ ์ฅ, ํธ์ง, ์ฑ์ฐ๊ธฐ, ๋ณํ ๊ฒฐ๊ณผ๋ฌผ ์์ฑ์ ์์ฒญํ๋ฉด ํด๋น ์์
์ ์ํํ์ง ์๋๋ค.
.hwp๋ก ๊ฒฐ๊ณผ ํ์ผ์ ๋ง๋ค์ด ๋ฌ๋ผ๋ ์์ฒญ์ ๊ฑฐ๋ถํ๋ค..hwp์๋ณธ์ ์ง์ ์์ ํ๊ฑฐ๋ ์ฑ์ ๋ฌ๋ผ๋ ์์ฒญ์ ๊ฑฐ๋ถํ๋ค..hwp๋ฅผ ์ฝ์ด ์ฐธ๊ณ ํ๋ ์์ค์ ๊ฐ๋ฅํ์ง๋ง, ๊ฒฐ๊ณผ๋ฌผ์ ์ฌ์ฉ์๊ฐ ๋ช ์์ ์ผ๋ก HWPX๋ฅผ ํ์ฉํ ๊ฒฝ์ฐ์๋ง.hwpx๋ก ๋ง๋ ๋ค.- ์ฌ์ฉ์๊ฐ
.hwp์์ฑ์ ์์ฒญํ๋๋ฐ HWPX ๋์ฒด ์ฐ์ถ๋ฌผ์ ๋ช ์์ ์ผ๋ก ํ์ฉํ์ง ์์๋ค๋ฉด, ์์๋ก.hwpxํ์ผ์ ๋ง๋ค์ด ๋์ ์๋ฃ ์ฒ๋ฆฌํ์ง ์๋๋ค. - ์๋ด ๋ฌธ๊ตฌ๋ ๊ฐ๋จํ ์ด๋ค: "์ด ์คํฌ์ HWPX๋ง ์ง์ํด์ .hwp ์์ฑ์ ํ ์ ์์ต๋๋ค. ํ๊ธ์์ HWPX๋ก ์ ์ฅํ ํ์ผ์ ์ฃผ๊ฑฐ๋, ๊ฒฐ๊ณผ๋ฌผ์ .hwpx๋ก ๋ฐ๋ ๋ฐฉ์์ด๋ฉด ์ฒ๋ฆฌํ ์ ์์ต๋๋ค."
๊ธฐ๋ณธ ๋์ ๋ชจ๋ (ํ์): ์ฒจ๋ถ HWPX ๋ถ์ โ ๊ณ ์ XML ๋ณต์(99% ๊ทผ์ ) โ ์์ฒญ ๋ฐ์ ์ฌ์์ฑ
์ฌ์ฉ์๊ฐ .hwpx๋ฅผ ์ฒจ๋ถํ ๊ฒฝ์ฐ, ์ด ์คํฌ์ ์๋ ์์๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋ฐ๋ฅธ๋ค.
- ๋ ํผ๋ฐ์ค ํ๋ณด: ์ฒจ๋ถ๋ HWPX๋ฅผ ๊ธฐ์ค ๋ฌธ์๋ก ์ฌ์ฉ
- ์ฌ๋กฏ ์ถ์ถ:
hwpx_slots.py๋ก ํธ์ง ๊ฐ๋ฅํ ๋ฌธ๋จ/์ ์ฌ๋กฏ ๋ชฉ๋ก์ JSON์ผ๋ก ๋ง๋ ๋ค - ์ฌ์ฉ์ ๊ฐ ๋งคํ: ์ฌ๋กฏ ํค(
p:12,cell:0:2:1)์ ์ ๊ฐ์ ๋งคํํ๋ค - ๊ตฌ์กฐ ๋ณด์กด ํธ์ง:
edit_hwpx.py --slot-json์ผ๋ก ์๋ณธ ํจํค์ง๋ฅผ ๋ณต์ ํ๊ณ ์ฌ๋กฏ ํ ์คํธ๋ง ์์ - ๋น๋/๊ฒ์ฆ:
edit_hwpx.py๊ฒฐ๊ณผ ๋๋build_hwpx.py๊ฒฐ๊ณผ๋ฅผvalidate.py๋ก ๋ฌด๊ฒฐ์ฑ ํ์ธ - ์ต์ข
ํ/๋ ์ด์์ ๊ฒฝ๊ณ :
fix_namespaces.py,finalize_hwpx.py --strip-linesegarray --layout,validate.py --layout๋ก ๋ค์์คํ์ด์ค์ ๋ ๋๋ง ์ํ์ ์ ๊ฒํ๋ค - ๊ธ์ ์์ฐ/์ชฝ์ ๊ฐ๋(ํ์):
page_guard.py๋ก ๋ฌธ๋จ/์ ๋ณ ๊ธ์ ์์ฐ๊ณผ ๋ ํผ๋ฐ์ค ๋๋น ํ์ด์ง ๋๋ฆฌํํธ ์ํ ๊ฒ์ฌ - ๋ด์ฉ ์์ฑ๋ ๊ฐ๋(ํ์):
content_guard.py๋ก ์๋ฌธ ์์ฌ, placeholder, ํ์ ํค์๋ ๋๋ฝ์ ๊ฒ์ฌํ๋ค
99% ๊ทผ์ ๋ณต์ ๊ธฐ์ค (์ค๋ฌด ์ฒดํฌ๋ฆฌ์คํธ)
charPrIDRef,paraPrIDRef,borderFillIDRef์ฐธ์กฐ ์ฒด๊ณ ๋์ผ- ํ์
rowCnt,colCnt,colSpan,rowSpan,cellSz,cellMargin๋์ผ - ๋ฌธ๋จ ์์, ๋ฌธ๋จ ์, ์ฃผ์ ๋น ์ค/๊ตฌํ ์์น ๋์ผ
- ํ์ด์ง/์ฌ๋ฐฑ/์น์ (secPr) ๋์ผ
- ๋ณ๊ฒฝ์ ์ฌ์ฉ์ ์์ฒญ ๋ฒ์(๋ณธ๋ฌธ ํ ์คํธ, ๊ฐ, ํญ๋ชฉ๋ช ๋ฑ)๋ก ์ ํ
- ๋ด์ฉ ๋ณ๊ฒฝ ์์ฒญ์ด ๋ฌธ์ ์ ์ฒด์ ๊ด์ ์ ํ(์: ํ๊ตญ ๊ธฐ๊ด โ ๋ฏธ๊ตญ ๊ธฐ๊ด, ํน์ ํ์ฌ โ ๋ค๋ฅธ ํ์ฌ)์ด๋ผ๋ฉด ์ด์ ๊ธฐ๊ด๋ช /๋ด๋น์/์ ํ๋ฒํธ/์ ์ฑ ๋ช ์์ฌ๊ฐ ๋จ์ผ๋ฉด ์คํจ
์ชฝ์ ๋์ผ(100%) ํ์ ๊ธฐ์ค
- ์ฌ์ฉ์๊ฐ ๋ ํผ๋ฐ์ค๋ฅผ ์ ๊ณตํ ๊ฒฝ์ฐ ๊ฒฐ๊ณผ ๋ฌธ์์ ์ต์ข ์ชฝ์๋ ๋ ํผ๋ฐ์ค์ ๋์ผํด์ผ ํ๋ค
- ์ชฝ์๊ฐ ๋์ด๋ ๊ฐ๋ฅ์ฑ์ด ๋ณด์ด๋ฉด ๋จผ์ ํ ์คํธ๋ฅผ ์์ถ/์์ฝํด์ ๊ธฐ์กด ๋ ์ด์์์ ๋ง์ถ๋ค
- ์ฌ์ฉ์ ๋ช
์ ์์ฒญ ์์ด
hp:p,hp:tbl,rowCnt,colCnt,pageBreak,secPr๋ฅผ ๋ณ๊ฒฝํ์ง ์๋๋ค validate.pyํต๊ณผ๋ง์ผ๋ก ์๋ฃ ์ฒ๋ฆฌํ์ง ์๋๋ค. ๋ฐ๋์page_guard.py์content_guard.py๋ ํต๊ณผํด์ผ ํ๋คpage_guard.py๋๋content_guard.py์คํจ ์ ๊ฒฐ๊ณผ๋ฅผ ์๋ฃ๋ก ์ ์ถํ์ง ์๊ณ , ์์ธ(๊ธธ์ด ๊ณผ๋ค/๊ตฌ์กฐ ๋ณ๊ฒฝ/์๋ฌธ ์์ฌ/ํ์ ํค์๋ ๋๋ฝ)์ ์์ ํ ์ฌ๋น๋ํ๋ค- ๊ฐ๋ฅํ๋ฉด ํ๊ธ(๋๋ ์ฌ์ฉ์์ ํ์ธ) ๊ธฐ์ค ์ต์ข ์ชฝ์ ๊ฐ์ ํ์ธํ๊ณ ๋ ํผ๋ฐ์ค์ ์ผ์น ์ฌ๋ถ๋ฅผ ์ฌํ์ธํ๋ค
๊ธฐ๋ณธ ์คํ ๋ช ๋ น (์ฒจ๋ถ ๋ ํผ๋ฐ์ค๊ฐ ์์ ๋)
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.py๋ ํ/๊ทธ๋ฆผ/ํ
์คํธ์์๋ฅผ ํ์ ์ปจํ
์ด๋ ๋ฌธ๋จ์ ์ ์ธํ๊ณ , ์ค์ ํธ์ง ๊ฐ๋ฅํ ๋ฌธ๋จ๊ณผ ํ ์
์ ์ฌ๋กฏ์ผ๋ก ๋
ธ์ถํ๋ค. ์ฌ์ฉ์๋ ์ฌ๋กฏ ํค์ ๊ฐ๋ง ๋ฃ์ผ๋ฉด ๋๋ค.
edit_hwpx.py๋ mimetype, content.hpf, header.xml, settings.xml, Preview/*, BinData/*๋ฅผ ์๋ณธ์์ ๊ทธ๋๋ก ๋ณต์ ํ๋ค. ์์ ๋์์ ๊ธฐ๋ณธ์ ์ผ๋ก Contents/section0.xml ๋ด๋ถ์ ์ฌ๋กฏ ํ
์คํธ๋ฟ์ด๋ค. ๋ฐ๋ผ์ ์ฌ์ฉ์๊ฐ ๊ธฐ์กด ์์์ ์ ๊ณตํ๊ณ ํ
์คํธ/์
์ฑ์ฐ๊ธฐ๋ง ํ์ํ๋ค๋ฉด build_hwpx.py๋ณด๋ค edit_hwpx.py --slot-json์ ์ฐ์ ํ๋ค.
ํจํค์ง์ ์ผ๋ฐ ZIP ์ฌ์์ถ์ ๊ธ์งํ๋ค. ์๋ณธ ZIP์ ๋ก์ปฌ ํค๋์ ์์ถ ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋๋ก ๋ณต์ฌํ๊ณ , ๋ณ๊ฒฝ ๋์ ์ํธ๋ฆฌ๋ง ๊ต์ฒดํ๋ค. ๋ณ๊ฒฝํ์ง ์์ ์ํธ๋ฆฌ๋ CRC, compressed size, file size, flag bits, ์์ถ ๋ฐฉ์, ๋ ์ง, ์์ฑ ์์คํ , ์ธ๋ถ/๋ด๋ถ ์์ฑ์ด ์๋ณธ๊ณผ ๊ฐ์์ผ ํ๋ค.
edit_hwpx.py ์ฌ์ฉ ๊ท์น
# ์ ์ฒด ํ
์คํธ ์นํ
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
--replace๋ ๋จผ์ ๊ฐ๋ณhp:t์์์ ์นํํด ๋ฐ ์์์ ์ต๋ํ ์ ์งํ๋ค.- ์นํ ๋์์ด ์ฌ๋ฌ ๋ฐ์ ๋๋์ด ์์ผ๋ฉด ๋ฌธ๋จ ๋จ์๋ก ํฉ์น๋, ์ฒซ ๋ฒ์งธ ๋ฐ์ ๋ฌด์กฐ๊ฑด ์ฐ์ง ์๋๋ค.
header.xml์charPr๋ฅผ ํ์ธํด ๋ณผ๋/์์/๋ฐ์ค ๊ฐ์ ๊ฐ์กฐ๊ฐ ์ ๊ณ , ํด๋น ๋ฌธ๋จ ์์์ ๋น๊ฐ์กฐ ํ ์คํธ๊ฐ ๊ฐ์ฅ ๋ง์ด ์ฐ๋ ๊ธ์ ๋์ด์ ๊ฐ๊น์ด ์ฃผ ๋ฐ์ ์ ํํ๋ค. --cell์ ํด๋นhp:tc์์ ์ฒซ ๋ฒ์งธ ๋ฐ ์์์ ์ ์งํ๊ณ ํ ์คํธ๋ง ๋ฃ๋๋ค.--paragraph/--paragraph-json์ ์ค๋ฌด ๋ณธ๋ฌธ ์ฌ์์ฑ์ฉ์ด๋ค. ๊ธฐ์กด ๋ฌธ์ฅ์ ์ฌ๋ฌ ๋ฐ์ ๊ธฐ๊ณ์ ์ผ๋ก ์ชผ๊ฐ์ง ์๊ณ ๋ฌธ๋จ์ ์ฃผ ํ ์คํธ ๋ฐ ํ๋์ ์์ฐ๋ฌธ์ผ๋ก ๋ฃ๋๋ค. ์ฃผ ๋ฐ์ ๊ธ์ ์๋ง ๋ณด์ง ์๊ณcharPrIDRef์ ๋ณผ๋/์์/๋ฐ์ค๊ณผ ๋ฌธ๋จ ๋ด ์ง๋ฐฐ์ ๋ณธ๋ฌธ ๋์ด๋ก ๊ณ ๋ฅธ๋ค.hh:strikeout shape="3D"๊ฐ์ ๊ฐ์ ์ผ๋ถ HWPX์์ ์ผ๋ฐ ๋ณธ๋ฌธ charPr์๋ ๋ถ์ด ์๋ค. ์ค์ ์ทจ์์ ๋ ๋๋ง์ด ๋ช ํํ์ง ์์ผ๋ฉด ๋จ๋ ์ผ๋ก ๊ฐ์กฐ/๋ฐฐ์ ๊ธฐ์ค์ผ๋ก ์ฐ์ง ์๋๋ค.--slot/--slot-json์ ๊ธฐ๋ณธ ์ธํฐํ์ด์ค๋ค. ์ฌ๋กฏ ํค๋hwpx_slots.py๊ฐ ์์ฑํ ๊ฒ๋ง ์ฌ์ฉํ๋ค.--paragraph๋์์ ์ง์ hp:run/hp:t๋ฅผ ๊ฐ์ง ์ค์ ํ ์คํธ ๋ฌธ๋จ์ด์ด์ผ ํ๋ค. ํ/๊ทธ๋ฆผ/ํ ์คํธ์์๋ฅผ ํ์ ์ปจํ ์ด๋ ๋ฌธ๋จ์ ์ง์ ์์ ํ์ง ์๋๋ค. ์ปจํ ์ด๋๋ฅผ ์์ ํ๋ฉด ๋ด๋ถ ํ ์คํธ์ ์ ํ ์คํธ๊ฐ ๊ฒน์ณ ๋ ๋๋ง๋๋ค.- ๋น ์
๋ ฅ ์
์ฒ๋ผ
hp:run์ ์์ผ๋hp:t๊ฐ ์๋ ๊ฒฝ์ฐ์๋ ๊ธฐ์กด ๋ฐ ์์hp:t๋ง ์ถ๊ฐํ๋ค. hp:t๋ด๋ถ์hp:fwSpace,hp:lineBreak, ๊ธฐํ ์์ ์ปจํธ๋กค์ ์ ๋ ์ญ์ ํ์ง ์๋๋ค. ํ ์คํธ๋ฅผ ๋น์ธ ๋๋ ์์ ํ๊ทธ๋ ์ ์งํ๊ณtext/tail๋ง ๋น์ด๋ค.- ํ
์คํธ๋ฅผ ๋ฐ๊พผ ๋ฌธ๋จ์
hp:linesegarray๋ ์ ๊ฑฐํ๋ค. ์ด ํ๊ทธ๋ ๋ฌธ๋จ ์ค ๋ฐฐ์น ์บ์๋ผ์ ๋ณธ๋ฌธ ์์ ๋ค ๋ณด์กดํ๋ฉด ํ์ปด์์ ์์/๋ณ์กฐ ๊ฒฝ๊ณ ๋ฅผ ๋ผ ์ ์๋ค. hp:tbl,hp:tr,hp:tc,cellAddr,cellSpan,cellSz,cellMargin,borderFillIDRef๋ ๋ณ๊ฒฝํ์ง ์๋๋ค.
์ค๋ฌด ์ ๋ ฅ ํ์ง ๊ท์น
๋ณธ๋ฌธ ๋ฌธ๋จ์ ๋ง์ถค๋ฒ๊ณผ ๋์ด์ฐ๊ธฐ๊ฐ ์ฐ์ ์ด๋ค. ์์์ ๋ณด์กดํ๋ ค๊ณ ์ ๋ถ๋ํ์ฅ์ ๊ฒ๊ณผ๊ณต๊ฐ๋ณด๊ณ ...์ฒ๋ผ ๊ณต๋ฐฑ์ ์ ๊ฑฐํ๊ฑฐ๋ ๋ฌธ์ฅ์ ์ค๊ฐ์์ ์๋ฅด์ง ์๋๋ค.
- ๋ณธ๋ฌธ ์ฌ์์ฑ์
--paragraph-json์ ์ฌ์ฉํ๋ค. - ๋ฌธ๋จ ์ธ๋ฑ์ค๋ฅผ ๊ณ ๋ฅผ ๋
analyze_template.py๋๋ ํ ์คํธ ์ถ์ถ ๊ฒฐ๊ณผ์์ ์ปจํ ์ด๋ ๋ฌธ๋จ์ด ์๋๋ผ ๋ด๋ถ ์ค์ ํ ์คํธ ๋ฌธ๋จ์ ๊ณ ๋ฅธ๋ค. ์๋ฅผ ๋ค์ด ์๋จ ํ ์ ์ฒด ๋ฌธ๋จ์ด ์๋๋ผ ํ ์ ์์ ๊ธฐ๊ด๋ช /์ ๋ชฉ ๋ฌธ๋จ์ ์์ ํ๋ค. - ๋ณธ๋ฌธ์ ์๋ณธ ๋ฌธ๋จ์ ๊ณต๋ฐฑ ์ ์ธ ๊ธ์ ์ ์ดํ๋ก ์งง๊ฒ ๋ค์ ์ด๋ค. ์๋ณธ๊ณผ ์ ํํ ๊ฐ์ ๊ธ์ ์๋ก ์ต์ง ๋ณด๊ฐํ์ง ์๋๋ค.
- ์ด๋ฆ, ๋ ์ง, ์ ํ๋ฒํธ, ์ง์ฑ , ๊ธ์ก, ์งง์ ํ ์ ๊ฐ์ ํ๋๋ง ์ ํ ๊ธธ์ด/์ ์์ฐ์ ์๊ฒฉ ์ ์ฉํ๋ค.
- ๊ณผ๋ํ๊ฒ ๊ธด ๋ฌด๊ณต๋ฐฑ ํ๊ธ ๋ฌธ์์ด์
edit_hwpx.py์ ๊ธฐ๋ณธ ํ์ง ๊ฐ๋์์ ์คํจํ๋ค. ์ ์ ๋ฌธ์ฅ์ด ์๋๋ผ๋ฉด ๊ฒฐ๊ณผ๋ฅผ ์ ์ถํ์ง ์๋๋ค. - ์๋ณธ ๋ฐ์ ๋ณผ๋/๊ฐ์กฐ๊ฐ ๋ณธ๋ฌธ ์ ๋ฌธ์ฅ ์ค๊ฐ์ ๋ฌป์ด ๋ค์ด๊ฐ๋ฉด ์คํจ๋ค. ๋ณธ๋ฌธ์
--paragraph๊ณ์ด๋ก ๋จ์ผ ์ฃผ ๋ฐ์ ๋ฃ๊ณ , ์ ๋ชฉ/์์ ๋ชฉ์ฒ๋ผ ์๋๋ ๊ฐ์กฐ๋ง ์๋ณธ ์คํ์ผ์ ์ ์งํ๋ค. - ์๋ณธ๋ณด๋ค ๊ธ์ ํฌ๊ธฐ๊ฐ ๋ฌ๋ผ ๋ณด์ด๋ฉด
header.xml์ด ๋ฐ๋ ๊ฒ์ธ์ง, ์ ํ ์คํธ๊ฐ ๋ค๋ฅธcharPrIDRef์ ๋ค์ด๊ฐ ๊ฒ์ธ์ง ๋จผ์ ๋น๊ตํ๋ค.header.xml์ด ๊ฐ์๋ 14pt ๋ณธ๋ฌธ ๋์ 12pt ๊ดํธ/๊ฐ์ฃผ run์ ์ ํํ๋ฉด ํ์ ์์ฒด๊ฐ ๋ฌ๋ผ์ง๋ค.
๊ธ์ ์์ฐ ๊ฐ๋ ๊ท์น
page_guard.py --write-budget๋ ์๋ณธ ์์์์ ๋ค์ ์์ฐ์ ๊ณ์ฐํ๋ค.
- ์ผ๋ฐ ๋ฌธ๋จ: ์ง์
hp:run/hp:tํ ์คํธ์ ๊ณต๋ฐฑ ์ ์ธ ๊ธ์ ์ - ํ ์
: ์
๋ด๋ถ ์ ์ฒด
hp:tํ ์คํธ์ ๊ณต๋ฐฑ ์ ์ธ ๊ธ์ ์์ ์ ํญ/๊ธ์ ํฌ๊ธฐ ๊ธฐ๋ฐ ์ถ์ ์์ฉ๋ ์ค ํฐ ๊ฐ - ๋น ์
๋ ฅ ์
: ๊ธฐ์กด ์
ํญ๊ณผ ์ฒซ ๋ฐ์
charPrIDRef๊ธ์ ํฌ๊ธฐ๋ฅผ ์ด์ฉํด ๋ณด์์ ์์ฉ๋ ์ฐ์
๊ฒฐ๊ณผ ๊ฒ์ฆ ์ --budget-profile์ ๋ฐ๋์ ์ฌ์ฉํ๋ค. ์ค๋ฌด ๋ณธ๋ฌธ ์ฌ์์ฑ ๊ฒฐ๊ณผ๋ --no-strict-paragraph-budget --skip-text-drift๋ฅผ ํจ๊ป ์ฌ์ฉํด ์๋ณธ ๊ธ์ ์ โ์ผ์นโ๊ฐ ์๋๋ผ ์์ฐ ์ดํ์ ๊ตฌ์กฐ ๋ณด์กด ์ฌ๋ถ๋ฅผ ๋ณธ๋ค. ํ ์
๊ณผ ์งง์ ์
๋ ฅ ํ๋๋ ์์ฐ์ ์ด๊ณผํ๋ฉด ์คํจํ๋ค. ์
๋ด๋ถ ๋ฌธ๋จ์ ์
์์ฐ์ผ๋ก ๊ฒ์ฌํ๋ฏ๋ก ๋น ์นธ ์
๋ ฅ์ด ๊ฐ๋ฅํ๋ค.
์ ์ฒด ๊ตฌ์กฐ fingerprint ๊ฐ๋ ๊ท์น
page_guard.py --write-structure๋ ์๋ณธ ์์์์ ๋ค์์ ์ ์ฅํ๋ค.
- ZIP ํจํค์ง ํ์ผ ๋ชฉ๋ก๊ณผ ์์
- ๊ฐ ์ํธ๋ฆฌ์ ์์ถ ๋ฐฉ์, ๋ ์ง, ์์ฑ ์์คํ , ๋ฒ์ , flag bits, ์ธ๋ถ/๋ด๋ถ ์์ฑ
- ๋ฐ์ด๋๋ฆฌ/๋ถ์ ํ์ผ์ SHA-256 ํด์
- ๋ชจ๋ XML ํ์ผ์ ํ๊ทธ๋ช , ์์ฑ, ๊ณ์ธต, ์์ ์์
hp:t์ ์ง์ ํ ์คํธ์hp:tํ์ ์ปจํธ๋กค์ tail ํ ์คํธ๋ ์ ๋ ฅ๊ฐ์ผ๋ก ๋ณด๊ณ ์ ์ธhp:t๋ด๋ถ์hp:fwSpace,hp:lineBreak๊ฐ์ ์์ ์ปจํธ๋กค ํ๊ทธ/์์๋ ๊ตฌ์กฐ fingerprint์ ํฌํจhp:linesegarray๋ ์ค ๋ฐฐ์น ์บ์๋ก ๋ณด๊ณ ๊ตฌ์กฐ fingerprint์์ ์ ์ธ
๊ฒฐ๊ณผ ๊ฒ์ฆ ์ --structure-profile์ ๋ฐ๋์ ์ฌ์ฉํ๋ค. hp:t ํ
์คํธ ์
๋ ฅ๊ณผ hp:linesegarray ์บ์ ์ ๊ฑฐ๋ฅผ ์ ์ธํ ํ๊ทธ/์์ฑ/ํ์ผ/์ด๋ฏธ์ง/๋ฉํ ๊ตฌ์กฐ๊ฐ ๋ฐ๋๋ฉด ์คํจํ๋ค. ํนํ hp:t ๋ด๋ถ ์ปจํธ๋กค์ด ์ญ์ ๋๋ฉด ์์ ๊ฐ๋ฅ์ฑ์ด ๋์ผ๋ฏ๋ก ์คํจํด์ผ ํ๋ค.
ZIP ์ํธ๋ฆฌ์ flag_bits๋ ์๋ณธ๊ณผ ๊ฐ์์ผ ํ๋ค. Python zipfile.writestr()๋ก ์ ์ฒด ์ํธ๋ฆฌ๋ฅผ ๋ค์ ์ฐ๋ฉด flag_bits์ ์์ถ ๋ฐ์ดํธ๊ฐ ๋ฐ๋ ์ ์์ผ๋ฏ๋ก ์ฌ์ฉํ์ง ์๋๋ค. ๋ฐ๋์ ์์ ZIP ๋ณต์ฌ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝ ๋์ ์ํธ๋ฆฌ๋ง ๊ต์ฒดํ๋ค.
edit_hwpx.py๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์
๋ ฅ ์ ๊ธ์์ ์์ฐ๊ณผ ๊ธฐ๋ณธ ๋ฌธ์ฅ ํ์ง์ ๊ฒ์ฌํ๋ค. --paragraph ๊ฐ์ ์๋ณธ ๋ฌธ๋จ ์์ฐ ์ดํ์ฌ์ผ ํ๊ณ , --cell ๊ฐ์ ์๋ณธ ์
์์ฐ์ ๋์ผ๋ฉด ์ ๋๋ค. --replace์ ์ ๊ฐ์ด ๊ธฐ์กด ์๋ฆฌ๋ณด๋ค ๊ธธ๋ฉด ์ฐ๊ธฐ ์ ์ ์คํจํ๋ค. --allow-over-budget์ ์ฌ์ฉ์๊ฐ ๋ช
์์ ์ผ๋ก ์ํ์ ๊ฐ์ํ ๋๋ง ์ด๋ค.
๋ด์ฉ ์์ฑ๋ ๊ฐ๋ ๊ท์น
content_guard.py๋ ๋ฌธ์๊ฐ ์ด๋ฆฌ๋์ง์ ๋ฌด๊ดํ๊ฒ ๊ฒฐ๊ณผ ํ
์คํธ๊ฐ ์์
์๋์ ์ผ์นํ๋์ง ๊ฒ์ฌํ๋ค. ๋ค์ ๊ฒฝ์ฐ์๋ ๋ฐ๋์ ๊ท์น ํ์ผ์ ๋ง๋ค์ด ์คํํ๋ค.
- ๋ฌธ์์ ๊ธฐ๊ด/๊ตญ๊ฐ/ํ์ฌ/๋ธ๋๋๋ฅผ ๋ฐ๊พธ๋ ๊ฒฝ์ฐ: ์ด์ ๋ช
์นญ, ์ด์ ๋ด๋น์, ์ด์ ์ ํ๋ฒํธ, ์ด์ ์ ์ฑ
๋ช
์
forbid์ ๋ฃ๋๋ค. - ์์ placeholder๋ฅผ ์ฑ์ฐ๋ ๊ฒฝ์ฐ:
โโ,{{name}},... . .๊ฐ์ ์์ฌ placeholder๋ฅผ ๊ธ์งํ๋ค. - ์ ๋ฌธ์ ๊ด์ ์ ํต์ฌ์ด๊ฐ ๋ฐ๋์ ์์ด์ผ ํ๋ ๊ฒฝ์ฐ: ์ ๊ธฐ๊ด๋ช
, ์ ๋ฒ์ธ๋ช
, ์ ๋ด๋น ๋ถ์ ๋ฑ์
require์ ๋ฃ๋๋ค. - ์ฌ๋ฌ ๋ถ์/์ฐธ๊ณ /๋ด๋น์ ํ๊ฐ ์๋ ๋ฌธ์์์๋ ์๋ถ๋ถ๋ง ๋ฐ๊พธ์ง ๋ง๊ณ ์ ์ฒด ํ
์คํธ ์ถ์ถ ๊ฒฐ๊ณผ์ ๋ํด
content_guard.py๋ฅผ ์คํํ๋ค. - โ๋ด์ฉ์ ์น ๋ฐ๊พธ๋ผโ, โ๋ฏธ๊ตญ์์ ์์ฑํ ๊ฒ์ฒ๋ผ ๋ฐ๊พธ๋ผโ์ฒ๋ผ ์ ๋ฉด ์ฌ์์ฑ ์์ฒญ์ด๋ฉด
--reference์--max-unchanged-ratio๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ค. ์๋ณธ์ ๊ธด ๋ฌธ์ฅ์ด ๋ง์ด ๋จ์ผ๋ฉด ๊ตฌ์กฐ๊ฐ ์ ์์ด์ด๋ ์คํจ๋ค.
์์:
{
"require": ["๋ฏธ๊ตญ๋
ธ๋๋ถ", "์ข์ ์ผ์๋ฆฌ"],
"forbid": ["๊ณ ์ฉ๋
ธ๋๋ถ", "๊น์ํ", "์ต์๋ฒ", "์ฅ์งํ", "044-"],
"forbid_regex": ["โโ+", "\\(\\s*\\.\\s*\\.\\s*\\.\\s*\\)"]
}
content_guard.py๊ฐ ์คํจํ๋ฉด ๊ตฌ์กฐ๊ฐ ์๋ฌด๋ฆฌ ์ ์์ด์ด๋ ๊ฒฐ๊ณผ๋ฅผ ์๋ฃ๋ก ์ ์ถํ์ง ์๋๋ค. ์คํจ ๋ชฉ๋ก์ ๋ณด๊ณ ๋จ์ ์ฌ๋กฏ์ ์ถ๊ฐ ์์ ํ๊ฑฐ๋, ๋ฌธ์ ์ ์ฒด ์ฌ์์ฑ ๋ฒ์๋ฅผ ๋ํ ๋ค ๋ค์ ๋น๋ํ๋ค.
ํ๊ฒฝ
# SKILL_DIR๋ ์ด SKILL.md๊ฐ ์์นํ ๋๋ ํ ๋ฆฌ์ ์ ๋ ๊ฒฝ๋ก๋ก ์ค์
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)" # ์คํฌ๋ฆฝํธ ๋ด์์
# ๋๋ Claude Code๊ฐ ์๋์ผ๋ก ์ฃผ์
ํ๋ base directory ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉ
# Python ๊ฐ์ํ๊ฒฝ (ํ๋ก์ ํธ์ ๋ง๊ฒ ์ค์ )
VENV="<ํ๋ก์ ํธ>/.venv/bin/activate"
๋ชจ๋ Python ์คํ ์:
# ํ๋ก์ ํธ์ .venv๋ฅผ ํ์ฑํ (pip install lxml ํ์)
source "$VENV"
๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ
.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 ์์ ๋ ํผ๋ฐ์ค
์ํฌํ๋ก์ฐ 1: XML-first ๋ฌธ์ ์์ฑ (๋ณด์กฐ ์ํฌํ๋ก์ฐ, ๋ ํผ๋ฐ์ค ํ์ผ์ด ์์ ๋๋ง)
ํ๋ฆ
- ํ ํ๋ฆฟ ์ ํ (base/gonmun/report/minutes/proposal)
- section0.xml ์์ฑ (๋ณธ๋ฌธ ๋ด์ฉ)
- (์ ํ) header.xml ์์ (์ ์คํ์ผ ์ถ๊ฐ ํ์ ์)
- build_hwpx.py๋ก ๋น๋
- validate.py๋ก ๊ฒ์ฆ
์์น: ์ฌ์ฉ์๊ฐ ๋ ํผ๋ฐ์ค HWPX๋ฅผ ์ ๊ณตํ ๊ฒฝ์ฐ์๋ ์ด ์ํฌํ๋ก์ฐ ๋์ ์๋จ์ "๊ธฐ๋ณธ ๋์ ๋ชจ๋(๋ ํผ๋ฐ์ค ๋ณต์ ์ฐ์ )"๋ฅผ ์ฌ์ฉํ๋ค.
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
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
์ค์ ํจํด: section0.xml์ ์ธ๋ผ์ธ ์์ฑ โ ๋น๋
# 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 ์์ฑ ๊ฐ์ด๋
ํ์ ๊ตฌ์กฐ
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 idhangul="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์ ํ: ๋ฒํธ + ์ ๋ชฉ):
<!-- borderFillIDRef="5" + charPrIDRef="10" โ ๋
น์๋ฐฐ๊ฒฝ ํฐ์ ๋ก๋ง์ซ์ -->
<!-- borderFillIDRef="6" + charPrIDRef="8" โ ํ์๋ฐฐ๊ฒฝ ๊ฒ์ ๋ณผ๋ ์ ๋ชฉ -->
์ํญ๋ชฉ ํค๋ (2์ ํ: ๋ฒํธ๋ฐฐ์ง + ์ ๋ชฉ):
<!-- borderFillIDRef="7" + charPrIDRef="11" โ ํ๋๋ฐฐ๊ฒฝ ํฐ์ ์๋ผ๋น์์ซ์ -->
<!-- borderFillIDRef="8" + charPrIDRef="8" โ ํ๋จ์ ๋ง ๊ฒ์ ๋ณผ๋ ์ ๋ชฉ -->
์ํฌํ๋ก์ฐ 2: ๊ธฐ์กด ๋ฌธ์ ํธ์ง (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 --pretty๋ ์ฌ๋์ด XML์ ์ฝ๊ธฐ ์ข๊ฒ ํ์ธํ ๋๋ง ์ฌ์ฉํ๋ค. HWPX์ hp:t๋ ํ
์คํธ์ hp:fwSpace, hp:lineBreak ๊ฐ์ ์์ ์ปจํธ๋กค์ด ์์ธ mixed content๋ฅผ ๊ฐ์ง ์ ์๊ณ , pretty-print๊ฐ ์ฝ์
ํ ์ค๋ฐ๊ฟ/๊ณต๋ฐฑ์ด ํ์ปด์์ ์ค์ ํ
์คํธ์ฒ๋ผ ๋ ๋๋ง๋ ์ ์๋ค. ๋ฐ๋ผ์ --pretty๋ก ํผ ๋๋ ํ ๋ฆฌ๋ฅผ ๋ค์ pack.py ์
๋ ฅ์ผ๋ก ์ฌ์ฉํ์ง ์๋๋ค.
์ํฌํ๋ก์ฐ 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๊ฐ ์์ ๋ ๊ธฐ๋ณธ ์ ์ฉ)
์ฌ์ฉ์๊ฐ ์ ๊ณตํ HWPX ํ์ผ์ ๋ถ์ํ์ฌ ๋์ผํ ๋ ์ด์์์ ๋ฌธ์๋ฅผ ์์ฑํ๋ ์ํฌํ๋ก์ฐ. ์ด ์คํฌ์์๋ ์ฒจ๋ถ ๋ ํผ๋ฐ์ค๊ฐ ์กด์ฌํ๋ฉด ๋ณธ ์ํฌํ๋ก์ฐ๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ๋ค.
ํ๋ฆ
- ๋ถ์ โ
analyze_template.py๋ก ๋ ํผ๋ฐ์ค ๋ฌธ์ ์ฌ์ธต ๋ถ์ - header.xml ์ถ์ถ โ ๋ ํผ๋ฐ์ค์ ์คํ์ผ ์ ์๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉ
- section0.xml ์์ฑ โ ๋ถ์ ๊ฒฐ๊ณผ์ ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ผ ์ ๋ด์ฉ์ผ๋ก ์์ฑ
- ๋น๋ โ ์ถ์ถํ header.xml + ์ section0.xml๋ก ๋น๋
- ๊ฒ์ฆ โ
validate.py - ์ชฝ์ ๊ฐ๋ โ
page_guard.py(์คํจ ์ ์ฌ์์ )
์ฌ์ฉ๋ฒ
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
๋ถ์ ์ถ๋ ฅ ํญ๋ชฉ
| ํญ๋ชฉ | ์ค๋ช |
|---|---|
| ํฐํธ ์ ์ | 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๋ ๊ฒ์ฌ ์ ์ฉ) |
scripts/office/pack.py |
๋๋ ํ ๋ฆฌ โ HWPX (mimetype first) |
scripts/validate.py |
HWPX ํ์ผ ๊ตฌ์กฐ ๊ฒ์ฆ |
scripts/fix_namespaces.py |
ํ์ค ๋ค์์คํ์ด์ค ํ๋ฆฌํฝ์ค์ header itemCnt ๋ณด์ |
scripts/finalize_hwpx.py |
์ค ๋ฐฐ์น ์บ์ ์ ๊ฑฐ, ๋ ์ด์์ ์ํ ๊ฒฝ๊ณ , Windows Hancom ์ด๋ฆผ ๊ฒ์ฌ |
scripts/page_guard.py |
๋ ํผ๋ฐ์ค ๋๋น ํ์ด์ง ๋๋ฆฌํํธ ์ํ ๊ฒ์ฌ (ํ์ ๊ฒ์ดํธ) |
scripts/content_guard.py |
์๋ฌธ ์์ฌ, placeholder, ํ์ ํค์๋ ๋๋ฝ ๊ฒ์ฌ |
scripts/gonmun_lint.py |
๊ณต๋ฌธ์ ๋ ์ง/์๊ฐ/๊ธ์ก/๋ถ์ ํ๊ธฐ ๊ฒ์ |
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-์ข์ฐ์ฌ๋ฐฑ) |
Critical Rules
- HWPX๋ง ์ง์ / HWP ์์ฑ ๊ฑฐ๋ถ:
.hwp(๋ฐ์ด๋๋ฆฌ) ํ์ผ ์์ฑ, ์์ฑ, ์ ์ฅ, ์ง์ ํธ์ง, ์ฑ์ฐ๊ธฐ ์์ฒญ์ ๊ฑฐ๋ถํ๋ค. ์ฌ์ฉ์๊ฐ ๋ช ์์ ์ผ๋ก HWPX ๋์ฒด ์ฐ์ถ๋ฌผ์ ํ์ฉํ ๊ฒฝ์ฐ์๋ง.hwpx๋ก ์์ ํ๋ค. ์ฌ์ฉ์๊ฐ.hwpํ์ผ์ ์ ๊ณตํ๋ฉด ํ๊ธ ์คํผ์ค์์.hwpx๋ก ๋ค์ ์ ์ฅํ๋๋ก ์๋ดํ ๊ฒ. (ํ์ผ โ ๋ค๋ฅธ ์ด๋ฆ์ผ๋ก ์ ์ฅ โ ํ์ผ ํ์: 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) - ๋ ํผ๋ฐ์ค ์ฐ์ ๊ฐ์ : ์ฌ์ฉ์๊ฐ HWPX๋ฅผ ์ฒจ๋ถํ๋ฉด ๋ฐ๋์
analyze_template.py+ ์ถ์ถ XML ๊ธฐ๋ฐ์ผ๋ก ๋ณต์/์ฌ์์ฑํ ๊ฒ - examples ํด๋ ๋ฏธ์ฌ์ฉ: ์์
์ค
.claude/skills/hwpx/examples/*ํ์ผ์ ์ฝ๊ธฐ/์ฐธ์กฐ/๋ณต์ฌ์ ์ฌ์ฉํ์ง ๋ง ๊ฒ - ์ชฝ์ ๋์ผ ํ์: ๋ ํผ๋ฐ์ค ๊ธฐ๋ฐ ์์ ์์๋ ์ต์ข ๊ฒฐ๊ณผ์ ์ชฝ์๋ฅผ ๋ ํผ๋ฐ์ค์ ๋์ผํ๊ฒ ์ ์งํ ๊ฒ
- ๋ฌด๋จ ํ์ด์ง ์ฆ๊ฐ ๊ธ์ง: ์ฌ์ฉ์ ๋ช ์ ์์ฒญ/์น์ธ ์์ด ์ชฝ์ ์ฆ๊ฐ๋ฅผ ์ ๋ฐํ๋ ๊ตฌ์กฐ ๋ณ๊ฒฝ ๊ธ์ง
- ๊ตฌ์กฐ ๋ณ๊ฒฝ ์ ํ: ์ฌ์ฉ์ ์์ฒญ์ด ์๋ ํ ๋ฌธ๋จ/ํ์ ์ถ๊ฐยท์ญ์ ยท๋ถํ ยท๋ณํฉ ๊ธ์ง (์นํ ์ค์ฌ ํธ์ง)
- page_guard ํ์ ํต๊ณผ:
validate.py์ ๋ณ๊ฐ๋กpage_guard.py๋ฅผ ๋ฐ๋์ ํต๊ณผํด์ผ ์๋ฃ ์ฒ๋ฆฌ