Fetching XML child values in c# - c#

I have some data i need to fetch from a xml file.
<book category="cooking">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
How would I fetch the year based of the category?
So it would be like
writeline( FetchYearFromCategory("cooking") );
and it would output 2005
Hope you can understand

You can use LINQ to Xml
// Load xml in the memory
var document = XDocument.Load(pathToXmlFile);
// Filter xml to find required data
var yearsOfCooking =
document.Descendants("book")
.Where(book => book.Attribute("category").Value == "cooking")
.Select(book => book.Element("year").Value);
// Print result
foreach (var year in yersOfCooking)
{
Console.WriteLine(year);
}

The tool you are looking for is called XPath. You can define which elements are going to match using a relatively easy syntax. W3Schools has a pretty good tutorial, if you are not already familiar with it. Here is the link.
The syntax to use it would be:
// This will fetch all "book" items in the root node
// that have the category = "cooking"
// and select their year node.
var strXPathExpression = "/book[#category='cooking']/year";
var doc = new XmlDocument();
doc.Load("YourXmlFile.xml");
var root = doc.DocumentElement;
var desiredNodes = root.SelectNodes(strXPathExpression).
foreach (var node in desiredNodes)
{
// You have your year here
var year = node.Value;
}

You can use System.Xml to access XML Files in C#.NET
public int FetchYearFromCategory(string category){
XmlDocument doc = new XmlDocument();
doc.Load("path_to_your_file");
XmlNodeList allBooks = doc.GetElementsByTagName("book");
foreach (XmlNode singleBookNode in allBooks)
{
if(singleBookNode.Attributes["category"] != null && singleBookNode.Attributes["category"].Value.Equals(category))
{
XmlNodeList childNodes = singleBookNode.ChildNodes;
foreach (XmlNode childNode in childNodes)
{
if(childNode.Name.Equals("year"))
{
return Int32.Parse(childNode.InnerText);
}
}
}
}
return -1;}
I think the above should do you the job.

Related

Xml decendants for a Xml node

Here is a simplified mxl structure 'xml',
<store>
<book_1>
<author_1><name>Alice</name><age>30</age></author_1>
<author_2><name>Bob</name><age>31</age></author_2>
<book_1>
<author_1><name>Charley</name><age>29</age></author_1>
<author_2><name>Dory</name><age>25</age></author_2>
<book_1>
</store>
Here is what I tried;
XmlDocument submission = new XmlDocument();
submission.LoadXml(xml);
var bookNodes = submission.SelectNodes("//*[starts-with(local-name(),'book_')]");
This gives me a list of books.
foreach (XmlNode book in bookNodes)
{
//I want to do something like to find book authors for the book in context e.g. for the first book I just want nodes for Alice and Bob.
// var bookAuthors = book.SelectNodes("decendants::[starts-with(local-name(),'author_')");
}
How can I just do a starts with to check on decendent elements?
EDIT:
Seems like it's a typo...
var bookAuthors = book.SelectNodes("descendant::*[starts-with(local-name(),'MeritCriterion_')]");
You can access your descendant nodes by using the following XPath syntax:
XmlDocument submission = new XmlDocument();
submission.LoadXml(xml);
var bookNodes = submission.SelectNodes("//*[starts-with(local-name(),'book_')]");
foreach (XmlNode book in bookNodes)
{
var author = book.SelectNodes("descendant::*[starts-with(local-name(),'author_')]");
foreach (XmlNode authorInfo in author)
{
Console.WriteLine(authorInfo .InnerText);
}
}
In short, you need to access descendant::(all)[starts-with] or else you're just trying to access no descendant in your XPath. :)

Adding info to a xml file

I have a XML file which contains about 850 XML nodes. Like this:
<NameValueItem>
<Text>Test</Text>
<Code>Test</Code>
</NameValueItem>
........ 849 more
And I want to add a new Childnode inside each and every Node. So I end up like this:
<NameValueItem>
<Text>Test</Text>
<Code>Test</Code>
<Description>TestDescription</Description>
</NameValueItem>
........ 849 more
I've tried the following:
XmlDocument doc = new XmlDocument();
doc.Load(xmlPath);
XmlNodeList nodes = doc.GetElementsByTagName("NameValueItem");
Which gives me all of the nodes, but from here am stuck(guess I need to iterate over all of the nodes and append to each and every) Any examples?
You need something along the lines of this example below. On each of your nodes, you need to create a new element to add to it. I assume you will be getting different values for the InnerText property, but I just used your example.
foreach (var rootNode in nodes)
{
XmlElement element = doc.CreateElement("Description");
element.InnerText = "TestDescription";
root.AppendChild(element);
}
You should just be able to use a foreach loop over your XmlNodeList and insert the node into each XmlNode:
foreach(XmlNode node in nodes)
{
node.AppendChild(new XmlNode()
{
Name = "Description",
Value = [value to insert]
});
}
This can also be done with XDocument using LINQ to XML as such:
XDocument doc = XDocument.Load(xmlDoc);
var updated = doc.Elements("NameValueItem").Select(n => n.Add(new XElement() { Name = "Description", Value = [newvalue]}));
doc.ReplaceWith(updated);
If you don't want to parse XML using proper classes (i.e. XDocument), you can use Regex to find a place to insert your tag and insert it:
string s = #"<NameValueItem>
<Text>Test</Text>
<Code>Test</Code>
</NameValueItem>";
string newTag = "<Description>TestDescription</Description>";
string result = Regex.Replace(s, #"(?<=</Code>)", Environment.NewLine + newTag);
but the best solution is Linq2XML (it's much better, than simple XmlDocument, that is deprecated at now).
string s = #"<root>
<NameValueItem>
<Text>Test</Text>
<Code>Test</Code>
</NameValueItem>
<NameValueItem>
<Text>Test2</Text>
<Code>Test2</Code>
</NameValueItem>
</root>";
var doc = XDocument.Load(new StringReader(s));
var elms = doc.Descendants("NameValueItem");
foreach (var element in elms)
{
element.Add(new XElement("Description", "TestDescription"));
}
var text = new StringWriter();
doc.Save(text);
Console.WriteLine(text);

How to parse nested XML in C#

I'm working with an API and retrieving the data in XML. Here's my XML:
<RTT>
<AgencyList>
<Agency Name="Caltrain" HasDirection="True" Mode="Rail">
<RouteList>
<Route Name="BABY BULLET" Code="BABY BULLET">
<RouteDirectionList>
<RouteDirection Code="SB2" Name="SOUTHBOUND TO TAMIEN">
<StopList>
<Stop name="Sunnyvale Caltrain Station" StopCode="70222">
<DepartureTimeList/>
</Stop>
</StopList>
</RouteDirection>
<RouteDirection Code="NB" Name="NORTHBOUND TO SAN FRANCISCO">
<StopList>
<Stop name="Sunnyvale Caltrain Station" StopCode="70221">
<DepartureTimeList>
<DepartureTime>69</DepartureTime>
</DepartureTimeList>
</Stop>
</StopList>
</RouteDirection>
</RouteDirectionList>
</Route>
<Route Name="LIMITED" Code="LIMITED">...</Route>
<Route Name="LOCAL" Code="LOCAL">...</Route>
</RouteList>
</Agency>
</AgencyList>
</RTT>
Not every DepartureTimeList will have a DepartureTime child node. Here's what I got so far, which retrieves the Route name:
List<string> trainType = new List<string>();
XDocument doc = XDocument.Load("http://services.my511.org/Transit2.0/GetNextDeparturesByStopName.aspx?token=0f01ac4a-bc16-46a5-8527-5abc79fee435&agencyName=Caltrain&stopName=" + DropDownList1.SelectedItem.Text.ToString());
doc.Save("times.xml");
string feed = doc.ToString();
XmlReader r = XmlReader.Create(new StringReader(feed));
r.ReadToFollowing("RouteList");
if (r.ReadToDescendant("Route"))
{
do
{
trainType.Add(r.GetAttribute("Name"));
} while (r.ReadToNextSibling("Route"));
}
I'm mostly interested in the departure time (if it exists) and I've been struggling all afternoon to try and parse it.
Try this... Hopefully this will do it.
XmlDocument doc = new XmlDocument();
doc.Load("xml path");
XmlNode node = doc.SelectSingleNode("/RTT");
foreach (XmlNode nodes in node.SelectNodes(
"/AgencyList/Agency Name/RouteList/Route"))
{
trainType.Add(r.GetAttribute("Name"));
XmlNode s = nodes.SelectSingleNode("Route Name/RouteDirectionList/RouteDirection Code/StopList/Stop");
if (s != null && s["DepartureTimeList"].HasChildNodes)
{
// do stuff here
}
}

Parsing XmlDocument with Variable Parent Tags

Assuming I have an XmlDocument like this:
<xmlFile>
<details>
<code1>ADJ</code1>
<code2>ADC </code2>
<Shipment>
<foo></foo>
<bar></bar>
</Shipment>
<Shipment>
<foo></foo>
<bar></bar>
</Shipment>
</details>
<details>
<code1>ADJ</code1>
<code2>SCC </code2>
<Shipment>
<foo></foo>
<bar></bar>
</Shipment>
</details>
</xmlFile>
I need to process each in an xml file but only shipments that fall under the tags with a child node with a value of "ADC". So far I have:
// Assume there is an XmlDocument named xml
XmlNodeList details= xml.GetElementsByTagName("details");
foreach (XmlNode node in details)
{
if (node["code2"].InnerText == "ADC ")
{
// Get each shipment and process it accordingly.
}
}
I can't figure out what to do next. Thanks.
Assuming Data\Sample.xml contains xml as mentioned in the question,
Following is the XLINQ query
XElement root = XElement.Parse(File.ReadAllText(#"Data\Sample.xml"));
var adcShipment = root.Descendants().Where(e=>String.Equals(e.Value, "ADC "));
//next query for nodes/elements inside/next to ADC shipments
XPath can simplify your search for matches:
foreach (XmlNode node in xml.SelectNodes("/xmlFile/details[normalize-space(code2)='ADC']"))
{
string foo = node.SelectSingleNode("foo").InnerText;
string bar = node.SelectSingleNode("bar").InnerText;
}
I'm in the process of adding XPath parsing to this library: https://github.com/ChuckSavage/XmlLib/
I modified it so you can do this:
XElement root = XElement.Load(file);
var shipments = root.XPath("details[starts-with(*,'ADC')]/Shipment");
Long-hand that looks like:
var shipments = root.Elements("details")
.Where(x => x.Elements().Any(xx => ((string)xx).StartsWith("ADC")))
.Elements("Shipment");
This is the sort of thing you're after
XmlNodeList details = xml.GetElementsByTagName("details");
foreach (XmlNode node in details)
{
if (node["code2"].InnerText.Trim() == "ADC")
{
// Get each shipment and process it accordingly.
foreach(XmlNode shipment in node.SelectNodes("Shipment"))
{
var foo = shipment.SelectSingleNode("foo");
var bar = shipment.SelectSingleNode("bar");
}
}
}

How to compare two big XML files item by item efficiently?

I plan to implement an method to compare two big XML files (but less than 10,000 element lines for each of other).
The method below works, but it doesn't well when the file more than 100 lines. It begin very slowly. How Can I find a more efficient solution. Maybe need High C# programming design or better Algorithm in C# & XML handling.
Thanks for your comments in advance.
//Remove the item which not in Event Xml and ConfAddition Xml files
XmlDocument doc = new XmlDocument();
doc.Load(xmlFile_AlarmSettingUp);
bool isNewAlid_Event = false;
bool isNewAlid_ConfAddition = false;
int alid = 0;
XmlNodeList xnList = doc.SelectNodes("/Equipment/AlarmSettingUp/EnabledALIDs/ALID");
foreach (XmlNode xn in xnList)
{
XmlAttributeCollection attCol = xn.Attributes;
for (int i = 0; i < attCol.Count; ++i)
{
if (attCol[i].Name == "alid")
{
alid = int.Parse(attCol[i].Value.ToString());
break;
}
}
//alid = int.Parse(attCol[1].Value.ToString());
XmlDocument docEvent_Alarm = new XmlDocument();
docEvent_Alarm.Load(xmlFile_Event);
XmlNodeList xnListEvent_Alarm = docEvent_Alarm.SelectNodes("/Equipment/Alarms/ALID");
foreach (XmlNode xnEvent_Alarm in xnListEvent_Alarm)
{
XmlAttributeCollection attColEvent_Alarm = xnEvent_Alarm.Attributes;
int alidEvent_Alarm = int.Parse(attColEvent_Alarm[1].Value.ToString());
if (alid == alidEvent_Alarm)
{
isNewAlid_Event = false;
break;
}
else
{
isNewAlid_Event = true;
//break;
}
}
XmlDocument docConfAddition_Alarm = new XmlDocument();
docConfAddition_Alarm.Load(xmlFile_ConfAddition);
XmlNodeList xnListConfAddition_Alarm = docConfAddition_Alarm.SelectNodes("/Equipment/Alarms/ALID");
foreach (XmlNode xnConfAddition_Alarm in xnListConfAddition_Alarm)
{
XmlAttributeCollection attColConfAddition_Alarm = xnConfAddition_Alarm.Attributes;
int alidConfAddition_Alarm = int.Parse(attColConfAddition_Alarm[1].Value.ToString());
if (alid == alidConfAddition_Alarm)
{
isNewAlid_ConfAddition = false;
break;
}
else
{
isNewAlid_ConfAddition = true;
//break;
}
}
if ( isNewAlid_Event && isNewAlid_ConfAddition )
{
// Store the root node of the destination document into an XmlNode
XmlNode rootDest = doc.SelectSingleNode("/Equipment/AlarmSettingUp/EnabledALIDs");
rootDest.RemoveChild(xn);
}
}
doc.Save(xmlFile_AlarmSettingUp);
my XML file as this. The two XML files are same style. Except some time one of them may be modified by my app. That's why I need compare them if modified.
<?xml version="1.0" encoding="utf-8"?>
<Equipment xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Licence LicenseId="" LicensePath="" />
<!--Alarm Setting Up XML File-->
<AlarmSettingUp>
<EnabledALIDs>
<ALID logicalName="Misc_EV_RM_STATION_ALREADY_RESERVED" alid="536870915" alcd="7" altx="Misc_Station 1 UnitName 2 SlotId already reserved" ceon="Misc_AlarmOn_EV_RM_STATION_ALREADY_RESERVED" ceoff="Misc_AlarmOff_EV_RM_STATION_ALREADY_RESERVED" />
<ALID logicalName="Misc_EV_RM_SEQ_READ_ERROR" alid="536870916" alcd="7" altx="Misc_Sequence ID 1 d step 2 d read error for wafer in 3 UnitName 4 SlotId" ceon="Misc_AlarmOn_EV_RM_SEQ_READ_ERROR" ceoff="Misc_AlarmOff_EV_RM_SEQ_READ_ERROR" />
...
...
...
</EnabledALIDs>
</AlarmSettingUp>
</Equipment>
The "ALID/#alid" seems to be your key, so the first thing I would do (before foreach (XmlNode xn in xnList)) is build a dictionary (assuming this is unique) over the docEvent_Alarm.SelectNodes("/Equipment/Alarms/ALID") #alid values - then you can do most of the work without O(n*m) performance - it'll be more O(n+m) (which is a big difference).
var lookup = new Dictionary<string, XmlElement>();
foreach(XmlElement el in docEvent_Alarm.SelectNodes("/Equipment/Alarms/ALID")) {
lookup.Add(el.GetAttribute("alid"), el);
}
then you can use:
XmlElement other;
if(lookup.TryGetValue(otherKey, out other)) {
// exists; element now in "other"
} else {
// doesn't exist
}
XmlDocument and related classes (XmlNode, ...) are not pretty fast in xml processing. Try XmlTextReader instead.
Also you call docEvent_Alarm.Load(xmlFile_Event); and docConfAddition_Alarm.Load(xmlFile_ConfAddition); each iteration of the parental loop - it's not good. If your xmlFile_Event and xmlFile_ConfAddition are persistent during all processing - better to initialize it before the main loop.
Have you tried using Microsoft's XmlDiff class? See http://msdn.microsoft.com/en-us/library/aa302294.aspx

Categories

Resources