Skip to content

Commit 4c6bc3c

Browse files
committed
Added enhanced xpath capability in selector
1 parent 6b4c731 commit 4c6bc3c

File tree

4 files changed

+126
-0
lines changed

4 files changed

+126
-0
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,24 @@ var contentNode = document.Body.SelectSingleNode("//div[@id='content']");
2121

2222
Besides `SelectSingleNode` we can also use `SelectNodes`. Both are extension methods defined in the `AngleSharp.XPath` namespace.
2323

24+
If wanted we can also use XPath directly in CSS selectors such as in `QuerySelector` or `QuerySelectorAll` calls. For this we only need to apply the following configuration:
25+
26+
```cs
27+
var config = Configuration.Default.WithXPath();
28+
```
29+
30+
Now we can write queries such as
31+
32+
```cs
33+
var secondLi = document.QuerySelector("*[xpath>'//li[2]']");
34+
```
35+
36+
It is important that the original selector has all elements (`*`) as the intersection of the ordinary CSS selector and the XPath attribute is considered. The XPath attribute consists of a head (`xpath>`) and a value - provided as a string, e.g., `//li[2]`.
37+
2438
## Features
2539

2640
- Uses `XPathNavigator` from `System.Xml.XPath`
41+
- Includes XPath capabilities to CSS query selectors if wanted
2742

2843
## Participating
2944

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
namespace AngleSharp.XPath.Tests
2+
{
3+
using NUnit.Framework;
4+
using System.Threading.Tasks;
5+
6+
[TestFixture]
7+
public class XPathConfigurationTests
8+
{
9+
[Test]
10+
public async Task RunXPathQueryFromDocumentWithSelectAll()
11+
{
12+
var source = @"<body><ul><li><li><li></ul>";
13+
var config = Configuration.Default.WithXPath();
14+
var context = BrowsingContext.New(config);
15+
var document = await context.OpenAsync(res => res.Content(source));
16+
var elements = document.QuerySelectorAll("*[xpath>'//li']");
17+
Assert.AreEqual(3, elements.Length);
18+
}
19+
20+
[Test]
21+
public async Task RunXPathQueryFromDocumentWithSelectSingle()
22+
{
23+
var source = @"<body><ul><li><li><li></ul>";
24+
var config = Configuration.Default.WithXPath();
25+
var context = BrowsingContext.New(config);
26+
var document = await context.OpenAsync(res => res.Content(source));
27+
var element = document.QuerySelector("*[xpath>'//li']");
28+
Assert.IsNotNull(element);
29+
Assert.AreEqual("LI", element.TagName);
30+
}
31+
32+
[Test]
33+
public async Task RunXPathQueryFromDocumentWithSelectSingleSpecialN()
34+
{
35+
var source = @"<body><ul><li><li class=two><li></ul>";
36+
var config = Configuration.Default.WithXPath();
37+
var context = BrowsingContext.New(config);
38+
var document = await context.OpenAsync(res => res.Content(source));
39+
var element = document.QuerySelector("*[xpath>'//li[2]']");
40+
Assert.IsNotNull(element);
41+
Assert.AreEqual("LI", element.TagName);
42+
Assert.AreEqual("two", element.ClassName);
43+
}
44+
}
45+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
namespace AngleSharp.XPath
2+
{
3+
using AngleSharp.Css;
4+
using AngleSharp.Css.Dom;
5+
using AngleSharp.Dom;
6+
using System;
7+
using System.Collections.Generic;
8+
9+
sealed class XPathAttrSelector : ISelector
10+
{
11+
private readonly String _value;
12+
private IElement _scope;
13+
private List<INode> _result;
14+
15+
public XPathAttrSelector(String value) => _value = value;
16+
17+
public Priority Specificity => Priority.OneClass;
18+
19+
public String Text => $"[xpath>'${_value}']";
20+
21+
public void Accept(ISelectorVisitor visitor)
22+
{
23+
}
24+
25+
public Boolean Match(IElement element, IElement scope)
26+
{
27+
if (_scope != scope)
28+
{
29+
_scope = scope;
30+
_result = scope.SelectNodes(_value);
31+
}
32+
33+
return _result.Contains(element);
34+
}
35+
}
36+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace AngleSharp
2+
{
3+
using AngleSharp.Css;
4+
using AngleSharp.XPath;
5+
using System.Linq;
6+
7+
/// <summary>
8+
/// Additional extensions for integrating XPath.
9+
/// </summary>
10+
public static class XPathConfigurationExtensions
11+
{
12+
/// <summary>
13+
/// Adds XPath to standard queries.
14+
/// </summary>
15+
/// <param name="configuration">The configuration to use.</param>
16+
/// <returns>The new configuration.</returns>
17+
public static IConfiguration WithXPath(this IConfiguration configuration)
18+
{
19+
var selectorFactory = configuration.Services.OfType<DefaultAttributeSelectorFactory>().FirstOrDefault();
20+
21+
if (selectorFactory != null)
22+
{
23+
selectorFactory.Unregister(">");
24+
selectorFactory.Register(">", (name, value, prefix, mode) => new XPathAttrSelector(value));
25+
}
26+
27+
return configuration;
28+
}
29+
}
30+
}

0 commit comments

Comments
 (0)