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
Related
I have a complex XML which is loaded into XDocument and I want to turn it into a DataTable please. Here is the actual XML:
<?xml version="1.0"?>
<XMLCONTAINER version="1.0" incCount="3">
<APPLICATION id="APPLICATION_NODE_1" title="" name="55038170812RDOpayment" application="Excel" appfile="%SAAR%\xmlsFs\Dbxmls.xla" command="openDatabaseDesigner">
<MAINDATA id="CONTAINER_NODE_2" title=""/>
<SKY.FORM id="CONTAINER_NODE_3" title="" table="tblMain" database="\\c\E\DARA\Data\Link\RDOpayments.mdb">
<FIELD fieldName="RID">1234567</FIELD>
<FIELD fieldName="DateTime">05/02/2019 09:00:50</FIELD>
<FIELD fieldName="DateOfLetter">29/01/2019</FIELD>
<FIELD fieldName="Name">Mr Joe Bloggs</FIELD>
<FIELD fieldName="NRID">XXX1234X</FIELD>
<FIELD fieldName="paymentAmount">776.40</FIELD>
<FIELD fieldName="Amountfor0809">776.40</FIELD>
<FIELD fieldName="ACE">FALSE</FIELD>
<FIELD fieldName="Telephone">123456789</FIELD>
<FIELD fieldName="AcceptWithheldNumber">FALSE</FIELD>
<FIELD fieldName="PotentialACECase">FALSE</FIELD>
<FIELD childID="CONTAINER_NODE_3_field7option1" fieldName="CustomerType">3</FIELD>
<FIELD fieldName="ContactHistory">05/02/2019</FIELD>
<FIELD fieldName="ACERed">FALSE</FIELD>
</SKY.FORM>
The DataTable needs to look like this:
RID DateTime DateOfLetter Name NRID paymentAmount ACE
--- ----------- ------------ ---- ---- ------------- ----------
1234567 2019-02-05 10:23:51 2019-02-05 MISS LL TEST X1234X 123.45 FALSE
7654321 2019-01-11 11:11:11 2019-02-03 MR I WONG Y4321Y 321.21 TRUE
My current attempts of coding this are not picking up the xml as Node? I think it should be attributes xattribute??
// Conversion Xml file to DataTable
public DataTable CreateDataTableXML(string XmlFile)
{
XmlDocument doc = new XmlDocument();
doc.Load(XmlFile);
DataTable Dt = new DataTable();
try
{
Dt.TableName = GetTableName(XmlFile);
XmlNode NodeStructure = doc.DocumentElement.ChildNodes.Cast<XmlNode>().ToList()[0];
progressBar1.Maximum = NodeStructure.ChildNodes.Count;
progressBar1.Value = 0;
foreach (XmlNode column in NodeStructure.ChildNodes)
{
Dt.Columns.Add(column.Name, typeof(String));
Progress();
}
XmlNode Xnodes = doc.DocumentElement;
progressBar1.Maximum = Xnodes.ChildNodes.Count;
progressBar1.Value = 0;
foreach (XmlNode xnode in Xnodes.ChildNodes)
{
List<string> Values = xnode.ChildNodes.Cast<XmlNode>().ToList().Select(x => x.InnerText).ToList();
Dt.Rows.Add(Values.ToArray());
Progress();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
return Dt;
}
So I finally solved this. For anyone reading this, you need to solve in two parts, first get the column names into the datatable and then the xml values.
For the column names as we already have a table in sql we want to upload to, I simply used a dataadapter to fill the datatable with the schema which made sure every column name was there in the dt.
For the xml values, this proved more tricky as in a normal xml I could have just looped on the fieldname=, however as each attribute tag is different I extracted each manually with .attribute ?.value. It was a simple case then of creating datarows which would coalesce to DBNull if data was null.
my XML :
- <resources start="0" count="188">-
- <resource classname="Quote">
<field name="name">USD/GEL</field>
<field name="price">2.418900</field>
<field name="symbol">GEL=X</field>
<field name="ts">1488758461</field>
<field name="type">currency</field>
<field name="utctime">2017-03-06T00:01:01+0000</field>
<field name="volume">0</field>
</resource>-
</resources>
C# Code:
var xmlNodes = xElement.Descendants("resource")
.Select(e => new
{
ConvertFrom = e.Attribute("symbol").Value,
ConvRate = e.Attribute("price").Value,
ConvDate = e.Attribute("utctime").Value
});
I tried the above code to fetch and load into oracle but i got a below error.
System.Linq.Enumerable+WhereSelectEnumerableIterator2[System.Xml.Linq.XElement,<>f__AnonymousType03[System.String,System.String,System.String]]
Please help me to resolve this issue.
I think you have misunderstood Attribute method, the name of the attribute is name, symbol is the value, so your query could be this way:
var xmlNodes = xElement.Descendants("resource")
.Select(e => new
{
ConvertFrom = (string)e.Elements().FistOrDefault(r=>r.Attribute("name").Value=="symbol"),
ConvRate = (string)e.Elements().FistOrDefault(r=>r.Attribute("name").Value=="price"),
ConvDate = (DateTime)e.Elements().FistOrDefault(r=>r.Attribute("name").Value=="utctime"),
});
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...
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.
I'm looking to get to value further in the XML code than I've seen examples for and with more attributes to consider.
The XML code is:
<?xml version="1.0"?>
<mysqldump xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<database name="Members">
<table_structure name="Logins">
<field Field="ID" Type="int(100)" Null="NO" Key="PRI" Extra="auto_increment" Comment="" />
<field Field="Username" Type="text" Null="YES" Key="" Extra="" Comment="" />
<field Field="Password" Type="text" Null="YES" Key="" Extra="" Comment="" />
<key Table="Logins" Non_unique="0" Key_name="PRIMARY" Seq_in_index="1" Column_name="ID" Collation="A" Cardinality="1" Null="" Index_type="BTREE" Comment="" />
<options Name="Logins" Engine="MyISAM" Version="10" Row_format="Dynamic" Rows="1" Avg_row_length="20" Data_length="40" Max_data_length="281474976710655" Index_length="2048" Data_free="20" Auto_increment="3" Create_time="2011-10-14 19:30:57" Update_time="2011-10-14 19:32:21" Collation="latin1_swedish_ci" Create_options="" Comment="" />
</table_structure>
<table_data name="Logins">
<row>
<field name="ID">1</field>
<field name="Username">MyName</field>
<field name="Password">MyPassowrd</field>
</row>
<row>
<field name="ID">2</field>
<field name="Username">MyName2</field>
<field name="Password">MyPassowrd2</field>
</row>
</table_data>
</database>
</mysqldump>
I'm trying to get to 1, MyName and Password to then enter them into an SQL database.
The issue I'm having is getting through the multiple levels:
<database name="Members">
<table_data name="Logins">
<row>
<field name="ID">
With all the examples I'm finding only ones showing 1 or 2 levels.
This seems to work,
XElement xe = XElement.Load("C:\\PATH.xml");
int count = xe.Descendants("row").Count();
var items = xe.Descendants("field").Where(n => (string)n.Attribute("name") == "ID").ToList();
var items2 = xe.Descendants("field").Where(n => (string)n.Attribute("name") == "Username").ToList();
var items3 = xe.Descendants("field").Where(n => (string)n.Attribute("name") == "Password").ToList();
for (int i = 0; i < count; i++)
{
string res = items[i].Value.ToString();
string res2 = items2[i].Value.ToString();
string res3 = items3[i].Value.ToString();
}
I've got an ID, but I now need all its other elements in its row.
That is,
if (id = 2)
{
string name = username.value;
string password = password.value;
}
where name will result in myName2 and password will be MyPassword2.
I don't want to put usernames and passwords in a list.
Any advice again?
Try with LINQ to XML.
Here you have a tutorial.