How to get all non empty nodes from XElement? - c#

I'm trying to get all nodes from an XElement that actually has a value,
currently I'm using this code:
var nodes = from node in elem.Nodes()
where node.NodeType == XmlNodeType.Element &&
((XElement) node).Value.Length > 0
select node;
Is there a build in operator to do this operation?
Thanks

I don't believe there's anything like this built in. Are you sure you want to include elements that have subelements though? For example:
XElement e = new XElement("Foo", new XElement("Bar"));
Console.WriteLine(e);
Console.WriteLine(e.Value.Length);
This will print:
<Foo>
<Bar />
</Foo>
0
... so Foo would be included as an "empty" node even though it contains another element. Is that definitely what you're after?

Related

XNode.DeepEquals considers selfclosing tags and closing tags as different

I want to compare two xml documents to equality. One of my document have self-closing tag and other don't.
XNode node = XDocument.Parse("<Root/>");
XNode node2 = XDocument.Parse("<Root></Root>");
bool result = XNode.DeepEquals(node, node2);
Console.WriteLine(result);
Run it quickly
I assume "<Root/>" is equivalent to "<Root></Root>" however DeepEquals says they are not(returns false).
How can I compare these these two documents to be equal?
ps:Not constrained with XNode. Solution using XmlDocument also welcome.
To compare two documents for equality they should be normalized to some extent.
Eric White made a couple blog posts detailing advanced normalization[1] and simple normalization[2].
To fix the empty tag vs self closing tag the latter article is more relevant.
From the article:
static XElement CloneElement(XElement element)
{
return new XElement(element.Name,
element.Attributes(),
element.Nodes().Select(n =>
{
XElement e = n as XElement;
if (e != null)
return CloneElement(e);
return n;
})
);
}
This code will copy an element and its decedents recursively into a normalized form (using the default XElement construction). This includes normalizing empty tags into self closing tags. Comparing the documents will then work as expected.
Consider the following XML:
<Foo>
<Bar/>
<Root/>
</Foo>
and
<Foo>
<Bar></Bar>
<Root></Root>
</Foo>
After using CloneElement on each element they will be equal.
var node = XElement.Parse("<Foo><Bar/><Root/></Foo>");
var node2 = XElement.Parse("<Foo><Bar></Bar><Root></Root></Foo>");
XElement.DeepEquals(node, node2); // False
XElement.DeepEquals(CloneElement(node), CloneElement(node2)); // True
[1]: Equality Semantics of LINQ to XML Trees
[2]: Empty Elements and Self-Closing Tags

use LINQ on XmlNodeList

<X version="1.0">
<Y id="abc" abv="a"/>
<Y id="edf" abv="e"/>
</X>
I want to select the node whose id is "abc", and return its abv "a".
XmlDocument doc = new XmlDocument();
doc.Load(filePath);
XmlNodeList list = doc.SelectNodes("X/Y");
var node = list.Cast<XmlNode>().Where(node => node["id"].InnerText == "abc")
.Select(x=>x["abv"].InnerText);
But it does't work, node["id"].InnerText is always "". Can you point out where is a problem?
Thanks a lot
Aside from the fact what your code snippet wouldn't be compiled because of non-unique node variable (first outside of linq query and second in "where" method lambda), you have also missed Attributes in your query.
It should be something like
var node = list.Cast<XmlNode>()
.Where(n => n.Attributes["id"].InnerText == "abc")
.Select(x => x.Attributes["abv"].InnerText);
The InnerText for a node is the text that appears between <node> and </node>. So for, eg <Y attributes /> there is no inner text.
You need to use node => node.Attributes["id"].Value == "abc"
Just cast XmlNodeList to List, like that:
List<XmlNode> list = new List<XmlNode>();
foreach(XmlNode a in xmlNodeList)
{
list.Add(a);
}
list.OrderBy((element) => element.ChildNodes[0].InnerText);

Search all child nodes of XML node for a value and remove the grandparent node

Trying to use
exportDoc.Root.Elements("string").Where(node => !(node.Element("product").HasElements) || node.Element("product").Element("type").Value != product).Remove();
to remove the nodes in my XML document where the product string I'm searching for doesn't occur. Here is a sample of my XML structure:
<root>
<string id = "Hithere">
<product>
<type>orange</type>
<type>yellow</type>
<type>green</type>
<product>
<element2/>
<element3/>
</string>
<string id ="...">
...
...
</root>
So I need to look under the product element of each string element AND at each of the type elements therein to see if the value of string product (input to the method where this is contained) occurs. At present, it looks like my code only removes the node if the product string I'm searching for matches the value of just the first type element.
The whole point is to remove all string nodes from this xdoc that don't have the product I'm looking for listed under their product element.
You need to change your search condition slightly:
var nodesToRemove = xDoc.Root
.Elements("string")
.Where(node =>
!(node.Element("product").HasElements) ||
node.Element("product").Elements("type").All(x => x.Value != product))
.ToList();
This should match elements which all string:product:types differ from product value (or in other words - if at least one <type> will match your product, it won't be marked for removal).
You can't Remove() while you're still enumerating (deferred execution).
You need something more like:
// untested
var toRemove = exportDoc.Root.Elements("string")
.Where(node => !(node.Element("product").HasElements) ||
node.Element("product").Element("type").Value != product).ToList();
toRemove.Remove();

Why isn't this XElement query working on my xml

My xml looks like:
<nodes>
<node name="somekey">
<item name="subject">blah</item>
<item name="body">body</item>
</node>
</nodes>
And my code so far is:
XDocument doc = XDocument.Load(HttpContext.Current.Server.MapPath(String.Format("~/files/{0}/text.xml", "en")));
if (doc != null)
{
XElement element = doc.Elements().Where(e => e.Elements().Any() && e.Attribute("name").Value == "someKey").First();
}
I am getting an error saying:
Sequence contains no elements
Is my query wrong?
I stepped through the code, and it errors out on the line with XElement..
You want something like this:
var element = doc.Descendants("node").Where(x => x.Attribute("name") != null && x.Attribute("name").Value == "somekey").FirstOrDefault();
Edit: Edited to grab first element from result;
You could also use:
var element = doc.Elements()
.Elements()
.Where(e => (e.Elements().Any()
&& e.Attribute("name").Value == "somekey"))
.First();
Explanation:
The doc.Elements() grabs the root element, which is nodes. Then the .Elements() selects the child elements of that, which is just one, node. The .Where() is then performed on that nodeset, which is what you want. The lambda selects those elements that have child elements, and also have an attribute "name" with value "somekey".
Your original code was not getting the Child-of-Child-elements. Hence the original result set was empty.
You could also do this with .Descendants() but that feels a little sloppy and loose, to me.

What's XmlNodeType.Document ? Difference between XmlNodeType.Element and XmlNodeType.Document

I am using XmlReader to read an XML File and I want to count XML Element right under of Document Element that as I know it should be the root element.
XML
<?xml version="1.0" encoding="utf-8"?>
<NewsLetters>
<EMail Date="10/10/2009">hello#hello.com</EMail>
<EMail Date="10/10/2009">hello#hello.com</EMail>
<EMail Date="10/10/2009">hello#hello.com</EMail>
<EMail Date="10/10/2009">hello#hello.com</EMail>
</NewsLetters>
C# Code :
public static string TotalMemberCount()
{
XmlTextReader reader = new XmlTextReader(HttpContext.Current.Server.MapPath("~/Newsletter/NewsLetter.xml"));
int totalCount = 0;
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element && reader.NodeType != XmlNodeType.Document)
totalCount++;
}
return totalCount.ToString();
}
Normally I was expecting 4 records but it returns 5 because it also counts the root element. What should I do ? Actually I know how to solve with using XDocument,XElement and LINQ but I want to solve in this way, don't ask me why because I want to learn every way in which I can solve such problems.
Thanks in advance.
Sincerely....
The answer you've come up with is flawed in at least five different ways.
It's certainly not a "generic solution", since your code is now completely dependent on information in the XML - your method can only count elements properly if the XML document it's processing contains the flag attribute that you've added.
The flag attribute's unnecessary. Any time an XmlReader starts at the beginning of the stream it's reading, the first element it reads is always going to be the top-level element. It can't possibly be anything else. Instead of adding an attribute to your document to identify the top-level element, you can just use a flag to track whether or not you've read the top-level element yet. Or heck, you can just subtract 1 from the total.
And even if you did need the flag attribute, you're doing it wrong. You're using MoveToFirstAttribute to find it. What if there's more than one atttribute on the element? What if the first attribute your code finds whose value is True isn't Root? And what if one of the child elements has an attribute on it with that value? If you're going to use an attribute for this purpose, you should, at the very least, search for it by name.
This code won't count all child elements of the top-level element, it will count all descendant elements. The reader moves from node to node in document order. If an element node has a child node, that child node is the next node that gets read by Read(). There are methods of the XmlReader that you can use to read an entire element and all of its content in one single gulp, but you're not using them.
The condition reader.NodeType != XmlNodeType.XmlDeclaration && reader.NodeType == XmlNodeType.Element is redundant: there's no way that a node can be an XML declaration if it's an element.
Anyways, I found my own solution, what I was trying to achieve is to find a generic solution so I came up with such a solution :
XML
<?xml version="1.0" encoding="utf-8"?>
<NewsLetters Root="True">
<EMail Date="10/10/2009">hello#hello.com</EMail>
<EMail Date="10/10/2009">hello#hello.com</EMail>
<EMail Date="10/10/2009">hello#hello.com</EMail>
<EMail Date="10/10/2009">hello#hello.com</EMail>
</NewsLetters>
C#
public static string TotalMemberCount()
{
int totalCount = 0;
using (XmlTextReader reader = new XmlTextReader(HttpContext.Current.Server.MapPath("~/Newsletter/NewsLetter.xml")))
{
while (reader.Read())
{
if (reader.NodeType != XmlNodeType.XmlDeclaration && reader.NodeType == XmlNodeType.Element)
{
if (reader.MoveToFirstAttribute())
{
if (reader.Value == "True")
//gotcha, I don't want this,this is root element
continue;
}
totalCount++;
}
}
return totalCount.ToString();
}
}

Categories

Resources