Skip to content

Releases: DanMeon/rhwp-python

v0.8.1 — 상류 HWPX fidelity sync (도형 shapeComment 이슈 1451) (PATCH)

Choose a tag to compare

@DanMeon DanMeon released this 07 Jul 16:38
d9b009c

PATCH release. 상류 rhwp 의 HWPX serializer fidelity 개선을 흡수한다 — 본 프로젝트가 보고한 이슈 edwardkim/rhwp#1451 (legacy 도형 shapeComment 미직렬화) 해결로 verify_hwpx_roundtrip 보존 boundary 가 도형 shapeComment 까지 실질 확대됐다. 공개 API / IR schema ("1.1") 불변, 표면 신규 0.

주요 변경

  • test_lossy 재설계 → 무손실 positive + RoundtripReport(ok=False) unit-construct 계약
  • submodule pin 7d9aae7f10f5c51e (238 commit) — 실제 pin 은 이미 10f5c51e (= upstream main HEAD) 였고 CHANGELOG 기록만 뒤처져 정정
  • 상류 이슈 문서 RESOLVED (in-place Frozen) 전환
  • 로드맵 미착수 계획에 IR 노출 확장 라인 추가 (갭 조사)

코드(src/*.rs, python/rhwp) 변경 0 — 테스트 1파일 + 문서/버전.

rhwp core pin

  • external/rhwp submodule: 10f5c51e

전체 변경 내역: CHANGELOG.md

v0.8.0: HWPX writeback round-trip 검증 (verify_hwpx_roundtrip)

Choose a tag to compare

@DanMeon DanMeon released this 21 Jun 05:46
81f58bb

MINOR release. parse 한 Document 를 HWPX 로 저장했을 때 IR 의미가 보존되는지 검증하는 Document.verify_hwpx_roundtrip() 표면을 추가하고, 보존 boundary 를 v0.7.0 의 텍스트·문단에서 상류 diff_documents 가 실제 비교하는 필드 집합 (표 cell·캡션·page_break, 그림 크기·캡션, char_shape·lineseg, PageDef, 리소스·BinData count) 으로 확대한다. 직렬화·진단 모두 상류 위임 — 추가만 있고 기존 표면 보존, IR schema ("1.1") 변경 0. 동시에 상류 v0.7.13 ~ v0.7.16 sync 를 흡수한다.

Added

  • Document.verify_hwpx_roundtrip() -> RoundtripReport — 현재 문서를 HWPX 로 직렬화 → 재파싱한 뒤 상류 diff_documents 로 원본 대비 IR 차이를 측정. 반환 RoundtripReportok: bool + differences: list[str] 경량 리포트 (불변 ok == (not differences)). differences 각 항목은 상류 IrDifference 의 사람 가독 문자열 (차이 종류 + 위치). 직렬화·재파싱 실패는 ValueErrorto_hwpx_bytes / export_hwpx 와 동일 에러 계약. GIL 보유 — diff_documentsself.inner.document() (&self.inner) 를 캡처하고 DocumentCore!Sync (v0.7.0 결정 3 일관).
  • rhwp.RoundtripReport — verify 결과 모델 (frozen=True / extra="forbid") 을 public 노출. report.ok 로 프로그램 분기, report.differences 로 사람 가독 진단. 상류 IrDifference variant 가 게이트 진행마다 증가하므로 강타입 mirror 대신 forward-compatible 한 문자열 리스트로 출고.

보존 boundary 확대: v0.7.0 의 텍스트·문단 → 상류 diff_documents실제 round-trip 비교 하는 필드 집합 (표 cell 내용·캡션·page_break, 그림 크기·캡션, 문단 char_shape·lineseg, 섹션 PageDef, 리소스·BinData 엔트리 카운트). 미비교 요소 (수식 script, 표 cell rowspan/colspan, BinData byte, 도형 raw) 는 보장 범위 밖 — 상류 비교 확대에 의존. 직렬화·진단 모두 상류 위임 — 추가만 있고 기존 표면 보존, IR schema ("1.1") 변경 0. 회귀 가드: tests/test_hwpx_writeback.py 에 AC-1 ~ AC-6 (7 테스트) 추가 — 표·그림 round-trip 동등 (aift.hwpx) + verify positive + 자연 발생 negative 검출 (table-vpos-01.hwpx 의 그리기 도형 shapeComment 상류 미직렬화) + 부작용 없음 + v0.7.0 텍스트·문단 보장 유지. spec / ADR: docs/roadmap/v0.8.0/hwpx-writeback-expansion.md / docs/design/v0.8.0/hwpx-writeback-expansion-research.md (둘 다 Draft — GA 전환은 릴리스 시점).

Build

  • external/rhwp submodule pin ce45231c (v0.7.12 + 394) → 7d9aae7f (v0.7.16 + 36). 상류 v0.7.13 ~ v0.7.16 GA 흡수 (pin 간 1,209 commit). 본 binding 관점 회귀 0 — 공개 API / IR schema ("1.1") / wheel 의존성 모두 불변. 검증: maturin develop --release clean, pytest -m "not slow" 599 passed / 2 skipped (v0.7.0 GA 와 동일), IR baseline byte-equal (tests/test_view_baseline.py 2/2 — aift.hwp + table-vpos-01.hwpx), cargo clippy --all-targets -D warnings clean. 우리가 소비하는 상류 심볼 (serialize_hwpx / render_page_svg_native / build_page_layer_tree / renderer::pdf::svgs_to_pdf / RasterRenderOptions / get_bin_data) 시그니처 전부 불변.
    • HWPX serializer fidelity 대폭 강화 — lossless round-trip 도달 (DocInfo / numbering paraHead / cellzoneList / useKerning / useFontSpace 무손실, 표·그림·묶음 캡션 직렬화, 그림 크기 요소 curSz/imgRect/imgDim, MEMO 필드 parameters, shapeComment, borderFill 등록, 표 pageBreak 보존). 상류 round-trip IrDiff 가 Stage 0 (섹션·문단 카운트만) → Stage 4 (표·그림·수식 의미 동등성) 로 성숙, 143 HWPX 샘플 xfail 0 — v0.8.0 HWPX writeback 확장의 상류 선행조건 충족.
    • native PDF export API (DocumentCore::render_*_pdf_native, 상류 #1359) — 기존 renderer::pdf::svgs_to_pdf 경로와 additive 공존, 본 binding 의 PDF 표면 영향 0.
    • Text IR v2 폰트 증명 게이트 / 그림 effects·shadow round-trip / 차트 샘플 코퍼스 27종 / 미주 높이 모델 정규화.
  • 상류 #823 (macOS headless Skia font lookup hang) 해결 (v0.7.13). v0.6.1 Build 섹션이 미해결로 기록했던 PNG 표면 known limitation 종결 — headless macOS 에서 render_png 가 hang 없이 동작. 이에 맞춰 ci.yml 의 macOS smoke 잡 (test-other-os 매트릭스) 을 복원 — 4083a27 의 비활성화를 되돌리고 macos-latest 를 추가 (docs/upstream/issue-macos-png-coretext-hang.md RESOLVED 전환).

v0.7.0 — HWPX writeback baseline

Choose a tag to compare

@DanMeon DanMeon released this 04 Jun 11:23

MINOR release. parse 한 Document 를 다시 HWPX 로 저장하는 첫 역방향 표면을 추가한다 — v0.2.0 ~ v0.6.0 의 모든 산출물 (IR / SVG / PDF / PNG) 이 read-only 였던 것에 writeback 을 연다. 직렬화는 상류 serialize_hwpx 에 위임한다. 추가만 있고 기존 IR / 렌더 / MCP 표면은 모두 보존 (additive only) — IR schema ("1.1") 변경 0.

Added

  • Document.to_hwpx_bytes() -> bytes — 문서를 HWPX (ZIP+XML) 바이트로 직렬화. 출력은 ZIP magic b"PK\x03\x04" 으로 시작하고 첫 엔트리가 STORED mimetype = application/hwp+zip. Document IR 이 포맷 독립이라 HWP5 로 parse 한 문서도 HWPX 로 출력된다 (HWP5 → HWPX 포맷 변환). 직렬화 실패 (참조 무결성 위반 — BinData 누락 등) 는 ValueError.
  • Document.export_hwpx(path) -> int — 문서를 HWPX 파일로 저장하고 작성 바이트 수 (> 0) 를 반환. 파일 쓰기 실패 (부모 디렉토리 부재 등) 는 OSError. render_pdf / export_pdf 의 메모리 / 파일 분리 패턴과 대칭.
  • README § "HWPX 저장 (writeback)" 신설 — to_hwpx_bytes / export_hwpx 사용 예 + HWP5 → HWPX 변환 + 보존 범위 / 에러 계약.

보존 범위: 텍스트·문단은 round-trip 의미를 보존한다 (parse → 저장 → 재파싱 시 섹션 수 / 문단 수 / 문단 텍스트 동등). 표·그림·수식은 상류 serializer 의 현 보존 범위에 위임 — 의미 보존 미보장 (예외 없는 직렬화만 보장). round-trip 은 의미적 동등성 기준이며 byte 단위 동일은 보장하지 않는다. 표·그림 round-trip 의미 보존은 v0.8.0, HWP5 binary 출력 (export_hwp) 은 별도 minor.

Build

  • external/rhwp submodule pin 1899ef9b (v0.7.12) → ce45231c (v0.7.12 + 394 commit, 2026-05-27 상류). spec·feat 는 1899ef9b 기준 작성됐고 (2026-05-20) GA 직전 재동기화 후 그 위에서 회귀를 재검증했다. 본 binding 관점 회귀 0serialize_hwpx 시그니처 불변, maturin develop --release clean, pytest -m "not slow" 599 passed / 2 skipped (IR baseline byte-equal 포함). 흡수한 상류 변경: serializer +2092 (16 commit 거의 전부 HWP5 binary writeback serialize_hwp 한컴 호환), model +1267, rendering +1320 — 모두 직렬화·렌더 내부라 binding 이 소비하는 IR schema ("1.1") / IR·렌더 출력은 불변.
  • Cargo.tomlversion 0.6.10.7.0. pyproject.tomldynamic = ["version"] 으로 자동 추종.

Full Changelog: v0.6.1...v0.7.0

v0.6.1 — v0.6.0 publish 회복 + 후속 polish (PATCH)

Choose a tag to compare

@DanMeon DanMeon released this 18 May 06:24
1048ce6

PATCH release. v0.6.0 (Frozen, 2026-05-10) 의 GitHub Release / PyPI publish 가 누락된 상태에서 발견된 release 인프라 정합화 + 후속 polish 를 한 묶음 PATCH 로 발행한다. 사용자 영향: PyPI 첫 게시 패키지가 v0.5.1 다음 v0.6.1 로 점프 — v0.6.0 의 모든 표면 (페이지 PNG 렌더링 + 문서 시스템 개편) 은 변경 없이 그대로 포함하며 [0.6.0] 섹션은 historical record 로 보존. 외부 공개 API / IR schema ("1.1") 변경 0.

Added

  • examples/07_render_png.py 신규 — v0.6.0 PNG 표면의 typer 진입점 예제 (단일 페이지 / --all 일괄 / --scale / --max-pixels / --output-dir / --prefix). Pillow 가 있으면 디코드 dimension 까지 검증, 없으면 PNG magic + 길이만 출력 (graceful degrade). README §"페이지 PNG 렌더링 (VLM 입력)" 의 typer 진입점 시연.
  • examples/README.md §7 항목 + "일곱 스크립트" 안내. [examples] extras 에 Pillow 추가 (07 디코드 검증용).
  • benches/bench_gil.pypng_task 추가 — parse + render_png(page=0) 의 ThreadPoolExecutor worker 1/2/4/8 별 wall-clock 비교 (기존 parse + render_pdf 패턴 동형). --json 플래그 출고 옵션 — drift 추적 / ADR 첨부 재활용. v0.6.0 spec row-6 의 "≥50 ms 임계 충족" rationale 을 closed-loop 으로 실측 검증.

Changed

  • rhwp.parse / rhwp.aparse / rhwp.arender_png / Document.__init__path: strpath: str | os.PathLike[str] 수용. 내부에서 str(path) 정규화 후 Rust _Document(&str) 위임. 사용자가 pathlib.Path 인스턴스를 그대로 넘길 수 있다 — IDE 자동완성 정합. v0.5.x 의 str 호출 시그니처는 그대로 유지 (additive widening, breaking 아님).

Build

  • external/rhwp submodule pin 62a458a (v0.7.10) → 1899ef9b (v0.7.12). v0.6.0 Build 섹션 disclosure 의 v0.7.10 record 와 wheel binary 의 불일치를 해소. 본 binding 관점 변경 0 — 공개 API / IR schema ("1.1") / wheel 의존성 모두 동일, cargo check clean + maturin develop --release clean + IR baseline byte-equal (tests/test_view_baseline.py 2/2 pass) + 회귀 가드 592 통과 직접 검증. 상류 v0.7.11 + v0.7.12 GA 흡수 — Renderer 시각 회귀 fix 다수 + Text IR v2 (GlyphRun / GlyphOutline variant, rhwp-python 미소비) + HWP3 ch=9 탭 spec 정합 + skia-safe 0.93.10.97.0 binary-cache. macOS PNG headless hang (상류 #823) 은 v0.7.12 에서도 미해결 — 별도 issue 진행.
  • Cargo.tomlversion 0.6.00.6.1. pyproject.tomldynamic = ["version"] 으로 자동 추종.
  • [project.optional-dependencies] examplespillow>=10 추가 — 07 예제의 dimension 디코드 검증 옵션. 미설치 시 graceful degrade (PNG magic + 길이만).

Notes

  • v0.6.0 publish 누락의 회복 경로 — [0.6.0] historical record 보존 + v0.6.1 = v0.6.0 표면 + 본 PATCH 변경. SemVer 측 단조 증가 (PyPI 는 게시되지 않은 v0.6.0 의 부재를 허용).
  • tests/type_check_errors.py 의 의도된 pyright 에러 4건 + test-without-extras job 의 expected skip count 6 변동 없음.

v0.5.1 — MCP tool 출력 강타입화 (PATCH)

Choose a tag to compare

@DanMeon DanMeon released this 07 May 07:09
0fd49fc

PATCH release. v0.5.0 GA 한 rhwp-mcp 의 7 도구 중 약타입 (dict[str, Any] / list[dict[str, Any]]) 으로 반환하던 3 도구 (get_ir / iter_blocks / chunks) 의 출력 시그니처를 Pydantic V2 모델 (HwpDocument / list[Block] / list[ChunkRecord]) 로 강타입화. fastmcp v3 의 자동 outputSchema 가 약타입 (additionalProperties: true 만) → 강타입 (필드별 type / Discriminator + Tag 11 변형 / required 명시) 으로 강화 — LLM 의 응답 해석 / 후속 도구 호출 정확도 향상. wire format byte-equal — 외부 클라이언트 (Claude Desktop / Cline 등) 의 LLM 프롬프트 / 후처리 코드 영향 0. 코어 wheel 의존성 / extras / schema ("1.1") 변경 없음.

Added

  • rhwp.mcp.tools.ChunkRecord BaseModel 신규 — page_content: str + metadata: dict[str, Any]. model_config = ConfigDict(extra="forbid", frozen=True). RAG 청크의 직렬화 표면 — LangChain Documentpage_content / metadata 평탄화. mode × block kind 동적 metadata 키 집합은 rhwp.integrations.langchain.HwpLoader 가 SSOT — 분기 모델 (ChunkRecord_Single 등) 거부 결정으로 metadata 자유 dict 유지 (schema 비대 + forward-compat 깨짐 회피).

Changed

  • rhwp.mcp.tools.get_ir(path) 반환 타입 — dict[str, Any]HwpDocument. fastmcp 가 자동으로 model_dump(mode="json") 직렬화 → result.structured_content 가 v0.5.0 dict 출력과 byte-equal. result.data 는 typed BaseModel 인스턴스 (v0.5.1 신규 표면) — discriminated union block 들의 강타입 access 가능.
  • rhwp.mcp.tools.iter_blocks(path, ...) 반환 타입 — list[dict[str, Any]]list[Block]. callable Discriminator + Tag 11 변형 (paragraph / table / picture / formula / footnote / endnote / list_item / caption / toc / field / unknown) 그대로 노출 — outputSchema 의 oneOf 11 변형이 LLM 에 정확한 kind 별 필드 구조 노출.
  • rhwp.mcp.tools.chunks(path, ...) 반환 타입 — list[dict[str, Any]]list[ChunkRecord]. dict 평탄화 코드 제거 — fastmcp 자동 직렬화에 위임.
  • README MCP 도구 표 출력 컬럼 갱신 + v0.5.1 마이그 노트 한 단락 추가 — result.data 의 dict 인덱싱 → typed attribute access 마이그 (단 iter_blocks list element 는 fastmcp v3 의 oneOf deserialization 한계로 dict 폴백 — v0.5.0 access 패턴 그대로 동작).

Fixed

  • python/rhwp/ir/nodes.pyUnknownBlock.kind 의 JSON Schema 에 not.enum: sorted(_KNOWN_KINDS) constraint 추가 (Field(json_schema_extra=callable) 표준 hook). fastmcp v3 + jsonschema 의 strict oneOf validation 이 ParagraphBlock 과 UnknownBlock 양쪽 valid 인스턴스 (예: 빈 ParagraphBlock) 에서 RuntimeError: Invalid structured content 로 fail 하던 client side wire format 호환 깨짐 해결. callable Discriminator (_block_discriminator) 의 런타임 동작은 SSOT 그대로 — schema export 만 strict 화. packaged python/rhwp/ir/schema/hwp_ir_v1.json 자동 재생성. v0.2.0 Frozen IR 의 forward-compat 라우팅 (미지 kind → UnknownBlock) 보존 — tests/test_ir_schema_export.py::test_unknown_kind_routing_pydantic_matches_schema 가 회귀 가드.

Build

  • external/rhwp submodule pin 변경 없음 — v0.5.0 동일 (62a458a, v0.7.10). 본 PATCH 는 pure Python schema 강화, 상류 변경 0.
  • 신규 의존성 / extras 변경 없음. CI test-without-extras job 의 expected skip count 5 그대로 (tests/test_mcp_server.py 의 file-level pytest.importorskip("fastmcp") 보존).

Notes

  • spec / ADR / 구현 로그: docs/roadmap/v0.5.1/mcp-typed-output.md (Frozen, 10 인수조건, 8 결정) / docs/design/v0.5.1/mcp-typed-output-research.md (Frozen, 5 결정 매트릭스) / docs/implementation/v0.5.1/migration.md (Frozen).
  • v0.5.1 작업 중 표면화된 fastmcp v3 의 두 한계는 spec body 에 정확히 반영: (1) result.structured_content 의 wrap 분기 (BaseModel = inline / list[T] = {"result": ...} envelope), (2) callable Discriminator + Tag 유니온의 oneOf schema 가 dynamic 모델 reconstruct 안 됨 (list element dict 폴백). server side 의 typed 출력은 AC-2 / AC-3 가, wire format byte-equal 은 AC-5 가 cover.

v0.5.0 — MCP server (rhwp-mcp)

Choose a tag to compare

@DanMeon DanMeon released this 07 May 03:37
9adacfa

MINOR release. Model Context Protocol (Anthropic, 2024) 서버를 새 entry point rhwp-mcp 로 노출한다. LLM 에이전트 (Claude Desktop / Cursor / Cline / Continue.dev / Goose / 자체 에이전트) 가 HWP/HWPX 를 직접 파싱·요약·청크화 가능. standalone fastmcp v3 (jlowin) 기반 — 2026-05 기준 MCP 서버 약 70% 시장 점유의 사실상 표준. 7 도구 / 2 transport (stdio 기본 + streamable-http 옵션) / runtime extras gate / unsendable 안전 패턴 강제. 코어 wheel 의존성 변경 0 (additive extras), schema ("1.1") 유지.

Added

  • 새 entry point rhwp-mcp = "rhwp.mcp:run" — argparse 기반 --transport stdio | streamable-http / --host / --port CLI. stdio 가 기본 (Claude Desktop / IDE 통합용), streamable-http 는 옵션 (서버 배포 / 다중 클라이언트, uvicorn ASGI). --host 기본 127.0.0.1 외부 노출 회피 + --port [1, 65535] argparse validator + stdio 와 명시적 host/port 조합 시 parser.error 강제 (silent ignore 보안 사고 회피).
  • 7 MCP 도구 (mcp.server.fastmcp.FastMCP.tool() 등록): parse_hwp_summary(path) / extract_text(path) / get_ir(path) / iter_blocks(path, kind?, scope, limit?) / to_markdown(path) / to_html(path, *, include_css=False) / chunks(path, mode, size, overlap, include_furniture). 모두 sync 함수 — _Documentunsendable 이라 handler 안에서 parse → consume → primitive return 패턴 강제 (async + asyncio.to_thread(rhwp.parse, ...) 는 panic). chunks 는 런타임 lazy import — langchain-text-splitters 미설치 시 fastmcp ToolError 로 wrap → MCP CallToolResult(isError=True) 응답 (서버 기동 / 다른 6 도구는 정상 — AC-7).
  • 모듈 위치: python/rhwp/mcp/{__init__.py, __main__.py, server.py, tools.py} (top-level, integrations/ 가 아님). __init__.py 는 lazy-import 패턴 — rhwp.cli 와 동일하게 [mcp] extras 미설치 시 친절 에러 + exit 2 (AC-1).
  • 새 extras: [project.optional-dependencies] mcp = ["fastmcp>=3,<4"] + mcp-chunks = ["fastmcp>=3,<4", "langchain-core>=0.2", "langchain-text-splitters>=0.2"]. extras 키 이름은 "MCP 서버 기능" 표시 — 의존성 패키지명 (fastmcp) 과 분리. [examples] extras 가 fastmcp 합집합 포함하도록 갱신.
  • examples/06_mcp_server.py — fastmcp Client(server) in-process round-trip 데모 (typer 기반, --skip-chunks 옵션). 7 도구 차례로 호출하며 출력 형식 학습용.
  • README § "MCP server (rhwp-mcp)" 섹션 신설 — 도구 7 종 표 / Claude Desktop claude_desktop_config.json 등록 예 / 클라이언트 호환성 표 (Claude Desktop / Cline / Cursor / Continue.dev / Goose / 자체 에이전트, transport 별 ✅/❌/⚠️) / streamable-http 사용 예.
  • spec / ADR / 구현 로그: docs/roadmap/v0.5.0/mcp.md (Frozen, 11 인수조건) / docs/design/v0.5.0/mcp-research.md (Frozen, 4 결정 매트릭스 — SDK 채택 근거 / transport 우선순위 / handler 동시성 / 도구 분할) / docs/implementation/v0.5.0/stages/ (S1 ~ S5 Frozen).

Changed

  • ADR § 1 SDK 결정 갱신 (S1 진행 중) — 공식 mcp Python SDK (FastMCP v1 흡수) → standalone fastmcp v3 (jlowin). 2026-05 현업 표준 패턴 정합 (시장 점유 약 70%) + v3 의 OAuth / OpenTelemetry / server composition / streamable-http 우선 같은 프로덕션 기능. 공식 SDK 의 FastMCP v1 은 frozen 상태 — 추가 framework 기능은 standalone 으로만 발전.
  • docs-lint 정책 갱신 (S1 진행 중) — Frozen + target 조합을 docs/implementation/vX.Y.Z/ pre-GA stage log 에 한해 허용 (scripts/_doc_lint.pyis_pre_ga_stage 면제 분기). Rust RFC / PEP / ADR 의 editorial vs release 차원 분리 패턴 정합 — stage 본문은 작성 즉시 immutable, GA 라벨은 미부여 (CONVENTIONS § 131 의 의도). CONVENTIONS.md § 필드 schema 에 예외 명시.
  • CI test-without-extras job — expected skip count 4 → 5 (tests/test_mcp_server.py 의 file-level pytest.importorskip("fastmcp") 추가). .github/workflows/ci.yml + AGENTS.md § Tests 동시 갱신 (AC-11).

Build

  • external/rhwp submodule pin 0fb3e67 유지 — 본 MINOR 는 pure Python MCP layer, 상류 변경 0.
  • 신규 의존성 (extras 만): fastmcp>=3,<4 ([mcp], [mcp-chunks], [examples], [dependency-groups] testing). 코어 wheel 의존성 (pydantic>=2.5,<3) 변경 없음.

Notes

  • 회귀 가드: tests/test_mcp_server.py (40 테스트 — 36 fast + 1 slow + 3 LOW reviewer-suggested). file-level importorskip("fastmcp") 게이트, 메서드별 importorskip("langchain_text_splitters") 게이트로 chunks smoke 분리. AC-1 ~ AC-11 모두 11/11 충족 (evidence 매핑은 stage-5.md § AC sweep 참조).
  • 미확정 이슈는 v0.5.0 GA 후 demand-driven: get_ir 응답 크기 / 에러 응답 형식 통일 / Resource·Prompt 추상 / 출력 schema 강타입화 (ChunkRecord 등). spec § 미확정 이슈 에 기록.

v0.4.0 — IR view 렌더러 (Markdown / HTML)

Choose a tag to compare

@DanMeon DanMeon released this 05 May 05:58
4df1fba

MINOR release. Document IR (HwpDocument) → Markdown / HTML view 변환 표면을 추가한다. v0.7.0 MCP server (to_markdown / to_html 도구) + 후속 RAG 프레임워크 통합 (v0.5 LlamaIndex / v0.6 Haystack) 의 문자열 출력 1차 인터페이스로 사용. Pure-stdlib 구현 — 신규 의존성 0, schema ("1.1") / 파싱 경로 / Document wrapper / extras 모두 변경 없음 (additive only).

Added

  • HwpDocument.to_markdown() -> str — IR → GFM (GitHub Flavored Markdown). 표는 모든 셀 span == 1 일 때 GFM |...| 표, 병합 셀 (rowspan/colspan > 1) 이 있으면 TableBlock.html 인라인 폴백 (lossy 회피). 이미지는 placeholder 모드 (picture.image.uri pass-through, alt 의 [/] 는 backslash escape), 수식은 script_kind/inline 분기 (latex display → $$..$$, inline → $..$, hwp_eq```hwp-eq fenced, mathml```mathml fenced), 각주/미주는 본문 paragraph 끝 [^N] reference + 출력 끝 [^N]: text 정의 (미주는 [^enN] 별도 number 공간). 머리글/꼬리말은 출력 미포함 (페이지 단위 장식, 결정 8). ListItemBlock.level" " * level 들여쓰기로 보존.
  • HwpDocument.to_html(*, include_css: bool = False) -> str — IR → 완전 HTML5 문서 (<!DOCTYPE html> + <html> + <head> + <body>). 표는 IR TableBlock.html 그대로 inline (재합성 없음, rowspan/colspan 보존), 이미지는 <img alt="<description>" src="<picture.image.uri>">, 수식 디스플레이는 <div class="math">$$..$$</div> (KaTeX/MathJax 호환), 인라인은 <span class="math">$..$</span>, hwp_eq/mathml<pre><code class="language-...">. 각주/미주는 본문 직후 <aside id="fn-N|en-N" class="footnote|endnote"> 정의 + 본문 안 <sup><a href="#...">[N]</a></sup> 인용 마커. ListItemBlock.level > 0<li data-level="N"> 속성으로 보존. include_css=True 일 때 <head> 안 embedded <style> 1회 동봉 (외부 stylesheet 도입 없음).
  • spec / ADR / 구현 로그: docs/roadmap/v0.4.0/view-renderer.md / docs/design/v0.4.0/view-renderer-research.md / docs/implementation/v0.4.0/migration.md. HtmlRAG (arXiv:2411.02959, WWW 2025) 등 최근 연구가 보고하는 구조 보존이 RAG 체감 성능과 직결 동기.

Build

  • external/rhwp submodule pin 0fb3e67 유지 — 본 MINOR 는 pure Python view layer, 상류 변경 0.

Notes

v0.3.2

Choose a tag to compare

@DanMeon DanMeon released this 03 May 13:29

[0.3.2] — 2026-05-03

PATCH release. v0.2.0 IR 매핑이 보유해 온 자체 UTF-16 → codepoint 변환 복사본 (src/ir.rs::utf16_to_cp) 을 상류 Paragraph::utf16_pos_to_char_idx (PR #494 / Task #484, v0.7.9 GA) 로 치환해 SSOT 를 단일화한다. 알고리즘 동등 — IR 출력 byte-equal, 공개 API 변경 없음, SchemaVersion "1.1" 유지.

Build

  • src/ir.rs::utf16_to_cp 자체 복사본 + u32::MAX short-circuit + fallback_end 인자 + 짝 단위 테스트 2건 (utf16_to_cp_sentinel_returns_fallback, utf16_to_cp_matches_first_ge) 제거. build_char_runs 호출부를 para.utf16_pos_to_char_idx(start_utf16) / (end_utf16) 로 치환. 본 binding 운영 정책 ("상류 신뢰 + 결함 시 PR") 일관 적용 — v0.3.1 의 Paragraph::control_text_positions 채택과 같은 결.
  • external/rhwp submodule pin 0fb3e67 유지 — 핀 history 에 PR #494 머지 commit 60eaa91 (2026-04-30) 포함, cargo build 가 시그니처 해소로 직접 검증. v0.7.9 GA 흡수는 직교 영역, 본 PATCH 영구 비목표.
  • 부수 정리: 본 binding 이 제출한 issue 초안 docs/upstream/issue-utf16-pos-to-char-idx.md in-place Frozen 전환 + docs/upstream/README.md 인덱스 RESOLVED 컬럼 채움.

v0.3.1

Choose a tag to compare

@DanMeon DanMeon released this 03 May 04:48

[0.3.1] — 2026-05-02

PATCH release. v0.3.0 의 IR 출고에서 inline 컨트롤 마커의 Provenance.char_start / char_end 가 항상 null 이던 문제를 정정. 상류 v0.7.8 의 Paragraph::control_text_positions() (PR #405 / Task #390) 노출을 활용해 7 종 블록 (각주·미주 마커, 그림, 수식, 필드, TOC, 표) 의 zero-width character 위치를 채운다. SchemaVersion 변경 없음 ("1.1" 유지) — 기존에 nullable 슬롯에 정의된 int | None 에 non-null 값을 출고할 뿐, schema 호환 100%.

Fixed

  • inline 컨트롤 마커 (각주/미주/그림/수식/필드/TOC/표) 의 Provenance.char_start / char_end 가 v0.3.0 까지 항상 null 이던 문제 정정. 부모 paragraph 안 zero-width character 위치 (char_start == char_end == position) 로 채운다.
  • 상류 Paragraph::control_text_positions() (v0.7.8 GA, PR #405 / Task #390) 의 결과를 paragraph 당 1회 호출로 공유하여 controls.len() == positions.len() 길이 invariant 를 release/debug 모두에서 assert_eq! 로 가드 — 상류 contract 위반의 silent regression 차단.
  • 부모 paragraph 의 char_offsets 가 빈 경우 (보통 layout-only 컨트롤만 있는 paragraph) None 폴백 — 상류 fallback 분기의 의미 손실 position 을 그대로 흘리지 않음 (fail-fast).

Build

  • external/rhwp submodule pin 033617e (v0.7.7) → 0fb3e67 (post-v0.7.8). 본 v0.3.1 의 enabling change 는 v0.7.8 의 PR #405 (pub fn Paragraph::control_text_positions). 후속 commit 들은 직교 영역 (Task #484 utf16_pos_to_char_idx 등) 으로 본 PATCH 동작에 영향 없음.
  • PyO3 extension-module feature 를 default features 에서 분리 (PR #13, PyO3 FAQ 권장 패턴) — cargo test 가 libpython 링크 시도 없이 정상 작동, src/ir.rs 의 Rust unit test 13개 (assert_position_invariant_panics_on_mismatch#[should_panic] 포함) 가 CI 에서 검증됨. wheel 빌드 동작 동일 — [tool.maturin] features = ["extension-module"] 가 명시적 활성화. AC-12 invariant 가 source-grep 외에 진짜 panic 검증으로도 보호됨.

Known limitations

  • 중첩 표 안 inline 컨트롤의 Provenance.char_start/end(section_idx, para_idx) 가 다른 paragraph 를 가리켜 text[char_start:char_end] 슬라이싱이 잘못된 결과를 낸다. v0.3.0 부터 있던 Provenance 모델 한계 — v0.3.1 은 새 마커 채움이 동일 모델을 재사용. v0.4.0+ Provenance 정정 spec 에서 다룬다 (spec § 영구 비목표 마지막 항목).

v0.3.0 — IR 확장 + rhwp-py CLI 재도입

Choose a tag to compare

@DanMeon DanMeon released this 28 Apr 08:37

Changed — async API 의존성 정리

  • rhwp.aparseaiofiles 대신 stdlib asyncio.to_thread 사용 — 외부 의존성 제거. [async] extras 키는 빈 배열로 보존 (pip install rhwp[async] 명령 호환 유지, v0.4.0 에서 키 자체 제거 검토). 의미·성능 동등 — Python asyncio 가 native async file I/O 를 미지원하는 한 모든 async file lib (aiofiles 포함) 도 결국 thread pool wrapping 이라 둘은 같은 메커니즘. CI test-without-extras skip count 5 → 4 (test_async.py 가 더 이상 gated 아님).

MINOR release — Phase 2 두 축 동시 GA. v0.2.0 의 Document IR v1.0 위에 HWP 고유 의미 요소 8 종을 추가하고 (SchemaVersion 1.1), 동시에 Python 고유 가치 (IR/LangChain 청크/스키마 export) 를 shell 에서 직접 소비할 수 있는 rhwp-py CLI 를 재도입한다. 모든 v0.2.0 공개 API 보존 — 추가만 있고 기존 코드는 그대로 동작.

상류 edwardkim/rhwp 커밋 핀을 bea635b033617e 로 bump (upstream v0.6.x → v0.7.7 흡수, 380 commits). IR 확장이 사용하는 first-class struct/enum (Picture, Equation, Footnote/Endnote, Caption, Field/FieldType, Header/Footer, ParaShape) 자체는 핀 변경 전부터 노출돼 있어 IR 매핑 작업은 상류 변경에 의존하지 않는다 — bump 의 효과는 직교 영역에 한정: 렌더 경로 정정 (TypesetEngine pagination drift, TAC 표/그림 좌표 통합 수정), export text/markdown 추가 (Task #237), v0.7.6/v0.7.7 릴리즈 흡수.

Added — Document IR v1.1 (8 신규 블록 + Furniture 채움)

  • PictureBlock (S1) — 이미지. image: ImageRef | None (URI 스킴 bin://<bin_data_id> 기본), caption: CaptionBlock | None (S3 부터), description: str | None (HWP alt-text). Document.bytes_for_image(picture) 헬퍼로 raw bytes 해석.
  • FormulaBlock (S2) — 수식. script: str (HWP equation script raw) + script_kind: Literal["hwp_eq", "latex", "mathml"] + text_alt: str | None (RAG 폴백 평문 근사). LaTeX/MathML 자동 변환은 v0.3.0 미제공 (공개 변환기 부재).
  • FootnoteBlock / EndnoteBlock (S2) — 각주 / 미주. blocks: list[Block] 재귀, marker_prov (본문 인용 위치) 와 prov (각주 본문 위치) 분리. 각주/미주 본문은 furniture.footnotes / endnotes 로 라우팅 — body 검색 오염 회피.
  • ListItemBlock (S3) — 목록 항목. level + marker + enumerated 평면 모델 (group container 미도입, HWP 상류가 list group 미지원). marker 는 v0.3.0 placeholder ("•" / "1." / "-") — 정확 marker ("가.", "(a)") 는 v0.4.0+.
  • CaptionBlock (S3) — 캡션. blocks: list[Block] 재귀 + direction: Literal["top", "bottom", "left", "right"]. 부모 컨테인먼트 — PictureBlock.caption / TableBlock.caption_block 으로 1:1 부착 (ref-id 미도입). v0.2.0 TableBlock.caption: str 보존 + caption_block 옵셔널 추가.
  • TocBlock + TocEntryBlock (S3) — 목차. entries: list[TocEntryBlock] (TocEntryBlock 은 leaf type, Block 유니온 멤버 아님). v0.3.0 entries 는 빈 placeholder — 항목 추출은 v0.4.0+ (heading hierarchy + bookmark resolver 필요).
  • FieldBlock + FieldKind (S3) — 필드. 닫힌 Literal 14 종 (date/crossref/hyperlink/...) + "unknown" 안전판 + field_type_code: int | None (forward-compat). FieldType::Formula"calc" (Equation 의 "formula" 와 이름 충돌 회피).
  • Furniture 채움 (S1+S2) — page_headers / page_footers (master_pages + Control::Header/Footer 매핑) / footnotes / endnotes 모두 실제 본문 출고. iter_blocks(scope="furniture") 순서: page_headers → page_footers → footnotes → endnotes (v0.2.0 furniture 순서 계약 확장).

Added — rhwp-py CLI 재도입

v0.2.0 에서 폐기됐던 CLI 를 별도 이름 (rhwp-py) 으로 재도입. 상류 Rust rhwp 바이너리와 PATH 충돌 회피 + Python 고유 가치 (IR / LangChain) 에 집중 — 기능 중복 0.

  • 신규 entry point: rhwp-py = "rhwp.cli:app" (typer 지연 로드, 미설치 시 친절 에러 + exit 2).
  • 서브커맨드: parse (요약 정보) / version / schema (in-package JSON Schema) / ir (전체 IR JSON) / blocks (블록 스트림 NDJSON / JSON / text) / chunks (LangChain RecursiveCharacterTextSplitter 결과).
  • blocks--kind enum 11 종 (paragraph / table + 8 신규 + all) — IR 확장 GA 와 동기.
  • chunks --include-furnitureHwpLoader(mode="ir-blocks", include_furniture=True) 와 동일 정책.
  • 글로벌 옵션 --quiet/-q — stderr progress 메시지 가드.
  • 새 extras: [cli] (typer 만), [cli-chunks] (typer + langchain-text-splitters).
  • 업스트림 바이너리와의 역할 분담: 구조 추출은 rhwp-py, 시각 출력 (SVG/PDF) / 메타데이터 덤프 (info/dump) / 라운드트립 진단 (diag/ir-diff) 은 상류 rhwp — overlap 0.

Added — LangChain HwpLoader.include_furniture

  • HwpLoader(mode="ir-blocks", include_furniture=True) — body 다음에 furniture (page_headers / page_footers / footnotes / endnotes) 도 LangChain Document 로 yield. 각 furniture Document 는 metadata.scope="furniture" 로 표시되어 RAG 가 body / furniture 분리 색인 가능.
  • 기본 include_furniture=False — v0.2.0 시절 동작 (body 만) 보존.
  • mode="single" / "paragraph" 에서는 include_furniture 무시 — text 추출은 항상 body 만.

Added — Schema GA + content-addressed alias

  • SchemaVersion "1.0""1.1" (in-place v1 URL — major 안의 minor 추가).
  • _KNOWN_KINDS 11 known kinds (10 known + UnknownBlock) — Block Annotated Union 11 멤버. callable Discriminator 는 O(1) lookup.
  • JSON Schema in-place 갱신 — python/rhwp/ir/schema/hwp_ir_v1.json (1,261 lines, 20 $defs).
  • Content-addressed alias hwp_ir_v1-sha256-<hash>.jsonpublish-schema.yml 가 매 deploy 시 hash-tagged immutable copy 를 alongside 발행. 구 hash 는 영구 보존 (SchemaStore / 외부 도구 reproducibility).
  • _harden_unknown_variant_KNOWN_KINDS SSOT 를 사용 — TocEntryBlock.kind="toc_entry" 같은 leaf-only kind 가 not.enum 에 포함되어 라운드트립 깨지는 케이스 회피.

Fixed

  • LangChain HwpLoader(mode="ir-blocks") 와 CLI rhwp-py blocks --format text 가 각주·미주·캡션 본문을 평문화할 때 ParagraphBlock 만 처리하여 ListItemBlock 으로 변환된 list 항목이 RAG 색인에서 통째로 누락되던 문제 정정. 신규 rhwp.ir._plain_text 모듈에 ParagraphBlock + ListItemBlock + FormulaBlock + FieldBlock 평문 추출 SSOT 헬퍼 (block_inline_text / join_inline_blocks) 를 도입하고 integration / CLI 양쪽에서 공유한다. caption 평문화도 동일 정책으로 통합 (langchain.py::_caption_plain_text / cli/ir.py::_caption_plain 제거).

Documentation

  • docs/roadmap/v0.3.0/ir-expansion.md — IR 확장 spec (8 결정 사항 + research 인용).
  • docs/roadmap/v0.3.0/cli.mdrhwp-py 재도입 spec (이름 선정 + overlap=0 + extras 정책).
  • docs/design/v0.3.0/ir-expansion-research.md / cli-design-research.md — 결정 증거.
  • docs/implementation/v0.3.0/stages/stage-{1..4}.md — 단계별 구현 로그 (S1: Picture+Furniture, S2: Formula+Footnote/Endnote, S3: ListItem+Caption+Toc+Field, S4: Schema GA + CLI + LangChain include_furniture + 문서).
  • README.md — v0.3.0 신규 블록 + rhwp-py CLI 섹션 추가, content-addressed alias 안내.

Tests

  • 5 신규 IR 테스트 파일 (S1 picture+furniture, S2 formula+footnote, S3 list+caption+toc+field) + 1 CLI 테스트 파일 → 405 (S3) → S4 추가.
  • tests/test_cli.py — typer.testing.CliRunner 기반 smoke + 통합 (parse/version/schema/ir/blocks/chunks 전 서브커맨드 + exit code 1/2 검증 + langchain-text-splitters 미설치 monkeypatch).
  • tests/test_langchain_loader_ir.py 확장 — include_furniture 옵션 4 테스트.
  • CI test-without-extras skip count 4 → 5 (typer 추가).
  • tests/test_ir_plain_text.py 신규 + footnote/caption 회귀 테스트 (LangChain·CLI 양쪽) — ListItemBlock 누락 정정 가드.
  • 테스트 docstring 의 가변 카운트·스테이지 마커 정리 — 다른 파일·CI 잡에 의존하는 카운트가 박혀 있어 stale 되는 안티패턴 (5 skipped 카운트 중 1 / exactly 29 테스트 유지 등) 제거, SSOT 단일화.

Deferred to v0.4.0+

  • ListItemBlock 정확 marker ("가.", "(a)") — Numbering.level_formats lookup.
  • TocBlock.entries 채움 + target_section_idx resolver + is_stale 검출.
  • FieldBlock.cached_value 추출 (paragraph text inline field_ranges 매핑 필요).
  • InlineRun.href 자동 채움 (Hyperlink/Bookmark Field 와 cross-link).
  • PictureBlock embedded / external_dir 임베딩 모드 (Document.to_ir(image_mode=...) 옵션).
  • RevisionMark (변경 이력) — 상류 미지원 (영구 비목표 후보).