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

1

16

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);
}
Dishearten answered 24/11, 2010 at 21:25 Comment(0)
K
39
// 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.

Kremer answered 24/11, 2010 at 21:54 Comment(7)
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-expressionDishearten
@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.Proverbial
@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 nodesKremer
+1 good answer. @Alejandro, @Dimitre, @Dishearten - 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.Heaven
@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".Kremer
Does "new XmlNamespaceManager()" not need doc.NameTable passed in as a parameter?Telltale
@ro͢binmckenzie, Seems the version of .NET in 2010 I used allowed a parameterless constructor. Thank you for the edit.Kremer

© 2022 - 2024 — McMap. All rights reserved.