Xml reader skipping values - c#

I have the following XML snippet-
-<Row>
<RowType Id="1"Label="Scotland">1985</RowType>
<Year Id="11"Label="1994"/>
<Value Id="123">18</Value>
<Field Id="123"Label="Country">16</Field>
<Field Id="123"Label="Soccer">Yes</Field>
</Row>
-<Row>
<RowType Id="1"Label="England">1986</RowType>
<Year Id="11"Label="1994"/>
<Value Id="123">19</Value>
<Field Id="123"Label="Country">16</Field>
<Field Id="123"Label="Soccer">Yes</Field>
</Row>
-<Row>
<RowType Id="1"Label="Wales">1987</RowType>
<Year Id="11"Label="1994"/>
<Value Id="123">20</Value>
<Field Id="123"Label="Country">16</Field>
<Field Id="123"Label="Soccer">Yes</Field>
</Row>
I am using XmlReader to retrieve specific data from it like so -
using (XmlReader reader = XmlReader.Create(new StringReader(xml)))
{
string country = "";
string Year = "";
string count = "";
string tss= "";
string tss2 = "";
reader.MoveToContent();
while (reader.Read())
{
reader.ReadToFollowing("RowType");
country = reader.GetAttribute("Label");
country = country.Replace("'", "");
reader.ReadToFollowing("Year");
Year = reader.GetAttribute("Label");
reader.ReadToFollowing("Value");
count = reader.ReadElementContentAsString();
reader.ReadToFollowing("Field");
tss = reader.GetAttribute("Label");
reader.ReadToFollowing("Field");
tss2 = reader.GetAttribute("Label");
}
}
This is working fine for the first iteration, however on the second, it retrieves the values from the third row in the XML, and continues to skip to the next row after the one it should be parsing.
How can I resolve this?

Actually, your code is right; what is not right is the structure of the document. Or better, your code does not account for the specific structure of the document.
You can change that by adding the following bit:
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
using (XmlReader reader = XmlReader.Create(new StringReader(xml), settings))
By default the XMLReader expects ConformanceLevel.Document and thus the file should have a structure like the following one:
<main>
<Row id="5">
<RowType Id="1" Label="Scotland">1985</RowType>
<Year Id="11" Label="1994"/>
<Value Id="123">18</Value>
<Field Id="123" Label="Country">16</Field>
<Field Id="123" Label="Soccer">Yes</Field>
</Row>
<Row id="1">
<RowType Id="1" Label="England">1986</RowType>
<Year Id="11" Label="1994"/>
<Value Id="123">19</Value>
<Field Id="123" Label="Country">16</Field>
<Field Id="123" Label="Soccer">Yes</Field>
</Row>
<Row id="4">
<RowType Id="1" Label="Wales">1987</RowType>
<Year Id="11" Label="1994"/>
<Value Id="123">20</Value>
<Field Id="123" Label="Country">16</Field>
<Field Id="123" Label="Soccer">Yes</Field>
</Row>
</main>
I understand that the lack of separation between elements (e.g., Id="1"Label="Scotland" instead of Id="1" Label="Scotland") is a typo because separations have to exist in any case.
------------------- UPDATE
You report that your code does not deliver the expected result even after changing the conformance level. I have done a new test of your code and it works fine; at least, it iterates correctly. Thus, what I understand is that you want to retrieve different values than what you code does (it mixes app names, attributes and content).
Below you can see my own code (although I insist that yours iterates through the given information OK, too), which is more adaptable than yours; I am also including some comments in the parts where I think that you want to retrieve different information than what your code does. The basic idea is just retrieving information from the content (content), but your code takes it from anywhere.
string path = #"XML file";
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
using (XmlReader reader = XmlReader.Create(path, settings))
{
string country = "";
string Year = "";
string count = "";
string tss = "";
string tss2 = "";
while (reader.ReadToFollowing("Row"))
{
XmlReader reader2 = reader.ReadSubtree();
while (reader2.Read())
{
if (reader2.NodeType == XmlNodeType.Element)
{
if (reader2.Name == "RowType")
{
country = reader2.GetAttribute("Label");
country = country.Replace("'", ""); //country_year = reader.ReadElementContentAsString(); -> "Scotland" -> 1985
}
else if (reader2.Name == "Year")
{
//IF XML IS -> <Year Id="11">1994<Year/>
//Then -> Year = reader2.GetAttribute("Label")
Year = reader2.GetAttribute("Label"); //-> 1994
}
else if (reader2.Name == "Value")
{
count = reader2.ReadElementContentAsString();
}
else if (reader2.Name == "Field")
{
if (reader2.GetAttribute("Label") == "Country")
{
tss = reader2.ReadElementContentAsString(); //I understand that this is what you want to read, instead the Label name
}
else if (reader2.GetAttribute("Label") == "Soccer")
{
tss2 = reader2.ReadElementContentAsString();//I understand that this is what you want to read, instead the Label name
}
}
}
}
}
}
This should deliver what you are looking for; or, in the worst scenario, a much clear idea about how to deal with the XML reading. Also it might be a good thing to include a try...catch just in case; note that any error while reading/dealing with the variables would provoke the reading process to be immediately stopped.

We can use LINQ to get this done if you want.
If you really want to read all the values from Xml into some variable....you can try something in similar lines...
XElement po = XElement.Load(#"SoccerCup.xml");
IEnumerable<XElement> childElements =
from el in po.Elements()
select el;
foreach (XElement el in childElements)
{
var Year=el.Element("Year").Value;
var country = el.Element("country").Value;
var count =el.Elemet("Value").Value;
Console.WriteLine("Year: " + Year);
Console.WriteLine("Country: " + country);
Console.WriteLine("Count: " + count);
}
Hope this helps...

Related

Get last child element using XmlReader

Say I have this XML:
<fields>
<field fieldid="fdtElem3Group">
<value actionid="1" actiontype="review">123456789</value>
<value actionid="2" actiontype="review">123456789</value>
<value actionid="3" actiontype="review">123456789</value>
<value actionid="4" actiontype="review">123456789</value>
<value actionid="5" actiontype="review">123456789</value>
</field>
<field fieldid="fdtElem7Group">
<value actionid="1" actiontype="review">29/10/75</value>
<value actionid="2" actiontype="review">29/10/74</value>
<value actionid="3" actiontype="review">29/10/74</value>
<value actionid="4" actiontype="review">29/10/76</value>
<value actionid="5" actiontype="review">29/10/74</value>
</field>
</fields>
I'm trying to get the value of the last 'value' element of each respective 'field' element using XmlReader. How would I do that please? This doesn't work:
while (xmlReader.Read())
{
if ((xmlReader.NodeType == System.Xml.XmlNodeType.Element) && (xmlReader.Name == "field"))
{
xmlReader.ReadToDescendant("value");
while (xmlReader.ReadToNextSibling("value"))
{
//just iterate over until it reaches the end
}
xmlReader.Read();
Console.WriteLine(xmlReader.Value);
}
}
Sorry, just read now you're looking for an xmReader solution. But using XDocument and Linq, you could do the following:
string xml = #"<fields>
<field fieldid=""fdtElem3Group"">
<value actionid=""1"" actiontype=""review"">123456789</value>
<value actionid=""2"" actiontype=""review"">123456789</value>
<value actionid=""3"" actiontype=""review"">123456789</value>
<value actionid=""4"" actiontype=""review"">123456789</value>
<value actionid=""5"" actiontype=""review"">123456789</value>
</field>
<field fieldid=""fdtElem7Group"">
<value actionid=""1"" actiontype=""review"">29/10/75</value>
<value actionid=""2"" actiontype=""review"">29/10/74</value>
<value actionid=""3"" actiontype=""review"">29/10/74</value>
<value actionid=""4"" actiontype=""review"">29/10/76</value>
<value actionid=""5"" actiontype=""review"">29/10/74</value>
</field>
</fields>";
var xmlDoc = XDocument.Parse(xml).Root;
var lastElements = xmlDoc.Descendants("field").Select(x => x.LastNode);
Hope this helps !
static void Main(string[] args)
{
string xml = #"<fields><field fieldid='fdtElem3Group'><value actionid='1' actiontype='review'>123456789</value><value actionid='2' actiontype='review'>123456789</value><value actionid='3' actiontype='review'>123456789</value><value actionid='4' actiontype='review'>123456789</value><value actionid='5' actiontype='review'>123456789</value></field><field fieldid='fdtElem7Group'><value actionid='1' actiontype='review'>29/10/75</value> <value actionid='2' actiontype='review'>29/10/74</value><value actionid='3' actiontype='review'>29/10/74</value><value actionid='4' actiontype='review'>29/10/76</value><value actionid='5' actiontype='review'>29/10/74</value></field></fields>";
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
foreach (XmlNode item in xmlDocument.DocumentElement.ChildNodes)
{
Console.WriteLine(item.LastChild.InnerXml);
}
Console.ReadKey();
}
If you don't want to load your entire XML into an XDocument (because, e.g., it is very large), you can adopt a hybrid approach where you use an XmlReader to iterate through the <field> and <value> elements in your XML file, load each <value> element into an XElement, then select the last one of each parent <field>. This keeps the memory footprint small and constant no matter how large the XML file grows.
First introduce the following two extension methods:
public static class XmlReaderExtensions
{
public static IEnumerable<IEnumerable<XElement>> ReadNestedElements(this XmlReader xmlReader, string outerName, string innerName)
{
while (!xmlReader.EOF)
{
if (xmlReader.NodeType == System.Xml.XmlNodeType.Element && xmlReader.Name == outerName)
{
using (var subReader = xmlReader.ReadSubtree())
{
yield return subReader.ReadElements(innerName);
}
}
xmlReader.Read();
}
}
public static IEnumerable<XElement> ReadElements(this XmlReader xmlReader, string name)
{
while (!xmlReader.EOF)
{
if (xmlReader.NodeType == System.Xml.XmlNodeType.Element && xmlReader.Name == name)
{
var element = (XElement)XNode.ReadFrom(xmlReader);
yield return element;
}
else
{
xmlReader.Read();
}
}
}
}
Then your algorithm to get the last 'value' element of each respective 'field' element becomes very simple:
public static List<string> LastFieldValues(XmlReader reader)
{
var query = reader.ReadNestedElements("field", "value")
.Select(l => l.LastOrDefault())
.Where(v => v != null)
.Select(v => (string)v);
return query.ToList();
}
Notes:
XmlReaderExtensions.ReadNestedElements() returns an enumerable of enumerables of <value> elements belonging to <field> elements. Enumerable.LastOrDefault() is then used to select the last <value> belonging to each <field>.
Whenever working directly with XmlReader, be sure to test XML both with and without indentation, for reasons explained here and here.
XmlReader.ReadSubtree() leaves the XmlReader positioned on the EndElement node of the element being read, while XNode.ReadFrom() leaves the reader positioned immediately after the EndElement node of the element being read. Just an annoying inconsistency to watch out for.
On the other hand, if you are willing to load the entire XML into memory as an XDocument, this can be done very simply using XPathSelectElements():
// Parse the XML into an XDocument.
// You can use use XDocument.Load(fileName) to load from a file
var doc = XDocument.Parse(xmlString);
var xpathLastValues = doc
.XPathSelectElements(#"//fields/field/value[last()]")
.Select(e => e.Value)
.ToList();
Sample fiddle.

Editing xml on live in C#. Deleting nodes that contain specific value

I have an xml document of type like this:
<?xml version="1.0" encoding="UTF-16"?>
<Recordset>
<Table>Recordset</Table>
<Rows>
<Row>
<Fields>
...
<Field>
<Alias>StatusName</Alias>
<Value>Scheduled</Value>
</Field>
<Field>
<Alias>U_Revision</Alias>
<Value>code00</Value>
</Field>
<Field>
<Alias>U_Quantity</Alias>
<Value>10.000000</Value>
</Field>
<Field>
<Alias>U_ActualQty</Alias>
<Value>0.000000</Value>
</Field>
...
</Fields>
</Row>
...
<Row>
<Fields>
...
<Field>
<Alias>StatusName</Alias>
<Value>Scheduled</Value>
</Field>
<Field>
<Alias>U_Revision</Alias>
<Value>code00</Value>
</Field>
<Field>
<Alias>U_Quantity</Alias>
<Value>150.000000</Value>
</Field>
<Field>
<Alias>U_ActualQty</Alias>
<Value>0.000000</Value>
</Field>
...
</Fields>
</Row>
</Rows>
</Recordset>
I have different values in field with alias of StatusName. There are some Scheduled, notScheduled, Realeased, Finished etc values. What I would like to do is to delete each node that contain node with alias StatusName and value lets say Scheduled or Finished.
I was thinking to do this more or less in that way however I am doing something wrong. May anybody let me on right way ?
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
XmlNodeList nodes = xmlDocument.SelectNodes("//Rows[#StatusName='Finished']");
for (int i = nodes.Count - 1; i >= 0; i--)
{
nodes[i].ParentNode.RemoveChild(nodes[i]);
}
var newXml = nodes.ToString();
I would like to delete the whole node if contains with alias StatusName and specific value lets say Finished.
I would expect the result in new string variable.
I like to work with DataTable with xml, I found it very easy.
I used a DataTable to work with your nodes.
So, I took your xml file and wrote some code for you that might help you:
//READ THE XML FILE
XmlDocument xmlDoc = new XmlDocument();
//My path
xmlDoc.LoadXml(Properties.Resources.test);
//Read the xml file into a dataSet
DataSet ds = new DataSet();
XmlNodeReader xnr = new XmlNodeReader(xmlDoc);
ds.ReadXml(xnr);
//Your data will be store in the 4's dataTable of the dataSet ( the <field> )
for(int i=0;i<ds.Tables[4].Rows.Count;i++)
{
//Check the value as you wish
//Here i want to suppress all the <Field> nodes with <Value> = "Scheduled"
if ( ds.Tables[4].Rows[i]["Value"].ToString().Equals("Scheduled"))
{
//RemoteAt will remove all the node, so the node <Field> in your example data
ds.Tables[4].Rows.RemoveAt(i);
//If you want to only remove the node <Value> (and not all the <Field> node ) just do ds.Tables[4].Rows["Value"]=null;
}
}
//Write your new content in a new xml file
//As you wanted here you just read the new xml file created as a string
using (var stringWriter = new StringWriter())
using (var xmlTextWriter = XmlWriter.Create(stringWriter))
{
ds.WriteXml(xmlTextWriter);
xmlTextWriter.Flush();
stringWriter.GetStringBuilder().ToString();
//Here the result is in stringWriter, and there is 6 <Field> nodes, and not 8 like before the suppress
}
//If you want to create a new xml file with the new content just do
ds.WriteXml(yourPathOfXmlFile);
//( like rewriting the previous xml file )
I assume, you are going to delete entire <Row> which matches your condition
i.e.,
<Row>
<Fields>
...
<Field>
<Alias>StatusName</Alias>
<Value>Finished</Value>
</Field>
</Fields>
</Row>
The required XPath:
//Row[Fields[Field[Alias[text()='StatusName'] and Value[text() = 'Finished']]]]
C#
string xPath = #"//Row[Fields[Field[Alias[text()='StatusName'] and Value[text() = 'Finished']]]]";
var nodes = xmlDocument.SelectNodes(xPath);
for (int i = nodes.Count - 1; i >= 0; i--)
{
nodes[i].ParentNode.RemoveChild(nodes[i]);
}
var newXml = xmlDocument.OuterXml;

XML Reading Issue

I have the following XML doc... I have just left one "line" in for simplicity.
<?xml version="1.0" encoding="UTF-8"?>
<files>
<file type="INVOICES">
<document>blah.pdf</document>
<line>
<field name="JobNo">321654</field>
<field name="Issues">1</field>
<field name="PageCount">200</field>
<field name="PrintRun">250</field>
<field name="Size">Small</field>
</line>
</file>
</files>
C# code:
static void Main(string[] args)
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("input.xml");
XmlNodeList itemNodes = xmlDoc.SelectNodes("//files/file/line");
foreach (XmlNode itemNode in itemNodes)
{
XmlNode jobNo = itemNode.SelectSingleNode("field");
if (jobNo != null)
Console.WriteLine(jobNo.InnerText);
}
Console.ReadKey();
}
This iterates through each line and displays the job number however I want to access the field by it's name JobNo i.e.
<field name="JobNo">321654</field> accessed with...
jobNo = itemNode.SelectSingleNode("JobNo");
I know I can change the xml but the XML is supplied by a customer so this is not really an option.
You can access that specific field element by providing the attribute name and value such as
XmlNode jobNo = itemNode.SelectSingleNode("field[#name='JobNo']");

Parsing XML using LINQ in c#

In this xml file (http://www.studiovincent.net/list.xml):
<list version="1.0">
<meta>
<type>resource-list</type>
</meta>
<resources start="0" count="4">
<resource classname="Quote">
<field name="name">Vincent</field>
<field name="username">Hill</field>
<field name="age">31</field>
<field name="hair">black</field>
</resource>
<resource classname="Quote">
<field name="name">John</field>
<field name="username">Tedelon</field>
<field name="age">27</field>
<field name="hair">brown</field>
</resource>
<resource classname="Quote">
<field name="name">Michael</field>
<field name="username">Lopez</field>
<field name="age">20</field>
<field name="hair">red</field>
</resource>
<resource classname="Quote">
<field name="name">Frank</field>
<field name="username">Lopez</field>
<field name="age">25</field>
<field name="hair">black</field>
</resource>
</resources>
</list>
using this code:
using System.Xml;
using.System.Xml.Linq;
XmlReader reader = XmlReader.Create("http://www.studiovincent.net/list.xml");
XElement el = XElement.Load(reader);
reader.Close();
var items = el.Elements("resources").Elements("resource").Descendants().DescendantNodes();
var items = from item in el.Elements("resources").Elements("resource").Descendants()
where item.Attribute("name").Value == "name" select item.FirstNode;
foreach (XNode node in items)
{
Console.WriteLine(node.ToString());
}
I have this OUTPUT:
Vincent
John
Michael
Frank
Code working very good, but I need get value 31 which corresponds field name="age" where field name="name" is Vincent. How Can I get this result?
I recommend you do as you would when reading XML naturally.
In your code, try to find all the fields with the name attribute set to "name".
This process cannot be used to associate a name with an age. It is more natural to read the XML and check all resource elements. Then add to this element some information described in the field elements.
// Using LINQ to SQL
XDocument document = XDocument.Load("http://www.studiovincent.net/list.xml"); // Loads the XML document.
XElement resourcesElement = document.Root.Element("resources"); // Gets the "resources" element that is in the root "list" of the document.
XElement resourceElementVincent = (from resourceElement in resourcesElement.Elements("resource")// Gets all the "resource" elements in the "resources" element
let fieldNameElement = resourceElement.Elements("field").Single(fieldElement => fieldElement.Attribute("name").Value == "name") // Gets the field that contains the name (there one and only one "name" field in the "resource" element -> use of Single())
where fieldNameElement.Value == "Vincent" // To get only resources called "Vincent"
select resourceElement).Single(); // We suppose there is one and only 1 resource called "Vincent" -> Use of Single()
XElement fieldAgeElement = resourceElementVincent.Elements("field").Single(fieldElement => fieldElement.Attribute("name").Value == "age"); // Gets the corresponding "age" field
int age = int.Parse(fieldAgeElement.Value, CultureInfo.InvariantCulture); // Gets the age by Parse it as an integer
Console.WriteLine(age);
Does it do what you want?
Consider this below XML is there as one of the SQL table's column.
<Root>
<Name>Dinesh</Name>
<Id>2</Id>
</Root>
The objective of the query is to fetch the Name from the XML. In this example we will fetch the 'Dinesh' as the value.
var Query = (from t in dbContext.Employee.AsEnumerable()
where t.active == true
select new Employee
{
Id = t.AtpEventId,
Name = XDocument.Parse(t.Content).Descendants("Root").Descendants("Name").ToList().
Select(node => node.Value.ToString()).FirstOrDefault()
});
Note the following:
t.active == true is just an example to make some condition if needed.
Please note, in the above LINQ query, always use the AsEnumerable, as I did in the first line.
Descendants("Root").Descendants("Name") - here "Root" should be the element matching with the XML, and under Root we have Name element.

XML String into a DataGridView

I am currently working with a webservice to pull a report about users in a remote support system.
After pulling my report and receiving the result, I am given the following string back by the method:
<report>
<header>
<field id="0">Source</field>
<field id="1">Session ID</field>
<field id="2">Date</field>
<field id="3">Name</field>
<field id="24">Technician Name</field>
<field id="25">Technician ID</field>
</header>
<data>
<row>
<field id="0">Email</field>
<field id="1">55037806</field>
<field id="2">4/13/2010 2:28:06 AM</field>
<field id="3">Bill Gates</field>
<field id="24">John</field>
<field id="25">1821852</field>
</row>
<row>
<field id="0">Telephone</field>
<field id="1">55034548</field>
<field id="2">4/13/2010 12:59:44 AM</field>
<field id="3">Steve Jobs</field>
<field id="24">John</field>
<field id="25">1821852</field>
</row>
</data>
</report>
After receiving this string, I need to take it and display the actual data in a datagridview. I've tried putting it into an XMLDocument then reading that, but it seems to keep failing. Just interested in another set of eyes :) Application is written in C# in VS2010.
You could use Linq to XML to build a DataTable with the column names from the XML file, then set that table as the DataSource of the DataGridView :
string xml = theWebService.TheMethod();
XDocument doc = XDocument.Parse(xml);
var queryHeaders =
from field in doc.Root.Element("header").Elements("field")
select new
{
Id = field.Attribute("id").Value,
Name = field.Value
};
var headers = queryHeaders.ToDictionary(f => f.Id, f => f.Name);
DataTable table = new DataTable();
foreach (var kvp in headers)
{
table.Columns.Add(kvp.Value);
}
Func<XElement, DataRow> rowGenerator =
element =>
{
var row = table.NewRow();
foreach (var field in element.Elements("field"))
{
string fieldId = field.Attribute("id").Value;
string columnName = headers[fieldId];
string value = field.Value;
row[columnName] = value;
}
return row;
};
var rows =
from rowElement in doc.Root.Element("data").Elements("row")
select rowGenerator(rowElement);
foreach (var row in rows)
{
table.Rows.Add(row);
}
dataGridView.AutoGenerateColumns = true;
dataGridView.DataSource = table;
Note that the code above only works if all fields have distinct names. If that's not the case, you might want to use the field ID as the DataColumn name, and change the headers of the DGV with the actual field names

Categories