.Net 2.0 XPath Oddity

Martin "xarragon" Persson, 10th November 2008
Email: xarragon <magic-symbol> gmail <le dot> com

Here is the XML snippet that I tried to extract data from.

<?xml version="1.0" encoding="iso-8859-1"?>
<itemcollection xmlns:e="namespace1" xmlns:d="namespace2">
    <item id="1">
        <e:property1>item1:namespace1:property1</e:property1>
        <e:property2>item1:namespace1:property2</e:property2>
        <d:property1>item1:namespace2:property1</d:property1>
        <d:property2>item1:namespace2:property2</d:property2>
    </item>
    <item id="2">
        <e:property1>item2:namespace1:property1</e:property1>
        <e:property2>item2:namespace1:property2</e:property2>
        <d:property1>item2:namespace2:property1</d:property1>
        <d:property2>item2:namespace2:property2</d:property2>
    </item>
</itemcollection>

And here is the C# code that is causing some confusion.

using System;
using System.IO;
using System.Data;
using System.Xml;
using System.Xml.XPath;

public class ParseTest
{
    public static void Main (string[] arguments)
    {
        XmlDocument document = new XmlDocument();

        // Read the XML-based response into the XML document.
        try {
            document.Load("parsetest.xml");
        } catch (XmlException e) {
            Console.Write("Error: Unable loading XML document (" + e.ToString() + ")\n");
            return;
        }

        XmlNamespaceManager manager = new XmlNamespaceManager(document.NameTable);
        manager.AddNamespace("ns1", "namespace1");
        manager.AddNamespace("ns2", "namespace2");
    
        // Select all the book nodes.
        XmlNodeList responses = document.SelectNodes("//item", manager);

        foreach (XmlNode responseNode in responses) {
            XmlNode ns1_property1_node = responseNode.SelectSingleNode("//ns1:property1", manager);
            XmlNode ns1_property2_node = responseNode.SelectSingleNode("//ns1:property2", manager);
            XmlNode ns2_property1_node = responseNode.SelectSingleNode("//ns2:property1", manager);
            XmlNode ns2_property2_node = responseNode.SelectSingleNode("//ns2:property2", manager);

            Console.Write("Item data:\n");
            Console.Write("ns1:property1: " + ns1_property1_node.InnerXml + "\n");
            Console.Write("ns1:property2: " + ns1_property2_node.InnerXml + "\n");
            Console.Write("ns2:property1: " + ns2_property1_node.InnerXml + "\n");
            Console.Write("ns2:property2: " + ns2_property2_node.InnerXml + "\n");
            Console.Write("\n");
            
        }

    }
}

And here is the console output.

Item data:
ns1:property1: item1:namespace1:property1
ns1:property2: item1:namespace1:property2
ns2:property1: item1:namespace2:property1
ns2:property2: item1:namespace2:property2

Item data:
ns1:property1: item1:namespace1:property1
ns1:property2: item1:namespace1:property2
ns2:property1: item1:namespace2:property1
ns2:property2: item1:namespace2:property2

I expected the second chunk of data to come from the <item id="2">...</item> part of the XML document.

The XPath expression obviously selects the correct nodes from the XML document, as we can see here in the screenshot on the CLR debugger.

Screenshot of Microsoft CLR debugger with example code running.

Further investigation reveals that responseNode do contains the the XML for <item id="2">...</item>, so the first XPath expression and the foreach-loop seems to be working. I was initially afraid that I had misunderstood the use of the foreach construct, but this (luckily) didn’t seem to be the case.

It seems that on the second iteration, the XPath expressions doesn’t return the appropiate parts of the XML document, and thus the previous values are retained from the first run. Or are they? I thought it was supposed to return null in that case?

What am I doing wrong?