From hwpx-generator
Creates, edits, reads, and validates HWPX Korean documents via XML extraction from section0.xml/header.xml, Python script assembly, and page drift checks to preserve layout.
npx claudepluginhub orientpine/honeypot --plugin hwpx-generatorThis skill uses the workspace's default tool permissions.
HWPX XML-first ์คํฌ์ ๋๋ค. ํต์ฌ ์์น์ `section0.xml` + `header.xml`์ ์ง์ ์ ์ดํ๊ณ ,
references/hwpx-format.mdreferences/zip-surgery-guide.mdscripts/analyze_template.pyscripts/build_hwpx.pyscripts/cell_writer.pyscripts/image_embedder.pyscripts/md_merger.pyscripts/md_parser.pyscripts/office/pack.pyscripts/office/unpack.pyscripts/page_guard.pyscripts/proofread.pyscripts/section_transplant.pyscripts/text_extract.pyscripts/validate.pyscripts/xml_writer.pyscripts/zip_surgery.pytemplates/base/Contents/content.hpftemplates/base/Contents/header.xmltemplates/base/Contents/section0.xmlGenerates HWPX documents from user-uploaded or default templates using ZIP-level XML text replacement and ObjectFinder surveys. Use for quick report and official document creation.
Creates, reads, edits, analyzes .docx files using docx-js for JS generation, pandoc for extraction, Python unpack/edit/repack, LibreOffice for conversions and tracked changes.
Creates, reads, edits, and analyzes .docx files using docx-js for generation, pandoc/Python for extraction, XML editing, tracked changes, and LibreOffice conversions.
Share bugs, ideas, or general feedback.
HWPX XML-first ์คํฌ์
๋๋ค. ํต์ฌ ์์น์ section0.xml + header.xml์ ์ง์ ์ ์ดํ๊ณ ,
build_hwpx.py๋ก ๋ฌธ์๋ฅผ ์กฐ๋ฆฝํ ๋ค validate.py๋ก ๋ฌด๊ฒฐ์ฑ์ ํ์ธํ๋ ๊ฒ์
๋๋ค.
์์ธํ XML ์์ ํด์ค, ๊ณ ๊ธ ํ ์ฐ์ ์์, ์ฌํ ๋ค์์คํ์ด์ค ๋ ํผ๋ฐ์ค๋
$SKILL_DIR/references/๋ก ๋ถ๋ฆฌํด ์ ์งํฉ๋๋ค.
์ฌ์ฉ์๊ฐ .hwpx๋ฅผ ์ฒจ๋ถํ ๊ฒฝ์ฐ, ์ด ์คํฌ์ ์๋ ์์๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋ฐ๋ฅธ๋ค.
analyze_template.py๋ก header.xml, section0.xml ์ถ์ถbuild_hwpx.py + validate.py๋ก ๊ฒฐ๊ณผ ์ฐ์ถ ๋ฐ ๋ฌด๊ฒฐ์ฑ ํ์ธpage_guard.py๋ก ๋ ํผ๋ฐ์ค ๋๋น ํ์ด์ง ๋๋ฆฌํํธ ์ํ ๊ฒ์ฌcharPrIDRef, paraPrIDRef, borderFillIDRef ์ฐธ์กฐ ์ฒด๊ณ ๋์ผrowCnt, colCnt, colSpan, rowSpan, cellSz, cellMargin ๋์ผhp:p, hp:tbl, rowCnt, colCnt, pageBreak, secPr๋ฅผ ๋ณ๊ฒฝํ์ง ์๋๋คvalidate.py ํต๊ณผ๋ง์ผ๋ก ์๋ฃ ์ฒ๋ฆฌํ์ง ์๋๋ค. ๋ฐ๋์ page_guard.py๋ ํต๊ณผํด์ผ ํ๋คpage_guard.py ์คํจ ์ ๊ฒฐ๊ณผ๋ฅผ ์๋ฃ๋ก ์ ์ถํ์ง ์๊ณ , ์์ธ(๊ธธ์ด ๊ณผ๋ค/๊ตฌ์กฐ ๋ณ๊ฒฝ)์ ์์ ํ ์ฌ๋น๋ํ๋ค# 1) ๋ ํผ๋ฐ์ค ๋ถ์ + XML ์ถ์ถ
python3 "$SKILL_DIR/scripts/analyze_template.py" reference.hwpx \
--extract-header /tmp/ref_header.xml \
--extract-section /tmp/ref_section.xml
# 2) /tmp/ref_section.xml์ ๋ณต์ ํด /tmp/new_section0.xml ์์ฑ
# (๊ตฌ์กฐ ์ ์ง, ํ
์คํธ/๋ฐ์ดํฐ๋ง ์์ฒญ์ ๋ง๊ฒ ์์ )
# 3) ๋ณต์ ๋น๋
python3 "$SKILL_DIR/scripts/build_hwpx.py" \
--header /tmp/ref_header.xml \
--section /tmp/new_section0.xml \
--output result.hwpx
# 4) ๊ฒ์ฆ
python3 "$SKILL_DIR/scripts/validate.py" result.hwpx
# 5) ์ชฝ์ ๋๋ฆฌํํธ ๊ฐ๋ (ํ์)
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/์คํฌ๋ฆฝํธ๋ ์ด ์คํฌ์ ์๋๊ฒฝ๋ก๋ฅผ ๊ธฐ์ค์ผ๋ก ์ฐพ์ต๋๋ค.
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 ์ฝ๋๋ฅผ ์์ฑํ์ง ์์ต๋๋ค. ์ฆ์ ์ค๋จ ํ ๊ฒฝ๋ก ํ์ธ์ ์์ฒญํฉ๋๋ค.
| Script | Purpose |
|---|---|
scripts/build_hwpx.py | ํ
ํ๋ฆฟ + XML ์ค๋ฒ๋ผ์ด๋๋ก .hwpx ์กฐ๋ฆฝ |
scripts/zip_surgery.py | ๊ธฐ์กด HWPX ์์ ํธ์ง (ZIP-level surgery, ๋ฐ์ดํธ ๋ ๋ฒจ ๋ณด์กด) |
scripts/cell_writer.py | linesegarray ์์ฑ + ์ /ํ ์ด๋ธ ๋์ด ์๋ ์กฐ์ (XML-first/pack ์ ์ฉ) |
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 ์๋ ๊ณ์ฐ |
| 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 | ์ ํ | ์ค๋ช |
|---|---|---|
| charPr 0 | ๊ธ์ | 10pt ํจ์ด๋กฌ๋ฐํ, ๊ธฐ๋ณธ |
| charPr 1 | ๊ธ์ | 10pt ํจ์ด๋กฌ๋์ |
| charPr 2~6 | ๊ธ์ | Skeleton ๊ธฐ๋ณธ ์คํ์ผ |
| paraPr 0 | ๋ฌธ๋จ | JUSTIFY, 160% ์ค๊ฐ๊ฒฉ |
| paraPr 1~19 | ๋ฌธ๋จ | Skeleton ๊ธฐ๋ณธ (๊ฐ์, ๊ฐ์ฃผ ๋ฑ) |
| borderFill 1 | ํ ๋๋ฆฌ | ์์ (ํ์ด์ง ๋ณด๋) |
| borderFill 2 | ํ ๋๋ฆฌ | ์์ + ํฌ๋ช ๋ฐฐ๊ฒฝ (์ฐธ์กฐ์ฉ) |
| 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 ๋ฐฐ๊ฒฝ |
| 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")๋ก ์๋จ ๊ตต์์ + ํ๋จ ์์์ ์๋ ํ์.
| 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 ๋ฐฐ๊ฒฝ |
์๊ฐ์ ๊ตฌ๋ถ์ด ํ์ํ ๊ณต์ ๋ฌธ์์ฉ. ์์ ๋ฐฐ๊ฒฝ ํค๋๋ฐ์ ๋ฒํธ ๋ฐฐ์ง๋ฅผ ํ(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 (์ํญ๋ชฉ ์ ๋ชฉ ์์ญ) |
๋ํญ๋ชฉ ํค๋ (2์ ํ: ๋ฒํธ + ์ ๋ชฉ):
<!-- borderFillIDRef="5" + charPrIDRef="10" โ ๋
น์๋ฐฐ๊ฒฝ ํฐ์ ๋ก๋ง์ซ์ -->
<!-- borderFillIDRef="6" + charPrIDRef="8" โ ํ์๋ฐฐ๊ฒฝ ๊ฒ์ ๋ณผ๋ ์ ๋ชฉ -->
์ํญ๋ชฉ ํค๋ (2์ ํ: ๋ฒํธ๋ฐฐ์ง + ์ ๋ชฉ):
<!-- borderFillIDRef="7" + charPrIDRef="11" โ ํ๋๋ฐฐ๊ฒฝ ํฐ์ ์๋ผ๋น์์ซ์ -->
<!-- borderFillIDRef="8" + charPrIDRef="8" โ ํ๋จ์ ๋ง ๊ฒ์ ๋ณผ๋ ์ ๋ชฉ -->
| 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 ํ์(.md ํ์ผ ๋๋ Markdown ๊ตฌ๋ฌธ ํฌํจ ํ
์คํธ)์ธ ๊ฒฝ์ฐ,
Markdown ์์ ๊ธฐํธ(**, *, ~~ ๋ฑ)๋ฅผ HWPX XML์ multi-run ๊ตฌ์กฐ๋ก ๋ณํํด์ผ ํ๋ค.
| Markdown | charPrIDRef | ์ค๋ช |
|---|---|---|
**ํ
์คํธ** | 30 | ๋ณผ๋ |
*ํ
์คํธ* | 31 | ์ดํค๋ฆญ |
***ํ
์คํธ*** | 32 | ๋ณผ๋+์ดํค๋ฆญ |
<u>ํ
์คํธ</u> | 33 | ๋ฐ์ค |
~~ํ
์คํธ~~ | 34 | ์ทจ์์ |
| (์์) | 0 | ์ผ๋ฐ ๋ณธ๋ฌธ |
**, *, ~~, #, `, - , > )๋ <hp:t> ํ
์คํธ์ ํฌํจ์ํค์ง ์๋๋ค.<hp:run>์ ์์ฑํ๋ค (multi-run ๋ถํ ).#, -, > ๋ฑ)์ ํด๋น ๊ธฐํธ๋ฅผ ์ ๊ฑฐํ๊ณ ์ ์ ํ paraPrIDRef๋ก ๋ณํํ๋ค.์
๋ ฅ: ์ฐ๊ตฌ ๊ฒฐ๊ณผ **์ ์๋ฏธํ** ์ฐจ์ด๊ฐ *๊ด์ฐฐ*๋์๋ค.
<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>
๊ธฐ์กด HWPX์ ํน์ ์น์ ์ Markdown ํ์ผ์ ๋ด์ฉ์ ์ฝ์ ํ ๋, ๋งํฌ๋ค์ด heading๊ณผ ํ ํ๋ฆฟ sub-header์ ์ค๋ณต์ ๋ฐฉ์งํด์ผ ํ๋ค.
๋์ ์น์ ์ ๊ธฐ์กด ๋ฌธ๋จ ์ค, sub-header ํจํด์ ํด๋นํ๋ ๋ฌธ๋จ์ ์๋ณ
์
๋ ฅ ๋งํฌ๋ค์ด์ ##, ### ๋ ๋ฒจ heading์ ์ถ์ถ
์์ธก ํ ์คํธ๋ฅผ ์ ๊ทํํ์ฌ ๋น๊ต
| ์ํฉ | heading ์ฒ๋ฆฌ | body ์ฒ๋ฆฌ | placeholder ์ฒ๋ฆฌ |
|---|---|---|---|
| ๋งค์นญ๋จ | skip | ํ ํ๋ฆฟ sub-header ๋ค์ ์ฝ์ | sub-header~๋ค์ sub-header ์ฌ์ด์ ๋น ๋ฌธ๋จ ์ญ์ |
| ๋ฏธ๋งค์นญ | ๋ณํํ์ฌ ์ฝ์ | heading ๋ค์ ์ฝ์ | ํด๋น ์์ |
์ญ์ ๋์:
<hp:t/> (self-closing, ํ
์คํธ ์์)<hp:t> ๋ด์ฉ์ด ๊ณต๋ฐฑ๋ง ํฌํจ<hp:t> ๋ด์ฉ์ด ๋จ๋
๊ธฐํธ: โฆ, โ, โข, -, โป, ยท, โก, โ ๋ณด์กด ๋์:
์์น: ์ฌ์ฉ์๊ฐ ๋ ํผ๋ฐ์ค HWPX๋ฅผ ์ ๊ณตํ ๊ฒฝ์ฐ์๋ ์ด ์ํฌํ๋ก์ฐ ๋์ ์๋จ์ "๊ธฐ๋ณธ ๋์ ๋ชจ๋(๋ ํผ๋ฐ์ค ๋ณต์ ์ฐ์ )"๋ฅผ ์ฌ์ฉํ๋ค.
# ๋น ๋ฌธ์ (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
# 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์ ์ฒซ ๋ฌธ๋จ(<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 = paraPr์ left margin + ์์ indent (์ฒซ ์ค์ด ์ผ์ชฝ์ผ๋ก ๋์ถ).
๋ถ๋ฆฟ ๋ง์ปค (โฆ, โ, โก)๋ ์ฒซ ๋ฒ์งธ <hp:run>์, ๋ณธ๋ฌธ ํ
์คํธ๋ ํ์ <hp:run>์ ๋ฐฐ์น.
์ฌ๋ฐ๋ฅธ ์์:
<hp:p paraPrIDRef="24"> <!-- left="600" indent="-300" โ hanging indent -->
<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 ์กฐํฉ
์ค์: ์๋ 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>
table_width / col_count ๊ท ๋ฑ ๋ถ๋ฐฐ (๋ ํผ๋ฐ์ค๊ฐ ์์ผ๋ฉด ๋ ํผ๋ฐ์ค ๋ฐ๋ฆ)42520 / 3 โ 14173 + 14173 + 14174xml_writer.py์ build_table() ํจ์๋ฅผ ํธ์ถํ๋ค. ์์ด์ ํธ๊ฐ ์ง์ <hp:tbl> XML์ ์์ฑํ์ง ์๋๋ค.1000000001๋ถํฐ ์์ฐจ ์ฆ๊ฐ1000000099 ๋ฑ ๋ณ๋ ๋ฒ์ ์ฌ์ฉ ๊ถ์ฅtemplates/base/Contents/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" โ ํจ์ด๋กฌ๋์ (๊ณ ๋)hangul="1" โ ํจ์ด๋กฌ๋ฐํ (๋ช
์กฐ)hp:switch ๊ตฌ์กฐ ํฌํจ (hp:case + hp:default)hp:case์ hp:default์ ๊ฐ์ ๋ณดํต ๋์ผ (๋๋ default๊ฐ 2๋ฐฐ)borderFillIDRef="2" ์ ์ง# 1. HWPX โ ๋๋ ํ ๋ฆฌ (XML pretty-print)
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
# ์์ ํ
์คํธ
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
python3 "$SKILL_DIR/scripts/validate.py" document.hwpx
๊ฒ์ฆ ํญ๋ชฉ: ZIP ์ ํจ์ฑ, ํ์ ํ์ผ ์กด์ฌ, mimetype ๋ด์ฉ/์์น/์์ถ๋ฐฉ์, XML well-formedness
์ฌ์ฉ์๊ฐ ์ ๊ณตํ HWPX ํ์ผ์ ๋ถ์ํ์ฌ ๋์ผํ ๋ ์ด์์์ ๋ฌธ์๋ฅผ ์์ฑํ๋ ์ํฌํ๋ก์ฐ. ์ด ์คํฌ์์๋ ์ฒจ๋ถ ๋ ํผ๋ฐ์ค๊ฐ ์กด์ฌํ๋ฉด ๋ณธ ์ํฌํ๋ก์ฐ๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ๋ค.
analyze_template.py๋ก ๋ ํผ๋ฐ์ค ๋ฌธ์ ์ฌ์ธต ๋ถ์validate.pypage_guard.py (์คํจ ์ ์ฌ์์ )# 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 + ๋ด์ฉ |
ํต์ฌ: ๊ธฐ์กด HWPX ํ์ผ์ ๋ฐ์ดํธ ๋ ๋ฒจ ๋ฌด๊ฒฐ์ฑ์ ๋ณด์กดํ๋ฉด์ ๋ด์ฉ์ ์์ ํ๋ ์ ์ผํ๊ฒ ์์ ํ ๋ฐฉ๋ฒ. ์์ธ ๊ท์น:
$SKILL_DIR/references/zip-surgery-guide.md์ฐธ์กฐ.
standalone='no', ๋ค์์คํ์ด์ค, ๊ฐํ ํ์์ ๋ณด์กดํด์ผ ํ ๋from zip_surgery import HwpxSurgeon
surgeon = HwpxSurgeon('document.hwpx')
# ๋ฐฉ๋ฒ 1: ํ
์คํธ ์นํ (๊ตฌ์กฐ ์ ์ง)
surgeon.replace_text({"๊ธฐ์กด ํ
์คํธ": "์ ํ
์คํธ"})
# ๋ฐฉ๋ฒ 2: ์์ ์์ ํธ์ง (๊ตฌ์กฐ ๋ณ๊ฒฝ)
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
# ์ถ์ถ
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
cell_writer.py ์คํ ๊ธ์ง โ standalone/namespace/newline ํ๊ดดET.tostring() / tree.write() ์ฌ์ฉ ๊ธ์ง โ XML ์ ์ธ/๋ค์์คํ์ด์ค ๋ณ๊ฒฝ์ฌ์ฉ์๊ฐ ๋งํฌ๋ค์ด ๋ฌธ์์ HWPX ํ ํ๋ฆฟ์ ์ ๊ณตํ์ ๋, ํ ํ๋ฆฟ ์คํ์ผ์ ์ ์งํ๋ฉด์ ๋ด์ฉ์ ์ฑ์ ๋ฃ๋ ์ํฌํ๋ก์ฐ.
analyze_template.py <template.hwpx> --style-map styles.json
1.5. (๋ค์ค MD ํตํฉ) โ ์ฌ๋ฌ MD ํ์ผ์ด ์๋ ๊ฒฝ์ฐ md_merger.py ์คํ
python3 md_merger.py file1.md file2.md --target-level 2 --output merged.jsonmd_parser.py input.md --output parsed.json
numbered_item ํ์
์ผ๋ก ํ์ฑํ์ฌ ๋ฒํธ ๋ง์ปค๋ฅผ ๋ณด์กดํจxml_writer.py --input parsed.json --style-config styles.json --output fragment.xmlzip_surgery.py replace template.hwpx -s fragment.xml -o result.hwpximage_embedder.py --hwpx result.hwpx --images-dir <dir> --mapping map.json --output final.hwpxvalidate.py final.hwpx + page_guard.py --reference template.hwpx --output final.hwpx# 1) ์คํ์ผ ์ถ์ถ
python3 "$SKILL_DIR/scripts/analyze_template.py" template.hwpx --style-map /tmp/styles.json
# 2) ๋งํฌ๋ค์ด ํ์ฑ
python3 "$SKILL_DIR/scripts/md_parser.py" input.md --output /tmp/parsed.json
# 3) XML ํ๋๊ทธ๋จผํธ ์์ฑ
python3 "$SKILL_DIR/scripts/xml_writer.py" \
--input /tmp/parsed.json --style-config /tmp/styles.json --output /tmp/fragment.xml
# 4) ํ
ํ๋ฆฟ์ ์ฝ์
python3 "$SKILL_DIR/scripts/zip_surgery.py" replace template.hwpx \
-s /tmp/fragment.xml -o /tmp/result.hwpx
# 5) ์ด๋ฏธ์ง ์๋ฒ ๋ฉ (์ด๋ฏธ์ง๊ฐ ์์ ๊ฒฝ์ฐ)
python3 "$SKILL_DIR/scripts/image_embedder.py" \
--hwpx /tmp/result.hwpx --images-dir ./images/ --mapping map.json --output final.hwpx
# 6) ๊ฒ์ฆ
python3 "$SKILL_DIR/scripts/validate.py" final.hwpx
python3 "$SKILL_DIR/scripts/page_guard.py" --reference template.hwpx --output final.hwpx
analyze_template.py --style-map ์ถ๋ ฅ์ ID๋ฅผ ์ฌ์ฉํ๋ค. ํ
ํ๋ฆฟ๋ง๋ค charPr/paraPr/borderFill 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)
| ๊ท์น | ์ค๋ช |
|---|---|
| 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๋ฅผ ์ถ๊ฐํ์ง ์๋๋ค. ๊ธฐ์กด binDataList๊ฐ ์์ผ๋ฉด ์ ๊ฑฐํ๋ค.
์ด๋ฏธ์ง ๋ฑ๋ก์ BinData/ ํด๋ + content.hpf๋ง์ผ๋ก ์ถฉ๋ถํ๋ค.
ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ์์ฑํ ํ๋ ๋ฐ๋์ ๋ค์ ์์ฑ์ ์ค์ ํด์ผ ํ๋ค:
<hp:tbl ... noAdjust="0" pageBreak="CELL">
| ์์ฑ | ํ์ ๊ฐ | ํจ๊ณผ |
|---|---|---|
noAdjust="0" | ํ์ | ์ ๋ด์ฉ์ ๋ง์ถฐ ํ ๋์ด ์๋ ํ์ฅ |
noAdjust="1" | ๊ธ์ง | ๊ณ ์ ๋์ด โ ๋ด์ฉ ์๋ฆผ |
pageBreak="CELL" | ๊ถ์ฅ | ์ ๋จ์ ํ์ด์ง ๋๊น ํ์ฉ |
pageBreak="NONE" | ๊ธ์ง | ํฐ ํ๊ฐ ํ ํ์ด์ง์ ๊ฐ์ ์์ถ |
HWPX ํ์ผ์ด ํ๊ธ์์ ์ด๋ฆฌ์ง ์์ ๋:
<hp:p> 1๊ฐ๋ง ์ถ๊ฐํด์ ์ด๋ฆฌ๋์ง ํ์ธ<hp:tbl> 1๊ฐ ์ถ๊ฐํด์ ์ด๋ฆฌ๋์ง ํ์ธ๊ฐ ๋จ๊ณ์์ ์คํจํ๋ฉด, ํด๋น ๋จ๊ณ์ ๋ณ๊ฒฝ ๋ด์ฉ์ด ์์ธ์ด๋ค.
.hwp(๋ฐ์ด๋๋ฆฌ) ํ์ผ์ ์ง์ํ์ง ์๋๋ค. ์ฌ์ฉ์๊ฐ .hwp ํ์ผ์ ์ ๊ณตํ๋ฉด ํ๊ธ ์คํผ์ค์์ .hwpx๋ก ๋ค์ ์ ์ฅํ๋๋ก ์๋ดํ ๊ฒ. (ํ์ผ โ ๋ค๋ฅธ ์ด๋ฆ์ผ๋ก ์ ์ฅ โ ํ์ผ ํ์: HWPX)hp:, hs:, hh:, hc: ์ ๋์ฌ ์ ์งvalidate.py๋ก ๋ฌด๊ฒฐ์ฑ ํ์ธ$SKILL_DIR/references/hwpx-format.md ์ฐธ์กฐ<hp:t/> ์ฌ์ฉ (self-closing tag)references/์ ๋ถ๋ฆฌanalyze_template.py + ์ถ์ถ XML ๊ธฐ๋ฐ์ผ๋ก ๋ณต์/์ฌ์์ฑํ ๊ฒexamples/* ํ์ผ์ ์ฝ๊ธฐ/์ฐธ์กฐ/๋ณต์ฌ์ ์ฌ์ฉํ์ง ๋ง ๊ฒvalidate.py์ ๋ณ๊ฐ๋ก page_guard.py๋ฅผ ๋ฐ๋์ ํต๊ณผํด์ผ ์๋ฃ ์ฒ๋ฆฌ<hp:linesegarray>๋ ๋ผ์ธ ๋ ์ด์์ ์บ์๋ก, ํ
์คํธ ์์ ํ ์ค์ ๋ด์ฉ๊ณผ ๋ถ์ผ์นํ๋ฉด '๋ฌธ์ ๋ณ์กฐ' ๊ฒฝ๊ณ ๋ฐ ๋น-ํ๊ธ ๋ทฐ์ด์์ ํ์ ์ค๋ฅ๋ฅผ ์ ๋ฐํ๋ค. build_hwpx.py์ pack.py๋ ํจํค์ง ์ cell_writer.py๋ฅผ ํธ์ถํ์ฌ ์ฌ๋ฐ๋ฅธ linesegarray๋ฅผ ์๋ ์์ฑํ๋ค. ์์ฑ ์คํจ ์ ๊ธฐ์กด ๋ฐฉ์(์๋ ์ ๊ฑฐ)์ผ๋ก ํด๋ฐฑํ๋ค. section0.xml ์์ฑ ์ linesegarray๋ฅผ ํฌํจํ ํ์ ์๋ค โ ๋น๋ ํ์ดํ๋ผ์ธ์ด ์๋ ์์ฑํ๋ค. ๋จ, ZIP-level surgery ํธ์ง ํ์๋ cell_writer๋ฅผ ์ ๋ ์คํํ์ง ์๋๋ค (ํ๊ธ์ด ์๋ ์ฌ๊ณ์ฐ).zip_surgery.py๋ฅผ ์ฌ์ฉํ๊ณ , ์์ธ ๊ท์น์ $SKILL_DIR/references/zip-surgery-guide.md ์ค์. ET.tostring()/tree.write() ์ฌ์ฉ ๊ธ์ง, ๊ฐํ ์ฝ์
๊ธ์ง, standalone='no' ๋ณด์กด ํ์.noAdjust="0" (ํ ๋์ด ์๋ ์กฐ์ ) + pageBreak="CELL" (ํ์ด์ง ๋๊น ํ์ฉ)validate.py --strict๋ก ์ถ๊ฐ ๊ฒ์ฆ (standalone, xmlns, newlines, ํ ์์ฑ)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์ build_table() / table_cell_xml() ํจ์๋ก ์์ฑํ๋ค. ์์ด์ ํธ๊ฐ ์ง์ <hp:tbl> XML์ ์์ฑํ๊ฑฐ๋ generate_content.py ๋ฑ์ ์์ฒด ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ๋ ๊ฒ์ ๊ธ์งํ๋ค.BinData/ + content.hpf 2๊ณณ์๋ง ๋ฑ๋กํ๋ค. header.xml binDataList ์ถ๊ฐ ๊ธ์ง. ๊ธฐ์กด binDataList๊ฐ ์์ผ๋ฉด ์ ๊ฑฐ. image_embedder.py๊ฐ ์๋ ์ฒ๋ฆฌํ๋ฏ๋ก ์ง์ ๋ฑ๋ก ๋ก์ง์ ์์ฑํ์ง ์๋๋ค..png ํ์ฅ์ ํ์ผ์ ์ค์ ํฌ๋งท์ด JPEG์ผ ์ ์๋ค (Gemini API ๋ฑ). image_embedder.py๊ฐ PIL๋ก ์๋ ๊ฐ์ง/๋ณํํ๋ฏ๋ก ๋ณ๋ ์ฒ๋ฆฌ ๋ถํ์.image_embedder.py์ make_pic_xml()์ ์ฌ์ฉํ๋ค. ๊ฒ์ฆ๋ ๊ตฌ์กฐ(pypandoc-hwpx/HwpForge)๋ฅผ ์ฌ์ฉํ๋ฉฐ, ์์ ์์๊ฐ ์ค์ํ๋ค.# Create
python3 "$SKILL_DIR/scripts/build_hwpx.py" --template base --output quick.hwpx
# Inspect
python3 "$SKILL_DIR/scripts/text_extract.py" quick.hwpx --format markdown
# Validate
python3 "$SKILL_DIR/scripts/validate.py" quick.hwpx