Skip to content

Task #172: section.xml 컨트롤 디스패처 — 표/그림/도형 직렬화 연결#730

Closed
oksure wants to merge 5 commits into
edwardkim:develfrom
oksure:contrib/hwpx-control-dispatch
Closed

Task #172: section.xml 컨트롤 디스패처 — 표/그림/도형 직렬화 연결#730
oksure wants to merge 5 commits into
edwardkim:develfrom
oksure:contrib/hwpx-control-dispatch

Conversation

@oksure

@oksure oksure commented May 9, 2026

Copy link
Copy Markdown
Contributor

요약

section.rsrender_run_content()/render_control_slot()을 확장하여, 이슈 #172의 체크리스트 6개 항목을 모두 커버합니다.

이슈 #172 체크리스트 커버리지

  • 표 (Control::Table) → <hp:tbl>render_control_slot() 디스패치 연결
  • 그림 (Control::Picture) → <hp:pic> + BinData — 디스패치 연결 + BinData ZIP/매니페스트 기존 구현 활용
  • 도형 (Control::Shape) → 변형별 공통 속성 XML 출력
  • 각주/미주 → <hp:ctrl><hp:footNote>/<hp:endNote> + <hp:subList> 재귀 문단 직렬화
  • BinData ZIP 엔트리 → mod.rs 기존 3-way 동기화 구현 활용
  • content.hpf manifest 자동 등록 → mod.rs 기존 구현 활용

변경 내용

1. 컨트롤 디스패처 확장 (section.rs)

  • render_run_content() 의 Equation-only 게이트를 slots.is_empty()로 변경 — Table/Picture/Shape/Footnote/Endnote 모두 직렬화
  • render_control_slot() 에 Table/Picture/Shape/Footnote/Endnote 분기 추가
  • 각주/미주에 <hp:ctrl> 래퍼 추가 (HWPX 파서 호환)
  • Shape: ShapeObject::Picturepicture::write_picture()에 위임

2. SerializeContext 확장 (context.rs)

  • write_section()ctx: &SerializeContext&mut SerializeContext
  • collect_from_document(): 인라인 Table의 borderFillIDRef를 1-pass에서 사전 등록

3. Writer→String 브릿지

  • writer_to_string(): Writer<Vec<u8>>String 변환 (Cursor 불필요)
  • 에러 발생 시 eprintln 로깅 (조용한 누락 방지)

4. 라운드트립 테스트 4개 추가 (mod.rs)

  • picture_bindata_roundtrip: Picture + BinData ZIP 엔트리 + binaryItemIDRef 직렬화 검증
  • table_control_roundtrip: Table 직렬화 + 파싱 왕복 검증
  • footnote_endnote_roundtrip: 각주/미주 직렬화 + 파싱 왕복 검증 (내부 문단 텍스트 보존)
  • tac_img_sample_has_pictures_and_bindata: 실문서 BinData/Picture 존재 스모크 테스트

테스트

  • cargo test 전체 통과 (1285 테스트, 0 실패)
  • cargo clippy -- -D warnings 경고 0건
  • 실문서 라운드트립 통합 테스트 14개 전부 통과

Closes #172

기존 render_control_slot()이 Equation만 처리하던 것을 Table, Picture, Shape로 확장:

- Table → table::write_table() 호출 (quick_xml Writer → String 변환 브릿지)
- Picture → picture::write_picture() 호출 (BinData 참조 포함)
- Shape → ShapeObject 변형별 공통 속성(sz/pos/outMargin) 기반 XML 출력
- write_section() ctx 시그니처 &→&mut 변경 (Table 직렬화의 borderFillIDRef 등록 지원)
- 기존 테스트 borderFill 검증 호환을 위해 테스트 fixture에 default BorderFill 추가

Refs edwardkim#172

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 9, 2026 05:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

section.xml 생성 경로에서 기존 Equation만 처리하던 컨트롤 디스패처를 Table/Picture/Shape까지 확장해, Stage 3~5에서 구현된 표/그림 writer를 실제 섹션 직렬화에 연결합니다. 또한 표 직렬화에서 borderFillIDRef 참조 추적이 가능하도록 SerializeContext를 mutable로 전달하도록 시그니처를 조정합니다.

Changes:

  • render_control_slot()Table/Picture/Shape 분기를 추가하고, table::write_table()/picture::write_picture()를 section 경로에 배선
  • write_section() 및 관련 호출부에서 SerializeContext&mut로 전달하도록 변경
  • 테스트 fixture에서 Table::default()가 참조하는 기본 border fill(0번) 엔트리를 추가

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/serializer/hwpx/section.rs 컨트롤 디스패처 확장, Writer→String 브릿지 추가, Shape 공통 속성 기반 출력 추가, ctx를 mutable로 전달
src/serializer/hwpx/mod.rs write_section() 호출부를 &mut ctx로 변경하고 테스트 fixture에 기본 border fill 추가

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +291 to 310
fn render_control_slot(out: &mut String, control: &Control, ctx: &mut SerializeContext) {
match control {
Control::Equation(eq) => {
out.push_str(&render_equation(eq));
}
Control::Table(tbl) => {
if let Ok(xml) = writer_to_string(|w| table::write_table(w, tbl, ctx)) {
out.push_str(&xml);
}
}
Control::Picture(pic) => {
if let Ok(xml) = writer_to_string(|w| picture::write_picture(w, pic, ctx)) {
out.push_str(&xml);
}
}
Control::Shape(shape) => {
out.push_str(&render_shape(shape));
}
_ => {}
}
Comment on lines +331 to +336
ShapeObject::Polygon(p) => ("polygon", &p.common),
ShapeObject::Curve(cv) => ("curve", &cv.common),
ShapeObject::Group(g) => ("container", &g.common),
ShapeObject::Picture(_) => return String::new(),
ShapeObject::Chart(ch) => ("chart", &ch.common),
ShapeObject::Ole(o) => ("ole", &o.common),
Comment on lines +344 to +359
r#"<hp:{tag} id="{id}" zOrder="{zo}" textWrap="{tw}" textFlow="BOTH_SIDES" lock="0">"#,
r#"<hp:sz width="{w}" height="{h}" widthRelTo="ABSOLUTE" heightRelTo="ABSOLUTE"/>"#,
r#"<hp:pos treatAsChar="{tac}" vertRelTo="{vr}" vertAlign="{va}" horzRelTo="{hr}" horzAlign="{ha}" vertOffset="{vo}" horzOffset="{ho}"/>"#,
r#"<hp:outMargin left="{ml}" right="{mr}" top="{mt}" bottom="{mb}"/>"#,
r#"</hp:{tag}>"#,
),
tag = tag,
id = c.instance_id, zo = c.z_order,
tw = text_wrap_to_hwpx(c.text_wrap),
tac = if c.treat_as_char { "1" } else { "0" },
w = c.width, h = c.height,
vr = vert_rel_to_hwpx(c.vert_rel_to), va = vert_align_to_hwpx(c.vert_align),
hr = horz_rel_to_hwpx(c.horz_rel_to), ha = horz_align_to_hwpx(c.horz_align),
vo = c.vertical_offset, ho = c.horizontal_offset,
ml = c.margin.left, mr = c.margin.right,
mt = c.margin.top, mb = c.margin.bottom,
Comment on lines +313 to +323
fn writer_to_string<F>(f: F) -> Result<String, SerializeError>
where
F: FnOnce(&mut Writer<Cursor<Vec<u8>>>) -> Result<(), SerializeError>,
{
let buf = Vec::new();
let cursor = Cursor::new(buf);
let mut writer = Writer::new(cursor);
f(&mut writer)?;
let bytes = writer.into_inner().into_inner();
String::from_utf8(bytes).map_err(|e| SerializeError::XmlError(e.to_string()))
}
render_control_slot()에 Footnote/Endnote 분기 추가:
- <hp:footNote>/<hp:endNote> + <hp:subList> + 재귀 문단 직렬화
- is_hwpx_inline_slot()에 Footnote/Endnote 추가
- render_note_sublist() 공통 헬퍼로 footNote/endNote 양쪽 처리

Refs edwardkim#172

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@oksure

oksure commented May 9, 2026

Copy link
Copy Markdown
Contributor Author

각주/미주 직렬화 디스패치 추가 커밋 (99c8ac6):

  • render_control_slot()Control::Footnote/Control::Endnote 분기 추가
  • <hp:footNote>/<hp:endNote> + <hp:subList> 구조로 내부 문단 재귀 직렬화
  • 기존 render_paragraph_parts() 재활용으로 문단 속성(paraPrIDRef, charPrIDRef, lineseg) 보존

이슈 #172 체크리스트의 각주/미주 항목이 이 커밋으로 커버됩니다.

- writer_to_string(): 불필요한 Cursor 제거, Writer<Vec<u8>> 직접 사용
- Table/Picture 직렬화 실패 시 eprintln 로깅 (조용한 누락 방지)
- ShapeObject::Picture: picture::write_picture()로 위임 직렬화
- render_shape()에 ctx 파라미터 추가로 Picture BinData 참조 지원

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@oksure

oksure commented May 9, 2026

Copy link
Copy Markdown
Contributor Author

Copilot 리뷰 반영 (328b00e):

  1. 에러 로깅: Table/Picture 직렬화 실패 시 eprintln 로깅 추가 (조용한 누락 방지)
  2. ShapeObject::Picture 위임: picture::write_picture() 로 위임 직렬화 (Group 자식 그림 누락 해소)
  3. writer_to_string 간소화: 불필요한 Cursor 제거, Writer<Vec<u8>> 직접 사용, UTF-8 실패 시 명확한 에러 메시지
  4. Shape 속성 누락 (affectLSpacing 등): 유효한 지적이나 현재 CommonObjAttr IR에 해당 필드가 없어 즉시 수정 불가 — 향후 IR 확장 시 반영 예정으로 PR 설명에 잔여 범위로 명시

section.rs의 render_run_content()가 Equation 컨트롤만 확인하여
Table/Picture/Shape/Footnote/Endnote 컨트롤이 직렬화되지 않던
버그를 수정합니다.

변경 사항:
- section.rs: Equation-only 게이트를 slots.is_empty()로 변경
- section.rs: 각주/미주에 <hp:ctrl> 래퍼 추가 (파서 호환)
- context.rs: 인라인 Table의 borderFillIDRef를 1-pass에서 사전 등록
- mod.rs: Picture+BinData, Table, Footnote/Endnote 라운드트립 테스트 4개 추가

이슈 edwardkim#172 체크리스트 6개 항목 모두 커버:
- 표, 그림, 도형 → 디스패치 연결 (이전 커밋)
- 각주/미주 → 디스패치 연결 (이전 커밋)
- BinData ZIP 엔트리 → mod.rs 기존 구현 활용
- content.hpf 매니페스트 → mod.rs 기존 구현 활용

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@oksure

oksure commented May 9, 2026

Copy link
Copy Markdown
Contributor Author

인라인 컨트롤 직렬화 게이트 수정 + 라운드트립 테스트 추가 (815759b):

핵심 수정: render_run_content()에서 Equation 컨트롤만 확인하던 가드를 slots.is_empty()로 변경하여, 이전 커밋에서 추가한 Table/Picture/Shape/Footnote/Endnote 디스패치가 실제로 작동하도록 수정했습니다.

추가 수정:

  • 각주/미주 XML에 <hp:ctrl> 래퍼 추가 (HWPX 파서 호환)
  • collect_from_document()에서 인라인 Table의 borderFillIDRef를 사전 등록 (실문서 라운드트립 호환)

테스트: Picture+BinData, Table, Footnote/Endnote 라운드트립 테스트 4개 추가. 전체 1285 테스트 통과, clippy 경고 0건.

이슈 #172 체크리스트 6개 항목 모두 커버됩니다.

글상자(TextBox) 직렬화:
- shape.rs: write_draw_text() — TextBox의 paragraphs를 <hp:drawText>
  + <hp:subList> + <hp:p> 구조로 직렬화
- shape.rs: write_rect()에 drawText 자동 연결 (paragraphs 비어있지 않을 때)
- section.rs: Rectangle/Line Shape을 Writer-based serializer로 변경
  (render_common_shape_xml → write_rect/write_line)

실문서 라운드트립 통합 테스트 3개 추가:
- stage5_table_control_preserved_on_roundtrip: 표-텍스트.hwpx 표 보존
- stage5_picture_bindata_preserved_on_roundtrip: tac-img-02.hwpx 그림+BinData 보존
- stage5_large_doc_table_count_preserved: 2025년 1분기 보도자료 표 보존

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@oksure

oksure commented May 9, 2026

Copy link
Copy Markdown
Contributor Author

추가 커밋 (ec0b509):

drawText 글상자 직렬화:

  • shape.rs: write_draw_text() — TextBox의 paragraphs를 hp:drawText + hp:subList 구조로 직렬화
  • section.rs: Rectangle/Line Shape을 Writer-based serializer로 변경 (draw_text 포함)

실문서 라운드트립 통합 테스트 3개 추가:

  • 표-텍스트.hwpx: 표 컨트롤 보존 검증 ✅
  • tac-img-02.hwpx: Picture + BinData 전체 라운드트립 검증 ✅
  • 2025년 1분기 보도자료: 대형 문서 표 보존 검증 ✅

전체 1290+ 테스트 통과, 통합 테스트 17개 (기존 14개 + 신규 3개).

edwardkim added a commit that referenced this pull request May 9, 2026
PR #730 (@oksure) 영역 5 commits 영역 squash cherry-pick (개별 cherry-pick 영역 영역 commits 영역 영역 누적 변경 영역 영역 충돌 발생 영역 영역 PR HEAD 영역 squash 채택 — PR #729 영역 영역 동일 패턴).

원본 commits:
- 7a54437 Task #172: section.xml 컨트롤 디스패처 — 표/그림/도형 직렬화 연결
- 99c8ac6 Task #172: 각주/미주 디스패처 연결 — Footnote/Endnote subList 문단 직렬화
- 328b00e fix: Copilot 리뷰 반영 — 에러 로깅, Cursor 제거, ShapeObject::Picture 위임
- 815759b fix: 인라인 컨트롤 직렬화 게이트 수정 + 라운드트립 테스트 추가
- ec0b509 feat: drawText 글상자 직렬화 + 실문서 라운드트립 검증 테스트

본질 (5 files, +619/-37):
- src/serializer/hwpx/section.rs (+159/-33): render_run_content / render_control_slot 디스패처 확장 — Equation-only 게이트 영역 → slots.is_empty() 영역 영역 Table/Picture/Shape/Footnote/Endnote 모두 직렬화. 각주/미주 영역 <hp:ctrl> 래퍼 + <hp:subList> 재귀 문단. is_hwpx_inline_slot 영역 Footnote/Endnote 추가
- src/serializer/hwpx/mod.rs (+210/-2): 라운드트립 테스트 4건 신규 (picture_bindata / table_control / footnote_endnote / tac_img_sample)
- src/serializer/hwpx/shape.rs (+122/-2): drawText 글상자 직렬화 + Rectangle 영역 영역 의 Writer-based serializer
- src/serializer/hwpx/context.rs (+19): collect_from_document 영역 인라인 Table 영역 borderFillIDRef 1-pass 사전 등록
- tests/hwpx_roundtrip_integration.rs (+109): stage5_table_control / stage5_picture_bindata / stage5_footnote_endnote / stage5_tac_img_sample 영역 4건 신규

Issue #172 체크리스트 6 항목 모두 커버:
- [x] 표 (Control::Table) → <hp:tbl>
- [x] 그림 (Control::Picture) → <hp:pic> + BinData
- [x] 도형 (Control::Shape) → 변형별 공통 속성
- [x] 각주/미주 → <hp:footNote/endNote> + <hp:subList>
- [x] BinData ZIP 엔트리 (mod.rs 기존 활용)
- [x] content.hpf manifest 자동 등록 (mod.rs 기존 활용)

Copilot 리뷰 반영 (commit 328b00e):
- 에러 로깅 (eprintln 영역 조용한 누락 방지)
- Cursor 제거 (Writer<Vec<u8>> 직접)
- ShapeObject::Picture 위임 (picture::write_picture)

⚠️ 한컴 호환 검증 한계:
- 자기 라운드트립 영역 영역 만 입증 (parse → serialize → parse)
- 한컴 호환 영역 영역 입증 부재 — 작업지시자 한컴2020/2022 검증 게이트 권장
- feedback_self_verification_not_hancom 정합

closes #172
edwardkim added a commit that referenced this pull request May 9, 2026
PR #730 (@oksure) 옵션 A 처리 — PR HEAD squash cherry-pick + no-ff merge.

본질 정정 (5 files, +619/-37):
- src/serializer/hwpx/section.rs (+159/-33): render_run_content / render_control_slot 디스패처 확장 — Equation-only 게이트 영역 → slots.is_empty() 영역 영역 Table/Picture/Shape/Footnote/Endnote 모두 직렬화. 각주/미주 영역 <hp:ctrl> 래퍼 + <hp:subList> 재귀 문단. is_hwpx_inline_slot 영역 Footnote/Endnote 추가
- src/serializer/hwpx/mod.rs (+210/-2): 라운드트립 테스트 4건 신규
- src/serializer/hwpx/shape.rs (+122/-2): drawText 글상자 직렬화 + Rectangle 영역 영역 의 Writer-based serializer
- src/serializer/hwpx/context.rs (+19): collect_from_document 영역 인라인 Table 영역 borderFillIDRef 1-pass 사전 등록
- tests/hwpx_roundtrip_integration.rs (+109): stage5_table_control / stage5_picture_bindata / stage5_footnote_endnote / stage5_tac_img_sample 영역 4건 신규

Issue #172 체크리스트 6 항목 모두 커버:
- [x] 표 (Control::Table) → <hp:tbl>
- [x] 그림 (Control::Picture) → <hp:pic> + BinData
- [x] 도형 (Control::Shape) → 변형별 공통 속성
- [x] 각주/미주 → <hp:footNote/endNote> + <hp:subList>
- [x] BinData ZIP 엔트리 (mod.rs 기존 활용)
- [x] content.hpf manifest 자동 등록 (mod.rs 기존 활용)

Copilot 리뷰 반영 (commit 328b00e): 에러 로깅 + Cursor 제거 + ShapeObject::Picture 위임.

자기 검증:
- cherry-pick: PR HEAD squash 채택 (개별 commits 영역 영역 누적 변경 충돌 영역 영역 squash, PR #729 동일 패턴)
- cargo build/test --release ✅ ALL GREEN
- cargo test hwpx_roundtrip_integration ✅ **17 PASS** (신규 4건 + 기존 13건)
- 광범위 sweep 7 fixture / 170 페이지 / **회귀 0** ✅
- WASM 빌드 4.61 MB

판정 게이트:
- **라운드트립 테스트 17 PASS** ✅ 영역 영역 본 PR 영역 영역 의 판정 게이트 (작업지시자 결정)
- 한컴 호환 영역 영역 영역 영역 후속 작업 영역 영역 (작업지시자 한컴 검증 영역 영역 별건 진행 가능)
- feedback_self_verification_not_hancom 영역 영역 본 PR 영역 영역 영역 자기 검증 영역 영역 게이트 채택 영역 (라운드트립 광범위 + 신규 4건 영역 영역 결정적 입증)

closes #172
edwardkim added a commit that referenced this pull request May 9, 2026
- mydocs/pr/archives/pr_730_review.md: 검토 문서 archives 이동
- mydocs/pr/archives/pr_730_report.md: 처리 보고서 작성
  · HWPX section.xml 컨트롤 디스패처 확장 (Issue #172 체크리스트 6 항목 모두 커버)
  · PR HEAD squash cherry-pick (5 commits → 1 commit `3d2a8eeb`)
  · hwpx_roundtrip_integration 17 PASS (신규 4건 + 기존 13건)
  · 광범위 sweep 170/170 same
  · 판정 게이트: 라운드트립 테스트 (작업지시자 결정)
  · 한컴 호환 검증 영역 영역 후속 별건 (feedback_self_verification_not_hancom 정합)
- mydocs/orders/20260510.md: PR #730 항목 추가 (5/10 사이클 영역 영역 6건 처리)
@edwardkim

Copy link
Copy Markdown
Owner

@oksure 님, 검토 + 머지 완료했습니다.

처리 결과

옵션 A (PR HEAD squash cherry-pick + no-ff merge `47a303f2`) 로 처리.

본 환경 영역 영역 영역 5 commits 영역 영역 개별 cherry-pick 영역 영역 commits 영역 영역 누적 변경 영역 영역 충돌 발생 영역 영역 영역 PR HEAD squash cherry-pick (`3d2a8eeb`) 영역 영역 단일 commit 영역 영역 적용 (충돌 0건). 5 commits 영역 영역 의 의미 (Task 2 + fix 2 + feat 1) 영역 영역 squash commit message 영역 영역 보존.

자기 검증

  • `cargo build/test --release` ✅ ALL GREEN
  • `cargo test --release --test hwpx_roundtrip_integration` ✅ 17 PASS (신규 4건 + 기존 13건)
  • 광범위 sweep (7 fixture / 170 페이지) ✅ 170 same / 0 diff (직렬화 영역 영역 만 변경 영역 영역 시각 출력 영역 영역 무관 보장)
  • WASM 빌드 ✅ 4.61 MB

판정 게이트 — 라운드트립 테스트 (작업지시자 결정)

본 PR 영역 영역 영역 라운드트립 테스트 17 PASS 영역 영역 판정 게이트 영역 영역 채택 (작업지시자 결정). 자기 라운드트립 결정적 입증 + 광범위 sweep 0 회귀 + 직렬화 영역 영역 만 변경 영역 영역 시각 출력 무관 영역 영역 면제 합리.

신규 회귀 가드 4건 (영역 영역 `tests/hwpx_roundtrip_integration.rs`):

  • `stage5_table_control_preserved_on_roundtrip` (표-텍스트.hwpx)
  • `stage5_picture_bindata_preserved_on_roundtrip` (tac-img-02.hwpx)
  • `stage5_footnote_endnote_preserved_on_roundtrip`
  • `stage5_tac_img_sample_has_pictures_and_bindata`

한컴 호환 검증 영역 영역 후속 별건

`feedback_self_verification_not_hancom` 영역 정합 — 본 PR 영역 영역 영역 자기 라운드트립 영역 영역 게이트 채택 영역 영역, 한컴2020/한컴2022 영역 영역 정상 열림 + 표/그림/각주 정합 검증 영역 영역 영역 후속 별건 영역 영역 처리 가능.

처리 보고서: `mydocs/pr/archives/pr_730_report.md`.

Issue #172 체크리스트 6 항목 모두 커버

  • 표 (`Control::Table` → `hp:tbl`)
  • 그림 (`Control::Picture` → `hp:pic` + BinData)
  • 도형 (`Control::Shape` 변형별 공통 속성)
  • 각주/미주 (`hp:footNote/endNote` + `hp:subList`)
  • BinData ZIP 엔트리 (mod.rs 기존 활용)
  • content.hpf manifest 자동 등록 (mod.rs 기존 활용)

@oksure20+ 사이클 컨트리뷰션 영역 — 5/10 사이클 영역 영역 PR #720/#723/#725/#728/#729/#730 영역 6건 처리 완료 영역.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants