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
- Style-based formatting. Markdown (
**bold**) and formatting preservation require creating/reusing automatic styles instead of setting run properties.
- 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.
- Tables. Merged cells (
table:covered-table-cell), repeated rows/columns (table:number-rows-repeated) when cloning rows in loops.
- Lists and numbering (
text:list / text:list-item) when loops/conditionals remove or clone list items.
- 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.
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:
Customers in this space need to author and maintain templates in LibreOffice, not just receive
.odtoutput. 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 referenceDocumentFormat.OpenXml. Reusable as-is:Conditionals/Engine/*(lexer, parser, operator registry, operators),ConditionalEvaluator, publicConditionEvaluatorPlaceholders/*(finder, resolver,ValueConverter),PropertyPaths/*,Formatting/*Markdown/MarkdownParser,Loops/LoopContext,LoopEvaluationContext,GlobalEvaluationContextProcessingResult, warnings,PlaceholderReplacementOptionsWhat 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
w:p/w:r/w:ttext:p/text:h,text:span, text nodesw:rPr(bold, color…)text:style-name→office:automatic-stylesw:tbl/w:tr/w:tctable:table/table:table-row/table:table-cell(+table:covered-table-cell)style:master-pageinstyles.xmlw:tab,w:brtext:tab,text:line-break,text:s(repeated spaces)meta.xmlAn
.odtis a ZIP withcontent.xml,styles.xml,meta.xml,META-INF/manifest.xml, and amimetypeentry that must be stored first and uncompressed. Implementation can useSystem.IO.Compression+System.Xml.Linq— no new dependency.Main technical challenges
**bold**) and formatting preservation require creating/reusing automatic styles instead of setting run properties.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.table:covered-table-cell), repeated rows/columns (table:number-rows-repeated) when cloning rows in loops.text:list/text:list-item) when loops/conditionals remove or clone list items..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) andPlaceholderVisitor, and several related bugs (textboxes, hyperlinks/fields lost by inline conditionals, table-row double walk). Extract an internal document abstraction:The existing DOCX behavior becomes the first implementation; the current ~1190 tests are the compatibility oracle.
Phase 1 — ODT MVP.
OdtDocumentAdaptersupporting 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,
ValidateTemplatefor ODT, document properties (meta.xml),UpdateFieldsOnOpenequivalent (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:
DocumentTemplateProcessorsignatures and DOCX behavior stay unchanged.OdtTemplateProcessorwith the same method shapes, orTemplateFormatoption (default: DOCX, current behavior).internaluntil the design is proven; nothing new becomes public accidentally.Open questions
OdtTemplateProcessorvs. auto-detection by content (mimetypeentry)?.fodtand.ott(ODF templates) as input?.odtprogrammatically (likeDocumentBuilder) and additionally keep a few real LibreOffice-authored files.Out of scope
.ods) and presentations (.odp).Rough estimate
Most effort is the abstraction and tests, not ODF parsing itself.