From b6bbc91176bf384bb8173b13ea8f98cd5be681cd Mon Sep 17 00:00:00 2001
From: Daniel Cazzulino
Date: Tue, 31 Aug 2021 20:32:42 -0300
Subject: [PATCH] Add :only-child pseudo-class selector
See https://www.w3.org/TR/selectors-3/#only-child-pseudo
Fixes #7
---
readme.md | 5 +++++
src/Css/Css.cs | 7 +++++++
src/Css/Parser.cs | 2 ++
src/Tests/XPathTests.cs | 1 +
src/Tests/page.html | 5 ++++-
5 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/readme.md b/readme.md
index 7ab9a27..92ede23 100644
--- a/readme.md
+++ b/readme.md
@@ -26,6 +26,11 @@ At the moment, supports the following CSS selector features:
- [Attribute selector](https://www.w3.org/TR/selectors-3/#attribute-selectors)
- [Class selector](https://www.w3.org/TR/selectors-3/#class-html)
- [ID selector](https://www.w3.org/TR/selectors-3/#id-selectors)
+- [Pseudo-classes](https://www.w3.org/TR/selectors-3/#pseudo-classes):
+ * [:checked](https://www.w3.org/TR/selectors-3/#checked)
+ * [:first-child](https://www.w3.org/TR/selectors-3/#first-child-pseudo)
+ * [:last-child](https://www.w3.org/TR/selectors-3/#last-child-pseudo)
+ * [:only-child](https://www.w3.org/TR/selectors-3/#only-child-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 82cd1ab..008e573 100644
--- a/src/Css/Css.cs
+++ b/src/Css/Css.cs
@@ -170,6 +170,13 @@ record LastChildSelector : SimpleSelector
public override void Append(StringBuilder builder) => builder.Append("[not(following-sibling::*)]");
}
+record OnlyChildSelector : SimpleSelector
+{
+ public static SimpleSelector Default { get; } = new OnlyChildSelector();
+ OnlyChildSelector() { }
+ public override void Append(StringBuilder builder) => builder.Append("[not(preceding-sibling::*) and not(following-sibling::*)]");
+}
+
enum Combinator
{
None,
diff --git a/src/Css/Parser.cs b/src/Css/Parser.cs
index 511d558..fa64a81 100644
--- a/src/Css/Parser.cs
+++ b/src/Css/Parser.cs
@@ -81,11 +81,13 @@ class Parser
from identifier in Span.EqualTo("checked")
.Or(Span.EqualTo("first-child"))
.Or(Span.EqualTo("last-child"))
+ .Or(Span.EqualTo("only-child"))
select identifier.ToStringValue() switch
{
"checked" => CheckedSelector.Default,
"first-child" => FirstChildSelector.Default,
"last-child" => LastChildSelector.Default,
+ "only-child" => OnlyChildSelector.Default,
_ => throw new NotSupportedException()
})
.Named("checked pseudo selector");
diff --git a/src/Tests/XPathTests.cs b/src/Tests/XPathTests.cs
index d73223f..d6c0a2a 100644
--- a/src/Tests/XPathTests.cs
+++ b/src/Tests/XPathTests.cs
@@ -44,6 +44,7 @@ public void ToXPath(string css, string xpath)
[InlineData("option:checked", "second")]
[InlineData("div:first-child", "Warning Hello")]
[InlineData("div:last-child", "Footer")]
+ [InlineData("h1:only-child", "Sub-header")]
[Theory]
public void EvaluatePageHtml(string expression, string value)
=> Assert.Equal(value, string.Join(' ', XDocument.Load("page.html")
diff --git a/src/Tests/page.html b/src/Tests/page.html
index a541581..356fbe1 100644
--- a/src/Tests/page.html
+++ b/src/Tests/page.html
@@ -4,7 +4,10 @@
Warning
- para
+ Header
+
+
Sub-header
+