Xml-SelectNodes with default-namespace via XmlNamespaceManager not working as expected

21,922
// This will fail with dotnet 3.5sp1. Why? 
//!!!! 
Assert.AreEqual(2, doc.SelectNodes("//b", nsmgr).Count);

This is a FAQ. In XPath any unprefixed name is assumed to be in "no namespace". In order to select elements that belong to a namespace, in any XPath expression their names must be prefixed with a prefix that is associated with this namespace. The AddNamespace() method serves exactly this purpose. It creates a binding between a specific namespace and a specific prefix. Then, if this prefix is used in an XPath expression, the element prefixed by it can be selected.

It is written in the XPath W3C spec: "A QName in the node test is expanded into an expanded-name using the namespace declarations from the expression context. This is the same way expansion is done for element type names in start and end-tags except that the default namespace declared with xmlns is not used: if the QName does not have a prefix, then the namespace URI is null".

See this at: w3.org/TR/xpath/#node-tests .

So, any unprefixed name is considered to be in "no namespace". In the provided XML document there are no b elements in "no namespace" and this is why the XPath expression //b selects no nodes at all.

Use:

XmlNamespaceManager nsmanager = new XmlNamespaceManager(doc.NameTable);
nsmanager.AddNamespace("x", "urn:test.Schema");

and later:

Assert.AreEqual(2, doc.SelectNodes("//x:b", nsmanager).Count);

Remember: The whole purpose of registering the namespace is to be able to use the prefix (in this case x) in any XPath expression.

Share:
21,922
k3b
Author by

k3b

Main intrest currently [java] development for [android] and the hybris platform; also [c#], [tdd], [bdd] ([gherkin], [specflow]) , [agile] ([scrum])

Updated on May 13, 2020

Comments

  • k3b
    k3b about 4 years

    I have some xml with default namespace

    <a xmlns='urn:test.Schema'><b/><b/></a>
    

    and want to count the number of <b/>

    How do I have to define

    XmlNamespaceManager nsmgr = ????
    Assert.AreEqual(2, doc.SelectNodes("//b", nsmgr).Count);
    

    so that the assert becomes true?

    I have tried so far (using nunit):

    [Test]
    [Ignore("Why does this not work?")]
    public void __DoesNotWork_TestSelectWithDefaultNamespace()
    {
        // xml to parse with defaultnamespace
        string xml = @"<a xmlns='urn:test.Schema'><b/><b/></a>";
    
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(xml);
    
        // fails because xpath does not have the namespace
        //!!!!
        Assert.AreEqual(2, doc.SelectNodes("//b").Count);
    
        // using XPath defaultnamespace 
        XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
        nsmgr.AddNamespace("", "urn:test.Schema");
    
        // This will fail with dotnet 3.5sp1. Why?
        //!!!!
        Assert.AreEqual(2, doc.SelectNodes("//b", nsmgr).Count);
    }
    
    [Test]
    public void TestSelectWithoutNamespaces_Ok()
    {
        // xml to parse without namespace
        string xml = @"<a><b/><b/></a>";
    
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(xml);
    
        // works ok
        Assert.AreEqual(2, doc.SelectNodes("//b").Count);
    
        // works ok
        XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
        Assert.AreEqual(2, doc.SelectNodes("//b", nsmgr).Count);
    }
    
    [Test]
    public void TestSelectWithNamespacesPrefixed_Ok()
    {
        // xml to parse with defaultnamespace
        string xml = @"<a xmlns='urn:test.Schema'><b/><b/></a>";
    
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(xml);
    
        // using XPath namespace via alias "t". works ok but xpath is to complicated
        XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
        nsmgr.AddNamespace("t", doc.DocumentElement.NamespaceURI);
        Assert.AreEqual(2, doc.SelectNodes("//t:b", nsmgr).Count);
    }
    
  • k3b
    k3b over 13 years
    thanks for your reply. you described the same as what the third unitest TestSelectWithNamespacesPrefixed_Ok() does. i still hope that there is a workaroud without the need to modify the xpath-expression
  • Admin
    Admin over 13 years
    @k3b: You wrote i still hope that there is a workaroud without the need to modify the xpath-expression. No. This is FAQ: a QName test without prefix selects elements in the null (or empty) namespace URI, not in default namespace.
  • Dimitre Novatchev
    Dimitre Novatchev over 13 years
    @k3B: No, there is no such workaround -- It is written in the XPath W3C spec: "A QName in the node test is expanded into an expanded-name using the namespace declarations from the expression context. This is the same way expansion is done for element type names in start and end-tags except that the default namespace declared with xmlns is not used: if the QName does not have a prefix, then the namespace URI is null" see this at: w3.org/TR/xpath/#node-tests . So, any unprefixed name is considered to be in "no namespace". There are no b elements in "no namespace" and you get 0 nodes
  • LarsH
    LarsH over 13 years
    +1 good answer. @Alejandro, @Dimitre, @k3b - it might be helpful to mention, as @Alejandro has mentioned before, that the above statements about QName tests without a prefix apply only to XPath 1.0. In 2.0, "An unprefixed QName ... has the namespace URI of the default element/type namespace in the expression context" (w3.org/TR/xpath20/#node-tests) That won't help in this C# application, since XPath 2.0 is not available. But I wanted to avoid the possibility that someone would read the above and conclude that no version of XPath allows use of a default namespace.
  • Dimitre Novatchev
    Dimitre Novatchev over 13 years
    @LarsH: Thanks for binging this up. I didn't want to mention this, because even in XPath 2.0 there isn't any way to set the default namespace within the XPath language -- the items of the static context can only be set by the hosting language -- this isn't too-different than the current situation with XPath 1.0 -- the only small step forward is that in registering a namespace in the hosting language one could specify a namespace as the default one and this would mean that any unprefixed element name is in this default namespace and not in "no namespace".
  • ro͢binmckenzie
    ro͢binmckenzie about 8 years
    Does "new XmlNamespaceManager()" not need doc.NameTable passed in as a parameter?
  • Dimitre Novatchev
    Dimitre Novatchev about 8 years
    @ro͢binmckenzie, Seems the version of .NET in 2010 I used allowed a parameterless constructor. Thank you for the edit.