| name | hwpx-core |
| description | HWPX XML-first document authoring skill for create/edit/read/validate workflows and template-driven generation. Use when you need deterministic Korean document layout control via section0.xml/header.xml and script-based build pipelines instead of opaque editors. |
HWPX Core
HWPX XML-first ์คํฌ์
๋๋ค. ํต์ฌ ์์น์ section0.xml + header.xml์ ์ง์ ์ ์ดํ๊ณ ,
build_hwpx.py๋ก ๋ฌธ์๋ฅผ ์กฐ๋ฆฝํ ๋ค validate.py๋ก ๋ฌด๊ฒฐ์ฑ์ ํ์ธํ๋ ๊ฒ์
๋๋ค.
์์ธํ XML ์์ ํด์ค, ๊ณ ๊ธ ํ ์ฐ์ ์์, ์ฌํ ๋ค์์คํ์ด์ค ๋ ํผ๋ฐ์ค๋
$SKILL_DIR/references/๋ก ๋ถ๋ฆฌํด ์ ์งํฉ๋๋ค.
๊ธฐ๋ณธ ๋์ ๋ชจ๋ (ํ์): ์ฒจ๋ถ HWPX ๋ถ์ โ ๊ณ ์ XML ๋ณต์(99% ๊ทผ์ ) โ ์์ฒญ ๋ฐ์ ์ฌ์์ฑ
์ฌ์ฉ์๊ฐ .hwpx๋ฅผ ์ฒจ๋ถํ ๊ฒฝ์ฐ, ์ด ์คํฌ์ ์๋ ์์๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋ฐ๋ฅธ๋ค.
- ๋ ํผ๋ฐ์ค ํ๋ณด: ์ฒจ๋ถ๋ HWPX๋ฅผ ๊ธฐ์ค ๋ฌธ์๋ก ์ฌ์ฉ
- ์ฌ์ธต ๋ถ์/์ถ์ถ:
analyze_template.py๋ก header.xml, section0.xml ์ถ์ถ
- ๊ตฌ์กฐ ๋ณต์: header ์คํ์ผ ID/ํ ๊ตฌ์กฐ/์
๋ณํฉ/์ฌ๋ฐฑ/๋ฌธ๋จ ํ๋ฆ์ ์ต๋ํ ๋์ผํ๊ฒ ์ ์ง
- ์์ฒญ ๋ฐ์ ์ฌ์์ฑ: ์ฌ์ฉ์๊ฐ ์๊ตฌํ ํ
์คํธ/๋ฐ์ดํฐ๋ง ๊ต์ฒดํ๊ณ ๊ตฌ์กฐ๋ ๋ณด์กด
- ๋น๋/๊ฒ์ฆ:
build_hwpx.py + validate.py๋ก ๊ฒฐ๊ณผ ์ฐ์ถ ๋ฐ ๋ฌด๊ฒฐ์ฑ ํ์ธ
- ์ชฝ์ ๊ฐ๋(ํ์):
page_guard.py๋ก ๋ ํผ๋ฐ์ค ๋๋น ํ์ด์ง ๋๋ฆฌํํธ ์ํ ๊ฒ์ฌ
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๋ ํต๊ณผํด์ผ ํ๋ค
page_guard.py ์คํจ ์ ๊ฒฐ๊ณผ๋ฅผ ์๋ฃ๋ก ์ ์ถํ์ง ์๊ณ , ์์ธ(๊ธธ์ด ๊ณผ๋ค/๊ตฌ์กฐ ๋ณ๊ฒฝ)์ ์์ ํ ์ฌ๋น๋ํ๋ค
- ๊ฐ๋ฅํ๋ฉด ํ๊ธ(๋๋ ์ฌ์ฉ์์ ํ์ธ) ๊ธฐ์ค ์ต์ข
์ชฝ์ ๊ฐ์ ํ์ธํ๊ณ ๋ ํผ๋ฐ์ค์ ์ผ์น ์ฌ๋ถ๋ฅผ ์ฌํ์ธํ๋ค
๊ธฐ๋ณธ ์คํ ๋ช
๋ น (์ฒจ๋ถ ๋ ํผ๋ฐ์ค๊ฐ ์์ ๋)
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
python3 "$SKILL_DIR/scripts/page_guard.py" \
--reference reference.hwpx \
--output result.hwpx
๋๋ ํ ๋ฆฌ ๊ธฐ์ค
SKILL_DIR: SKILL.md๊ฐ ์์นํ /hwpx-generator:hwpx-core ๋๋ ํ ๋ฆฌ์ ์ ๋ ๊ฒฝ๋ก
- ์คํฌ๋ฆฝํธ:
$SKILL_DIR/scripts/
- ํ
ํ๋ฆฟ:
$SKILL_DIR/templates/
- ์ฌํ ๋ ํผ๋ฐ์ค:
$SKILL_DIR/references/
์คํฌ๋ฆฝํธ ์ฐธ์กฐ ๋ฐ ์คํ (CRITICAL)
์คํฌ๋ฆฝํธ๋ ์ด ์คํฌ์ ์๋๊ฒฝ๋ก๋ฅผ ๊ธฐ์ค์ผ๋ก ์ฐพ์ต๋๋ค.
Step 1. ์๋๊ฒฝ๋ก๋ก ์คํ (์ต์ฐ์ )
python scripts/build_hwpx.py --output result.hwpx
Step 2. ์๋๊ฒฝ๋ก ์คํจ ์ Glob ํด๋ฐฑ
Glob: **/hwpx-generator/skills/hwpx-core/scripts/build_hwpx.py
Step 3. Glob๋ ์คํจ ์ ํ์ฅ ํ์
Glob: **/build_hwpx.py
์ ๋ ๊ธ์ง: ์คํฌ๋ฆฝํธ๋ฅผ ์ฐพ์ง ๋ชปํ์ ๋ ์์ฒด Python ์ฝ๋๋ฅผ ์์ฑํ์ง ์์ต๋๋ค.
์ฆ์ ์ค๋จ ํ ๊ฒฝ๋ก ํ์ธ์ ์์ฒญํฉ๋๋ค.
์คํฌ๋ฆฝํธ ์์ฝ (13)
| Script | Purpose |
|---|
scripts/build_hwpx.py | ํ
ํ๋ฆฟ + XML ์ค๋ฒ๋ผ์ด๋๋ก .hwpx ์กฐ๋ฆฝ |
scripts/zip_surgery.py | ๊ธฐ์กด HWPX ์์ ํธ์ง (ZIP-level surgery, ๋ฐ์ดํธ ๋ ๋ฒจ ๋ณด์กด) |
scripts/cell_writer.py | stale linesegarray ์ ๊ฑฐ ์ ํธ๋ฆฌํฐ (์์ฑ ์ ํจ) |
scripts/analyze_template.py | ๋ ํผ๋ฐ์ค HWPX ๊ตฌ์กฐ/์คํ์ผ ๋ถ์ |
scripts/page_guard.py | ๋ ํผ๋ฐ์ค ๋๋น ํ์ด์ง ๋๋ฆฌํํธ ์ํ ๊ฒ์ฌ (ํ์ ๊ฒ์ดํธ) |
scripts/text_extract.py | ๋ณธ๋ฌธ/ํ ํ
์คํธ ์ถ์ถ |
scripts/validate.py | ZIP/XML/ํ์ ์ํธ๋ฆฌ ๊ตฌ์กฐ ๊ฒ์ฆ (--strict: surgery ํธํ์ฑ ๊ฒ์ฆ) |
scripts/office/unpack.py | HWPX๋ฅผ ๋๋ ํ ๋ฆฌ๋ก ํ์ด XML ํธ์ง ์ค๋น |
scripts/office/pack.py | ์์ ๋๋ ํ ๋ฆฌ๋ฅผ HWPX๋ก ์ฌํจํค์ง |
scripts/md_parser.py | ๋งํฌ๋ค์ด โ ๊ตฌ์กฐํ JSON ํ์ฑ (python3 md_parser.py <input.md> --output <output.json>) |
scripts/xml_writer.py | JSON โ HWPX XML ํ๋๊ทธ๋จผํธ ์์ฑ (python3 xml_writer.py --input <parsed.json> --style-config <styles.json> --output <fragment.xml>) |
scripts/image_embedder.py | HWPX์ ์ด๋ฏธ์ง ZIP-level ์๋ฒ ๋ฉ (python3 image_embedder.py --hwpx <.hwpx> --images-dir <dir> --mapping <map.json> --max-width <int> --quality <int> --output <out.hwpx>) |
scripts/proofread.py | ์ด์ค ๋ถ๋ฆฟ, ์ค๋ฐ๊ฟ ์ค๋ฅ, ์คํ์ผ ๋ฏธ์ ์ฉ ๋ฌธ๋จ ์๋ ๊ต์ |
scripts/md_merger.py | ๋ค์ค MD ํ์ผ ๋ณํฉ, heading offset ์๋ ๊ณ์ฐ |
scripts/form_mapper.py | HWPX ์์ ํ์
โ ๋น ์
/placeholder/๋ผ๋ฒจ ํ๋ณด ๊ฒฐ์ ์ ์ถ์ถ โ partial form_map.json |
scripts/slot_filler.py | paragraph-id ๊ธฐ๋ฐ ์ฌ๋กฏ ์ฑ์ฐ๊ธฐ โ id-scoped string surgery (zip_surgery ๋ถ๋ณ์ ์ค์) |
๋จ์ ๋ณํ (HWP Units)
| Item | Value | Note |
|---|
| 1 pt | 100 HWPUNIT | ํฐํธ/๋ฌธ๋จ ๊ธฐ๋ณธ ๋จ์ |
| 10 pt | 1000 HWPUNIT | ๊ธฐ๋ณธ ๋ณธ๋ฌธ ํฌ๊ธฐ ์์ |
| 1 mm | 283.5 HWPUNIT | ์ค๋ฌด ๊ทผ์ฌ์น |
| 1 cm | 2835 HWPUNIT | ์ค๋ฌด ๊ทผ์ฌ์น |
| A4 width | 59528 HWPUNIT | 210 mm |
| A4 height | 84186 HWPUNIT | 297 mm |
| Left/Right margin | 8504 HWPUNIT | 30 mm |
| Body width | 42520 HWPUNIT | 59528 - 8504 x 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์
ํ: ๋ฒํธ๋ฐฐ์ง + ์ ๋ชฉ):
์ธ๋ผ์ธ ์์ ๋ณํ์ฉ (์์ฝ ID, ์ ํ
ํ๋ฆฟ ๊ณตํต)
| Group | IDs | Meaning |
|---|
| charPr | 30 | ์ธ๋ผ์ธ ๋ณผ๋ (10pt, <hh:bold/>) |
| charPr | 31 | ์ธ๋ผ์ธ ์ดํค๋ฆญ (10pt, <hh:italic/>) |
| charPr | 32 | ์ธ๋ผ์ธ ๋ณผ๋+์ดํค๋ฆญ (10pt, <hh:bold/> + <hh:italic/>) |
| charPr | 33 | ์ธ๋ผ์ธ ๋ฐ์ค (10pt, <hh:underline type="BOTTOM"/>) |
| charPr | 34 | ์ธ๋ผ์ธ ์ทจ์์ (10pt, <hh:strikeout shape="SOLID"/>) |
Markdown-to-HWPX ์ธ๋ผ์ธ ์์ ๋ณํ
์
๋ ฅ ์ฝํ
์ธ ๊ฐ Markdown ํ์(.md ํ์ผ ๋๋ Markdown ๊ตฌ๋ฌธ ํฌํจ ํ
์คํธ)์ธ ๊ฒฝ์ฐ,
Markdown ์์ ๊ธฐํธ(**, *, ~~ ๋ฑ)๋ฅผ HWPX XML์ multi-run ๊ตฌ์กฐ๋ก ๋ณํํด์ผ ํ๋ค.
๋ณํ ๋งคํ
| Markdown | charPrIDRef | ์ค๋ช
|
|---|
**ํ
์คํธ** | 30 | ๋ณผ๋ |
*ํ
์คํธ* | 31 | ์ดํค๋ฆญ |
***ํ
์คํธ*** | 32 | ๋ณผ๋+์ดํค๋ฆญ |
<u>ํ
์คํธ</u> | 33 | ๋ฐ์ค |
~~ํ
์คํธ~~ | 34 | ์ทจ์์ |
| (์์) | 0 | ์ผ๋ฐ ๋ณธ๋ฌธ |
๋ณํ ์์น
- Markdown ๊ธฐํธ(
**, *, ~~, #, `, - , > )๋ <hp:t> ํ
์คํธ์ ํฌํจ์ํค์ง ์๋๋ค.
- ์์์ด ๋ฐ๋๋ ์ง์ ๋ง๋ค ๋ณ๋์
<hp:run>์ ์์ฑํ๋ค (multi-run ๋ถํ ).
- ์์ฝ charPr ID 30-34๋ ๋ชจ๋ ํ
ํ๋ฆฟ(base, gonmun, report, minutes, proposal)์ ๊ณตํต ์ ์๋์ด ์๋ค.
- ๋ธ๋ก ๋ ๋ฒจ Markdown(
#, -, > ๋ฑ)์ ํด๋น ๊ธฐํธ๋ฅผ ์ ๊ฑฐํ๊ณ ์ ์ ํ paraPrIDRef๋ก ๋ณํํ๋ค.
XML ์์: ํผํฉ ์์ ๋ฌธ๋จ
์
๋ ฅ: ์ฐ๊ตฌ ๊ฒฐ๊ณผ **์ ์๋ฏธํ** ์ฐจ์ด๊ฐ *๊ด์ฐฐ*๋์๋ค.
<hp:p id="..." paraPrIDRef="0" styleIDRef="0" pageBreak="0" columnBreak="0" merged="0">
<hp:run charPrIDRef="0">
<hp:t>์ฐ๊ตฌ ๊ฒฐ๊ณผ </hp:t>
</hp:run>
<hp:run charPrIDRef="30">
<hp:t>์ ์๋ฏธํ</hp:t>
</hp:run>
<hp:run charPrIDRef="0">
<hp:t> ์ฐจ์ด๊ฐ </hp:t>
</hp:run>
<hp:run charPrIDRef="31">
<hp:t>๊ด์ฐฐ</hp:t>
</hp:run>
<hp:run charPrIDRef="0">
<hp:t>๋์๋ค.</hp:t>
</hp:run>
</hp:p>
Markdown ์น์
์ฝ์
์ ํ
ํ๋ฆฟ ๊ตฌ์กฐ ์ธ์ ๊ท์น
๊ธฐ์กด HWPX์ ํน์ ์น์
์ Markdown ํ์ผ์ ๋ด์ฉ์ ์ฝ์
ํ ๋,
๋งํฌ๋ค์ด heading๊ณผ ํ
ํ๋ฆฟ sub-header์ ์ค๋ณต์ ๋ฐฉ์งํด์ผ ํ๋ค.
ํ๋ณ ์ ์ฐจ
-
๋์ ์น์
์ ๊ธฐ์กด ๋ฌธ๋จ ์ค, sub-header ํจํด์ ํด๋นํ๋ ๋ฌธ๋จ์ ์๋ณ
- ์กฐ๊ฑด: charPrIDRef๊ฐ ํค๋๊ธ ์คํ์ผ AND ํ
์คํธ ๊ธธ์ด 50์ ์ดํ
- ์: "3-1 ๋น์ ", "4-2. ์ธ๋ถ๊ธฐ์ 2", "๋ชฉํ ์ค์ ๊ทผ๊ฑฐ"
-
์
๋ ฅ ๋งํฌ๋ค์ด์ ##, ### ๋ ๋ฒจ heading์ ์ถ์ถ
-
์์ธก ํ
์คํธ๋ฅผ ์ ๊ทํํ์ฌ ๋น๊ต
- ์ ๊ทํ: strip(), ์ ํ ๋ฒํธ ํจํด ํต์ผ ("3-1" = "3.1" = "3 1")
- ๋งค์นญ: ์ ๊ทํ ๊ฒฐ๊ณผ๊ฐ ๋์ผํ๊ฑฐ๋, ํ์ชฝ์ด ๋ค๋ฅธ ์ชฝ์ ํฌํจ
์ฝ์
๊ท์น
| ์ํฉ | heading ์ฒ๋ฆฌ | body ์ฒ๋ฆฌ | placeholder ์ฒ๋ฆฌ |
|---|
| ๋งค์นญ๋จ | skip | ํ
ํ๋ฆฟ sub-header ๋ค์ ์ฝ์
| sub-header~๋ค์ sub-header ์ฌ์ด์ ๋น ๋ฌธ๋จ ์ญ์ |
| ๋ฏธ๋งค์นญ | ๋ณํํ์ฌ ์ฝ์
| heading ๋ค์ ์ฝ์
| ํด๋น ์์ |
๋น placeholder ๋ฌธ๋จ ํ๋ณ ๊ธฐ์ค
์ญ์ ๋์:
<hp:t/> (self-closing, ํ
์คํธ ์์)
<hp:t> ๋ด์ฉ์ด ๊ณต๋ฐฑ๋ง ํฌํจ
<hp:t> ๋ด์ฉ์ด ๋จ๋
๊ธฐํธ: โฆ, โ, โข, -, โป, ยท, โก, โ
๋ณด์กด ๋์:
- ์ค์ ํ
์คํธ ๋ด์ฉ์ด ์๋ ๋ฌธ๋จ (2๊ธ์ ์ด์์ ์๋ฏธ ์๋ ํ
์คํธ)
- ํ(hp:tbl)๋ฅผ ํฌํจํ ๋ฌธ๋จ (โ
์์ฑ์๋ น ํ๋ ๋ณ๋ ๊ท์น์ผ๋ก ์ญ์ )
Workflow 1. XML-first ๋ฌธ์ ์์ฑ (๋ณด์กฐ ์ํฌํ๋ก์ฐ, ๋ ํผ๋ฐ์ค ํ์ผ์ด ์์ ๋๋ง)
์์น: ์ฌ์ฉ์๊ฐ ๋ ํผ๋ฐ์ค HWPX๋ฅผ ์ ๊ณตํ ๊ฒฝ์ฐ์๋ ์ด ์ํฌํ๋ก์ฐ ๋์ ์๋จ์ "๊ธฐ๋ณธ ๋์ ๋ชจ๋(๋ ํผ๋ฐ์ค ๋ณต์ ์ฐ์ )"๋ฅผ ์ฌ์ฉํ๋ค.
ํ๋ฆ
- ํ
ํ๋ฆฟ ์ ํ (base/gonmun/report/minutes/proposal)
- section0.xml ์์ฑ (๋ณธ๋ฌธ ๋ด์ฉ)
- (์ ํ) header.xml ์์ (์ ์คํ์ผ ์ถ๊ฐ ํ์ ์)
- build_hwpx.py๋ก ๋น๋
- validate.py๋ก ๊ฒ์ฆ
๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
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"
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>
๋ถ๋ฆฟ/ํญ๋ชฉ ๋ฌธ๋จ (hanging indent)
hanging indent = paraPr์ left margin + ์์ indent (์ฒซ ์ค์ด ์ผ์ชฝ์ผ๋ก ๋์ถ).
๋ถ๋ฆฟ ๋ง์ปค (โฆ, โ, โก)๋ ์ฒซ ๋ฒ์งธ <hp:run>์, ๋ณธ๋ฌธ ํ
์คํธ๋ ํ์ <hp:run>์ ๋ฐฐ์น.
์ฌ๋ฐ๋ฅธ ์์:
<hp:p paraPrIDRef="24">
<hp:run charPrIDRef="0"><hp:t>โก </hp:t></hp:run>
<hp:run charPrIDRef="0"><hp:t>ํญ๋ชฉ ํ
์คํธ ๋ด์ฉ</hp:t></hp:run>
</hp:p>
๊ธ์ง ํจํด: ๊ณต๋ฐฑ ๋ฌธ์๋ก ๋ค์ฌ์ฐ๊ธฐํ์ง ์๋๋ค. ๋ฐ๋์ paraPr์ left/indent ์์ฑ ์ฌ์ฉ.
๋ถ๋ฆฟ ๊ณ์ธต ๋ ๋๋ง: idRef(๋ฌธ์) + hc:left(์ฌ๋ฐฑ) + level(์๋) + leftMargin override ์กฐํฉ
- paraPr 87: โฆ ์์ ๋ถ๋ฆฟ (left=1500)
- paraPr 88: - ํ์ ๋ถ๋ฆฟ (left=2500)
ํญ/๊ณต๋ฐฑ ๋ค์ฌ์ฐ๊ธฐ โ ๊ณ์ธต (๊ธ๋จธ๋ฆฌ ๋ฌธ์ยท์ซ์ ๊ณตํต): ๋งํฌ๋ค์ด์์ -/*/โฆ ๊ธ๋จธ๋ฆฌ๋ 1./a./(1)/โ ๋ฒํธ๋ , ์์ ๋ถ์ ๋ค์ฌ์ฐ๊ธฐ๋ก ๊ณ์ธต(indent_level)์ด ๊ฒฐ์ ๋๋ค. md_parser.py์ detect_indent_unit()์ด ๋ฌธ์๋ณ ์ต์ ๋ค์ฌ์ฐ๊ธฐ ๋จ์(๊ณต๋ฐฑ 2์นธ/4์นธยทํญ)๋ฅผ ์๋ ๊ฐ์งํ๊ณ expandtabs(4) ํ ๊ทธ ๋จ์๋ก ๋๋ ๋จ๊ณ๋ฅผ ๊ณ์ฐํ๋ฏ๋ก, ํญ 1๋ฒ = 1๋จ๊ณ์ด๋ฉฐ ๊ณต๋ฐฑ 2์นธยท4์นธ ์ด๋ ๋ค์ฌ์ฐ๊ธฐ ํญ์ด๋ 1๋จ๊ณ๋ก ์ผ๊ด ์ฒ๋ฆฌ๋๋ค. xml_writer.py์ resolve_indent_style()๊ฐ ๋จ๊ณ๋ณ bullet_level_N ์คํ์ผ์ ์ ์ฉํ๊ณ , ์ ์๋ ์ต๋ ๋ ๋ฒจ์ ๋์ผ๋ฉด HWPUNIT_PER_LEVEL(800)๋งํผ left_margin์ ๊ฐ์ฐํด ๋ฌธ๋จ ์ ์ฒด๋ฅผ ์ฐ์ธก์ผ๋ก ๋ฏผ๋ค. ๊ธ๋จธ๋ฆฌ ๋ง์ปค๋ LEVEL_MARKERS(โ โกโโโชโซโโ, 8๋จ๊ณ ์ํ)๋ก ๋จ๊ณ๋ง๋ค ์๋ ๊ต์ฒด๋์ด ๋ฌธ์ ์ ์ฒด์์ ๊ฐ์ ๋จ๊ณ=๊ฐ์ ๋ง์ปค๋ก ์ผ๊ด๋๋ค. ๋ฒํธ ํญ๋ชฉ(build_numbered)๋ ๊ธ๋จธ๋ฆฌ์ ๋์ผํ๊ฒ indent_level์ ๋ฐ์ํ๋ค.
ํ ์์ฑ๋ฒ (์ฐธ์กฐ ํ์ โ ํ๋ก๊ทธ๋๋ฐ ์์ฑ ์ xml_writer.py ์ฌ์ฉ ํ์)
์ค์: ์๋ XML ์์๋ HWPX ํ ๊ตฌ์กฐ๋ฅผ ์ดํดํ๊ธฐ ์ํ ์ฐธ์กฐ ํ์์ด๋ค.
ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ํ๋ฅผ ์์ฑํ ๋๋ ๋ฐ๋์ xml_writer.py์ build_table() / table_cell_xml() ํจ์๋ฅผ ์ฌ์ฉํ๋ค.
์์ด์ ํธ๊ฐ ์ง์ <hp:tbl> XML์ ์์ฑํ๋ฉด ๋ค์์คํ์ด์ค(hc: ์ค์ฉ), ์์ฑ ์์, ํ์ ์์ ๋๋ฝ ๋ฑ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
python3 xml_writer.py --input parsed.json --style-config styles.json --output fragment.xml
<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
ํ ๋ณํ ์์น (MDโHWPX)
- MD ์๋ณธ ๋ฐ์ดํฐ ๊ทธ๋๋ก: ๋งํฌ๋ค์ด ํ์ ์
๋ด์ฉ์ ๋ณํ ์์ด ์ฎ๊ธด๋ค
- ๊ธ์ง: ์
์ โ , โถ, โก ๋ฑ ์ฅ์ ๋ง์ปค๋ฅผ ์์ ์ถ๊ฐํ์ง ์๋๋ค
- ์ด ๋๋น ๊ณต์:
table_width / col_count ๊ท ๋ฑ ๋ถ๋ฐฐ (๋ ํผ๋ฐ์ค๊ฐ ์์ผ๋ฉด ๋ ํผ๋ฐ์ค ๋ฐ๋ฆ)
- ์: 3์ด ๊ท ๋ฑ โ
42520 / 3 โ 14173 + 14173 + 14174
- xml_writer.py ํ์: ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ํ๋ฅผ ์์ฑํ ๋๋ ๋ฐ๋์
xml_writer.py์ build_table() ํจ์๋ฅผ ํธ์ถํ๋ค. ์์ด์ ํธ๊ฐ ์ง์ <hp:tbl> XML์ ์์ฑํ์ง ์๋๋ค.
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" ์ ์ง
Workflow 2. ๊ธฐ์กด ๋ฌธ์ ํธ์ง (unpack โ Edit โ pack)
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
Workflow 3. ์ฝ๊ธฐ/ํ
์คํธ ์ถ์ถ
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
Workflow 4. ๊ฒ์ฆ
python3 "$SKILL_DIR/scripts/validate.py" document.hwpx
๊ฒ์ฆ ํญ๋ชฉ: ZIP ์ ํจ์ฑ, ํ์ ํ์ผ ์กด์ฌ, mimetype ๋ด์ฉ/์์น/์์ถ๋ฐฉ์, XML well-formedness
Workflow 5. ๋ ํผ๋ฐ์ค ๊ธฐ๋ฐ ๋ฌธ์ ์์ฑ (์ฒจ๋ถ HWPX๊ฐ ์์ ๋ ๊ธฐ๋ณธ ์ ์ฉ)
์ฌ์ฉ์๊ฐ ์ ๊ณตํ HWPX ํ์ผ์ ๋ถ์ํ์ฌ ๋์ผํ ๋ ์ด์์์ ๋ฌธ์๋ฅผ ์์ฑํ๋ ์ํฌํ๋ก์ฐ.
์ด ์คํฌ์์๋ ์ฒจ๋ถ ๋ ํผ๋ฐ์ค๊ฐ ์กด์ฌํ๋ฉด ๋ณธ ์ํฌํ๋ก์ฐ๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ๋ค.
ํ๋ฆ
- ๋ถ์ โ
analyze_template.py๋ก ๋ ํผ๋ฐ์ค ๋ฌธ์ ์ฌ์ธต ๋ถ์
- header.xml ์ถ์ถ โ ๋ ํผ๋ฐ์ค์ ์คํ์ผ ์ ์๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉ
- section0.xml ์์ฑ โ ๋ถ์ ๊ฒฐ๊ณผ์ ๊ตฌ์กฐ๋ฅผ ๋ฐ๋ผ ์ ๋ด์ฉ์ผ๋ก ์์ฑ
- ๋น๋ โ ์ถ์ถํ header.xml + ์ section0.xml๋ก ๋น๋
- ๊ฒ์ฆ โ
validate.py
- ์ชฝ์ ๊ฐ๋ โ
page_guard.py (์คํจ ์ ์ฌ์์ )
์ฌ์ฉ๋ฒ
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
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 ๋ณด์กด: ๋ถ์๋ ์
์ฌ๋ฐฑ ๊ฐ์ ๋์ผํ๊ฒ ์ ์ฉ
- ํ์ด์ง ์ฆ๊ฐ ๊ธ์ง: ์ฌ์ฉ์ ๋ช
์ ์น์ธ ์์ด ๊ฒฐ๊ณผ ์ชฝ์๋ฅผ ๋๋ฆฌ์ง ๋ง ๊ฒ
- ์นํ ์ฐ์ ํธ์ง: ์ ๋ฌธ๋จ/ํ ์ถ๊ฐ๋ณด๋ค ๊ธฐ์กด ํ
์คํธ ๋
ธ๋ ์นํ์ ์ฐ์ ํ ๊ฒ
Workflow 6. ZIP-Level Surgery (๊ธฐ์กด HWPX ์์ ํธ์ง)
ํต์ฌ: ๊ธฐ์กด HWPX ํ์ผ์ ๋ฐ์ดํธ ๋ ๋ฒจ ๋ฌด๊ฒฐ์ฑ์ ๋ณด์กดํ๋ฉด์ ๋ด์ฉ์ ์์ ํ๋ ์ ์ผํ๊ฒ ์์ ํ ๋ฐฉ๋ฒ.
์์ธ ๊ท์น: $SKILL_DIR/references/zip-surgery-guide.md ์ฐธ์กฐ.
์ธ์ ์ฌ์ฉํ๋๊ฐ
- ๊ธฐ์กด HWPX ํ์ผ์ ํ
์คํธ๋ฅผ ๊ต์ฒดํ ๋
- ๊ธฐ์กด HWPX ํ์ผ์ ๋ฌธ๋จ/ํ๋ฅผ ์ถ๊ฐ/์ญ์ ํ ๋
standalone='no', ๋ค์์คํ์ด์ค, ๊ฐํ ํ์์ ๋ณด์กดํด์ผ ํ ๋
์ฌ์ฉ๋ฒ
from zip_surgery import HwpxSurgeon
surgeon = HwpxSurgeon('document.hwpx')
surgeon.replace_text({"๊ธฐ์กด ํ
์คํธ": "์ ํ
์คํธ"})
children = surgeon.extract_children()
children.append(surgeon.make_paragraph('9999', '์ ๋ฌธ๋จ'))
surgeon.replace_children(children)
surgeon.save('output.hwpx')
errors = surgeon.validate('output.hwpx')
assert not errors, errors
CLI
python3 "$SKILL_DIR/scripts/zip_surgery.py" extract document.hwpx -o section0.xml
python3 "$SKILL_DIR/scripts/zip_surgery.py" replace document.hwpx -s new_section0.xml -o result.hwpx
python3 "$SKILL_DIR/scripts/zip_surgery.py" validate document.hwpx result.hwpx
์ ๋ ๊ธ์ง (ZIP-level surgery ํ)
cell_writer.py ์คํ ๊ธ์ง โ standalone/namespace/newline ํ๊ดด
ET.tostring() / tree.write() ์ฌ์ฉ ๊ธ์ง โ XML ์ ์ธ/๋ค์์คํ์ด์ค ๋ณ๊ฒฝ
- pretty-print / indent ๊ธ์ง โ ๊ฐํ์ด ํ
์คํธ ๋
ธ๋๋ก ํด์๋จ
Workflow 7. ๋งํฌ๋ค์ดโHWPX ํ
ํ๋ฆฟ ์ฑ์ฐ๊ธฐ
์ฌ์ฉ์๊ฐ ๋งํฌ๋ค์ด ๋ฌธ์์ HWPX ํ
ํ๋ฆฟ์ ์ ๊ณตํ์ ๋, ํ
ํ๋ฆฟ ์คํ์ผ์ ์ ์งํ๋ฉด์ ๋ด์ฉ์ ์ฑ์ ๋ฃ๋ ์ํฌํ๋ก์ฐ.
ํ๋ฆ
- ์คํ์ผ ์ถ์ถ โ
analyze_template.py <template.hwpx> --style-map styles.json
1.5. (๋ค์ค MD ํตํฉ) โ ์ฌ๋ฌ MD ํ์ผ์ด ์๋ ๊ฒฝ์ฐ md_merger.py ์คํ
- CLI:
python3 md_merger.py file1.md file2.md --target-level 2 --output merged.json
- ๋จ์ผ MD ํ์ผ์ด๋ฉด ์ด ๋จ๊ณ ์๋ต
- ๋งํฌ๋ค์ด ํ์ฑ โ
md_parser.py input.md --output parsed.json
- md_parser.py๊ฐ numbered list๋ฅผ
numbered_item ํ์
์ผ๋ก ํ์ฑํ์ฌ ๋ฒํธ ๋ง์ปค๋ฅผ ๋ณด์กดํจ
- indent_level: ๋ค์ฌ์ฐ๊ธฐ ๋ ๋ฒจ (2-space ๊ธฐ์ค, 0=์ต์์). bullet/numbered_item ๋ธ๋ก์ ์๋ ์ถ๊ฐ๋จ.
- bullet_level_N: style_config์ ๋ ๋ฒจ๋ณ ์คํ์ผ ํค (analyze_template.py --style-map์์ ์๋ ์ถ์ถ)
- ์์ ํ์
(Phase 2.5) โ
form_mapper.py๋ก partial form_map ์ถ์ถ โ hwpx-form-analyzer ์์ด์ ํธ๊ฐ ์๋ฏธ ๋งคํ(slot_type/zone/confidence) ์์ฑ โ ์ต์ข
form_map.json ์ฐ์ถ
- XML ์์ฑ โ
xml_writer.py --input parsed.json --style-config styles.json --output fragment.xml
- (form_map ๊ฒฝ๋ก) ์ฌ๋กฏ ์ฑ์ฐ๊ธฐ โ
slot_filler.py --hwpx {template} --fills {content_per_slot} --output {result} ; ์ฆํฅ ๋งคํ ์์ด form_map ์ฌ๋กฏ์๋ง ์ฝ์
- ์ฝ์
โ
zip_surgery.py replace template.hwpx -s fragment.xml -o result.hwpx
- ์ด๋ฏธ์ง ์๋ฒ ๋ฉ โ
image_embedder.py --hwpx result.hwpx --images-dir <dir> --mapping map.json --output final.hwpx
- ๊ฒ์ฆ โ
validate.py final.hwpx + page_guard.py --reference template.hwpx --output final.hwpx
CLI ์์
python3 "$SKILL_DIR/scripts/analyze_template.py" template.hwpx --style-map /tmp/styles.json
python3 "$SKILL_DIR/scripts/md_parser.py" input.md --output /tmp/parsed.json
python3 "$SKILL_DIR/scripts/xml_writer.py" \
--input /tmp/parsed.json --style-config /tmp/styles.json --output /tmp/fragment.xml
python3 "$SKILL_DIR/scripts/zip_surgery.py" replace template.hwpx \
-s /tmp/fragment.xml -o /tmp/result.hwpx
python3 "$SKILL_DIR/scripts/image_embedder.py" \
--hwpx /tmp/result.hwpx --images-dir ./images/ --mapping map.json --output final.hwpx
python3 "$SKILL_DIR/scripts/validate.py" final.hwpx
python3 "$SKILL_DIR/scripts/page_guard.py" --reference template.hwpx --output final.hwpx
ํ
ํ๋ฆฟ ์คํ์ผ ID ์์น
- ๋ฐ๋์
analyze_template.py --style-map ์ถ๋ ฅ์ ID๋ฅผ ์ฌ์ฉํ๋ค. ํ
ํ๋ฆฟ๋ง๋ค charPr/paraPr/borderFill ID ์ฒด๊ณ๊ฐ ๋ค๋ฅด๋ฏ๋ก ํ๋์ฝ๋ฉ ๊ธ์ง.
- ๋นํธ์ธ ์์ฝ ID (30-34)๋ XML-first ์ ์ฉ์ด๋ค. ํ
ํ๋ฆฟ ์ฑ์ฐ๊ธฐ์ ์ฌ์ฉ ๊ธ์ง โ ํ
ํ๋ฆฟ header.xml์ ํด๋น ID๊ฐ ์ ์๋์ด ์์ง ์์ ์ ์๋ค.
- XML ์ด์ค์ผ์ดํ ํ์:
& โ &, < โ <, > โ >
- ๋ฌธ๋จ ID:
9000000001๋ถํฐ ์์ฐจ ์ฆ๊ฐ (๊ธฐ์กด ํ
ํ๋ฆฟ ID์ ์ถฉ๋ ๋ฐฉ์ง)
์ด๋ฏธ์ง ์๋ฒ ๋ฉ ๊ฐ์ด๋
HWPX ์ด๋ฏธ์ง ๊ตฌ์กฐ๋ 2๊ณณ์ ๋ฑ๋กํ๋ค:
BinData/ ํด๋์ ์ค์ ์ด๋ฏธ์ง ํ์ผ (image1.png, image2.png, ...)
Contents/content.hpf์ <opf:item> ๋ฑ๋ก
Contents/section0.xml์ <hp:pic> ์์ ์ฝ์
header.xml binDataList ์ถ๊ฐ ๊ธ์ง: ๊ธฐ์กด binDataList๊ฐ ์์ผ๋ฉด ์ ๊ฑฐํ๋ค.
CLI:
python3 "$SKILL_DIR/scripts/image_embedder.py" \
--hwpx input.hwpx \
--images-dir ./images/ \
--mapping map.json \
--output output.hwpx
--mapping JSON ํ์: {"placeholder_id": "image_filename.png", ...}
--auto-map ์ต์
์ผ๋ก ํ๋ ์ด์คํ๋-์ด๋ฏธ์ง ์๋ ๋งค์นญ ๊ฐ๋ฅ.
--max-width INT: ์ต๋ ์ด๋ฏธ์ง ๋๋น(px). ์ด๊ณผ ์ ๋น์จ ์ ์ง ๋ฆฌ์ฌ์ด์ฆ. (๊ธฐ๋ณธ: ์์ถ ์์)
--quality INT: JPEG ํ์ง (0-100). (๊ธฐ๋ณธ: 85)
์ด๋ฏธ์ง ์๋ฒ ๋ฉ ํ์ ๊ท์น (CRITICAL)
| ๊ท์น | ์ค๋ช
|
|---|
| 2๊ณณ ๋ฑ๋ก | BinData/ + content.hpf์๋ง ๋ฑ๋ก. header.xml binDataList ์ถ๊ฐ ๊ธ์ง. ๊ธฐ์กด binDataList๊ฐ ์์ผ๋ฉด ์ ๊ฑฐ |
| binaryItemIDRef ํ์ | header.xml binDataList ๋ฏธ์ฌ์ฉ. section0.xml์ hc:img์์ binaryItemIDRef="imageN" ํ์ ์ฌ์ฉ (image1, image2, ...) |
| ์์ค ์ด๋ฏธ์ง ํฌ๋งท ๊ฒ์ฆ | .png ํ์ฅ์ ํ์ผ์ ์ค์ ํฌ๋งท์ด JPEG์ผ ์ ์์. image_embedder.py๊ฐ ์๋ ๊ฐ์ง/๋ณํ |
| orgSz = pixelร36 | orgSz = ์๋ณธ ์ด๋ฏธ์ง ํฝ์
ร 36 HWP units (200DPI ๊ธฐ์ค). ์: ์๋ณธ 1000ร800px โ orgSz=36000ร28800 |
| ์ด๋ฏธ์ง ๋์ด ์ํ | MAX_IMAGE_HEIGHT = 70000 HWP units (~247mm). ์ด๊ณผ ์ ์๋ฌ |
| BIN ID ํ์ | imageN ํ์ (image1, image2, ...). BIN0001 ํ์์ ์ฌ์ฉํ์ง ์์ |
| hp:pic ๋ฐ๋์ hp:run ์์ ์์น | hp:pic์ section-level sibling์ผ๋ก ๋ฐฐ์นํ๋ฉด ํ/๊ธ์ด ๋ ๋๋งํ์ง ์์. ๋ฐ๋์ <hp:p><hp:run>...</hp:run></hp:p> ๊ตฌ์กฐ ๋ด์ ์์นํด์ผ ํจ |
<hp:pic> ๊ฒ์ฆ๋ ๊ตฌ์กฐ (pypandoc-hwpx, python-hwpx, HwpForge ์ฐธ์กฐ)
์์ ์์๊ฐ ์ค์ํ๋ฉฐ, ํ/๊ธ์ ์ง๋ ฌํ ์์์ ์ผ์นํด์ผ ํ๋ค:
offset โ orgSz โ curSz โ flip โ rotationInfo โ renderingInfo โ hc:img โ
imgRect โ imgClip โ inMargin โ imgDim โ effects โ sz โ pos โ outMargin โ shapeComment
```xml
<hp:p>
<hp:run>
<hp:pic id="PIC_ID" instid="INST_ID" reverse="0"
numberingType="PICTURE" textWrap="TOP_AND_BOTTOM"
textFlow="BOTH_SIDES" lock="0" dropcapstyle="None"
href="" groupLevel="0">
<hp:offset x="0" y="0"/>
<hp:orgSz width="36000" height="28800"/>
<hp:curSz width="42520" height="34016"/>
<hp:flip horizontal="0" vertical="0"/>
<hp:rotationInfo angle="0" centerX="21260" centerY="17008" rotateimage="1"/>
<hp:renderingInfo>
<hc:transMatrix e1="1" e2="0" e3="0" e4="0" e5="1" e6="0"/>
<hc:scaMatrix e1="1.18111" e2="0" e3="0" e4="0" e5="1.18111" e6="0"/>
<hc:rotMatrix e1="1" e2="0" e3="0" e4="0" e5="1" e6="0"/>
</hp:renderingInfo>
<hc:img binaryItemIDRef="image1" bright="0" contrast="0"
effect="REAL_PIC" alpha="0"/>
<hp:imgRect>
<hc:pt0 x="0" y="0"/><hc:pt1 x="36000" y="0"/>
<hc:pt2 x="36000" y="28800"/><hc:pt3 x="0" y="28800"/>
</hp:imgRect>
<hp:imgClip left="0" right="75000" top="0" bottom="60000"/>
<hp:inMargin left="0" right="0" top="0" bottom="0"/>
<hp:imgDim dimwidth="75000" dimheight="60000"/>
<hp:effects/>
<hp:sz width="42520" widthRelTo="ABSOLUTE" height="34016"
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:shapeComment>image1.png 1000x800</hp:shapeComment>
</hp:pic>
</hp:run>
</hp:p>
header.xml binDataList (์ฌ์ฉํ์ง ์์)
๊ท์น: header.xml์ binDataList๋ฅผ ์ถ๊ฐํ์ง ์๋๋ค. ๊ธฐ์กด binDataList๊ฐ ์์ผ๋ฉด ์ ๊ฑฐํ๋ค.
์ด๋ฏธ์ง ๋ฑ๋ก์ BinData/ ํด๋ + content.hpf๋ง์ผ๋ก ์ถฉ๋ถํ๋ค.
ํ ํ ๋์ด ์๋ ์กฐ์ ๊ท์น (CRITICAL)
ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ์์ฑํ ํ๋ ๋ฐ๋์ ๋ค์ ์์ฑ์ ์ค์ ํด์ผ ํ๋ค:
<hp:tbl ... noAdjust="0" pageBreak="CELL">
| ์์ฑ | ํ์ ๊ฐ | ํจ๊ณผ |
|---|
noAdjust="0" | ํ์ | ์
๋ด์ฉ์ ๋ง์ถฐ ํ ๋์ด ์๋ ํ์ฅ |
noAdjust="1" | ๊ธ์ง | ๊ณ ์ ๋์ด โ ๋ด์ฉ ์๋ฆผ |
pageBreak="CELL" | ๊ถ์ฅ | ์
๋จ์ ํ์ด์ง ๋๊น ํ์ฉ |
pageBreak="NONE" | ๊ธ์ง | ํฐ ํ๊ฐ ํ ํ์ด์ง์ ๊ฐ์ ์์ถ |
๋จ๊ณ์ ๋๋ฒ๊น
์ ๋ต
HWPX ํ์ผ์ด ํ๊ธ์์ ์ด๋ฆฌ์ง ์์ ๋:
- ์ต์ ๋ณ๊ฒฝ ํ
์คํธ: ์ญ์ ๋ง ํ๊ณ ์ฝ์
์์ด ์ด๋ฆฌ๋์ง ํ์ธ
- ๋จ์ผ ์์ ํ
์คํธ: ๊ฐ๋จํ
<hp:p> 1๊ฐ๋ง ์ถ๊ฐํด์ ์ด๋ฆฌ๋์ง ํ์ธ
- ํ
์ด๋ธ ํ
์คํธ: ๊ฐ๋จํ
<hp:tbl> 1๊ฐ ์ถ๊ฐํด์ ์ด๋ฆฌ๋์ง ํ์ธ
- ์ ์ฒด ์ฝ์
ํ
์คํธ: ๋ชจ๋ ๋ด์ฉ ์ฝ์
- linesegarray ์ ๊ฑฐ ํ
์คํธ: linesegarray ์ ๊ฑฐ ์ ํ ๋น๊ต
๊ฐ ๋จ๊ณ์์ ์คํจํ๋ฉด, ํด๋น ๋จ๊ณ์ ๋ณ๊ฒฝ ๋ด์ฉ์ด ์์ธ์ด๋ค.
Critical Rules
- HWPX๋ง ์ง์:
.hwp(๋ฐ์ด๋๋ฆฌ) ํ์ผ์ ์ง์ํ์ง ์๋๋ค. ์ฌ์ฉ์๊ฐ .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 ์ ์์ ์ผ์น
- ๊ฒ์ฆ: ์์ฑ ํ ๋ฐ๋์
validate.py๋ก ๋ฌด๊ฒฐ์ฑ ํ์ธ
- ๋ ํผ๋ฐ์ค: ์์ธ XML ๊ตฌ์กฐ๋
$SKILL_DIR/references/hwpx-format.md ์ฐธ์กฐ
- build_hwpx.py ์ฐ์ : ์ ๋ฌธ์ ์์ฑ์ build_hwpx.py ์ฌ์ฉ (python-hwpx API ์ง์ ํธ์ถ ์ง์)
- ๋น ์ค:
<hp:t/> ์ฌ์ฉ (self-closing tag)
- ๋๋ ๋ด์ฉ ํ์ฅ ์: ๋ณธ ํ์ผ์ ์ ์ฐจ ์ค์ฌ์ผ๋ก ์ ์งํ๊ณ , ์ธ๋ถ ๋ํ/์คํค๋ง๋
references/์ ๋ถ๋ฆฌ
- ๋ ํผ๋ฐ์ค ์ฐ์ ๊ฐ์ : ์ฌ์ฉ์๊ฐ HWPX๋ฅผ ์ฒจ๋ถํ๋ฉด ๋ฐ๋์
analyze_template.py + ์ถ์ถ XML ๊ธฐ๋ฐ์ผ๋ก ๋ณต์/์ฌ์์ฑํ ๊ฒ
- examples ํด๋ ๋ฏธ์ฌ์ฉ: ์์
์ค
examples/* ํ์ผ์ ์ฝ๊ธฐ/์ฐธ์กฐ/๋ณต์ฌ์ ์ฌ์ฉํ์ง ๋ง ๊ฒ
- ์ชฝ์ ๋์ผ ํ์: ๋ ํผ๋ฐ์ค ๊ธฐ๋ฐ ์์
์์๋ ์ต์ข
๊ฒฐ๊ณผ์ ์ชฝ์๋ฅผ ๋ ํผ๋ฐ์ค์ ๋์ผํ๊ฒ ์ ์งํ ๊ฒ
- ๋ฌด๋จ ํ์ด์ง ์ฆ๊ฐ ๊ธ์ง: ์ฌ์ฉ์ ๋ช
์ ์์ฒญ/์น์ธ ์์ด ์ชฝ์ ์ฆ๊ฐ๋ฅผ ์ ๋ฐํ๋ ๊ตฌ์กฐ ๋ณ๊ฒฝ ๊ธ์ง
- ๊ตฌ์กฐ ๋ณ๊ฒฝ ์ ํ: ์ฌ์ฉ์ ์์ฒญ์ด ์๋ ํ ๋ฌธ๋จ/ํ์ ์ถ๊ฐยท์ญ์ ยท๋ถํ ยท๋ณํฉ ๊ธ์ง (์นํ ์ค์ฌ ํธ์ง)
- page_guard ํ์ ํต๊ณผ:
validate.py์ ๋ณ๊ฐ๋ก page_guard.py๋ฅผ ๋ฐ๋์ ํต๊ณผํด์ผ ์๋ฃ ์ฒ๋ฆฌ
- linesegarray ์ ๊ฑฐ: linesegarray๋ ํ/๊ธ์ด ์์ ํ๋ ๋ผ์ธ ๋ ์ด์์ ์บ์์ด๋ฏ๋ก ์์ฑ ํ์ดํ๋ผ์ธ์ ์ด๋ฅผ ์์ฑํ์ง ์๊ณ ์ ๊ฑฐํ๋ค. build_hwpx.pyยทpack.pyยทfix_namespaces.py ๋ชจ๋ ํจํค์ง ์
<hp:linesegarray>๋ฅผ ์ ๊ฑฐํ์ฌ ํ/๊ธ์ด ๋ฌธ์๋ฅผ ์ด ๋ ์ ํํ ๋ ์ด์์์ ์ฌ๊ณ์ฐํ๋๋ก ํ๋ค. ํด๋ฆฌ์คํฑ ์๋ ์์ฑ์ ์ค์ ๊ธ๋ฆฌํ ๋ฉํธ๋ฆญ๊ณผ ๋ถ์ผ์นํ์ฌ ์๊ฐ์ด ์ข์์ง๊ณ ํ ์ค์ ๋ญ์น๋ ๋ ๋๋ง ๋ฒ๊ทธ๋ฅผ ์ ๋ฐํ๋ฏ๋ก ๊ธ์งํ๋ค. section0.xml ์์ฑ ์ linesegarray๋ฅผ ํฌํจํ์ง ์๋๋ค.
- ZIP-level surgery ๊ท์น: ๊ธฐ์กด HWPX ํธ์ง ์
zip_surgery.py๋ฅผ ์ฌ์ฉํ๊ณ , ์์ธ ๊ท์น์ $SKILL_DIR/references/zip-surgery-guide.md ์ค์. ET.tostring()/tree.write() ์ฌ์ฉ ๊ธ์ง, ๊ฐํ ์ฝ์
๊ธ์ง, standalone='no' ๋ณด์กด ํ์.
- ํ ์์ฑ ํ์:
noAdjust="0" (ํ ๋์ด ์๋ ์กฐ์ ) + pageBreak="CELL" (ํ์ด์ง ๋๊น ํ์ฉ)
- validate.py --strict: ZIP-level surgery ๊ฒฐ๊ณผ๋ฌผ์
validate.py --strict๋ก ์ถ๊ฐ ๊ฒ์ฆ (standalone, xmlns, newlines, ํ ์์ฑ)
- lxml/ElementTree๋ก section XML ์ง๋ ฌํ ์ ๋ ๊ธ์ง: lxml์
etree.tostring()๊ณผ tree.write()๋ XML ์ ์ธ(<?xml ... ?>) ๋ค์ \n(๊ฐํ)์ ์ฝ์
ํ๋ค. ํ/๊ธ์ ์ด ๊ฐํ์ ํ
์คํธ ๋
ธ๋๋ก ํด์ํ์ฌ ํ์ผ์ ๊นจ๋จ๋ฆฐ๋ค. ์๋ณธ: ...yes"?><hs:sec(๊ฐํ 0๊ฐ) โ lxml: ...yes"?>\n<hs:sec(๊ฐํ 1๊ฐ). ๋ชจ๋ section XML ์์ฑ/์กฐ์์ ์์ ๋ฌธ์์ด ๊ธฐ๋ฐ ์คํฌ๋ฆฝํธ(xml_writer.py, zip_surgery.py)๋ง ์ฌ์ฉํ๋ค. ์์ด์ ํธ๊ฐ ์์ฒด ์ฝ๋์์ from lxml import etree ๋๋ import xml.etree.ElementTree๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๊ธ์งํ๋ค.
- ํ ์์ฑ ์ xml_writer.py ํ์: ํ(table) XML์ ๋ฐ๋์
xml_writer.py์ build_table() / table_cell_xml() ํจ์๋ก ์์ฑํ๋ค. ์์ด์ ํธ๊ฐ ์ง์ <hp:tbl> XML์ ์์ฑํ๊ฑฐ๋ generate_content.py ๋ฑ์ ์์ฒด ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ๋ ๊ฒ์ ๊ธ์งํ๋ค.
- ์ด๋ฏธ์ง 2๊ณณ ๋ฑ๋ก: ์ด๋ฏธ์ง๋ฅผ HWPX์ ์๋ฒ ๋ฉํ ๋๋
BinData/ + content.hpf 2๊ณณ์๋ง ๋ฑ๋กํ๋ค. header.xml binDataList ์ถ๊ฐ ๊ธ์ง. ๊ธฐ์กด binDataList๊ฐ ์์ผ๋ฉด ์ ๊ฑฐ. image_embedder.py๊ฐ ์๋ ์ฒ๋ฆฌํ๋ฏ๋ก ์ง์ ๋ฑ๋ก ๋ก์ง์ ์์ฑํ์ง ์๋๋ค.
- ์ด๋ฏธ์ง ํฌ๋งท ๊ฒ์ฆ:
.png ํ์ฅ์ ํ์ผ์ ์ค์ ํฌ๋งท์ด JPEG์ผ ์ ์๋ค (Gemini API ๋ฑ). image_embedder.py๊ฐ PIL๋ก ์๋ ๊ฐ์ง/๋ณํํ๋ฏ๋ก ๋ณ๋ ์ฒ๋ฆฌ ๋ถํ์.
- hp:pic ๊ตฌ์กฐ ์ง์ ์์ฑ ๊ธ์ง: hp:pic XML์ ์์ด์ ํธ๊ฐ ์ง์ ์์ฑํ์ง ์๋๋ค. ๋ฐ๋์
image_embedder.py์ make_pic_xml()์ ์ฌ์ฉํ๋ค. ๊ฒ์ฆ๋ ๊ตฌ์กฐ(pypandoc-hwpx/HwpForge)๋ฅผ ์ฌ์ฉํ๋ฉฐ, ์์ ์์๊ฐ ์ค์ํ๋ค.
- ํ
ํ๋ฆฟ ์ฑ์ฐ๊ธฐ ์ form_map.json ๊ธฐ๋ฐ ์ฌ๋กฏ ์ฝ์
ํ์: Workflow 7 / template-fill ๊ฒฝ๋ก์์ ์ฝํ
์ธ ์ฝ์
์ ๋ฐ๋์
form_mapper.py + hwpx-form-analyzer๊ฐ ์ฐ์ถํ form_map.json์ ์ฌ๋กฏ์๋ง ์ํํ๋ค. MDโํ
ํ๋ฆฟ ์์ญ์ ์ฆํฅ์ ๋งคํ ๊ฒฐ์ ์ ๊ธ์งํ๋ค.
- ๋น ์
์ ์ญ ์นํ ๊ธ์ง:
<hp:t/> ๋๋ ๋น ๋ฐ์ global str.replace()๋ก ์นํํ์ง ์๋๋ค. ์ฌ๋ฌ ๋น ์
์ด ๋ฐ์ดํธ-๋์ผํ๋ฏ๋ก ์ ์ญ ์นํ์ ๋ชจ๋ ์
์ ์์์ํจ๋ค. ๋ฐ๋์ slot_filler.py์ paragraph-id ์ค์ฝํ ์นํ์ ์ฌ์ฉํ๋ค.
๋น ๋ฅธ ์คํ ์์
python3 "$SKILL_DIR/scripts/build_hwpx.py" --template base --output quick.hwpx
python3 "$SKILL_DIR/scripts/text_extract.py" quick.hwpx --format markdown
python3 "$SKILL_DIR/scripts/validate.py" quick.hwpx