How do I select rows into columns? - c#

Xml is as follows.
<System>
<ID></ID>
<Name></Name>
<Monitor>
<ID></ID>
<Type></Type>
<Alert>
<ID></ID>
<Status></Status>
</Alert>
<Alert>
<ID></ID>
<Status></Status>
</Alert>
</Monitor>
</System>
<System>
<ID></ID>
<Name></Name>
<Monitor>
<ID></ID>
<Type></Type>
<Alert>
<ID></ID>
<Status></Status>
</Alert>
</Monitor>
</System>
I want to traverse it like this
XElement xmlDoc = XElement.Load(#"xml");
var q = from el in xmlDoc.Elements("System") select el;
foreach(el in q) {
Console.WriteLine(el.ID);
Console.WriteLine(el.Name);
if (el.Monitor) {
foreach (mon in el.Monitor) {
Console.WriteLine(el.ID);
Console.WriteLine(el.Type);
if (mon.Alert) {
foreach (alert in mon.Alert) {
Console.WriteLine(alert.ID);
Console.WriteLine(alert.Status);
}
}
}
}
}
Currently I loop through each several times and use if to check field and then assign value to a variable. Then I have to loop through it again. Is there an easier way, and should I use plain LINQ or LINQ-TO-XML?

Ok try this code (see below code for explaination)
class Program {
static void Main(string[] args) {
var xml = #"
<Root>
<System>
<ID>1</ID>
<Name>one</Name>
<Monitor>
<ID>3</ID>
<Type>t3</Type>
<Alert>
<ID>5</ID>
<Status>a5</Status>
</Alert>
<Alert>
<ID>6</ID>
<Status>a6</Status>
</Alert>
</Monitor>
</System>
<System>
<ID>2</ID>
<Name>two</Name>
<Monitor>
<ID>4</ID>
<Type>t4</Type>
<Alert>
<ID>7</ID>
<Status>a7</Status>
</Alert>
</Monitor>
</System>
</Root>
";
XElement xmlDoc = XElement.Parse(xml);
// set q to an enumeration of XElements
// where the elements xname is "System"
// the query actually executes the first time q is used
var q = xmlDoc.Elements("System");
foreach (var ele in q) {
// Get the value of the Element with the xname of "ID"
Console.WriteLine(ele.Element("ID").Value);
Console.WriteLine(ele.Element("Name").Value);
// if ele.Elements("Monitor") returns nothing
// then the foreach will be skipped (null-execution)
foreach (var mon in ele.Elements("Monitor")) {
Console.WriteLine(mon.Element("ID").Value);
Console.WriteLine(mon.Element("Type").Value);
foreach (var alert in mon.Elements("Alert")) {
Console.WriteLine(alert.Element("ID").Value);
Console.WriteLine(alert.Element("Status").Value);
}
}
}
}
}
This code will move through the XML document exactly once. In C# LINQ contains both language elements (like 'select' and 'from') and library element (.NET framework methods like XDocument.Elements); mixing the two is ok, but should only be done with understanding of what is occurring behind the statements. In this case you are asking for the XDocument to return all the child elements with an XName of "System". In the above code 'q' does not receive all of the elements, it receives an enumeration which can be iterated. The assignment of q is a very low cost operation because the XDocument contents are not transverse until the first foreach and then only one element at a time is examined. Do a search for "C# yield return" to see how this is implemented.
If you were only interested in the "Alert" elements you could do something like this:
var alerts = xmlDoc.Descendants("Alert")
This would return an enumeration of all elements with an XName of "Alert" (regardless of where they are in the hierarchy of the XML document). If you wanted to ensure the hierarchy you can use 'where', for example:
var alerts = xmlDoc.Descendants("Alert")
.Where(ele => (ele.Parent != null) && (ele.Parent.Name == "Monitor"))
.Where(ele => (ele.Parent.Parent != null) && (ele.Parent.Parent.Name == "System"));
foreach (var alert in alerts) {
Console.WriteLine(alert.Element("ID").Value);
Console.WriteLine(alert.Element("Status").Value);
}
If you need to iterate over the same nodes multiple times you should consider converting the enumeration to a list or array, this saves time but increases memory usage. IEnumerable<> has extension methods ".ToArray()" and ".ToList()" for this purpose.

C# is an OOP language, I think you should harness that for this:
Example:
public class MySystem
{
public int Id { get; private set; }
public string Name { get; private set; }
public MyMonitor[] Monitors { get; private set; }
public MySystem(XElement x)
{
Id = (int)x.Element("ID");
Name = x.Element("Name").Value;
// a little confusing from your code if there can be more than one Monitor
Monitors = x.Elements("Monitor").Select(m => new MyMonitor(m)).ToArray();
}
}
Do something similar for your Monitor class and your Alert class. I named it MySystem since a class named System is a mess.
You create your array of systems like I created the Monitor's array above with:
XElement xmlDoc = XElement.Load(#"xml");
MySystem[] systems = xmlDoc.Elements("System")
.Select(s => new MySystem(s))
.ToArray();
Now you have all your values in easy to use classes.

If you want to traverse it like that you can:
var xml = #"
<Root>
<System>
<ID>1</ID>
<Name>One</Name>
<Monitor>
<ID>2</ID>
<Type>Two</Type>
<Alert>
<ID>3</ID>
<Status>Three</Status>
</Alert>
<Alert>
<ID>4</ID>
<Status>Four</Status>
</Alert>
</Monitor>
</System>
<System>
<ID>5</ID>
<Name>Five</Name>
<Monitor>
<ID>6</ID>
<Type>Six</Type>
<Alert>
<ID>7</ID>
<Status>Seven</Status>
</Alert>
</Monitor>
</System>
</Root>
";
XElement xmlDoc = XElement.Parse(xml);
var q = xmlDoc.Elements("System");
foreach(var el in q) {
Console.WriteLine(el.Element("ID").Value);
Console.WriteLine(el.Element("Name").Value);
foreach(var mon in el.Elements("Monitor")) {
Console.WriteLine(mon.Element("ID").Value);
Console.WriteLine(mon.Element("Type").Value);
foreach(var alert in mon.Elements("Alert")) {
Console.WriteLine(alert.Element("ID").Value);
Console.WriteLine(alert.Element("Status").Value);
}
}
}

Related

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

XElement with LINQ Select and optional elements

I am trying to display some date from some XML I get from an external service. I am using XElement and I try to use LINQ select to get my data.
var xElem = XElement.Load(HttpUtility.UrlPathEncode(url));
var books = (from pubs in xElem.Elements("result")
select new
{
Id = (string)pubs.Element("data").Element("id"),
Title = (string)pubs.Element("data").Element("title"),
Year = (string)pubs.Element("data").Element("year"),
Resources = (string)pubs.Element("data")
.Element("resource")
.Element("url")
.ElementValueNull(),
Authors= pubs.Element("data").Elements("person")
}).ToList();
foreach (var book in books)
{
// Put the string together with string builder....
foreach (var person in book.Authors)
{
//Get the authors
}
}
And of course I have made the class for ElementValueNull.
//This method is to handle if element is missing
public static string ElementValueNull(this XElement element)
{
if (element != null)
return element.Value;
return "";
}
//This method is to handle if attribute is missing
public static string AttributeValueNull(this XElement element, string attributeName)
{
if (element == null)
return "";
else
{
XAttribute attr = element.Attribute(attributeName);
return attr == null ? "" : attr.Value;
}
}
The problem is that the resource tag with it's elements are not always present. And if it isn't there it will skip the whole record. Is there any easy way of making it so that it will just make the Resources have the empty string returned from my class but still add the record still using a LINQ select?
EDIT with XML example:
<?xml version="1.0" encoding="UTF-8"?>
<tester xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://tester.no/xmlSchema/xsd/tester.xsd">
<generert>2014-12-01</generert>
<result>
<data>
<id>297474</id>
<person>
<id>11690</id>
<surname>Medel-Svensson</surname>
<firstname>Ronnie</firstname>
</person>
<title>Title 1</title>
<year>2009</year>
</data>
</result>
<result>
<data>
<id>807059</id>
<person>
<id>11690</id>
<surname>Bronskimlet</surname>
<firstname>Hallstein</firstname>
</person>
<person>
<id>328009</id>
<surname>Kroksleiven</surname>
<firstname>Jostein</firstname>
</person>
<person>
<id>328010</id>
<surname>Gassolini</surname>
<firstname>Ruffino</firstname>
</person>
<person>
<id>327990</id>
<surname>von Schnellfahrer</surname>
<firstname>Heinrich</firstname>
</person>
<title>Title 2</title>
<year>2010</year>
<resource>
<type>
<code>TEXT</code>
</type>
<url>http://www.example.com/</url>
</resource>
</data>
</result>
<result>
<data>
<id>1164653</id>
<person>
<id>11690</id>
<surname>Bergsprekken</surname>
<firstname>Mysil</firstname>
</person>
<title>Title 3</title>
<year>2014</year>
<resource>
<type>
<code>FULLTEKST</code>
</type>
<url>http://www.example.com/</url>
</resource>
</data>
</result>
</tester>
A couple of things:
if you use Element(..), then the result could be null. This may cause null reference exceptions if elements are missing in your path. A more elegant way to handle this would be to use sequences and return an element if present using SingleOrDefault()
Both XElement and XAttribute have a bunch of explicit type conversion operators built in. This means you can cast to string and various other primitives. As string is a reference type, it would return null if the XObject was null. Value types such as int would throw an exception in this case, though int? would not.
With this in mind, something like this should solve your problem. Note as 'data' is common to all, you can put this in the initial selector:
from pubs in xElem.Elements("result").Elements("data")
select new
{
Id = (string)pubs.Element("id"),
Title = (string)pubs.Element("title"),
Year = (string)pubs.Element("year"),
Resources = (string)pubs.Elements("resource")
.Elements("url")
.SingleOrDefault(),
Authors= pubs.Elements("person")
}

Extracting the particular part of an XML

I have an XML in a string variable i want to check if this xml content contains
<xml>
<message display='yes'>
....
or
<xml>
<xdp:xdp>
...
is it possible?
Here is how you can check for it:
string example = #"<xml>
<message display='yes'></message>
</xml>";
XDocument doc = XDocument.Parse(example);
if (doc.Element("xml").Element("message") != null)
{
// node "message" exists within node "xml" which is located at the root of the document
}
You can use XDocument class to check for existence of any node at any location in the hierarchy of an XML document. You can load contents from string or file easily.
An example below
<Order>
<AmazonOrderID>000-1111111-2222222</AmazonOrderID>
<MerchantOrderID>111-3333333-4444444</MerchantOrderID>
<PurchaseDate>2012-03-02T13:28:53+00:00</PurchaseDate>
<LastUpdatedDate>2012-03-02T13:29:05+00:00</LastUpdatedDate>
<OrderStatus>Pending</OrderStatus>
<SalesChannel>Amazon.com</SalesChannel>
<URL>http://www.amazon.com</URL>
<FulfillmentData>
<FulfillmentChannel>Amazon</FulfillmentChannel>
<ShipServiceLevel>Standard</ShipServiceLevel>
<Address>
<City>Beverly Hills</City>
<State>CA</State>
<PostalCode>90210-1234</PostalCode>
<Country>US</Country>
</Address>
</FulfillmentData>
<OrderItem>
<ASIN>AmazonASIN </ASIN>
<SKU> Internal-SKU</SKU>
<ItemStatus>Pending</ItemStatus>
<ProductName> This is the name of the product </ProductName>
<Quantity>1</Quantity>
<ItemPrice>
<Component>
<Type>Principal</Type>
<Amount currency="USD">19.99</Amount>
</Component>
</ItemPrice>
</OrderItem>
</Order>
List<string> getNodes(string path, string nodeName) {
List<string> nodes = new List<string>();
XDocument xmlDoc = XDocument.Load(path); //Create the XML document type
foreach (var el in xmlDoc.Descendants(nodeName)) {
//for debugging
//nodes.Add(el.Name + " " + el.Value);
//for production
nodes.Add(el.Value);
}
return nodes;
} //end getNodes
List<string> skuNodes = xml.getNodes(#"AmazonSalesOrders.xml", "SKU");

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