Skip to content

feat(odt): conditionals for OpenDocument Text templates (#138) - #211

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

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

Conversation

@vaceslav

@vaceslav vaceslav commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds conditionals to OdtTemplateProcessor: {{#if}}, {{#elseif}}, {{#else}} and {{/if}}, with the full 1.7.0 condition engine. This is the second step of OpenDocument Text support.

Part of #138

Design

Follows docs/superpowers/specs/2026-09-26-odt-support-design.md (phase 2). The spec gains §8.1, which records the list decision below.

The ODT engine works like the Word pipeline:

  • Conditionals are detected per block sequence and processed deepest first. Blocks already removed are skipped.
  • Marker paragraphs are not processed for placeholders.
  • Inline conditionals are resolved on the paragraph text with the shared InlineConditionalParser and ConditionalEvaluator. Only marker text and non-matching branch text is removed, and kept text retains its spans.
  • Errors and warnings are the same as for DOCX: unmatched {{#if}}, {{#elseif}} after {{#else}}, several row markers in one row, and a malformed condition evaluates to false with an ExpressionFailed warning. Messages are unchanged, and errors surface as a failed ProcessingResult.

Design decision (lists), made conservatively and documented in spec §8.1. In ODF, list items are nested containers, not paragraphs as in Word. So:

  • Markers in their own list items work like table rows: marker items are removed, and the items between them are kept or removed as a whole.
  • A list with a single item (LibreOffice's representation of a lone bullet between paragraphs) carries marker text at the enclosing level, so a one-item list can act as a block marker, as in Word.
  • A marker inside a longer list must be matched within that list; otherwise processing fails with the usual "no matching {{/if}}" error.

Container integrity:

  • Emptied cells, text boxes, notes and headers/footers get an empty text:p.
  • An emptied list item is removed.
  • Lists, tables and header-row groups left without items or rows are removed, since ODF requires at least one row per table.

Changes

  • OpenDocument/OdtMarkerText: marker text of paragraphs, rows, cells, list items and single-item lists. Text boxes, notes and annotations are excluded.
  • OpenDocument/OdtConditionalBlock, OdtConditionalDetector: block, table-row and list-item detection. This is a port of ConditionalDetector over XElement. A container that holds a complete conditional is not treated as a marker.
  • OpenDocument/OdtConditionalProcessor: evaluates branches and resolves inline conditionals. This is the counterpart of ConditionalVisitor.
  • OdtTemplateEngine: conditional step per block sequence, per row container (table, header rows, row groups) and per list, plus the container-integrity rules above.
  • XML docs of OdtTemplateProcessor updated to describe what is now supported.

No DOCX file was touched.

Tests

  • Odt/OdtConditionalTests: 31 test methods.
    • Block conditionals, true and false.
    • elseif chains, nested blocks, removed branches not reporting missing variables.
    • Tables and lists inside blocks; headings as markers.
    • Inline conditionals: with else, elseif and nesting, several per paragraph, split across spans (keeps formatting), with placeholders.
    • Operators (and, in, contains, not, is empty, grouping, exists), conditions containing text:s, malformed condition → warning.
    • Table-row conditionals: else, multi-column rows, cell-level blocks and inline conditionals, all rows removed → table removed, header-row group removed, two markers in one row → failure.
    • List-item conditionals: an emptied list is removed; single-item-list markers; single-item list with a complete inline conditional.
    • Sections, headers/footers, text boxes and footnotes.
    • Unmatched {{#if}} and {{#elseif}} after {{#else}} → failure; marker paragraphs removed with their other text, as in Word.
  • LibreOffice round trip (Conditionals_RoundTripThroughLibreOffice): block, inline with span, table row and list items. All 4 LibreOffice tests passed locally on net8.0, net9.0 and net10.0 (LibreOffice 25, macOS).
  • Full suite green: 1968 tests on each of net8, net9 and net10; Tools and Converter tests green.
  • Checks run: CI-mode Release build, dotnet format --verify-no-changes.

Public API impact: additive

No new public symbols. OdtTemplateProcessor (unreleased, added in #210) now evaluates conditionals, and its XML docs describe this. PublicAPI.Shipped.txt and PublicAPI.Unshipped.txt are unchanged.

@codecov-commenter

Copy link
Copy Markdown

@vaceslav
vaceslav merged commit eaf4673 into main Sep 26, 2026
12 checks passed
@vaceslav
vaceslav deleted the feat/138-odt-conditionals branch September 26, 2026 12: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