XML Lambda query in C# - c#

I have an XmlDocument object and xml in the format:
<?xml version="1.0" encoding="utf-8"?>
<S xmlns="http://server.com/DAAPI">
<TIMESTAMP>2010-08-16 17:25:45.633</TIMESTAMP>
<MY_GROUP>
<GROUP>1 </GROUP>
<NAME>Amsterdam</NAME>
....
</MY_GROUP>
<MY_GROUP>
<GROUP>2 </GROUP>
<NAME>Ireland</NAME>
....
</MY_GROUP>
<MY_GROUP>
<GROUP>3 </GROUP>
<NAME>UK</NAME>
....
</MY_GROUP>
Using a Lambda expression (or Linq To XML if it's more appropriate) on the XmlDocument object how can i do the following:
get the text of a specific element, say the text of NAME where GROUP = 1
the value of the first occurance of the element "NAME"
Thanks a lot

Assuming you mean XDocument rather than XmlDcoument:
First question:
XNamespace ns = "http://server.com/DAAPI";
string text = (from my_group in doc.Elements(ns + "MY_GROUP")
where (int) my_group.Element(ns + "GROUP") == 1
select (string) my_group.Element(ns + "NAME")).First();
I didn't really understand the second question... what do yuo mean by "contains an element of that name"? Which name? And if you're checking for NAME being equal to a give name, wouldn't you already know that name? Did you perhaps mean the value of GROUP for a specific name? If so, it's easy:
XNamespace ns = "http://server.com/DAAPI";
int group = (from my_group in doc.Elements(ns + "MY_GROUP")
where (string) my_group.Element(ns + "NAME")
select (int) my_group.Element(ns + "GROUP")).First();
Both of these queries assume that the values do exist, and that each MY_GROUP element has a GROUP and NAME subelement. Please let us know if that's not the case.

I have used Linq to XML.
string input = "<?xml version=\"1.0\" encoding=\"utf-8\"?><S xmlns=\"http://server.com/DAAPI\"><TIMESTAMP>2010-08-16 17:25:45.633</TIMESTAMP><MY_GROUP><GROUP>1 </GROUP><NAME>Amsterdam</NAME>....</MY_GROUP><MY_GROUP><GROUP>2 </GROUP><NAME>Ireland</NAME>....</MY_GROUP><MY_GROUP><GROUP>3 </GROUP><NAME>UK</NAME>....</MY_GROUP></S>";
var doc = XDocument.Parse(input);
XNamespace ns = "http://server.com/DAAPI";
//The first question
var name = (from elem in doc.Root.Elements(ns + "MY_GROUP")
where elem.Element(ns + "GROUP") != null //Checks whether the element actually exists - if you KNOW it does then it can be removed
&& (int)elem.Element(ns + "GROUP") == 1 //This could fail if not an integer - insure it is if nessasary
select (string)elem.Element(ns + "NAME")).SingleOrDefault();

I understood only your first question. Here you are for the first:
var xmlSource = myGroup.Load(#"../../MyGroup.xml");
var q = from c in xmlSource.myGroup
where c.group = 1
select c.name;

Related

XDocument C# parse node value inside node

I have an XML document which basically looks like this:
<Item>
<Seller>
<UserID>SomeSeller</UserID>
<FeedbackScore>2535</FeedbackScore>
</Seller>
</Item>
Now I'm trying to parse the document like following:
var document = XDocument.Parse(upcList);
XNamespace ns = "urn:ebay:apis:eBLBaseComponents";
var result= document
.Descendants(ns + "Item")
.Select(item => new CustomClass
{
FeedBackScore = Convert.ToInt32(item.Descendants(ns+ "Seller")
.Where(p=>p.Name.LocalName=="FeedbackScore").FirstOrDefault()),
Sales = (int) item.Element(ns+"QuantitySold"),
Location = (string)item.Element(ns+"Location"),
CurrentPrice = (double)item.Element(ns + "CurrentPrice"),
Title = (string)item.Element(ns + "Title"),
ItemID = (string)item.Element(ns + "ItemID")
}).ToList();
Please note this part how I try to parse the FeedbackScore node value:
FeedBackScore = Convert.ToInt32(item.Descendants(ns+ "Seller")
.Where(p=>p.Name.LocalName=="FeedbackScore").FirstOrDefault()),
But when I try to parse it I'm getting all "FeedbackScore" nodes values as "0" :(
Can someone tell me what am I doing wrong and how can I fetch this value inside this node "2535"?
FeedBackScore = Convert.ToInt32(item.Descendants(ns + "FeedbackScore").Value)
You have mistakenly checked the names of the Seller Nodes not its Children. By doing so, the FirstOrDefault() will yield null(condition of Where() is never met due the wrong Node) and Convert.ToIn32(null) will yield 0.
To fix this, you can go for the "FeedbackScore" Node directly and Convert its Value like this
FeedBackScore = Convert.ToInt32(item.Descendants("FeedBackValue").FirstOrDefault()?.Value),
Descendants here will return Seller elements, and then you check if any of them have the name FeedbackScore. This isn't possible - they can't have two names at once.
Assuming you want the FeedbackScore only if the parent is Seller, you need to read the Elements of the Seller elements.
I'd also note you can use the explicit conversions in the same way you do for other properties.
Putting that together, this would work:
FeedBackScore = (int) item.Elements(ns + "Seller")
.Elements(ns + "FeedbackScore")
.Single()
If this element isn't always present, you can default to 0:
FeedBackScore = (int?) item.Elements(ns + "Seller")
.Elements(ns + "FeedbackScore")
.SingleOrDefault() ?? 0;

LINQ xml finding nodes returns null

I try to parse an xml file with XDocument class, with criteria that if the child node matches a given string, its parent node is selected.
<SalesQuotes xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://api.some.com/version/1">
<Pagination>
<NumberOfItems>2380</NumberOfItems>
<PageSize>200</PageSize>
<PageNumber>1</PageNumber>
<NumberOfPages>12</NumberOfPages>
</Pagination>
<SalesQuote>
<Guid>825634b9-28f5-4aa7-98e7-5e4a4ed6bc6a</Guid>
<LastModifiedOn>2018-01-09T12:23:56.6133445</LastModifiedOn>
<Comments>Please note:
installation is not included in this quote
</Comments>
</SalesQuote>
</SalesQuotes>
I tried using
var contents = File.ReadAllText(path: "test1.xml");
var doc = XDocument.Parse(contents);
var root = doc.Root;
var sq = root.Elements("SalesQuote");//return null
var theQuote = root.Elements("SalesQuote").Where(el => el.Element("Guid").Value == "825634b9-28f5-4aa7-98e7-5e4a4ed6bc6a");//return null
var theAlternativeQuote =
from el in doc.Descendants("SalesQuote").Elements("Guid")
where el.Value == "825634b9-28f5-4aa7-98e7-5e4a4ed6bc6a"
select el;//return null
I can't seem to find what's wrong.
Any help is much appreciated! Thanks.
You ignored the namespace bro.
Do remove the xmlns attribute in your XML or try this:
var contents = File.ReadAllText("XMLFile1.xml");
var doc = XDocument.Parse(contents);
var root = doc.Root;
XNamespace ns = "http://api.some.com/version/1";
var sq = root.Descendants(ns + "SalesQuotes"); //return null
var theQuote = root.Elements(ns + "SalesQuote")
.Where(el => el.Element(ns + "Guid").Value == "825634b9-28f5-4aa7-98e7-5e4a4ed6bc6a"); //return null
var theAlternativeQuote =
from el in doc.Descendants(ns + "SalesQuote").Elements(ns + "Guid")
where el.Value == "825634b9-28f5-4aa7-98e7-5e4a4ed6bc6a"
select el; //return null
If you are not too concerned about keeping your current implementation, you could consider using a Typed DataSet and load your XML into fully typed, structured objects.
Querying those objects with Linq will be more straight forward than what I see in your current implementation.
You might also find this useful:
SO Question: Deserialize XML Document to Objects
yap, you're missing the namespace that you can grab with document.Root.GetDefaultNamespace()
// Load
var document = XDocument.Parse(xml);
var xmlns = document.Root.GetDefaultNamespace();
// Find
var query = from element in document
.Descendants(xmlns + "SalesQuote")
.Elements(xmlns + "Guid")
where element.Value == "825634b9-28f5-4aa7-98e7-5e4a4ed6bc6a"
select element;

How to get XML Attribute and Element with One Linq Query?

I have an XML document that looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<searchlayers>
<searchlayer whereClause="ProjectNumber=a">Herbicide</searchlayer>
<searchlayer whereClause="ProjectNumber=b">Herbicide - Point</searchlayer>
<searchlayer whereClause="ProjectNumber=c">miscellaneous</searchlayer>
<searchlayer whereClause="ProjectNumber=d">miscellaneous - Point</searchlayer>
<searchlayer whereClause="ProjectNumber=e">Regrowth Control</searchlayer>
<searchlayer whereClause="ProjectNumber=f">Regrowth Control - Point</searchlayer>
<searchlayer whereClause="ProjectNumber=g">Tree Removal</searchlayer>
<searchlayer whereClause="ProjectNumber=h">Tree Removal - Point</searchlayer>
<searchlayer whereClause="ProjectNumber=i">Trimming</searchlayer>
<searchlayer whereClause="ProjectNumber=j">Trimming - Point</searchlayer>
</searchlayers>
</configuration>
Is it possible to write one single Linq statement to get each of the element (e.g. Herbicide, miscellaneous, Regrowth Control... etc) with its matching whereClause (e.g. for Herbicide, the where clause would be "ProjectNumber=a")?
I can write two statements separately, one to get the elements, one to get the attributes, but it would be nice to write just one Linq statement that gets both at the same time.
Thanks.
Yes it is possible. But there are many possible data structure can be used to store list of 2 values pair, here is one example using Tuple :
XDocument doc = XDocument.Load("path_to_xml_file.xml");
List<Tuple<string, string>> result =
doc.Root
.Descendants("searchlayer")
.Select(o => Tuple.Create((string) o, (string) o.Attribute("whereClause")))
.ToList();
You can create a set of anonymous objects as follows:
var result = root.Element("searchlayers")
.Elements("searchlayer")
.Select(i =>
new {attribute = i.Attribute("whereClause").Value,
value = i.Value});
This will give a set of records, where the attributes are paired with the element values.
If you want this in query syntax, it looks like this:
var result = from el in root.Elements("searchlayers").Elements("searchlayer")
select new {attribute = el.Attribute("whereClause").Value,
value = el.Value};
You can use this to to select all elements matching Herbicide whose whereClause matches ProjectNumber=a
IEnumerable<XElement> result =
from el in doc.Elements("Herbicide")
where (string)el.Attribute("whereClause") == "ProjectNumber=a"
select el;
Another alternative would be:
var result = doc.Descendants()
.Where(e => e.Attribute("ProjectNumber=a") != null)
.ToList();
Which should provide you every element whose whereClause equals "ProjectNumber=a".
You can use the Attributes property to get the attribute from the XML node, along with the InnerText, like so:
XmlDocument doc = new XmlDocument();
doc.LoadXml(yourxml);
XmlNodeList xlist = doc.GetElementsByTagName("searchlayer");
for(int i=0;i<xlist.Count;i++)
{
Console.WriteLine(xlist[i].InnerText + " " + xlist[i].Attributes["whereClause"].Value);
}
If you must use LINQ, you could use XDocument and then return anonymous class objects consisting of the attribute and text, like so:
XDocument xdoc = XDocument.Load(yourxmlfile);
var result = xdoc.Root.Elements("searchlayers").Elements("searchlayers").Select(x => new {attr = x.Attribute("whereClause").Value, txt = x.Value});
foreach (var r in result)
{
Console.WriteLine(r.attr + " " + r.txt);
}

XML Xpath expression

I am trying to get the elements title and runtime (siblings) where the runtime value is larger than the input value. My C# code with the XPath expression is:
ElementValue = 140;
nodeList = root.SelectNodes(#"/moviedb/movie[./runtime>'" + ElementValue + "'/title | /moviedb/movie[./runtime>'" + ElementValue + "']/runtime");
This XPath expression is not returning anything.
My XML file:
<moviedb>
<movie>
<imdbid>tt0120689</imdbid>
<genres>Crime,Drama,Fantasy,Mystery</genres>
<languages>English,French</languages>
<country>USA</country>
<rating>8.5</rating>
<runtime>189</runtime>
<title lang="english">The Green Mile</title>
<year>1999</year>
</movie>
<movie>
<imdbid>tt0415800</imdbid>
<genres>Action,Animation,Drama,Thriller</genres>
<languages>English</languages>
<country>USA</country>
<rating>4.5</rating>
<runtime>139</runtime>
<title lang="english">Fight Club</title>
<year>2004</year>
</movie>
</moviedb>
You can instead use linq2xml
var doc=XDocument.Load(path);
var movies=doc.Elements("movie")
.Where(x=>(int)x.Element("runtime")>input)
.Select(x=>new
{
Title=x.Element("title").Value,
Runtime=(int)x.Element("runtime")
});
You can now iterate over movies
foreach(var movie in movies)
{
movie.Title;
movie.Runtime;
}
You seem to be applying the values you want off the node as a filter criteria, which won't work. I would go about this another way, first finding the nodes which meet the criteria:
nodeList = root.SelectNodes(#"/moviedb/movie[runtime > " + ElementValue + "]");
And then grabbing the child elements from each:
foreach (var node in nodeList)
{
Debug.WriteLine(node.SelectSingleNode("title").InnerText);
Debug.WriteLine(node.SelectSingleNode("runtime").InnerText);
}
You can do this using a single XPath expression by performing a union i.e. the | operator. As mentioned in other answers here, you had your select inside your predicate which would not result in the correct answer for you anyway.
Note, if you want to see if a number is bigger than another number, unless you are using a Schema driven data-type aware XQuery engine you will need to cast the text() to a number before performing the comparison. In this instance I have assumed an xs:int will be suitable for you. Also you can use the atomic gt as opposed to = which may be more efficient.
ElementValue = 140;
nodeList = root.SelectNodes(#"/moviedb/movie[xs:int(runtime) gt " + ElementValue + "]/(title | runtime)");

Noob LINQ - reading, filtering XML with XDocument

I'm just learning XDocument and LINQ queries. Here's some simple XML (which doesn't look formatted exactly right in this forum in my browser, but you get the idea . . .)
<?xml version="1.0" encoding="utf-8"?>
<quiz
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.example.com/name XMLFile2.xsd"
title="MyQuiz1">
<q_a>
<q_a_num>1</q_a_num>
<q_>Here is question 1</q_>
<_a>Here is the answer to 1</_a>
</q_a>
<q_a>
<q_a_num>2</q_a_num>
<q_>Here is question 2</q_>
<_a>Here is the answer to 2</_a>
</q_a>
</quiz>
I can iterate across all elements in my XML file and display their Name, Value, and NodeType in a ListBox like this, no problem:
XDocument doc = XDocument.Load(sPath);
IEnumerable<XElement> elems = doc.Descendants();
IEnumerable<XElement> elem_list = from elem in elems
select elem;
foreach (XElement element in elem_list)
{
String str0 = "Name = " + element.Name.ToString() +
", Value = " + element.Value.ToString() +
", Nodetype = " + element.NodeType.ToString();
System.Windows.Controls.Label strLabel = new System.Windows.Controls.Label();
strLabel.Content = str0;
listBox1.Items.Add(strLabel);
}
...but now I want to add a "where" clause to my query so that I only select elements with a certain name (e.g., "qa") but my element list comes up empty. I tried . . .
IEnumerable<XElement> elem_list = from elem in elems
where elem.Name.ToString() == "qa"
select elem;
Could someone please explain what I'm doing wrong? (and in general are there some good tips for debugging Queries?) Thanks in advance!
The problem is that the Name property is not a string, it's an XName. When you ToString it, you get a lot more than you think.
While it's possible to write the query in the way you're attempting to, also consider these possibilites:
//from nodes immediately below this one
IEnumerable<XElement> elem_list = doc.Elements("qa");
//from nodes of all levels below this node.
IEnumerable<XElement> elem_list = doc.Descendants("qa");
I would perhaps change your query to something that looks more like this
var query = from q_a in document.Descendants("q_a")
select new
{
Number = (int)q_a.Element("q_a_num"),
Question = (string)q_a.Element("q_"),
Answer = (string)q_a.Element("_a")
};
With this, you'll pull from each of your q_a descendants the inner elements into an IEnumerable<[Anonymous Type]>, each object containing the number, question, and answer.
However, if you just want to extract the XElements where the name is q_a, you could do this using a where clause.
IEnumerable<XElement> elem_list = elems.Where(elem => elem.Name.LocalName == "q_a");
Of course, as David B showed, the where clause is not necessary here.
IEnumerable<XElement> elem_list = elems.Elements("q_a");

Categories