XPath in C#
The code under the tool runs your expression with the API you pick. There are three ways to run XPath in .NET, and all of them use the same XPath 1.0 engine:
| You have | Call | Returns |
|---|---|---|
XDocument / XElement | XPathSelectElements, XPathSelectElement, XPathEvaluate (namespace System.Xml.XPath) | Elements only, or object: a node sequence, double, string or bool |
XmlDocument | SelectNodes, SelectSingleNode | Nodes only; a number or string throws "Expression must evaluate to a node-set" |
| Either | CreateNavigator(), then Select or Evaluate | Nodes as XPathNavigator, or the scalar |
XPathSelectElements throws InvalidOperationException when the result has attributes, text or comments. The code switches to XPathEvaluate in that case.
Namespaces: why your XPath returns nothing
In XPath 1.0 a name without a prefix only matches an element that is in no namespace. When the document has a default namespace, such as an Atom feed or an SVG file, /feed/entry finds nothing. Map any prefix to the namespace URI and use it in the expression. The prefix does not have to be the one in the document. Load the "Atom feed" example to try it; the page warns when the document has a default namespace and your expression cannot match it.
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
var doc = XDocument.Parse("""<feed xmlns="http://www.w3.org/2005/Atom"><entry><title>XPath in C#</title></entry></feed>""");
// An unprefixed name never matches an element in a default namespace.
Console.WriteLine(doc.XPathSelectElements("/feed/entry/title").Count()); // 0
// Map a prefix of your choice to the namespace URI and use it in the expression.
var ns = new XmlNamespaceManager(new NameTable());
ns.AddNamespace("a", "http://www.w3.org/2005/Atom");
Console.WriteLine(doc.XPathSelectElement("/a:feed/a:entry/a:title", ns)!.Value); // XPath in C#
// Or match on local-name() and ignore namespaces.
Console.WriteLine(doc.XPathSelectElement("//*[local-name()='title']")!.Value); // XPath in C#
A prefix that the manager does not know fails before anything runs: Namespace prefix 'x' is not defined. Without a manager at all, any prefix fails with "Namespace Manager or XsltContext needed".
XPath 1.0 only
.NET has no XPath 2.0 or 3.1. Functions such as lower-case(), ends-with(), matches(), tokenize() and string-join() fail with XsltContext is needed for this query because of an unknown function, and so do variables ($name). The XPath 1.0 ways around the common ones:
using System.Xml.Linq;
using System.Xml.XPath;
var doc = XDocument.Parse("""<users><user email="[email protected]"/><user email="[email protected]"/></users>""");
// No lower-case() in XPath 1.0: translate() folds ASCII letters.
const string Upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const string Lower = "abcdefghijklmnopqrstuvwxyz";
var example = doc.XPathSelectElements($"//user[contains(translate(@email, '{Upper}', '{Lower}'), 'example.com')]");
Console.WriteLine(example.Count()); // 1
// No ends-with() either: compare the tail with substring().
var org = doc.XPathSelectElements("//user[substring(@email, string-length(@email) - string-length('.org') + 1) = '.org']");
Console.WriteLine(org.Count()); // 1
The functions you do have: last(), position(), count(), local-name(), namespace-uri(), name(), string(), concat(), starts-with(), contains(), substring-before(), substring-after(), substring(), string-length(), normalize-space(), translate(), boolean(), not(), true(), false(), lang(), number(), sum(), floor(), ceiling() and round(). id() exists but throws NotSupportedException on an XDocument.
What .NET does that other testers may not
- Numbers print like
double.ToString("R").string(1 div 0)isInfinity,string(100000000000000000000)is1E+20andstring(-0)is-0.number()acceptsInfinityandNaNbut no exponent and no leading+. - Text and CDATA next to each other are one text node, and whitespace between elements is not a node at all, because
XDocument.Parsedrops it. Load withLoadOptions.PreserveWhitespaceandtext()and positions change. name()uses the prefixXElement.GetPrefixOfNamespacefinds: the nearestxmlns:prefixfor that URI, which is not always the prefix written on the element.- A parenthesized expression is typed as a node-set when it is parsed, so
(1+2)[1]andcount((1+2))only fail when they run, and not at all when nothing asks for their nodes. following::processing-instruction('x')matches elements named x, and adescendant::orfollowing-sibling::step over nested nodes with a condition before a position (//descendant::*[@id][1]) applies the position to each node on its own. Both are .NET bugs; the tester reproduces them so it never promises results your code will not get.
FAQ
Why does my XPath return nothing when the XML has an xmlns attribute?
A name without a prefix only matches elements in no namespace. Map a prefix to the default namespace URI (the box under the expression, or XmlNamespaceManager.AddNamespace) and write /a:feed/a:entry, or match on local-name().
Does .NET support XPath 2.0 or 3.1?
No, only XPath 1.0. Newer functions fail with "XsltContext is needed for this query because of an unknown function". Use translate() and substring() as shown above, or query with LINQ to XML.
What is the difference between SelectSingleNode and XPathSelectElement?
Both return the first match or null. SelectSingleNode works on XmlDocument and returns any node; XPathSelectElement works on LINQ to XML and only returns elements.
Why does XPathSelectElements throw InvalidOperationException?
It only returns elements. For attributes, text, numbers, strings or booleans call XPathEvaluate and cast the result to IEnumerable<object>, double, string or bool.
Which .NET API should I use to run XPath?
With LINQ to XML use XPathSelectElements, XPathSelectElement and XPathEvaluate from System.Xml.XPath. With XmlDocument use SelectNodes and SelectSingleNode. XPathNavigator (Select, Evaluate) works over both and returns numbers, strings and booleans as well as nodes.
Does position start at 0 or 1?
At 1. //book[1] is the first book child of each parent; (//book)[1] is the first book in the whole document. On reverse axes (ancestor, preceding, preceding-sibling) position 1 is the nearest node.
Is my XML uploaded?
No. The XPath engine runs in your browser.