Metadata-Version: 2.4
Name: jakal-hwpx
Version: 0.3.0b3
Summary: Round-trip safe HWPX reader/editor/writer for Python
Author: Ahnd6474
License-Expression: MIT
Project-URL: Homepage, https://github.com/Ahnd6474/jakal-hwpx
Project-URL: Source, https://github.com/Ahnd6474/jakal-hwpx
Project-URL: Issues, https://github.com/Ahnd6474/jakal-hwpx/issues
Project-URL: Documentation, https://github.com/Ahnd6474/jakal-hwpx#readme
Project-URL: Releases, https://github.com/Ahnd6474/jakal-hwpx/releases
Keywords: hwpx,hancom,document,xml,library
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Editors :: Word Processors
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: THIRD_PARTY_NOTICES.md
Requires-Dist: lxml<6,>=5.0.0
Requires-Dist: olefile<1,>=0.47
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: setuptools>=77; extra == "dev"
Requires-Dist: tox>=4.0; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Provides-Extra: release
Requires-Dist: build>=1.2; extra == "release"
Requires-Dist: pytest>=8.0; extra == "release"
Requires-Dist: setuptools>=77; extra == "release"
Requires-Dist: twine>=5.0; extra == "release"
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == "test"
Dynamic: license-file

# jakal-hwpx

파이썬에서 HWPX와 HWP 문서를 읽고, 수정하고, 검증하고, 변환하는 라이브러리입니다.

이 프로젝트는 한컴 GUI를 그대로 흉내 내는 편집기가 아닙니다. 대신 코드로 문서를 만들고, 구조화된 내용을 바꾸고, 저장 전후를 검증하는 데 초점을 둡니다. 템플릿 생성, 대량 치환, 표/필드/머리말/꼬리말 수정 같은 자동화 작업에 잘 맞습니다.

## 목차

- [설치](#설치)
- [빠른 시작](#빠른-시작)
- [이 라이브러리로 할 수 있는 일](#이-라이브러리로-할-수-있는-일)
- [어떤 객체를 쓰면 되는가](#어떤-객체를-쓰면-되는가)
- [지원 범위](#지원-범위)
- [주요 API](#주요-api)
- [예제](#예제)
- [검증과 테스트](#검증과-테스트)
- [추가 문서](#추가-문서)
- [라이선스](#라이선스)

## 설치

요구 사항:

- Python 3.11 이상
- Git LFS 불필요

PyPI에서 설치:

```bash
python -m pip install --upgrade pip
python -m pip install jakal-hwpx
```

로컬 체크아웃에서 설치:

```bash
python -m pip install --upgrade pip
python -m pip install .
```

개발 의존성까지 설치:

```bash
python -m pip install -e .[dev]
```

패키지 이름은 `jakal-hwpx`, import 경로는 `jakal_hwpx`입니다.

## 빠른 시작

새 문서를 만들고 HWPX와 HWP 둘 다 저장하려면 `HancomDocument`가 가장 단순합니다.

```python
from jakal_hwpx import HancomDocument

doc = HancomDocument.blank()
doc.metadata.title = "분기 보고서"

doc.append_header("사내 배포용")
doc.append_paragraph("매출 요약")
doc.append_table(rows=2, cols=2, cell_texts=[["Q1", "Q2"], ["120", "135"]])
doc.append_equation("x+y", width=3200, height=1800)

doc.write_to_hwpx("build/report.hwpx")
doc.write_to_hwp("build/report.hwp")
```

HWPX만 직접 만질 거라면 `HwpxDocument`가 가장 짧습니다.

```python
from jakal_hwpx import HwpxDocument

doc = HwpxDocument.blank()
doc.set_metadata(title="Hello")
doc.append_paragraph("Hello HWPX")
doc.save("build/hello.hwpx")
```

기존 HWP를 바로 수정하려면 `HwpDocument`를 쓰면 됩니다.

```python
from jakal_hwpx import HwpDocument

doc = HwpDocument.open("input.hwp")
doc.append_paragraph("추가 문단")
doc.append_hyperlink("https://example.com", text="Example")
doc.strict_validate()
doc.save("build/edited.hwp")
```

## 이 라이브러리로 할 수 있는 일

- HWPX 문서를 직접 열고 수정하고 저장
- HWP 문서를 순수 Python으로 열고 수정하고 저장
- HWP와 HWPX를 `HancomDocument` 기준으로 오가며 같은 편집 모델로 다루기
- 문단, 스타일, 표, 그림, 필드, 각주/미주, 머리말/꼬리말, 섹션 설정 같은 구조화된 요소 수정
- 저장 전후에 엄격 검증과 왕복 테스트로 결과 확인
- 필요하면 저수준 API로 내려가 HWP binary record를 직접 조사

이 라이브러리는 특히 이런 작업에 잘 맞습니다.

- 문서 템플릿 생성
- 필드 채우기
- 대량 텍스트 치환
- 표/도형/그림 데이터 갱신
- 문서 포맷 변환 자동화
- 저장 후 깨지지 않는지 확인하는 배치 처리

## 어떤 객체를 쓰면 되는가

처음 고를 때는 아래 표만 보면 됩니다.

| 객체 | 언제 쓰면 좋은가 | 설명 |
|---|---|---|
| `HancomDocument` | HWP와 HWPX를 같은 코드로 다루고 싶을 때 | 가장 권장되는 공통 편집 모델 |
| `HwpxDocument` | 입력과 출력이 둘 다 HWPX일 때 | 가장 넓은 직접 편집 범위 |
| `HwpDocument` | 기존 HWP를 직접 수정하거나 HWP로 저장할 때 | HWP 전용 객체 API |
| `HwpHwpxBridge` | HWP/HWPX 사이를 오가며 한쪽 결과물을 재료로 다른 쪽을 만들 때 | 문서 전환을 묶는 도우미 객체 |
| `HwpBinaryDocument` | binary record, stream, docinfo를 직접 봐야 할 때 | 가장 저수준 API |

짧게 말하면:

- 처음 시작: `HancomDocument`
- HWPX만 편집: `HwpxDocument`
- HWP 직접 편집: `HwpDocument`
- reverse engineering이나 디버깅: `HwpBinaryDocument`

## 지원 범위

이 README의 지원 표는 "느낌"이 아니라 아래 기준으로 판정합니다.

- `직접 수정·재오픈`: 공개 API로 생성 또는 수정한 뒤 저장하고, 다시 열어서 같은 의미로 복원되는 자동 테스트가 있습니다.
- `왕복 보존`: `HWP -> HWPX`, `HWPX -> HWP`, 또는 `A -> HancomDocument -> B` 경로에서 의미가 유지되는 자동 테스트가 있습니다.
- `strict lint`: 저장 전 구조 검사를 하는 `strict_lint_errors()` 또는 `strict_validate()`가 연결돼 있습니다.
- `metadata-backed`: target 포맷에 native 자리가 없어 라이브러리 metadata나 carrier를 함께 써서 의미를 보존합니다. 현재 `memo/comment`의 `author/memo_id/anchor_id/order/visible`와 `form.placeholder`가 대표적인 예시입니다. memo carrier는 `Text` parameter를 pairing key로 저장하고, 없으면 기존 순서 기반으로 병합합니다.
- `정규화`: richer source를 target 포맷 제약에 맞게 일부 normalize해서 저장합니다.

표에 있는 항목만 "지원 약속"으로 봐야 합니다. 표에 없는 항목은 읽히더라도 공개 API나 안정성 계약의 일부로 보지 않는 편이 안전합니다.

### 문서 흐름별 판정 기준

| 흐름 | 판정 | 최소 기준 | 대표 테스트/근거 |
|---|---|---|---|
| `HWPX -> HWPX` | 검증됨 | 직접 수정·재오픈 + strict lint | `tests/test_document_model.py`, `tests/test_extended_features.py` |
| `HWP -> HWP` | 검증됨 | 직접 수정·재오픈 + strict lint + binary/reencode 회귀 | `tests/test_hwp_binary.py`, `tests/test_hwp_document.py`, `scripts/audit_hwp_lossless_roundtrip.py` |
| `HWP -> HWPX` | 검증됨 | HWP를 읽어 HWPX로 재생성하고 block/control 의미 보존 확인 | `tests/test_hancom_document.py`, `tests/test_bridge.py` |
| `HWPX -> HWP` | 검증됨, 단 정규화 있음 | HWPX를 읽어 HWP로 재생성하고 reopen/bridge roundtrip 확인 | `tests/test_hancom_document.py`, `tests/test_bridge.py` |

대표 회귀 명령은 아래와 같습니다. 통과 개수는 테스트 추가나 환경에 따라 변하므로, 지원 표의 근거는 숫자보다 "어떤 명령과 어떤 파일이 어떤 경로를 검증하는가"를 기준으로 읽는 편이 안전합니다.

```bash
python -m pytest tests/test_document_model.py tests/test_extended_features.py tests/test_hwp_binary.py tests/test_hwp_document.py tests/test_hancom_document.py tests/test_bridge.py tests/test_hancom_interop.py -q
```

### 컨트롤 패밀리별 지원 범위

아래 표도 같은 기준을 씁니다. `직접 수정·재오픈`은 해당 포맷을 직접 편집하는 public API가 있다는 뜻이고, `왕복 보존`은 교차 포맷 변환에서 의미가 유지된다는 뜻입니다.

| 항목 | `HWPX -> HWPX` | `HWP -> HWP` | `HWP -> HWPX` | `HWPX -> HWP` | 비고 |
|---|---|---|---|---|---|
| 문단, 텍스트, 스타일 | 직접 수정·재오픈 | 직접 수정·재오픈 | 왕복 보존 | 왕복 보존(정규화) | 가장 넓게 테스트된 축 |
| 머리말, 꼬리말 | 직접 수정·재오픈 | 직접 수정·재오픈 | 왕복 보존 | 왕복 보존(정규화) | `apply_page_type` 포함 |
| 필드, 북마크, 하이퍼링크 | 직접 수정·재오픈 | 직접 수정·재오픈 | 왕복 보존 | 왕복 보존(정규화) | HWP native field subtype 보존 |
| 각주, 미주, 자동번호, 페이지번호 | 직접 수정·재오픈 | 직접 수정·재오픈 | 왕복 보존 | 왕복 보존(정규화) | setter와 bridge parity 있음 |
| 섹션 설정, page border fill, visibility | 직접 수정·재오픈 | 직접 수정·재오픈 | 왕복 보존 | 왕복 보존(정규화) | strict lint와 section-level 회귀 포함 |
| 표 | 직접 수정·재오픈 | 직접 수정·재오픈 | 왕복 보존 | 왕복 보존(정규화) | cell, margin, spacing, border fill, span 계열 포함 |
| 그림 | 직접 수정·재오픈 | 직접 수정·재오픈 | 왕복 보존 | 왕복 보존(일부 정규화) | crop, line style, layout, 일부 native probe 반영 |
| 도형, `connectLine` | 직접 수정·재오픈 | 직접 수정·재오픈 | 왕복 보존 | 왕복 보존(일부 정규화) | subtype wrapper와 주요 setter 포함 |
| 수식 | 직접 수정·재오픈 | 직접 수정·재오픈 | 왕복 보존 | 왕복 보존(일부 정규화) | layout/outMargins/rotation surface 포함 |
| OLE | 직접 수정·재오픈 | 직접 수정·재오픈 | 왕복 보존 | 왕복 보존(일부 정규화) | extent, line style, metadata 포함 |
| 차트 | 직접 수정·재오픈 | 직접 수정·재오픈(`metadata-backed`) | 왕복 보존 | 왕복 보존(`metadata-backed`) | HWPX는 native chart schema, HWP는 semantic metadata layer 사용 |
| form object | 직접 수정·재오픈 | 직접 수정·재오픈(`metadata-backed`) | 왕복 보존 | 왕복 보존(`metadata-backed`) | HWPX는 native form tag를 사용합니다. `label/items/value/checked/locked`는 native 쪽에 있고, `placeholder`는 Hancom native HWPX probe에서 전용 slot이 확인되지 않아 `JAKAL_FORM_META` carrier를 씁니다. HWP는 semantic metadata layer 사용 |
| memo/comment | 직접 수정·재오픈 | 직접 수정·재오픈(텍스트 native, 추가 metadata는 `metadata-backed`) | 왕복 보존(native + carrier 혼합) | 왕복 보존(native + `metadata-backed`) | HWPX 텍스트는 native `hiddenComment`, 추가 metadata는 `JAKAL_MEMO` carrier 사용. carrier는 `Text` parameter로 native memo와 먼저 matching하고, 없을 때만 순서로 병합합니다. Hancom native HWPX/HWPML probe에서 전용 slot이 아직 확인되지 않음 |

### 안정성에 대해

이 프로젝트는 한컴 GUI보다 "자동화 자유도" 쪽이 강합니다. 사용자가 화면에서 임의 위치를 마우스로 편집하는 범용 GUI 편집기 역할보다는, 코드로 반복 작업을 처리하고 저장 후 안정성을 검증하는 데 더 잘 맞습니다.

무변경 `HWP -> HWP` 재인코드 쪽은 lossless audit이 붙어 있고, 최근 기준으로 corpus 64건이 byte-identical입니다. 지원된 control 계열은 save/reopen 회귀와 교차 포맷 roundtrip 테스트를 같이 유지합니다.

## 주요 API

### `HancomDocument`

가장 권장되는 공통 편집 모델입니다.

주요 메서드:

- `HancomDocument.blank()`
- `HancomDocument.read_hwpx(path)`
- `HancomDocument.read_hwp(path)`
- `append_paragraph()`
- `append_table()`, `append_picture()`, `append_shape()`, `append_equation()`, `append_ole()`
- `append_field()`, `append_hyperlink()`, `append_bookmark()`, `append_note()`
- `append_header()`, `append_footer()`
- `write_to_hwpx(path)`
- `write_to_hwp(path)`

### `HwpxDocument`

HWPX를 직접 편집할 때 쓰는 주력 객체입니다.

주요 메서드:

- `HwpxDocument.open(path)`
- `HwpxDocument.blank()`
- `append_paragraph()`
- `append_header()`, `append_footer()`
- `append_table()`, `append_picture()`, `append_shape()`, `append_equation()`, `append_ole()`
- `append_field()`, `append_hyperlink()`, `append_bookmark()`, `append_note()`
- `section_settings()`
- `strict_lint_errors()`, `strict_validate()`
- `save(path)`

### `HwpDocument`

HWP 편집에 쓰는 주력 객체입니다.

주요 메서드:

- `HwpDocument.open(path)`
- `HwpDocument.blank()`
- `append_paragraph()`
- `append_table()`, `append_picture()`, `append_shape()`, `append_equation()`, `append_ole()`
- `append_field()`, `append_hyperlink()`, `append_bookmark()`, `append_note()`
- `append_header()`, `append_footer()`, `append_auto_number()`
- `tables()`, `pictures()`, `fields()`, `notes()`, `section(index)`
- `strict_lint_errors()`, `strict_validate()`
- `save(path)`

### `HwpHwpxBridge`

이미 가진 문서를 기준으로 HWP/HWPX/HancomDocument를 오가며 저장할 때 편리한 도우미 객체입니다.

주요 메서드:

- `HwpHwpxBridge.open(path)`
- `HwpHwpxBridge.from_hwp(source)`
- `HwpHwpxBridge.from_hwpx(source)`
- `hancom_document()`
- `hwp_document()`
- `hwpx_document()`
- `save_hwp(path)`
- `save_hwpx(path)`
- `save(path)`

### `HwpBinaryDocument`

가장 저수준의 HWP API입니다.

다음 상황에서 씁니다.

- binary stream을 직접 보고 싶을 때
- `DocInfoModel`, `SectionModel`을 다뤄야 할 때
- reencode나 probe 작업을 할 때
- 지원 매핑을 더 넓히기 위해 record tree를 조사할 때

## 예제

### HWPX 열고 수정 후 저장

```python
from jakal_hwpx import HwpxDocument

doc = HwpxDocument.open("input.hwpx")
doc.replace_text("초안", "최종")
doc.append_paragraph("승인 완료")
doc.strict_validate()
doc.save("build/edited.hwpx")
```

### HWP 열고 수정 후 저장

```python
from jakal_hwpx import HwpDocument

doc = HwpDocument.open("input.hwp")
doc.append_paragraph("추가 문단")
doc.append_hyperlink("https://example.com", text="Example")
doc.strict_validate()
doc.save("build/edited.hwp")
```

### HWP를 HWPX로 변환

```python
from jakal_hwpx import HancomDocument

doc = HancomDocument.read_hwp("input.hwp")
doc.write_to_hwpx("build/exported.hwpx")
```

### HWPX를 HWP로 변환

```python
from jakal_hwpx import HancomDocument

doc = HancomDocument.read_hwpx("input.hwpx")
doc.write_to_hwp("build/exported.hwp")
```

### bridge 도우미로 포맷 전환

```python
from jakal_hwpx import HwpHwpxBridge

bridge = HwpHwpxBridge.open("input.hwp")
bridge.save_hwpx("build/bridge.hwpx")
bridge.save_hwp("build/bridge-copy.hwp")
```

### HWP record 살펴보기

```python
from jakal_hwpx import HwpBinaryDocument

doc = HwpBinaryDocument.open("input.hwp")
print(doc.file_header().version)
print(doc.docinfo_model().id_mappings_record().named_counts())
print(doc.section_model(0).controls())
```

## 검증과 테스트

핵심 회귀 세트:

```bash
python -m pip install -e .[dev]
python -m pytest tests/test_document_model.py tests/test_extended_features.py tests/test_hwp_binary.py tests/test_hwp_document.py tests/test_hancom_document.py tests/test_bridge.py tests/test_hancom_interop.py -q
```

각 파일은 대략 아래 범위를 봅니다.

- `tests/test_document_model.py`: HWPX 직접 authoring, native HWPX schema, strict lint
- `tests/test_extended_features.py`: HWPX 고급 기능과 batch edit surface
- `tests/test_hwp_binary.py`: HWP reencode, docinfo, section model, binary payload 안정성
- `tests/test_hwp_document.py`: HWP 공개 wrapper/setter, strict lint, save/reopen 회귀
- `tests/test_hancom_document.py`: `HWP <-> HancomDocument <-> HWPX` bridge parity
- `tests/test_bridge.py`: explicit bridge helper와 `HwpHwpxBridge` 공개 API 회귀
- `tests/test_hancom_interop.py`: Hancom smoke/security script contract

release gate와 안정성 검사:

```bash
python scripts/check_release.py
```

HWP lossless audit:

```bash
python scripts/audit_hwp_lossless_roundtrip.py
```

선택적 Hancom smoke validation:

```powershell
powershell -ExecutionPolicy Bypass -File scripts/setup_hancom_security_module.ps1 -DownloadIfMissing
powershell -ExecutionPolicy Bypass -File scripts/run_hancom_smoke_validation.ps1 -InputPath examples\samples\hwpx\AI와_특이점_보고서.hwpx -OutputPath .codex-temp\hancom-smoke\sample.roundtrip.hwpx
powershell -ExecutionPolicy Bypass -File scripts/run_hancom_corpus_smoke_validation.ps1
```

일반적인 사용에선 pure Python 경로가 기본입니다. Hancom 쪽은 "정말 한컴에서 열어도 괜찮은지"를 추가로 확인할 때 쓰면 됩니다.

## 추가 문서

- [HWPX_MODULE.md](./HWPX_MODULE.md): 어떤 객체를 언제 써야 하는지 정리한 모듈 선택 가이드
- [STABILITY_CONTRACT.md](./STABILITY_CONTRACT.md): 지원 범위와 release gate 기준
- [scripts/check_release.py](./scripts/check_release.py): release gate 스크립트
- [scripts/audit_hwp_lossless_roundtrip.py](./scripts/audit_hwp_lossless_roundtrip.py): HWP lossless reencode audit
- [scripts/run_bridge_stability_lab.py](./scripts/run_bridge_stability_lab.py): HWP/HWPX bridge stability matrix
- [examples/SHOWCASE.md](./examples/SHOWCASE.md): 생성 예시 모음
- [RELEASING.md](./RELEASING.md): 배포 절차
- [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md): 샘플 문서와 재배포 관련 고지

고급 도구도 루트 import에서 바로 쓸 수 있습니다.

- `build_hwp_pure_profile()`
- `append_feature_from_profile()`
- `run_template_lab()`
- `hwp_collection` donor scanning 도구

이 도구들은 corpus 분석이나 template-backed HWP 작업에 유용하지만, 일반적인 앱 코드의 기본 진입점은 아닙니다.

## 라이선스

프로젝트 소스 코드는 [MIT](./LICENSE) 라이선스를 따릅니다.

샘플 문서와 생성 결과물은 별도 권리를 가질 수 있습니다. 재배포 전에는 [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md)를 확인하세요.
