From a88562330b7c5c2692bc906dce3c577913296ef1 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Fri, 21 Jan 2022 16:52:32 -0300 Subject: [PATCH] Add support for simplified syntax for nth-child Only supports a single value, not the full `an+b` parameter syntax. from https://www.w3.org/TR/selectors-3/#nth-child-pseudo. Fixes #38 --- readme.md | 1 + src/Css/Css.cs | 6 ++++++ src/Css/Parser.cs | 2 ++ src/Tests/CssParserTests.cs | 7 ++++++- src/Tests/XPathTests.cs | 2 ++ 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index ff755ba..a50db2e 100644 --- a/readme.md +++ b/readme.md @@ -35,6 +35,7 @@ At the moment, supports the following CSS selector features: * [:last-of-type](https://www.w3.org/TR/selectors-3/#last-of-type-pseudo) * [:not(...)](https://www.w3.org/TR/selectors-3/#negation) * [:nth-of-type(n)](https://www.w3.org/TR/selectors-3/#nth-of-type-pseudo) + * [:nth-child(n)](https://www.w3.org/TR/selectors-3/#nth-child-pseudo): simplified syntax with just a position only * [:has(...)](https://www.w3.org/TR/selectors-4/#has-pseudo) And all [combinators](https://www.w3.org/TR/selectors-3/#combinators) diff --git a/src/Css/Css.cs b/src/Css/Css.cs index 1952fd4..b270d4b 100644 --- a/src/Css/Css.cs +++ b/src/Css/Css.cs @@ -301,6 +301,12 @@ public override void Append(StringBuilder builder) => builder.Append("[").Append(Position).Append("]"); } +record NthChildSelector(string Position) : SimpleSelector +{ + public override void Append(StringBuilder builder) + => builder.Append("[(count(preceding-sibling::*)+1)=").Append(Position).Append("]"); +} + enum Combinator { None, diff --git a/src/Css/Parser.cs b/src/Css/Parser.cs index 63384dd..3d8bfd1 100644 --- a/src/Css/Parser.cs +++ b/src/Css/Parser.cs @@ -104,6 +104,7 @@ class Parser .Or(Span.EqualTo("last-of-type").Try()) .Or(Span.EqualTo("not").Try()) .Or(Span.EqualTo("nth-of-type").Try()) + .Or(Span.EqualTo("nth-child").Try()) .Or(Span.EqualTo("has")) from argument in PseudoArgumentSelector.OptionalOrDefault() select identifier.ToStringValue() switch @@ -118,6 +119,7 @@ class Parser "not" => new NegationSelector(argument), "nth-of-type" => (SimpleSelector)argument, "has" => new HasSelector(argument), + string id when id == "nth-child" && argument is PositionSelector position => new NthChildSelector(position.Position), _ => throw new NotSupportedException() }) .Named("pseudo selector"); diff --git a/src/Tests/CssParserTests.cs b/src/Tests/CssParserTests.cs index b63334f..4af2e40 100644 --- a/src/Tests/CssParserTests.cs +++ b/src/Tests/CssParserTests.cs @@ -164,10 +164,15 @@ public void CanParseSelector1() [Fact] public void CanParseSelector2() { - var selector = Parser.Selector.Parse("foo > bar"); + var selector = Parser.Selector.Parse("foo:nth-child(2)"); + var xpath = selector.ToXPath(); } [InlineData("foo\\+bar", "foo+bar")] + [InlineData("foo_bar", "foo_bar")] + [InlineData("foo__bar", "foo__bar")] + [InlineData("_foo-bar", "_foo-bar")] + [InlineData("-foo_bar", "-foo_bar")] [Theory] public void ParseIdentifier(string expression, string identifier) => Assert.Equal(identifier, Parser.Identifier.Parse(expression)); diff --git a/src/Tests/XPathTests.cs b/src/Tests/XPathTests.cs index 1c6a4c1..322bb57 100644 --- a/src/Tests/XPathTests.cs +++ b/src/Tests/XPathTests.cs @@ -65,6 +65,8 @@ public void ToXPath(string css, string xpath) [InlineData("span:not([text()='4']),div[role]", "Warning 1 2 5 Standard File Archivo Edit Footer")] [InlineData(".item__hiden-content", "Archivo")] [InlineData("body > div > span", "1 2 4")] + [InlineData("body > div:nth-of-type(3)", "Standard")] + [InlineData("body > span:nth-child(5)", "5")] [Theory] public void EvaluatePageHtml(string expression, string expected) {