Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@

### 추가

- `from_markdown(markdown, style=...)`(`hwpx.tools.markdown_import`): 흔히 쓰는 Markdown 부분 집합으로 새 문서를 만든다.
`#`~`####` 제목, 문단, `-`/`*`/`1.` 목록(들여쓰기로 수준을 나누고 한/글 목록으로 적용), `|` 표(구분선 위 행은
굵게·음영 머리글), `**굵게**`·`*기울임*`을 쓰고, 그 밖의 Markdown은 평문으로 쓰며 오류를 내지 않는다. 이미 있는
문서에는 `doc.text.append_markdown(markdown)`으로 덧붙인다. 모양은 장르 중립 `MarkdownStyle`
(같은 모듈, 기본 `#` 16pt·`##` 12pt 굵게)로 바꾼다. 공문·가정통신문 같은 장르 프리셋은 응용
계층이 이 스타일로 넘긴다.
- `HwpxDocument.new(inherit_style=False)`: 깨끗한 새 문서. `add_paragraph`가 앞 문단의 문단·글자 모양을 물려받지
않고 바탕글에서 시작하므로, 제목이나 목록 다음 문단에 개요·목록 번호가 따라붙지 않는다. `add_paragraph`의
`inherit_style` 기본값은 이제 `None`(문서를 따름)이고, 기본 문서에서는 전처럼 물려받는다.
- `doc.text.blocks()`가 본문 문단과 표 칸 문단(칸 안 표 포함)을 문서 순서대로, 위치로 정해지는 id(`s0p16`,
`s0p16t0r3c0q0`)와 사람이 읽는 위치(`표1 4행 1열`)와 함께 내놓는다. 병합 칸은 대표 칸에서 한 번만 나오고, id는
저장·재열기 뒤에도 같다(#666).
Expand Down
6 changes: 6 additions & 0 deletions docs/architecture/module-ownership.json
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,12 @@
"approvedBy": "owner-authorized issue565 additive implementation",
"rationale": "QName-aware bounded byte-span selection of text after a table within its own paragraph; reusable format behavior. The caller supplies the predicate, so this module contains no genre, institution, workflow or caption policy. Secure XML parsing and existing OPC markup tokenization validate namespace scopes without reserializing source bytes."
},
{
"path": "src/hwpx/tools/markdown_import.py",
"disposition": "core",
"approvedBy": "markdown-import-for-small-models",
"rationale": "from_markdown/append_markdown: writes a common Markdown subset (headings, paragraphs, nested lists, pipe tables, bold/italic) into HWPX through the document's own paragraph, list and table authoring. MarkdownStyle is genre-neutral sizing only; genre presets (official letters, notices) stay with the application layer that passes a style in."
},
{
"path": "src/hwpx/_document/text_blocks.py",
"disposition": "core",
Expand Down
3 changes: 2 additions & 1 deletion docs/editor-surface-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ support-matrix.md` · `docs/coverage-ledger.json`)과 OWPML 스키마 ·

<!-- AUTO-GENERATED:BEGIN (scripts/editor_surface_inventory.py) -->

자동 생성 시점 교차 확인: 원장(요소 축) 132건 render-verified(요소 345개 중) · 캐파빌리티 영역(기능 축) 42개 등록됨.
자동 생성 시점 교차 확인: 원장(요소 축) 132건 render-verified(요소 345개 중) · 캐파빌리티 영역(기능 축) 43개 등록됨.

### 서식

Expand Down Expand Up @@ -122,6 +122,7 @@ support-matrix.md` · `docs/coverage-ledger.json`)과 OWPML 스키마 ·

| 기능 | 엔진 상태 | 근거 | 실한컴 검증 |
|---|---|---|---|
| Markdown 가져오기(Markdown → 문서) | 저작 api | 지원 매트릭스 「Markdown 가져오기(Markdown → 문서)」(`Create(experimental)`) · capabilities 영역 `markdown-import` · 위치 모듈 — `hwpx.tools.markdown_import` | 미실측 |
| id 기반 글 읽기·일괄 바꿈(text blocks) | 저작 api | 지원 매트릭스 「id 기반 글 읽기·일괄 바꿈(text blocks)」(`Parse·Edit`) · capabilities 영역 `text-blocks` · 위치 `doc.text` | 미실측 |
| 메일머지(placeholder 템플릿 배치 생성) | 저작 api | 지원 매트릭스 「메일머지(placeholder 템플릿 배치 생성)」(`Edit·Render-verified`) · capabilities 영역 `mail-merge` · 위치 모듈 — `hwpx.tools.mail_merge` | Render-verified |
| 문서 끼워 넣기(문서 병합) | 저작 api | 지원 매트릭스 「문서 끼워 넣기(문서 병합)」(`Create·Render-verified`) · capabilities 영역 `document-merge` · 위치 모듈 — `hwpx.tools.document_merge` | Render-verified |
Expand Down
3 changes: 2 additions & 1 deletion docs/error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ except HwpxError as exc:
| 형태 | `style-not-found` | `VISUAL_COMPLETE_FAILED` |
| 쓰임 | 예외 분기 | **발행된 영수증 스키마의 필드값** |
| 관리 | major 경계 | 영수증 스키마 버전 |
| 개수 | 199 | 11 |
| 개수 | 200 | 11 |

통합하지 않는 이유: quality 코드는 `hwpx.mutation-report/v1` 과
`VisualCompleteReport` 에 이미 실려 나간 값이다. 이름을 바꾸면 영수증을 읽는
Expand Down Expand Up @@ -376,6 +376,7 @@ except HwpxError as exc:
| `text-highlight-match-crosses-markup` | 찾은 구간이 인라인 마크업을 가로질러 안전하게 감쌀 수 없다. |
| `text-highlight-match-empty` | 형광펜으로 감쌀 문자열이 비어 있다. |
| `text-highlight-match-not-found` | 문단에서 형광펜으로 감쌀 문자열을 찾지 못했다. |
| `text-markdown-style-invalid` | MarkdownStyle 의 제목·본문 글자 크기가 양수가 아니거나 제목 크기가 네 개가 아니다. |
| `text-outline-budget` | outline 의 글자·항목 예산이 1보다 작다. |
| `text-search-empty` | 바꿀 대상 문자열이 비어 있다. |

Expand Down
33 changes: 17 additions & 16 deletions docs/python-example-ledger.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
"docs/safe-write-contract.md",
"docs/golden-api.md"
],
"fenceCount": 116,
"sourceSha256": "5db7dce9e8f65361d4cc9a4911e5b9148dc5e9e75cabec7bbcdce4b1861e8a80",
"fenceCount": 117,
"sourceSha256": "a164785f35a43a2f224d2b1b906b71f113de67c0c7e05dea6e3c3a4fa3cb989d",
"manualSha256": {
"README.md": "d63726c59b063948126eaaa05022b13c3264c88019466ee4bb67db6d8ba27fc3",
"README_EN.md": "185f69b6cde4093acdde7bfb2505aa2e9e31b2bb8f46763dc93a854702742082",
"docs/quickstart.md": "44d8ca82ee70c0e3dfdf087713a4e4ce935e64cd3f6a546311f95b054a285a18",
"docs/recipes-traversal.md": "b63f42b1a6ed23812688599474dcf83300509b86a94b5daceae679bfdbde9343",
"docs/mutation-semantics.md": "6de1471f64c1cd8db2e661c413d92bfd27221f5d930708942ac47c6aff2d608b",
"docs/usage.md": "df93e611c465f9da290af12612e4c5b524a88406307d953ed08fa354763f8dc5",
"docs/usage.md": "ec70af303f2efaed9fe31f633af01817a4f2f1c6f31174dce4c21fadfd3b6948",
"docs/examples.md": "f03f0ecee4faef94dda05c92dd25672dfbde3a7447f823603b611bea59f6432e",
"docs/faq.md": "cd82e2f1e740cff3f50e620c060ed8ae0aa130271fa373eca3a07a70fb9ca604",
"docs/index.md": "111754d7637414e0078ba0c7bbcf19d1a4550a68202acf3dd2c89c0748ea0050",
Expand Down Expand Up @@ -100,6 +100,7 @@
"docs/usage.md#65",
"docs/usage.md#66",
"docs/usage.md#67",
"docs/usage.md#72",
"docs/examples.md#1",
"docs/examples.md#6",
"docs/golden-api.md#1"
Expand All @@ -121,8 +122,8 @@
"docs/usage.md#8",
"docs/usage.md#68",
"docs/usage.md#70",
"docs/usage.md#80",
"docs/usage.md#81",
"docs/usage.md#82",
"docs/examples.md#2",
"docs/examples.md#3",
"docs/examples.md#4",
Expand All @@ -146,16 +147,16 @@
"docs/usage.md#34",
"docs/usage.md#69",
"docs/usage.md#71",
"docs/usage.md#72",
"docs/usage.md#73",
"docs/usage.md#74",
"docs/usage.md#75",
"docs/usage.md#76",
"docs/usage.md#77",
"docs/usage.md#78",
"docs/usage.md#79",
"docs/usage.md#82",
"docs/usage.md#80",
"docs/usage.md#83",
"docs/usage.md#84",
"docs/faq.md#2",
"docs/faq.md#4",
"docs/faq.md#5"
Expand Down Expand Up @@ -187,36 +188,36 @@
"document",
"section"
],
"docs/usage.md#72": [
"document"
],
"docs/usage.md#73": [
"document"
],
"docs/usage.md#74": [
"document",
"section"
"document"
],
"docs/usage.md#75": [
"document",
"section"
],
"docs/usage.md#76": [
"document"
"document",
"section"
],
"docs/usage.md#77": [
"section"
"document"
],
"docs/usage.md#78": [
"document"
"section"
],
"docs/usage.md#79": [
"document"
],
"docs/usage.md#82": [
"finder"
"docs/usage.md#80": [
"document"
],
"docs/usage.md#83": [
"finder"
],
"docs/usage.md#84": [
"document"
],
"docs/faq.md#2": [
Expand Down
2 changes: 2 additions & 0 deletions docs/support-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
| 메일머지(placeholder 템플릿 배치 생성) | Edit·Render-verified | `hwpx.tools.mail_merge.merge_template_rows`(6.8 트레인㉛ 등재, 코드는 이전 사이클부터 존재 — 편집기 표면 인벤토리 트레인㉙·계층 판정 트레인㉚이 찾을 때까지 캐파빌리티·매트릭스 어디에도 등재가 없었다). `{{field}}`/`${field}`/`<<field>>` 세 플레이스홀더 문법을 지원하는 템플릿 1개 + 행 데이터(csv/json/시퀀스)로 문서 N개를 배치 생성 — 기존 `hp:t` 텍스트를 치환하는 것이라 새 요소를 만들지 않는다(Create가 아니라 Edit). `strict`/`fit_policy`/`value_sanitizer` 등 운영 옵션 다수. 실한컴 렌더 검증: `docs/openrate/report-v13.json` authored-mailmerge 스트라텀(세 플레이스홀더 문법 각각 겨냥하는 전용 템플릿으로 회전), macOS Hancom GUI 오라클 10/10 render_checked·0 render_failed(2026-08-08 측정) — 이 영역의 첫 독립 실한컴 판정 |
| 메일머지 표시 필드 | Create(experimental) | `HwpxOxmlParagraph.add_mail_merge_field(name, cached_text=)`(`oxml/field_marks.py`, P4). 팀장 실한컴 GUI 프로브 gold(`tests/fixtures/gui_probes/mailmerge_display_fields.hwpx`, "도구 > 메일 머지 > 메일 머지 표시 달기"로 필드 2개 삽입) 역설계 — 위 「메일머지(placeholder 템플릿 배치 생성)」가 **기존 `hp:t` 텍스트를 치환**하는 Edit인 것과 달리 이쪽은 `hp:fieldBegin type="MAILMERGE"` 자체를 새로 만드는 Create다(그래서 별도 영역). DATE/PATH와 같은 단일 run 안 `ctrl(begin)`/`t`/`ctrl(end)` 배치에 더해 **끝에 빈 `hp:t`가 하나 더** 붙고(gold 2/2), `hp:fieldBegin`에 `metaTag` 속성이 **없다**(DATE/교정 부호/PATH gold는 셋 다 `metaTag=""`) — 필드 종류마다 실측이 갈리는 유일한 속성. `hp:parameters`(`cnt="5"`): `Fiexde=1`(**오타 철자가 실물·한컴 공식 문서 공통의 정본이라 정정 금지** — `trackchageConfig`·memo의 `NOMAL`과 같은 부류) · `Prop=8`(DATE/PATH와 같은 캐시 필드류 공유값) · `Command`/`FieldValue`가 둘 다 필드 이름 그대로 · `FieldType="USER_DEFINE"`. 캐시 텍스트 기본값 `{{이름}}`은 `hwpx.tools.mail_merge`가 인식하는 플레이스홀더 문법이라 이 필드로 저작한 템플릿이 그 배치 생성기에 그대로 물린다. gold의 두 필드는 `id`가 다른데 `fieldid`는 같은 값을 공유하나, 표본이 문서 하나뿐이라 채번 규칙을 역산할 근거가 없어 DATE/PATH와 같은 독립 난수를 쓴다. 실한컴 렌더 검증: `docs/openrate/report-v22.json` authored-mailmerge-field 스트라텀(단일/2필드/커스텀 캐시/**한글 필드명(경계)**/표 셀 안 필드, 5건), macOS Hancom GUI 오라클 5/5 render_checked·0 render_failed(2026-08-18 측정) |
| 문서 끼워 넣기(문서 병합) | Create·Render-verified | `hwpx.tools.document_merge.append_document`/`.insert_document`(6.9 트레인㉝ v1, 6.10 트레인㊱ v2 — 편집기 표면 인벤토리 트레인㉙의 macOS 메뉴 전수 스캔이 찾은 신규 갭, "입력→문서 끼워 넣기…"). 다른 HWPX 문서의 본문을 연 문서에 끼워 넣는다 — 핵심은 헤더가 소유한 8개 공유 자원(charPr/paraPr/style/borderFill/tabPr/numbering·bullet/memoPr/fontfaces)을 원본에서 새 id로 복제해 재매핑하는 것이지 단순 복사가 아니다(그대로 복사하면 대상 문서의 기존 id를 조용히 가로챈다). 한컴 편집기 실측(팀장, 2026-08-07): "입력→문서 끼워 넣기…" 패널의 4개 고유 옵션(글자 모양 유지·스타일 유지·쪽 모양 유지·문단 모양 유지, 기본값 전부 해제=흡수)이 정책 축의 실측 근거. v2는 이 4축을 한컴과 같은 이름의 매개변수(`keep_character_shape`/`keep_style`/`keep_paragraph_shape`/`keep_page_shape`)로 노출한다 — 기본값은 v1의 기출하·실검증 동작과 동일(글자·스타일·문단 모양=유지, 쪽 모양=흡수), 반대 방향은 매핑 실측이 없어 typed 오류로 정직 거부(과설계 안 함). v2는 MEMO 병합도 지원한다(DEV-042): `hp:memogroup`은 절 레벨 형제라 원본의 모든 절에서 참조된 `hp:memo`를 찾아 복제, 새 메모 id 채번 후 body 문단 복제본과 같은 리스트로 묶어 기존 축1 재매핑(charPr/paraPr/style/memoShapeIDRef)에 그대로 태운다. 실측으로 찾은 결함 8건을 수정(v1 6건 + v2 2건): 스타일 자신의 기본 charPr/paraPr 누락, `HwpxOxmlDocument.char_properties` 캐시 미무효화, `linkListIDRef`/`linkListNextIDRef` 과잉거부(팀장 독립 검증 — 벤더드 코퍼스 5,891/5,891건이 전부 "0" 관용값인데 존재만으로 거부해 표가 든 모든 문서, 우리 자신의 `add_table` 산출물 포함, 병합이 불가능했다), 저장 경로 dirty-tracking 미설정(가장 심각 — `target_header.mark_dirty()`를 호출하지 않아 헤더에 새로 추가한 요소가 저장 시 조용히 누락되는데, 그림이 있는 병합만 우연히 `add_image`의 부수효과로 가려져 있었다), `hp:secPr` 제거가 통째 run을 지워 표·텍스트까지 함께 삭제(실코퍼스 표 문서에서 secPr·표·텍스트가 한 run에 공존, 수정 후 secPr만 개별 제거), 새 id 채번 순서가 프로세스마다 비결정적(`set` 순회를 `sorted()`로 교체), `check_id_integrity` 자신의 `memoShapeIDRef="0"` 오탐(fresh 문서의 secPr 스켈레톤 기본값을 memo_shapes 테이블이 채워지는 순간부터 댕글링으로 오판 — 병합과 무관한 사전 존재 결함, `id_integrity.py`의 `_ALLOWED_SENTINELS`로 수정), `hp:fieldEnd`의 `fieldid` 미재생성(원본 문서의 raw uuid4().hex 값이 복사된 내용에 그대로 남음 — v14 생성기 자체 결정성 검사로 발견, `begin_fieldid_map`으로 수정). `hp:connectLine`(스마트 연결선)·`chartIDRef`·실체인 `linkListIDRef`가 있으면 fail-closed 거부. 참조 무결성은 재발명하지 않고 기존 `hwpx.tools.id_integrity.check_id_integrity`로 검증(설계 문서: `docs/2026-08-08-document-merge-contract.md`). 실한컴 렌더 검증: `docs/openrate/report-v13.json` authored-docmerge 스트라텀(v1, 10건 중 4건이 표 포함 — 위 결함 셋이 전부 표 병합에서 터졌던 형태를 정면으로 겨냥), macOS Hancom GUI 오라클 10/10 render_checked·0 render_failed(2026-08-08 측정). v2(4축 매개변수+MEMO 병합)는 `docs/openrate/report-v14.json` authored-docmerge2 스트라텀(단일 메모/센티널 기본값/대상 기존 memogroup 합류/표 동반 4갈래, 4축 기본값 명시통과 호출), macOS Hancom GUI 오라클 5/5 render_checked·0 render_failed(2026-08-08 측정)로 검증 완료. 6.15: 클론 경로 감사(속성-클론 중첩 참조 앨리어싱 수리, `b81713e`)의 후속으로 v20(`cross-real-merge`, 우리 자신이 생성한 문서가 아니라 실한컴이 저장한 문서끼리 병합 — v13의 성긴 헤더로는 이 앨리어싱 결함이 애초에 안 보였을 것이라는 지적)을 실행: 실 문서 3종 쌍방향 병합·중간 삽입 3건·필드 보유 소스 2종·3단 연쇄 병합 12건, macOS Hancom GUI 오라클 12/12 render_checked·0 render_failed(2026-08-12 측정) — 앨리어싱 수리 후 재현이 사라졌음을 실 소스로 재확인. 세 버전 모두 같은 스트라텀 그룹(document_merge)이라 원장 라우팅은 여전히 없음(위 산문 근거만) |
| Markdown 가져오기(Markdown → 문서) | Create(experimental) | `hwpx.tools.markdown_import.from_markdown(markdown, style=)`으로 새 문서를, `doc.text.append_markdown(markdown)`으로 기존 문서 끝에 쓴다. `#`~`####` 제목(굵게·크기, 개요 번호 없음), 문단(줄 끝 공백 둘·`\`은 줄 바꿈), `-`/`*`/`+`·`1.`/`1)` 목록(들여쓰기로 수준, `doc.styles.apply_list_format`의 한/글 목록), `\|` 표(구분선 위 행은 굵게·음영 머리글, 짧은 행은 빈 칸으로 채움, 칸 안 `<br>`은 줄 바꿈), `**굵게**`·`*기울임*`·`` `코드` ``(글자만)·`[글](주소)`(글만)를 쓴다. 인용·HTML·코드 블록은 평문으로 쓰고 수평선은 버리며, 모르는 Markdown으로 오류를 내지 않는다. 새 문단은 앞 문단의 모양을 물려받지 않아(`HwpxDocument.new(inherit_style=False)`) 제목·목록 번호가 뒤 문단에 따라붙지 않는다. 모양은 장르 중립 `MarkdownStyle`(기본 `#` 16pt·`##` 12pt·`###` 11pt·`####` 10pt 굵게, 머리글 음영 `#D9D9D9`) — 장르 프리셋은 응용 계층 몫. 새 요소를 만들지 않고 기존 저작 primitive만 쓰므로 원장 라우팅은 없다. 저장·재열기와 편집기 열기 안전성 검사는 테스트로 확인했고, 실한컴 렌더 판정은 아직 없다 |
| 덧말·글자 겹치기 | Parse·Create·Render-verified | `doc.shapes.add_dutmal`/`.add_composed_character`(6.9 트레인㉞). `hp:compose`(글자 겹치기/원문자·합자)는 읽기 모델이 이전 사이클(commit `6f88e2e`)부터 있었으나 저작 API·캐파빌리티 등재가 없었다 — 이번에 `add_composed_character`로 채우고 같은 `body.ComposedCharacter`/`_composed_character_to_xml` 경로를 저작에도 재사용(파싱·저작 한 코드 경로). `hp:dutmal`(덧말)은 이번에 처음 리버스엔지니어링 — 실코퍼스 표본 정확히 1건(`reader_writer__SimpleDutmal.hwpx`, hwpxlib 왕복 픽스처), macOS 편집기 메뉴 전수 스캔(트레인㉙)이 1급 메뉴 항목임은 확인했으나 그 이상의 빈도 근거는 없다 — 정직 고지. 그 1건이 스키마의 두 주장을 직접 반증한다(`option`은 스키마상 `fixed="4"`인데 실측 `"0"`, `szRatio`는 `xs:positiveInteger`인데 실측 `"0"` — DEV-041). 저작 API는 이 실측값을 기본값으로 채택하고 스키마 주장을 검증 규칙으로 강제하지 않는다. 둘 다 `hp:t`의 자식이 아니라 `hp:run` 직속(스키마 문서 순서와 실제 위치가 다름, 실코퍼스로 확인). 실한컴 렌더 검증: `docs/openrate/report-v13.json` authored-dutmal-compose 스트라텀(dutmal 기본값을 실측값 그대로 두고 검증 — 스키마 주장이 아니라 우리 채택을 오라클이 판정), macOS Hancom GUI 오라클 10/10 render_checked·0 render_failed(2026-08-08 측정) — `hp:compose`/`hp:dutmal`/`hp:mainText`/`hp:subText` 4개 요소 전부 원장에 `by-openrate-corpus` 반영(코드-쓰기 분류기가 `_qualified_tag(tag, "literal")`류 관용구를 못 잡던 갭도 이번에 수동 override로 같이 메움) |

## 6.0 표면 위치
Expand Down Expand Up @@ -113,6 +114,7 @@
| 메일머지(placeholder 템플릿 배치 생성) | 모듈 — `hwpx.tools.mail_merge` |
| 메일머지 표시 필드 | `doc.fields` |
| 문서 끼워 넣기(문서 병합) | 모듈 — `hwpx.tools.document_merge` |
| Markdown 가져오기(Markdown → 문서) | 모듈 — `hwpx.tools.markdown_import` |
| 덧말·글자 겹치기 | `doc.shapes` |

5.x 의 옛 이름은 6.x 동안 계속 답하되 `DeprecationWarning` 을 내고 7.0 에서
Expand Down
Loading
Loading