Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
12 commits
Select commit Hold shift + click to select a range
4d68849
fix(body-patch): strip_runs_by_color가 표를 품은 run에서 안쪽 글을 지우지 않게 (ENG-03)
airmang Nov 10, 2026
a06e1eb
fix(text): 찾아 바꾸기·부분 편집이 줄 바꿈을 hp:lineBreak로 쓰게 (ENG-05)
airmang Nov 10, 2026
1e2ea58
fix(line-cache): 내용이 바뀐 문단에 옛 줄 배치 캐시를 남기지 않게 (ENG-06)
airmang Nov 10, 2026
02fedd3
fix(validator): 빈 글맵시·OLE·동영상을 저장 관문에서 거부 (ENG-23)
airmang Nov 10, 2026
aab0f90
fix(validator): 주석·처리 지시문이 든 문서의 저장이 TypeError로 죽지 않게 (ENG-04)
airmang Nov 10, 2026
1ff07ea
test(body-patch): 본문 바이트 연산의 연산별 테스트를 core에 (ARC-16)
airmang Nov 10, 2026
8d25da8
fix(body-patch): 구역 정의·안쪽 문단을 망가뜨리던 연산을 거부하고 restyle 대상을 바로잡음
airmang Nov 10, 2026
318f52b
docs: 기본 저장 검사가 참조 무결성을 보지 않음을 정확히 적음 (ENG-30)
airmang Nov 10, 2026
c948cc3
docs: 문서 예제 원장의 설명서 해시를 다시 계산
airmang Nov 10, 2026
17183d6
refactor(line-cache): 캐시 안전망을 section.py 안으로 옮기고 공개 멤버를 늘리지 않음 (ENG-0…
airmang Nov 10, 2026
37ce2c2
fix(body-patch): 새 거부 사유를 코드가 있는 오류형으로 (오류 인구조사 관문)
airmang Nov 10, 2026
fa58131
test(pages): 손으로 만든 OLE 개체에 한/글이 쓰는 필수 자식을 채움 (ENG-23 후속)
airmang Nov 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,40 @@

### 고침

- `apply_body_ops`가 문서를 망가뜨리던 네 경우를 거부하거나 바로잡는다(전에는 모두 `applied`에 열림 검사 통과였다).
- 구역 정의(`hp:secPr`)를 품은 문단: `delete_paragraph`는 구역 정의째 지웠고, `insert_paragraph_by_clone`은 구역
정의를 한 벌 더 만들었고, `reorder_paragraphs`는 그 문단을 첫 자리에서 옮겼다. 이제 셋 다 거부한다.
- 글상자·각주·머리말을 품은 문단: `set_paragraph_text`와 `texts`를 준 `insert_paragraph_by_clone`이 새 글을 안쪽
문단의 첫 글 자리에 넣고 나머지 글을 비울 수 있었다. 이제 거부한다(표를 품은 문단은 전부터 거부).
- `restyle_text`: 찾은 글이 표 뒤에 있으면 그 글의 run이 아니라 표 마지막 칸 안 run의 글자 모양을 바꿨다. 이제 글을
직접 가진 run을 바꾼다.
- XML 주석이나 처리 지시문이 `header.xml`이나 줄 배치 캐시가 있는 문단에 든 문서도 저장된다. 전에는 문서가 열리고
열림 검사도 통과했지만, 고치지 않은 저장까지 `TypeError: argument of type 'cython_function_or_method' is not
iterable`로 끝났다(저장 때 도는 검사가 주석 노드의 `tag`를 요소 이름으로 읽었다). 문서 병합(`append_document`)도
같은 오류였다.
- 저장 관문이 필수 자식(`offset`·`orgSz`·`curSz`·`sz`·`pos`, `renderingInfo`)이 없는 글맵시(`hp:textart`)·OLE(`hp:ole`)·
동영상(`hp:video`)도 `hp:rect`처럼 거부한다. `doc.shapes.add_raw("textart"|"ole"|"video")`로 만든 빈 요소는 전에
저장됐고, 그 파일을 한/글 2024가 열다가 경고 없이 종료했다(종료 코드 0xC0000005). `validate_package`·
`validate_editor_open_safety`도 오류로 보고한다. 한/글이 저장한 글맵시·OLE·동영상은 전처럼 통과하고, 빈
수식(`hp:equation`)은 한/글이 정상으로 열므로 막지 않는다.
- `paragraph.apply_model`(글·런을 바꾼 경우), `add_index_mark`, `add_title_mark`, `add_drop_cap`이 고친 문단의 줄 배치
캐시를 지운다. 전에는 문단 내용이 바뀌어도 옛 캐시가 남았다. `apply_model`은 모델이 문단 내용을 바꾸고 캐시는 그대로
돌려준 때만 지우므로, 모델로 캐시 자체를 고치는 쓰임은 전과 같다.
- 저장 때의 낡은 캐시 훑기가, 고친 구역에서 **내용이 바뀐 문단에 남은 옛 캐시**를 지운다. 구역은 읽을 때(그리고 저장할
때마다)의 "문단 내용 + 캐시" 짝을 기억하고, 파일에 있던 캐시가 다른 내용 위에 놓여 있으면 그 캐시를 지운다. 편집
API가 캐시 지우기를 빠뜨려도 글이 한 글자만 늘어난 경우까지 잡힌다(전에는 마지막 줄 뒤 글이 그 줄 폭의 2배를 넘어야
지워졌다). 내용이 그대로인 문단의 캐시와 읽은 뒤 새로 쓴 캐시는 건드리지 않는다. 고치지 않은 구역은 전처럼 바이트
그대로 저장된다. 폭 추정으로 지우는 기존 기준(2배)은 그대로다.
- 찾아 바꾸기와 부분 편집도 줄 바꿈을 `hp:lineBreak`로, 탭을 `hp:tab`으로 쓴다. `run.replace_text`,
`doc.text.replace`(기본·`everywhere=True`), `doc.replace_text_in_runs`, `paragraph.apply_model`·`run.apply_model`은
바꿀 글의 `"\n"`을 `hp:t` 안에 글자로 남겼고, `paragraph.set_text_preserving_runs`는 `"\n"`을 글자로 남기고 탭은
지웠다. 이제 `paragraph.text =`·`add_run`과 같은 요소로 쓴다(`"\r\n"`·`"\r"`도 줄 바꿈 하나).
- `strip_runs_by_color`가 표·글상자·각주를 품은 대상 색 run에서 안쪽 run의 글을 지우지 않는다. 전에는 run의 여는
태그를 그 뒤 첫 `</hp:run>`과 짝지어, 빨강 run이 표를 품고 있으면 첫 칸의 검은 글이 지워지고도 `applied`로
보고됐다. 빈 run(`<hp:run …/>`) 뒤의 다른 색 글도 같은 까닭으로 지워졌다. 이제 대상 run이 직접 가진 글만 비우고,
표 등을 품은 run을 비운 수를 `runsHoldingNestedRuns`로 알린다. 줄 배치 캐시는 글이 바뀐 문단의 것만 지운다(전에는
표 뒤 글이 지워진 바깥 문단의 캐시가 남았고, 원래 비어 있던 문단의 캐시는 지워졌다).

- 표 행·열·칸 구조 편집(객체 경로와 `apply_table_ops`)이 쪽을 넘어 나뉜 표의 저장 높이(`hp:sz`)를 그대로 둔다. 한/글은
그런 표에 첫 쪽 조각의 높이만 저장하므로(같은 행의 표가 놓인 자리에 따라 30463·17551·5865로 저장됨) 행 높이 증감을
더할 근거가 없다. 전에는 증감을 그대로 더해, 평가계획 채우기가 13행 표(행 28762, 저장 13222)에서 9행을 지우면
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ doc.save_to_path("계획.hwpx")
- 배치 추정(실험): `estimate_pages`는 한/글 없이 쪽 수와 줄마다의 쪽·세로 위치를 추정합니다.
`write_line_caches`는 한/글이 저장할 때 쓰는 줄 배치 정보를, 한/글과 같은 값을 확실히 아는 문단에만 씁니다.
rhwp처럼 이 정보로 그림 자리를 찾는 뷰어에서도 그림이 보이게 됩니다. 기본으로는 쓰지 않습니다.
- 검증·안전: 패키지 구조 검사 CLI, 저장 전 열림 검사, 저장 결과 보고(`return_report=True`).
- 검증·안전: 패키지 구조 검사 CLI, 저장 전 열림 검사, 저장 결과 보고(`return_report=True`). 기본 저장은 없는 글자 모양·
문단 모양을 가리키는 참조까지는 확인하지 않습니다. 그 검사는 `save_report(quality=QualityPolicy.strict())`가 합니다
([검사 범위](docs/usage.md#변경-사항-저장과-결과물-확인)).

자세한 내용: [5분 퀵스타트](docs/quickstart.md) · [사용 가이드](docs/usage.md) · [API 레퍼런스](https://airmang.github.io/python-hwpx/) · [예제](docs/examples.md) · [지원 매트릭스](docs/support-matrix.md)

Expand Down
3 changes: 2 additions & 1 deletion README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ For higher-level workflows (document authoring, form filling, exam typesetting),
paragraphs where it is sure to match Hangul. Viewers such as rhwp, which place pictures from that data, then
show them too. By default nothing is written.
- Verify and stay safe: a package-structure validation CLI, an open-safety check before saving, and a save
report (`return_report=True`).
report (`return_report=True`). A default save does not check that the character and paragraph shapes the
text points at exist; `save_report(quality=QualityPolicy.strict())` does.

More: [five-minute quickstart](docs/quickstart.md) · [usage guide](docs/usage.md) · [API reference](https://airmang.github.io/python-hwpx/) · [examples](docs/examples.md) · [support matrix](docs/support-matrix.md)

Expand Down
6 changes: 3 additions & 3 deletions docs/python-example-ledger.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
"fenceCount": 117,
"sourceSha256": "a164785f35a43a2f224d2b1b906b71f113de67c0c7e05dea6e3c3a4fa3cb989d",
"manualSha256": {
"README.md": "d63726c59b063948126eaaa05022b13c3264c88019466ee4bb67db6d8ba27fc3",
"README_EN.md": "185f69b6cde4093acdde7bfb2505aa2e9e31b2bb8f46763dc93a854702742082",
"README.md": "645e47f52ac3cbdc32b75a600e37dcfb6e64af089ec75b2acf350582e4ddd701",
"README_EN.md": "d6afeebcd4281108b49ac8763443943cece4932dd9e6b49b6a8c572d178e2f05",
"docs/quickstart.md": "44d8ca82ee70c0e3dfdf087713a4e4ce935e64cd3f6a546311f95b054a285a18",
"docs/recipes-traversal.md": "b63f42b1a6ed23812688599474dcf83300509b86a94b5daceae679bfdbde9343",
"docs/mutation-semantics.md": "6de1471f64c1cd8db2e661c413d92bfd27221f5d930708942ac47c6aff2d608b",
"docs/usage.md": "ec70af303f2efaed9fe31f633af01817a4f2f1c6f31174dce4c21fadfd3b6948",
"docs/usage.md": "885e37139630b996f817a42ad8fc1c05daabe75acf54429a2cad49fa6059fb81",
"docs/examples.md": "f03f0ecee4faef94dda05c92dd25672dfbde3a7447f823603b611bea59f6432e",
"docs/faq.md": "cd82e2f1e740cff3f50e620c060ed8ae0aa130271fa373eca3a07a70fb9ca604",
"docs/index.md": "111754d7637414e0078ba0c7bbcf19d1a4550a68202acf3dd2c89c0748ea0050",
Expand Down
9 changes: 9 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,8 @@ for mark in span.marks:
paragraph.apply_model(model)
```

모델로 문단의 글이나 런을 바꾸면 `apply_model()`이 그 문단의 줄 배치 캐시(`hp:linesegarray`)를 지웁니다. 한/글은 캐시의 줄 수를 믿고 글을 그 안에 눌러 담으므로, 바뀐 내용 위에 옛 캐시를 남기지 않습니다. 런 텍스트에 넣은 `"\n"`과 탭은 `hp:lineBreak`·`hp:tab`으로 기록됩니다.

## 런 서식 지정

헤더의 `<hh:charPr>` 정의는 여러 런이 공유하는 문자 서식을 담고 있습니다. `HwpxDocument.ensure_run_style()`은 굵게/기울임/밑줄 조합에 맞는 `charPr` 항목을 찾아 ID를 반환하고, 필요한 경우 새 항목을 생성합니다. 문단 객체는 `add_run()` 메서드를 통해 해당 서식을 즉시 사용하는 런을 만들 수 있습니다.
Expand Down Expand Up @@ -1255,6 +1257,13 @@ for element in paragraphs_with_bookmark:
| `save_to_stream(stream)` | 검증된 HWPX만 `BinaryIO` 스트림에 기록 |
| `to_bytes()` | 검증된 HWPX를 `bytes`로 직렬화 |

세 방법의 기본 검사가 보는 범위는 다음과 같습니다.

- 보는 것: ZIP과 각 XML 파트가 읽히는지, 한/글이 열지 못하는 구조(필수 자식이 없는 표·도형·글맵시·OLE·동영상, 빈 컨트롤, 필수 파트·속성 누락), 글보다 뒤를 가리키거나 줄 수가 크게 모자란 줄 배치 캐시.
- 보지 않는 것: **참조 무결성**. 본문이 가리키는 글자 모양(`charPrIDRef`)·문단 모양(`paraPrIDRef`)·스타일·테두리·그림 자료가 `header.xml`과 패키지에 실제로 있는지는 기본 저장에서 확인하지 않습니다. 없는 id를 가리키는 문서도 기본 검사를 통과해 저장됩니다.

참조까지 확인하려면 `save_report(path, quality=QualityPolicy.strict())`를 쓰거나(`REFERENCE_INTEGRITY_FAILED`로 보고), `hwpx.tools.id_integrity.check_id_integrity(document)`를 직접 부릅니다. 이 검사는 문서를 여는 시간의 0.7배쯤 더 걸리고, 한/글이 저장한 문서 가운데에도 걸리는 것이 있어(정의되지 않은 글머리표 `idRef="0"`) 기본 저장에는 넣지 않았습니다.

```python
# 파일로 저장
document.save_to_path("result.hwpx")
Expand Down
Loading
Loading