XPath with Xml Namespaces

I spent a very frustrating hour this afternoon trying to modify an XPath query to work with namespaces.  I’m going to recreate this scenario and illustrate how we execute an XPath query against an Xml document without namespaces and then select the same data on an Xml Document with namespaces.

Note:  In general I distinctly dislike Xml namespaces – I suppose I could think of a use for them but most of the time YAGNI.

XPath without namespaces

<?xml version="1.0" encoding="utf-8" ?>
<books>
  <book title="Harry Potter">
    <author name="J.K. Rowling">Some details about the author</author>
  </book>
  <book title="WPF Unleashed">
    <author name="Adam Nathan" />
  </book>
</books>

This is the sample document I created.  Now suppose we want to select the details of the author of ‘Harry Potter’ as well as the name of the author of ‘WPF Unleashed’.  This is the most common scenario I encounter when working with Xml – I can see the data I want – I just need to figure out the query to select it.

In this case we have 2 simple XPath expressions that we execute using the C# XmlDocument class.

var withoutNamespaces = new XmlDocument();
withoutNamespaces.Load("DataWithoutNamespaces.xml");

withoutNamespaces.SelectSingleNode("/books/book[@title='Harry Potter']/author").InnerText
withoutNamespaces.SelectSingleNode("/books/book[@title='WPF Unleashed']/author/@name").Value

This works as expected.  Now let’s do the same for a document with Xml namespaces.

XPath with namespaces

<?xml version="1.0" encoding="utf-8" ?>
<books xmlns="http://www.amazon.com">
  <book xmlns="http://www.amazon.com/HarryPotter" title="Harry Potter">
    <author name="J.K. Rowling">Some details about the author</author>
  </book>
  <book title="WPF Unleashed">
    <author name="Adam Nathan" />
  </book>
</books>

So we have the same data as before, but our XPath queries will no longer be able to select it.  You won’t even be able to select the root node – every query will simply return null.

To be able to specify the namespaces for our XPath queries we need to use the C# XmlNamespaceManager class and pass this as an overload to the SelectSingleNode method.  You need to create an alias for every namespace and then use this alias as a prefix for every element that inherits the namespace.

var withNamespaces = new XmlDocument();
withNamespaces.Load("DataWithNamespaces.xml");

var namespaceManager = new XmlNamespaceManager(withNamespaces.NameTable);
namespaceManager.AddNamespace("ama", "http://www.amazon.com");
namespaceManager.AddNamespace("pot", "http://www.amazon.com/HarryPotter");

withNamespaces.SelectSingleNode("/ama:books/pot:book[@title='Harry Potter']/pot:author", namespaceManager).InnerText
withNamespaces.SelectSingleNode("/ama:books/ama:book[@title='WPF Unleashed']/ama:author/@name", namespaceManager).Value

Note the use of the ‘pot’ prefix on the first author element and ‘ama’ on the second author element – because these elements don’t have explicit namespaces the namespaces are inherited.  Also note that the ‘name’ attribute selector does not have a namespace prefix – only elements inherit the namespaces.

Hope this helps someone.  You can get the code here.