Skip to content

Multi-section <w:sectPr> structure collapses to single generic sectPr — headerReference / footerReference lost #67

Description

@kiki830621

Problem

ooxml-swift v0.27.0 round-trip on a docx with multiple <w:sectPr> (one per section break, e.g. front-matter / body / title-page) collapses all sections into a single generic sectPr at the end of document.xml. The collapsed sectPr drops:

  • <w:headerReference w:type="default|even|first" r:id="..."/> — every section's header invocations
  • <w:footerReference w:type="default|even|first|first" r:id="..."/> — every section's footer invocations
  • Section-break positions (the section structure is flattened — original break locations gone)
  • Section-level page properties (<w:pgSz>, <w:pgMar>, <w:pgNumType>, <w:titlePg/>)

Even when header/footer XML files exist with rich content, none of them are activated because no section references them. Word opens the docx without errors but renders no watermark, no page numbers, no chapter-aware page numbering format (lowerRoman vs decimal).

Discovered while rescuing the NTPU master's thesis (kiki830621/collaboration_guo_analysis#20) immediately after fixing #65 (replaceText) and #66 (header/footer VML).

Type

bug

Evidence

Reproduction: DocxReader.read(_raw.docx) then DocxWriter.write(_, to: out.docx) with no edits in between.

_raw.docx — 3 sections with full structure

Section 1 (front matter, lowerRoman pages):

<w:sectPr w:rsidR="008A36FA" w:rsidRPr="000128A9" w:rsidSect="00F25889">
  <w:headerReference w:type="even"    r:id="rId8"/>
  <w:headerReference w:type="default" r:id="rId9"/>
  <w:footerReference w:type="default" r:id="rId10"/>
  <w:headerReference w:type="first"   r:id="rId11"/>
  <w:footerReference w:type="first"   r:id="rId12"/>
  <w:pgSz w:w="11906" w:h="16838"/>
  <w:pgMar w:top="1440" w:right="1797" w:bottom="1440" w:left="1797"
           w:header="1474" w:footer="397" w:gutter="0"/>
  <w:pgNumType w:fmt="lowerRoman" w:start="1"/>
  <w:cols w:space="425"/>
  <w:docGrid w:type="lines" w:linePitch="381"/>
</w:sectPr>

Section 2 (body, different headers/footers):

<w:sectPr w:rsidR="0020589C" w:rsidRPr="005611B2" w:rsidSect="0089106F">
  <w:headerReference w:type="even"    r:id="rId13"/>
  <w:headerReference w:type="default" r:id="rId14"/>
  <w:footerReference w:type="default" r:id="rId15"/>
  <w:headerReference w:type="first"   r:id="rId16"/>
  <w:footerReference w:type="first"   r:id="rId17"/>
  …same pgSz/pgMar/pgNumType/etc…
</w:sectPr>

Section 3 (cover page, no headers/footers):

<w:sectPr …>
  <w:pgSz w:w="11906" w:h="16838"/>
  …
  <w:titlePg/>
</w:sectPr>

v0.27.0 output — single generic sectPr, no references

Total 228 chars:

<w:sectPr>
  <w:pgSz w:w="12240" w:h="15840"/>
  <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"
           w:header="720" w:footer="720" w:gutter="0"/>
  <w:cols w:space="720" w:num="1"/>
  <w:docGrid w:linePitch="360"/>
</w:sectPr>

Diff:

  • 3 <w:sectPr> → 1
  • 5 <w:headerReference> × 2 sections → 0
  • 4 <w:footerReference> × 2 sections → 0
  • <w:titlePg/> (cover page) → lost
  • <w:pgNumType w:fmt="lowerRoman"/>lost (defaults to decimal)
  • Page size: A4 (11906 × 16838) → Letter (12240 × 15840) — likely default fallback because original pgSz dropped
  • Page margins: thesis margins (1797/1474/397) → Word defaults (1440/720/720)

document.xml.rels correctly maps rId8..rId17 → header1-6.xml + footer1-4.xml, all files exist in the zip. They are orphaned because the body never invokes them via <w:sectPr>.

Expected

Round-trip preserves:

  1. Section break positions (<w:p><w:pPr><w:sectPr>…</w:sectPr></w:pPr></w:p> mid-document and final-paragraph-with-sectPr at end)
  2. Each <w:sectPr> byte-equivalent (or at least: all <w:headerReference> / <w:footerReference> / <w:titlePg/> / <w:pgSz> / <w:pgMar> / <w:pgNumType> retained)
  3. Multi-section structure → multi-section output

Actual

All sections collapse to one generic sectPr at the end of body. References lost. Page properties reset to Word defaults.

Impact

Workaround for thesis #20

Surgical Python sectPr swap: replace v0.27.0's single empty sectPr with _raw section 2's sectPr (string-level inject). Single-section result, but watermark + footer references active. Page-number format distinction (lowerRoman in front matter vs decimal in body) is lost — accepted as P3 cosmetic compromise for thesis-submission deadline.

Reproduction recipe

import OOXMLSwift

// _raw.docx has 3 sectPr in document.xml; v0.27.0 output has 1
let inURL = URL(fileURLWithPath: "/path/to/_raw.docx")
let outURL = URL(fileURLWithPath: "/tmp/out.docx")

var doc = try DocxReader.read(from: inURL)
try DocxWriter.write(doc, to: outURL)

// inspect:
//   unzip -p _raw.docx word/document.xml | grep -c '<w:sectPr'  → 3
//   unzip -p out.docx  word/document.xml | grep -c '<w:sectPr'  → 1
//   unzip -p out.docx  word/document.xml | grep -c '<w:headerReference'  → 0

Long-term fix sketch

DocumentParser (or whoever owns <w:body> parsing) needs to:

  1. Capture <w:sectPr> as a typed structure (model class SectionProperties) holding:
    • headerReferences: [HeaderReference] (with type: even|default|first, relId: String)
    • footerReferences: [FooterReference]
    • pageSize: PageSize?
    • pageMargin: PageMargin?
    • pageNumberType: PageNumberType?
    • titlePage: Bool
    • rawXML: String (for byte-equivalent fallback while typed coverage incomplete)
  2. Capture both mid-document section breaks (sectPr inside <w:p><w:pPr>) AND final-section sectPr (direct child of <w:body>).
  3. Track section break paragraph positions so the writer emits sectPr at the same body position.
  4. DocumentWriter re-emits sectPr blocks at captured positions with all header/footer references intact.

If full typed coverage is too large for a single PR, an interim fix is just:

  • Capture rawXML for every <w:sectPr> and its enclosing paragraph break verbatim → emit verbatim. No typed access, but byte-equivalent for round-trip safety.

Relationship to #62 / #66

  • #62 — VML/wps/wpg in body
  • #66 — VML in headers/footers (sister of this issue)
  • This issue (#NEW) — section structure / sectPr in document.xml

All three are surfaced during the same NTPU thesis rescue. They're orthogonal but compound: #62 fixes body shapes, #66 fixes header/footer VML, this issue activates header/footer references so they render. All three needed for full thesis fidelity.

Source

Surfaced during /idd-issue invocation while rescuing NTPU thesis (kiki830621/collaboration_guo_analysis#20). Confirmed via Python diff of _raw.docx vs ooxml-swift v0.27.0 round-trip output.


Current Status

Phase: implemented
Last updated: 2026-05-05 by idd-implement

Key Decisions

Scope Changes

  • (none — Phase A scope held strictly per plan)

Blocking

  • (none — ready for /idd-verify)

Tangential (filed during plan drafting Step 2.5)

Commits

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions