Reading multiple child nodes of xml file - c#

I have created an Xml file with example contents as follows:
<?xml version="1.0" encoding="utf-8" ?>
<Periods>
<PeriodGroup name="HER">
<Period>
<PeriodName>Prehistoric</PeriodName>
<StartDate>-500000</StartDate>
<EndDate>43</EndDate>
</Period>
<Period>
<PeriodName>Iron Age</PeriodName>
<StartDate>-800</StartDate>
<EndDate>43</EndDate>
</Period>
<Period>
<PeriodName>Roman</PeriodName>
<StartDate>43</StartDate>
<EndDate>410</EndDate>
</Period>
</PeriodGroup>
<PeriodGroup name="CAFG">
<Period>
<PeriodName>Prehistoric</PeriodName>
<StartDate>-500000</StartDate>
<EndDate>43</EndDate>
</Period>
<Period>
<PeriodName>Roman</PeriodName>
<StartDate>43</StartDate>
<EndDate>410</EndDate>
</Period>
<Period>
<PeriodName>Anglo-Saxon</PeriodName>
<StartDate>410</StartDate>
<EndDate>800</EndDate>
</Period>
</PeriodGroup>
</Periods>
I need to be able to read the Period node children within a selected PeriodGroup. I guess the PeriodName could be an attribute of Period if that is more sensible.
I have looked at loads of examples but none seem to be quite right and there seems to be dozens of different methods, some using XmlReader, some XmlTextReader and some not using either. As this is my first time reading an Xml file, I thought I'd ask if anyone could give me a pointer. I've got something working just to try things out, but it feels clunky. I'm using VS2010 and c#. Also, I see a lot of people are using LINQ-Xml, so I'd appreciate the pros and cons of using this method.
string PG = "HER";
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("./Xml/XmlFile.xml"));
string text = string.Empty;
XmlNodeList xnl = doc.SelectNodes("/Periods/PeriodGroup");
foreach (XmlNode node in xnl)
{
text = node.Attributes["name"].InnerText;
if (text == PG)
{
XmlNodeList xnl2 = doc.SelectNodes("/Periods/PeriodGroup/Period");
foreach (XmlNode node2 in xnl2)
{
text = text + "<br>" + node2["PeriodName"].InnerText;
text = text + "<br>" + node2["StartDate"].InnerText;
text = text + "<br>" + node2["EndDate"].InnerText;
}
}
Response.Write(text);
}

You could use an XPath approach like so:
XmlNodeList xnl = doc.SelectNodes(string.Format("/Periods/PeriodGroup[#name='{0}']/Period", PG));
Though prefer LINQ to XML for it's readability.
This will return Period node children based on the PeriodGroup name attribute supplied, e.g. HER:
XDocument xml = XDocument.Load(HttpContext.Current.Server.MapPath(FileLoc));
var nodes = (from n in xml.Descendants("Periods")
where n.Element("PeriodGroup").Attribute("name").Value == "HER"
select n.Element("PeriodGroup").Descendants().Elements()).ToList();
Results:
<PeriodName>Prehistoric</PeriodName>
<StartDate>-500000</StartDate>
<EndDate>43</EndDate>
<PeriodName>Iron Age</PeriodName>
<StartDate>-800</StartDate>
<EndDate>43</EndDate>
<PeriodName>Roman</PeriodName>
<StartDate>43</StartDate>
<EndDate>410</EndDate>
The query is pretty straightforward
from n in xml.Descendants("Periods")
Will return a collection of the descendant elements for the element Periods.
We then use where to filter this collection of nodes based on attribute value:
where n.Element("PeriodGroup").Attribute("name").Value == "HER"
Will then filter down the collection to PeriodGroup elements that have a name attribute with a value of HER
Finally, we select the PeriodGroup element and get it's descendant nodes
select n.Element("PeriodGroup").Descendants().Elements()
EDIT (See comments)
Since the result of this expression is just a query, we use .ToList() to enumerate the collection and return an object containing the values you need. You could also create anonymous types to store the element values for example:
var nodes = (from n in xml.Descendants("Period").
Where(r => r.Parent.Attribute("name").Value == "HER")
select new
{
PeriodName = (string)n.Element("PeriodName").Value,
StartDate = (string)n.Element("StartDate").Value,
EndDate = (string)n.Element("EndDate").Value
}).ToList();
//Crude demonstration of how you can reference each specific element in the result
//I would recommend using a stringbuilder here..
foreach (var n in nodes)
{
text += "<br>" + n.PeriodName;
text += "<br>" + n.StartDate;
text += "<br>" + n.EndDate;
}
This is what the nodes object will look like after the query has run:

Since the XmlDocument.SelectNodes method actually accepts an XPath expression, you're free to go like this:
XmlNodeList xnl = doc.SelectNodes("/Periods/PeriodGroup[#name='" + PG + "']/Period");
foreach (XmlNode node in xnl) {
// Every node here is a <Period> child of the relevant <PeriodGroup>.
}
You can learn more on XPath at w3schools.

go thru this
public static void XMLNodeCheck(XmlNode xmlNode)
{
if (xmlNode.HasChildNodes)
{
foreach (XmlNode node in xmlNode)
{
if (node.HasChildNodes)
{
Console.WriteLine(node.Name);
if (node.Attributes.Count!=0)
{
foreach (XmlAttribute att in node.Attributes)
{
Console.WriteLine("----------" + att.Name + "----------" + att.Value);
}
}
XMLNodeCheck(node);//recursive function
}
else
{
if (!node.Equals(XmlNodeType.Element))
{
Console.WriteLine(node.InnerText);
}
}
}
}
}

Related

XElement ReplaceWith

I would like to replace a specific node with string.
It successfully replace the node but instead of "<div>", it appear "<div>"
What should I do to make it into "<div>"?
I have tried XElement.Parse but it will give me error as I replace node with "</div><div>"
foreach (var node in Nodes)
{
var newElement = XElement.Parse("</div><div>");
node.ReplaceWith(sbb.ToString);
}
Use the following
XElement foo = XElement.Parse("<div>example</div>");
foo.ReplaceNodes(XElement.Parse("<div>" + otherMarkUp + "</div>").Nodes());
Based on your code, you should replace with the newElement.
foreach (var node in Nodes)
{
var newElement = XElement.Parse("<div></div>");
node.ReplaceWith(newElement);
}
If the above does not work, please share some more code to work on it.

XDocument, it says that a node is text but it is an element

I am reading an XML that contains a tag like this:
<source><bpt id="1"><donottranslate></bpt><ph id="2">($ T_353_1 Parent ID $)</ph><ept id="1"></donottranslate></ept></source>
When reading source node I get that this node type is Text, but it should be Element.
This is an XML that I am receiving and I cannot change it.
Do you know how can I get this sorted out?
This is my code:
XDocument doc = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
foreach (var elUnit in doc.Descendants("trans-unit"))
{
if (elUnit.AttributeString("translate").ToString() == "no")
{
foreach (var elSource in elUnit.Elements("source"))
{
string text = "";
foreach (var node in elSource.DescendantNodes().Where(n => XmlNodeType.Text == n.NodeType).ToList())
{
//When reading that "source" node, it enters inside this code
Thanks
First check whether your XML is wellformed
http://www.w3schools.com/xml/xml_validator.asp
http://chris.photobooks.com/xml/default.htm
I could get this to work
//using System.Xml.Linq;
var str = "<source><bpt id=\"1\"><donottranslate></bpt>" +
"<ph id=\"2\">($ T_353_1 Parent ID $)</ph>" +
"<ept id=\"1\"></donottranslate></ept></source>";
XElement element = XElement.Parse(str);
Console.WriteLine(element);
The output is this
<source>
<bpt id="1"><donottranslate></bpt>
<ph id="2">($ T_353_1 Parent ID $)</ph>
<ept id="1"></donottranslate></ept>
</source>
Please provide some code sample for more help if this example if not suffient.
Finally, I solved this checking if the node is correct or not:
if (System.Security.SecurityElement.IsValidText(text.XmlDecodeEntities()))

How to iterate a xml file with XmlReader class

my xml stored in xml file which look like as below
<?xml version="1.0" encoding="utf-8"?>
<metroStyleManager>
<Style>Blue</Style>
<Theme>Dark</Theme>
<Owner>CSRAssistant.Form1, Text: CSR Assistant</Owner>
<Site>System.ComponentModel.Container+Site</Site>
<Container>System.ComponentModel.Container</Container>
</metroStyleManager>
this way i am iterating but some glitch is there
XmlReader rdr = XmlReader.Create(System.IO.Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath) + #"\Products.xml");
while (rdr.Read())
{
if (rdr.NodeType == XmlNodeType.Element)
{
string xx1= rdr.LocalName;
string xx = rdr.Value;
}
}
it is always getting empty string xx = rdr.Value;
when element is style then value should be Blue as in the file but i am getting always empty....can u say why?
another requirement is i want to iterate always within <metroStyleManager></metroStyleManager>
can anyone help for the above two points. thanks
Blue is the value of Text node, not of Element node. You either need to add another if to get value of text nodes, or you can read inner xml of current element node:
rdr.MoveToContent();
while (rdr.Read())
{
if (rdr.NodeType == XmlNodeType.Element)
{
string name = rdr.LocalName;
string value = rdr.ReadInnerXml();
}
}
You can also use Linq to Xml to get names and values of root children:
var xdoc = XDocument.Load(path_to_xml);
var query = from e in xdoc.Root.Elements()
select new {
e.Name.LocalName,
Value = (string)e
};
You can use the XmlDocument class for this.
XmlDocument doc = new XmlDocument.Load(filename);
foreach (XmlNode node in doc.ChildNodes)
{
if (node.ElementName == "metroStyleManager")
{
foreach (XmlNode subNode in node.ChildNodes)
{
string key = subNode.LocalName; // Style, Theme, etc.
string value = subNode.Value; // Blue, Dark, etc.
}
}
else
{
...
}
}
you can user XDocument xDoc = XDocument.Load(strFilePath) to load XML file.
then you can use
foreach (XElement xeNode in xDoc.Element("metroStyleManager").Elements())
{
//Check if node exist
if (!xeNode.Elements("Style").Any()
//If yes then
xeNode.Value
}
Hope it Helps...
BTW, its from System.XML.Linq.XDocument

Query to remove last result using LINQ to XML

I am having trouble parsing an xml file . A sample is below.
<G_LOG>
<LINE>9206</LINE>
<TEXT>Generating
</TEXT>
</G_LOG>
<G_LOG>
<LINE>9207</LINE>
<TEXT>Inserted Actual
</TEXT>
O.K , so this is just a snapshot of thousands of nodes in the file. I need to search for the TEXT "Inserted Actual" and not only remove this node , but the previous node as well. So it would find the text on line 9207 and remove 9206 as well. (removing everything in the above snippet)
I can search for the lines I want to remove .
XDocument xmlDoc = XDocument.Load(filename);
var q = from c in xmlDoc.Descendants("G_LOG")
where c.Element("TEXT").Value.Contains("Inserted Actual")
select (string)c.Element("LINE");
foreach (string name in q)
{
Console.WriteLine("Actuals Success on ID : " + name);
}
But I am unsure of how to obtain the previous node and remove it as well (without buckets of code)?.
var elementsToRemove =
from logElement in xml.Descendants("G_LOG")
where logElement.Element("TEXT").Value.Contains("Inserted Actual")
from element in new[] { logElement, logElement.PreviousNode }
select element;
foreach(var element in elementsToRemove.ToList())
{
element.Remove();
}
A couple of things to note:
The second from flattens out each node and its previous node into one sequence
The .ToList() eagerly evaluates the query, ensuring we don't remove a node while evaluating
XElement xmlDoc = XElement.Load("c:\\temp.xml");
var q = xmlDoc.Descendants("G_LOG").Where(c=>c.Element("TEXT").Value.Contains("Inserted Actual")).Select(d=>d.Element("LINE"));
foreach(XElement elm in q)
{
if(elm.Parent.ElementsBeforeSelf().Count()!=0)
elm.Parent.PreviousNode.Remove();
elm.Parent.RemoveNodes();
}

how to read value from XML?

The data:
<sys>
<id>SCPUCLK</id>
<label>CPU Clock</label>
<value>2930</value>
</sys>
<sys>
<id>CPUTEMP</id>
<label>CPU Temp</label>
<value>39</value>
</sys>
This is the code that I'm using to read the data:
XmlDocument document = new XmlDocument();
document.LoadXml(data);
XmlElement node = document.SelectSingleNode("/sys/value") as XmlElement;
Console.WriteLine("node = " + node);
The issue: Console.WriteLine("node = " + node); doesn't give me any output besides node: but no actual value like 2930 from the sample above.
Thanks
use node.value ie., XmlElement.value
As an alternative to using XmlDocument, you can also use LINQ to XML (which is my preference):
using System.Xml.Linq;
XDocument xDoc = new XDocument();
// Parse loads the XDocument with XML from a string
xDoc = XDocument.Parse(data);
string node = (from x in xDoc.Root.Elements("value")
select x.Value).SingleOrDefault();
Console.WriteLine("node = " + node);
Nothing wrong with using XmlDocument, especially for what you're doing, but you might want to check out LINQ to XML when you get a chance, as I find it a lot easier to work with than XmlDocument.
If you want to get all the "value" elements, simply remove the SingleOrDefault() from the query, and then you can loop through the result, like this:
var nodes = from x in xDoc.Root.Elements("value")
select x.Value;
foreach (var node in nodes)
{
Console.WriteLine("node = " + node);
}
Here's a site worth checking out:
LINQ to XML - 5 Minute Overview

Categories

Resources