Skip to content

fix(conditionals): keep table cells valid when a branch is removed (#117)#118

Merged
vaceslav merged 2 commits into
mainfrom
fix/117-conditional-empty-table-cell
Jul 3, 2026
Merged

fix(conditionals): keep table cells valid when a branch is removed (#117)#118
vaceslav merged 2 commits into
mainfrom
fix/117-conditional-empty-table-cell

Conversation

@vaceslav

@vaceslav vaceslav commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #117 and removes the dead legacy processing classes that caused the issue to be diagnosed against the wrong file.

1. Fix: conditional removal no longer leaves empty <w:tc> (#117)

When a conditional block ({{#if}} … {{/if}}) made up the entire content of a table cell and the branch was removed, ConditionalVisitor deleted the cell's only paragraphs and left an empty <w:tc>. That is invalid OOXML — ECMA‑376 §17.4.66 requires a cell to contain at least one block‑level element and to end with a paragraph.

Word silently repairs such files on open, so the defect is invisible there. LibreOffice / soffice --headless (e.g. Stirling PDF) do not repair it and collapse the whole table on render/PDF export.

Fix (Visitors/ConditionalVisitor.csProcessBranches):

  • capture ancestor table cells before mutating the tree,
  • after removing branches, append an empty paragraph to any cell left without a trailing paragraph,
  • cells removed together with their row (table‑row conditionals) are skipped via a Parent is null check.

Purely additive for non‑table conditionals.

Note: the issue proposed fixing Conditionals/ConditionalProcessor.cs, but that class is legacy and not used at runtime — the live path is ConditionalVisitor. See change 2.

2. Refactor: remove unused legacy processing classes

Deleted the pre‑Phase 2 processing path, kept only "for reference" and instantiated nowhere:

  • Conditionals/ConditionalProcessor.cs
  • Loops/LoopProcessor.cs
  • Placeholders/DocumentBodyReplacer.cs
  • Placeholders/TableReplacer.cs

Their responsibilities live in the visitor pattern (ConditionalVisitor, LoopVisitor, PlaceholderVisitor). Also pruned stale doc‑comments, the file list in CLAUDE.md, and added a removal note in ARCHITECTURE.md. Historical journals (REFACTORING.md, TODO.md) are left untouched as records of past work; git history remains the reference for the old design.

Tests

New TDD regression suite Integration/ConditionalTableCellTests.cs:

  • conditional as entire cell content, condition false → cell keeps exactly one paragraph;
  • condition true → content preserved;
  • nested conditionals (the issue's matrix‑cell reproduction);
  • 2×2 matrix where only the matching cell keeps X, all cells stay valid.

Helpers extended: DocumentBuilder.AddTableWithCellParagraphs, DocumentVerifier.DoesTableCellEndWithParagraph / GetTableCellParagraphCount.

Verification

  • dotnet build — 0 errors
  • dotnet test1130/1130 passing
  • dotnet format --verify-no-changes — clean

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an OOXML validity bug where removing a conditional branch could leave a table cell (<w:tc>) without a trailing paragraph, which breaks rendering in LibreOffice/headless export scenarios (issue #117). It also removes unused legacy “pre-Phase 2” processors in favor of the visitor-based runtime path and adds regression coverage to prevent recurrence.

Changes:

  • Ensure table cells impacted by conditional branch removals still end with a <w:p> by post-processing affected cells in ConditionalVisitor.
  • Remove dead legacy processing classes (ConditionalProcessor, LoopProcessor, DocumentBodyReplacer, TableReplacer) and prune related documentation references.
  • Add integration tests plus helper extensions to validate table-cell paragraph invariants after conditional processing.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.

Show a summary per file
File Description
TriasDev.Templify/Visitors/TemplateElementHelper.cs Updates remarks to remove legacy-processor references and align docs with visitor usage.
TriasDev.Templify/Visitors/PlaceholderVisitor.cs Prunes outdated remark about legacy replacer behavior.
TriasDev.Templify/Visitors/LoopVisitor.cs Removes outdated remark tying visitor to the deleted legacy processor.
TriasDev.Templify/Visitors/ConditionalVisitor.cs Captures ancestor table cells before mutation and ensures impacted cells end with a paragraph after branch removals.
TriasDev.Templify/Placeholders/TableReplacer.cs Deletes unused legacy table placeholder replacer.
TriasDev.Templify/Placeholders/DocumentBodyReplacer.cs Deletes unused legacy body placeholder replacer.
TriasDev.Templify/Loops/LoopProcessor.cs Deletes unused legacy loop processor.
TriasDev.Templify/Conditionals/ConditionalProcessor.cs Deletes unused legacy conditional processor (not used in visitor-based runtime).
TriasDev.Templify/ARCHITECTURE.md Documents removal of the legacy processing path while retaining historical sections.
TriasDev.Templify.Tests/Integration/ConditionalTableCellTests.cs Adds regression tests covering conditional-as-entire-cell scenarios (including nested/matrix cases).
TriasDev.Templify.Tests/Helpers/DocumentVerifier.cs Adds helpers to verify table cell paragraph count and trailing-paragraph invariant.
TriasDev.Templify.Tests/Helpers/DocumentBuilder.cs Adds a builder helper to create tables with multi-paragraph cells for block-conditional scenarios.
CLAUDE.md Removes references to deleted legacy classes from the file list.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

vaceslav added 2 commits July 3, 2026 14:44
)

When a conditional block was the entire content of a table cell and the
branch was removed, ConditionalVisitor deleted the cell's only paragraphs
and left an empty <w:tc>. That violates ECMA-376 §17.4.66 (a cell must
contain at least one block-level element and end with a paragraph).

Word silently repairs such files, but LibreOffice/soffice do not and
collapse the whole table on render/PDF export.

Fix: in ProcessBranches, capture ancestor table cells before mutating the
tree, then append an empty paragraph to any cell left without a trailing
paragraph. Cells removed with their row (table-row conditionals) are
skipped via a Parent null-check. Purely additive for non-table conditionals.

Note: the issue proposed fixing Conditionals/ConditionalProcessor.cs, but
that class is legacy/unused at runtime; the live path is ConditionalVisitor.

Fixes #117
Delete the pre-Phase 2 processing path that was kept only 'for reference'
and instantiated nowhere at runtime:
- Conditionals/ConditionalProcessor.cs
- Loops/LoopProcessor.cs
- Placeholders/DocumentBodyReplacer.cs
- Placeholders/TableReplacer.cs

Their responsibilities live in the visitor pattern (ConditionalVisitor,
LoopVisitor, PlaceholderVisitor). Keeping the dead code around is what led
issue #117 to propose a fix in the wrong file.

Also update stale doc-comments referencing these classes, prune the file
list in CLAUDE.md, and add a removal note to ARCHITECTURE.md. Git history
remains the reference for the old design. The historical journals
(REFACTORING.md, TODO.md) are left untouched as records of past work.
@vaceslav vaceslav force-pushed the fix/117-conditional-empty-table-cell branch from f71f82e to f59fb12 Compare July 3, 2026 12:45
@vaceslav vaceslav merged commit fbd159a into main Jul 3, 2026
14 checks passed
@vaceslav vaceslav deleted the fix/117-conditional-empty-table-cell branch July 3, 2026 12:48
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.

BUG: Conditional removal leaves empty <w:tc> (invalid OOXML) when a block is a table cell's entire content

2 participants