LINQ to XML return second element - c#

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");

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;

Xelement adds element value to itself twice

In my code i iterate through an xelement and have it return the value of each node within that element e.g.
foreach(XElement n in XDocument.Descedants("element_name)
{
Console.WriteLine("Searching: " n.Value);
}
My problem is the both <Directory> elements are returned in the string
Searching: C:\Users\215358\OneDrive\MusicC:\Users\215358\Dropbox\Music
My XML file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Directories>
<Directory>C:\Users\215358\OneDrive\Music</Directory>
<Directory>C:\Users\215358\Dropbox\Music</Directory>
</Directories>
I expect it to output the second line element in <Directory> like this:
C:\Users\215358\Dropbox\Music
Why is this happening?
XElement.Value gets the concatenated text contents of an element. This includes the text of child elements which is not always very helpful. If you just want the text from the current element, you can find the text node in its child nodes.
foreach(XElement n in XDocument.Descedants("Directory"))
{
var text = n.Nodes().Where (x => x is XText).Cast<XText>().FirstOrDefault ();
if(text!=null){
Console.WriteLine("Searching: " + text.Value);
}else{
Console.WriteLine("No text node found");
}
}
Since you want to iterate through each entry and search element value, you could do something like this.
foreach (var element in doc.Descendants("Directory"))
{
if((string)element.Value == "searchstring")
{
// your logic
}
}
In case if you are looking for second element in the xml, you could apply Skip extension to skip specified count of elements.
var secondelement = doc.Descendants("Directory").Skip(1); // Skip first element;
or if you are looking for last element, you could take Last or LastOrDefault extension.
var lastelement = doc.Descendants("Directory").LastOrDefault();
Check this example.

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();

How can I know the index of a XML Tag

How can I get the index of my current XML tag ?
Example:
<User>
<Contact>
<Name>Lucas</Name>
</Contact>
<Contact>
<Name>Andre</Name>
</Contact>
...
</User>
I'm trying the code below
foreach (var element2 in doc2.Root.Descendants())
{
String name = element.Name.LocalName;
String value = element.Value;
}
I want to know if I'm reading the first <Contact> tag, or the second, or the third...
Using the appropriate overload of Select will yield the index as you enumerate the collection.
var userContacts = doc2.Root
.Descendants()
.Where(element => element.Name == "Contact")
.Select((c, i) => new {Contact = c, Index = i});
foreach(var indexedContact in userContacts)
{
// indexedContact.Contact
// indexedContact.Index
}
Note: I added the .Where because .Descendants will recurse.
You can use a for statement, then you'll always know the index. I am making an assumption that Descendants() can be used in a for statement.
The other possibility it to create a count variable outside the foreach.
int count = 0
foreach (var element2 in doc2.Root.Descendants())
{
String name = element.Name.LocalName;
String value = element.Value;
count++;
}
Replace your foreach loop with a normal for loop:
for (int i = 0; i < doc2.Root.Descendants().Count(); i++)
{
String name = doc2.Root.Descendants()[i].Name.LocalName;
String value = doc2.Root.Descendants()[i].Value;
}
Then use i to see if you're reading the first, second, third, etc. tag.
There is no way to get the index of a foreach enumerator without using an external counter.. AFAIK.
This also presents an efficiency problem, as you have to process the Descendants method twice every loop iteration, so I recommend keeping a List representing the Descendants outside of the for loop, and then use it like this:
var desecendants = doc2.Root.Descendants().ToList();
for (int i = 0; i < descendants.Count; i++)
{
String name = descendants[i].Name.LocalName;
String value = descendants[i].Value;
}
Use a variable as counter and put the result into an array. The problem here is, that you need to know the size of the array in advance.
int i = 0;
foreach (var element in doc2.Root.Descendants()) {
name[i] = element.Name.LocalName;
value[i] = element.Value;
i++;
}
with the use of a List<T> you don't have this problem
var list = new List<KeyValuePair<string,string>>();
foreach (var element in doc2.Root.Descendants()) {
list.Append(new KeyValuePair(element.Name.LocalName, element.Value));
}
I don't think you can with foreach, try using a normal for loop instead.
To get the position of your current node without counters (as previous solutions pointed out) you'll need to write a function to build up a the XPath of your current XmlElement. The only way to do it is to traverse the document from your node using parent node and previous siblings. That way you'll be able to build up the exact XPath to access your node from the document. Here's a sample taken from here
public static string GetXPath_UsingPreviousSiblings(this XmlElement element)
{
string path = "/" + element.Name;
XmlElement parentElement = element.ParentNode as XmlElement;
if (parentElement != null)
{
// Gets the position within the parent element, based on previous siblings of the same name.
// However, this position is irrelevant if the element is unique under its parent:
XPathNavigator navigator = parentElement.CreateNavigator();
int count = Convert.ToInt32(navigator.Evaluate("count(" + element.Name + ")"));
if (count > 1) // There's more than 1 element with the same name
{
int position = 1;
XmlElement previousSibling = element.PreviousSibling as XmlElement;
while (previousSibling != null)
{
if (previousSibling.Name == element.Name)
position++;
previousSibling = previousSibling.PreviousSibling as XmlElement;
}
path = path + "[" + position + "]";
}
// Climbing up to the parent elements:
path = parentElement.GetXPath_UsingPreviousSiblings() + path;
}
return path;
}
Assuming that's really what you really need, depending on the document size it could be resource intensive. If you only require the index, I'd recommend using one of the other methods.

traverse every element in xml tree using linq to xml

I would like to traverse every element and attribute in an xml and grab the name an value without knowing the names of the elements in advance. I even have a book on linq to xml with C# and it only tells me how to query to get the value of elements when I already know the name of the element.
The code below only gives me the most high level element information. I need to also reach all of the descending elements.
XElement reportElements = null;
reportElements = XElement.Load(filePathName.ToString());
foreach (XElement xe in reportElements.Elements())
{
MessageBox.Show(xe.ToString());
}
Elements only walks one level; Descendants walks the entire DOM for elements, and you can then (per-element) check the attributes:
foreach (var el in doc.Descendants()) {
Console.WriteLine(el.Name);
foreach (var attrib in el.Attributes()) {
Console.WriteLine("> " + attrib.Name + " = " + attrib.Value);
}
}
You should try:
reportElements.Descendants()

Categories