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
41 changes: 40 additions & 1 deletion TriasDev.Templify.Tests/Odt/OdtValidationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,46 @@ public void TableRowLoop_UnmatchedInsideCell_IsStillReported()

ValidationResult result = Validate(template);

Assert.Contains(result.Errors, e => e.Message.Contains("'{{#if A}}'", StringComparison.Ordinal));
// Reported once, with the row-level message that processing fails with (as for Word templates).
ValidationError error = Assert.Single(result.Errors);
Assert.Equal(ValidationErrorType.UnmatchedConditionalStart, error.Type);
Assert.Equal("Table row conditional start marker '{{#if A}}' has no matching '{{/if}}'.", error.Message);
}

[Fact]
public void UnmatchedListItemMarkers_AreReportedOnce_WithTheMessageProcessingFailsWith()
{
OdtDocumentBuilder template = new OdtDocumentBuilder()
.AddXml("<text:list><text:list-item><text:p>{{#if A}}</text:p></text:list-item><text:list-item><text:p>b</text:p></text:list-item></text:list>")
.AddXml("<text:list><text:list-item><text:p>{{#foreach Items}}</text:p></text:list-item><text:list-item><text:p>b</text:p></text:list-item></text:list>");
byte[] bytes = template.ToBytes();

ValidationResult result = new OdtTemplateProcessor().ValidateTemplate(new MemoryStream(bytes));
ProcessingResult processing = new OdtTemplateProcessor().ProcessTemplate(
bytes,
new Dictionary<string, object> { ["A"] = true, ["Items"] = new List<int> { 1 } },
out _);

Assert.Equal(
new[]
{
"UnmatchedConditionalStart: List item conditional start marker '{{#if A}}' has no matching '{{/if}}'.",
"UnmatchedLoopStart: List item loop start marker '{{#foreach Items}}' has no matching '{{/foreach}}'.",
},
result.Errors.Select(e => $"{e.Type}: {e.Message}").Order(StringComparer.Ordinal));
Assert.False(processing.IsSuccess);
Assert.Contains(processing.ErrorMessage!, result.Errors.Select(e => "Processing failed: " + e.Message));
}

[Fact]
public void SameUnmatchedMarker_InTwoParagraphs_IsReportedOnce()
{
ValidationResult result = Validate(new OdtDocumentBuilder()
.AddTable(new[] { "{{#if A}} x" }, new[] { "y" })
.AddParagraph("{{#if A}}"));

ValidationError error = Assert.Single(result.Errors);
Assert.Equal("Conditional start marker '{{#if A}}' has no matching '{{/if}}'.", error.Message);
}

[Fact]
Expand Down
10 changes: 9 additions & 1 deletion TriasDev.Templify/OpenDocument/OdtTemplateValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,9 @@ private sealed class SyntaxWalker
{
private readonly HashSet<string> _allPlaceholders;
private readonly List<ValidationError> _errors;
/// <summary>The marker quoted in an unmatched-marker message, e.g. <c>'{{#if A}}'</c>.</summary>
private static readonly Regex _quotedMarker = new Regex(@"start marker '(\{\{#[^']*\}\})'", RegexOptions.CultureInvariant);

private readonly HashSet<string> _reported = new HashSet<string>(StringComparer.Ordinal);

public SyntaxWalker(HashSet<string> allPlaceholders, List<ValidationError> errors)
Expand Down Expand Up @@ -339,7 +342,12 @@ private IReadOnlyList<T> Detect<T>(Func<IReadOnlyList<T>> detect)
}
catch (TemplateSyntaxException ex)
{
if (_reported.Add(ex.Message))
// A marker that fails at row or list-item level is found again inside its cell or item, with the
// cell-level wording ("Conditional start marker '{{#if A}}' ..."); report each marker once, with
// the message of the outermost level, which is the one processing fails with.
Match marker = _quotedMarker.Match(ex.Message);
string key = marker.Success ? $"{ex.ErrorType}:{marker.Groups[1].Value}" : ex.Message;
if (_reported.Add(key))
{
_errors.Add(ValidationError.Create(ex.ErrorType, ex.Message));
}
Expand Down
Loading