C# - using Linq to XML to populate list of objects - c#

I'm trying to use the contents of an XML file as the data source to a List of objects. The object looks like this:
public class QuestionData
{
public string QuestionName{get;set;}
public List<string> Answers{get;set;}
}
And here is my XML:
<?xml version="1.0" encoding="utf-8" ?>
<QuestionData>
<Question>
<QuestionName>Question 1</QuestionName>
<Answers>
<string>Answer 1</string>
<string>Answer 2</string>
<string>Answer 3</string>
<string>Answer 4</string>
</Answers>
</Question>
<Question>
<QuestionName>Question 2</QuestionName>
<Answers>
<string>Answer 1</string>
<string>Answer 2</string>
<string>Answer 3</string>
</Answers>
</Question>
</QuestionData>
The code I'm using to try and do this is:
var xml = XDocument.Load ("C:\temp\xmlfile.xml");
List<QuestionData> questionData = xml.Root.Elements("Question").Select
(q => new QuestionData {
QuestionName = q.Element ("QuestionName").Value,
Answers = new List<string> {
q.Element ("Answers").Value }
}).ToList ();
The code compiles, but I'm not getting any data from the XML. I looped through questionData to try and display the information to the console but it was empty.

List<QuestionData> questionData =
xml.Root
.Elements("Question")
.Select(q => new QuestionData
{
QuestionName = (string)q.Element("QuestionName"),
Answers = q.Element("Answers")
.Elements("string")
.Select(s => (string)s)
.ToList()
}).ToList();
I used (string)XElement cast instead of XElement.Value property because it doesn't throw NullReferenceException when element is null.

Related

Obtain value stored within a set of attributes of a given type

I have the Following XML File , How can I obtain the value stored within the tags of a given type (In this Case say of type "FileModel")
.. How can I write some code which will get me the values Rep1 and Rep2 . I need these values to run a certain validation in a method
How can I achieve this ?
<?xml version="1.0"?>
<MainClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Items>
<Settings xsi:type="FileModel">
<Name>Rep1</Name>
<IsActive>true</IsActive>
<IsHidden>false</IsHidden>
</Settings>
<Settings xsi:type="FileModel">
<Name>Rep2</Name>
<IsActive>true</IsActive>
<IsHidden>false</IsHidden>
</Settings>
<Settings xsi:type="ServerModel">
<Name>DelRep</Name>
<IsActive>false</IsActive>
<IsHidden>false</IsHidden>
</Settings>
</Items>
</MainClass>
You can do this using XPath and a XmlNamespaceManager.
Example (.NetFiddle):
using System;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
public class Program
{
public static void Main()
{
var doc = XDocument.Parse(XmlString);
var namespaceManager = new XmlNamespaceManager(new NameTable());
namespaceManager.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
var names = doc.XPathSelectElements(
"/MainClass/Items/Settings[#xsi:type='FileModel']/Name",
namespaceManager
).Select(e => e.Value);
Console.WriteLine(String.Join(", ", names));
}
private static string XmlString = #"<?xml version=""1.0""?>
<MainClass xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<Items>
<Settings xsi:type=""FileModel"">
<Name>Rep1</Name>
<IsActive>true</IsActive>
<IsHidden>false</IsHidden>
</Settings>
<Settings xsi:type=""FileModel"">
<Name>Rep2</Name>
<IsActive>true</IsActive>
<IsHidden>false</IsHidden>
</Settings>
<Settings xsi:type=""ServerModel"">
<Name>DelRep</Name>
<IsActive>false</IsActive>
<IsHidden>false</IsHidden>
</Settings>
</Items>
</MainClass>";
}
Which will output: "Rep1, Rep2"
If you want to use System.Xml:
static void Main(string[] args)
{
List<string> result = new List<string>();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("myfile.xml");
XmlNodeList elemList = xmlDoc.GetElementsByTagName("Settings");
foreach (XmlNode node in elemList)
{
if(node.Attributes["xsi:type"].Value == "FileModel")
{
foreach (XmlNode item in node.ChildNodes)
{
if (item.Name == "Name")
result.Add(item.InnerText);
}
}
}
}
You can use linq to xml to achieve you desired result. Its more simple than you think. You don't need to have full knowledge of xml or linq.
To retrieve all Name within settings node with type is equal to FileModel.
Code:
var doc = XDocument.Load("Path to xml file");
//Or
var doc = XDocument.Parse(XmlString);
var ns = doc.Root.GetNamespaceOfPrefix("xsi");
var names = doc.Descendants("Items")
.Elements("Settings")
.Where(x => x.Attribute(ns + "type").Value == "FileModel")
.Select(x => x.Element("Name").Value)
.ToList();
names.ForEach(x => Console.WriteLine(x));
Output:
Or if you want your result in single string then you can join above list with comma(,) like,
var allName = string.Join(", ", names);
Console.WriteLine(allName);
Output:
Online Demo

Create XML file from collection list

I have a collection string with information about customers like name, gender etc. All custumers have an id.
Now i want to create a common XML file with all customers in it. Something like example below:
<custumers>
<custumer>
<name></name>
<id></id>
<etc></etc>
</custumer>
</custumers>
The start up with no XML file is easy, I used linq to create the xml file.
For initial creation I used following code:
try
{
var xEle = new XElement("Customers",
from cus in cusList
select new XElement("Customer",
new XElement("Name", cus.Name),
new XElement("gender", cus.gender),
new XElement("etc", cus.etc));
}
xEle.Save(path);
But on the point if i want to update the XML file i get some problems to get it.
My approach to solve it:
Iterate over all customers in list and check for all customers if the customer.id exists in the XML.
IF not: add new customer to xml
IF yes: update values
My code so far:
var xEle = XDocument.Load(xmlfile);
foreach (cus in cusList)
try
{
var cids = from cid in xEle.Descendants("ID")
where Int32.Parse(xid.Element("ID").Value) == cus.ID
select new XElement("customer", cus.name),
new XElement ("gender"), cus.gender),
new XELement ("etc."), cus.etc)
);
xEle.Save(xmlpath);
}
How about a different approach....
using System.IO;
using System.Text;
using System.Xml.Serialization;
public void Main()
{
DataSet Custumers = new DataSet("Custumers");
DataTable Custumer = new DataTable("Custumer");
// Create the columns
Custumer.Columns.Add("id", typeof(int)).Unique = true;
Custumer.Columns.Add("name", typeof(string));
Custumer.Columns.Add("gender", typeof(string));
Custumer.Columns.Add("etc", typeof(string));
// Set the primary key
Custumer.PrimaryKey = { Custumer.Columns("id") };
// Add table to dataset
Custumers.Tables.Add(Custumer);
Custumers.AcceptChanges();
// Add a couple of rows
Custumer.Rows.Add(1, "John", "male", "whatever");
Custumer.Rows.Add(2, "Jane", "female", "whatever");
Custumer.AcceptChanges();
// Let's save this to compare to the updated version
Custumers.WriteXml("Custumers_Original.xml");
// Read in XML that contains an existing Custumer and adds a new one
// into a Clone of the Custumer table
DataTable CustumerUpdate = Custumer.Clone;
CustumerUpdate.ReadXml("Custumers_Update.xml");
// Merge the clone table data to the Custumer table
Custumer.Merge(CustumerUpdate);
Custumer.AcceptChanges();
Custumers.WriteXml("Custumers_Final.xml");
}
Custumers_Original.xml looks like this:
<?xml version="1.0" standalone="yes"?>
<Custumers>
<Custumer>
<id>1</id>
<name>John</name>
<gender>male</gender>
<etc>whatever</etc>
</Custumer>
<Custumer>
<id>2</id>
<name>Jane</name>
<gender>female</gender>
<etc>whatever</etc>
</Custumer>
</Custumers>
Custumers_Update.xml has this, making a change to John and adding George:
<?xml version="1.0" encoding="utf-8" ?>
<Custumers>
<Custumer>
<name>John</name>
<id>1</id>
<gender>male</gender>
<etc>this is new</etc>
</Custumer>
<Custumer>
<name>George</name>
<id>3</id>
<gender>male</gender>
<etc>grandson</etc>
</Custumer>
</Custumers>
After the merge, the Custumers_Final.xml contains this:
<?xml version="1.0" standalone="yes"?>
<Custumers>
<Custumer>
<id>1</id>
<name>John</name>
<gender>male</gender>
<etc>this is new</etc>
</Custumer>
<Custumer>
<id>2</id>
<name>Jane</name>
<gender>female</gender>
<etc>whatever</etc>
</Custumer>
<Custumer>
<id>3</id>
<name>George</name>
<gender>male</gender>
<etc>grandson</etc>
</Custumer>
</Custumers>
Also my solution with linq:
try
{
XDocument xEle = XDocument.Load(path);
var ids = from id in xEle.Descendants("Custom")
where id.Element("id").Value == cus.ID
select id;
foreach (XElement idCustom in ids)
{
idCustom.SetElementValue("NewName", "NewElement");
}
xEle.Save(path);
}

C# Add nodes in xml

I am trying to add my code for already existing code. I have below two xmls:
Out.xml
<School>
<Classes>
<Class>
<Students>
<Student SequenceNumber="1">
<ID>123</ID>
<Name>AAA</Name>
</Student>
</Students>
</Class>
</Classes>
</School>
In.xml
<School>
<Classes>
<Class>
<Students>
<Student SequenceNumber="1">
<ID>456</ID>
<Name>BBB</Name>
</Student>
<Student SequenceNumber="2">
<ID>123</ID>
<Name>AAA</Name>
</Student>
<Student SequenceNumber="3">
<ID>789</ID>
<Name>CCC</Name>
</Student>
</Students>
</Class>
</Classes>
</School>
Now I need to check Out.xml and In.xml and my final Out.xml must be like below. The rule here is check StudentID in Out and In xmls. If Out xml doesnot have it and In xml has it add it to Out.xml at end of already existing elements.
Out.xml
<School>
<Classes>
<Class>
<Students>
<Student SequenceNumber="1">
<ID>123</ID>
<Name>AAA</Name>
</Student>
<Student SequenceNumber="2">
<ID>456</ID>
<Name>BBB</Name>
</Student>
<Student SequenceNumber="3">
<ID>789</ID>
<Name>CCC</Name>
</Student>
</Students>
</Class>
</Classes>
</School>
Already existing code is as below
string inFileName = #"C:\In.xml";
string inXml = System.IO.File.ReadAllText(inFileName);
var xmlReaderSource = XmlReader.Create(new StringReader(inXml));
var mgr = new XmlNamespaceManager(xmlReaderSource.NameTable);
mgr.AddNamespace("m", "http://www.mismo.org/residential/2009/schemas");
XDocument sourceXmlDoc = XDocument.Load(xmlReaderSource);
string outFileName = #"C:\Out.xml";
string outXml = System.IO.File.ReadAllText(outFileName);
XmlDocument targetXmlDoc = new XmlDocument();
targetXmlDoc.LoadXml(outXml);
I cannot change above code now I need add my logic.
I added like below
string xpath = #"/m:School/m:Classes/m:Class/m:Students";
XmlNodeList outStudentNodes = targetXmlDoc.SelectNodes(xpath + "/m:Student", namespaceManager);
if(outStudentNodes== null || outStudentNodes.Count <= 0)
{
return;
}
XElement root = sourceXmlDoc.Root;
IEnumerable<XElement> inStudentsColl = from item in root.Elements("Classes").Descendants("Class")
.Descendants("Students").Descendants("Student")
select item;
Now I have XmlNodeList and IEnumerble, trying to see whether I can use LINQ statement and make code simple for my comparison.
Node: I am not asking how to add nodes/elements using C#. I am looking for how to compare two xmls and then add nodes/elements into the one which is missing those nodes/elements. My issue here is one xml is read like XDocument and other using XmlDocument.
UPDATE
Thank you very much #TheAnatheme. I really appreciate it.
I followed what TheAnatheme suggested me and it worked. I marked TheAnatheme's answer as real solution. Please see below what I did in foreach block so that if anyone wants to use they can refer to this post.
string xpath = #"/m:School/m:Classes/m:Class/m:Students
XmlNode studentsNode = targetXmlDoc.SelectSingleNode(xpath, namespaceManager);
foreach (var element in elementsToAdd)
{
//Add Microsoft.CSharp.dll (if needed ) to your project for below statement to work
dynamic studentElement = element as dynamic;
if (studentElement != null)
{
XmlElement studentXmlElement = targetXmlDoc.CreateElement("Student");
XmlElement studentIDXmlElement = targetXmlDoc.CreateElement("ID");
studentIDXmlElement.InnerText = studentElement.ID;
XmlElement studentNameXmlElement = targetXmlDoc.CreateElement("Name");
studentNameXmlElement .InnerText = studentElement.Name;
studentXmlElement.AppendChild(studentIDXmlElement);
studentXmlElement.AppendChild(studentNameXmlElement);
studentsNode.AppendChild(childElement);
}
}
This projects both sets into an anonymous object List, makes comparisons, and gives you a set of anonymous objects that don't yet exist by which you can add to the out XML.
public static List<object> GetInStudents(XDocument sourceXmlDoc)
{
IEnumerable<XElement> inStudentsElements =
sourceXmlDoc.Root.Elements("Classes").Descendants("Class")
.Descendants("Students").Descendants("Student");
return inStudentsElements.Select(i =>
new { Id = i.Elements().First().Value,
Name = i.Elements().Last().Value }).Cast<object>().ToList();
}
public static List<object> GetOutStudents(XmlDocument targetXmlDoc)
{
XmlNodeList outStudentsElements = targetXmlDoc.GetElementsByTagName("Students")[0].ChildNodes;
var outStudentsList = new List<object>();
for (int i = 0; i < outStudentsElements.Count; i++)
{
outStudentsList.Add(new { Id = outStudentsElements[i].ChildNodes[0].InnerText,
Name = outStudentsElements[i].ChildNodes[1].InnerText });
}
return outStudentsList;
}
And you compare them as such:
var inStudents = GetInStudents(sourceXmlDoc);
var outStudents = GetOutStudents(targetXmlDoc);
if (inStudents.SequenceEqual(outStudents))
{
return;
}
else
{
var elementsToAdd = inStudents.Except(outStudents);
foreach (var element in elementsToAdd)
{
// create xmlNode with element properties, add element to xml
}
}

XML TO linq get list of element into ArrayList

I have this XML file settings , How can I (USING LINQ) return the list of (StartItem) to ArrayList
and return( PriceItem) to another ArrayList
<Settings>
<ConSetting SettingID="1">
<Company CompanyID="1" CompanyName="CA" Code="*100#" Pin="11111" MobileName="M1">
<StartItems StartID="1"> 094</StartItems>
<StartItems StartID="2"> 095</StartItems>
<StartItems StartID="4"> 097</StartItems>
<StartItems StartID="5"> 098</StartItems>
</Company>
<Company CompanyID="2" CompanyName="CB" Code="*200#" Pin="22222" MobileName="M2">
<StartItems StartID="1"> 099</StartItems>
<StartItems StartID="2"> 093</StartItems>
<StartItems StartID="3"> 091</StartItems>
<StartItems StartID="4"> 092</StartItems>
</Company>
</ConSetting>
<Price SettingID ="2" CompanyName="CA" >
<Company CompanyID="1">
<PriceItem P="40"> 50</PriceItem>
<PriceItem P="90"> 100</PriceItem>
<PriceItem P="200"> 225</PriceItem>
</Company>
<Company CompanyID="2" CompanyName="CB" >
<PriceItem P="40"> 60</PriceItem>
<PriceItem P="90"> 110</PriceItem>
<PriceItem P="200"> 235</PriceItem>
</Company>
</Price>
</Settings>
XDocument is easy to use in your case:
var doc = XDocument.Load("settings.xml");
var result = from items in doc.Descendants("StartItems")
where items.Parent.Attribute("CompanyID").Value == "1"
select new StartItem()
{
StartID = items.Attribute("StartID").Value,
Value = items.Value
};
var Company1List = new ArrayList();
foreach(var item in result)
{
Company1List.Add(item);
}
public class StartItem
{
public string StartID { get; set; }
public string Value { get; set; }
}
You might want to look at the XmlReader Class in .NET
It should easily be able to parse this XML and then you can select certain items from the nodes into any list you want.
http://msdn.microsoft.com/en-us/library/system.xml.xmlreader.aspx
Look at this question and answer
Reading Xml with XmlReader in C#

how to get root node attribute value using linq

I have the following XML. How to read the root node attribite value and it's decendents using LINQ? I am trying to read "dId" and "dTime" from root node, "id" from Customer element and Order number.
<?xml version="1.0" encoding="utf-8" ?>
<Customers dId="wqwx" dTime="10-9-09 11:23">
<Customer id="1">
<Orders>
<Order number="22" status="ok">
</Orders>
</Customer>
</Customers>
I tried the following code but it doesn't work.
XDocument doc= XDocument.Load(#"C:\Customers.xml");
var q = from c in doc.Descendants("Customers")
select new
{
dID = c.Attribute("dId"),
dTime = c.Attribute("dTime");
}
first, fix your xml (<Order .... />)
then, your linq should look like this....
// .Elements(...) selects all elements of type "Customer"
var q = from c in xDoc.Elements("Customers")
select new
{
dID = c.Attribute("dId"),
dTime = c.Attribute("dTime")
};
you should dl LinqPad... it lets you do Linq queries on the fly, even agains SQL databases. Then, once you get the results you want, copy and past your linq into your source code.
You have to end the order tag with: />
xDoc.Descendants("Customers") should work as well as xDoc.Elements("Customers").
Chris, is there a specific advantage to using .Elements?
You can't use LINQ to access the root tag.
The code below does what you want (I included a well formed xml file as well):
using System;
using System.Linq;
using System.Xml.Linq;
namespace ReadXmlSpike
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Reading file...");
XDocument doc = XDocument.Load("Customers.xml");
var customers =
new
{
DID = (string) doc.Element("Customers").Attribute("did"),
DTime = (DateTime) doc.Element("Customers").Attribute("dTime"),
Customers = from customerxml in doc.Descendants("Customer")
select
new
{
ID = (string)customerxml.Attribute("id"),
Orders = from orderxml in customerxml.Descendants("Order")
select
new
{
Number =(string) orderxml.Attribute("number")
}
}
};
Console.WriteLine("Customersfile with id: {0} and time {1}",customers.DID,customers.DTime);
foreach (var customer in customers.Customers)
{
Console.WriteLine("Customer with id {0} has the following orders:",customer.ID);
foreach (var order in customer.Orders)
{
Console.WriteLine("Order with number {0}",order.Number);
}
}
Console.ReadLine();
}
}
}
and the xml file:
<?xml version="1.0" encoding="utf-8" ?>
<Customers did="www" dTime="10-09-09 11:23">
<Customer id="1">
<Orders>
<Order number="22" status="ok"/>
<Order number="23" status="bad"/>
</Orders>
</Customer>
<Customer id="2">
<Orders>
<Order number="24" status="ok"/>
<Order number="25" status="bad"/>
</Orders>
</Customer>
</Customers>
XDocument d = XDocument.Parse(#"<?xml version='1.0' encoding='utf-8' ?>
<Customers dId='wqwx' dTime='10-9-09 11:23'>
<Customer id='1'>
<Orders>
<Order number='22' status='ok'/>
</Orders>
</Customer>
</Customers>");
var cu = d.Root.Elements().Where(n => n.Name == "Customer");
var c = from cc in cu
select new
{
dId = cc.Document.Root.Attribute("dId").Value,
dTime = cc.Document.Root.Attribute("dTime").Value,
ID = cc.Attribute("id").Value,
number = cc.Element("Orders").Element("Order").Attribute("number").Value
};
foreach (var v in c)
{
Console.WriteLine("dId \t\t= {0}", v.dId);
Console.WriteLine("dTime \t\t= {0}", v.dTime);
Console.WriteLine("CustomerID \t= {0}", v.ID);
Console.WriteLine("OrderCount \t= {0}", v.number);
}
Console Output:
================================
dId = wqwx
dTime = 10-9-09 11:23
CustomerID = 1
OrderCount = 22
请按任意键继续. . .
It does not work the way you wrote it: while printing the above code would complain about anonymous type.
However, with this simple modified version d.Document.Root.Attribute("dId").Value; you can assign it to a string.

Categories

Resources