Select from XML, Build datastructure - c#

I'm trying to build a datastructure based on xml content.
The structure looks like that:
Dictionary<string, List<Dictionary<string, string>>>
The XML looks like that:
<Table name="testTable">
<Row>
<Column name="test01" value="2029" />
<Column name="test02" value="2029" />
</Row>
<Row>
<Column name="test01" value="2029" />
<Column name="test02" value="2029" />
</Row>
</Table>
<Table name="testTable01">
<Row>
<Column name="test01" value="2029" />
<Column name="test02" value="2029" />
</Row>
<Row>
<Column name="test01" value="2029" />
<Column name="test02" value="2029" />
</Row>
</Table>
It should result in something like that:
Dictionary<tableName, List<Dictionary<columnName, columnValue>>>
It's no problem to do that with some nested foreach-loops, but I'm searching for a way to do that "in one line" with the LINQ extension methods. How could I do that?

Sounds like you want something like:
var tables = doc.Descendants("Table")
.ToDictionary(t => (string) t.Attribute("name"),
t => ExtractRowsFromTable(t));
...
private static List<Dictionary<string, string>> ExtractRowsFromTable(XElement table)
{
return table.Elements("Row")
.Select(row => row.Elements("Column")
.ToDictionary(c => (string) c.Attribute("name"),
c => (string) c.Attribute("value"))
.ToList();
}
You could do all of this in one line, basically inlining ExtractRowsFromTable - but I really wouldn't.

string xml =
#"<Root>
<Table name=""testTable"">
<Row>
<Column name=""test01"" value=""2029"" />
<Column name=""test02"" value=""2029"" />
</Row>
<Row>
<Column name=""test01"" value=""2029"" />
<Column name=""test02"" value=""2029"" />
</Row>
</Table>
<Table name=""testTable2"">
<Row>
<Column name=""test01"" value=""2029"" />
<Column name=""test02"" value=""2029"" />
</Row>
<Row>
<Column name=""test01"" value=""2029"" />
<Column name=""test02"" value=""2029"" />
</Row>
</Table>
</Root>";
XDocument xDoc = XDocument.Parse(xml);
var table = xDoc
.Descendants("Table")
.Select(t => new
{
Name = t.Attribute("name").Value,
Rows = t.Descendants("Row")
.Select(r=> r.Descendants("Column")
.ToDictionary(c=>c.Attribute("name").Value,
c=>c.Attribute("value").Value))
.ToList()
})
.ToList();

Related

System.Data.DuplicateNameException

I'm trying to read an xml file into a datagridview using c# and im having this error (System.Data.DuplicateNameException: 'A Relation named 'database_table' already belongs to this DataSet.') on the line ds.ReadXml(opf.FileName);
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog opf = new OpenFileDialog();
opf.Filter = "XML File | *.xml";
opf.FileName = "";
if (opf.ShowDialog() == DialogResult.OK)
{
DataSet ds = new DataSet();
ds.ReadXml(opf.FileName);
dataGridView1.DataSource = ds.Tables["Record"];
}
}
I tried doing
DataSet ds = new DataSet();
ds.Tables.Remove("database_table");
ds.ReadXml(opf.FileName);
dataGridView1.DataSource = ds.Tables["Record"];
still received the same error
Here is the XML File
<!--
- Database: 'sialabact'
-->
<database name="sialabact">
<!-- Table studentinfo -->
<table name="studentinfo">
<column name="Stud_No">20-1399</column>
<column name="FirstName">Al-Sharief</column>
<column name="LastName">Dinglasa</column>
<column name="Section">SBIT3E</column>
</table>
<table name="studentinfo">
<column name="Stud_No">21-4874</column>
<column name="FirstName">Ryan</column>
<column name="LastName">Wibowo</column>
<column name="Section">SFIT4A</column>
</table>
<table name="studentinfo">
<column name="Stud_No">19-2317</column>
<column name="FirstName">Mark</column>
<column name="LastName">Gallego</column>
<column name="Section">BAENT3C</column>
</table>
<table name="studentinfo">
<column name="Stud_No">20-1114</column>
<column name="FirstName">Carlo</column>
<column name="LastName">Enrile</column>
<column name="Section">SBIT3M</column>
</table>
</database>
This XML file was exported from MySQL

Adding XElement inside a XML Element/Node in c# using Linq

i want to create some xml with this format
<root>
<columns>
<column name="name1" value="value1" />
<column name="name2" value="value2" />
<column name="name3" value="value3" />
...
</columns>
<rows>
<row name="name1" value="value1" />
<row name="name2" value="value2" />
<row name="name3" value="value3" />
...
</rows>
</root>
so i create a
XElement tree = new XElement("root", new XElement("columns", from c in columns select new XElement("column", c) ...
And the result is
<root>
<columns>
<column>
<column>
<column>
...
</column>
</column>
</columns>
...
</root>
how can i close a node children or something to have what i want??
Following code:
List<KeyValuePair<string, string>> columns = new Dictionary<string,string> {
{"name1", "value1"},
{"name2", "value2"},
{"name3", "value3"}
}.ToList();
XElement tree =
new XElement("root",
new XElement("columns",
from c in columns
select new XElement("column",
new XAttribute("name", c.Key),
new XAttribute("value", c.Value)
)
),
new XElement("rows",
from r in rows // same dictionary as columns
select new XElement("row",
new XAttribute("name", r.Key),
new XAttribute("value", r.Value)
)
)
);
Produces required xml:
<root>
<columns>
<column name="name1" value="value1" />
<column name="name2" value="value2" />
<column name="name3" value="value3" />
</columns>
<rows>
<row name="name1" value="value1" />
<row name="name2" value="value2" />
<row name="name3" value="value3" />
</rows>
</root>
XElement tree = new XElement("root",
new XElement("columns",
from c in columns
select new XElement("column", new XAttribute("name", c.Name,), new XAttribute("value", c.Value))),
new XElement("rows",
from r in rows
select new XElement("row", new XAttribute("name", r.Name,), new XAttribute("value", r.Value))));

XPathNavigator with xpath gives wrong node back

I try to get some specific values from an xml config. See example below.
<?xml version="1.0" encoding="utf-8"?>
<ExcelConfig>
<ExcelDocument name="Customer" type="flat">
<IdentityColumn>
<Column name="Id" />
</IdentityColumn>
<Validate>
<Column name="Name" mandatory="true" />
<Column name="FirstName" mandatory="true" />
<OrColumns mandatory="true">
<Column name="PostalCode" mandatory="false" />
<Column name="PostalCode2" mandatory="false" />
</OrColumns>
</Validate>
</ExcelDocument>
<ExcelDocument name="Company" type="flat">
<IdentityColumn>
<Column name="Id" />
</IdentityColumn>
<Validate>
<Column name="Name" mandatory="true" />
<Column name="FirstName" mandatory="true" />
<OrColumns mandatory="true">
<Column name="PostalCode" mandatory="false" />
<Column name="PostalCode2" mandatory="false" />
</OrColumns>
</Validate>
</ExcelDocument>
<ExcelDocument name="SomeOtherType" type="block">
<IdentityBlock>
<Column name="Period" col="A" />
<Column name="Period2" col="B" />
</IdentityBlock>
<Validate>
<Column name="Name" mandatory="true" />
<Column name="FirstName" mandatory="true" />
</Validate>
</ExcelDocument>
</ExcelConfig>
I use the following code to get some information from the excel file.
"ValidationConfiguration" is the string with the previous configuration.
//Get Different NodeTypes of Excel documents
List<XPathNavigator> types = XmlHelper.GetNodeTypes(validationConfiguration, "/ExcelConfig/ExcelDocument");
List<XPathNavigator> flatTypes = XmlHelper.GetNodeTypes(validationConfiguration,
"//ExcelConfig/ExcelDocument[#type='flat']");
List<XPathNavigator> blockTypes = XmlHelper.GetNodeTypes(validationConfiguration,
"//ExcelConfig/ExcelDocument[#type='block']");
//First we check if the file is from the flat type and get the IdentityColumns
List<XPathNavigator> identityColumnsNode = XmlHelper.GetNodeTypes(validationConfiguration, "//ExcelConfig/ExcelDocument[#type='flat']/IdentityColumn");
You can find the XmlHelper class below.
public static class XmlHelper
{
public static List<XPathNavigator> GetNodeTypes(string xmlConfiguration,string xPath)
{
XPathDocument doc = new XPathDocument(new StringReader(xmlConfiguration));
XPathNavigator nav = doc.CreateNavigator();
XPathExpression expr = nav.Compile(xPath);
List<XPathNavigator> elements = new List<XPathNavigator>();
foreach (XPathNavigator node in nav.Select(expr))
{
elements.Add(node);
}
return elements;
}
public static List<string> GetIdentityColumnNames(List<XPathNavigator> xPathNavigators)
{
List<string> identityColumns = new List<string>();
foreach (XPathNavigator xPathNavigator in xPathNavigators)
{
foreach (XPathNavigator test in xPathNavigator.Select("//Column"))
{
identityColumns.Add(test.GetAttribute("name", ""));
}
}
return identityColumns;
}
}
Now i want to do the following. I selected the identityColumnsNodes(they contains the IdentityColumn from the exceldocuments that have the flat type).
The i get for al that types the colums. But when i try that, i get all columns back from the whole file. He don't only the items from the node that i use.
foreach (XPathNavigator identityColumNode in identityColumnsNode)
{
List<string> identityColumns = XmlHelper.GetIdentityColumnNames(identityColumnsNode);
}
The second problem/thing i want to do --> the best way to select the right validate node from the specific file. With the identityColumns (that i get back and my list of HeaderRow Cells i know what file it is. But how can i select that validate node?
Or are their better methods to do this stuff?

Build List<List<XElement>> from XML

I have an XML that is laid out to be reformatted into nested HTML table headers. I am working on getting each tier of the XML document into it's own list. For example:
<column name="Total" size="0">
<column name="Users" size="0" />
</column>
<column name="Date" size="0" />
<column name="Unique" size="0">
<column name="Clicks" size="0">
<column name="RC" size="0" />
<column name="CB" size="0" />
</column>
</column>
From this example, columns "Total", "Date", and "Unique" should be in the first list. Columns "Users" and "Clicks" should be in the second list. And, columns "RC" and "CB" should be in the third list. This should be accomplished using recursion to make the method completely dynamic. Any help is greatly appreciated.
Here you go:
XElement root = XElement.Parse(#"
<doc>
<column1>
<column2 />
</column1>
<column3 />
<column4>
<column5>
<column6 />
<column7 />
</column5>
</column4>
</doc>");
List<List<XElement>> outerList = new List<List<XElement>>();
List<XElement> innerList = root.Elements().ToList();
while (innerList.Any())
{
outerList.Add(innerList);
innerList = innerList.SelectMany(element => element.Elements()).ToList();
}
Edit: If you want to strip ancestor XElement instances of their descendants within your list, then you could use the following:
XElement root = XElement.Parse(#"
<table>
<column name=""Total"" size=""0"">
<column name=""Users"" size=""0"" />
</column>
<column name=""Date"" size=""0"" />
<column name=""Unique"" size=""0"">
<column name=""Clicks"" size=""0"">
<column name=""RC"" size=""0"" />
<column name=""CB"" size=""0"" />
</column>
</column>
</table>");
List<List<XElement>> outerList = new List<List<XElement>>();
IEnumerable<XElement> innerList = root.Elements();
while (innerList.Any())
{
outerList.Add(innerList.Select(e => new XElement(e.Name, e.Attributes())).ToList());
innerList = innerList.SelectMany(element => element.Elements());
}
Note: For the record, your intuition that you should use recursion was correct. However, it is also well known that any recursive function can be converted to an iteration, typically by simulating the stack. Sometimes, this leads to bloated code; however, other times, the conversion lends itself naturally. In your case, if you were to recurse, your recursive parameter would have been the immediate children of the set of elements currently being considered – which already happens to be available in innerList, thus allowing us to use the innerList = innerList.<SequenceOperation> trick to substitute the recursion.

C# Xml attribute values to string variable

Hey I was wondering if anyone could help to save the value of my xml doc to a c# variable. It is to help with a larger program feature. The XML layout is:
<row>
<var name="bud" value="45" />
<var name="acc" value="345" />
</row>
<row>
<var name="bud" value="45" />
<var name="acc" value="345" />
</row>
I would like to extract the value of bud and store it as a string in my c# code
thanks for any help guys I appreciate it.
XML has to be valid so added a root element.
XML:
<foo>
<row>
<var name="bud" value="45" />
<var name="acc" value="345" />
</row>
<row>
<var name="bud" value="45" />
<var name="acc" value="345" />
</row>
</foo>
Code:
This will return a List with the values of all variables "var" in your XML named "bud" and finally create a comma separated string with all the values.
string xml = "<foo><row><var name=\"bud\" value=\"45\" /><var name=\"acc\" value=\"345\" /></row><row><var name=\"bud\" value=\"45\" /><var name=\"acc\" value=\"345\" /></row></foo>";
XDocument doc = XDocument.Parse(xml);
var budValues =(from c in doc.Descendants("var")
where c.Attribute("name").Value == "bud"
select c.Attribute("value").Value).ToList();
string myBuddy = string.Join(",", budValues);
Your xml is not valid. It requires a single root node.
Here is simple solution using XPath:
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(#"
<dataset><row>
<var name=""bud"" value=""45"" />
<var name=""acc"" value=""345"" />
</row>
<row>
<var name=""bud"" value=""45"" />
<var name=""acc"" value=""345"" />
</row></dataset>");
XmlNode node = xDoc.SelectSingleNode("/dataset/row/var[#name='bud']");
string value = node.Attributes["value"].Value;
This gets only the first of the matches where #name='bud'. Checkout XPath to adjust your result. (it's pretty powerful)

Categories