Skip to content

Commit afcf0af

Browse files
serithemageclaude
andcommitted
feat: HWP 5.x 바이너리 포맷 지원 추가
HWP 5.x (한글 2002~2022) 바이너리 문서 포맷을 지원합니다. 주요 기능: - OLE2/CFB 컨테이너 파싱 (mscfb 라이브러리) - FileHeader, DocInfo 스트림 파싱 - Section 파싱 (문단, 텍스트 추출) - 테이블 파싱 (행/열, 셀 병합, 셀 내용) - zlib/deflate 압축 해제 지원 - UTF-16LE 텍스트 및 컨트롤 문자 처리 제한 사항: - 암호화/DRM 문서 미지원 - 이미지는 BinData 참조만 지원 문서 추가: - docs/hwp5-schema.md: HWP 5.x 파일 포맷 스키마 - docs/hwp5-research.md: 구현 상태 업데이트 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ac8b11a commit afcf0af

17 files changed

Lines changed: 3003 additions & 6 deletions

File tree

CLAUDE.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ HWP/HWPX → Stage 1 (Parser) → IR → Stage 2 (LLM, optional) → Markdown
2727
```
2828

2929
### Stage 1: Parser (`internal/parser/`)
30-
- **HWPX Parser** (`hwpx/parser.go`): Native XML-based parser for HWPX format (default)
30+
- **HWPX Parser** (`hwpx/parser.go`): Native XML-based parser for HWPX format
31+
- **HWP5 Parser** (`hwp5/parser.go`): Native binary parser for HWP 5.x format (OLE2/CFB container)
3132
- **Upstage Parser** (`upstage/upstage.go`): Optional external API parser, outputs `RawMarkdown` directly (bypasses IR conversion)
3233

3334
### IR - Intermediate Representation (`internal/ir/`)
@@ -51,6 +52,9 @@ HWP/HWPX → Stage 1 (Parser) → IR → Stage 2 (LLM, optional) → Markdown
5152
|------|---------|
5253
| `internal/cli/convert.go` | Main conversion pipeline, parser/LLM orchestration |
5354
| `internal/parser/hwpx/parser.go` | HWPX XML parsing, table/cell span handling |
55+
| `internal/parser/hwp5/parser.go` | HWP5 OLE2 parsing, main entry point |
56+
| `internal/parser/hwp5/section.go` | HWP5 section parsing, table/paragraph extraction |
57+
| `internal/parser/hwp5/text.go` | HWP5 UTF-16LE text extraction with control chars |
5458
| `internal/ir/ir.go` | IR document structure definitions |
5559
| `internal/llm/provider.go` | LLM provider interface |
5660
| `internal/llm/prompt.go` | System prompts for LLM formatting |
@@ -70,3 +74,11 @@ HWP/HWPX → Stage 1 (Parser) → IR → Stage 2 (LLM, optional) → Markdown
7074
- Korean is the primary language for CLI messages, comments, and documentation
7175
- Cell merge handling: rowspan → ``, colspan → empty cell
7276
- Special whitespace elements (`<hp:fwSpace/>`, `<hp:hwSpace/>`) → regular space
77+
78+
## HWP5 Binary Format Notes
79+
80+
- OLE2/CFB container parsed with `github.com/richardlehane/mscfb`
81+
- Records use 4-byte header: TagID (10-bit), Level (10-bit), Size (12-bit)
82+
- Text is UTF-16LE with inline control characters (Extended/Inline use +14 bytes)
83+
- Tables: CTRL_HEADER(" lbt") → TABLE → LIST_HEADER (per cell) → PARA_HEADER/PARA_TEXT
84+
- Compression: zlib or raw deflate (try zlib first, fallback to deflate)

README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ flowchart LR
2222
subgraph Stage1[Stage 1: Parser]
2323
direction TB
2424
Parser{Parser}
25-
Parser --> |기본| HWPX[HWPX Parser]
25+
Parser --> |HWPX| HWPX[HWPX Parser]
26+
Parser --> |HWP| HWP5[HWP5 Parser]
2627
Parser -.-> |선택적| Upstage[Upstage Document Parse]
2728
HWPX --> IR[IR - 중간 표현]
29+
HWP5 --> IR
2830
end
2931
3032
subgraph Stage2[Stage 2: LLM - 선택적]
@@ -83,6 +85,9 @@ go install github.com/roboco-io/hwp2md/cmd/hwp2md@latest
8385
# HWPX 파일을 Markdown으로 변환
8486
hwp2md document.hwpx -o output.md
8587

88+
# HWP 5.x 파일을 Markdown으로 변환
89+
hwp2md document.hwp -o output.md
90+
8691
# 표준 출력으로 변환
8792
hwp2md document.hwpx
8893
```
@@ -182,9 +187,19 @@ hwp2md convert document.hwpx --llm --model llama3.2 --base-url http://localhost:
182187
| 포맷 | 상태 | 설명 |
183188
|------|------|------|
184189
| HWPX | ✅ 지원 | XML 기반 개방형 포맷 (한컴오피스 2014+) |
185-
| HWP 5.x | 🚧 계획 | OLE/CFBF 바이너리 포맷 |
190+
| HWP 5.x | ✅ 지원 | OLE2/CFB 바이너리 포맷 (한글 2002~2022) |
186191
| HWP 3.x | 미정 | 레거시 바이너리 포맷 |
187192

193+
### HWP 5.x 지원 기능
194+
195+
| 기능 | 상태 |
196+
|------|------|
197+
| 문단/텍스트 | ✅ 지원 |
198+
| 테이블 (셀 병합 포함) | ✅ 지원 |
199+
| 이미지 참조 | ⚠️ 부분 지원 |
200+
| 압축 문서 | ✅ 지원 |
201+
| 암호화/DRM 문서 | ❌ 미지원 |
202+
188203
## LLM 프로바이더
189204

190205
| 프로바이더 | 기본 모델 | 설명 |
@@ -237,7 +252,8 @@ hwp2md/
237252
│ │ ├── upstage/ # Upstage Solar
238253
│ │ └── ollama/ # Local Ollama
239254
│ └── parser/ # 문서 파서
240-
│ ├── hwpx/ # HWPX 파서 (내장, 기본)
255+
│ ├── hwpx/ # HWPX 파서 (XML 기반)
256+
│ ├── hwp5/ # HWP 5.x 파서 (바이너리)
241257
│ └── upstage/ # Upstage Document Parse (선택적)
242258
├── docs/ # 문서
243259
└── tests/ # 테스트 데이터
@@ -278,6 +294,7 @@ Stage 1은 문서 구조를 그대로 추출하며, Stage 2는 LLM을 통해 더
278294
- [PRD](docs/PRD.md) - 제품 요구사항
279295
- [기술 스택](docs/tech-stack.md) - 기술 스택 결정
280296
- [HWPX 스키마](docs/hwpx-schema.md) - HWPX 파일 포맷 문서
297+
- [HWP5 스키마](docs/hwp5-schema.md) - HWP 5.x 바이너리 포맷 문서
281298
- [HWPX-Markdown 차이점](docs/hwpx-markdown-differences.md) - 포맷 간 차이점 및 변환 방식
282299

283300
### 개발 가이드

docs/hwp5-research.md

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# HWP 5.x 파일 포맷 조사 결과
2+
3+
## 개요
4+
5+
HWP 5.x는 한글과컴퓨터의 한글 워드프로세서에서 사용하는 바이너리 문서 포맷이다. 한글 2002부터 한글 2022까지 사용되었으며, 2014년부터는 XML 기반의 HWPX 포맷이 병행 지원된다.
6+
7+
### 구현 상태
8+
9+
| 기능 | 상태 | 설명 |
10+
|------|------|------|
11+
| OLE2 파싱 | ✅ 완료 | mscfb 라이브러리 사용 |
12+
| FileHeader | ✅ 완료 | 버전, 압축, 암호화 플래그 파싱 |
13+
| DocInfo | ✅ 완료 | ID 매핑, 글꼴, 스타일 정보 |
14+
| BodyText/Section | ✅ 완료 | 문단, 텍스트 추출 |
15+
| 테이블 | ✅ 완료 | 행/열, 셀 병합, 셀 내용 |
16+
| 이미지 | ⚠️ 부분 | BinData 참조만 지원 |
17+
| 압축 해제 | ✅ 완료 | zlib/deflate 지원 |
18+
| 암호화 | ❌ 미지원 | 암호화 문서 차단 |
19+
| DRM | ❌ 미지원 | DRM 문서 차단 |
20+
21+
## 공식 문서
22+
23+
### 한글 문서 파일 형식 5.0 명세서
24+
- **URL**: https://cdn.hancom.com/link/docs/한글문서파일형식_5.0_revision1.3.pdf
25+
- **버전**: Revision 1.3
26+
- **발행**: 한글과컴퓨터
27+
28+
### 한컴 기술 블로그
29+
- https://tech.hancom.com/python-hwp-parsing-1/
30+
- https://tech.hancom.com/한-글-문서-파일-형식-hwp-포맷-구조-살펴보기/
31+
32+
## 파일 구조
33+
34+
### OLE2 Compound File Binary (CFB) 컨테이너
35+
36+
HWP 5.x 파일은 Microsoft의 OLE2 Compound File 형식을 기반으로 한다. 이는 파일 내부에 여러 스트림(Stream)과 저장소(Storage)를 포함하는 계층적 구조이다.
37+
38+
```
39+
HWP 파일
40+
├── FileHeader # 파일 인식 정보 (고정 256바이트)
41+
├── DocInfo # 문서 정보 (압축/암호화 가능)
42+
├── BodyText/ # 본문 저장소
43+
│ ├── Section0 # 첫 번째 섹션
44+
│ ├── Section1 # 두 번째 섹션
45+
│ └── ...
46+
├── BinData/ # 바이너리 데이터 저장소
47+
│ ├── BIN0001.jpg # 이미지 등
48+
│ └── ...
49+
├── PrvText # 미리보기 텍스트
50+
├── PrvImage # 미리보기 이미지
51+
└── Scripts/ # 스크립트 저장소 (선택)
52+
```
53+
54+
### FileHeader 구조
55+
56+
| 오프셋 | 크기 | 설명 |
57+
|--------|------|------|
58+
| 0 | 32 | 시그니처: "HWP Document File" |
59+
| 32 | 4 | 파일 버전 (예: 5.0.3.0) |
60+
| 36 | 4 | 속성 플래그 |
61+
| ... | ... | 예약 영역 |
62+
63+
**속성 플래그**:
64+
- 비트 0: 압축 여부
65+
- 비트 1: 암호화 여부
66+
- 비트 2: 배포용 문서
67+
- 비트 3: 스크립트 저장
68+
- 비트 4: DRM 보안
69+
- 비트 5: XMLTemplate 저장
70+
- 비트 6: 문서 이력 관리
71+
- 비트 7: 전자 서명
72+
- 비트 8: 공인 인증서 암호화
73+
- 비트 9: 전자 서명 예비
74+
- 비트 10: 공인 인증서 DRM
75+
- 비트 11: CCL 문서
76+
77+
### 레코드 구조
78+
79+
DocInfo와 BodyText/Section 스트림은 레코드(Record) 단위로 데이터가 저장된다.
80+
81+
**레코드 헤더 (4바이트)**:
82+
```
83+
┌─────────────────────────────────────────────────────────────┐
84+
│ Tag ID (10비트) │ Level (10비트) │ Size (12비트) │
85+
└─────────────────────────────────────────────────────────────┘
86+
```
87+
88+
- **Tag ID**: 레코드 종류 식별자
89+
- **Level**: 논리적 계층 표현 (0부터 시작)
90+
- **Size**: 데이터 길이 (바이트), 4095 이상이면 0xFFF로 설정하고 다음 4바이트에 실제 크기
91+
92+
**주요 레코드 태그**:
93+
| 태그 || 설명 |
94+
|------|-----|------|
95+
| HWPTAG_DOCUMENT_PROPERTIES | 0x0010 | 문서 속성 |
96+
| HWPTAG_ID_MAPPINGS | 0x0011 | ID 매핑 테이블 |
97+
| HWPTAG_BIN_DATA | 0x0012 | 바이너리 데이터 정보 |
98+
| HWPTAG_FACE_NAME | 0x0013 | 글꼴 이름 |
99+
| HWPTAG_BORDER_FILL | 0x0014 | 테두리/배경 |
100+
| HWPTAG_CHAR_SHAPE | 0x0015 | 글자 모양 |
101+
| HWPTAG_TAB_DEF | 0x0016 | 탭 정의 |
102+
| HWPTAG_NUMBERING | 0x0017 | 문단 번호 |
103+
| HWPTAG_BULLET | 0x0018 | 글머리표 |
104+
| HWPTAG_PARA_SHAPE | 0x0019 | 문단 모양 |
105+
| HWPTAG_STYLE | 0x001A | 스타일 |
106+
| HWPTAG_PARA_HEADER | 0x0042 | 문단 헤더 |
107+
| HWPTAG_PARA_TEXT | 0x0043 | 문단 텍스트 |
108+
| HWPTAG_PARA_CHAR_SHAPE | 0x0044 | 문단 글자 모양 |
109+
| HWPTAG_PARA_LINE_SEG | 0x0045 | 문단 레이아웃 |
110+
| HWPTAG_CTRL_HEADER | 0x0046 | 컨트롤 헤더 |
111+
| HWPTAG_TABLE | 0x0050 ||
112+
| HWPTAG_LIST_HEADER | 0x0051 | 리스트 헤더 |
113+
114+
### 압축 및 암호화
115+
116+
- **압축**: zlib (DEFLATE) 알고리즘 사용
117+
- **암호화**: 다양한 암호화 방식 지원
118+
- 일반 비밀번호 암호화
119+
- 공인인증서 기반 암호화
120+
- DRM
121+
122+
## 오픈소스 구현체
123+
124+
### Go 언어
125+
126+
현재 Go로 작성된 완전한 HWP 5.x 파서는 찾지 못함. 구현 시 참고할 수 있는 프로젝트들:
127+
128+
### Rust
129+
130+
| 프로젝트 | URL | 특징 |
131+
|----------|-----|------|
132+
| **OpenHWP** | https://github.com/openhwp/openhwp | HWP 5.0 읽기, HWPX 읽기/쓰기, IR 변환 지원 |
133+
| **unhwp** | https://lib.rs/crates/unhwp | HWP 5.0+, HWPX, HWP 3.x 지원, Markdown 출력 |
134+
135+
### Python
136+
137+
| 프로젝트 | URL | 특징 |
138+
|----------|-----|------|
139+
| **pyhwp** | https://github.com/mete0r/pyhwp | 가장 성숙한 구현체, ODT/TXT 변환, HWP Binary Specification 1.1 기반 |
140+
| **hwpers** | https://github.com/Indosaram/hwpers | HWP 5.0 완전 지원 |
141+
| **hwp-extract** | https://github.com/volexity/hwp-extract | 암호화된 HWP 지원, 메타데이터 추출 |
142+
143+
### JavaScript/TypeScript
144+
145+
| 프로젝트 | URL | 특징 |
146+
|----------|-----|------|
147+
| **hwp.js** | https://github.com/hahnlee/hwp.js | TypeScript 기반, 웹 뷰어, cfb-js로 OLE 파싱 |
148+
| **hwp-parser** | https://github.com/BOB-APT-Solution/hwp-parser | JS 코드 추출 등 보안 분석용 |
149+
150+
## 구현 전략 권장사항
151+
152+
### 1. OLE2 파싱
153+
154+
Go에서 OLE2 Compound File을 파싱하기 위한 라이브러리:
155+
- 직접 구현 또는 기존 라이브러리 활용 필요
156+
- 참고: https://github.com/richardlehane/mscfb (Go OLE2 라이브러리)
157+
158+
### 2. 레코드 파싱
159+
160+
1. FileHeader 읽기 → 버전/속성 확인
161+
2. DocInfo 스트림 압축 해제 → 레코드 순회 → ID 매핑 테이블 구축
162+
3. BodyText/SectionN 스트림 압축 해제 → 레코드 순회 → IR 변환
163+
164+
### 3. IR 변환
165+
166+
현재 HWPX 파서와 동일한 IR 구조(`internal/ir/`)로 변환:
167+
- `HWPTAG_PARA_HEADER` + `HWPTAG_PARA_TEXT``ir.Paragraph`
168+
- `HWPTAG_TABLE` + 셀 데이터 → `ir.Table`
169+
- BinData의 이미지 → `ir.Image`
170+
171+
### 4. 참고 구현체
172+
173+
가장 참고하기 좋은 구현체:
174+
1. **pyhwp** (Python): 가장 완성도 높고 문서화 잘 됨
175+
2. **OpenHWP** (Rust): 최신 구현, IR 변환 아키텍처 참고
176+
3. **hwp.js** (TypeScript): 웹 기반 구현 참고
177+
178+
## 구현 완료 항목
179+
180+
1. [x] Go OLE2 라이브러리 선정 → `github.com/richardlehane/mscfb` 사용
181+
2. [x] FileHeader 파싱 구현 → `internal/parser/hwp5/header.go`
182+
3. [x] DocInfo 스트림 파싱 (레코드 구조) → `internal/parser/hwp5/docinfo.go`
183+
4. [x] BodyText/Section 파싱 → `internal/parser/hwp5/section.go`
184+
5. [x] IR 변환 로직 구현 → `internal/parser/hwp5/parser.go`
185+
6. [x] 테이블 파싱 구현 → 셀 병합, 셀 내용 포함
186+
7. [x] 테스트 케이스 작성 → `internal/parser/hwp5/*_test.go`, `tests/e2e_test.go`
187+
188+
## 향후 개선 사항
189+
190+
1. [ ] BinData 이미지 추출 개선
191+
2. [ ] 다양한 HWP 버전 테스트 (5.0.0.0 ~ 5.1.1.0)
192+
3. [ ] 복잡한 테이블 구조 지원 (중첩 테이블 등)
193+
194+
## 참고 자료
195+
196+
- 한글 문서 파일 형식 5.0: https://cdn.hancom.com/link/docs/한글문서파일형식_5.0_revision1.3.pdf
197+
- 한컴 기술 블로그: https://tech.hancom.com/python-hwp-parsing-1/
198+
- pyhwp 문서: https://pyhwp.readthedocs.io/
199+
- OpenHWP: https://github.com/openhwp/openhwp
200+
- unhwp: https://lib.rs/crates/unhwp
201+
- hwp.js: https://github.com/hahnlee/hwp.js
202+
- mscfb (Go OLE2): https://github.com/richardlehane/mscfb

0 commit comments

Comments
 (0)