linq to XML: unique attribute value count per group - c#

I have got XML nodes as below.
...
<ParentNode>
<Node id="2343" name="some name" mode="Some Mode">
//Some child nodes here
</Node>
<Node id="2344" name="some other name" mode="Some Mode">
//Some child nodes here
</Node>
...
</ParentNode>
<ParentNode>
<Node id="2343" name="some name" mode="Some Other Mode">
//Some child nodes here
</Node>
<Node id="2344" name="some other name" mode="Some Mode">
//Some child nodes here
</Node>
</ParentNode>
....
What I need is
id name distinct-mode-count
--------------------------------------------
2343 some name 2
2344 some other name 1
I have tried below to get this.
XElement myXML = XElement.Load(filePath);
IEnumberable<XElement> parentNodes = myXML.Descendants("ParentNode");
var nodeAttributes = parentNodes.Select(le => le.Descendants("Node")
.GroupBy(x => new {
id = x.Attribute("id").Value,
name = x.Attribute("name").Value
}).Select(g => new {
id = g.Key.id,
name = g.Key.name,
distinct_mode_count = // This is where I am stuck
}));
I am not sure how to get distinct_mode_count in the above query.
Edit
I need distinct attribute value count for attribute "mode", regardless of which ParentNode they are in.

Assuming you want the count of the distinct "mode" attribute values within the nodes with the same ID/name, you just need to project from each element in the group to the mode, then take the distinct sequence of those modes, then count it:
You just need to take the count of the group, and also use SelectMany to "flatten" your parent nodes. (Or just use myXml.Descendants("Node") to start with.)
Short but complete example which gives your desired results:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
class Test
{
static void Main()
{
XElement myXML = XElement.Load("test.xml");
IEnumerable<XElement> parentNodes = myXML.Descendants("ParentNode");
var nodeAttributes = parentNodes
.SelectMany(le => le.Descendants("Node"))
.GroupBy(x => new {
Id = x.Attribute("id").Value,
Name = x.Attribute("name").Value
})
.Select(g => new {
g.Key.Id,
g.Key.Name,
DistinctModeCount = g.Select(x => x.Attribute("mode").Value)
.Distinct()
.Count()
});
foreach (var item in nodeAttributes)
{
Console.WriteLine(item);
}
}
}
Alternatively:
XElement myXML = XElement.Load("test.xml");
var nodeAttributes = myXML
.Descendants("Node")
.GroupBy(...)
// Remaining code as before

Related

Get xml node attribute depending on other attribute of the same node

For example in the xml below if I have the id attribute is it possible to get its corresponding reportid attribute in C#
The xml code:
<node text=" Acid Level versus Flowmeters" nodetype="1" reportid="118" id="626" />
<node text="Level versus Flowmeters" nodetype="1" reportid="119" id="627" />
<node text=" Bulk Levels" nodetype="1" reportid="120" id="629" />
<node text="Caustic and HCL" nodetype="1" reportid="121" id="630" />
the c# code:
string XMLFile = ConfigurationManager.AppSettings["XMLReportTreePath"];
XElement sitemap = XElement.Load(XMLFile);
XAttribute xatt = sitemap.Attribute(reportid); // where id = 630
Thanks
Assuming your xml document has a root node:
var reportid = sitemap.Descendants("node")
.Where(el => el.Attribute("id").Value == "630")
.Select(el => el.Attribute("reportid").Value)
.FirstOrDefault();
// or, lookup of all reportid by id
var lookup = sitemap.Descendants("node")
.ToDictionary(el => el.Attribute("id").Value, el => el.Attribute("reportid").Value);
Something like this ?
foreach (XmlNode chldNode in node.ChildNodes)
{
**//Read the attribute ID**
if (chldNode.ID== 630)
{
string reportid= chldNode.Attributes["reportid"].Value;
}
}
//just one example, but from what i could understand its something like this that you want

Extracting XML values from two nodes

I want to extract the value in the moduleId attibute and the value from the Field node. For example, in this first node I want to extract the 447 in the moduleId and the 124694 from the Field node. I have the XML loaded in an XDocument. The end result will be a Tuple where the first item is the value from the moduleId attribute and the second item is the value from the Field node. Is there a way I can do this using one XLinq statement?
As a bonus...I only want to do it for nodes where the guid = "07a188d3-3f8c-4832-8118-f3353cdd1b73". This part I can probably figure out if someone can tell me how to extract information from two nodes, but a bonus would be to add the WHERE clause in there for me :)
<Records>
<Record moduleId="447">
<Field guid="07a188d3-3f8c-4832-8118-f3353cdd1b73">124694</Field>
</Record>
<Record moduleId="447">
<Field guid="07a188d3-3f8c-4832-8118-f3353cdd1b73">124699</Field>
</Record>
<Records>
I have gotten as far as extracting the Field value using this...
IEnumerable<string> c = from p in sourceDocument.Descendants("Field")
where p.Attribute("guid").Value == "07a188d3-3f8c-4832-8118-f3353cdd1b73"
select p.Value;
But I have no idea how to get information from both the Record node and the Field node.
Give this a try:
var doc = XDocument.Parse(xml);
var r = doc.Descendants("Record")
.Where(n => n.Element("Field").Attribute("guid").Value == "07a188d3-3f8c-4832-8118-f3353cdd1b73")
.Select(n => new { ModuleId = n.Attribute("moduleId").Value, Field = n.Element("Field").Value });
var a = r.ToArray();
Here is a solution that uses LINQ Query Syntax:
XDocument document = XDocument.Parse(xml);
var query =
from el in document.Root.Elements("Record")
where (string)el.Element("Field").Attribute("guid") ==
"07a188d3-3f8c-4832-8118-f3353cdd1b73"
select new
{
ModuleId = (string)el.Attribute("moduleId"),
Field = (string)el.Element("Field")
};
foreach (var item in query)
{
Console.WriteLine
("ModuleId:[{0}]\nField:[{1}]\n--",
item.ModuleId,
item.Field);
}

Read specific element from XElement

I want to bind the MultipleCheckbox items from Choice Column of sharepoint List to asp.net CheckBoxListItem using c#. I am retriving information of List using XELEMENT as:
XElement listStructure;
listStructure = proxy.GetList("WebsiteSubscriber");
<Field Type="MultiChoice" DisplayName="Area" Required="FALSE" EnforceUniqueValues="FALSE" Indexed="FALSE" FillInChoice="FALSE" ID="{16cc1615-a490-44de-a870-c7ebe603e2cc}" SourceID="{2c8a80ea-38c5-48f7-9d7d-400d445a5e64}" StaticName="Area" Name="Area" ColName="ntext2" RowOrdinal="0">
<Default>Articles</Default>
<CHOICES>
<CHOICE>Articles</CHOICE>
<CHOICE>Websites</CHOICE>
<CHOICE>Books</CHOICE>
</CHOICES>
</Field>
I want to read choices from this XML. and get values only "Articles,Websites,Books"
Note: This XML may contain many sections with <choices> i want to fetech it by <fieldType> or the DisplayName="Area" attribute and get values in c#.
var xml = #"<Field Type=""MultiChoice"" DisplayName=""Area"" Required=""FALSE"" EnforceUniqueValues=""FALSE"" Indexed=""FALSE"" FillInChoice=""FALSE"" ID=""{16cc1615-a490-44de-a870-c7ebe603e2cc}"" SourceID=""{2c8a80ea-38c5-48f7-9d7d-400d445a5e64}"" StaticName=""Area"" Name=""Area"" ColName=""ntext2"" RowOrdinal=""0"">
<Default>Articles</Default>
<CHOICES>
<CHOICE>Articles</CHOICE>
<CHOICE>Websites</CHOICE>
<CHOICE>Books</CHOICE>
</CHOICES>
</Field>";
var doc = XDocument.Parse(xml);
XElement element =
doc.Descendants("Field")
.First(field => (string)field.Attribute("DisplayName") == "Area");
string[] result =
element.Descendants("CHOICE")
.Select(v => (string)v)
.ToArray();
Console.WriteLine(string.Join(Environment.NewLine, result));
prints:
Articles
Websites
Books
Try this:
var temp =
listStructure.Descendants("Field")
.Where(i => i.Attribute("DisplayName").Value == "Area")
.Select(i => i.Descendants("CHOICE")
.Select(j => j.Value)).ToList();
List<string> result = new List<string>();
foreach (IEnumerable<string> item in temp)
{
result.AddRange(item);
}
//result: Articles; Websites; Books

How to select and iterate through a XML xElement node from attribute values?

I have a xml like this.
<?xml version="1.0" encoding="utf-8" ?>
<Category ID="1" AICategoryName="Schedule K: Income/Loss" Taxonomy="K">
<Level1 ID="11965" Name="Guaranteed payments" Taxonomy="4">
<Level2 ID="27058" Name="Gtd Pmts(trade/bus) to Sch. M-1" Taxonomy="1">
</Level2>
<Level2 ID="27059" Name="Gtd Pmts not to Sch. M-1" Taxonomy="2">
</Level2>
</Level1>
<Level1 ID="119652" Name="2Guaranteed payments" Taxonomy="4">
<Level2 ID="227058" Name="2Gtd Pmts(trade/bus) to Sch. M-1" Taxonomy="1">
</Level2>
<Level2 ID="227059" Name="2Gtd Pmts not to Sch. M-1" Taxonomy="2">
</Level2>
</Level1>
</Category>
I want to get the child nodes under a parent node by providing the parent node attribite ID.
for example if I provide Level1 and 11965, I should get all the level 2 nodes and their Name and IDs.
I have tried this code.
XDocument xd = XDocument.Load(xmlPath);
var xl = from xml2 in xd.Descendants("Level1")
where xml2.Parent.Attribute("ID").Value == parentNode.ID
select xml2;
But the code yeilds no results. also once I get the xl, how can I iterate through it to get child node names and IDs?
XDocument xd = XDocument.Load(xmlPath);
var nodes = (from n in xd.Root.Desendants(tagName/*Level1*/) where n.Attribute("Id").Value == "idValue" select n.Elements()).single().select(n=>{return new{
Id = n.attribute("Id").value,
Name = n.attribute("Name").value,
Taxonomy = n.attribute("Taxonomy").value
}});
you can also change the code above if the tag name requested is always "Level1" and the xml struvvture is fixed, to this.
XDocument xd = XDocument.Load(xmlPath);
var nodes = (from n in xd.Root.Elements("Level1") where n.Attribute("Id").Value == "idValue" select n.Elements()).single().select(n=>{return new{
Id = n.attribute("Id").value,
Name = n.attribute("Name").value,
Taxonomy = n.attribute("Taxonomy").value
}});
LINQ (suppose you always get single Level1 node for provided id):
XDocument xd = XDocument.Load(xmlPath);
int parentId = 119652;
var nodes = (from level1 in xd.Descendants("Level1")
where ((int)level1.Attribute("ID")) == parentId
select level1.Descendants("Level2"))
.Single()
.Select(level2 => new { ID = (int)level2.Attribute("ID"),
Name = level2.Attribute("Name").Value });
Iterate
foreach (var level2 in nodes)
// level2.Name and level2.ID
If there possible that Level1 node not exist for provided id or you have several Level1 nodes with same ID:
int parentId = 119652;
XDocument xd = XDocument.Load(xmlPath);
var query = xd.Descendants("Level1")
.Where(level1 => ((int)level1.Attribute("ID")) == parentId)
.SelectMany(level1 => level1.Descendants("Level2"))
.Select(level2 => new { ID = (int)level2.Attribute("ID"),
Name = level2.Attribute("Name").Value });
foreach (var level2 in query)
// level2.Name and level2.ID

Filtering out duplicate XElements based on an attribute value from a Linq query

I'm using Linq to try to filter out any duplicate XElements that have the same value for the "name" attribute.
Original xml:
<foo>
<property name="John" value="Doe" id="1" />
<property name="Paul" value="Lee" id="1" />
<property name="Ken" value="Flow" id="1" />
<property name="Jane" value="Horace" id="1" />
<property name="Paul" value="Lee" id="1" />
... other xml properties with different id's
</foo>
// project elements in group into a new XElement
// (this is for another part of the code)
var props = group.data.Select( f => new XElement("property",
new XAttribute("name", f.Attribute("name").Value), f.Attribute("value"));
// filter out duplicates
props = props.Where(f => f.ElementsBeforeSelf()
.Where(g => g.Attribute("name").Value ==
f.Attribute("name").Value)
.Count() == 0);
Unfortunately, the filter step isnt working. I would think that Where() filter would check for any element before the current one that has the same property name and then include that in a set that was more than zero, thereby excluding the current element (called 'f'), but thats not happening. Thoughts ?
You could just create an IEqualityComparer to use with the Distinct(), that should get you what you need.
class Program
{
static void Main(string[] args)
{
string xml = "<foo><property name=\"John\" value=\"Doe\" id=\"1\"/><property name=\"Paul\" value=\"Lee\" id=\"1\"/><property name=\"Ken\" value=\"Flow\" id=\"1\"/><property name=\"Jane\" value=\"Horace\" id=\"1\"/><property name=\"Paul\" value=\"Lee\" id=\"1\"/></foo>";
XElement x = XElement.Parse(xml);
var a = x.Elements().Distinct(new MyComparer()).ToList();
}
}
class MyComparer : IEqualityComparer<XElement>
{
public bool Equals(XElement x, XElement y)
{
return x.Attribute("name").Value == y.Attribute("name").Value;
}
public int GetHashCode(XElement obj)
{
return obj.Attribute("name").Value.GetHashCode();
}
}
Your appoach is a bit weird, e.g., You don't need to project elements into new elements; it just works(tm) when you add existing elements to a new document.
I would simply group the <property> elements by the name attribute and then select the first element from each group:
var doc = XDocument.Parse(#"<foo>...</foo>");
var result = new XDocument(new XElement("foo",
from property in doc.Root
group property by (string)property.Attribute("name") into g
select g.First()));
I think you should remove the duplicates first, and then do your projection. For example:
var uniqueProps = from property in doc.Root
group property by (string)property.Attribute("name") into g
select g.First() into f
select new XElement("property",
new XAttribute("name", f.Attribute("name").Value),
f.Attribute("value"));
or, if you prefer method syntax,
var uniqueProps = doc.Root
.GroupBy(property => (string)property.Attribute("name"))
.Select(g => g.First())
.Select(f => new XElement("property",
new XAttribute("name", f.Attribute("name").Value),
f.Attribute("value")));

Categories

Resources