query xmlnode using linq - c#

I have following file:
<root>
<Product desc="Household">
<Product1 desc="Cheap">
<Producta desc="Cheap Item 1" category="Cooking" />
<Productb desc="Cheap Item 2" category="Gardening" />
</Product1>
<Product2 desc="Costly">
<Producta desc="Costly Item 1" category="Decoration"/>
<Productb desc="Costly Item 2" category="Furnishing" />
<Productc desc="Costly Item 3" category="Pool" />
</Product2>
</Product>
</root>
I want to find out info like: Total items in Cheap and Costly,
list of all category (like Cooking, Gardening, DEcoration...), list of sorted category and select only the Product which is 'Costly'
How can i do using LINQ.
I did this till now:
XElement xe = XElement.Load(Server.MapPath("~/product.xml"));
????

Your XML structure is unfortunate as it uses a Product element for three levels of the hierarchy. Do you have other elements similar to the "household" one?
Assuming we only want the household ones, you can use:
Count items in each of cheap/costly
xe.Element("Product") // Select the Product desc="household" element
.Elements() // Select the elements below it
.Select(element => new { Name=(string) element.Attribute("desc"),
Count=element.Elements().Count() });
List all categories
xe.Descendants() // Select all descendant elements
.Attributes() // All attributes from all elements
// Limit it to "category" elements
.Where(attr => attr.Name == "category")
// Select the value
.Select(attr => attr.Value)
// Remove duplicates
.Distinct();
To sort this, just use .OrderBy(x => x) at the end.
Select 'costly' products
xe.Descendants() // Select all elements
// Only consider those with a "Costly" description
.Where(element => (string) element.Attribute("desc") == "Costly")
// Select the subelements of that element, and flatten the result
.SelectMany(element => element.Elements());

Well, personally I find it easier with XmlDocument:
XmlDocument root = new XmlDocument();
root.LoadXml(xml); // or .Load(path);
var categories = root.SelectNodes(
"/root/Product/Product/Product/#category")
.Cast<XmlNode>().Select(cat => cat.InnerText).Distinct();
var sortedCategories = categories.OrderBy(cat => cat);
foreach (var category in sortedCategories)
{
Console.WriteLine(category);
}
var totalItems = root.SelectNodes(
"/root/Products/Product/Product").Count;
Console.WriteLine(totalItems);
foreach (XmlElement prod in root.SelectNodes(
"/root/Product/Product[#desc='Costly']/Product"))
{
Console.WriteLine(prod.GetAttribute("desc"));
}

Related

Sort list into children using linq

I am trying to create a tree from a somewhat large list of 13883 objects using Linq to sort the objects into their parent and child groups, each object is associated with a parentId, I can group the objects together using
var sortedList = marketItems.GroupBy(p => p.parentId).Select(grp => grp.ToList()).ToList();
but this only sorts them into groups, I'm not yet fluent with linq and cant figure out how to map the children correctly. the null ParentGroup is the top level of the tree. Anyone with more experience with linq able to offer some ideas on how to group the llist correctly.
public JsonResult GetJsTree3Data()
{
var marketItems = new List<JsTree3Node>();
var itemList = new List<JsTree3Node>();
foreach (var group in GenerateGroups(connString))
{
var node = JsTree3Node.NewNode(group.id_str);
node.text = group.name;
node.state = new State(false, false, false);
node.parentId = group.marketParentGroup;
marketItems.Add(node);
}
foreach (var group in GenerateItems(connString))
{
var node = JsTree3Node.NewNode(group.id_str);
node.text = group.name;
node.state = new State(false, false, false);
node.parentId = group.marketParentGroup;
marketItems.Add(node);
}
// Create our root node and ensure it is opened
var root = new JsTree3Node()
{
id = "0",
text = "Market Items",
state = new State(true, false, false)
};
var sortedList = marketItems.GroupBy(u => u.parentId).Select(grp => grp.ToList()).ToList();
root.children = sortedList;
return Json(root, JsonRequestBehavior.AllowGet);
}
The final result I am trying achive is a tree of items that users can choose from. there is only one level of the tree as the children arent ordered in the sorted list
I see, the parents can contain multiple items: I believe this is what you are looking for:
var sortedList = from p in marketItems
group p by p.ParentGroup into Groups
select Groups;
//to access the items in each group individually
foreach (var grouping in sortedList)
{
foreach (var Item in grouping)
{
Item.Description///you can access the individual items from this layer...
}
}
Your code should do this, but to access the grouping items you need to use a nested foreach loop.
I'm not sure what your end goal is, but I'm not sure if what you're trying to do can be strictly done with a single LINQ pass. If it were possible, though, I imagine that it would be quite convoluted. You can do it in two LINQ queries quite simply, though:
var refDict = marketItems.ToDictionary(k => k.Id, v => v);
var groupedList = marketItems.Select(s =>
new
{
Parent = s.ParentGroup == null ? null : refDict[s.ParentGroup],
Child = s
}).ToList();
Then use it like this:
var parent = groupedList[0].Parent;
var child = groupedList[0].Child;
In all honesty, though, you could just keep yourself to the first line of code then query the dictionary for an object's parent yourself if you ever need it.

Way to populate class object in single LINQ XML query?

Given the following XML snippet, is there a way to both query and populate a class object in one LINQ statement? It's confusing because of the need to select using attribute values.
<data>
<array>
<item key="0">
<map>
<item key="mrid">53030</item>
<item key="mrtitle">GeneralFeedback</item>
</map>
</item>
</array>
</data>
Class:
public class Incident
{
public int ID { get; set; }
public string Title { get; set; }
}
Current (working) code (where result is the XML snippet as a string):
var data = XDocument.Parse(result);
var id = from item in data.Descendants("item")
where item.Attribute("key").Value == "mrid"
select item.Value;
var title = from item in data.Descendants("item")
where item.Attribute("key").Value == "mrtitle"
select item.Value;
var incident = new Incident
{
ID = Convert.ToInt32(id.FirstOrDefault()),
Title = title.FirstOrDefault()
};
Based on the answers given I learned some useful things and came up with this variation:
var incidents = data.Descendants("map")
.Select(i => i.Descendants("item")
.ToDictionary(m => m.Attribute("key").Value, m => m.Value))
.Where(i => i.ContainsKey("mrid")
&& i.ContainsKey("mrtitle"))
.Select(i => new Incident
{
ID = int.Parse(i["mrid"]),
Title = i["mrtitle"]
});
One thing I really like is that this creates an IEnumerable that allows for multiple incidents being present in the XML data.
is there a way to both query and populate a class object in one LINQ statement?
Yes, well sorta ... and it remains quite ugly. The below "single" multi-step LINQ statement ensures only the items that belong to the same map element get selected. Like your code sample, it will blow up in your face if the items with the required key values are missing (or the "mrid" element is not an int).
var key_vals = new List<string> { "mrid", "mrtitle" };
var xdoc = XDocument.Load(#"c:\temp\test.xml");
var incidents = xdoc.Descendants("map").Select(map => {
var items = map.Descendants("item").Where(i => key_vals.Contains(i.Attribute("key").Value));
var idItem = items.Where(x => x.Attribute("key").Value == "mrid").First();
var titleItem = items.Where(x => x.Attribute("key").Value == "mrtitle").First();
return new Incident {
ID = int.Parse(idItem.Value),
Title = titleItem.Value
};
});
foreach (var i in incidents)
Console.WriteLine("ID = {0}, Title = {1}", i.ID, i.Title);
It will produce the output below for your given xml input file:
ID = 53030, Title = GeneralFeedback
Check out this post to learn how to convert your XML schema to a C# class
Generate C# class from XML
Then you can use your new type and de-serialize your XML to a class
XmlSerializer serializer = new XmlSerializer(typeof(Incident));
using (StringReader reader = new StringReader(xmlDocumentText))
{
Incident incident= (Incident)(serializer.Deserialize(reader));
}
Alex has already given a perfect answer, but I find this a little more readable (:
The Where clause ensures each item found, has the keys required to construct an Incident.
var incidents = xdoc.Root
.Element("array")
.Elements("item")
.Select(i => i.Element("map")
.Elements("item")
.ToDictionary(m => m.Attribute("key").Value,
m => m.Value))
.Where(i => i.ContainsKey("mrid")
&& i.ContainsKey("mrtitle"))
.Select(i => new Incident
{
ID = int.Parse(i["mrid"]),
Title = i["mrtitle"]
});

Linq query on XML to Select multiple elements of subnodes

I want to select all distinct values of child from following xml
<root>
<parent>
<child>value 1</child>
<child>value 2</child>
</parent>
<parent>
<child>value 1</child>
<child>value 4</child>
</parent>
</root>
I tried following:
var vals = (from res in XmlResources.Elements("root").Elements("parent") select res)
.SelectMany(r => r.Elements("child")).Distinct().ToList();
But can't get the value from it, gives me value wrapped in tag and not Distinct
Is it possible to show both ways to get it - query and chaining aka lambda.
yes it is possible both ways
var doc = new XDocument("your xml string");
var values = (from c in doc.Root.Descendants("child") select c.Value).Distinct();
//chaining style
var values = doc.Root.Descendants("child").Select(c=>c.Value).Distinct();
You're selecting the elements, and the elements are all distinct. You need to get the distinct values. For example:
var values = XmlResources.Element("root")
.Elements("parent")
.Elements("child")
.Select(x => x.Value)
.Distinct();
There's really no benefit in using a query expression here - it only adds cruft. I only use a query expression when the query has multiple aspects to it (e.g. a where and a meaningful select, or a join). For just a select or just a where it's pretty pointless. So yes, you can use:
var values = (from x in XmlResources.Element("root")
.Elements("parent")
.Elements("child")
select x.Value).Distinct();
... but why would you? It's a lot less clear IMO.
Note that if you don't care too much about the root/parent/child hierarchy, and are happy to just get all the child descendants, you can use:
var values = XmlResources.Descendants("child")
.Select(x => x.Value)
.Distinct();

Reorder using Join in Linq

I want my XElements in document order.
Can I reorder my xpathGroups using Join like in this example?
XDocument message_doc = XDocument.Load(message);
var xpathGroups =
from e in contextErrors
group e by SelectElement(message_doc, e.XPath) into g
select new
{
Element = g.Key,
ErrorItems = g,
};
var documentOrderedGroups =
from elem in message_doc.Root.DescendantsAndSelf()
join e in xpathGroups on elem equals e.Element
select e;
Message:
<root>
<a>
<aa>
<aaa>9999</aaa>
</aa>
<aa>
<aaa>8888</aaa>
</aa>
</a>
<b>
<bb>
<bbb>7777</bbb>
</bb>
</b>
<c>
<cc>
<ccc>6666</ccc>
</cc>
</c>
</root>
Input data:
/root/c[1]/cc[1]/ccc[1]
/root/a[1]/aa[2]/aaa[1]
/root/b[1]/bb[1]/bbb[1]
/root/a[1]/aa[1]/aaa[1]
Expected result:
/root/a[1]/aa[1]/aaa[1]
/root/a[1]/aa[2]/aaa[1]
/root/b[1]/bb[1]/bbb[1]
/root/c[1]/cc[1]/ccc[1]
Your original queries work, and the result is an object with the element and its relevant XPath query in document order. However, the result conflicts with the comment you made that you only want the elements in document order.
Elements and XPath: if you want both the element and its XPath then the join will remain as part of the query but I would replace the grouping with a projection into an anonymous type.
var xpathElements = contextErrors.Select(e => new
{
Element = message_doc.XPathSelectElement(e.XPath),
XPath = e.XPath
});
var ordered = from e in message_doc.Descendants()
join o in xpathElements on e equals o.Element
select o;
Elements only: if you only want the elements to be in document order, the following approach would work as well.
var xpathElements = contextErrors.Select(e => message_doc.XPathSelectElement(e.XPath));
var ordered = message_doc.Descendants()
.Where(e => xpathElements.Any(o => e == o));
In both examples I've used the XPathSelectElement method to take the place of your
SelectElement method, which I gather has the same purpose.

LINQ: grouping based on property in sublist

I'am trying to use LINQ to create a grouped list of documents based on metadata which is a list on the document.
Below is how my object structure looks:
List<Document>
--> List<Metadata>
--> Metadata has a name and a value property.
I want to group the documents based on an metadata tag which has a name: ID and group them where the values for the ID property are the same.
I tried it like this:
var x = response.Document
.GroupBy(d => d.Metadata.Where(dc => dc.Name == DocProperty.ID)
.Select(dc => dc.Value));
This results in a list of single documents, but not grouped on ID.
Also thought about selecting a distinct list of ID's and then loop through the document list and find documents that match the ID. That one seems like a lot of overhead, because for every ID in the distinct list i have to go every time into the metadata list and find the documents and have to extra checks for multiple items found, get the property i need etc.
Anyone has a good idea about how to get this thing working?
var x = from doc in source
from meta in doc.Metadata
where meta.Name == DocProperty.Id
group doc by meta.Value;
Or (comments) as fluent notation:
var y = source
.SelectMany(doc => doc.Metadata, (doc, meta) => new { doc, meta })
.Where(pair => pair.meta.Name == DocProperty.Id)
.GroupBy(pair => pair.meta.Value, pair => pair.doc);

Categories

Resources