Skip to content

feat: OpenDocument Text (.odt) template support for LibreOffice / public-sector use #138

Description

@vaceslav

Summary

Add native support for OpenDocument Text (.odt) templates — the format used by LibreOffice, Collabora and OpenOffice — with the same template syntax Templify supports for .docx (placeholders, format specifiers, conditionals incl. the 1.7.0 operator engine, loops, table-row loops, headers/footers, markdown).

Motivation

Public administrations in the EU, and especially in Germany, are moving away from Microsoft Office:

  • Schleswig-Holstein is migrating its administration to LibreOffice.
  • openDesk (the sovereign workplace suite for the German public sector) ships Collabora Online, i.e. the LibreOffice engine.
  • ODF is the designated open document standard in these initiatives.

Customers in this space need to author and maintain templates in LibreOffice, not just receive .odt output. There is no mature free .NET library for ODF templating, so this would be a real differentiator.

Why this is feasible

The core is already largely format-agnostic. Of ~52 source files in TriasDev.Templify/, only 16 reference DocumentFormat.OpenXml. Reusable as-is:

  • Conditionals/Engine/* (lexer, parser, operator registry, operators), ConditionalEvaluator, public ConditionEvaluator
  • Placeholders/* (finder, resolver, ValueConverter), PropertyPaths/*, Formatting/*
  • Markdown/MarkdownParser, Loops/LoopContext, LoopEvaluationContext, GlobalEvaluationContext
  • ProcessingResult, warnings, PlaceholderReplacementOptions

What is OpenXML-specific and needs an abstraction or a second implementation: DocumentWalker, the visitors (PlaceholderVisitor, ConditionalVisitor, LoopVisitor), ConditionalDetector, LoopDetector, FormattingPreserver, DocumentTemplateProcessor (package handling, fields, document properties).

ODF vs. OOXML mapping

DOCX (OOXML) ODT (ODF)
w:p / w:r / w:t text:p / text:h, text:span, text nodes
inline w:rPr (bold, color…) text:style-name → office:automatic-styles
w:tbl / w:tr / w:tc table:table / table:table-row / table:table-cell (+ table:covered-table-cell)
header/footer parts style:master-page in styles.xml
w:tab, w:br text:tab, text:line-break, text:s (repeated spaces)
core properties meta.xml

An .odt is a ZIP with content.xml, styles.xml, meta.xml, META-INF/manifest.xml, and a mimetype entry that must be stored first and uncompressed. Implementation can use System.IO.Compression + System.Xml.Linq — no new dependency.

Main technical challenges

  1. Style-based formatting. Markdown (**bold**) and formatting preservation require creating/reusing automatic styles instead of setting run properties.
  2. Split text. A placeholder can be split across text:spans and special elements (text:s, text:tab, text:soft-page-break, bookmarks, change tracking), the same class of problem as split runs in DOCX.
  3. Tables. Merged cells (table:covered-table-cell), repeated rows/columns (table:number-rows-repeated) when cloning rows in loops.
  4. Lists and numbering (text:list / text:list-item) when loops/conditionals remove or clone list items.
  5. Flat ODT (.fodt) — single-file XML variant; decide whether to support it.

Proposed approach

Phase 0 — prerequisite refactoring (useful even without ODF). The audit of 2026-09-25 found that paragraph run-rebuild logic is duplicated between ConditionalVisitor (~490 lines) and PlaceholderVisitor, and several related bugs (textboxes, hyperlinks/fields lost by inline conditionals, table-row double walk). Extract an internal document abstraction:

  • a paragraph text rewriter (read text segments with formatting → replace ranges → write back), and
  • a document tree adapter (enumerate blocks / tables / rows / cells / header-footer containers; clone and remove).

The existing DOCX behavior becomes the first implementation; the current ~1190 tests are the compatibility oracle.

Phase 1 — ODT MVP. OdtDocumentAdapter supporting placeholders (incl. format specifiers), block + inline conditionals, loops (paragraph and table-row), headers/footers, ProcessingResult/warnings.

Phase 2 — parity. Markdown via automatic styles, newline handling, lists, ValidateTemplate for ODT, document properties (meta.xml), UpdateFieldsOnOpen equivalent (if meaningful), Converter/GUI support, docs and examples.

Public API (hard requirement: no breaking changes)

The library already has consumers. This must be purely additive and ship in a minor release:

  • Existing DocumentTemplateProcessor signatures and DOCX behavior stay unchanged.
  • New API, one of (to be decided in design):
    • a separate OdtTemplateProcessor with the same method shapes, or
    • a format-detecting entry point / TemplateFormat option (default: DOCX, current behavior).
  • New internal abstractions stay internal until the design is proven; nothing new becomes public accidentally.

Open questions

  • Separate OdtTemplateProcessor vs. auto-detection by content (mimetype entry)?
  • Support .fodt and .ott (ODF templates) as input?
  • Output format: always same as input, or allow DOCX→ODT? (Cross-format conversion is out of scope; could be documented via LibreOffice headless.)
  • Test fixtures: build .odt programmatically (like DocumentBuilder) and additionally keep a few real LibreOffice-authored files.

Out of scope

  • Spreadsheets (.ods) and presentations (.odp).
  • Format conversion DOCX ↔ ODT.

Rough estimate

  • Phase 0 refactoring: ~1–2 weeks.
  • Phase 1 MVP: ~2 weeks.
  • Phase 2 parity: ~2–3 weeks.

Most effort is the abstraction and tests, not ODF parsing itself.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    architectureCode structure and organizationenhancementNew feature or requestfeatureNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions