Find parent with defined attribute wuth LINQ - c#

I have variable XElement content =
"...
<item text="Name-1" id="1" segment="1" secret-id="Find-Me">
<item text="Name-1-1" id="1.1">
<item text="Name-1-1-1" id="1.1.1" />
<item text="Name-1-1-2" id="1.1.2" />
</item>
</item>
..."
I have also an item with id = 1.1.2
I need to find the first parent of this item which has attribute segment="1" and get its secret-id
How to do this with LINQ?

You mean the "closest ancestor"?
string secretId = element.Ancestors()
.Where(x => (string) x.Attribute("segment") == "1")
.Select(x => (string) x.Attribute("secret-id"))
.FirstOrDefault();
The result will be null if either:
There are no ancestors with an attribute segment="1"
The element with that attribute has no secret-id attribute.

Like this:
item.Ancestors().Select(p => p.Attribute("secret-id")).First(a => a != null)

Related

Parse XML file nodes and store it to a Dictionary

<?xml version="1.0" encoding="utf-8" ?>
<LanguagePacks>
<Language name = "EN">
<item key="play" value="play"/>
<item key="pause" value="pause"/>
<item key="resume" value="resume"/>
<item key="all" value="all"/>
<item key="songs" value="songs"/>
<item key="song" value="song"/>
<item key="skip" value="skip"/>
<item key="next" value="next"/>
<item key="previous" value="previous"/>
<item key="number" value="number"/>
<item key="album" value="album"/>
</Language>
<Language name = "DE">
<item key="play" value="spiel"/>
<item key="pause" value="pause"/>
<item key="resume" value="resume"/>
<item key="all" value="alle"/>
<item key="songs" value="lieder"/>
<item key="song" value="lied"/>
<item key="skip" value="skipp"/>
<item key="next" value="nachste"/>
<item key="previous" value="vorheriger"/>
<item key="number" value="nummer"/>
<item key="album" value="alben"/>
</Language>
</LanguagePacks>
I want to parse the above xml file for Language name == "EN" and store the key value pairs in a dictionary. The code I am trying in below.. But error shows item with same key is already been added. Please help.
XDocument doc = XDocument.Load($"{path}");
var output = doc.Element("LanguagePacks")
.Descendants().Where(r=>(string)r.Attribute("name").Value == "EN")
.Descendants()
.ToDictionary(k => k.Name, v => v.Value);
.ToDictionary(k => k.Name, v => v.Value);
k is an item, if you want your Dictionary like
[0] = {[play, play]}
[1] = {[pause, pause]}
I think for each item, you access the attribute to get value
var output = doc.Element("LanguagePacks")
.Descendants().Where(r=>(string)r.Attribute("name").Value == "EN")
.Descendants() // list of item
.ToDictionary(k => k.Attribute("key").Value, v => k.Attribute("value").Value);
ToDictionary(k => k.Name, v => v.Value) will not work the way you want, because the Name and Value properties are the node name (item) and node contents (nothing), not the xml attributes "key" and "value".
You also should not use Descendants when you actually only want to loop through elements on one level.
Running your code on your example XML gives a NullReferenceException, because some descendants of <LanguagePacks> does not have a name attribute, and you assume they all do.
This code should do what you want. Note that this will also crash if you have duplicate keys, or if an <item /> element is missing the key or value attributes.
doc.Element("LanguagePacks")
.Elements("Language")
.Where(x => x.Attribute("name")?.Value == "EN")
.Elements("item")
.ToDictionary(x => x.Attribute("key").Value, x => x.Attribute("value").Value)
.Dump();
In
.ToDictionary(k => k.Name, v => v.Value);
k.Name will always be item, not the value of key="...".
And I guess that v.Value will always be "", not entirely sure.

Deserialize key/value xml into an object

Having some difficulties deserializing the following xml to an object in the most efficient/cleanest possible way:
<item xsi:type="ns2:Map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<item>
<key xsi:type="xsd:string">mailinglistid</key>
<value xsi:type="xsd:string">1</value>
</item>
<item>
<key xsi:type="xsd:string">uniqueid</key>
<value xsi:type="xsd:string">1a0d0d2195</value>
</item>
<item>
<key xsi:type="xsd:string">name</key>
<value xsi:type="xsd:string">OSM NL</value>
</item>
</item>
This is a single item along with its properties. As you can see the properties are defined as key/value pairs.
Because they are key value pairs i could not find a way to use the Xml deserialize attributes (as described in this question: How to deserialize xml to object )
Therefore i have created the following alternative:
// Retrieves all the elements from the "return" node in the Mailinglist_allResponse node.
var items = response.ResponseFromServer.Descendants(_engineResponseNamespace + "Mailinglist_allResponse").Descendants("return").Elements();
foreach (var item in items)
{
var values = item.Descendants("item").ToArray();
var list = new EngineMailingList
{
MailingListId =
values.Descendants("key")
.First(v => v.Value == "mailinglistid")
.ElementsAfterSelf("value")
.First()
.Value,
UniqueId =
values.Descendants("key")
.First(v => v.Value == "uniqueid")
.ElementsAfterSelf("value")
.First()
.Value,
Name =
values.Descendants("key")
.First(v => v.Value == "name")
.ElementsAfterSelf("value")
.First()
.Value,
};
result.Add(list);
As you can see this is alot more code than when using the deserialize attributes. Is there any way i could still use these attributes so that my code could be cleaner/ efficient? i am going to have to make alot of these functions otherwise.
I don't think attributes will work because of key/value structure. There is no way for a program to infer from the xml alone what properties an object has. I would make a static extension method helper function to get the values:
public static string GetValue(this XElement element, string key){
return values.Descendants("key")
.First(v => v.Value == key)
.ElementsAfterSelf("value")
.First()
.Value;
}
That way your code could look like this:
MailingListId = values.GetValue("mailinglistid"),
UniqueId = values.GetValue("uniqueid"),
Name = values.GetValue("name")

Get XML nodes attributes and set as properties of List<myType>?

Example XML:
<Root>
<Product value="Candy">
<Item value="Gum" price="1.00"/>
<Item value="Mints" price="0.50"/>
</Product>
</Root>
Let's say I have a class with properties:
public class CandyItems
{
public string Value{get; set;}
public string Price{get; set;}
}
And within my main program class, I have a list:
var Candies = new List<CandyItems>;
I am struggling with a concise way to populate the Candies list, using LINQ.
I could do it in steps, like this:
//Get list of Items within <Product value="Candy">
XElement tempCandies = XDocument.Load("file.xml").Root.Elements("Product").Single(c => c.Attributes("value") == "Candy").Descendants("Item");
//Loop through the elements
foreach(var item in tempCandies){
Candies.Add(new CandyItems{Value = item.Attributes("value"), Price = item.Attributes("price")});
}
But it seems like I could do this more concisely with pure LINQ somehow. Or is there another recommended method?
Try this:-
XDocument xdoc = XDocument.Load(#"Path\Candies.xml");
List<CandyItems> Candies = xdoc.Descendants("Item")
.Select(x => new CandyItems
{
Value = (string)x.Attribute("value"),
Price = (string)x.Attribute("price")
}).ToList();
Although, you have not mentioned but if you want to just fetch Candies and your XML may contain other products too like:-
<Root>
<Product value="Candy">
<Item value="Gum" price="1.00"/>
<Item value="Mints" price="0.50"/>
</Product>
<Product value="Chocolate">
<Item value="MilkChocolate" price="7.00"/>
<Item value="DarkChocolate" price="10.50"/>
</Product>
</Root>
Then you can apply a filter to fetch only Candy products like this:-
List<CandyItems> Candies = xdoc.Descendants("Item")
.Where(x => (string)x.Parent.Attribute("value") == "Candy")
.Select(x => new CandyItems
{
Value = (string)x.Attribute("value"),
Price = (string)x.Attribute("price")
}).ToList();
How about something like this (after loading the document):
var candies =
xdoc.Root.Elements("Product")
.Where(p => p.Attribute("value").Value == "Candy")
.SelectMany(p => p.Descendants("Item").Select(i => new CandyItems {
Value = i.Attribute("value").Value,
Price = i.Attribute("price").Value }));
Note: any and all error handling omitted.

How to retrieve attributes value with multiple level using Xml Linq?

I have a xml like this :
<?xml version="1.0" encoding="iso-8859-1" ?>
<Menu>
<Group Flow="Horizontal">
<Item Text="Basket" >
<Item Text="Favorites">
<Item Text="Add" Roles="SuperAdmin">
<Item Text="Evaluation" Roles="Admin">
</Item>
<Item Text="Titularisation" Roles="users,Admin">
</Item>
</Item>
</Group>
</Menu>
and I want to retrieve all attributes "Roles".
I tried this :
XElement rootElement = XElement.Load(#"C:\Users\TTM\Desktop\GeneralMenu.xml");
XElement menuElt = rootElement.Element("Group");
var itemsAdd = from el in menuElt.Descendants("Item")
where (string)el.Attribute("Text") == "Add"
select el;
foreach (var item in itemsAdd)
{
Console.WriteLine(item.Attribute("Roles").Value);
}
Console.Read();
but I get only the "Roles" of this balise:
<Item Text="Add" Roles="SuperAdmin">
Try this:-
XDocument xdoc = XDocument.Load(XMLFile);
var itemAdd = xdoc.Descendants("Item")
.Where(x => (string)x.Attribute("Text") == "Add")
.Select(x => new
{
Roles = (string)x.Attribute("Roles"),
ChildRoles = x.Descendants("Item")
.Select(z => (string)z.Attribute("Roles")).ToList()
});
Here, Roles will contain the Roles from your parent node, and ChildRoles will hold all the Roles present in the descendants of parent where Text is Add.
You can then access them using foreach loop:-
foreach (var item in itemAdd)
{
Console.WriteLine(item.Roles);
foreach (var childRoles in item.ChildRoles)
{
Console.WriteLine(childRoles);
}
}
If you want all the roles to be fetched in a single property then you can fetch it like this:-
Roles = x.Descendants("Item").Select(z => (string)z.Attribute("Roles"))
.Concat(new List<string> { (string)x.Attribute("Roles") }).ToList()

XML to SelectList using LINQ

I am attempting to get a Select List from an XML string...
<?xml version="1.0" encoding="utf-8"?>
<selectListItemDefinition id="UPMCTypes">
<item key="bla1" value="bla" />
<item key="bla2" value="bla" />
<item key="bla3" value="bla" />
<item key="bla4" value="bla" />
<item key="bla5" value="bla" />
<item key="bla6" value="bla" />
<item key="bla7" value="bla" />
<item key="bla8" value="bla" />
<item key="bla9" value="bla" />
<item key="bla10" value="bla" />
<item key="bla11" value="bla" />
</selectListItemDefinition>
That would be the XML string that I am trying to turn into a SelectList
Here is how I am trying to do it...
List<SelectListItem> SListItem =
(
from xmlSList in xDoc.Descendants("selectListItemDefinition")
let listItem = xmlSList.Elements("item")
select new SelectListItem
{
Key = xmlSList.Attribute("key").Value,
Value = xmlSList.Attribute("value").Value
}
).ToList();
This only gets the first Key-Value one for me.
Key "blah1" string
Value "blah" string
Now I think it's because I am only getting one Element here? But I am not sure what I would do to get this right.
I think you have a problem in your query, as you should be getting an exception.
You are trying to select the attributes "key" and "value" of <selectListItemDefinition>, but they don't exist. And you are not actually using listItem in your select clause.
Your first line is returning an IEnumerable<XElement> of all nodes called "selectListItemDefinition". Your second line is just taking that IEnumerable<> and assigning the child elements called "item" it to variable named listItem.
When you should be doing is a second query to iterate over the <selectListItemDefinition> elements instead of assigning it.
So replace let listItem = xmlSList.Elements("item") with from listItem in xmlSList.Elements("item") and it should behave as you expect.
the full query would be:
var SListItem =
(
from xmlSList in xDoc.Descendants("selectListItemDefinition")
from listItem in xmlSList.Elements("item")
select new SelectListItem
{
Key = listItem .Attribute("key").Value,
Value = listItem .Attribute("value").Value
}
).ToList();
And if you are 100% certain that the only elements called <item> in your XML document are the ones you want, you can replace the first and second lines with a single one from listItem in xDoc.Descendants("item") and you will get the same behavior.

Categories

Resources