Parsing XML in Compact Framework 3.5 - c#

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();
}

Related

Selecting a child node having specific value

I want to check that if the "< city>" node 'having a specific value (say Pathankot )' already exist in the xml file under the a particular "< user Id="any"> having a specific Id", before inserting a new city node into the xml.
< users>
< user Id="4/28/2015 11:29:44 PM">
<city>Fazilka</city>
<city>Pathankot </city>
<city>Jalandher</city>
<city>Amritsar</city>
</user>
</users>
In order to insert I am using the Following c# code
XDocument xmlDocument = XDocument.Load(#"C:\Users\Ajax\Documents\UserSelectedCity.xml");
string usrCookieId = Request.Cookies["CookieId"].Value;
xmlDocument.Element("Users")
.Elements("user")
.Single(x => (string)x.Attribute("Id") == usrCookieId)
//Incomplete because same named cities can be entered more that once
//need to make delete function
.Add(
new XElement("city", drpWhereToGo.SelectedValue));
My Questions:
How Can i check weather the < city> node having specific value say
Pathankot already exist in the xml file Before Inserting a new city
node.
I am Using absolute Path in" XDocument xmlDocument =
XDocument.Load(#"C:\Users\Ajax\Documents\Visual Studio
2012\WebSites\punjabtourism.com\UserSelectedCity.xml");" This
does not allow me to move the files to new folder without changing
the path which is not desirable. But if i use the relative path The
Error Occures "Access Denied";
I would use this simple approach:
var query =
xmlDocument
.Root
.Elements("user")
.Where(x => x.Attribute("Id").Value == usrCookieId)
.Where(x => !x.Elements("city").Any(y => y.Value == "Pathankot"));
foreach (var xe in query)
{
xe.Add(new XElement("city", drpWhereToGo.SelectedValue));
}
It's best to avoid using .Single(...) or .First(...) if possible. The description of your problem doesn't sound like you need to use these though.
Try this:-
First load the XML file into XDocument object by specifying the physical path where your XML file is present. Once you have the object just take the First node with matching condition (please note I am using First instead of Single cz you may have multiple nodes with same matching condition, Please see the Difference between Single & First)
XDocument xmlDocument = XDocument.Load(#"YourPhysicalPath");
xmlDocument.Descendants("user").First(x => (string)x.Attribute("Id") == "1"
&& x.Elements("city").Any(z => z.Value.Trim() == "Pathankot"))
.Add(new XElement("city", drpWhereToGo.SelectedValue));
xmlDocument.Save("YourPhysicalPath");
Finally add the required city to the node retrieved from the query and save the XDocument object.
Update:
If you want to check first if all the criteria fulfills then simply use Any like this:-
bool isCityPresent = xdoc.Descendants("user").Any(x => (string)x.Attribute("Id") == "1"
&& x.Elements("city").Any(z => z.Value.Trim() == "Pathankot"));
I'd create an extension to clean it up a bit, and use XPath for the search.
public static class MyXDocumentExtensions
{
public static bool CityExists(this XDocument doc, string cityName)
{
//Contains
//var matchingElements = doc.XPathSelectElements(string.Format("//city[contains(text(), '{0}')]", cityName));
//Equals
var matchingElements = doc.XPathSelectElements(string.Format("//city[text() = '{0}']", cityName));
return matchingElements.Count() > 0;
}
}
And call it like that:
XDocument xmlDocument = XDocument.Load("xml.txt");
var exists = xmlDocument.CityExists("Amritsar");
Expanding on your question in the comment, you can then use it as:
if(!xmlDocument.CityExists("Amritsar"))
{
//insert city
}
If you would like to match regardless of the trailing whitespace in the XML, you can wrap the text() call in XPath with a normalize-space:
var matchingElements = doc.XPathSelectElements(string.Format("//city[normalize-space(text()) = '{0}']", cityName.Trim()));

Extracting XML inner node elements in C#

I have an XML document that looks like this:
<root>
<key>
<id>v1</id>
<val>v2</val>
<iv>v3</iv>
</key>
</root>
How do I extract the v2 values and v3 values of a key node using its v1 value in C#?
Use Linq.
var myXml = XDocument.Parse("<root>
<key>
<id>v1</id>
<val>v2</val>
<iv>v3</iv>
</key>
</root>").Root.Elements("key")
.FirstOrDefault(x=> x.Element("id").Value == value);
if (myXml != null)
{
var myObject = new
{
id = myXml.Element("id").Value,
val = myXml.Element("val").Value,
iv = myXml.Element("iv").Value
});
}
Of course, you need to check for missing elements, etc, if required.
Use xpath:
/root/key[id='v1']/val
/root/key[id='v1']/iv
so something like
myXmlDoc.SelectSingleNode("/root/key[id='v1']/val").Value
myXmlDoc.SelectSingleNode("/root/key[id='v1']/iv").Value
I like using LINQ to XML for processing XML:
var xml = XElement.Parse(#"<root>
<key>
<id>v1</id>
<val>v2</val>
<iv>v3</iv>
</key>
</root>");
var key = xml.Elements("key").First(x => x.Element("id").Value == "v1");
Console.WriteLine("val: " + key.Element("val").Value);
Console.WriteLine(" iv: " + key.Element("iv").Value);
I have ignored all error checking for brevity.
For example First() would throw an exception if the element is not found. You might want to use FirstOrDefault() and check for null if you are expecting that or handle edge cases a bit more gracefully.
Same goes for Element() calls. They might return null so calling .Value could result in a System.NullReferenceException. To avoid clutter I usually use extension methods to do these checks:
static class XElementUtilities
{
public static string GetValue(this XElement xml, string name)
{
var element = xml.Element(name);
return element == null ? null : element.Value;
}
public static bool ValueEqual(this XElement xml, string name, string value)
{
var element = xml.Element(name);
return element != null && value != null && element.Value == value;
}
}

Querying XML and updating certain elements

I have an XML file in the following format
<?xml version="1.0" ?>
<AA someattrib="xyz">
<BB someOtherAttrib="xyz">
<Title></Title>
<CC>
<myNode rowid="">
<subNode1></subNode1>
<subNode2></subNode2>
<nodeOfInterest></nodeOfInterest>
</myNode >
<myNode rowid="">
<subNode1> </subNode1>
</myNode>
</CC>
</BB>
</AA>
I want to use Linq to pick out one node by the name 'MyNode' where the rowid is a particular number that I will be getting from a collection in an object. Once I get myNode I want to update the value of the child nodeOfInterest if it is present. If not present, then I would like to add it. Once done I want to save the file.
This is what I have at the moment but it may not be the right approach.
foreach (User employee in Users)
{
XPathNavigator node = xNav.SelectSingleNode("/AA/BB/CC/myNode[#rowid = '"+employee.ID.ToString()+"']");
XPathNodeIterator nodeIterator= node.SelectChildren("nodeOfInterest", "");
if (nodeIterator.Count == 1)
{
}
else
{
}
}
Is there a way this can be done using a direct join between the List and the xmldoc in memory? This will be a large list and an equally large xml file. I dont think running a loop and calling selectSingleNode is the most efficient way.
Thanks for your inputs
Well one starting point would be to create a Dictionary<string, XElement> mapping the row ID to the element:
var dictionary = doc.Element("AA").Element("BB").Element("CC").Elements("myNode")
.ToDictionary(x => x.Attribute("rowId").Value);
Then:
foreach (User employee in Users)
{
XElement myNode;
if (dictionary.TryGetValue(employee.ID, out myNode))
{
// Use myNode
}
else
{
// Employee not found
}
}
Personally I prefer using the selection methods provided by LINQ to XML (Elements, Element, Descendants etc) rather than SelectSingleNode, SelectChildren etc.
The full answer, with help from Jon's replies...
var doc = XDocument.Load("thefile.xml");
var dictionary = doc.Element("AA").Element("BB").Element("CC").Elements("myNode")
.ToDictionary(x => x.Attribute("rowId").Value);
foreach (User employee in Users)
{
XElement myNode;
if (dictionary.TryGetValue(employee.ID, out myNode))
{
XElement nodeOfInterest = myNode.Elements("nodeOfInterest").FirstOrDefault();
if (nodeOfInterest != null)
{
nodeOfInterest.Value = "update with this value";
}
else
{
XElement nodeOfInterest = new XElement("nodeOfInterest", "Add nodeOfInterest with this value");
myNode.Add(newElement);
}
}
}
doc.Save("TheFile.xml");

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;
}
}

How to select XML node by attribute and use it's child nodes data?

Here is my XML file
<?xml version="1.0" encoding="utf-8" ?>
<storage>
<Save Name ="Lifeline">
<Seconds>12</Seconds>
<Minutes>24</Minutes>
<Hours>9</Hours>
<Days>25</Days>
<Months>8</Months>
<Years>2010</Years>
<Health>90</Health>
<Mood>100</Mood>
</Save>
<Save Name ="Hellcode">
<Seconds>24</Seconds>
<Minutes>48</Minutes>
<Hours>18</Hours>
<Days>15</Days>
<Months>4</Months>
<Years>1995</Years>
<Health>50</Health>
<Mood>50</Mood>
</Save>
Here is a code which get's data from XML and loads it into application.
System.IO.StreamReader sr = new System.IO.StreamReader(#"Saves.xml");
System.Xml.XmlTextReader xr = new System.Xml.XmlTextReader(sr);
System.Xml.XmlDocument save = new System.Xml.XmlDocument();
save.Load(xr);
XmlNodeList saveItems = save.SelectNodes("Storage/Save");
XmlNode seconds = saveItems.Item(0).SelectSingleNode("Seconds");
sec = Int32.Parse(seconds.InnerText);
XmlNode minutes = saveItems.Item(0).SelectSingleNode("Minutes");
min = Int32.Parse(minutes.InnerText);
XmlNode hours = saveItems.Item(0).SelectSingleNode("Hours");
hour = Int32.Parse(hours.InnerText);
XmlNode days = saveItems.Item(0).SelectSingleNode("Days");
day = Int32.Parse(days.InnerText);
XmlNode months = saveItems.Item(0).SelectSingleNode("Months");
month = Int32.Parse(months.InnerText);
XmlNode years = saveItems.Item(0).SelectSingleNode("Years");
year = Int32.Parse(years.InnerText);
XmlNode health_ = saveItems.Item(0).SelectSingleNode("Health");
health = Int32.Parse(health_.InnerText);
XmlNode mood_ = saveItems.Item(0).SelectSingleNode("Mood");
mood = Int32.Parse(mood_.InnerText);
The problem is that this code loads data inly from "Lifeline" node. I would like to use a listbox and be able to choose from which node to load data.
I've tried to take string from listbox item content and then use such a line
XmlNodeList saveItems = save.SelectNodes(string.Format("storage/Save[#Name = '{0}']", name));
variable "name" is a string from listboxe's item. While compiled this code gives exception.
Do somebody knows a way how to select by attribute and load nedeed data from that XML?
If you can use XElement:
XElement xml = XElement.Load(file);
XElement storage = xml.Element("storage");
XElement save = storage.Elements("Save").FirstOrDefault(e => ((string)e.Attribute("Name")) == nameWeWant);
if(null != save)
{
// do something with it
}
Personally I like classes that have properties that convert to and from the XElement to hide that detail from the main program. IE say the Save class takes an XElement node in the constructor, saves it internally globally, and the properties read/write to it.
Example class:
public class MyClass
{
XElement self;
public MyClass(XElement self)
{
this.self = self;
}
public string Name
{
get { return (string)(self.Attribute("Name") ?? "some default value/null"); }
set
{
XAttribute x = source.Attribute("Name");
if(null == x)
source.Add(new XAttribute("Name", value));
else
x.ReplaceWith(new XAttribute("Name", value));
}
}
}
Then you can change the search to something like:
XElement save = storage.Elements("Save")
.FirstOrDefault(e => new MyClass(e).Name == NameWeWant);
Since it is not that much data, I'd suggest loading all information to a list of saves(constructor) and then drawing from there which one the user would like to use...
As for things not working, I personally use a lower level approach to get my data and it is not error prone. Remodeling it to fit your problem a bit:
int saves = 0;
List<Saves> saveGames = new List<Saves>();
saveGames.Add(new Saves());
while (textReader.Read())
{
if (textReader.NodeType == XmlNodeType.Element)
whatsNext = textReader.Name;
else if (textReader.NodeType == XmlNodeType.Text)
{
if (whatsNext == "name")
saveGames[saves].name = Convert.ToString(textReader.Value);
//else if statements for the rest of your attributes
else if (whatsNext == "Save")
{
saveGames.Add(new Saves());
saves++;
}
}
else if (textReader.NodeType == XmlNodeType.EndElement)
whatsNext = "";
}
Basically throw everything in the xml file into a list of objects and manipulate that list to fill the listbox. Instead of having Saves name = "...", have a name attribute as the first attribute in the save.
Code tags hate me. Why they break so easily ( ._.)
The select nodes is returning two XmlNode objects.
XmlNodeList saveItems = save.SelectNodes("Storage/Save");
Later in your code you seem to be selecting the first one and with saveItems.Item(0) and getting values from it which in this case would be the save node with the Name="LifeLine". So if you were to do saveItems.Item(1) and select nodes and its values then you would get the other set of nodes.

Categories