Reading XML values from different namespaces in same LINQ Query - c#

I hope you guys will be able to help me out?
I have this XML structure:
<DataBase
xsi:schemaLocation="http://somestuff.new/xml http://somestuff.xsd"
xmlns:ns3="http://somestuff.new/ns3"
xmlns:ns2="http://somestuff.new/ns2"
xmlns="http://somestuff.new/ns"
xmlns:xsi="http://somestuff.new/XMLScema-instance"
xmlns:ns4="http://somestuff.new/ns4">
<Cars>
<SmallCars attribute="Something">
<Id>licenceplate</Id>
<Parts attribute="All Parts">
<Extras>
<Gauges xmlns="http://somestuff.new/ns3">
<Speed>100</Speed>
<Rpm>3200</Rpm>
</Gauges>
</Extras>
</Parts>
</SmallCars>
</Cars>
</DataBase>
I have then created a class of Cars like this:
public class Cars
{
public string CarType { get; set; }
public List<Parts> CarParts { get; set; }
}
And a Class of Parts:
public class Parts
{
public List<Gauges> CarExtras { get; set; }
}
And last but not least a class of Gauges:
public class Gauges
{
public double Speed { get; set; }
public int Rpm { get; set; }
}
Now I would like to create a LINQ query that gets the values from the Gauges part of the XML but I seem to fail in my attempt:
XDocument xml = XDocument.Load(file);
XNamespace xmlns =XNamespace.Get("http://somestuff.new/ns");
XNamespace ns3 = XNamespace.Get("http://somestuff.new/ns3");
var Cars = from car in xml.Descendants(ns + "Cars")
select new Cars
{
CarParts = (from part in car.Descendants(ns + "Parts")
select new Parts
{
CarExtras = (from extra in part.Descendants(ns + "Extras")
select new Gauges
{
Speed = (double?)extra.Element(ns3 + "Speed") ?? 0.0,
Rpm = (int?)extra.Element(ns3 + "Rpm") ?? 0
})
})
});
I have tried a lot of combinations with the namespaces because it changes when I get to Gauges but I do not get any values returned.
Hope someone can help me out here?

Notice that extra in in your linq-to-xml code is <Extras> element, and since <Speed> and <Rpm> are not direct child of <Extras> you can't select any of them by using extra.Element(ns3 + "elementName"). You can use Descendants instead of Element in this case :
XNamespace ns =XNamespace.Get("http://somestuff.new/ns");
XNamespace ns3 = XNamespace.Get("http://somestuff.new/ns3");
var Cars = from car in xml.Descendants(ns + "Cars")
select new Cars
{
CarParts = (from part in car.Descendants(ns + "Parts")
select new Parts
{
CarExtras =
(from extra in part.Descendants(ns + "Extras")
select new Gauges
{
Speed =
(double?)
extra.Descendants(ns3 + "Speed").FirstOrDefault() ??
0.0,
Rpm =
(int?)
extra.Descendants(ns3 + "Rpm").FirstOrDefault() ?? 0
}).ToList()
}).ToList()
};

Related

How to parse xml with LINQ to an Object with XDocument

I have an xml file as below:
<Message xsi:schemaLocation ="..">
<Header>..</Header>
<Body>
<History
xmlns="..">
<Number></Number>
<Name></Name>
<Item>
<CreateDate>..</CreateDate>
<Type>..</Type>
<Description></Description>
</Item>
<Item>
<CreateDate>..</CreateDate>
<Type>..</Type>
<Description>1..</Description>
<Description>2..</Description>
</Item>
</History>
</Body>
</Message>
I would like to create and object from this as History object.
public class History
{
public string Name { get; set; }
public string Number { get; set; }
public List<Item> Items { get; set; }
}
var xElement = XDocument.Parse(xmlString);
XElement body = (XElement)xElement.Root.LastNode;
XElement historyElement = (XElement)body.LastNode;
var history = new History
{
Name = (string)historyElement.Element("Name"),
Number = (string)historyElement.Element("Number"),
Items = (
from e in historyElement.Elements("Item")
select new Item
{
CraeteDate = DateTime.Parse(e.Element("CreateDate").Value),
Type = (string)e.Element("Type").Value,
Description = string.Join(",",
from p in e.Elements("Description") select (string)p.Element("Description"))
}).ToList()
};
Why this does not work?
The values are always null.
It seems that "historyElement.Element("Name")" is always null even there is an element and value for the element.
Any idea what am I missing?
Thanks
It's due to the namespace, try doing this:
XNamespace ns = "http://schemas.microsoft.com/search/local/ws/rest/v1";// the namespace you have in the history element
var xElement = XDocument.Parse(xmlString);
var history= xElement.Descendants(ns+"History")
.Select(historyElement=>new History{ Name = (string)historyElement.Element(ns+"Name"),
Number = (string)historyElement.Element(ns+"Number"),
Items = (from e in historyElement.Elements(ns+"Item")
select new Item
{
CraeteDate= DateTime.Parse(e.Element(ns+"CreateDate").Value),
Type = (string) e.Element(ns+"Type").Value,
Description= string.Join(",",
from p in e.Elements(ns+"Description") select (string)p)
}).ToList()
}).FirstOrDefault();
If you want to read more about this subject, take a look this link
A couple of minor things here, the xml was malformed here. So had to take a while to test and make it work.
You have an xsi in the front which I assume should be somewhere mentioned in the xsd.
Turns out you have to append the namespace if your xml node has a namespace attached to it as you are parsing the xml tree here:
My sample solution looked like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace ConsoleApplication1
{
public class History
{
public string Name { get; set; }
public string Number { get; set; }
public List<Item> Items { get; set; }
}
public class Item
{
public DateTime? CreateDate { get; set; }
public string Type { get; set; }
public string Description { get; set; }
}
class Program
{
static void Main(string[] args)
{
string xmlString =
#"<Message>
<Header>..</Header>
<Body>
<History
xmlns=""http://schemas.somewhere.com/types/history"">
<Number>12</Number>
<Name>History Name</Name>
<Item>
<CreateDate></CreateDate>
<Type>Item 1 Type</Type>
<Description>Item 1 Description</Description>
</Item>
<Item>
<CreateDate></CreateDate>
<Type>Item 2 Type</Type>
<Description>Item 2 Description 1</Description>
<Description>Item 2 Description 2</Description>
</Item>
</History>
</Body>
</Message>";
XNamespace ns = "http://schemas.somewhere.com/types/history";
var xElement = XDocument.Parse(xmlString);
var historyObject = xElement.Descendants(ns +"History")
.Select(historyElement => new History
{
Name = historyElement.Element(ns + "Name")?.Value,
Number = historyElement.Element(ns + "Number")?.Value,
Items = historyElement.Elements(ns + "Item").Select(x => new Item()
{
CreateDate = DateTime.Parse(x.Element(ns + "CreateDate")?.Value),
Type = x.Element(ns + "Type")?.Value,
Description = string.Join(",", x.Elements(ns + "Description").Select(elem=>elem.Value))
}).ToList()
}).FirstOrDefault();
}
}
}
If you dont wan't to care about finding the namespace, you might want to try the following:
var document = XDocument.Parse(xmlString);
var historyObject2 = document.Root.Descendants()
.Where(x=>x.Name.LocalName == "History")
.Select(historyElement => new History
{
Name = historyElement.Element(historyElement.Name.Namespace + "Name")?.Value,
Number = historyElement.Element(historyElement.Name.Namespace+ "Number")?.Value,
Items = historyElement.Elements(historyElement.Name.Namespace + "Item").Select(x => new Item()
{
//CreateDate = DateTime.Parse(x.Element("CreateDate")?.Value),
Type = x.Element(historyElement.Name.Namespace + "Type")?.Value,
Description = string.Join(",", x.Elements(historyElement.Name.Namespace + "Description").Select(elem => elem.Value))
}).ToList()
}).FirstOrDefault();

How to do the pagination with XmlReader class c#

At present i am fetching data from xml file using LINQ but the problem is i am using XDocument to load xml file but XDocument class load xml data into memory. so if there is 10,000 data in my xml file then XDocument class will load 10,000 data into memory. so some one tell me if use read xml data with XmlReader class then it will not dump full data into memory.
At present this way i am fetching data from xml file.
My xml data look like:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Orders>
<OrderID>10248</OrderID>
<CustomerID>VINET</CustomerID>
<EmployeeID>5</EmployeeID>
<OrderDate>1996-07-04T00:00:00</OrderDate>
<RequiredDate>1996-08-01T00:00:00</RequiredDate>
<ShippedDate>1996-07-16T00:00:00</ShippedDate>
<ShipVia>3</ShipVia>
<Freight>32.3800</Freight>
<ShipName>Vins et alcools Chevalier</ShipName>
<ShipAddress>59 rue de l'Abbaye</ShipAddress>
<ShipCity>Reims</ShipCity>
<ShipPostalCode>51100</ShipPostalCode>
<ShipCountry>France</ShipCountry>
</Orders>
</Root>
here i am posting code wich fetch data from xml file with order by and paging.
XDocument document = XDocument.Load(#"c:\users\documents\visual studio 2010\Projects\WindowsFormsApplication5\WindowsFormsApplication5\Orders.xml");
bool isDesc = true;
//setup basic query
var query = from r in document.Descendants("Orders")
select new
{
OrderID = r.Element("OrderID").Value,
CustomerID = r.Element("CustomerID").Value,
EmployeeID = r.Element("EmployeeID").Value,
};
//setup query result ordering,
//assume we have variable to determine ordering mode : bool isDesc = true/false
if (isDesc)
query = query.OrderByDescending(o => o.OrderID);
else
query = query.OrderBy(o => o.OrderID);
//setup pagination,
//f.e displaying result for page 2 where each page displays 100 data
var page = 1;
var pageSize = 5;
query = query.Skip(page - 1 * pageSize).Take(pageSize);
//execute the query to get the actual result
//var items = query.ToList();
dataGridView1.DataSource = query.ToList();
So some one tell me how could i use xmlreader to read data from xml file with pagination and order by clause will be there.
I got one hit but do not understand how to use it for my purpose:
using( var reader = XmlReader.Create( . . . ) )
{
reader.MoveToContent();
reader.ReadToDescendant( "book" );
// skip N <book> elements
for( int i = 0; i < N; ++i )
{
reader.Skip();
reader.ReadToNextSibling( "book" );
}
// read M <book> elements
for( int i = 0; i < M; ++i )
{
var s = reader.ReadOuterXml();
Console.WriteLine( s );
reader.ReadToNextSibling( "book" );
}
}
So please see the above code and help me to construct the code which would use xml reader to fetch paginated data.
Try this code. Your latest posting is locked so I had to answer here
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(FILENAME);
Order order = new Order();
while(reader.ReadToFollowing("Orders"))
{
string xml = reader.ReadOuterXml().Trim();
XElement element = XElement.Parse(xml);
order.Add(element);
}
order.Sort();
}
}
public class Order
{
public void Add(XElement element)
{
if (orders == null)
{
orders = new List<Order>();
}
Order newOrder = new Order();
newOrder.OrderId = int.Parse(element.Element("OrderID").Value);
newOrder.CustomerId = element.Element("CustomerID").Value;
newOrder.EmployeeId = int.Parse(element.Element("EmployeeID").Value);
newOrder.OrderDate = DateTime.Parse(element.Element("OrderDate").Value);
newOrder.RequiredDate = DateTime.Parse(element.Element("RequiredDate").Value);
newOrder.ShippedDate = DateTime.Parse(element.Element("ShippedDate").Value);
newOrder.ShipVia = int.Parse(element.Element("ShipVia").Value);
newOrder.Freight = double.Parse(element.Element("Freight").Value);
newOrder.ShipName = element.Element("ShipName").Value;
newOrder.ShipAddress = element.Element("ShipAddress").Value;
newOrder.ShipCity = element.Element("ShipCity").Value;
newOrder.ShipPostalCode = int.Parse(element.Element("ShipPostalCode").Value);
newOrder.ShipCountry = element.Element("ShipCountry").Value;
orders.Add(newOrder);
}
public void Sort()
{
orders = orders.OrderBy(x => x.OrderId).ToList();
}
public static List<Order> orders { get; set; }
public int OrderId { get; set; }
public string CustomerId { get; set; }
public int EmployeeId { get; set; }
public DateTime OrderDate { get; set; }
public DateTime RequiredDate { get; set; }
public DateTime ShippedDate { get; set; }
public int ShipVia { get; set; }
public double Freight { get; set; }
public string ShipName { get; set; }
public string ShipAddress { get; set; }
public string ShipCity { get; set; }
public int ShipPostalCode { get; set; }
public string ShipCountry { get; set; }
}
}
​

5 Day Forecast using Nodes

I have set up a program that gets my weather for 1 day fine, but I have no idea how to get the other 4 days information because on the xml file the days all have the same name. How do I access the information from the different days?
http://weather.yahooapis.com/forecastrss?w=2473224 The Xml i am using
Code:
string query = String.Format("http://weather.yahooapis.com/forecastrss?w=2473224");
XmlDocument wData = new XmlDocument();
wData.Load(query);
XmlNamespaceManager manager = new XmlNamespaceManager(wData.NameTable);
manager.AddNamespace("yweather", "http://xml.weather.yahoo.com/ns/rss/1.0");
XmlNode channel = wData.SelectSingleNode("rss").SelectSingleNode("channel");
XmlNodeList nodes = wData.SelectNodes("/rss/channel/item/yweather:forecast", manager);
string temperature = channel.SelectSingleNode("item").SelectSingleNode("yweather:condition", manager).Attributes["temp"].Value;
string condition = channel.SelectSingleNode("item").SelectSingleNode("yweather:condition", manager).Attributes["text"].Value;
string humidity = channel.SelectSingleNode("yweather:atmosphere", manager).Attributes["humidity"].Value;
string WindSpeed = channel.SelectSingleNode("yweather:wind", manager).Attributes["speed"].Value;
string town = channel.SelectSingleNode("yweather:location", manager).Attributes["city"].Value;
string tfcond = channel.SelectSingleNode("item").SelectSingleNode("yweather:forecast", manager).Attributes["text"].Value;
string tfhigh = channel.SelectSingleNode("item").SelectSingleNode("yweather:forecast", manager).Attributes["high"].Value;
string tflow = channel.SelectSingleNode("item").SelectSingleNode("yweather:forecast", manager).Attributes["low"].Value;
Well I figured it out by assigning the values to an array like this and using some of the code like this.
var fiveDays = channel.SelectSingleNode("item").SelectNodes("yweather:forecast", manager);
foreach (XmlNode node in fiveDays)
{
var day = node.Attributes["day"].Value;
dayarray[i] = (day);
//
var text = node.Attributes["text"].Value;
textarray[i] = (text);
Thanks for all your help!
I believe you can use the XmlNode.SelectNodes method instead of SelectSingleNode.
This will return a list that you can then iterate through and copy the values to where you need them.
var fiveDays = channel.SelectSingleNode("item").SelectNodes("yweather:forecast", manager);
foreach (XmlNode node in fiveDays)
{
var text = node.Attributes["text"].Value;
var high = node.Attributes["high"].Value;
var low = node.Attributes["low"].Value;
}
This will give you a collection of the upcoming days, you can then loop over them to get the details on each day:
var upcomingDays = channel.SelectSingleNode("item").SelectNodes("yweather:forecast", manager);
foreach(XmlNode d in upcomingDays)
{
//d.Attributes["day"].Value;
}
I ran this in LINQPad and it worked fine.
This is much easier if you use a deserializer like XmlSerializer and classes. With the following classes:
[XmlRoot("rss")]
public class RssRoot
{
[XmlElement("channel")]
public Channel Channel { get; set; }
}
public class Channel
{
[XmlElement("item")]
public Item Item { get; set; }
// add other properties, if relevant
}
public class Item
{
[XmlElement("title")]
public string Title { get; set; }
[XmlElement("forecast", Namespace="http://xml.weather.yahoo.com/ns/rss/1.0")]
public List<Forecast> Forecasts { get; set; }
// add other properties, if relevant
}
public class Forecast
{
[XmlAttribute("text")]
public string Text { get; set; }
// add other attributes, if relevant
}
This will get your data from the URL in query
string query = "http://weather.yahooapis.com/forecastrss?w=2473224";
using (var reader = XmlReader.Create(query))
{
var ser = new XmlSerializer(typeof(RssRoot));
var rss = (RssRoot)ser.Deserialize(reader);
// use rss
}
If you want to continue to work with the XML using XmlDocument, you should use SelectNodes to get multiple forecast nodes, instead of getting a single node.

LINQ to XML join with idref

I have a XML structure like the following:
[...]
<Fruits>
<Fruit>
<Specification>id_1001_0</Specification>
<Weight>23</Weight>
</Fruit>
</Fruits>
<FruitSpecification id="id_1001_0">
<Type>Apple</Type>
</FruitSpecification>
[...]
I want to use Linq to XML to read this into (non-anonymous) objects.
Let´s say i have the following code:
var fruits = from xele in xDoc.Root.Element("Fruits").Elements("Fruit")
select new Fruit()
{
Weight = xele.Element("Weight").Value
}
How can i extend the query to join the correct FruitSpecification tag?
The goal would be to be able to write this:
var fruits = from xele in xDoc.Root.Element("Fruits").Elements("Fruit")
//some join?
select new Fruit()
{
Weight = xele.Element("Weight").Value,
Type = xjoinedelement.Element("Type").Value
}
I hope this is understandable, i made this "fruit" sample up, my actual XML is much more complicated...
Yes, simple join will do the trick:
var fruits =
from f in xdoc.Root.Element("Fruits").Elements("Fruit")
join fs in xdoc.Root.Elements("FruitSpecification")
on (string)f.Element("Specification") equals (string)fs.Attribute("id")
select new Fruit()
{
Weight = (int)f.Element("Weight"),
Type = (string)fs.Element("Type")
};
Fruit class definition:
public class Fruit
{
public int Weight { get; set; }
public string Type { get; set; }
}
Try this:
class Fruit
{
public int Weight { get; set; }
public string Type { get; set; }
}
string xml = #"
<root>
<Fruits>
<Fruit>
<Specification>id_1001_0</Specification>
<Weight>23</Weight>
</Fruit>
<Fruit>
<Specification>id_1002_0</Specification>
<Weight>25</Weight>
</Fruit>
</Fruits>
<FruitSpecification id='id_1001_0'>
<Type>Apple</Type>
</FruitSpecification>
<FruitSpecification id='id_1002_0'>
<Type>Orange</Type>
</FruitSpecification>
</root>";
XElement element = XElement.Parse(xml);
IEnumerable<XElement> xFruites = element.Descendants("Fruit");
IEnumerable<XElement> xFruitSpecifications = element.Descendants("FruitSpecification");
IEnumerable<Fruit> frits = xFruites.Join(
xFruitSpecifications,
f => f.Element("Specification").Value,
s => s.Attribute("id").Value,
(f, s) =>
new Fruit
{
Type = s.Element("Type").Value,
Weight = int.Parse(f.Element("Weight").Value)
});

insert multiple xml elements in c#

I want to create XML document with multiple elements inside. The format should be something like this:
<Item>
<Id>2276138</Id>
<Title>92907-03100-00 WASHER, CHAIN</Title>
<Link>http://www.mywebsite.com/Product.aspx?ProductId=2453575&SKU=92907-03100-00</Link>
<Price>0.0000</Price>
<Description>WASHER, CHAIN (92907-03100-00)</Description>
<Condition>New</Condition>
<Brand />
<Product_Type />
<Availability>In Stock</Availability>
<Manufacturer>Suzuki</Manufacturer>
</Item>
Everything is ok after the first loop of fetching data, but once the 2nd loop is done, I have this:
<Item></Item>
<Item>
<Id>2276138</Id>
<Title>92907-03100-00 WASHER, CHAIN</Title>
<Link>http://www.mywebsite.com/Product.aspx?ProductId=2453575&SKU=92907-03100-00</Link>
<Price>0.0000</Price>
<Description>WASHER, CHAIN (92907-03100-00)</Description>
<Condition>New</Condition>
<Brand />
<Product_Type />
<Availability>In Stock</Availability>
<Manufacturer>Suzuki</Manufacturer>
</Item>
So after every round of fetching I get the empty item element and just the last element is populated.
Here is the code for the loop:
while (rdr.Read())
{
if (rdr.HasRows)
{
XmlElement part = docOrders.CreateElement("Item");
id.InnerText = rdr[0].ToString();
part.AppendChild(id);
title.InnerText = rdr[1].ToString();
part.AppendChild(title);
link.InnerText = rdr[2].ToString();
part.AppendChild(link);
price.InnerText = rdr[3].ToString();
part.AppendChild(price);
desc.InnerText = rdr[4].ToString();
part.AppendChild(desc);
cond.InnerText = rdr[5].ToString();
part.AppendChild(cond);
brand.InnerText = rdr[6].ToString();
part.AppendChild(brand);
productType.InnerText = rdr[7].ToString();
part.AppendChild(productType);
availability.InnerText = rdr[8].ToString();
part.AppendChild(availability);
manufacturer.InnerText = rdr[9].ToString();
part.AppendChild(manufacturer);
root.AppendChild(part);
}
}
rdr.Close();
}
How Can I solve this, so the data will be fetched correctly?
Thanks in advance
where do you create the id, title, etc. nodes? It looks like you're reusing these instead of creating new nodes, that's why it's not working correctly.
If you're reusing a child node, it will remove it from the current node and insert it into the new node, that's why you're seeing the empty element.
Also check this question here on SO, basically the same exact problem.
You haven't said which version of .NET you are using. If using .NET 3.5 or above, then I would recommend using LINQ to XML, especially if you can get strongly-typed data from your query. For example:
using System;
using System.Linq;
using System.Xml.Linq;
public class Testing
{
private void Main()
{
var items = new[]
{
new DataItem
{
Id = 2276138,
Title = "92907-03100-00 WASHER, CHAIN",
Link =
new Uri(
"http://www.mywebsite.com/Product.aspx?ProductId=2453575&SKU=92907-03100-00"),
Price = 0.0M,
Description = "WASHER, CHAIN (92907-03100-00)",
Condition = "New",
Availability = "In Stock",
Manufacturer = "Suzuki"
},
new DataItem
{
Id = 2276139,
Title = "92907-03100-01 WASHER, CHAIN",
Link =
new Uri(
"http://www.mywebsite.com/Product.aspx?ProductId=2453575&SKU=92907-03100-00"),
Price = 0.0M,
Description = "WASHER, CHAIN (92907-03100-00)",
Condition = "New",
Availability = "In Stock",
Manufacturer = "Suzuki"
},
new DataItem
{
Id = 2276140,
Title = "92907-03100-02 WASHER, CHAIN",
Link =
new Uri(
"http://www.mywebsite.com/Product.aspx?ProductId=2453575&SKU=92907-03100-00"),
Price = 0.0M,
Description = "WASHER, CHAIN (92907-03100-00)",
Condition = "New",
Availability = "In Stock",
Manufacturer = "Suzuki"
},
};
var doc = new XDocument(
new XElement(
"Items",
from item in items
select
new XElement(
"Item",
new XElement("Id", item.Id),
new XElement("Title", item.Title),
new XElement("Link", item.Link),
new XElement("Price", item.Price),
new XElement("Description", item.Description),
new XElement("Condition", item.Condition),
new XElement("Brand", item.Brand),
new XElement("Product_Type", item.ProductType),
new XElement("Availability", item.Availability),
new XElement("Manufacturer", item.Manufacturer))));
}
public class DataItem
{
public int Id { get; set; }
public string Title { get; set; }
public Uri Link { get; set; }
public decimal Price { get; set; }
public string Description { get; set; }
public string Condition { get; set; }
public string Brand { get; set; }
public string ProductType { get; set; }
public string Availability { get; set; }
public string Manufacturer { get; set; }
}
}
Have you considered using the System.XML.Serialisation namespaces, which has an XMLSerializer class which does this kind of thing very well for you? There's some MSDN documentation here - http://msdn.microsoft.com/en-us/library/swxzdhc0.aspx - which goes into depth with some good examples, and a shorter description here which has enough for the basics - http://support.microsoft.com/kb/815813

Categories

Resources