Linq: how to get values of second level nodes - c#

I have an xml that looks like
<response>
<book>
<title>Harry Potter</title>
<Price>
<Currency>USD</Currency>
<Amount>$19.89</Amount>
</Price>
</book>
</response>
I have no problem getting the title element, but when I'm trying to get all values within price, it doesn't work.
var prices = from price in xmlDoc.Descendants("Price")
select new
{
currency = price.Element("currency").Value,
amount = price.Element("amount").Value,
};

Given your XML snippet, this code can populate a sequence of objects
var books = from book in document.Descendants("book")
let title = book.Element("title").Value
let price = book.Element("Price")
let currency = price.Element("Currency").Value
let amount = price.Element("Amount").Value
select new
{
Title = title,
Price = new
{
Currency = currency,
Amount = amount
}
};
This structure follows the same hierarchy as established by the XML. You can, of course, flatten it into a single level if you wish.

I changed the casing of the currency and amount elements to title casing and it worked fine.
var prices =
from price in xmlDoc.Descendants("Price")
select new
{
currency = price.Element("Currency").Value,
amount = price.Element("Amount").Value,
};
XML is case-sensitive.

Related

How to get different values with same name from xml with linq

XML - Code:
<Store>
<Products>
<Product id="PROD01">
<Title>Product 1</Title>
<Description><![CDATA[Product <b>1</b> description]]></Description>
<Image>prod01.gif</Image>
<Specs>
<Spec>Good computer</Spec>
<Spec>Good display</Spec>
<Spec>Latest version</Spec>
</Specs>
<Availability>same day</Availability>
</Product>
<Product id="PROD02">
<Title>Product 2</Title>
<Description><![CDATA[Product <b>2</b> description]]></Description>
<Image>prod01.gif</Image>
<Specs>
<Spec>Good computer</Spec>
<Spec>Soon available</Spec>
</Specs>
<Availability>next day</Availability>
</Product>
</Products>
</Store>
C# - Code:
public List<DetailList> GetDetails()
{
DetailList d = new DetailList();
List<DetailList> DetailLists =
(from product in xdocList.Descendants("Product")
join detail in xdocDetail.Descendants("Product")
on (string)product.Attribute("id") equals (string)detail.Attribute("id")
into outerProducts
from outerProduct in outerProducts
select new DetailList
{
Detail1 = (string)product.Attribute("id"),
Detail2 = (string)product.Element("Title"),
Detail3 = (string)product.Element("Description"),
Detail4 = (string)product.Element("Image"),
Detail5 = (string)outerProduct.Elements("Specs")
Detail6 = (string)outerProduct.Element("Availability"),
Detail7 = (string)product.Element("Price"),
}).ToList();
return DetailLists;
}
Output: Good computerGood displayLatest version
But wanted output is:
Good computer
Good display
Latest version
For output I used asp:repeater. I tried to add tags like < b r/> and much more, but cant'find my mistake, how to get Spec to three different strings, not only one string. How to achive that?
I am not sure why you are joing the nodes with self, but as per your XML it is not required. You can simply project the elements like this:-
public static List<DetailList> GetDetails(XDocument xdocList)
{
DetailList d = new DetailList();
List<DetailList> DetailLists = (from product in xdocList.Descendants("Product")
select new DetailList
{
Detail1 = ((string)product.Attribute("id")),
Detail2 = ((string)product.Element("Title")),
Detail3 = ((string)product.Element("Description")),
Detail4 = ((string)product.Element("Image")),
Detail5 = product.Element("Specs")
.Elements("Spec")
.Select(x => (string)x).ToList(),
Detail6 = ((string)product.Element("Availability")),
Detail7 = ((string)product.Element("Price")),
}).ToList();
return DetailLists;
}
Since you need all the Specs separately, you should have a collection of string and not just string. So the datatype of property Detail5 should be:-
List<string> or string[]
Friend got solution from Rahul
Detail5 = String.Join("<br/>", outerProduct.Element("Specs").Elements("Spec").Select(x => (string)x).ToList()),
Thanks #Rahul Singh

Xdocument getting element node value from another note in the xml file

I'm trying to get some data from an XML file - see below.
Basically for each session data, I get all the elements in it and store it but I need to get the movie_Name from the elements with reference by the movie.
<Schedule_Data>
<Movies>
<Movie>
<Cinema_ID>3169101</Cinema_ID>
<Movie_ID>1012689</Movie_ID>
<Movie_Name>2D Captain America: Civil War</Movie_Name>
<Rating>PG13</Rating>
<Runtime>160</Runtime>
</Movie>
<Movie>
<Cinema_ID>3169101</Cinema_ID>
<Movie_ID>1012984</Movie_ID>
<Movie_Name>2D Zootopia</Movie_Name>
<Rating>PG</Rating>
<Runtime>115</Runtime>
</Movie>
</Movies>
<Sessions>
<Session>
<Cinema_ID>8888888</Cinema_ID>
<Movie_ID>1012689</Movie_ID>
<Session_ID>1083592422</Session_ID>
<Price_group_code>10007</Price_group_code>
<Auditorium_Number>9</Auditorium_Number>
<Assigned_Seating>True</Assigned_Seating>
<Attribute></Attribute>
<Date_time>20160607141000</Date_time>
<Total_Seats>87</Total_Seats>
<Available_Seats>87</Available_Seats>
</Session>
<Session>
<Cinema_ID>8888888</Cinema_ID>
<Movie_ID>1012984</Movie_ID>
<Session_ID>1083592423</Session_ID>
<Price_group_code>10007</Price_group_code>
<Auditorium_Number>9</Auditorium_Number>
</Session>
</Sessions>
</Schedule_Data>
Currently my code is:
XDocument thisXML = XDocument.Parse(responseText);
//get the dates element (which contains all the date nodes)
XElement datesElement = thisXML.Element("Schedule_Data").Element("Sessions");
//use linq to compile an ienumerable of the date nodes
var dates = from dateNode in datesElement.Elements("Session")
select dateNode;
//get the dates element (which contains all the film nodes)
XElement MoviesElement = thisXML.Element("Schedule_Data").Element("Movies");
foreach (XElement session in dates)
{
//get movie name
var films = from filmnode in MoviesElement.Elements("Movie")
select filmnode;
var movieId = session.Element("Movie_ID").Value;
// This is where i try do the where clause and try get the value but it returns null
var answer = from reply in films
where reply.Element("Movie_ID").Value == movieId
select films.Elements("Movie_Name");
//create a new session import record
ORM.Sessionimportattemptimportedsession newSessionimportattemptimportedsession = new ORM.Sessionimportattemptimportedsession();
//check data and set properties
newSessionimportattemptimportedsession.MovieTitle = answer.ToString();
newSessionimportattemptimportedsession.ScreenNumber = session.Element("Screen_bytNum").Value;
.....
numberOfSessions++;
}
Any suggestions?
You're just performing a join between sessions and movies. Just do this:
var query =
from s in doc.Descendants("Session")
join m in doc.Descendants("Movie")
on (string)s.Element("Movie_ID") equals (string)m.Element("Movie_ID")
select new
{
MovieName = (string)m.Element("Movie_Name"),
Session = s,
Movie = m,
};

How to use Linq To XML to get multiple elements and store them differently?

<MainData id="1" >
<Info>
<Date>2015-06-08 15:00:00</Date>
</Info>
<Data DataRef="uu91"/>
<Data DataRef="uu92">
</Data>
</MainData>
I have an xml file and I want to take the two data element and store them into two different variable so when I do the same value comes out. when I receive these two values I would like to get the ID, Date...
var data = from item in retreiveOptaHomeFixturesXml.Descendants("MainData")
select new
{
ID = item.Attribute("id").Value,
Date = item.Element("Info").Element("Date").Value,
DataRef1 = item.Element("Data").Attribute("DataRef").Value,
Dataref2 = item.Element("Data").Attribute("DataRef").Value,
};
Ideally you should be fetching the DataRef into a list because in each MainData you will have multiple data with DataRef attribute. You can do it like this:-
var data = from item in x1.Descendants("MainData")
let dataNodes = item.Elements("Data")
select new
{
ID = item.Attribute("id").Value,
Date = item.Element("Info").Element("Date").Value,
DataRef1Ref2 = dataNodes.Select(x => (string)x.Attribute("DataRef"))
.ToList()
};

Querying XML elements with identical name with Linq

I have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<ProductTypes>
<ProductType Name="MyProduct">
<Amount>100</Amount>
<Pattern Length="1" AllowedCharacters="ABCD"/>
<Pattern Length="7" AllowedCharacters="EFGH"/>
</ProductType>
</ProductTypes>
Using Linq to XML I can successfully extract information from any number of the element ProductType. However I also need the information from all of the elements Pattern.
XElement xml = XElement.Load("pattern_config.xml");
var productTypes = from productType in xml.Elements("ProductType")
select new {
Name = productType.Attribute("Name").Value,
Amount = Convert.ToInt32(productType.Element("Amount").Value)
// How to get all Pattern elements from that ProductType?
};
How can I do this? Or would you recommend another way accessing this XML?
You can nest queries.
var productTypes = from productType in xml.Elements("ProductType")
select new {
Name = productType.Attribute("Name").Value,
Amount = Convert.ToInt32(productType.Element("Amount").Value),
// How to get all Pattern elements from that ProductType?
Patterns = from patt in productType.Elements("Pattern")
select new { Length = int.Parse(patt.Attribute("Length").Value),
.... }
};

Sorting XDocument elements based on InnerXML

I have an XML document similar to this:
<document>
<post>
<author>Bill Smith</author>
<subject>Test Article</subject>
<dates>
<uploaded>some date</uploaded>
<published>some date</published>
</dates>
<price>
<provider>Amazon</provider>
<cost>1540</cost>
</price>
<price>
<provider>WH Smith</provider>
<cost>2640</cost>
</price>
</post>
<post>
<author>Bill Smith</author>
<subject>Test Article</subject>
<dates>
<uploaded>some date</uploaded>
<published>some date</published>
</dates>
<price>
<provider>Amazon</provider>
<cost>1540</cost>
</price>
<price>
<provider>WH Smith</provider>
<cost>2640</cost>
</price>
</post>
</document>
I'm using XDocument w/ .NET 4.5. I know there are other methods I could use to sort this.
I have it working OK to pull each post and put it into a Post model. However, I would like to sort the price elements and pick out the lowest price (and also the provider) so I can insert it into my EF database.
Any help would be much appreciated, I'm totally stuck on where to even start with this.
You can give this a try:
XDocument doc = XDocument.Load(#"Data.xml");
var elements = doc.Root
.Elements("post")
.Select(post => new
{
Author = post.Element("author").Value,
Subject = post.Element("subject").Value,
Uploaded = Convert.ToDateTime(post.Element("dates").Element("uploaded").Value),
Published = Convert.ToDateTime(post.Element("dates").Element("published").Value),
Price = new
{
P = post
.Elements("price")
.OrderByDescending(price => Convert.ToDecimal(price.Element("cost").Value))
.Select(o => new
{
Provider = o.Element("provider").Value,
Cost = Convert.ToDecimal(o.Element("cost").Value)
})
.First()
}
});
var p = elements.First();
Based on code you provided, see, if this works:
var xmlDocumentElement = xDocument.Load("xmlData.xml").Root;
var posts = xmlDocumentElement.Elements("post").Select(post => new
{
Author = post.Element("author").Value.ToString(),
Subject = post.Element("subject").Value.ToString(),
AvailableDate = DateTime.Parse(post.Descendants("dates").FirstOrDefault().Value),
Price = GetPriceFromPriceXElement(post.Elements("price").Aggregate((prev, next) =>
Decimal.Parse(prev.Element("cost").Value.ToString()) <= Decimal.Parse(next.Element("cost").Value.ToString()) ? prev : next ))
}
);
public Price GetPriceFromPriceXElement(XElement price)
{
return new Price
{
Provider = price.Element("provider").Value.ToString(),
Cost = price.Element("cost").Value.ToString()
};
}
Try this. It should work. Once you've data, you can massage it. Please take care of NULL etc. This is just basic idea around your requirement.
XDocument doc = XDocument.Load(#"XMLFile1.xml");
var posts = doc.Root.Elements("post")
.Select(p =>
new
{
Author = p.Element("author").Value,
Price = p.Elements("price").OrderBy(a => decimal.Parse(a.Element("cost").Value)).First()
});
foreach(var a in posts)
{
Console.WriteLine(a.Author + " " + a.Price);
}
Here is a pretty straightforward attempt (and actually very similar to Ivan G's answer):
var posts = from post in document.Root.Elements("post")
select new
{
Author = post.Element("author").Value,
Subject = post.Element("subject").Value,
Uploaded = DateTime.Parse(post.Element("dates").Element("uploaded").Value),
Published = DateTime.Parse(post.Element("dates").Element("published").Value),
Price = (from price in post.Elements("price")
let cost = decimal.Parse(price.Element("cost").Value)
orderby cost
select new
{
Provider = price.Element("provider").Value,
Cost = cost
}).First()
};
It works fine for the sample data you gave us (except the dates "some date").
If the data might be incomplete or wrong (i.e. missing/misspelled nodes or wrong datatypes) I would suggest to just take each XElement instead of the values: Author = post.Element("author") etc. Maybe you want to allow posts without an uploaded or published date. This way you can inspect and/or cast/convert data later and maybe fix problems properly.

Categories

Resources