Xml-SelectNodes with default-namespace via XmlNamespaceManager not working as expected
// 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.
![k3b](https://i.stack.imgur.com/AOukD.jpg?s=256&g=1)
k3b
Main intrest currently [java] development for [android] and the hybris platform; also [c#], [tdd], [bdd] ([gherkin], [specflow]) , [agile] ([scrum])
Updated on May 13, 2020Comments
-
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 over 13 yearsthanks 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 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 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 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 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 about 8 yearsDoes "new XmlNamespaceManager()" not need doc.NameTable passed in as a parameter?
-
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.