In XML, check if child nodes contain specific values with C# - c#

I'm getting back some XML and this is a section of it:
<pair name="cf_item7" type="pair">
<pair name="cf_id" type="integer">34</pair>
<pair name="value" type="string">0</pair>
</pair>
Within the XML are a number of pairs. The section I need to check with always have a name beginning with cf_item* but this could be cf_item7, cf_item6, or any other number.
I need to check within one of these cf_item* sections, and whether or not it has a cf_id, with the content equalling 34 (it will always be 34).
If 34 exists, I need to check the next node (value), and check if it is equal to 0 or 1.
If it equals 0, I need to output a message to the screen (web page).
I've inherited the original code, which is written in C# .net - Can anyone help as it's not a language I'm familiar with?
I started with this, based on some of the other code, but I'm unsure where to go next.
if (eleName.Contains("cf_item")) {
XmlNode nodes = eleList[i].ChildNodes;
}

First, get the first element with pair name then check if the name attribute contains cf_item.
If yes, get all elements with pair name from that first element.
The result will be in an IEnumerable<XElement> then you can check the first element if it contains 34 and if the second element contains 0 or not.
The code will be something like this:
XDocument xmldata = XDocument.Load(xmlFile);
XElement node = xmldata.Element("pair");
if (node.Attribute("name").Value.Contains("cf_item"))
{
IEnumerable<XElement> nextElmts = node.Elements("pair");
if (nextElmts.ElementAt(0).Attribute("name").Value.Contains("cf_id"))
{
if ((Convert.ToInt32(nextElmts.ElementAt(0).Value) == 34))
{
if (Convert.ToInt32(nextElmts.ElementAt(1).Value) == 0)
{
//do what you want here
Console.WriteLine("it's 0");
}
}
}
}
Note: this is only for one cf_item. You need to modified it if cf_item is more than one.

Using System.Xml.Linq.XElement, you can do something like this:
namespace ConsoleApplication4
{
using System;
using System.Linq;
using System.Xml.Linq;
internal class Program
{
private static void Main(string[] args)
{
var xmlDocument = XElement.Load(#"c:\tmp\foo.xml");
// Loop over all elements having a "name" attribute starting with "cf_item".
foreach (var pairElement in xmlDocument.Elements().Where(x => x.Attributes().Any(y => y.Name == "name" && y.Value.StartsWith("cf_item"))))
{
// Is there a child element, having a "name" attribute set to "cf_id" with a value of "34"?
if (pairElement.Elements().Any(x => x.Attributes().Any(y => y.Name == "name" && y.Value == "cf_id") && x.Value == "34"))
{
// Fetch the single element with a "name" attribute set to "value".
var valueElement = pairElement.Elements().Single(x => x.Attributes().Any(y => y.Name == "name" && y.Value == "value"));
// Output the value of it!
Console.WriteLine(valueElement.Value);
}
else
{
Console.WriteLine("No cf_id set to 34.");
}
}
}
}
}
XML
<root>
<pair name="cf_item7" type="pair">
<pair name="cf_id" type="integer">34</pair>
<pair name="value" type="string">0</pair>
</pair>
<pair name="cf_item8" type="pair">
<pair name="cf_id" type="integer">34</pair>
<pair name="value" type="string">1</pair>
</pair>
<pair name="cf_item9" type="pair">
<pair name="value" type="string">0</pair>
</pair>
</root>
Output
0
1
No cf_id set to 34.
Press any key to continue . . .

This should work:
var valueAfter34 = (from e in doc.Descendants("pair")
where e.Attribute("name").Value.StartsWith("cf_item")
let itemPairs = e.Descendants()
from idElement in itemPairs
where idElement.Attribute("name").Value.Equals("cf_id") &&
idElement.Value == "34"
select idElement.ElementsAfterSelf().FirstOrDefault().Value).FirstOrDefault();
if (valueAfter34 == "0")
{
// show message
}
It assumes there is one pair element with cf_id 34. If that's not the case it should need some fiddling.

Related

Remove Child Nodes Keep Their Parent

I have XML Code Block as below (it is part of similar lines of hundreds..):
<specs>
<spec name='fald' value = '100'>
<name></name>
<value></value>
</spec>
</specs>
I need to convert code as seen below:
<specs>
<spec name ='fald' value = '100'/>
</specs>
Using following code I am able to delete child nodes:
foreach (XElement child in doc.Descendants().Reverse())
{
if (child.HasAttributes)
{
foreach (var attribute in child.Attributes())
{
if (string.IsNullOrEmpty(attribute.Value) && string.IsNullOrEmpty(child.Value))
child.Remove();
}
}
}
But this process also deletes parent node ('spec') which is expected to take place there. Any help is appreciated, thanks...
It's a little unclear what the criteria for deleting an element is, but to get you started, perhaps this is somewhere along the lines of what you are looking for?
var xml =
#"<specs>
<spec name='fald' value='100'>
<name></name>
<value></value>
</spec>
</specs>";
var doc = XElement.Parse(xml);
var childrenToDelete = doc.XPathSelectElements("//spec/*")
.Where(elem => string.IsNullOrEmpty(elem.Value)
&& (!elem.HasAttributes
|| elem.Attributes().All(attr => string.IsNullOrEmpty(attr.Value))))
.ToList();
foreach (var child in childrenToDelete)
{
child.Remove();
}
// Produces:
// <specs>
// <spec name="fald" value="100" />
// </specs>
Check this fiddle for a test run.

Is my usage of LINQ to XML correct when I'm trying to find multiple values in the same XML file?

I have to extract values belonging to certain elements in an XML file and this is what I ended up with.
XDocument doc = XDocument.Load("request.xml");
var year = (string)doc.Descendants("year").FirstOrDefault();
var id = (string)doc.Descendants("id").FirstOrDefault();
I'm guessing that for each statement I'm iterating through the entire file looking for the first occurrence of the element called year/id. Is this the correct way to do this? It seems like there has to be a way where one would avoid unnecessary iterations. I know what I'm looking for and I know that the elements are going to be there even if the values may be null.
I'm thinking in the lines of a select statement with both "year" and "id" as conditions.
For clearance, I'm looking for certain elements and their respective values. There'll most likely be multiple occurrences of the same element but FirstOrDefault() is fine for that.
Further clarification:
As requested by the legend Jon Skeet, I'll try to clarify further. The XML document contains fields such as <year>2015</year> and <id>123032</id> and I need the values. I know which elements I'm looking for, and that they're going to be there. In the sample XML below, I would like to get 2015, The Emperor, something and 30.
Sample XML:
<?xml version="1.0" encoding="UTF-8"?>
<documents xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<make>Apple</make>
<year>2015</year>
<customer>
<name>The Emperor</name>
<level2>
<information>something</information>
</level2>
<age>30</age>
</customer>
A code that doesn't parse the whole xml twice would be like:
XDocument doc = XDocument.Load("request.xml");
string year = null;
string id = null;
bool yearFound = false, idFound = false;
foreach (XElement ele in doc.Descendants())
{
if (!yearFound && ele.Name == "year")
{
year = (string)ele;
yearFound = true;
}
else if (!idFound && ele.Name == "id")
{
id = (string)ele;
idFound = true;
}
if (yearFound && idFound)
{
break;
}
}
As you can see you are trading lines of code for speed :-) I do feel the code is still quite readable.
if you really need to optimize up to the last line of code, you could put the names of the elements in two variables (because otherwise there will be many temporary XName creation)
// before the foreach
XName yearName = "year";
XName idName = "id";
and then
if (!yearFound && ele.Name == yearName)
...
if (!idFound && ele.Name == idName)

Parsing XML in Compact Framework 3.5

I have a following XML that I need to parse, but I am having some problems. First, the amount of tags that I have inside Class tag is not known, and they not distinct (so I can't specify them by their name).
XML example:
<Class value="1B2">
<FirstName>Bob</FirstName>
<FirstName>Jim</FirstName>
<FirstName>Alice</FirstName>
<FirstName>Jessica</FirstName>
//(More similar lines, number is not known)
</Class>
<Class value="2C4">
<FirstName>Bob</FirstName>
<FirstName>Jim</FirstName>
<FirstName>Alice</FirstName>
<FirstName>Jessica</FirstName>
//(More similar lines, number is not known)
</Class>
Now this is my code so far to parse it:
Define xmlReader and XElement
XmlReader xmlReader = XmlReader.Create(modFunctions.InFName);
XElement xElem = new XElement("FirstName");
Then I am making connection to SQL Server CE database and this is my main loop that reads xmlfile:
while (xmlReader.Read())
{
if (xmlReader.NodeType == XmlNodeType.Element &&
(xmlReader.LocalName == "Class" || xmlReader.LocalName == "FirstName") &&
xmlReader.IsStartElement() == true)
{
// Find Class tag
if (xmlReader.LocalName == "Class")
{
xElem = (XElement)XNode.ReadFrom(xmlReader);
// Get 1B2 value
HRName = xElem.FirstAttribute.Value;
// Tried to read each node in xElement to get FirstName values.. this didn't work
for ( (XNode e in (XNode)xElem)
{
string newString = ((string)xElem.Element("FirstName"));
}
}
// Also tried this before, but it is skips it since FirstName tags are inside Class tag.
if (xmlReader.LocalName == "FirstName")
{
xElem = (XElement)XNode.ReadFrom(xmlReader);
record.SetValue(0, xElem.Value);
record.SetValue(1, HRName);
rs.Insert(record);
}
}
}
And in my table (to where I am trying to write this) consists of two columns (FirstName and Class)
As mentioned in the comments, Linq to Xml is your best bet: http://msdn.microsoft.com/en-us/library/system.xml.linq(v=vs.90).aspx
Your parse would look something like:
string xml = "<root><Class value='1B2'><FirstName>Bob</FirstName><FirstName>Jim</FirstName><FirstName>Alice</FirstName><FirstName>Jessica</FirstName></Class>" +
"<Class value='2C4'><FirstName>Bob</FirstName><FirstName>Jim</FirstName><FirstName>Alice</FirstName><FirstName>Jessica</FirstName></Class></root>";
XDocument doc = XDocument.Parse(xml);
foreach(var node in doc.Descendants("Class"))
{
var cls = node.Attribute("value").Value;
string[] firstNames = node.Descendants("FirstName").Select(o => o.Value).ToArray();
}

How to write xpath for this XDocument?

<tags>
<data mode="add" name="ttt" oldindex="-1" index="-1" oldnumber="1" number="1" type="VAR_INT" value="72" />
<data mode="delete" name="test3d" oldindex="-1" index="-1" oldnumber="1" number="1" type="VAR_INT" value="72" />
</tags>
I want to check whether "mode" is present in xml or not
xdDiffData.XPathSelectElement("//tags[#mode='add']") != null && xdDiffData.XPathSelectElement("//tags[#mode='delete']") != null
This always gives false..how to do this... ?
If you want to make sure that mode attribute is present in every data element, then you should better iterate all the data elements to look for the mode attribute this way:
XDocument doc = XDocument.Load("XmlFile.xml");
var nodes = doc.Descendants("data");
foreach (var node in nodes)
{
var attrMode = node.Attribute("mode");
if (attrMode == null)
{
// mode attribute is not available for this data element
}
}
Using Linq:
if (nodes.Where(c => c.Attribute("mode") == null).Count() == 0)
{
var result = nodes.All(e =>
e.Attribute("mode").Value.Equals("add") ||
e.Attribute("mode").Value.Equals("delete"));
}
else
{
// 'mode' attribute is missing for one or more 'data' element(s)
}
If result equals to true, then it means all the data elements have mode attribute either set to value "add" or "delete".
You are missing the 'data' element. Try
xdDiffData.XPathSelectElement("//tags/data[#mode='add']") != null && xdDiffData.XPathSelectElement("//tags/data[#mode='delete']") != null
xdDiffData.XPathSelectElement("/tags/data[#mode='add']") != null
I want to check whether "mode" is present in xml or not
Use:
//#mode
if this XPath expression selects a node, this means that an attribute named mode is present in the XML document.
or you could use:
boolean(//#mode)
and this produces a boolean value -- true() or false()

Converting XML nodes into attributes using C#

I have an XML string that is loaded into an XMLDocument, similar to the one listed below:
<note>
<to>You</to>
<from>Me</from>
<heading>TEST</heading>
<body>This is a test.</body>
</note>
I would like to convert the text nodes to attributes (using C#), so it looks like this:
<note to="You" from="Me" heading="TEST" body="This is a test." />
Any info would be greatly appreciated.
Linq to XML is great for this kind of stuff. You could probably achieve it in one line if you'd want to. Just grab the child node names and their respective value and add all those 'key value pairs' as attributes instead.
MSDN docs here: http://msdn.microsoft.com/en-us/library/bb387098.aspx
Like seldon suggests, LINQ to XML would be a much better fit for this sort of task.
But here's a way to do it with XmlDocument. Not claiming this is fool-proof (haven't tested it), but it does seem to work for your sample.
XmlDocument input = ...
// Create output document.
XmlDocument output = new XmlDocument();
// Create output root element: <note>...</note>
var root = output.CreateElement(input.DocumentElement.Name);
// Append attributes to the output root element
// from elements of the input document.
foreach (var child in input.DocumentElement.ChildNodes.OfType<XmlElement>())
{
var attribute = output.CreateAttribute(child.Name); // to
attribute.Value = child.InnerXml; // to = "You"
root.Attributes.Append(attribute); // <note to = "You">
}
// Make <note> the root element of the output document.
output.AppendChild(root);
Following converts any simple XML leaf node into an attribute of its parent. It is implemented as a unit test. Encapsulate your XML content into an input.xml file, and check it saved as output.xml.
using System;
using System.Xml;
using System.Linq;
using NUnit.Framework;
[TestFixture]
public class XmlConvert
{
[TestCase("input.xml", "output.xml")]
public void LeafsToAttributes(string inputxml, string outputxml)
{
var doc = new XmlDocument();
doc.Load(inputxml);
ParseLeafs(doc.DocumentElement);
doc.Save(outputxml);
}
private void ParseLeafs(XmlNode parent)
{
var children = parent.ChildNodes.Cast<XmlNode>().ToArray();
foreach (XmlNode child in children)
if (child.NodeType == XmlNodeType.Element
&& child.Attributes.Count == 0
&& child.ChildNodes.Count == 1
&& child.ChildNodes[0].NodeType == XmlNodeType.Text
&& parent.Attributes[child.Name] == null)
{
AddAttribute(parent, child.Name, child.InnerXml);
parent.RemoveChild(child);
}
else ParseLeafs(child);
// show no closing tag, if not necessary
if (parent.NodeType == XmlNodeType.Element
&& parent.ChildNodes.Count == 0)
(parent as XmlElement).IsEmpty = true;
}
private XmlAttribute AddAttribute(XmlNode node, string name, string value)
{
var attr = node.OwnerDocument.CreateAttribute(name);
attr.Value = value;
node.Attributes.Append(attr);
return attr;
}
}

Categories