Skip to content

Commit

Permalink
Add support for simplified syntax for nth-child
Browse files Browse the repository at this point in the history
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
  • Loading branch information
kzu committed Jan 21, 2022
1 parent 7e0b155 commit a885623
Show file tree
Hide file tree
Showing 5 changed files with 17 additions and 1 deletion.
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions src/Css/Css.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/Css/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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");
Expand Down
7 changes: 6 additions & 1 deletion src/Tests/CssParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
2 changes: 2 additions & 0 deletions src/Tests/XPathTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down

0 comments on commit a885623

Please sign in to comment.