"where" query using linq xml - c#

been taxing my brain trying to figure out how to perform a linq xml query.
i'd like the query to return a list of all the "product" items where the category/name = "First Category" in the following xml
<catalog>
<category>
<name>First Category</name>
<order>0</order>
<product>
<name>First Product</name>
<order>0</order>
</product>
<product>
<name>3 Product</name>
<order>2</order>
</product>
<product>
<name>2 Product</name>
<order>1</order>
</product>
</category>
</catalog>

Like so:
XDocument doc = XDocument.Parse(xml);
var qry = from cat in doc.Root.Elements("category")
where (string)cat.Element("name") == "First Category"
from prod in cat.Elements("product")
select prod;
or perhaps with an anonymous type too:
XDocument doc = XDocument.Parse(xml);
var qry = from cat in doc.Root.Elements("category")
where (string)cat.Element("name") == "First Category"
from prod in cat.Elements("product")
select new
{
Name = (string)prod.Element("name"),
Order = (int)prod.Element("order")
};
foreach (var prod in qry)
{
Console.WriteLine("{0}: {1}", prod.Order, prod.Name);
}

Here's an example:
string xml = #"your XML";
XDocument doc = XDocument.Parse(xml);
var products = from category in doc.Element("catalog").Elements("category")
where category.Element("name").Value == "First Category"
from product in category.Elements("product")
select new
{
Name = product.Element("name").Value,
Order = product.Element("order").Value
};
foreach (var item in products)
{
Console.WriteLine("Name: {0} Order: {1}", item.Name, item.Order);
}

You want to use the Single extension method here. Try the following:
var category = doc.RootNode.Elements("category").Single(
c => c.Attribute("name").Value == "First Category");
var products = category.Elements("product");
Note that this assumes you only have one category with name "First Category". If you possibly have more, I recommend using Marc's solution; otherwise, this should be the more appropiate/efficient solution. Also, this will throw an exception if any category node doesn't have a name child node. Otherwise, it should do exactly what you want.

Related

XML to List - Unable to add all nodes to list

I have two XML documents:
XmlDocument languagesXML = new XmlDocument();
languagesXML.LoadXml(
#"<languages>
<language>
<name>English</name>
<country>8</country>
<country>9</country>
<country>3</country>
<country>12</country>
</language>
<language>
<name>French</name>
<country>1</country>
<country>3</country>
<country>7</country>
<country>13</country>
</language>
</languages>");
XmlDocument productsXML = new XmlDocument();
productsXML.LoadXml(#"<products>
<product>
<name>Screws</name>
<country>3</country>
<country>12</country>
<country>29</country>
</product>
<product>
<name>Hammers</name>
<country>1</country>
<country>13</country>
</product>
</products>");
I am trying to add the relative information, such as name and country of each language and product, to a list as I want to compare the two and group the languages that correspond to a certain language. For example, taking the above into account, my goal is to have an output similar to this:
Screws -> English, French
Hammers -> French
English and French correspond to Screws as they all share a common country value. Same with Hammers. (The above XML is just a snapshot of the entire XML).
I have tried using How to read a XML file and write into List<>? and XML to String List. While this piece of code:
var languages = new List<string>();
XmlNode xmlNode;
foreach(var node in languagesXML.LastChild.FirstChild.ChildNodes)
{
xmlNode = node as XmlNode;
languages.Add(xmlNode.InnerXml);
}
languages.ForEach(Console.WriteLine);
works, it will only add "English", "8", "9", "3", and "12" to the list. The rest of the document seems to be ignored. Is there a better way of doing what I'm trying to achieve? Would I even be able to compare and attain an output like what I need even if I got everything adding to a list? Would Muenchian grouping be something I should be looking at?
This is a job for LINQ to XML. Eg
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace ConsoleApp18
{
static class EnumerableUtils
{
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> col)
{
return new HashSet<T>(col);
}
}
class Program
{
static void Main(string[] args)
{
XDocument languagesXML = XDocument.Parse(
#"<languages>
<language>
<name>English</name>
<country>8</country>
<country>9</country>
<country>3</country>
<country>12</country>
</language>
<language>
<name>French</name>
<country>1</country>
<country>3</country>
<country>7</country>
<country>13</country>
</language>
</languages>");
var languages = languagesXML.Root
.Elements("language")
.Select(e =>
new
{
Name = (string)e.Element("name"),
Countries = e.Elements("country").Select(c => (int)c).ToHashSet()
})
.ToList();
XDocument productsXML = XDocument.Parse(#"<products>
<product>
<name>Screws</name>
<country>3</country>
<country>12</country>
<country>29</country>
</product>
<product>
<name>Hammers</name>
<country>1</country>
<country>13</country>
</product>
</products>");
var products = productsXML.Root
.Elements("product")
.Select(e =>
new
{
Name = (string)e.Element("name"),
Countries = e.Elements("country").Select(c => (int)c).ToHashSet()
})
.ToList();
var q = from p in products
from l in languages
where p.Countries.Overlaps(l.Countries)
let pl = new { p, l, }
group pl by p.Name into byProductName
select new
{
ProductName = byProductName.Key,
Languages = byProductName.Select(e => e.l.Name).ToList()
};
foreach (var p in q.ToList())
{
Console.WriteLine($"Product: {p.ProductName} is available in languages: {String.Join(",", p.Languages.ToArray())}");
}
}
}
}
outputs
Product Screws is available in languages English,French
Product Hammers is available in languages French

get parent node filtered by sub level child node, XML

I have the following XML dataset
<?xml version="1.0" ?>
<productCatalog>
<catalogName>Freeman and Freeman Unique Catalog 2010</catalogName>
<expiryDate>2012-01-01</expiryDate>
<products>
<product id="1001">
<productName>Gourmet Coffee</productName>
<description>The finest beans from rare Chillean plantations.</description>
<productPrice>0.99</productPrice>
<inStock>true</inStock>
<category id ="100">
<name>Latin Breakfast</name>
<description>International Range</description>
<subcategory id ="SUB1000">
<name>Energy</name>
<description>blah blah</description>
</subcategory>
</category>
</product>
<product id="1002">
<productName>Blue China Tea Pot</productName>
<description>A trendy update for tea drinkers.</description>
<productPrice>102.99</productPrice>
<inStock>true</inStock>
<category id ="200">
<name>Asian Breakfast</name>
<description>Asian Range</description>
<subcategory id ="SUB1000">
<name>Organic</name>
<description>healthy organic food for a longer life</description>
</subcategory>
</category>
</product>
<product id="1002">
<productName>Blue China Tea Pot</productName>
<description>A trendy update for tea drinkers.</description>
<productPrice>102.99</productPrice>
<inStock>true</inStock>
<category id ="300">
<name>Italian Breakfast</name>
<description>Roman Breakfast</description>
<subcategory id ="SUB2000">
<name>italian</name>
<description>Roman sttyle breakfast</description>
</subcategory>
</category>
</product>
</products>
</productCatalog>
i want to get all products who's with subcategory id = "SUB1000"
i have written the code
public static void ProductsFilteredBySubCategory(string path) {
XElement root = XElement.Load(path);
IEnumerable<XElement> productElems = root.Element("products").Elements().Where(e => e.Name == "product" ).Select(s => s);
IEnumerable<XElement> subcats;
foreach (var item in productElems){
Console.WriteLine( item.Element("category").Elements().Where(e => e.Name == "subcategory").Select(s => s.Name) );
}
}
but the print statement in the foreach does not seems to have the products that was filtered, How do i filter the products by the desired subcategory id? Maybe i'm doing this in the incorrect way...
You're going about it in somewhat of a roundabout way. Here is how I would structure this:
XDocument document = XDocument.Load(path);
var elements = document.Descendants("subcategory")
.Where(i => (string)i.Attribute("id") == "SUB1000")
.Select(i => i.Parent.Parent);
foreach(var element in elements)
{
Console.WriteLine(element);
}
Granted, here I am not looking at specific entity types, but just assuming that the IDs are unique for what you are trying to find, that will extract the right elements.
Descendants can be useful in this case.
var document = XDocument.Load(path);
var products = document.Descendants("product")
.Where(product => product.Descendants("subcategory")
.Any(sub => sub.Attributes("id")
.Any(id => id.Value == "SUB1000")));
foreach(var product in products)
{
var subId = product.Attributes("id").Select(id => id.Value).FirstOrDefault();
Console.WriteLine($"Product: {subId}");
}
You can use the below code to get the product
var document = XDocument.Load("pathtothexml");
var coll = document.Descendants("subcategory").Where(s => s.Attribute("id").Value.Equals("SUB1000")).Ancestors("product");

get child nodes xml attribute value

<test>
<acc id="1"> acc1 </acc>
<acc id="2"> acc2 </acc>
<acc id="3"> acc3 </acc>
<acc id="4"> acc4 </acc>
</test>
For example, if I want to take the value of each <acc> element:
var iAccs = xdoc.Descendants("test").Elements("acc").Select(p => p.Value);
List<string> myList = new List<string>();
foreach(string p in iAccs)
{
myList.Add(p);
}
But how to substract all the attribute "id" values of each <acc> elements?
You can easily get this using LINQ-to-XML:-
XDocument xdoc = XDocument.Load(#"You XML file path");
List<string> result = xdoc.Descendants("acc")
.Select(x => (string)x.Attribute("id")).ToList();
Or if you prefer query syntax then:-
List<int> result2 = (from x in xdoc.Descendants("acc")
select (int)x.Attribute("id")).ToList();

List is empty after parsing XML with LinQ

I have an xml file similar to the following:
<doc>
<file>
<header>
<source>
RNG
</source>
</header>
<body>
<item name="items.names.id1">
<property>propertyvalue1</property>
</item>
<!-- etc -->
<item name="items.names.id100">
<property>propertyvalue100</property>
</item>
<!-- etc -->
<item name="otheritems.names.id100">
<property>propertyvalue100</property>
</item>
</body>
</file>
</doc>
And the following class:
private class Item
{
public string Id;
public string Property;
}
The file has, for example, 100 item entries (labeled 1 to 100 in the name attribute). How can I use Linq Xml to get hold of these nodes and place them a in list of item?
Using Selman22's example, I'm doing the following:
var myList = xDoc.Descendants("item")
.Where(x => x.Attributes("name").ToString().StartsWith("items.names.id"))
.Select(item => new Item
{
Id = (string)item.Attribute("name"),
Name = (string)item.Element("property")
}).ToList();
However, the list is empty. What am I missing here?
Using LINQ to XML:
XDocument xDoc = XDocument.Load(filepath);
var myList = xDoc.Descendants("item").Select(item => new Item {
Id = (string)item.Attribute("name"),
Property = (string)item.Element("property")
}).ToList();
You can use LinqToXml to directly query the XML, or deserialize it and use LINQ to object. If you choose to deserialize I suggest to start from the schema and generate the classes representing your datamodel with xsd.exe. If you don't have the schema of your xml, even xsd.exe can infer one from an example xml file, but you probably need to fine tune the result.
Try this one XElement root = XElement.Parse("your file name");
var items textSegs =(from item in root.Descendants("item")
select item).ToList();
Now iterate over list and store it
The below is a way of getting information from xml using Xdocument.
string input = "<Your xml>";
Xdocument doc = XDocument.Parse(input);
var data = doc.Descendants("item");
List<Items> itemsList = new List<Items>();
foreach(var item in data)
{
string itemname= item.Element("item").Value;
string property = item.Element("property").Value;
itemsList.Add(new item(itemname, property));
}
I'm guessing you want the code given how your question is phrased.. also I'm assuming the real XML is very simplistic as well.
var items = from item in doc.Descendants("item")
select new Item()
{
Id = item.Attributes("name").First().Value,
Property = item.Elements().First().Value,
};
Just ensure that your xml is loaded into doc. You can load the xml in two ways:
// By a string with xml
var doc = XDocument.Parse(aStringWithXml);
// or by loading from uri (file)
var doc = XDocuemnt.Load(aStringWhichIsAFile);

Filter XDocument more efficiently

I would like to filter with high performance XML elements from an XML document.
Take for instance this XML file with contacts:
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="asistentes.xslt"?>
<contactlist evento="Cena Navidad 2010" empresa="company">
<contact type="1" id="1">
<name>Name1</name>
<email>xxxx#zzzz.es</email>
<confirmado>SI</confirmado>
</contact>
<contact type="1" id="2">
<name>Name2</name>
<email>xxxxxxxxx#zzzze.es</email>
<confirmado>Sin confirmar</confirmado>
</contact>
</contaclist>
My current code to filter from this XML document:
using System;
using System.Xml.Linq;
class Test
{
static void Main()
{
string xml = #" the xml above";
XDocument doc = XDocument.Parse(xml);
foreach (XElement element in doc.Descendants("contact")) {
Console.WriteLine(element);
var id = element.Attribute("id").Value;
var valor = element.Descendants("confirmado").ToList()[0].Value;
var email = element.Descendants("email").ToList()[0].Value;
var name = element.Descendants("name").ToList()[0].Value;
if (valor.ToString() == "SI") { }
}
}
}
What would be the best way to optimize this code to filter on <confirmado> element content?
var doc = XDocument.Parse(xml);
var query = from contact in doc.Root.Elements("contact")
let confirmado = (string)contact.Element("confirmado")
where confirmado == "SI"
select new
{
Id = (int)contact.Attribute("id"),
Name = (string)contact.Element("name"),
Email = (string)contact.Element("email"),
Valor = confirmado
};
foreach (var contact in query)
{
...
}
Points of interest:
doc.Root.Elements("contact") selects only the <contact> elements in the document root, instead of searching the whole document for <contact> elements.
The XElement.Element method returns the first child element with the given name. No need to convert the child elements to a list and take the first element.
The XElement and XAttribute classes provide a wide selection of convenient conversion operators.
You could use LINQ:
foreach (XElement element in doc.Descendants("contact").Where(c => c.Element("confirmado").Value == "SI"))

Categories