Sorry for this long post....But i have a headache from this task.
I have a mile long xml document where I need to extract a list, use distinct values, and pass for transformation to web.
I have completed the task using xslt and keys, but the effort is forcing the server to its knees.
Description:
hundreds of products in xml, all with a number of named and Id'ed cattegories, all categories with at least one subcategory with name and id.
The categories are unique with ID, all subcategories are unique WITHIN that category:
Simplified example form the huge file (left our tons of info irrelevant to the task):
<?xml version="1.0" encoding="utf-8"?>
<root>
<productlist>
<product id="1">
<name>Some Product</name>
<categorylist>
<category id="1">
<name>cat1</name>
<subcategories>
<subcat id="1">
<name>subcat1</name>
</subcat>
<subcat id="2">
<name>subcat1</name>
</subcat>
</subcategories>
</category>
<category id="2">
<name>cat1</name>
<subcategories>
<subcat id="1">
<name>subcat1</name>
</subcat>
</subcategories>
</category>
<category id="3">
<name>cat1</name>
<subcategories>
<subcat id="1">
<name>subcat1</name>
</subcat>
</subcategories>
</category>
</categorylist>
</product>
<product id="2">
<name>Some Product</name>
<categorylist>
<category id="1">
<name>cat1</name>
<subcategories>
<subcat id="2">
<name>subcat2</name>
</subcat>
<subcat id="4">
<name>subcat4</name>
</subcat>
</subcategories>
</category>
<category id="2">
<name>cat2</name>
<subcategories>
<subcat id="1">
<name>subcat1</name>
</subcat>
</subcategories>
</category>
<category id="3">
<name>cat3</name>
<subcategories>
<subcat id="1">
<name>subcat1</name>
</subcat>
</subcategories>
</category>
</categorylist>
</product>
</productlist>
</root>
DESIRED RESULT:
<?xml version="1.0" encoding="utf-8"?>
<root>
<maincat id="1">
<name>cat1</name>
<subcat id="1"><name>subcat1</name></subcat>
<subcat id="2"><name>subcat2</name></subcat>
<subcat id="3"><name>subcat3</name></subcat>
</maincat>
<maincat id="2">
<name>cat2</name>
<subcat id="1"><name>differentsubcat1</name></subcat>
<subcat id="2"><name>differentsubcat2</name></subcat>
<subcat id="3"><name>differentsubcat3</name></subcat>
</maincat>
<maincat id="2">
<name>cat2</name>
<subcat id="1"><name>differentsubcat1</name></subcat>
<subcat id="2"><name>differentsubcat2</name></subcat>
<subcat id="3"><name>differentsubcat3</name></subcat>
</maincat>
</root>
(original will from 2000 products produce 10 categories with from 5 to 15 subcategories)
Things tried:
Xslt with keys - works fine, but pooooor performance
Played around with linq:
IEnumerable<XElement> mainCats =
from Category1 in doc.Descendants("product").Descendants("category") select Category1;
var cDoc = new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement("root"));
cDoc.Root.Add(mainCats);
cachedCategoryDoc = cDoc.ToString();
Result was a "categories only" (not distinct values of categories or subcategories)
Applied the same xlst to that, and got fairly better performance..... but still far from usable...
Can i apply some sort of magic with the linq statement to have the desired output??
A truckload of good karma goes out to the ones that can point me in det right direction..
//Steen
NOTE:
I am not stuck on using linq/XDocument if anyone has better options
Currently on .net 3.5, can switch to 4 if needed
If I understood your question corectly, here's a LINQ atempt.
The query below parses your XML data and creates a custom type which represents a category and contains the subcategories of that element.
After parsing, the data is grouped by category Id to get distinct subcategories for each category.
var doc = XElement.Load("path to the file");
var results = doc.Descendants("category")
.Select(cat => new
{
Id = cat.Attribute("id").Value,
Name = cat.Descendants("name").First().Value,
Subcategories = cat.Descendants("subcat")
.Select(subcat => new
{
Id = subcat.Attribute("id").Value,
Name = subcat.Descendants("name").First().Value
})
})
.GroupBy(x=>x.Id)
.Select(g=>new
{
Id = g.Key,
Name = g.First().Name,
Subcategories = g.SelectMany(x=>x.Subcategories).Distinct()
});
From the results above you can create your document using the code below:
var cdoc = new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement("root"));
cdoc.Root.Add(
results.Select(x=>
{
var element = new XElement("maincat", new XAttribute("id", x.Id));
element.Add(new XElement("name", x.Name));
element.Add(x.Subcategories.Select(c=>
{
var subcat = new XElement("subcat", new XAttribute("id", c.Id));
subcat.Add(new XElement("name", c.Name));
return subcat;
}).ToArray());
return element;
}));
Try this i have done something for it.. attributes are missing you can add them using XElement ctor
var doc = XDocument.Load(reader);
IEnumerable<XElement> mainCats =
doc.Descendants("product").Descendants("category").Select(r =>
new XElement("maincat", new XElement("name", r.Element("name").Value),
r.Descendants("subcat").Select(s => new XElement("subcat", new XElement("name", s.Element("name").Value)))));
var cDoc = new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement("root"));
cDoc.Root.Add(mainCats);
var cachedCategoryDoc = cDoc.ToString();
Regards.
This will parse your xml into a dictionary of categories with all the distinct subcategory names. It uses XPath from this library: https://github.com/ChuckSavage/XmlLib/
XElement root = XElement.Load(file);
string[] cats = root.XGet("//category/name", string.Empty).Distinct().ToArray();
Dictionary<string, string[]> dict = new Dictionary<string, string[]>();
foreach (string cat in cats)
{
// Get all the categories by name and their subcat names
string[] subs = root
.XGet("//category[name={0}]/subcategories/subcat/name", string.Empty, cat)
.Distinct().ToArray();
dict.Add(cat, subs);
}
Or the parsing as one statement:
Dictionary<string, string[]> dict = root
.XGet("//category/name", string.Empty)
.Distinct()
.ToDictionary(cat => cat, cat => root
.XGet("//category[name={0}]/subcategories/subcat/name", string.Empty, cat)
.Distinct().ToArray());
I give you the task of assembling your resulting xml from the dictionary.
Related
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");
I am trying to merge two XMLs with same structure but different data into one.
I am getting this error: A node of type Document cannot be added to content.
Below is my code
var productElements =
testGroupProvider.GetTestGroup().ProductTests.Select(
productTest => new XElement(xNamespace + "Product",
new XElement(xNamespace + "ExternalId", productTest.ProductNameKey),
new XElement(xNamespace + "Name", testGroupProvider.GetProductName(productTest)),
new XElement(xNamespace + "ImageUrl", ChoiceBaseHostName + GetProductImageUrl(productTest, TargetDatabase))));
var root = new XDocument(
new XElement(xNamespace + "Feed",
new XAttribute("xmlns", xNamespace),
new XAttribute("name", BVFeedsName),
new XAttribute("incremental", "true"),
new XAttribute("extractDate", DateTime.Now.ToString("o")),
new XElement(xNamespace + "Categories",
new XElement(xNamespace + "Category",
new XElement(xNamespace + "ExternalId", testGroupProvider.GetProductGroup().Id),
new XElement(xNamespace + "Name", testGroupProvider.GetProductGroup().Name),
testGroupProvider.GetTestGroup().Name),
new XElement(xNamespace + "Products", productElements)));
var filePath = #"D:\testXML\test.xml";
XElement xml = XElement.Load(filePath);
xml.Add(root);
xml.Save(filePath);
Can anyone tell me what i am doing wrong.
This is the XML structure in test.xml
<?xml version="1.0" encoding="utf-8"?>
<Feed xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.6" name="Choice" incremental="true" extractDate="2016-07-12T15:24:44.5732750+10:00">
<Categories>
<Category>
<ExternalId>{09B3B4FB-F5CF-4522-BE96-4C4B535580C3}</ExternalId>
<Name>Cereal and muesli</Name>
</Category>
</Categories>
<Products>
<Product>
<ExternalId>coles-almond-hazelnut-macadamia-cluster-fusions</ExternalId>
<Name>Coles Almond, Hazelnut & Macadamia Cluster Fusions</Name>
<ImageUrl></ImageUrl>
</Product>
</Products>
</Feed>
The second XML has the same structure with different products
<?xml version="1.0" encoding="utf-8"?>
<Feed xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.6" name="Choice" incremental="true" extractDate="2016-07-12T15:24:44.5732750+10:00">
<Categories>
<Category>
<ExternalId>{12}</ExternalId>
<Name>cat1</Name>
</Category>
</Categories>
<Products>
<Product>
<ExternalId>Id</ExternalId>
<Name>Ccoles</Name>
<ImageUrl></ImageUrl>
</Product>
</Products>
</Feed>
I want to combine them like below
<?xml version="1.0" encoding="utf-8"?>
<Feed xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.6" name="Choice" incremental="true" extractDate="2016-07-12T15:24:44.5732750+10:00">
<Categories>
<Category>
<ExternalId>{09B3B4FB-F5CF-4522-BE96-4C4B535580C3}</ExternalId>
<Name>Cereal and muesli</Name>
</Category>
<Category>
<ExternalId>{12}</ExternalId>
<Name>cat1</Name>
</Category>
</Categories>
<Products>
<Product>
<ExternalId>coles-almond-hazelnut-macadamia-cluster-fusions</ExternalId>
<Name>Coles Almond, Hazelnut & Macadamia Cluster Fusions</Name>
<ImageUrl></ImageUrl>
</Product>
<Product>
<ExternalId>Id</ExternalId>
<Name>Ccoles</Name>
<ImageUrl></ImageUrl>
</Product>
</Products>
</Feed>
A xml document must have only one root.
Working with the documents you attached, you can replace the xml.Add(root); with the following (i.e. it will add each node under one root to the other xml root)
foreach (var child in root.Root.Elements())
{
xml.Element(child.Name.ToString()).Add(child.Nodes());
}
Edit - A further generalization
You can generalize the above code using a Merge extension of 2 XElements so that it reads as follows
foreach (var child in root.Elements())
{
xml.Element(child.Name.ToString()).Merge(child, xNamespace + "ExternalId");
}
Having defined the extension
public static void Merge(this XElement root1, XElement root2, XName element_id)
{
root1.Add(root2.Elements().Except(root1.Elements(), new MyComparer(element_id)));
}
with a xml comparer
public class MyComparer : IEqualityComparer<XElement>
{
private XName _element_id;
public MyComparer(XName element_id)
{
_element_id = element_id;
}
public bool Equals(XElement x, XElement y)
{
return x.Element(_element_id).Value.Equals(y.Element(_element_id).Value);
}
public int GetHashCode(XElement el)
{
return el.Element(_element_id).Value.GetHashCode();
}
}
Select correct nodes to add and correct nodes to be added.
var filePath = #"D:\testXML\test.xml";
XElement xml = XElement.Load(filePath);
var xmlCategories = xml.Descendants("Categories").First();
var rootCategories = root.Descendants("Category");
xmlCategories.Add(rootCategories);
var xmlProducts = xml.Descendants("Products").First();
var rootProducts = root.Descendants("Product");
xmlProducts.Add(rootProducts);
xml.Save(filePath);
Be crystal clear what you are doing.
Try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication2
{
class Program
{
const string FILENAME1 = #"c:\temp\test1.xml";
const string FILENAME2 = #"c:\temp\test2.xml";
static void Main(string[] args)
{
XDocument doc1 = XDocument.Load(FILENAME1);
XDocument doc2 = XDocument.Load(FILENAME2);
XElement category1 = doc1.Descendants().Where(x => x.Name.LocalName == "Categories").FirstOrDefault();
XElement category2 = doc2.Descendants().Where(x => x.Name.LocalName == "Categories").FirstOrDefault();
category1.Add(category2.Descendants());
XElement product1 = doc1.Descendants().Where(x => x.Name.LocalName == "Products").FirstOrDefault();
XElement product2 = doc2.Descendants().Where(x => x.Name.LocalName == "Products").FirstOrDefault();
product1.Add(product2.Descendants());
}
}
}
Try this, sorry about the VB
'second is The second XML has the same structure with different products
Dim combined As XElement = New XElement(test) 'create copy of test.xml
combined.<Categories>.LastOrDefault.Add(second.<Categories>.Elements)
combined.<Products>.LastOrDefault.Add(second.<Products>.Elements)
or
'if test can be used to combine then
test.<Categories>.LastOrDefault.Add(second.<Categories>.Elements)
test.<Products>.LastOrDefault.Add(second.<Products>.Elements)
The result is
<Feed name="Choice" incremental="true" extractDate="2016-07-12T15:24:44.5732750+10:00" xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.6">
<Categories>
<Category>
<ExternalId>{09B3B4FB-F5CF-4522-BE96-4C4B535580C3}</ExternalId>
<Name>Cereal and muesli</Name>
</Category>
<Category>
<ExternalId>{12}</ExternalId>
<Name>cat1</Name>
</Category>
</Categories>
<Products>
<Product>
<ExternalId>coles-almond-hazelnut-macadamia-cluster-fusions</ExternalId>
<Name>Coles Almond, Hazelnut & Macadamia Cluster Fusions</Name>
<ImageUrl></ImageUrl>
</Product>
<Product>
<ExternalId>Id</ExternalId>
<Name>Ccoles</Name>
<ImageUrl></ImageUrl>
</Product>
</Products>
</Feed>
The test data I used is
Dim test As XElement =
<Feed xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.6" name="Choice" incremental="true" extractDate="2016-07-12T15:24:44.5732750+10:00">
<Categories>
<Category>
<ExternalId>{09B3B4FB-F5CF-4522-BE96-4C4B535580C3}</ExternalId>
<Name>Cereal and muesli</Name>
</Category>
</Categories>
<Products>
<Product>
<ExternalId>coles-almond-hazelnut-macadamia-cluster-fusions</ExternalId>
<Name>Coles Almond, Hazelnut & Macadamia Cluster Fusions</Name>
<ImageUrl></ImageUrl>
</Product>
</Products>
</Feed>
Dim second As XElement =
<Feed xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/5.6" name="Choice" incremental="true" extractDate="2016-07-12T15:24:44.5732750+10:00">
<Categories>
<Category>
<ExternalId>{12}</ExternalId>
<Name>cat1</Name>
</Category>
</Categories>
<Products>
<Product>
<ExternalId>Id</ExternalId>
<Name>Ccoles</Name>
<ImageUrl></ImageUrl>
</Product>
</Products>
</Feed>
The XElements can be loaded like this
test = XElement.Load("PATH")
second = XElement.Load("second PATH")
and saved like this
test.Save("PATH")
second.Save("second PATH")
combined.Save("combined PATH")
I made some try with XML Reader, Xpath... and know linq
But wont find a way to solve these things.
I have to extract the information, for each Order into one row, in this row should be the Information of the first elements and the Items and the orders as well as the status of the Orders...
Is there a way to extract all these information to one row within one linq-query? Or do I have to build steps for this?
(Visualstudio 2010/2013 C# .Net 4)
<Account>
<Name>Name1</Name>
<InId>100</InId>
<CustomID>100000087</CustomID>
<ZipCode>zipcode</ZipCode>
<Items>
<Item>
<ItemID>700</ItemID>
<ItemName>Itemname1</ItemName>
<Orders>
<Order>
<IDIndex>1000</IDIndex>
<IDParam>T1</IDParam>
<Themes>
<Theme>
<Status>Alert</Status>
<Lastget>01.01.2015</Lastget>
</Theme>
</Themes>
</Order>
</Orders>
<Item>
<ItemID>800</ItemID>
<ItemName>Itemname2</ItemName>
<Orders>
<Order>
<IDIndex>5001</IDIndex>
<IDParam>T1</IDParam>
<Themes>
<Theme>
<Status>Alert1</Status>
<Lastget>01.01.2015</Lastget>
</Theme>
</Themes>
</Order>
<Order>
<IDIndex>5002</IDIndex>
<IDParam>T1</IDParam>
<Themes>
<Theme>
<Status>Alert1</Status>
<Lastget>01.01.2015</Lastget>
</Theme>
</Themes>
</Order>
<Order>
<IDIndex>5003</IDIndex>
<IDParam>T1</IDParam>
<Themes>
<Theme>
<Status>Alert2</Status>
<Lastget>01.01.2015</Lastget>
</Theme>
</Themes>
</Order>
</Orders>
</Item>
</Items>
</Account>
Following query will give you the required data:-
var result = xdoc.Root.Descendants("Item")
.Select(x => new
{
Name = (string)x.Document.Root.Element("Name"),
InId = (string)x.Document.Root.Element("InId"),
CustomID = (string)x.Document.Root.Element("CustomID"),
ItemID = (string)x.Element("ItemID"),
ItemName = (string)x.Element("ItemName"),
OrdersList = x.Descendants("Order")
.Select(y => new
{
IDIndex = (string)y.Element("IDIndex"),
IDParam = (string)y.Element("IDParam"),
ThemesList = y.Descendants("Theme")
.Select(z => new
{
Status = (string)z.Element("Status"),
Lastget = (string)z.Element("Lastget")
}).ToList()
}).ToList()
});
Please note that two lists will be created for 2 items, and for each item I am creating a list of orders and within each order list of themes.
Using a loop with xelement class in C# i would like to get the below result!
<data>
<description>Cities that I have recently visited.</description>
<cities>
<city id="1">
<name>Chicago1</name>
<state>IN1</state>
</city>
<city id="2">
<name>Chicago2</name>
<state>IN2</state>
</city>
<city id="3">
<name>Chicago3</name>
<state>IN3</state>
</city>
</cities>
</data>
This is the code i have tried so far! any help?? i need to use a loop and get the above values..The loop i used is commented..
namespace ConsoleApplication13
{
class Program
{
static void Main(string[] args)
{
XElement xmlDataStore = new XElement("data",
new XElement("cities",
new XElement("city", new XAttribute("id", "1")),
new XElement("city", "Colombo"),
new XElement("name", "lname"),
new XElement("state", "0772569984")
)
)
;
//var list = from x in XElement.ReadFrom(xmlDataStore).Element("Node").Elements()
//select new
//{
// Name = x.Name,
// Value = (string)x
//};
Console.WriteLine(xmlDataStore);
Console.ReadLine();
}
}
}
What i get...
<cities>
<city id="1">
<name>Chicago1</name>
<state>IN1</state>
</city>
</cities>
What i want...
<data>
<description>Cities that I have recently visited.</description>
<cities>
<city id="1">
<name>Chicago1</name>
<state>IN1</state>
</city>
<city id="2">
<name>Chicago2</name>
<state>IN2</state>
</city>
<city id="3">
<name>Chicago3</name>
<state>IN3</state>
</city>
</cities>
</data>
Okay, I still don't exactly know what the real problem is, so let's start with this:
private static void citiesXml()
{
const string desc = "Cities that I have recently visited.";
// set up a list of all the different cities
var list = new List<Tuple<string, string>>();
list.Add(new Tuple<string, string>("Chicago1", "IN1"));
list.Add(new Tuple<string, string>("Chicago2", "IN2"));
list.Add(new Tuple<string, string>("Chicago3", "IN3"));
var xmlDataStore = new XElement("data", new XElement("description", desc));
var xmlCities = new XElement("cities");
// loop through the list of cities and create a XElement for each single one
for (var i = 0; i < list.Count; i++)
{
xmlCities.Add(new XElement("city",
new XAttribute("id", i + 1),
new XElement("name", list[i].Item1),
new XElement("state", list[i].Item2)));
}
// add the cities to the data store object
xmlDataStore.Add(xmlCities);
Console.WriteLine(xmlDataStore);
Console.ReadLine();
}
This will print:
<data>
<description>Cities that I have recently visited.</description>
<cities>
<city id="1">
<name>Chicago1</name>
<state>IN1</state>
</city>
<city id="2">
<name>Chicago2</name>
<state>IN2</state>
</city>
<city id="3">
<name>Chicago3</name>
<state>IN3</state>
</city>
</cities>
</data>
So as far as I see it, the only difference is that there are no blank lines between cities. Are the missing blank lines the problem?
Check by Descendants
XDocument xdoc = XDocument.Load("Xml File Path"); //save that xml in "C:\test.xml "
IEnumerable<XElement> xEle = xdoc.XPathSelectElements("//cities");
if(xEle !=null)
{
foreach(XElement xE in Xelement.Descendants())
{
// here you will get everything ......
}
}
you can loop through
I am beginner in C#.
Simple Example of bigger case:
Input:
<?xml version="1.0" encoding="utf-8"?>
<products>
<product>
<id>1</id>
<name>John</name>
</product>
<product>
<id>2</id>
<name>Tom</name>
</product>
<product>
<id>3</id>
<name>Sam</name>
</product>
</products>
</xml>
Output(for id=1):
<id>2</id>
<name>Tom</name>
My part code try psedocode:
XDocument doc=XDocument.Parse(".............");
var els= doc.Descendants("product");
foreach(e in els){
node=e.Element("id");
if(2==node.Value){
return e;
}
}
Please help,
Thanks
Currently your xml file is not well-formatted - remove closing </xml> tag from your file to make it valid. And here is the query:
int id = 1;
XDocument xdoc = XDocument.Load(path_to_xml);
var product = xdoc.Descendants("product")
.Where(p => (int)p.Element("id") == id)
.SingleOrDefault();
This query will return whole <product> element or null if match not found.
Also I believe product name will be enough for you to select (because you already have product id):
var name = xdoc.Descendants("product")
.Where(p => (int)p.Element("id") == id)
.Select(p => (string)p.Element("name"))
.SingleOrDefault();
Returns Tom for id = 2
This will return the product (as in your question) not the id
var product = doc.XPathSelectElement("//product[id and id[text() = '1']]");
You might be looking for XPath:
root.XPathSelectElements(#"//products/product/id[text()='2']")
Edit To the comment: Directly getting the name: //products/product/id[text()='2']/../name
See full example
using System.Xml.Linq;
using System.Xml.XPath;
public class Program
{
public static void Main(string[] args)
{
var doc = XDocument.Parse(XML);
foreach(var n in doc.Root.XPathSelectElements(
#"//products/product/id[text()='2']"))
{
System.Console.WriteLine("Not that hard: '{0}'", n.Parent.Element("name").Value);
}
// Direct query for name:
foreach(var n in doc.Root.XPathSelectElements(
#"//products/product/id[text()='2']/../name"))
{
System.Console.WriteLine("Directly: '{0}'", n.Value);
}
}
private const string XML =
#"<?xml version=""1.0"" encoding=""utf-8""?>
<products>
<product>
<id>1</id>
<name>John</name>
</product>
<product>
<id>2</id>
<name>Tom</name>
</product>
<product>
<id>3</id>
<name>Sam</name>
</product>
</products>";
}
Printing:
Not that hard: 'Tom'
Directly: 'Tom'