New to LINQ.
Example xml:
<item name="XX">
<inside type="A"/>
<inside type="B" />
</item>
<item name="YY">
<inside type="C"/>
<inside type="D" />
</item>
I would like to parse it to tuples:
(XX, A)
(XX, B)
(YY, C)
(YY, D)
So far I can retrieve the first lines online: (XX, A) and (YY, C) of each item using the code below:
var selected = (from item in doc.Root.Elements()
let inside = item.Element(XName.Get("inside", item.GetDefaultNamespace().NamespaceName))
select Tuple.Create(item.Attribute("name").Value,
inside.Attribute("type").Value)).ToList();
I believe I should modify item.Element to item.Elements but so far no luck.
instead of using item.Element you should iterate over item.Elements:
var selected = (from item in doc.Root.Elements()
from inside in item.Elements("inside")
select Tuple.Create(
item.Attribute("name").Value,
inside.Attribute("type").Value)
).ToList();
var tuples = XElement.Parse(xml).Descendants("item")
.SelectMany(item => item.Elements()
.Select(inside => Tuple.Create(item.Attribute("name"), inside.Attribute("type"))));
You can use SelectMany to help you build a collection of tuples from a single element.
Related
I have two xml documents with some elements like
doc1
<Item id="22"/>
<Item id="33"/>
<Item id="44"/>
...
doc2
<Item id="33"/>
<Item id="44"/>
<Item id="66"/>
<Item id="88"/>
...
I need a query to select
only those elements from doc1 that are missing in doc2 ignoring other doc2 elements.
In this case the result will be:
<Item id="22"/>
How do I do that?
Basically, you create a List with all ids from the second list, and check for each item of doc1 if it is in the list.
Performance wise, I think it isnt the best choice - but it should work
var qry = from item in doc1.Descendants("Item")
where
!(from item2 in doc2.Descendants("Item")
select item2.Attribute("id"),Value
).ToList().Contains(item.Attribute("id").Value)
select item;
In the linq-statement above, I think the list of ids is created for every element in doc1. Better option would be to create the list first and then use the list in the next statement:
List<string> items = (from item2 in doc2.Descendants("Item")
select item2.Attribute("id").Value
).ToList();
var qry = from item in doc1.Descendants("Item")
where !items.Contains(item.Attribute("id").Value)
select item;
Probably something along the lines of
doc1.Where(i1=>doc2.All(i2 => i2.id != i1.id))
could get you there.
HOWEVER, this is performing a subquery on doc2 for each element in doc1. Make sure they are small!
The easiest way is to use the ExceptedBy method from the MoreLinq library. Assuming the Item elements are directly under the root element:
var doc1 = XDocument.Load("doc1.xml");
var doc2 = XDocument.Load("doc2.xml");
var doc1Elements = doc1.Root.Elements("Item");
var doc2Elements = doc2.Root.Elements("Item");
var diff = doc1Elements.ExceptBy(doc2Elements, e => e.Attribute("id").Value);
I have the following xml:
<root ...>
<Tables>
<Table content="..">
</Table>
<Table content="interesting">
<Item ...></Item>
<Item ...></Item>
<Item ...></Item>
</Table>
...etc...
</Tables>
</root>
I'm using the following code to get the items from the 'interesting' node:
XElement xel = XElement.Parse(resp);
var nodes = from n in xel.Elements("Tables").Elements("Table")
where n.Attribute("content").Value == "interesting"
select n;
var items = from i in nodes.Elements()
select i;
Is there a simpler, cleaner way to achieve this?
Well there's no point in using a query expression for items, and you can wrap the whole thing up very easily in a single statement. I wouldn't even bother with a query expression for that:
var items = XElement.Parse(resp)
.Elements("Tables")
.Elements("Table")
.Where(n => n.Attribute("content").Value == "interesting")
.Elements();
Note that this (and your current query) will throw an exception for any Table element without a content attribute. If you'd rather just skip it, you can use:
.Where(n => (string) n.Attribute("content") == "interesting")
instead.
You can use XPath (extension is in System.Xml.XPath namespace) to select all items in one line:
var items = xel.XPathSelectElements("//Table[#content='interesting']/Item");
If you don't need nodes outside of your query for items, you can just do this:
var items = from n in xel.Elements("Tables").Elements("Table")
where n.Attribute("content").Value == "interesting"
from i in n.Elements()
select i;
using xml document
XmlDocument xdoc = new XmlDocument();
var item= xdoc.GetElementsByTagName("Table[#content='interesting']/Item");
Here is the XML I have in a file:
SPECIAL NOTE:
This is a question for Windows Phone 7, not general C#
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<item>
<date>01/01</date>
<word>aberrant</word>
<def>straying from the right or normal way</def>
</item>
<item>
<date>01/02</date>
<word>Zeitgeist</word>
<def>the spirit of the time.</def>
</item>
</rss>
I need it in a List (aka array) of Dictionary objects. Each Dictionary represents an <item>. Each element like <word> is the key with type string and each value like "Zeitgeist" is the value with type string.
Is there any easy way to do this? I'm coming from Objective-C and iOS so this is completely new to me with .NET and C#.
LINQ-to-XML makes it pretty easy. Here's a complete example:
public static void Main(string[] args)
{
string xml = #"
<rss version='2.0'>
<item>
<date>01/01</date>
<word>aberrant</word>
<def>straying from the right or normal way</def>
</item>
<item>
<date>01/02</date>
<word>Zeitgeist</word>
<def>the spirit of the time.</def>
</item>
</rss>";
var xdoc = XDocument.Parse(xml);
var result = xdoc.Root.Elements("item")
.Select(itemElem => itemElem.Elements().ToDictionary(e => e.Name.LocalName, e => e.Value))
.ToList();
}
Instead of loading from a string with XDocument.Parse(), you would probably do XDocument.Load(filename) but either way you get an XDocument object to work with (I did a string just for example).
You can use Linq-Xml to do this:
var doc = XDocument.Parse(xml); //xml is a String with your XML in it.
doc
.Root //Elements under the root element.
.Elements("item") //Select the elements called "item".
.Select( //Projecting each item element to something new.
item => //Selecting each element in the item.
item //And creating a new dictionary using the element name
.Elements() // as the key and element value as the value.
.ToDictionary(xe => xe.Name.LocalName, xe => xe.Value))
.ToList();
Yes, there is an easy way, it's called LINQ to XML.
Some resources:
Parsing complex XML with C#
LINQ to read XML
http://msdn.microsoft.com/en-us/library/bb387098.aspx
Hope this helps...
I have an XML file which has kind of a similar structure that you can see below:
I would like to select title and subitems using LINQ to XML. The difficulties that I have: sometimes a subitem can be just one and sometimes it can be 20 subitems, and I need to add them to List<string>.
<?xml version="1.0"?>
<items>
<item>
<title>Name of the title</title>
<subitem>Test</subitem>
<subitem1>Test</subitem1>
<subitem2>Test</subitem2>
<subitem3>Test</subitem3>
<subitem4>Test</subitem4>
<subitem5>Test</subitem5>
</item>
<item>
<title>Name of the title</title>
<subitem>Test</subitem>
<subitem1>Test</subitem1>
<subitem2>Test</subitem2>
<subitem3>Test</subitem3>
</item>
<item>
<title>Name of the title</title>
<subitem>Test</subitem>
<subitem1>Test</subitem1>
</item>
</items>
The solution, including getting the titles, is:
XDocument yourXDocument = XDocument.Load(yourXmlFilePath);
IEnumerable<Tuple<XElement, IEnumerable<XElement>>> yourSubItems =
yourXDocument.Root.Descendants()
.Where(xelem => xelem.Name == "title")
.Select(xelem => new Tuple<XElement, IEnumerable<XElement>>(xelem, xelem.Parent.Elements().Where(subelem => subelem.Name.LocalName.StartsWith("subitem")));
XDocument xdoc = XDocument.Load(path_to_xml);
var query = from i in xdoc.Descendants("item")
select new
{
Title = (string)i.Element("title"),
Subitems = i.Elements()
.Where(e => e.Name.LocalName.StartsWith("subitem"))
.Select(e => (string)e)
.ToList()
};
I have an XML feed (which I don't control) and I am trying to figure out how to detect the volume of certain attribute values within the document.
I am also parsing the XML and separating attributes into Arrays (for other functionality)
Here is a sample of my XML
<items>
<item att1="ABC123" att2="uID" />
<item att1="ABC345" att2="uID" />
<item att1="ABC123" att2="uID" />
<item att1="ABC678" att2="uID" />
<item att1="ABC123" att2="uID" />
<item att1="XYZ123" att2="uID" />
<item att1="XYZ345" att2="uID" />
<item att1="XYZ678" att2="uID" />
</items>
I want to find the volume nodes based on each att1 value. Att1 value will change. Once I know the frequency of att1 values I need to pull the att2 value of that node.
I need to find the TOP 4 items and pull the values of their attributes.
All of this needs to be done in C# code behind.
If I was using Javascript I would create an associative array and have att1 be the key and the frequency be the value. But since I'm new to c# I don't know how to duplicate this in c#.
So I believe, first I need to find all unique att1 values in the XML. I can do this using:
IEnumerable<string> uItems = uItemsArray.Distinct();
// Where uItemsArray is a collection of all the att1 values in an array
Then I get stuck on how I compare each unique att1 value to the whole document to get the volume stored in a variable or array or whatever data set.
Here is the snippet I ended up using:
XDocument doc = XDocument.Load(#"temp/salesData.xml");
var topItems = from item in doc.Descendants("item")
select new
{
name = (string)item.Attribute("name"),
sku = (string)item.Attribute("sku"),
iCat = (string)item.Attribute("iCat"),
sTime = (string)item.Attribute("sTime"),
price = (string)item.Attribute("price"),
desc = (string)item.Attribute("desc")
} into node
group node by node.sku into grp
select new {
sku = grp.Key,
name = grp.ElementAt(0).name,
iCat = grp.ElementAt(0).iCat,
sTime = grp.ElementAt(0).sTime,
price = grp.ElementAt(0).price,
desc = grp.ElementAt(0).desc,
Count = grp.Count()
};
_topSellers = new SalesDataObject[4];
int topSellerIndex = 0;
foreach (var item in topItems.OrderByDescending(x => x.Count).Take(4))
{
SalesDataObject topSeller = new SalesDataObject();
topSeller.iCat = item.iCat;
topSeller.iName = item.name;
topSeller.iSku = item.sku;
topSeller.sTime = Convert.ToDateTime(item.sTime);
topSeller.iDesc = item.desc;
topSeller.iPrice = item.price;
_topSellers.SetValue(topSeller, topSellerIndex);
topSellerIndex++;
}
Thanks for all your help!
Are you using .NET 3.5? (It looks like it based on your code.) If so, I suspect this is pretty easy with LINQ to XML and LINQ to Objects. However, I'm afraid it's not clear from your example what you want. Do all the values with the same att1 also have the same att2? If so, it's something like:
var results = (from element in items.Elements("item")
group element by element.Attribute("att1").Value into grouped
order by grouped.Count() descending
select grouped.First().Attribute("att2").Value).Take(4);
I haven't tested it, but I think it should work...
We start off with all the item elements
We group them (still as elements) by their att1 value
We sort the groups by their size, descending so the biggest one is first
From each group we take the first element to find its att2 value
We take the top four of these results
If you have the values, you should be able to use LINQ's GroupBy...
XDocument doc = XDocument.Parse(xml);
var query = from item in doc.Descendants("item")
select new
{
att1 = (string)item.Attribute("att1"),
att2 = (string)item.Attribute("att2") // if needed
} into node
group node by node.att1 into grp
select new { att1 = grp.Key, Count = grp.Count() };
foreach (var item in query.OrderByDescending(x=>x.Count).Take(4))
{
Console.WriteLine("{0} = {1}", item.att1, item.Count);
}
You can use LINQ/XLINQ to accomplish this. Below is a sample console application I just wrote, so the code might not be optimized but it works.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
namespace FrequencyThingy
{
class Program
{
static void Main(string[] args)
{
string data = #"<items>
<item att1=""ABC123"" att2=""uID"" />
<item att1=""ABC345"" att2=""uID"" />
<item att1=""ABC123"" att2=""uID"" />
<item att1=""ABC678"" att2=""uID"" />
<item att1=""ABC123"" att2=""uID"" />
<item att1=""XYZ123"" att2=""uID"" />
<item att1=""XYZ345"" att2=""uID"" />
<item att1=""XYZ678"" att2=""uID"" />
</items>";
XDocument doc = XDocument.Parse(data);
var grouping = doc.Root.Elements().GroupBy(item => item.Attribute("att1").Value);
foreach (var group in grouping)
{
var groupArray = group.ToArray();
Console.WriteLine("Group {0} has {1} element(s).", groupArray[0].Attribute("att1").Value, groupArray.Length);
}
Console.ReadKey();
}
}
}