Skip to content

feat(odt): nested containers and unique object names after cloning (#138) - #213

Merged
vaceslav merged 1 commit into
mainfrom
feat/138-odt-containers
Sep 26, 2026
Merged

vaceslav merged 1 commit into
mainfrom
feat/138-odt-containers

Conversation

@vaceslav

Copy link
Copy Markdown
Contributor

Summary

This is step 4 of OpenDocument Text support. It covers two areas:

Part of #138

Design

See docs/superpowers/specs/2026-09-26-odt-support-design.md, phase 4. This PR updates the spec.

Nested containers already went through the same ProcessContainer path in PRs 1–3, and this PR adds tests for them. A text box or note inside a loop iteration is processed with that iteration's context. An emptied text box or header gets an empty paragraph.

The new work is OdtUniqueNames. Loop cloning copies these identifiers:

  • draw:name (frames and shapes)
  • table:name
  • the text:name of sections
  • the text:id of notes
  • xml:id

ODF requires these to be unique. LibreOffice renames duplicate frames and tables on load, which breaks references to them, and duplicate xml:ids make the document invalid.

A single pass runs after processing, over the body plus headers and footers, which share one name space:

  • The first occurrence keeps its name. Later duplicates get _2, _3, and so on, avoiding names that already exist.
  • Duplicate xml:ids are removed from the later copies. They only link RDF metadata, which belongs to the original.
  • styles.xml stays byte-for-byte unchanged unless it actually contains a duplicate.

draw:name handling was planned for PR 5 and has been moved into this PR, because all the name fixes belong in one pass.

Conservative decision, documented in the spec: bookmark and annotation names are not renamed. They come in start/end pairs, LibreOffice tolerates duplicates, and DOCX does not rename loop-cloned bookmarks either.

Changes

  • OpenDocument/OdtUniqueNames (new, internal).
  • OdtTemplateEngine.Process runs the uniqueness pass after the body and the headers/footers.
  • Spec: phase table and component table updated, plus a new known limitation (bookmarks).

The DOCX code is unchanged.

Tests

  • Odt/OdtContainerTests (10 tests):
    • A loop that clones frames with text boxes, footnotes, tables and sections gets unique names and ids, and the nested content is processed per item.
    • New names avoid names that already exist.
    • Header and body share one name space.
    • Loops inside a text box and inside an endnote.
    • An empty loop inside a text box leaves one empty paragraph.
    • A loop inside a section.
    • Nested list-item loops inside a list-item loop, including access to the parent variable.
    • A table-row loop inside a footer table.
    • A conditional that removes the whole header content leaves an empty paragraph.
  • New LibreOffice round trip ClonedFramesTablesAndSections_KeepTheirUniqueNamesInLibreOffice. The processed document is loaded and saved again by LibreOffice. Afterwards Box/Box_2, Prices/Prices_2 and Details/Details_2 are still present unchanged, so LibreOffice accepted them without renaming, and the content converts to text.
  • All 6 LibreOffice tests passed locally on net8.0, net9.0 and net10.0 (LibreOffice 25, macOS).
  • The full suite is green: 2008 tests on each of net8, net9 and net10, plus the Tools and Converter tests. CI-mode build and dotnet format pass.

Public API impact: additive

No new public symbols. PublicAPI.*.txt is unchanged. The only behavior change is in the unreleased OdtTemplateProcessor: duplicate object names in its output are made unique.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 97.36842% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...riasDev.Templify/OpenDocument/OdtTemplateEngine.cs 87.50% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@vaceslav
vaceslav merged commit caa48c7 into main Sep 26, 2026
12 checks passed
@vaceslav
vaceslav deleted the feat/138-odt-containers branch September 26, 2026 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants