Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions TriasDev.Templify.Tests/Odt/OdtValidationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,45 @@ public void EmptyLoopCollection_AddsWarning_AndMissingCollectionIsError()
Assert.Equal(new[] { "Absent" }, result.MissingVariables);
}

[Fact]
public void TableRowAndListItemBlocks_AreValid()
{
// The marker rows and items hold the markers in their own paragraphs; those paragraphs are not markers of
// a loop or conditional inside the cell or item.
OdtDocumentBuilder template = new OdtDocumentBuilder()
.AddTable(new[] { "{{#foreach Rows}}", "" }, new[] { "{{Cell}}", "{{@index}}" }, new[] { "{{/foreach}}", "" })
.AddTable(new[] { "{{#if Flag}}" }, new[] { "x" }, new[] { "{{#else}}" }, new[] { "y" }, new[] { "{{/if}}" })
.AddXml("<text:list><text:list-item><text:p>{{#foreach item in Items}}</text:p></text:list-item>"
+ "<text:list-item><text:p>{{item}}</text:p></text:list-item>"
+ "<text:list-item><text:p>{{/foreach}}</text:p></text:list-item></text:list>")
.AddXml("<text:list><text:list-item><text:p>{{#if Flag}}</text:p></text:list-item>"
+ "<text:list-item><text:p>a</text:p></text:list-item>"
+ "<text:list-item><text:p>{{/if}}</text:p></text:list-item></text:list>");

ValidationResult result = Validate(template);
ValidationResult withData = Validate(template, new Dictionary<string, object>
{
["Rows"] = new List<Dictionary<string, object>> { new() { ["Cell"] = "c" } },
["Items"] = new List<string> { "a" },
["Flag"] = true,
});

Assert.True(result.IsValid, string.Join("; ", result.Errors.Select(e => e.Message)));
Assert.True(withData.IsValid, string.Join("; ", withData.Errors.Select(e => e.Message)));
Assert.Empty(withData.MissingVariables);
}

[Fact]
public void TableRowLoop_UnmatchedInsideCell_IsStillReported()
{
OdtDocumentBuilder template = new OdtDocumentBuilder()
.AddTable(new[] { "{{#foreach Rows}}" }, new[] { "{{#if A}}" }, new[] { "{{/foreach}}" });

ValidationResult result = Validate(template);

Assert.Contains(result.Errors, e => e.Message.Contains("'{{#if A}}'", StringComparison.Ordinal));
}

[Fact]
public void ReadOnlyDictionaryOverload_Works()
{
Expand Down
18 changes: 15 additions & 3 deletions TriasDev.Templify/OpenDocument/OdtTemplateValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,22 +267,29 @@ public void WalkBlocks(IReadOnlyList<XElement> blocks)
}

IReadOnlyList<OdtLoopBlock> loops;
IReadOnlyList<OdtConditionalBlock> conditionals;
if (blocks.All(OdtMarkerText.IsRow))
{
loops = Detect(() => OdtLoopDetector.DetectTableRowLoops(blocks));
Detect(() => OdtConditionalDetector.DetectTableRowConditionals(blocks));
conditionals = Detect(() => OdtConditionalDetector.DetectTableRowConditionals(blocks));
}
else if (blocks.All(IsListItem))
{
loops = Detect(() => OdtLoopDetector.DetectListItemLoops(blocks));
Detect(() => OdtConditionalDetector.DetectListItemConditionals(blocks));
conditionals = Detect(() => OdtConditionalDetector.DetectListItemConditionals(blocks));
}
else
{
loops = Detect(() => OdtLoopDetector.DetectLoops(blocks));
Detect(() => OdtConditionalDetector.DetectConditionals(blocks));
conditionals = Detect(() => OdtConditionalDetector.DetectConditionals(blocks));
}

// Marker blocks are removed by processing. A marker row or list item holds its marker in a paragraph of
// its own, which must not be detected again as an unmatched marker inside the cell or item.
HashSet<XElement> markers = new HashSet<XElement>(
loops.SelectMany(l => new[] { l.StartMarker, l.EndMarker })
.Concat(conditionals.SelectMany(c => c.Branches.Select(b => b.Marker).Append(c.EndMarker))));

foreach (OdtLoopBlock loop in loops)
{
_allPlaceholders.Add(loop.CollectionName);
Expand All @@ -293,6 +300,11 @@ public void WalkBlocks(IReadOnlyList<XElement> blocks)

foreach (XElement block in blocks)
{
if (markers.Contains(block))
{
continue;
}

foreach (List<XElement> sequence in GetChildSequences(block))
{
WalkBlocks(sequence);
Expand Down
Loading