Selecting XML nodes w/ namespace and XElement

M

Michael Bray

I'm playing around with XElement stuff, and I've come across a difficulty.
The XML document that I'm reading contains an xmlns declaration on the main
node...

<root xmlns="http://www.me.com">
<ANode>
<BNode>Hello</BNode>
<BNode>Goodbye</BNode>
</ANode>
</root>

I want to be able to query this document using XPath. I can successfully
do this with the following:

XmlDocument doc = new XmlDocument();
doc.LoadXml(s);
XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable);
nsm.AddNamespace("t", "http://www.me.com");
int n = doc.SelectNodes("/t:root/t:ANode/t:BNode", nsm).Count;

.... and I see that n = 2.

But when I try to convert this to XElement (and who knows I may be
WWWAAAYYY off here as I'm just starting to look at this):

XmlReader rdr = XmlReader.Create(new StringReader(s));
XElement x = XElement.Load(rdr);
XmlNamespaceManager nsm = new XmlNamespaceManager(rdr.NameTable);
nsm.AddNamespace("t", "http://www.me.com");
int n = x.XPathSelectElements("/t:root/t:ANode/t:BNode", nsm).Count();

.... I get n = 0.


What am I doing wrong?

-mdb
 
A

Anthony Jones

Michael Bray said:
I'm playing around with XElement stuff, and I've come across a difficulty.
The XML document that I'm reading contains an xmlns declaration on the main
node...

<root xmlns="http://www.me.com">
<ANode>
<BNode>Hello</BNode>
<BNode>Goodbye</BNode>
</ANode>
</root>

I want to be able to query this document using XPath. I can successfully
do this with the following:

XmlDocument doc = new XmlDocument();
doc.LoadXml(s);
XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable);
nsm.AddNamespace("t", "http://www.me.com");
int n = doc.SelectNodes("/t:root/t:ANode/t:BNode", nsm).Count;

... and I see that n = 2.

But when I try to convert this to XElement (and who knows I may be
WWWAAAYYY off here as I'm just starting to look at this):

XmlReader rdr = XmlReader.Create(new StringReader(s));
XElement x = XElement.Load(rdr);
XmlNamespaceManager nsm = new XmlNamespaceManager(rdr.NameTable);
nsm.AddNamespace("t", "http://www.me.com");
int n = x.XPathSelectElements("/t:root/t:ANode/t:BNode", nsm).Count();

... I get n = 0.


What am I doing wrong?

I've not done much with this new XElement either but I'd be willing to guess
that since x is root and in this case is the top most node (in the
XmlDocument the document is the top most node and root is a child). then
the path is wrong. / refers to root and root doesn't have a child called
root.

Try:-

"t:ANode/t:BNode"

I'm currently on a my 2000 machine so I can't test that at the moment.
 
C

Chakravarthy

Dude,

First, understand the difference between XMLDocument and XMLReader.
XMLDocument is inmemory .. where are XMLReader is not a cache.

thus, when you first queried you got the count, where are when you did the
second mechanism, it doesn't have loaded the entire XML into memory.

Hope am clear to you,
 
J

Jon Skeet [C# MVP]

First, understand the difference between XMLDocument and XMLReader.
XMLDocument is inmemory .. where are XMLReader is not a cache.

thus, when you first queried you got the count, where are when you did the
second mechanism, it doesn't have loaded the entire XML into memory.

Doesn't XElement.Load(rdr) load the whole thing into memory though?
That's certainly what I'd expect.

Jon
 
P

Pavel Minaev

I've not done much with this new XElement either but I'd be willing to guess
that since x is root and in this case is the top most node (in the
XmlDocument the document is the top most node and root is a child).  then
the path is wrong.  / refers to root and root doesn't have a child called
root.

 Try:-

"t:ANode/t:BNode"

Yes, this is correct - "/" refers to the root node in the XML tree,
which is document in XmlDocument, but <root> element in this case.
You may either follow the suggestion above, or load the XML into
System.Xml.Linq.XDocument instead - then you'll get the usual
behavior.

By the way, why XPath? The whole point of the new APIs is to use LINQ
queries instead:

XNamespace t = "http://www.me.com";
var result = doc.Elements(t + "ANode").Elements(t + "BNode");
 
M

Michael Bray

Yes, this is correct - "/" refers to the root node in the XML tree,
which is document in XmlDocument, but <root> element in this case.
You may either follow the suggestion above, or load the XML into
System.Xml.Linq.XDocument instead - then you'll get the usual
behavior.

By the way, why XPath? The whole point of the new APIs is to use LINQ
queries instead:

XNamespace t = "http://www.me.com";
var result = doc.Elements(t + "ANode").Elements(t + "BNode");

I'm using XPath because I want to be able to specify in a configuration
file which set of nodes to analyze. What you say makes a lot of sense if I
always know the path and can hard code it.

Even so, testing your code yields no results:

string s = "<root xmlns='http://www.me.com'><ANode><BNode>Hello</BNode>
<BNode>Goodbye</BNode></ANode></root>";

XDocument doc = XDocument.Parse(s);
XNamespace ns = "http://www.me.com";
int nn = doc.Elements(ns + "ANode").Elements(ns + "BNode").Count();

yields.. nn = 0. did I miss something?

-mdb
 
M

Michael Bray

I've not done much with this new XElement either but I'd be willing to
guess that since x is root and in this case is the top most node (in
the XmlDocument the document is the top most node and root is a
child). then the path is wrong. / refers to root and root doesn't
have a child called root.

Try:-

"t:ANode/t:BNode"

Yup tried that before I posted (along with every other path I could think
of).. Same result - 0 nodes selected.

I'm just grasping at straws at this point - almost ready to abandon XML
Linq for now and stick with what works.

-mdb
 
M

Martin Honnen

Michael said:
Even so, testing your code yields no results:

string s = "<root xmlns='http://www.me.com'><ANode><BNode>Hello</BNode>
<BNode>Goodbye</BNode></ANode></root>";

XDocument doc = XDocument.Parse(s);
XNamespace ns = "http://www.me.com";
int nn = doc.Elements(ns + "ANode").Elements(ns + "BNode").Count();

yields.. nn = 0. did I miss something?

Yes, certainly, the XDocument doc you construct has a root element named
'root' that your code ignores. You need/want
doc.Root.Elements(ns + "ANode").Elements(ns + "BNode").Count()
or (naming the root explicitly)
doc.Element(ns + "root").Elements(ns + "ANode").Elements(ns +
"BNode").Count()
 
M

Martin Honnen

Michael said:
Yup tried that before I posted (along with every other path I could think
of).. Same result - 0 nodes selected.

Here is a working sample that outputs 2:

XElement root = XElement.Load(@"..\..\XMLFile1.xml");
XmlNamespaceManager mgr = new XmlNamespaceManager(new
NameTable());
mgr.AddNamespace("pf", "http://www.me.com");

Console.WriteLine(root.XPathSelectElements("pf:ANode/pf:BNode",
mgr).Count());

XML document is

<?xml version="1.0" encoding="utf-8" ?>
<root xmlns="http://www.me.com">
<ANode>
<BNode>Hello</BNode>
<BNode>Goodbye</BNode>
</ANode>
</root>

If your code does not find anything then please show us a complete
sample that demonstrates that, somewhere there must be a mistake.
 
M

Michael Bray

I've not done much with this new XElement either but I'd be willing to
guess that since x is root and in this case is the top most node (in
the XmlDocument the document is the top most node and root is a
child). then the path is wrong. / refers to root and root doesn't
have a child called root.

Try:-

"t:ANode/t:BNode"


OK hmmm.. as I was playing around I think I fixed it... I can query
using both the XPath query mode and the Elements(..)... Here's the code
that works for me:

string s = "<root xmlns='http://www.me.com'><ANode><BNode>Hello</BNode>
<BNode>Goodbye</BNode></ANode></root>";

XmlReader rdr = XmlReader.Create(new StringReader(s));
XDocument doc = XDocument.Load(rdr);
XmlNamespaceManager nsm = new XmlNamespaceManager(rdr.NameTable);
nsm.AddNamespace("t", "http://www.me.com");
XNamespace ns = "http://www.me.com";

int n = doc.XPathSelectElements("/t:root/t:ANode/t:BNode", nsm).Count();
int nn = doc.Elements(ns + "root").Elements(ns + "ANode").Elements(ns +
"BNode").Count();
int nnn = doc.Elements("root").Elements("ANode").Elements("BNode").Count();

var q = doc.XPathSelectElements("/t:root/t:ANode/t:BNode", nsm);
foreach (var v in q) Console.WriteLine(v.Value);


yields.... n = 2 nn = 2 nnn = 0
and outputs: Hello Goodbye

so I think the main thing that fixed it was loading into XDocument instead
of XElement. Also, when querying using the Elements() I needed to make
sure I added the XNamespace.

Thanks to everyone for their input... I knew it would be something simple
I was missing.

-mdb
 
M

Michael Bray

Yes, certainly, the XDocument doc you construct has a root element named
'root' that your code ignores. You need/want
doc.Root.Elements(ns + "ANode").Elements(ns + "BNode").Count()
or (naming the root explicitly)
doc.Element(ns + "root").Elements(ns + "ANode").Elements(ns +
"BNode").Count()

Haha yeah I actually just meandered into the same solution and posted the
same answer... Turned out that my original problem was that I was loading
into XElement instead of XDocument!

Thanks for you help!!

-mdb
 
M

Michael Bray

If your code does not find anything then please show us a complete
sample that demonstrates that, somewhere there must be a mistake.

Hmmm now that is odd... I confirm that this works, but I can't see any
significant difference from what (I thought) I tested... maybe I just
was too blinded by frustration and was making silly mistakes.

Oh well... I have things working like I need them now, so I'm happy again.
Thanks!

-mdb
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top