From 6d2b0b952da38aafd26d2b9aa5ad0bcaff641fd1 Mon Sep 17 00:00:00 2001 From: Jo-Be-Co Date: Tue, 24 Mar 2026 00:18:40 +0100 Subject: [PATCH] - Inspect exception on regexp timeout - special tests for checks with whitespace - Escaping allows whitespace at the edges --- .../ConditionalTagCollection[TClass].cs | 29 ++++++++++--------- .../ConditionalTagCollectionTests.cs | 13 ++++++--- .../TemplatesTests.cs | 10 +++++++ 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/Source/FileManager/NamingTemplate/ConditionalTagCollection[TClass].cs b/Source/FileManager/NamingTemplate/ConditionalTagCollection[TClass].cs index 39225698..56884c35 100644 --- a/Source/FileManager/NamingTemplate/ConditionalTagCollection[TClass].cs +++ b/Source/FileManager/NamingTemplate/ConditionalTagCollection[TClass].cs @@ -166,11 +166,11 @@ public partial class ConditionalTagCollection(bool caseSensitive = true) var match = CheckRegex().Match(checkString); - var valStr = match.Groups["val"].Value; + var valStr = Unescape(match.Groups["val"]) ?? ""; var iVal = -1; var isNumericalOperator = match.Groups["num_op"].Success && int.TryParse(valStr, out iVal); - var checkItem = match.Groups["op"].ValueSpan switch + var checkItem = Unescape(match.Groups["op"]) switch { "=" or "" => (v, culture) => VComparedToStr(v, culture, valStr) == 0, "!=" or "!" => (v, culture) => VComparedToStr(v, culture, valStr) != 0, @@ -300,21 +300,24 @@ public partial class ConditionalTagCollection(bool caseSensitive = true) var getBool = CreateConditionExpression( exactName, matchData.GetValueOrDefault("property")?.Value, - Unescape(matchData.GetValueOrDefault("check"))); + matchData.GetValueOrDefault("check")?.Value); + // Unescape(matchData.GetValueOrDefault("check"))); return matchData["not"].Success ? Expression.Not(getBool) : getBool; } [GeneratedRegex(""" - (?x) # option x: ignore all unescaped whitespace in pattern and allow comments starting with # - ^\s* # anchor at start of line trimming leading whitespace - (? # capture operator in and - (?\#=|\#!=|\#?>=|\#?>|\#?<=|\#?<) # - numerical operators start with a # and might be omitted if unique - | ~|!=?|=? # - string comparison operators including ~ for regexp. No operator is like = - ) \s* # ignore space between operator and value - (?(?(num_op) # capture value in - \d+ # - numerical operators have to be followed by a number - | .*? ) # - string for comparison. May be empty. Non-greedy capture resulting in no whitespace at the end - )\s*$ # trimming up to the end + (?x) # option x: ignore all unescaped whitespace in pattern and allow comments starting with # + ^\s* # anchor at start of line trimming leading whitespace + (?(? # capture operator in and with every char escapable + \\?\#(?:\\?!)?\\?= # - numerical operators: #= #!= + | \\?\#\\?[<>](?:\\?=)? # - numerical operators: #>= #<= #> #< + | \\?[<>](?:\\?=)? # - numerical operators: >= <= > < + ) | \\?~|\\?!(?:\\?=)?|(?:\\?=)? # - string comparison operators including ~ for regexp, = and !=. No operator is like = + ) \s* # ignore space between operator and value + (?(?(num_op) # capture value in + (?:\\?\d)+ # - numerical operators have to be followed by a number + | (?:\\.|[^\\])* ) # - string for comparison. May be empty. Capturing also all whitespace up to the end as this must have been escaped. + )$ # match to the end """)] private static partial Regex CheckRegex(); } diff --git a/Source/_Tests/FileManager.Tests/ConditionalTagCollectionTests.cs b/Source/_Tests/FileManager.Tests/ConditionalTagCollectionTests.cs index 2a127fd2..eb9919af 100644 --- a/Source/_Tests/FileManager.Tests/ConditionalTagCollectionTests.cs +++ b/Source/_Tests/FileManager.Tests/ConditionalTagCollectionTests.cs @@ -53,9 +53,14 @@ public class ConditionalTagCollectionTests namingTemplate.Evaluate(testObj); Assert.Fail($"Expected InvalidOperationException for pattern: {pattern}"); } - catch (Exception ex) when (ex is InvalidOperationException or TargetInvocationException) + catch (TargetInvocationException ex) { - // Expected behavior - regex is invalid or caused timeout + // if evaluation of the template started but the regex is running into a timeout an InvalidOperationException is thrown + Assert.IsInstanceOfType(ex.InnerException); + } + catch (InvalidOperationException) + { + // Expected behavior - regex is invalid and parsing should fail } } @@ -66,7 +71,7 @@ public class ConditionalTagCollectionTests public void ConditionalTag_ValidRegexPattern_ParsesSuccessfully() { // Arrange: Valid simple regex pattern with proper closing tag - var template = "content<-testcond>"; + var template = "content<-testcond>"; // Act: Parse should succeed without throwing exceptions var namingTemplate = NamingTemplate.NamingTemplate.Parse(template, [_conditionalTags]); @@ -86,7 +91,7 @@ public class ConditionalTagCollectionTests public void ConditionalTag_ValidComplexRegexPatterns_ParseSuccessfully(string pattern) { // Arrange: Valid complex regex patterns with proper closing tags - var template = $"c<-testcond>"; + var template = $"c<-testcond>"; // Act: Parse should succeed without throwing var namingTemplate = NamingTemplate.NamingTemplate.Parse(template, [_conditionalTags]); diff --git a/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs b/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs index 8db009ba..1fbb81ac 100644 --- a/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs +++ b/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs @@ -556,6 +556,16 @@ namespace TemplatesTests [DataRow("false<-has>", "")] [DataRow("true<-has>", "true")] [DataRow("true<-has>", "true")] + [DataRow("false<-has>", "")] + [DataRow("false<-has>", "")] + [DataRow(@"true<-has>", "true")] + [DataRow("false<-has>", "")] + [DataRow("false<-has>", "")] + [DataRow(@"true<-has>", "true")] + [DataRow(@"false<-has>", "")] + [DataRow("false<-has>", "")] + [DataRow("false<-has>", "")] + [DataRow(@"true<-has>", "true")] public void HasValue_test(string template, string expected) { var bookDto = GetLibraryBook();