fix(conditionals): keep table cells valid when a branch is removed (#117)#118
Merged
Conversation
There was a problem hiding this comment.
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 inConditionalVisitor. - 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.
) 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.
f71f82e to
f59fb12
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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,ConditionalVisitordeleted 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.cs→ProcessBranches):Parent is nullcheck.Purely additive for non‑table conditionals.
2. Refactor: remove unused legacy processing classes
Deleted the pre‑Phase 2 processing path, kept only "for reference" and instantiated nowhere:
Conditionals/ConditionalProcessor.csLoops/LoopProcessor.csPlaceholders/DocumentBodyReplacer.csPlaceholders/TableReplacer.csTheir responsibilities live in the visitor pattern (
ConditionalVisitor,LoopVisitor,PlaceholderVisitor). Also pruned stale doc‑comments, the file list inCLAUDE.md, and added a removal note inARCHITECTURE.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:X, all cells stay valid.Helpers extended:
DocumentBuilder.AddTableWithCellParagraphs,DocumentVerifier.DoesTableCellEndWithParagraph/GetTableCellParagraphCount.Verification
dotnet build— 0 errorsdotnet test— 1130/1130 passingdotnet format --verify-no-changes— clean