XDocument C# parse node value inside node - c#

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;

Related

Count ChildElements of the same name, inside an XML Element, with XDocument

I have an XML file that looks like this -
<SST_SignageCompConfig>
<Items>
<Item>
<Index>0</Index>
<Type>1</Type>
<Duration>7</Duration>
<Name>Branding-Colours-for-business.jpg</Name>
</Item>
<Item>
<Index>1</Index>
<Type>1</Type>
<Duration>7</Duration>
<Name>Flower of Life Meditation - Copy.png</Name>
</Item>
</Items>
</SST_SignageCompConfig>
I need to count how many Item Elements there are within the Items Element.
ie how many images there are.
I'm using XDocument, so my XML file is loaded like this -
string configurationPath = System.IO.Path.Combine("C:\\SST Software\\DSS\\Compilations\\" + compName + #"\\Comp.cfg");
XDocument filedoc = XDocument.Load(configurationPath);
I've tried numerous variations of the following, with all returning a null object reference exception
foreach (var item in filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Nodes())
{
string name = filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Attribute("Name").ToString();
files.Append(name + "|");
}
I've found countless examples of how to count how many different child elements are within an element, but I need to know how many instances of the same element exist.
Can anyone point me in the right direction?
You can select all names like so:
var names = from item in filedoc.Descendants("Item")
select (string)item.Element("Name");
Or without the query syntax:
var names = filedoc.Descendants("Item").Elements("Name").Select(e => e.Value);
You can get only unique names by:
var uniqueNames = names.Distinct();
You're on the right track. Try finding out exactly which invocation is giving you the NullReferenceException. My guess is that it's the attempt to find:
.Element("SST_SignageCompConfig")
Which is your root. Try the following instead:
// note the difference between .Element and .Elements
var count = filedoc.Root.Element("Items").Elements("Item").Count();
You could also use XPath to help you nail down the navigation within your XDocument:
// returns the current top level element
var element = filedoc.Root.XPathSelectElement(".");
// If the returned element is "SST_SignageCompConfig", then:
var nextElement = filedoc.Root.XPathSelectElement("./Items")
// If the "." element is *not* "SST_SignageCompConfig", then try and locate where in your XML document that node is.
// You can navigate up with .Parent and down with .Element(s)
And so on.
How about:
var nav = fileDoc.CreateNavigator();
XPathNodeIterator navShape = nav.Select("/SST_SignageCompConfig/Items");
navShape.MoveNext()
var count = navShape.Count;
If your xml has only one Items element, this should do the trick:
filedoc.Descendants("Item")
.GroupBy(e => e.Element("Name")!=null? e.Element("Name").Value:String.Empty)
.Select(g => new
{
Name = g.Key,
Count = g.Count()
});
Because "Name" is an element and not an attribute of your xml structure.
can you try replacing this?
string name = filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Element("Name").ToString();

Find value in XML and output it

I have this XML
<?xml version="1.0" encoding="UTF-8" ?>
<uclassify xmlns="http://api.uclassify.com/1/ResponseSchema" version="1.01">
<status success="true" statusCode="2000"/>
<readCalls>
<classify id="cls1">
<classification textCoverage="1">
<class className="female" p="0.932408"/>
<class className="male" p="0.0675915"/>
</classification>
</classify>
</readCalls>
</uclassify>
or similar. What matters is, I don't have
<tag>value</tag>
but
<tag attribute1 attribute2 ... />.
What I want to output is for instance
attribute1: attributevalue1
So I want to enter a term like "female" and I want it to output 0.932408.
What I tried to get started
string xml = HttpGet("http://uclassify.com/browse/" + username + "/" + classifiername + "/" + operation + "?" + paramz.ToString());
XDocument doc = XDocument.Parse(xml);
var list = doc.Root.Elements("uclassify")
.Select(element => element.Value)
.ToList();
But list is always empty, which is presumably because there are no values, only attributes.
EDIT:
current version
string xml = HttpGet("http://uclassify.com/browse/" + username + "/" + classifiername + "/" + operation + "?" + paramz.ToString());
XDocument doc = XDocument.Parse(xml);
XNamespace ns = "http://api.uclassify.com/1/ResponseSchema";
var list = doc.Root.Descendants(ns + "class")
.Select(element => element.Value)
.ToList();
textBox1.Text = string.Join(",", list.ToArray());
Result is a comma.
SO your problem is the default namespace:
xmlns="http://api.uclassify.com/1/ResponseSchema"
To fix it, you need to qualify your element selector.
You can do this like so...
XNamespace ns = "http://api.uclassify.com/1/ResponseSchema";
var list = doc.Root.Descendants(ns + "class")
.Select(element => element.Value)
.ToList();
I've modified your code slightly to select all the class nodes, but you can see that I've prefaced your "class" in the Descendants() call with the namespace variable ns.
EDIT:
So now your problem is that you are selecting the element's values, not the attributes values...
so if we are building a dictionary of attribute names to attribute values, you might want to use some code like this:
Dictionary<string,double> dictionary = doc.Root.Descendants(ns + "class")
.ToDictionary(
element => element.Attribute("className").Value,
element => double.Parse(element.Attribute("p").Value));
foreach(var item in dictionary)
{
Console.WriteLine(string.Format("{0}: {1}", item.Key, item.Value));
}
so a couple of caveats:
I'm assuming that each of the className attributes in the class attributes are unique, otherwise you'll have an exception
I'm assuming the value of the attribute p is a double.

XML Lambda query in 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;

LINQ to XML return second element

I'm trying to return to the second <link> element in the XML from Flickr.
This always returns the first element:
ImageUrl = item.Element(ns + "link").Attribute("href").Value,
And this fails?
ImageUrl = item.Elements(ns + "link")[1].Attribute("href").Value,
Try .Skip(1).First().Attribute.... on the second snippet.
According to the documentation Element returns the first matching child - Elements returns all matching children. To get the second just skip the first item and take the next one.
ImageUrl = item.Elements(ns + "link").Skip(1).First().Attribute("href").Value;
If you can't be certain there are two children you could do this:
XElement xe = item.Elements(ns + "link").Skip(1).FirstOrDefault();
if(xe != null)
{
ImageUrl = ex.Attribute("href").Value;
}
You can use ElementAt to get the element at a specified position in an enumerable:
imageUrl = (string)item.Elements(ns + "link").ElementAt(1).Attribute("href");

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