From d64a700b4fd2fae11ee3de45f83e18c4c3984562 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 21 Oct 2021 20:08:12 -0400 Subject: [PATCH] Imrpove nth-child parsing --- .../Markup/Parsers/SelectorGrammar.cs | 56 +++++++++++++-- .../Parsers/SelectorGrammarTests.cs | 69 +++++++++++++++++++ 2 files changed, 119 insertions(+), 6 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index 56e64329b7..953a7e9a15 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -350,12 +350,28 @@ namespace Avalonia.Markup.Parsers } else { - var stepOrOffsetSpan = r.TakeWhile(c => c != ')' && c != 'n'); - if (!int.TryParse(stepOrOffsetSpan.ToString().Trim(), out var stepOrOffset)) + r.SkipWhitespace(); + + var stepOrOffset = 0; + var stepOrOffsetStr = r.TakeWhile(c => char.IsDigit(c) || c == '-' || c == '+').ToString(); + if (stepOrOffsetStr.Length == 0 + || (stepOrOffsetStr.Length == 1 + && stepOrOffsetStr[0] == '+')) + { + stepOrOffset = 1; + } + else if (stepOrOffsetStr.Length == 1 + && stepOrOffsetStr[0] == '-') + { + stepOrOffset = -1; + } + else if (!int.TryParse(stepOrOffsetStr.ToString(), out stepOrOffset)) { throw new ExpressionParseException(r.Position, "Couldn't parse nth-child step or offset value. Integer was expected."); } + r.SkipWhitespace(); + if (r.Peek == ')') { step = 0; @@ -365,13 +381,41 @@ namespace Avalonia.Markup.Parsers { step = stepOrOffset; + if (r.Peek != 'n') + { + throw new ExpressionParseException(r.Position, "Couldn't parse nth-child step value, \"xn+y\" pattern was expected."); + } + r.Skip(1); // skip 'n' - var offsetSpan = r.TakeUntil(')').TrimStart(); - if (offsetSpan.Length != 0 - && !int.TryParse(offsetSpan.ToString().Trim(), out offset)) + r.SkipWhitespace(); + + if (r.Peek != ')') { - throw new ExpressionParseException(r.Position, "Couldn't parse nth-child offset value. Integer was expected."); + int sign; + var nextChar = r.Take(); + if (nextChar == '+') + { + sign = 1; + } + else if (nextChar == '-') + { + sign = -1; + } + else + { + throw new ExpressionParseException(r.Position, "Couldn't parse nth-child sign. '+' or '-' was expected."); + } + + r.SkipWhitespace(); + + if (sign != 0 + && !int.TryParse(r.TakeUntil(')').ToString(), out offset)) + { + throw new ExpressionParseException(r.Position, "Couldn't parse nth-child offset value. Integer was expected."); + } + + offset *= sign; } } } diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs index 543d44c492..568f6deaf2 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs @@ -236,6 +236,75 @@ namespace Avalonia.Markup.UnitTests.Parsers result); } + [Theory] + [InlineData(":nth-child(xn+2)")] + [InlineData(":nth-child(2n+b)")] + [InlineData(":nth-child(2n+)")] + [InlineData(":nth-child(2na)")] + [InlineData(":nth-child(2x+1)")] + public void NthChild_Invalid_Inputs(string input) + { + Assert.Throws(() => SelectorGrammar.Parse(input)); + } + + [Theory] + [InlineData(":nth-child(+1)", 0, 1)] + [InlineData(":nth-child(1)", 0, 1)] + [InlineData(":nth-child(-1)", 0, -1)] + [InlineData(":nth-child(2n+1)", 2, 1)] + [InlineData(":nth-child(n)", 1, 0)] + [InlineData(":nth-child(+n)", 1, 0)] + [InlineData(":nth-child(-n)", -1, 0)] + [InlineData(":nth-child(-2n)", -2, 0)] + [InlineData(":nth-child(n+5)", 1, 5)] + [InlineData(":nth-child(n-5)", 1, -5)] + [InlineData(":nth-child( 2n + 1 )", 2, 1)] + [InlineData(":nth-child( 2n - 1 )", 2, -1)] + public void NthChild_Variations(string input, int step, int offset) + { + var result = SelectorGrammar.Parse(input); + + Assert.Equal( + new SelectorGrammar.ISyntax[] + { + new SelectorGrammar.NthChildSyntax() + { + Step = step, + Offset = offset + } + }, + result); + } + + [Theory] + [InlineData(":nth-last-child(+1)", 0, 1)] + [InlineData(":nth-last-child(1)", 0, 1)] + [InlineData(":nth-last-child(-1)", 0, -1)] + [InlineData(":nth-last-child(2n+1)", 2, 1)] + [InlineData(":nth-last-child(n)", 1, 0)] + [InlineData(":nth-last-child(+n)", 1, 0)] + [InlineData(":nth-last-child(-n)", -1, 0)] + [InlineData(":nth-last-child(-2n)", -2, 0)] + [InlineData(":nth-last-child(n+5)", 1, 5)] + [InlineData(":nth-last-child(n-5)", 1, -5)] + [InlineData(":nth-last-child( 2n + 1 )", 2, 1)] + [InlineData(":nth-last-child( 2n - 1 )", 2, -1)] + public void NthLastChild_Variations(string input, int step, int offset) + { + var result = SelectorGrammar.Parse(input); + + Assert.Equal( + new SelectorGrammar.ISyntax[] + { + new SelectorGrammar.NthLastChildSyntax() + { + Step = step, + Offset = offset + } + }, + result); + } + [Fact] public void OfType_NthChild() {