Skip to content

Commit d2df80e

Browse files
Denis IvanovDenis Ivanov
authored andcommitted
#18 Namespace support in XPath.
1 parent 4e19dda commit d2df80e

File tree

3 files changed

+76
-16
lines changed

3 files changed

+76
-16
lines changed

src/AngleSharp.XPath.Tests/HtmlDocumentNavigatorTests.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,23 @@ public void SelectPrecedingNodeInDocumentWithDoctype_ShouldReturnNode()
6565
}
6666

6767
[Test]
68-
public void SelectNodeWithNamespace_ShouldReturnNode()
68+
public void SelectSingleNode_IgnoreNamespaces_ShouldReturnNode()
69+
{
70+
// Arrange
71+
var xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?><urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\"><url><loc>https://www.test.com/de/accounts/profile</loc><xhtml:link rel=\"alternate\" hreflang=\"fr\" href=\"https://www.test.com/fr/accounts/profile\" /><xhtml:link rel=\"alternate\" hreflang=\"en\" href=\"https://www.test.com/en/accounts/profile\" /><xhtml:link rel=\"alternate\" hreflang=\"it\" href=\"https://www.test.com/it/accounts/profile\" /><changefreq>weekly</changefreq><priority>0.4</priority></url></urlset>";
72+
var parser = new XmlParser();
73+
var doc = parser.ParseDocument(xml);
74+
75+
// Act
76+
var node = doc.DocumentElement.SelectSingleNode("/urlset/url/link");
77+
78+
// Assert
79+
Assert.IsNotNull(node);
80+
Assert.That(node.NodeName, Is.EqualTo("xhtml:link"));
81+
}
82+
83+
[Test]
84+
public void SelectSingleNode_DontIgnoreNamespaces_ShouldReturnNode()
6985
{
7086
// Arrange
7187
var xml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?><urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\"><url><loc>https://www.test.com/de/accounts/profile</loc><xhtml:link rel=\"alternate\" hreflang=\"fr\" href=\"https://www.test.com/fr/accounts/profile\" /><xhtml:link rel=\"alternate\" hreflang=\"en\" href=\"https://www.test.com/en/accounts/profile\" /><xhtml:link rel=\"alternate\" hreflang=\"it\" href=\"https://www.test.com/it/accounts/profile\" /><changefreq>weekly</changefreq><priority>0.4</priority></url></urlset>";
@@ -77,7 +93,7 @@ public void SelectNodeWithNamespace_ShouldReturnNode()
7793
namespaceManager.AddNamespace("d", "http://www.sitemaps.org/schemas/sitemap/0.9");
7894

7995
// Act
80-
var node = doc.DocumentElement.SelectSingleNode("/urlset/url/link");
96+
var node = doc.DocumentElement.SelectSingleNode("/d:urlset/d:url/xhtml:link", namespaceManager, false);
8197

8298
// Assert
8399
Assert.IsNotNull(node);

src/AngleSharp.XPath/Extensions.cs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ public static class Extensions
1616
/// Creates a new navigator for the given document.
1717
/// </summary>
1818
/// <param name="document">The document to extend.</param>
19+
/// <param name="ignoreNamespaces"></param>
1920
/// <returns>The navigator for XPath expressions.</returns>
20-
public static XPathNavigator CreateNavigator(this IDocument document)
21+
public static XPathNavigator CreateNavigator(this IDocument document, bool ignoreNamespaces = true)
2122
{
2223
var doc = document ?? throw new ArgumentNullException(nameof(document));
23-
return new HtmlDocumentNavigator(doc, doc.DocumentElement);
24+
return new HtmlDocumentNavigator(doc, doc.DocumentElement, ignoreNamespaces);
2425
}
2526

2627
[DebuggerStepThrough]
@@ -35,14 +36,29 @@ internal static string GetOrAdd(this XmlNameTable table, string array)
3536
/// </summary>
3637
/// <param name="element">The element to start looking from.</param>
3738
/// <param name="xpath">The XPath expression.</param>
39+
/// <param name="ignoreNamespaces"></param>
3840
/// <returns>The node matching <paramref name="xpath"/> query, if any.</returns>
3941
/// <exception cref="ArgumentNullException">Throws if <paramref name="element"/> or <paramref name="xpath"/> is <c>null</c></exception>
40-
public static INode SelectSingleNode(this IElement element, string xpath)
42+
public static INode SelectSingleNode(this IElement element, string xpath, bool ignoreNamespaces = true)
43+
{
44+
return element.SelectSingleNode(xpath, new XmlNamespaceManager(new NameTable()), ignoreNamespaces);
45+
}
46+
47+
/// <summary>
48+
/// Selects a single node (or returns null) matching the <see cref="XPath"/> expression.
49+
/// </summary>
50+
/// <param name="element">The element to start looking from.</param>
51+
/// <param name="xpath">The XPath expression.</param>
52+
/// <param name="resolver"></param>
53+
/// <param name="ignoreNamespaces"></param>
54+
/// <returns>The node matching <paramref name="xpath"/> query, if any.</returns>
55+
/// <exception cref="ArgumentNullException">Throws if <paramref name="element"/> or <paramref name="xpath"/> is <c>null</c></exception>
56+
public static INode SelectSingleNode(this IElement element, string xpath, IXmlNamespaceResolver resolver, bool ignoreNamespaces = true)
4157
{
4258
var el = element ?? throw new ArgumentNullException(nameof(element));
4359
var xp = xpath ?? throw new ArgumentNullException(nameof(xpath));
44-
var nav = new HtmlDocumentNavigator(el.Owner, el);
45-
var it = nav.SelectSingleNode(xp);
60+
var nav = new HtmlDocumentNavigator(el.Owner, el, ignoreNamespaces);
61+
var it = nav.SelectSingleNode(xp, resolver ?? new XmlNamespaceManager(new NameTable()));
4662
return ((HtmlDocumentNavigator) it)?.CurrentNode;
4763
}
4864

@@ -51,14 +67,29 @@ public static INode SelectSingleNode(this IElement element, string xpath)
5167
/// </summary>
5268
/// <param name="element">The element to start looking from.</param>
5369
/// <param name="xpath">The XPath expression.</param>
70+
/// <param name="ignoreNamespaces"></param>
71+
/// <returns>List of nodes matching <paramref name="xpath"/> query.</returns>
72+
/// <exception cref="ArgumentNullException">Throws if <paramref name="element"/> or <paramref name="xpath"/> is <c>null</c></exception>
73+
public static List<INode> SelectNodes(this IElement element, string xpath, bool ignoreNamespaces = true)
74+
{
75+
return element.SelectNodes(xpath, new XmlNamespaceManager(new NameTable()), ignoreNamespaces);
76+
}
77+
78+
/// <summary>
79+
/// Selects a list of nodes matching the <see cref="XPath"/> expression.
80+
/// </summary>
81+
/// <param name="element">The element to start looking from.</param>
82+
/// <param name="xpath">The XPath expression.</param>
83+
/// <param name="resolver"></param>
84+
/// <param name="ignoreNamespaces"></param>
5485
/// <returns>List of nodes matching <paramref name="xpath"/> query.</returns>
5586
/// <exception cref="ArgumentNullException">Throws if <paramref name="element"/> or <paramref name="xpath"/> is <c>null</c></exception>
56-
public static List<INode> SelectNodes(this IElement element, string xpath)
87+
public static List<INode> SelectNodes(this IElement element, string xpath, IXmlNamespaceResolver resolver, bool ignoreNamespaces = true)
5788
{
5889
var el = element ?? throw new ArgumentNullException(nameof(element));
5990
var xp = xpath ?? throw new ArgumentNullException(nameof(xpath));
60-
var nav = new HtmlDocumentNavigator(el.Owner, el);
61-
var it = nav.Select(xp);
91+
var nav = new HtmlDocumentNavigator(el.Owner, el, ignoreNamespaces);
92+
var it = nav.Select(xp, resolver ?? new XmlNamespaceManager(new NameTable()));
6293
var result = new List<INode>();
6394

6495
while (it.MoveNext())

src/AngleSharp.XPath/HtmlDocumentNavigator.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,21 @@ public class HtmlDocumentNavigator : XPathNavigator
1111
private readonly IDocument _document;
1212
private INode _currentNode;
1313
private int _attrIndex;
14+
private readonly bool _ignoreNamespaces;
1415

1516
/// <summary>
1617
/// Creates a new XPath navigator for the given document using the provided root node.
1718
/// </summary>
1819
/// <param name="document">The document to navigate.</param>
1920
/// <param name="currentNode">The node to start navigation.</param>
20-
public HtmlDocumentNavigator(IDocument document, INode currentNode)
21+
/// <param name="ignoreNamespaces"></param>
22+
public HtmlDocumentNavigator(IDocument document, INode currentNode, bool ignoreNamespaces)
2123
{
2224
_document = document ?? throw new ArgumentNullException(nameof(document));
2325
NameTable = new NameTable();
2426
_currentNode = currentNode ?? throw new ArgumentNullException(nameof(currentNode));
2527
_attrIndex = -1;
28+
_ignoreNamespaces = ignoreNamespaces;
2629
}
2730

2831
/// <inheritdoc />
@@ -57,10 +60,20 @@ public HtmlDocumentNavigator(IDocument document, INode currentNode)
5760
: NameTable.GetOrAdd(_currentNode.NodeName);
5861

5962
/// <inheritdoc />
60-
public override string NamespaceURI => string.Empty
61-
/*_attrIndex != -1
62-
? NameTable.GetOrAdd(CurrentElement.Attributes[_attrIndex].NamespaceUri ?? string.Empty)
63-
: NameTable.GetOrAdd(CurrentElement?.NamespaceUri ?? string.Empty)*/;
63+
public override string NamespaceURI
64+
{
65+
get
66+
{
67+
if (_ignoreNamespaces)
68+
{
69+
return string.Empty;
70+
}
71+
72+
return _attrIndex != -1
73+
? NameTable.GetOrAdd(CurrentElement.Attributes[_attrIndex].NamespaceUri ?? string.Empty)
74+
: NameTable.GetOrAdd(CurrentElement?.NamespaceUri ?? string.Empty);
75+
}
76+
}
6477

6578
/// <inheritdoc />
6679
public override string Prefix =>
@@ -169,7 +182,7 @@ public override string Value
169182
/// <inheritdoc />
170183
public override XPathNavigator Clone()
171184
{
172-
return new HtmlDocumentNavigator(_document, _currentNode);
185+
return new HtmlDocumentNavigator(_document, _currentNode, _ignoreNamespaces);
173186
}
174187

175188
/// <inheritdoc />

0 commit comments

Comments
 (0)