https://github.com/pathcosmos/hwpx-generator
https://github.com/pathcosmos/hwpx-generator
Last synced: 21 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/pathcosmos/hwpx-generator
- Owner: pathcosmos
- Created: 2026-02-09T11:44:53.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-04-29T05:48:04.000Z (about 2 months ago)
- Last Synced: 2026-04-29T07:25:58.045Z (about 2 months ago)
- Language: Python
- Size: 250 KB
- Stars: 0
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Audit: audit_crossrefs.py
Awesome Lists containing this project
README
# HWPX Generator
한컴오피스 한글(Hangul) 문서를 **프로그래밍·AI 로 자동 생성**하는 도구. 정부 사업계획서 같은 복잡한 양식부터 단순 양식 채우기까지 두 자동화 경로를 모두 지원하며, Claude Code · Claude Desktop · CLI · MCP 클라이언트 등 다양한 AI/도구 환경에서 사용 가능.
## 두 자동화 경로
| 경로 | 환경 | 강점 | 사용처 |
|---|---|---|---|
| **A. Python + lxml + COM** (기존) | WSL (Ubuntu) + Windows + 한컴오피스 2024 | HWPX XML 직접 편집, 한컴 PDF 충실도, 119페이지 마크다운 → HWPX 자동 변환 | 정밀 서식 재현, PDF 출력 |
| **B. Rust + rhwp** (PR #1~#3) | macOS / Linux / Windows 어디서든 한컴오피스 설치 없이 동작 | HWP 5.0 binary 직접 처리, 다중 셀 일괄 채우기, AI 친화 API | 양식 자동 채우기, AI 통합 |
두 경로는 **배타적이지 않고 한 저장소 안에 공존**합니다. 시나리오에 따라 골라 쓰거나 조합하여 사용.
## AI 통합 진입점 (경로 B)
경로 B 위에 세 가지 AI 통합 진입점이 layered. 사용자 환경에 따라 가장 편한 길로 시작:
| 진입점 | 위치 | 시작 명령 | 적합한 사용자 |
|---|---|---|---|
| **Claude Code Skill** | `.claude/skills/fill-hwp/SKILL.md` | `/fill-hwp 양식.hwp` (Claude Code 안) | Claude Code 사용자 (즉시) |
| **MCP 서버** | `hwp-automate-py/mcp_server.py` | `python mcp_server.py` (또는 클라이언트 자동) | Claude Desktop / Cursor / 기타 MCP 클라이언트 |
| **(선택) Standalone CLI Agent** | 미구현 (3 단계 옵션) | — | batch · CI · cron |
세 진입점 모두 같은 `hwp_automate` 라이브러리 위에 layered. 코드 중복 없음.
## 주요 기능
### 경로 A — Python + lxml + COM
- **마크다운 → HWPX 자동 변환** — `.md` 파일을 파싱하여 HWPX 양식의 지정 위치에 서식 있는 본문 삽입
- **Two-Pass 하이브리드 파이프라인** — Pass 1(XML 직접 편집) + Pass 2(COM 서식 삽입)
- **다중 템플릿 지원** — 임의의 HWPX 파일을 템플릿으로 등록, 설정 기반 문서 생성
- **COM 포스트 포맷 패턴** — InsertText → 선택 → 서식 적용 → 해제로 정확한 폰트/크기 렌더링
- **PDF 자동 변환** — 한컴오피스 COM API 를 통한 정확한 PDF 출력
- **PDF 비교 검증** — SSIM(구조적 유사도) + 텍스트 일치도 자동 비교
- **감사/디버깅 도구** — 교차참조 검사, 콘텐츠 무결성 감사, COM 크래시 격리, XML 직렬화 진단
### 경로 B — Rust + rhwp (cross-platform)
- **HWP 5.0 binary 직접 처리** — 한컴오피스 설치 없이 macOS/Linux/Windows 동작
- **양식 분석** — `analyze_template` 가 모든 표·셀·라벨 추론 (`cells`, `empty_cells`, `suggested_fields`, `neighbor_label`) 노출 → AI 가 즉시 양식 의미 파악
- **다중 셀 일괄 채우기** — `fill_template` 한 호출로 여러 표·여러 컬럼·여러 셀 처리, 표 식별은 헤더 매칭 또는 직접 좌표
- **Pre-flight + post-fill 검증** — 적용 전 모든 op 유효성 확인, 적용 후 라운드트립 자동 검증
- **dry_run 모드** — 실제 적용 없이 plan 만 검증
- **BinData 보존 우회** — `preserve_images=True` 기본, 원본 이미지 byte-for-byte 보존 (rhwp 라운드트립 손실 회피)
- **셀 병합 자동 처리** — `find_cell_idx` 가 (row, col) 위치 검색으로 병합된 표에서도 정확한 인덱싱
- **AI 친화 API** — JSON 호환, abi3-py39 wheel (Python 3.9~3.14), MCP 서버·Claude Code Skill 노출
- **field_map.json 어댑터** — 경로 A 의 기존 매핑 형식 그대로 재사용 가능
## 아키텍처
### 전체 구성 (두 경로 + AI 통합 layer)
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 사용자 환경 │
│ ┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Claude Code │ │ Claude Desktop / │ │ 터미널 / CLI / │ │
│ │ (Skill) │ │ Cursor / 기타 MCP │ │ generate_hwpx.py │ │
│ └──────┬───────┘ └────────┬─────────┘ └─────────┬────────┘ │
│ │ │ │ │
└─────────┼───────────────────┼───────────────────────┼────────────────────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────┐ ┌──────────────────────────┐
│ AI 통합 layer (경로 B) │ │ 경로 A (CLI) │
│ • SKILL.md (multi-turn playbook)│ │ • generate_hwpx.py │
│ • mcp_server.py (5 tools) │ │ • form_filler.py │
│ • field_map.json adapter │ │ • md_parser → md_to_ops │
└──────────────┬───────────────────┘ └──────────────┬───────────┘
│ │
▼ ▼
┌──────────────────────────────────┐ ┌──────────────────────────┐
│ hwp_automate (PyO3 abi3 wheel) │ │ bridge.py (WSL→Win) │
│ • analyze_template │ │ + hwpx_editor.py (lxml) │
│ • fill_template │ │ + hwp_com.py (pywin32) │
│ • preserve_images_from_source │ │ + pdf_compare.py (SSIM) │
└──────────────┬───────────────────┘ └──────────────┬───────────┘
│ │
▼ ▼
┌──────────────────────────────────┐ ┌──────────────────────────┐
│ rhwp (외부 의존, MIT) │ │ 한컴오피스 한글 2024 │
│ Rust HWP 5.0 binary engine │ │ COM API (Windows) │
│ ../codebase/rhwp │ │ │
└──────────────────────────────────┘ └──────────────────────────┘
```
세 AI 진입점은 같은 `hwp_automate` Python wheel 위에 layered (코드 중복 0). 그 아래 rhwp 가 HWP 5.0 binary 의 모든 파싱·직렬화·IR 변형을 책임짐. 경로 A 는 HWPX (XML) 와 COM 자동화로 별도 운영.
### Two-Pass 파이프라인 (경로 A 상세)
```
┌─────────────────────────────────────────────────────────────────┐
│ Pass 1: XML 직접 편집 (WSL, lxml) │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ JSON │──>│ field_mapper │──>│ hwpx_editor │ │
│ │ Input │ │ (셀 매핑) │ │ (184셀 채움) │ │
│ └──────────┘ └──────────────┘ └──────┬───────┘ │
│ │ │
│ ┌──────────┐ ┌────────────────┐ ▼ │
│ │ Markdown │──>│ md_parser │ ┌──────────────┐ │
│ │ Sections │ │ (구조화 파싱) │ │ form_filler │ │
│ └──────────┘ └───────┬────────┘ │ (오케스트레이터)│ │
│ │ └──────┬───────┘ │
│ ▼ │ │
│ ┌────────────────┐ │ │
│ │ section_mapper │ │ │
│ │ (마커 매핑) │ │ │
│ └───────┬────────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌────────────────┐ │ │
│ │ md_to_ops │ │ │
│ │ (COM 명령 생성) │ │ │
│ └───────┬────────┘ │ │
│ │ │ │
├─────────────────────────┼───────────────────┼─────────────────────┤
│ Pass 2: COM 자동화 (Windows, pywin32) │ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────────────────────┐ │
│ │ bridge.py │ │
│ │ (WSL→Windows 브릿지) │ │
│ │ 포스트 포맷: Insert→Select→ │ │
│ │ Format→Deselect │ │
│ └───────────┬────────────────────┘ │
│ │ │
│ ┌────┴────┐ │
│ ▼ ▼ │
│ .hwpx .pdf │
├───────────────────────────────────────────────────────────────────┤
│ 검증 (WSL) │
│ ┌─────────────────┐ │
│ │ pdf_compare.py │ │
│ │ (SSIM 검증) │ │
│ └─────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
```
### 모듈 구성
| 모듈 | 실행 환경 | 역할 |
|------|----------|------|
| `src/form_filler.py` | WSL | **파이프라인 오케스트레이터**. Pass 1 + Pass 2 순차 실행 |
| `src/md_parser.py` | WSL | 마크다운 파서. `.md` → 구조화된 블록(헤딩/문단/표/리스트) |
| `src/md_to_ops.py` | WSL | 마크다운 블록 → COM 자동화 명령 시퀀스 변환 |
| `src/section_mapper.py` | WSL | 마크다운 섹션 → HWPX 마커(##SEC_CONTENT##) 매핑 |
| `src/generate_hwpx.py` | WSL | 메인 CLI 파이프라인. 전체 흐름 제어 |
| `src/bridge.py` | WSL | WSL↔Windows Python 브릿지. 포스트 포맷 패턴 구현 |
| `src/hwp_com.py` | Windows | 한컴오피스 COM 자동화 (pywin32) |
| `src/hwpx_editor.py` | WSL | HWPX ZIP 내부 section0.xml 직접 수정 (lxml) |
| `src/field_mapper.py` | WSL | JSON 입력 데이터 → 셀 좌표 매핑 |
| `src/pdf_compare.py` | WSL | PDF 페이지별 SSIM + 텍스트 비교 |
| `src/extract_template.py` | WSL | HWPX 파일 구조 분석/추출 |
### 데이터 흐름
```
sample_input.json
│
▼
field_mapper.py ──(field_map.json)──> {(row, col): text} 딕셔너리
│
▼
hwpx_editor.py ──> section0.xml 내 빈 셀에 텍스트 삽입 (93개 셀)
│
▼
bridge.py ──> Windows Python으로 COM 스크립트 전달
│
▼
hwp_com.py ──> 한컴오피스 COM: 텍스트 교체 + HWPX/PDF 저장
│
▼
pdf_compare.py ──> 참조 PDF와 SSIM/텍스트 비교 검증
```
## 환경 요구사항
### 경로 A — Python + lxml + COM (Windows 필수)
한컴오피스 COM API 만이 보장하는 가치:
- 119페이지, 456개 표, 63개 이미지, 370개 글자 속성의 **100% 서식 재현**
- 내장 렌더링 엔진을 통한 **정확한 PDF 변환** (`SaveAs "PDF"`)
- 기존 문서를 열어서 수정하는 **템플릿 기반 워크플로우**
**필수 구성:**
- **Windows 측**: Windows 10/11, 한컴오피스 2024 (한글), Python 3.13+, `pywin32`
- **WSL 측**: Ubuntu (WSL2 권장), Python 3.12+, `lxml`, `PyMuPDF(fitz)`, `scikit-image`, `Pillow`, `numpy`
**WSL ↔ Windows 브릿지:** WSL Python 이 `bridge.py` 로 Windows Python(`python.exe`) 을 subprocess 호출. Windows Python 이 한컴오피스 COM API 로 문서를 열고·수정·PDF 저장. 결과는 `/mnt/d/` 등 공유 드라이브로 WSL 에서 접근.
### 경로 B — Rust + rhwp (크로스플랫폼, 한컴 불필요)
**필수 구성 (모든 OS 동일):**
- Rust 1.75+ (`brew install rust` 또는 rustup, 검증 시점 1.95.0)
- Python 3.9+ (abi3 wheel — 단일 wheel 이 3.9~3.14 모두 호환)
- maturin 1.13+ (`brew install maturin` 또는 `pip install maturin`)
- (Mac) Xcode CLT — clang 21+ 이미 시스템에 있어야 함
- `../codebase/rhwp` 위치에 [edwardkim/rhwp](https://github.com/edwardkim/rhwp) git clone (저장소 외부 의존)
**MCP 서버 사용 시 추가 (선택):**
- Python 3.10+ (mcp SDK 가 3.10+ 요구 — abi3 wheel 자체는 3.9 호환 유지)
- `mcp[cli]>=1.2.0` (`pip install 'hwp-automate[mcp]'` 또는 직접 설치)
**Claude Code Skill 사용 시 추가 (선택):**
- Claude Code 가 이미 설치되어 있어야 함 (이 README 가 작성된 환경)
- 별도 설치 명령 없음 — `.claude/skills/fill-hwp/SKILL.md` 가 자동 인식
### 외부 의존 디렉토리 레이아웃
경로 B 는 `../codebase/rhwp` 위치에 rhwp 가 있어야 동작 (본 저장소에 미포함):
```
temp_git/ (또는 임의 작업 루트)
├── hwpx-generator/ ← 이 저장소
└── codebase/ ← 별도 git clone (저장소 외부)
└── rhwp/ ← gh repo clone edwardkim/rhwp
```
운영 시 vendor 고정이 필요하면 hop 의 `third_party/rhwp` git submodule 패턴을 따라할 수 있음 (현재 PoC 단계에선 path 의존이 더 빠른 반복을 위해 유지).
## 설치 및 실행
### 경로 A 설치 (Python + lxml + COM)
#### 1. 전제조건 확인
```bash
# WSL에서 확인
python3 --version # 3.12+
# Windows Python 경로 확인
/mnt/c/Users//AppData/Local/Microsoft/WindowsApps/python.exe --version
```
#### 2. WSL Python 패키지 설치
```bash
pip3 install --break-system-packages lxml pymupdf Pillow scikit-image numpy
```
#### 3. Windows Python 패키지 설치
```powershell
# Windows PowerShell에서
pip install pywin32
```
#### 4. 실행 (`generate_hwpx.py`)
```bash
# 데이터를 적용하여 문서 생성 + PDF 변환 (기본 cloud_integrated 템플릿)
python3 src/generate_hwpx.py \
--template ref/test_01.hwpx \
--data data/sample_input.json \
--output output/
# 다른 템플릿 설정으로 문서 생성
python3 src/generate_hwpx.py \
--template ref/새양식.hwpx \
--template-dir templates/새양식/ \
--data data/새양식_input.json \
--output output/
# PDF 비교 검증 포함
python3 src/generate_hwpx.py \
--template ref/test_01.hwpx \
--output output/ \
--pdf-only \
--compare ref/test_01.pdf
```
CLI 옵션:
| 옵션 | 설명 |
|------|------|
| `--template, -t` | (필수) 템플릿 HWPX 파일 경로 |
| `--template-dir` | 템플릿 설정 디렉토리 (기본: `templates/cloud_integrated/`) |
| `--data, -d` | JSON 입력 데이터 파일 경로 |
| `--output, -o` | 출력 디렉토리 (기본: `output`) |
| `--pdf-only` | 데이터 없이 템플릿을 그대로 PDF로 변환 |
| `--no-pdf` | PDF 생성 건너뛰기 |
| `--compare, -c` | 비교할 참조 PDF 경로 |
### 경로 B 설치 (Rust + rhwp, cross-platform)
#### 1. 외부 의존 (rhwp) 준비
```bash
mkdir -p ../codebase
cd ../codebase
gh repo clone edwardkim/rhwp # 또는 git clone https://github.com/edwardkim/rhwp.git
cd -
```
#### 2. Rust + maturin 설치 (이미 있으면 생략)
```bash
# macOS
brew install rust maturin
# Linux / Windows — rustup.rs 또는 패키지 매니저
```
#### 3. Python venv + wheel 빌드
```bash
cd hwp-automate-py
python3 -m venv .venv
source .venv/bin/activate # Linux/Mac
# Windows: .venv\Scripts\activate
pip install --upgrade maturin pytest
maturin develop --release # ~30초, rhwp 컴파일 + abi3 wheel 빌드 + venv 설치
```
#### 4. (선택) MCP 서버 의존 추가
```bash
# Python 3.10+ 환경에서만
pip install 'mcp[cli]>=1.2.0'
```
#### 5. 실행 — 3 가지 진입점
##### 5.1 — Python 직접 호출 (라이브러리)
```python
import hwp_automate
# 양식 분석 — AI 가 양식 의미를 파악할 수 있는 모든 정보 노출
info = hwp_automate.analyze_template("/path/to/form.hwp")
for t in info["tables"]:
print(t["header"], t["empty_cells"], t["suggested_fields"])
# 양식 채우기 — 다중 표·다중 셀, dry_run+verify+preserve_images 자동
result = hwp_automate.fill_template(
template_path="/path/to/form.hwp",
out_path="/path/to/output.hwp",
operations=[{
"header_match": "성명",
"cells": [{"row": 1, "col": 5, "value": "정보처리기사"}],
}],
)
print(result["status"]) # "applied + verified"
```
##### 5.2 — CLI (`hwp_automate_cli`)
```bash
source hwp-automate-py/.venv/bin/activate
# 분석
python -m hwp_automate_cli analyze --template /path/to/form.hwp --json
# 빠른 셀 채우기
python -m hwp_automate_cli cell \
--template /path/to/form.hwp \
--output /path/to/output.hwp \
--header-match 성명 \
--cell 1,5,정보처리기사 \
--cell 2,5,정보보안기사
# field_map.json + data.json 으로 일괄 채우기
python -m hwp_automate_cli fill \
--template /path/to/form.hwp \
--field-map templates/cloud_integrated/field_map.json \
--data data/sample_input.json \
--output /path/to/output.hwp \
--header-match "기관명"
```
##### 5.3 — Claude Code Skill (사용자가 이 환경에서 즉시 사용)
별도 설치 없음. `.claude/skills/fill-hwp/SKILL.md` 가 자동 인식되어:
```
사용자: /fill-hwp /Users/lanco/Downloads/사업신청서.hwp
Claude: [analyze 실행] 양식 분석 완료. 다음 정보가 필요합니다:
업종명, 주생산품, 매출액(백만원), 영업이익(백만원), 수출액(백만원), 부채비율(%)
알려주시면 채워서 저장하겠습니다.
사용자: 철강 특수강 제조 / 스테인리스 강재 / 12,500 / 1,200 / 8,300 / 45.2
Claude: [fill_template 실행]
✅ /Users/lanco/Downloads/사업신청서_filled.hwp (35MB) 완료
applied + verified, 6 셀 적용. 한컴/모바일 한글에서 확인해 주세요.
```
PostToolUse hook (`.claude/hooks/hwp-fill-verify.py`) 이 fill 명령 후 출력 파일 자동 확인 (크기, HWP 5.0 CFB 매직).
##### 5.4 — MCP 서버 (Claude Desktop / Cursor / 기타 MCP 클라이언트)
```bash
cd hwp-automate-py
source .venv/bin/activate
pip install 'mcp[cli]>=1.2.0' # Python 3.10+
python mcp_server.py # 또는 클라이언트가 자동 spawn
```
**Claude Desktop 등록** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
```json
{
"mcpServers": {
"hwp-automate": {
"command": "/abs/path/to/hwp-automate-py/.venv/bin/python",
"args": ["/abs/path/to/hwp-automate-py/mcp_server.py"]
}
}
}
```
**Claude Code 등록**:
```bash
claude mcp add hwp-automate -- /abs/path/to/.venv/bin/python /abs/path/to/mcp_server.py
```
노출 5 tools:
| tool | 용도 |
|---|---|
| `analyze_form` | 양식 구조·빈 셀·라벨 추론 (AI 가 의미 파악) |
| `preview_form_structure` | 가벼운 markdown 요약 (큰 양식 첫 검토) |
| `fill_form` | operations 로 양식 채우기 (dry_run, verify) |
| `fill_form_from_data` | field_map.json + data.json 호환 입력 |
| `verify_output` | 결과 셀 라운드트립 검증 |
상세 문서: [`hwp-automate-py/README.md`](hwp-automate-py/README.md), [`hwp-automate-poc/README.md`](hwp-automate-poc/README.md)
## 다중 템플릿 지원 (경로 A)
> **적용 범위**: 이 절의 `templates//` 디렉토리 + `template.json` + `field_map.json` 패턴은 **경로 A** 의 자산이다. 경로 B 는 어댑터를 통해 같은 `field_map.json` 을 재사용할 수 있다 ([hwp-automate-py/README.md](hwp-automate-py/README.md) 의 `fill_template_from_data` 절 참고).
기본 제공되는 `cloud_integrated`(클라우드 종합솔루션 사업계획서) 외에 다른 한글 문서를 템플릿으로 등록하여 사용할 수 있습니다.
### 템플릿 디렉토리 구조
```
templates/
cloud_integrated/ # 기본 템플릿 (test_01.hwpx용)
template.json # 메타 정보 + 찾아바꾸기 매핑
field_map.json # 커버 테이블 셀 좌표 매핑
새_양식_이름/ # 새 템플릿 추가 시
template.json
field_map.json
```
### template.json 형식
각 템플릿 설정 디렉토리에는 `template.json`이 있어 찾아바꾸기 매핑과 메타 정보를 정의합니다.
```jsonc
{
"name": "cloud_integrated",
"description": "2026 클라우드 종합솔루션 지원사업 사업계획서",
"cover_table_index": 0, // XML 셀 채우기 대상 표 인덱스
"replacements": [
{
"find": "원본 문서의 텍스트", // COM 찾아바꾸기 대상
"data_key": "사업명" // 입력 JSON의 키
},
{
"find": "원본의 기간 텍스트",
"data_key": "수행기간._개발", // 상위 객체 참조
"format": " · 개발 : {개발시작} ~ {개발종료} ({개발기간})" // 포맷 문자열
}
]
}
```
- **단순 치환**: `data_key`로 입력 데이터에서 값을 가져와 `find` 텍스트를 교체
- **포맷 치환**: `format` 키가 있으면 `data_key`의 상위 객체를 사용하여 `{필드명}` 패턴을 채움
### 새 템플릿 등록 워크플로우
```bash
# Step 1: HWPX 파일의 표 구조 분석
python3 src/extract_template.py --hwpx ref/새양식.hwpx --all-tables
# Step 2: 템플릿 설정 초안 자동 생성
python3 src/extract_template.py \
--hwpx ref/새양식.hwpx \
--generate-template-config \
-o templates/새양식/
# Step 3: 생성된 template.json, field_map.json 수동 검토 및 보정
# - cover_table_index 확인
# - replacements에 찾아바꾸기 항목 추가
# - field_map.json의 셀 좌표 검증
# Step 4: 입력 데이터 JSON 작성 후 문서 생성
python3 src/generate_hwpx.py \
--template ref/새양식.hwpx \
--template-dir templates/새양식/ \
--data data/새양식_input.json \
--output output/
```
### extract_template.py 옵션
| 옵션 | 설명 |
|------|------|
| `--hwpx` | HWPX 파일 경로 (기본: `ref/test_01.hwpx`) |
| `--cover` | 커버 페이지 추출 |
| `--sections` | 본문 섹션 추출 |
| `--styles` | 스타일 정보 추출 |
| `--tables` | 주요 표 추출 |
| `--all-tables` | 모든 표 요약 목록 출력 |
| `--sample-data` | sample_input.json 생성 |
| `--generate-template-config` | template.json + field_map.json 초안 자동 생성 |
| `--output, -o` | 출력 파일/디렉토리 경로 |
## 구현 접근방식 비교
3가지 접근방식을 실제로 테스트하고 평가한 결과입니다. 대상 문서는 119페이지, 456개 표의 정부 사업계획서입니다.
### 평가 결과
| 평가 기준 | COM 자동화 | python-hwpx | 직접 XML |
|-----------|:---------:|:----------:|:-------:|
| 서식 재현도 | ★★★★★ | ★★☆☆☆ | ★★★★☆ |
| PDF 변환 | ★★★★★ | ☆☆☆☆☆ | ☆☆☆☆☆ |
| 개발 용이성 | ★★★★☆ | ★★★☆☆ | ★☆☆☆☆ |
| 실행 속도 | ★★☆☆☆ | ★★★★★ | ★★★★☆ |
| 이식성 | ★☆☆☆☆ | ★★★★★ | ★★★★★ |
| 유지보수성 | ★★★★☆ | ★★★☆☆ | ★☆☆☆☆ |
| 기존 문서 수정 | ★★★★★ | ★★★☆☆ | ★☆☆☆☆ |
| **총점** | **28/35** | **18/35** | **16/35** |
### 최종 결정: 하이브리드 COM 중심 방식
**XML 직접 수정 + COM 교체/PDF**를 조합한 하이브리드 방식을 채택했습니다.
- **XML 직접 수정** (`hwpx_editor.py`): 빈 셀에 데이터를 채우는 단순 작업. lxml로 네임스페이스를 보존하면서 section0.xml을 수정합니다. 서식(charPrIDRef, paraPrIDRef)은 템플릿의 것을 그대로 유지합니다.
- **COM 텍스트 교체** (`hwp_com.py`): 사업명, 과제명 등 기존 텍스트를 찾아바꾸는 작업. 한컴오피스의 AllReplace를 사용하여 정확한 서식을 유지합니다.
- **COM PDF 변환**: `SaveAs("PDF")`로 한컴오피스 렌더링 엔진이 직접 PDF를 생성합니다.
상세 비교 보고서: [`analysis/approach_comparison.md`](analysis/approach_comparison.md)
## 핵심 기술 이슈 및 해결
### XML 선언부 호환성
**증상**: 생성된 HWPX 파일이 한컴오피스에서 열리지 않음
**원인**: lxml의 기본 XML 직렬화가 한컴오피스와 호환되지 않는 형식을 생성:
- 작은따옴표(`'`) 사용 (한컴오피스는 큰따옴표 `"` 필요)
- `standalone="yes"` 누락
- XML 선언부와 루트 요소 사이에 줄바꿈 삽입
**해결**: `hwpx_editor.py`의 `serialize_xml()` 메서드가 원본 XML 선언부를 보존하여 직접 구성. `etree.tostring(xml_declaration=True)` 대신 원본 선언부를 문자열로 접합
### ZIP 압축 방식 보존
**증상**: HWPX 파일이 열리지 않거나 이미지가 깨짐
**원인**: HWPX ZIP 엔트리별로 압축 방식이 다름 (`mimetype`, `version.xml`, 이미지 파일은 `ZIP_STORED`, 나머지는 `ZIP_DEFLATED`). 모든 엔트리를 동일하게 압축하면 한컴오피스가 인식하지 못함
**해결**: `hwpx_editor.py`가 원본 ZIP의 각 엔트리별 `compress_type`을 기록하고, 저장 시 그대로 복원
### PrintMethod=4 버그
**증상**: 119페이지 문서가 PDF로 변환하면 60페이지(가로 방향)로 출력됨
**원인**: HWPX의 `settings.xml`에 `PrintMethod=4`(2페이지/장 인쇄) 설정이 있어, COM이 PDF 생성 시 이 설정을 적용
**해결**: `fix_hwpx_for_pdf()` 함수로 COM이 파일을 열기 **전에** `PrintMethod=0`으로 변경. COM은 파일을 열 때 settings.xml을 읽으므로, 열린 후에 수정해도 효과 없음
### COM 좀비 프로세스 관리
COM 자동화 중 오류가 발생하면 `Hwp.exe`가 백그라운드에 남을 수 있습니다. 항상 `try/finally`로 `hwp.quit()`을 호출하고, 필요 시 수동 정리:
```bash
taskkill.exe /F /IM Hwp.exe
```
### 네임스페이스 보존
HWPX의 XML은 12개 이상의 네임스페이스를 사용합니다. `xml.etree.ElementTree`는 네임스페이스 프리픽스를 `ns0:`, `ns1:`로 변환하여 한글이 파일을 인식하지 못합니다. **lxml** 사용이 필수입니다 — 원본 프리픽스(`hp:`, `hs:`, `hh:` 등)를 그대로 보존합니다.
### (경로 B) 셀 병합 표의 cell_idx 어긋남
**증상**: rhwp 의 `insert_text_in_cell_native(... cell_idx ...)` 가 "셀 인덱스 N 범위 초과" 로 실패. 작은 표(병합 없음)에서는 동작하다가 큰 양식의 7×8 같은 병합 표에서 실패.
**원인**: HWP 표는 셀 병합으로 인해 `Table.cells` Vec 의 길이가 `rows × cols` 보다 작음. `cell_idx = row × cols + col` 공식이 어긋남.
**해결**: `find_cell_idx()` 가 `Table.cells` 에서 (row, col) 위치를 직접 검색. pre-flight 단계에서 한 번만 산출하여 캐시.
### (경로 B) HWP 셀 텍스트 trailing whitespace
**증상**: 사용자 입력 "단원구" 가 라운드트립 후 "단원구 " 로 보여 `verify=True` 가 실패.
**원인**: 한컴 셀 content 끝에 \n/공백을 자동 추가하는 관례.
**해결**: post-fill verify 비교를 `trim_end()` 로. leading whitespace 는 의도적일 수 있어 한쪽만 trim.
### (경로 B) rhwp BinData 라운드트립 손실
**증상**: 35MB YCP 양식을 fill 후 한컴이 "손상" 으로 판정. 54MB 코리녹스는 그림 일부 누락.
**원인**: rhwp v0.7.x 가 BinData stream (BMP 이미지 등) 을 재직렬화하면서 미세하게 변형 — 같은 stream 수지만 byte 가 ~5MB 손실.
**해결**: `merge_cfb_preserving_input()` — rhwp 출력 베이스 + BinData/Preview 만 입력 양식의 raw bytes 로 byte-for-byte 보존. 외부 `cfb` crate 의 strict 검증이 rhwp `mini_cfb` 출력과 비호환이라, rhwp 자체의 `LenientCfbReader` + `mini_cfb::build_cfb` 로 stream 단위 머지. **`preserve_images=True` 가 기본** — 사용자가 끄지 않는 한 자동 적용.
### (경로 B) HWP 표준 layout 으로 leaf 이름 → storage path 재구성
**증상**: `LenientCfbReader.list_entries()` 가 `BIN0001.bmp` 같이 leaf 이름만 반환 (parent storage 정보 없음). `mini_cfb::build_cfb` 에 그대로 넘기면 모든 stream 이 root 에 평탄화되어 한컴이 인식 못 함.
**해결**: `leaf_to_hwp_path()` 가 leaf 이름 패턴으로 storage path 추론 — `BIN****.*` → `/BinData/`, `Section{N}` → `/BodyText/`, `PrvText/PrvImage` → root 또는 `/Preview/` (양식별).
## 프로젝트 구조
```
hwpx-generator/
├── README.md
├── CHANGELOG.md # 변경 이력 (Milestone 3, 경로 B, V1, AI 통합 등)
├── CLAUDE.md # Claude Code · AI 에이전트 가이드 (두 경로 + AI 통합 진입점)
├── .gitignore # .venv, output, .claude 의 로컬 파일만 무시 (skill/hook 은 트래킹)
│
├── 🔵 경로 A — Python + lxml + COM (기존)
│
├── analysis/ # 접근방식 평가 보고서
│ ├── approach_comparison.md # 3가지 방식 종합 비교
│ ├── com_evaluation.md # COM API 테스트 결과
│ ├── pyhwpx_evaluation.md # python-hwpx 평가
│ ├── direct_xml_evaluation.md # 직접 XML 평가
│ └── hwpx_structure_analysis.md # HWPX 구조 분석
├── data/
│ ├── sample_input.json # 샘플 입력 데이터
│ ├── schema.json # 입력 데이터 JSON Schema
│ └── form_content_map.json # 마크다운 섹션 → HWPX 마커 매핑
├── ref/ # 참조 파일 (gitignore 대상)
├── src/ # 경로 A 메인 코드
│ ├── form_filler.py # ★ 파이프라인 오케스트레이터 (Pass 1 + Pass 2)
│ ├── md_parser.py # ★ 마크다운 파서
│ ├── md_to_ops.py # ★ 마크다운 → COM 명령 변환
│ ├── section_mapper.py # ★ 섹션 → 마커 매핑
│ ├── generate_hwpx.py # 메인 CLI 파이프라인
│ ├── bridge.py # WSL↔Windows 브릿지 (포스트 포맷 패턴)
│ ├── hwp_com.py # 한컴오피스 COM 자동화 (Windows only)
│ ├── hwpx_editor.py # HWPX XML 편집기 (lxml)
│ ├── field_mapper.py # JSON→셀 좌표 매핑
│ ├── pdf_compare.py # PDF 비교 검증
│ └── extract_template.py # 템플릿 구조 분석
├── templates/ # 경로 A 양식별 매핑
│ ├── cloud_integrated/ # 클라우드 종합솔루션 템플릿
│ │ ├── template.json
│ │ └── field_map.json
│ └── gyeongnam_rbd/ # ★ 경남 R&BD 사업계획서 (36표·184셀)
│ └── field_map.json
├── tools/
│ └── make_rawcopy.py # HWPX 클린 카피 유틸리티
├── tests/
│ ├── test_hwpx_editor.py # HwpxEditor 단위 테스트
│ ├── test_hwp_com_module.py # COM 모듈 (Windows only)
│ └── test_integration.py # 통합 테스트
├── audit_crossrefs.py # 교차참조 유효성 검사
├── audit_hwpx_content.py # 콘텐츠 무결성 감사
├── audit_section0.py # section0.xml 상세 감사
├── compare_section0{,_v2}.py # section0 비교
├── debug_crash_isolate.py # COM 크래시 격리
├── diagnose_xml_serialization.py # XML 직렬화 진단
│
├── 🟢 경로 B — Rust + rhwp + AI 통합
│
├── hwp-automate-poc/ # Rust PoC (binary)
│ ├── README.md
│ ├── Cargo.toml # rhwp = ../../codebase/rhwp
│ ├── src/main.rs # 양식 표 자동 채우기 데모
│ ├── output/ # 생성된 .hwp / .svg (gitignore)
│ └── target/ # cargo build (gitignore)
│
├── hwp-automate-py/ # Python 바인딩 + AI 통합 진입점
│ ├── README.md
│ ├── Cargo.toml # PyO3 + rhwp path 의존
│ ├── pyproject.toml # maturin abi3-py39, mcp optional
│ ├── src/lib.rs # ★ analyze_template, fill_template,
│ │ # fill_template_table, preserve_images_from_source
│ ├── mcp_server.py # ★ FastMCP stdio 서버 (5 tools)
│ ├── hwp_automate_cli/ # Python 보조 도구 (wheel 비번들)
│ │ ├── __init__.py
│ │ ├── __main__.py # CLI: analyze / fill / cell
│ │ └── field_map.py # field_map.json 어댑터
│ ├── tests/
│ │ └── test_svg_regression.py # SVG 기반 시각 회귀 (한컴 없이 자동)
│ ├── .venv/ # 격리 venv (gitignore)
│ ├── target/ # cargo build (gitignore)
│ └── output/ # V1_*.hwp 등 (gitignore)
│
├── .claude/ # Claude Code 통합 (skill/hook/settings 트래킹)
│ ├── skills/
│ │ └── fill-hwp/SKILL.md # ★ /fill-hwp 양식.hwp playbook (multi-turn)
│ ├── hooks/
│ │ └── hwp-fill-verify.py # PostToolUse: fill 후 출력 파일 자동 확인
│ └── settings.json # hook 등록
│
├── .github/
│ └── workflows/build-wheels.yml # macOS+Linux+Windows wheel 매트릭스 빌드
│
└── output/ # 경로 A 결과물 (gitignore)
```
**외부 의존 (본 저장소 외부, git clone 별도):**
```
../codebase/rhwp/ ← gh repo clone edwardkim/rhwp (경로 B 핵심 엔진, MIT)
../codebase/hop/ ← gh repo clone golbin/hop (참고 패턴 출처, MIT — 선택)
```
## 데이터 입력 형식
입력 데이터는 JSON 형식이며, `data/schema.json`에 정의된 스키마를 따릅니다.
### 주요 필드
```jsonc
{
"사업명": "OO 종합솔루션 지원사업(통합형 OO화)",
"과제명": "예시 과제명 — 중소제조기업형 클라우드 통합관리 SaaS 플랫폼",
"사업개요": "과제내용에 대하여 간단히 요약 기술 (예시 텍스트)",
"개발솔루션": ["예시 솔루션 A", "예시 솔루션 B", ...], // 최대 5개
"수행기간": {
"개발시작": "'26.6.30",
"개발종료": "'27.6.30",
"실증시작": "'27.6.30",
"실증종료": "'27.12.31"
},
"대표공급기업": { // 기관 정보 블록
"기업명": "(주)예시소프트",
"사업자등록번호": "000-00-00000",
"대표자명": "홍길동",
"담당자": { "성명": "김OO", "부서": "...", ... }
},
"클라우드사업자": { ... }, // 동일 구조
"협력기관": { ... }, // 동일 구조
"참여공급기업": [{ "기업명": "(주)예시기업A", ... }], // 최대 3개
"도입실증기업": [{ "기업명": "(주)실증기업A", ... }] // 최대 5개
}
```
필수 필드: `사업명`, `과제명`, `개발솔루션`, `수행기간`, `대표공급기업`, `클라우드사업자`
전체 스키마: [`data/schema.json`](data/schema.json) / 샘플 데이터: [`data/sample_input.json`](data/sample_input.json)
## 테스트
### 경로 A — 단위 + 통합 테스트
```bash
# HwpxEditor 단위 테스트 (11개, WSL 에서 실행)
python3 -m pytest tests/test_hwpx_editor.py -v
# 주의: 전체 테스트 (pytest tests/) 는 test_hwp_com_module.py 가
# Windows 전용이라 WSL 에서 sys.exit(1) 로 중단됨.
# 반드시 테스트 파일을 개별 지정할 것.
```
테스트 항목: 표 조회·범위 외 인덱스, 셀 텍스트 설정, 일괄 채우기, 저장 후 재로드, 네임스페이스 보존, charPrIDRef 보존, mimetype 비압축, XML 선언부 보존, ZIP 압축 방식 보존.
**통합 테스트 (전체 파이프라인):**
```bash
python3 src/generate_hwpx.py \
--template ref/test_01.hwpx \
--data data/sample_input.json \
--output output/milestone1 \
--compare ref/test_01.pdf
```
검증 기준: SSIM ≥ 0.90 (달성 0.9660), 페이지 수 119/119, 텍스트 일치도 0.9959.
### 경로 B — SVG 시각 회귀 + 실 양식 검증
#### SVG diff 자동 회귀 (한컴 없이 Mac/Linux 자동)
```bash
cd hwp-automate-py
source .venv/bin/activate
python -m pytest tests/ -v
```
3 testcase (`tests/test_svg_regression.py`):
- `test_no_chars_removed` — fill 후 양식의 어떤 글자도 사라지지 않는지
- `test_added_chars_match_intended` — 추가된 글자 멀티셋이 의도한 fill 값과 일치
- `test_total_char_count_difference` — 전체 글자 수 차이가 의도한 비공백 글자 수와 일치
baseline (원본) vs filled (자격증 컬럼 채움) SVG 의 글자 멀티셋 비교. 좌표는 무시 (rhwp 의 LineSeg 미세 재계산 노이즈 회피), 텍스트 보존만 검증.
#### 실 양식 V1 검증 (PR #2)
| 양식 | 크기 | 결과 |
|---|---|---|
| YCP_V0.4 (제조AI 사업신청서) | 35MB → 35MB | ✅ applied + verified, 6 셀 채움 |
| 코리녹스_V0.9 (제조AI 사업신청서) | 54MB → 54MB | ✅ applied + verified, 5 셀 채움 |
`preserve_images=True` 기본 적용으로 **BinData 54/54 동일 크기 보존** — 한컴이 손상으로 인식하지 않음.
#### CI 매트릭스 빌드 (`.github/workflows/build-wheels.yml`)
매 PR / push 시 macOS / Linux / Windows runner 에서 wheel 자동 빌드. Mac+Linux 에서는 SVG 회귀까지 자동 실행. abi3-py39 라 OS 한 곳 빌드한 wheel 이 그 OS 의 모든 Python 3.9~3.14 호환.
#### MCP 서버 smoke test (in-process)
```bash
cd hwp-automate-py
source .venv/bin/activate
python -c "
import asyncio
from mcp_server import mcp
async def t():
tools = await mcp.list_tools()
print(f' {len(tools)} tools 등록')
for tool in tools:
print(f' - {tool.name}')
asyncio.run(t())
"
# → 5 tools (analyze_form, preview_form_structure, fill_form, fill_form_from_data, verify_output)
```
## HWPX 형식 참고
- HWPX는 `application/hwp+zip` MIME 타입의 ZIP 아카이브
- 단위: HWPUNIT (1/7200 인치). A4 = 59528 x 84188
- 주요 네임스페이스: `hp:`(문단), `hs:`(섹션), `hh:`(헤더), `hc:`(코어), `ha:`(앱)
- 상세 구조: [`CLAUDE.md`](CLAUDE.md) 및 [`analysis/hwpx_structure_analysis.md`](analysis/hwpx_structure_analysis.md) 참고
## Acknowledgement (감사·출처)
본 저장소는 두 자동화 경로를 보유합니다. 각 경로가 의존하는 외부 오픈소스 프로젝트들에 대한 감사 표기입니다.
### 경로 A — Python + lxml + COM (기존)
| 라이브러리 | 용도 | 라이선스 |
|---|---|---|
| [lxml](https://lxml.de/) | HWPX (XML 기반) 직접 편집 | BSD |
| [pywin32](https://github.com/mhammond/pywin32) | Windows 한컴오피스 COM 자동화 | PSF |
| [PyMuPDF](https://github.com/pymupdf/PyMuPDF) | PDF 비교 검증 | AGPL |
| [scikit-image](https://scikit-image.org/) | SSIM 픽셀 비교 | BSD |
| 한컴오피스 한글 2024 (Windows) | COM 자동화 대상 | 한글과컴퓨터 (별도 라이선스 필요) |
### 경로 B — Rust + rhwp (크로스플랫폼, COM 불필요)
본 저장소의 `hwp-automate-poc/` 와 `hwp-automate-py/` 서브프로젝트는 다음 두 외부 오픈소스 프로젝트 위에서 만들어졌습니다.
#### 🦀 [edwardkim/rhwp](https://github.com/edwardkim/rhwp) — 핵심 엔진 (직접 의존)
- **저자:** Edward Kim ([@edwardkim](https://github.com/edwardkim))
- **라이선스:** MIT
- **설명:** Rust + WebAssembly 기반 오픈소스 HWP/HWPX 뷰어/에디터. v0.7.x 시점 891+ 테스트, hyper-waterfall 방법론(작업지시자-AI 페어 프로그래밍)으로 개발.
- **본 저장소가 사용하는 방법:**
- `../codebase/rhwp` 위치에 별도 git clone (본 저장소에 포함되지 않음)
- `hwp-automate-poc/Cargo.toml` 과 `hwp-automate-py/Cargo.toml` 이 `path = "../../codebase/rhwp"` 로 의존
- rhwp 코드는 일절 수정하지 않고 upstream 그대로 사용
- **활용 모듈:** `DocumentCore` (IR 빌더), `parse_document`, `serialize_hwp`, `parser::cfb_reader::LenientCfbReader` (비표준 CFB 메타 lenient 파싱), `serializer::mini_cfb::build_cfb` (CFB v3 writer), `model::control::Control::Table`
- **왜 rhwp 인가:** Mac/Linux/Windows 어디서든 한컴오피스 설치 없이 .hwp 처리가 가능한 유일한 성숙한 오픈소스 엔진. 한글과컴퓨터의 공개 문서를 참고하여 구현된 IR 모델·파서·직렬화기를 그대로 활용합니다.
#### 🪝 [golbin/hop](https://github.com/golbin/hop) — 설계 패턴 출처 (참고만, 직접 의존 안 함)
- **저자:** golbin ([@golbin](https://github.com/golbin))
- **라이선스:** MIT
- **설명:** Tauri 2 기반 macOS/Windows/Linux 데스크톱 HWP 뷰어·에디터. rhwp 를 third_party 서브모듈로 통합한 운영 환경 사례.
- **본 저장소가 흡수한 패턴:**
- `DocumentCore::from_bytes(bytes)` — 양식 로드 표준 진입점 (hop 의 `editable_core_from_bytes`)
- `mutate_document(operation, args)` JSON 디스패처 → 본 저장소의 `fill_template(operations=[...])` 다중 op 디자인 영감
- rhwp 의 raw IR 을 외부에서 만지지 않고 `*_native` 메서드 경유하는 boundary 정책
- **의존 방식:** 코드 의존 없음. 단지 hop 의 코드를 읽고 좋은 패턴을 흡수.
- **왜 hop 패턴인가:** rhwp 를 production 환경에서 어떻게 쓰는지 보여주는 가장 완성도 높은 오픈소스 사례. "rhwp 와의 깔끔한 boundary", "JSON 디스패처", "atomic save" 같은 운영 정책을 자연스럽게 채택할 수 있게 해주었습니다.
#### 🦀 추가 Rust 라이브러리 (rhwp 가 transitive 로 사용)
| 라이브러리 | 역할 | 라이선스 |
|---|---|---|
| [PyO3](https://github.com/PyO3/pyo3) | Rust ↔ Python FFI 바인딩 (직접 의존) | Apache-2.0 / MIT |
| [maturin](https://github.com/PyO3/maturin) | abi3 wheel 빌드 도구 | MIT |
| 그 외 rhwp 의 transitive 의존: cfb, byteorder, zip, quick-xml, encoding_rs, image, usvg, pdf-writer, ttf-parser 등 | 각 crate 의 MIT/Apache-2.0 |
#### 의존성 디렉토리 레이아웃
```
temp_git/ (사용자 작업 루트)
├── hwpx-generator/ (이 저장소)
│ ├── hwp-automate-poc/ (Rust PoC, rhwp 사용)
│ ├── hwp-automate-py/ (Python 바인딩, rhwp 사용)
│ └── ...
└── codebase/ (외부 참조, 본 저장소에 포함 안 됨)
├── rhwp/ ← gh repo clone edwardkim/rhwp
└── hop/ ← gh repo clone golbin/hop (참고용)
```
운영 시 vendor 고정이 필요하면 hop 의 `third_party/rhwp` git submodule 패턴을 따라할 수 있습니다. 현재 PoC 단계에선 path 의존이 더 빠른 반복을 위해 유지.
### 한글 / 한컴 상표 안내
- **"한글", "한컴", "HWP", "HWPX"** 는 주식회사 한글과컴퓨터의 등록 상표입니다.
- 본 프로젝트(hwpx-generator)는 한글과컴퓨터와 제휴, 후원, 승인 관계가 없는 **독립적인 오픈소스 작업**입니다.
- HWP/HWPX 포맷 처리는 한글과컴퓨터의 공개 문서를 참고한 다음 도구들을 활용합니다:
- 경로 A: lxml (XML 직접 편집) + 한컴오피스 한글 2024 (COM 자동화 — 별도 라이선스 필요)
- 경로 B: rhwp (오픈소스 Rust 엔진, MIT)
### 외부 재배포 / 공개 시
본 저장소를 외부에 재배포하거나 공개 자산화할 경우 다음을 준수하시기 바랍니다.
1. rhwp 와 hop 의 MIT 라이선스 텍스트 동봉 (또는 명시적 링크)
2. "한글", "한컴", "HWP", "HWPX" 상표 안내 유지
3. rhwp / hop 저자(@edwardkim, @golbin) 의 기여를 본 README 와 동일 수준으로 표기
## 라이선스
이 프로젝트는 내부 업무 자동화 목적으로 개발되었습니다. 외부 의존(rhwp, hop, lxml, pywin32 등) 의 각 라이선스 조건은 위 Acknowledgement 섹션을 참고하세요.