Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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);
}
like image 688
k3b Avatar asked Nov 24 '10 21:11

k3b


1 Answers

// 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.

like image 53
Dimitre Novatchev Avatar answered Nov 17 '22 13:11

Dimitre Novatchev