I just spent a few hours banging my head against a XPath query that didn't return the node I was trying to select using the right xpath string: to make a long story short, if your XML document has a default namespace without any prefix, to make XPath queries on it you have to use a NamespageManager, add the default namespace with a fake prefix, and then query the document specifying the namespace manager in the SelectNodes method.

But let's explain a bit more in detail. Imagine you want to make a XPath query on a VisualStudio project (the following is just a part of a real csproj file):

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="2.0" DefaultTargets="Build"
    xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <ProjectType>Local</ProjectType>
    <ProductVersion>8.0.50727</ProductVersion>
    <SchemaVersion>2.0</SchemaVersion>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Data" />
    <Reference Include="Microsoft.Practices.EnterpriseLibrary.Common">
      <Name>Microsoft.Practices.EnterpriseLibrary.Common</Name>
      <HintPath>C:\...\Microsoft.Practices.EnterpriseLibrary.Common.dll</HintPath>
    </Reference>
    <Reference Include="Microsoft.Practices.EnterpriseLibrary.Data">
      <Name>Microsoft.Practices.EnterpriseLibrary.Data</Name>
      <HintPath>C:\...\Microsoft.Practices.EnterpriseLibrary.Data.dll</HintPath>
    </Reference>
  </ItemGroup>
</Project>

If the xml document didn't have the default namespace

xmlns="http://schemas.microsoft.com/developer/msbuild/2003"

we could have queried the document to select the node with the reference for the Enterprise Library Data dll with the following XPath query string:

XmlNode entlibDataNode = doc.SelectSingleNode(
"/Project/ItemGroup/Reference[Name='Microsoft.Practices.EnterpriseLibrary.Data']"
);

But unfortunately our xml document has a default namespace, so this query doesn't work: in order to make it work we need to instruct the XPath query engine to use the default namespace as follows:

XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
nsManager.AddNamespace(
             "a",
             "http://schemas.microsoft.com/developer/msbuild/2003"
              );

The code above creates a XmlNamespaceManager and then add the document namespace with the fictitious prefix "a".

Once the XmlNamespaceManager has been set up we can go and query the document, but this time adding "a:" prefix before every node name:

XmlNode entlibDataNode = doc.SelectSingleNode(
"/a:Project/a:ItemGroup/a:Reference[a:Name='Microsoft.Practices.EnterpriseLibrary.Data']",
nsManager
);

I also noticed that this approach is probably used also by the XML Notepad 2007. If you open a VisualStudio project file with the XML Notepad and open the Find dialog selecting "Use XPath", and then you select a node of the XML, in the search string you will notice that it generates the XPath string using the prefix "a" before every node name.

xmlNotepad_search

Not sure if it's a bug or a feature, but this way it works. Hope this will save a bit of your time.

kick it on DotNetKicks.com

Technorati Tag: ,,