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);
}
k3b
  • 14,517
  • 7
  • 53
  • 85

1 Answers1

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

ro͢binmckenzie
  • 325
  • 3
  • 11
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • 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 – k3b Nov 24 '10 at 22:03
  • 4
    @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. –  Nov 24 '10 at 22:10
  • @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: http://www.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 – Dimitre Novatchev Nov 24 '10 at 22:10
  • 6
    +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" (http://www.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. – LarsH Nov 26 '10 at 04:39
  • 3
    @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". – Dimitre Novatchev Nov 26 '10 at 06:13
  • Does "new XmlNamespaceManager()" not need doc.NameTable passed in as a parameter? – ro͢binmckenzie May 12 '16 at 09:31
  • @ro͢binmckenzie, Seems the version of .NET in 2010 I used allowed a parameterless constructor. Thank you for the edit. – Dimitre Novatchev May 12 '16 at 14:21