- Inspect exception on regexp timeout

- special tests for checks with whitespace
- Escaping allows whitespace at the edges
This commit is contained in:
Jo-Be-Co
2026-03-24 00:18:40 +01:00
parent fb277cff81
commit 6d2b0b952d
3 changed files with 35 additions and 17 deletions

View File

@@ -166,11 +166,11 @@ public partial class ConditionalTagCollection<TClass>(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<TClass>(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
(?<op> # capture operator in <op> and <num_op>
(?<num_op>\#=|\#!=|\#?>=|\#?>|\#?<=|\#?<) # - 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
(?<val>(?(num_op) # capture value in <val>
\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
(?<op>(?<num_op> # capture operator in <op> and <num_op> 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
(?<val>(?(num_op) # capture value in <val>
(?:\\?\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();
}

View File

@@ -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<InvalidOperationException>(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 = "<testcond [~test.*]->content<-testcond>";
var template = "<testcond foobar[~test.*]->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 = $"<testcond [~{pattern}]->c<-testcond>";
var template = $"<testcond foobar[~{pattern}]->c<-testcond>";
// Act: Parse should succeed without throwing
var namingTemplate = NamingTemplate.NamingTemplate.Parse(template, [_conditionalTags]);

View File

@@ -556,6 +556,16 @@ namespace TemplatesTests
[DataRow("<!is author[!=Sherlock]->false<-has>", "")]
[DataRow("<is tag[=Tag1]->true<-has>", "true")]
[DataRow("<is tag[separator(:)slice(-2..)][=Tag2:Tag3]->true<-has>", "true")]
[DataRow("<is audible subtitle[3][=an]->false<-has>", "")]
[DataRow("<is audible subtitle[3][=an ]->false<-has>", "")]
[DataRow(@"<is audible subtitle[3][=an\ ]->true<-has>", "true")]
[DataRow("<is audible subtitle[3][= an]->false<-has>", "")]
[DataRow("<is audible subtitle[3][= an ]->false<-has>", "")]
[DataRow(@"<is audible subtitle[3][= an\ ]->true<-has>", "true")]
[DataRow(@"<is audible subtitle[3][=\ an\ ]->false<-has>", "")]
[DataRow("<is audible subtitle[3][ =an]->false<-has>", "")]
[DataRow("<is audible subtitle[3][ =an ]->false<-has>", "")]
[DataRow(@"<is audible subtitle[3][ =an\ ]->true<-has>", "true")]
public void HasValue_test(string template, string expected)
{
var bookDto = GetLibraryBook();