Combining XML Nodes - c#

I am looking for some help in seeing how to combine nodes from two separate sections of an XML file. The idea is there is going to be a section with default information and another section that can add more information or remove some of the default information. Here is an example of what it would look like.
<data>
<products>
<product name="Product A" />
<product name="Product B">
<category name="Category 2">
<issue name="Special Issue" />
</category>
</product>
<product name="Product C">
<category name="Category 1" remove="true" />
<category name="Special Category">
<issue name="Secret Issue" />
</category>
</product>
<product name="Product D">
<category name="Category 1">
<issue name="Standard Issue" remove="true"/>
<issue name="Complex Issue">
</category>
</product>
</products>
<categories>
<category name="Category 1">
<issue name="Standard Issue" />
<issue name="Advanced Issue" />
</category>
<category name="Category 2" />
</categories>
</data>
The idea is that I can define products separately from the categories/issues since there is a lot of overlap with this information. However, some products need to have slightly different categories or issues. Below is how it should look afterwards.
Product A
Category 1
Standard Issue
Advanced Issue
Category 2
Product B
Category 1
Standard Issue
Advanced Issue
Category 2
Special Issue
Product C
Category 2
Special Category
Secret Issue
Product D
Category 1
Advanced Issue
Complex Issue
Category 2
I could use a bunch of for loops to iterate over the information, however, I am trying to see if there are any more elegant ways of doing this.
PS - Right now just outputting the information as it should be is fine. I do not want to edit the XML itself since it is just a one-time load at the beginning of my program. I am going to be adding either some classes or structs to represent this data.

The complex data structure that you are trying to set up, is imho not the best way to simplify your XML. Eventually, it takes quite some effort in trying to read your data file, and saving it would probably be quite tiresome.
Some points to think about:
How do you save the data
What happens if half of your products suddenly need a new standard issue, or a new category, do you decide at that time to add it to half of your nodes, or to add it to the default array and add the remove directive to your product.category or product.category.issues nodes?
Is the remove directive really a requirement you need/want to add?
If you want to "read" the database as an outsider, would you understand the structure yourself (I found it quite hard, and these were only 4 products)
UPDATE (for the original implementation, please check below the update)
The more I think about the problem, I would say that you should re-examine your data structure from the top.
I see the structure now rather like:
Product -> Has 0 or more Issues
Issue -> Has exactly 1 category
Categories
So, in my opinion, the easier way to represent your data, would be to remove the category tag from your products, and directly add the Issues under the product. The categories could then still be in a separate nodelist containing potential extra information, as such:
<?xml version="1.0" encoding="utf-8" ?>
<data>
<products>
<product name="Product A">
<issue name="Standard Issue" category="Category 1" />
<issue name="Advanced Issue" category="Category 1" />
</product>
<product name="Product B">
<issue name="Standard Issue" category="Category 1" />
<issue name="Advanced Issue" category="Category 1" />
<issue name="Special Issue" category="Category 2" />
</product>
<product name="Product C">
<issue name="Secret Issue" category="Special Category" />
</product>
<product name="Product D">
<issue name="Advanced Issue" category="Category 1" />
<issue name="Complex Issue" category="Category 1" />
</product>
</products>
<categories>
<category name="Category 1" />
<category name="Category 2" />
<category name="Special category" />
</categories>
</data>
It would simplify reading your nodes, making it more readable (from a human perspective as well), more maintainable over a long term (face it, how many times will the data structure stay as originally designed?), and it would be possible to separate the categories from the issues for the products.
I explicitly removed the empty Category 2 option from each single product, as there would be no need for them with this structure (it's the issues which are important here, imho)
below is the implementation to give an idea how much effort has to be done to actually get so far as to reading the original xml
ORIGINAL
I checked to create an elegant reader design, but this eventually will only help you so far and would be very dependent on how much your simplified data structure posted here conforms with the actual requirements.
As a base, I made some classes for the data, I used Abstract classes to provide the Name & Remove attribute, so that it would be easier to "generalize" the reader, though, some special implementations were needed
[XmlRoot("data")]
public class Data
{
[XmlArray("products")]
[XmlArrayItem("product")]
public Product[] Products { get; set; }
[XmlArray("categories")]
[XmlArrayItem("category")]
public Category[] Categories { get; set; }
}
public abstract class AbstractNamedNode
{
[XmlAttribute("name")]
public string Name { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
if (obj is AbstractNamedNode)
{
return string.Equals(((AbstractNamedNode)obj).Name, this.Name);
}
return base.Equals(obj);
}
public override int GetHashCode()
{
if (string.IsNullOrEmpty(Name))
{
return base.GetHashCode();
}
return Name.GetHashCode();
}
public override string ToString()
{
return string.Format("{0}", Name);
}
public virtual T CloneBasic<T>()
where T: AbstractNamedNode, new()
{
T result = new T();
result.Name = this.Name;
return result;
}
}
public abstract class AbstractNamedRemovableNode : AbstractNamedNode
{
[XmlAttribute("remove")]
public bool Remove { get; set; }
public override T CloneBasic<T>()
{
var result = base.CloneBasic<T>() as AbstractNamedRemovableNode;
result.Remove = this.Remove;
return result as T;
}
}
public class Product : AbstractNamedNode
{
[XmlElement("category")]
public Category[] Categories { get; set; }
}
public class Category : AbstractNamedRemovableNode
{
[XmlElement("issue")]
public Issue[] Issues { get; set; }
}
public class Issue : AbstractNamedRemovableNode
{
// intended blank
}
This offers the structure to read the raw format of the XML already (using the XmlSerializer). The Issue class is currently quite basic, though, I guess that would be different in the actual implementation.
To read the raw dataset, you could use the standard XmlSerializer way:
Data dataFromXml = null;
string path = Path.Combine(Environment.CurrentDirectory, "datafile.xml");
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
XmlSerializer xs = new XmlSerializer(typeof(Data));
dataFromXml = xs.Deserialize(fs) as Data;
}
At this time (and when no exception occured trying to parse the data), the dataFromXML would contain all defined products (with their respective categories and issues) and the categories nodes.
To read this into your eventual setup, you would have to compare each category (and clone their info) and then compare your resultset another time with your standards set (to see which undefined categories in your product should still be added).
For this implementation, this DataProvider would return a combined cloned set of products, from the original dataFromXML file. By relying on the Abstract classes (with generic specification) it is possible to compress all the loops a bit, just it becomes harder to read
public static class DataProvider
{
private static T GetMatchingItem<T, K>(T[] SourceArray, K MatchingNode)
where T: AbstractNamedRemovableNode
where K: AbstractNamedRemovableNode
{
if (SourceArray == null || SourceArray.Length == 0 || MatchingNode == null)
{
return null;
}
var query = from i in SourceArray
where !i.Remove && i.Equals(MatchingNode)
select i;
return query.SingleOrDefault();
}
private static T[] CombineArray<T>(T[] sourceArray, T[] baseArray)
where T : AbstractNamedRemovableNode, new()
{
IList<T> results = new List<T>();
if (sourceArray != null)
{
foreach (var item in sourceArray)
{
if (item.Remove)
{
continue;
}
T copy = default(T);
copy = item.CloneBasic<T>();
if (copy is Category)
{
Category category = copy as Category;
Category original = item as Category;
Category matching = GetMatchingItem(baseArray, item) as Category;
if (matching != null)
{
category.Issues = CombineArray(original.Issues, matching.Issues);
}
else
{
category.Issues = CombineArray(original.Issues, null);
}
}
results.Add(copy);
}
}
if (baseArray != null)
{
foreach (var item in baseArray)
{
if (results.Contains(item))
{
continue;
}
if (sourceArray != null && sourceArray.Contains(item))
{
// the remove option would have worked here
continue;
}
T copy = item as T;
if (copy is Category)
{
Category category = copy as Category;
Category original = item as Category;
category.Issues = CombineArray(original.Issues, null);
}
results.Add(copy);
}
}
return results.OrderBy((item) => item.Name).ToArray();
}
public static Product[] GetCombinedProductInfoFromData(Data data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
IList<Product> products = new List<Product>();
if (data.Products != null)
{
foreach (var originalProduct in data.Products)
{
Product product = originalProduct.CloneBasic<Product>();
if (originalProduct.Categories != null && originalProduct.Categories.Length > 0)
{
product.Categories = CombineArray(originalProduct.Categories, data.Categories);
}
else
{
product.Categories = CombineArray(data.Categories, null);
}
products.Add(product);
}
}
return products.ToArray();
}
}
By using the following display program, it eventually works as specified, but it shouldn't be this "hard" to built your data, and it would be highly dependent on the size of the products and the standard nodes (categories and issues) how fast the algorithm would be.
var productList = DataProvider.GetCombinedProductInfoFromData(dataFromXml);
foreach (var product in productList)
{
Console.WriteLine(product);
if (product.Categories == null)
{
continue;
}
foreach (var category in product.Categories)
{
Console.WriteLine("\t{0}", category);
if (category.Issues == null)
{
continue;
}
foreach (var issue in category.Issues)
{
Console.WriteLine("\t\t{0}", issue);
}
}
}
The result however would be, a list of cloned products, with the default categories cloned combined with the specified categories and the same goes for the issues in the categories.
The only thing, I want to point out, is to think carefully if this is really the way you want to structure your data. Especially the "remove" directive is a b*** ;)

Related

How can I use XML as a data source in WebAPI .NET

I'm having some problems with my WebAPI. I have followed the guide from Microsoft and it works: https://learn.microsoft.com/en-us/aspnet/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api?fbclid=IwAR0tEMDMX3URn2YO0pwnTTAbY2RuGkdu-HUobznac4Lwus6rOVPSeiX-lFs
Now I want to be able to get data from my XML file instead of the hardcoded values the guide use. I have tried to search for this already but not sure I find the right thing. How can I do this?
Code I have tried:
public IEnumerable<Book> GetAllProducts()
{
XDocument doc = XDocument.Load("C:\\Users\\Name\\Desktop\\products.xml");
foreach (XElement element in doc.Descendants("Catalog")
.Descendants("Product"))
{
Product product = new Product();
product.Id = element.Element("Id").Value;
product.Name = element.Element("Name").Value;
product.Category = element.Element("Category").Value;
product.Added_Date = element.Element("Added_Date").Value;//this will not work because its not a string
product.Price = element.Element("Price").Value;//this will not work because its not a string
products.Add(product);
}
return products;
}
XML Code:
<?xml version="1.0"?>
<catalog>
<product id="P1">
<name>Royal Gala</name>
<category>Apple</category>
<country>New Zeeland</country>
<price>33.00</price>
<added_date>2011-01-11</added_date>
<description>This is a lovely red apple.</description>
</product>
<product id="P2">
<name>Granny Smith</name>
<category>Apple</category>
<country>Australia</country>
<price>33.00</price>
<added_date>2013-12-25</added_date>
<description>This is a lovely green apple.</description>
</product>
</catalog>
You just need to parse string to a specific type
Product product = new Product();
//..
product.Added_Date = DateTime.Parse(element.Element("Added_Date").Value);
product.Price = double.Parse(element.Element("Price").Value); //or float, or decimal
And consider using Elements method instead of Descendants cause last one returns all children and their inner children and so on and Elements returns first level children
var productNodes = doc.Root.Elements("product"); //note this is case sensitive
foreach (XElement element in productNodes) {
Product product = new Product();
//..
}

Searching Xml Document with linq doesn't return anything

I'm trying to learn how to save and extract data with XML in C#, and even though I've read various answers to similar questions on here, I cannot get a hold of why my statement isn't returning anything.
The program I'm writing is just a test, in which I've saved data about a couple movies in the xml document and now I'm trying to retrieve some of them based on their cost.
This is the class that I've written for searching:
class ExtractData
{
private XDocument _xdoc;
public List<string> SearchByCost(double cost)
{
_xdoc = XDocument.Load(FileLocation.XmlFileLocation);
List<string> list = new List<string>();
var movies = from movie in _xdoc.Root.Elements("Name")
where Convert.ToDouble(movie.Element("Cost").Value) < cost
select movie;
foreach (var item in movies)
{
list.Add(item.Value);
}
return list;
}
}
This is how I'm trying to make it print it in the console:
eData = new ExtractData();
foreach (var movie in eData.SearchByCost(9))
{
Console.WriteLine(movie);
}
And this is the content of the XML document:
<?xml version="1.0" encoding="utf-8"?>
<Movies>
<Movie>
<Id>1</Id>
<Name>Shawshank Redemption</Name>
<Director>Frank Darabont</Director>
<Year>1994</Year>
<Cost>9.95</Cost>
</Movie>
<Movie>
<Id>2</Id>
<Name>Pulp Fiction</Name>
<Director>Quentin Tarantino</Director>
<Year>1995</Year>
<Cost>8.95</Cost>
</Movie>
<Movie>
<Id>3</Id>
<Name>Sharknado</Name>
<Director>Anthony Ferrante</Director>
<Year>2013</Year>
<Cost>5.95</Cost>
</Movie>
</Movies>
I hope this is enough information to try and help me out, and thanks in advance! :)
Root element contains Movie elements:
var movies = from movie in _xdoc.Root.Elements("Movie") // here instead of "Name"
where (double)movie.Element("Cost") < cost
select movie;
Also, XElement supports explicit casting to double. And you can replace the second loop with LINQ query (assume you want to select names of movies):
List<string> list = movies.Select(m => (string)m.Element("Name")).ToList();
In plain English, you seem to be trying to find movies cheaper than 9 (of some arbitrary currency).
You can write this as:
public IReadOnlyCollection<string> SearchByCost(XDocument xdoc, double cost)
{
return xdoc.Root.Elements("Movie")
.Where(movie => (double)movie.Element("Cost") < cost)
.Select(movie => movie.Element("Name").Value)
.ToList();
}

C# XML Diffing algorithm

I have two XML, before and after the user has edited them. I need to check that user have only added new elements but have not deleted or changed old ones.
Can anybody suggest to me a good algorithm to do that comparison?
Ps:
My XML has a very trivial schema, they only represent an object's structure (with nested objects) in a naive way.
There are few allowed tags, <object> tag can only contains <name> tag, <type> tag or a <list> tag.
The <name> and <type> tag can only contain a string; <list> tag instead can contain a <name> tag and a single <object> tags (representing the structure of objects in the list).
The string in the <name> tag can be freely choosen, the string in <type> tag instead can be only "string" , "int" , "float" , "bool" , "date" or "composite".
Here an example :
<object>
<name>Person</name>
<type>composite</type>
<object>
<name>Person_Name</name>
<type>string</type>
</object>
<object>
<name>Person_Surname</name>
<type>string</type>
</object>
<object>
<name>Person_Age</name>
<type>int</type>
</object>
<object>
<name>Person_Weight</name>
<type>float</type>
</object>
<object>
<name>Person_Address</name>
<type>string</type>
</object>
<object>
<name>Person_BirthDate</name>
<type>date</type>
</object>
<list>
<name>Person_PhoneNumbers</name>
<object>
<name>Person_PhoneNumber</name>
<type>composite</type>
<object>
<name>Person_PhoneNumber_ProfileName</name>
<type>string</type>
</object>
<object>
<name>Person_PhoneNumber_CellNumber</name>
<type>string</type>
</object>
<object>
<name>Person_PhoneNumber_HomeNumber</name>
<type>string</type>
</object>
<object>
<name>Person_PhoneNumber_FaxNumber</name>
<type>string</type>
</object>
<object>
<name>Person_PhoneNumber_Mail</name>
<type>string</type>
</object>
<object>
<name>Person_PhoneNumber_Social</name>
<type>string</type>
</object>
<object>
<name>Person_PhoneNumber_IsActive</name>
<type>bool</type>
</object>
</object>
</list>
</object>
You said:
I need to check that user have only added new elements
but have not deleted or changed old ones.
Can you be more precise about what you mean?
For example, if I insert a new "object" element somewhere, I've changed every element it's inside of, right? As many lists and other objects as contain it. In fact, any insertion at all is a change to the root element.
So, presumably you want to not count changes that change nothing but the root element. How about adding a new item to the list you show? Do you want the list to count as changed? Or what if the objects in the list, or the list itself, are moved to new places without having their content changed at all?
Each of those possibilities is pretty easy to write, but one has to decide what counts as a change first.
If, for example, you only care about bottom-level objects, and "the same" means precisely the same text content (no attributes, white-space variations, etc. etc.), then the easiest way is to load the "before" file into a list of (name,type) pairs; then load the "after" file into a similar but separate list. Sort both lists, then run down them simultaneously and report anything in the new one that's not in the old one (you'll probably want to report any deletions too, just in case).
I need to check that user have only added new elements but have not deleted or changed old ones.
You can represent your 2 XML files as objects. Traverse the nodes, get child the element count for each node and check if its child nodes exists on the other file. For comparing 2 complex objects, you can use the IEquatable.Equals() interface method. Read it here.
The code below doesn't care about the structure of your XML document or on which position a particular element exists since each element is represented as an XElement object. All it knows is 1.) the name of the element, 2.) that each element has children or not, 3.) has attributes or not, 4.) has innerxml or not, etc. If you want to be strict about the structure of your XML, you can represent each level as a single class.
public class Program
{
static void Main(string[] args)
{
XDocument xdoc1 = XDocument.Load("file1.xml");
XDocument xdoc2 = XDocument.Load("file2.xml");
RootElement file1 = new RootElement(xdoc1.Elements().First());
RootElement file2 = new RootElement(xdoc2.Elements().First());
bool isEqual = file1.Equals(file2);
Console.ReadLine();
}
}
public abstract class ElementBase<T>
{
public string Name;
public List<T> ChildElements;
public ElementBase(XElement xElement)
{
}
}
public class RootElement : ElementBase<ChildElement>, IEquatable<RootElement>
{
public RootElement(XElement xElement)
: base(xElement)
{
ChildElements = new List<ChildElement>();
Name = xElement.Name.ToString();
foreach (XElement e in xElement.Elements())
{
ChildElements.Add(new ChildElement(e));
}
}
public bool Equals(RootElement other)
{
bool flag = true;
if (this.ChildElements.Count != other.ChildElements.Count())
{
//--Your error handling logic here
flag = false;
}
List<ChildElement> otherChildElements = other.ChildElements;
foreach (ChildElement c in this.ChildElements)
{
ChildElement otherElement = otherChildElements.FirstOrDefault(x => x.Name == c.Name);
if (otherElement == null)
{
//--Your error handling logic here
flag = false;
}
else
{
flag = c.Equals(otherElement) == false ? false : flag;
}
}
return flag;
}
}
public class ChildElement : ElementBase<ChildElement>, IEquatable<ChildElement>
{
public ChildElement(XElement xElement)
: base(xElement)
{
ChildElements = new List<ChildElement>();
Name = xElement.Name.ToString();
foreach (XElement e in xElement.Elements())
{
ChildElements.Add(new ChildElement(e));
}
}
public bool Equals(ChildElement other)
{
bool flag = true;
if (this.ChildElements.Count != other.ChildElements.Count())
{
//--Your error handling logic here
flag = false;
}
List<ChildElement> otherList = other.ChildElements;
foreach (ChildElement e in this.ChildElements)
{
ChildElement otherElement = otherList.FirstOrDefault(x => x.Name == e.Name);
if (otherElement == null)
{
//--Your error handling logic here
flag = false;
}
else
{
flag = e.Equals(otherElement) == false ? false : flag;
}
}
return flag;
}
}
If you also want to check for attributes or innerxml, you can do like so.
public List<XAttribute> ElementAttributes = new List<XAttribute>();
foreach (XAttribute attr in xElement.Attributes())
{
ElementAttributes.Add(attr);
}
List<XAttribute> otherAttributes = other.ElementAttributes;
foreach (XAttribute attr in ElementAttributes)
{
XAttribute otherAttribute = otherAttributes.FirstOrDefault(x => x.Name == attr.Name);
if (otherAttribute == null)
{
//--Your error handling logic here
flag = false;
}
else
{
if (otherAttribute.Value != attr.Value)
{
//--Your error handling logic here
flag = false;
}
}
}

Linq to XML query analyz

This query does work but I am not sure it is proper way to write this kind of query. I feel it is using too many Descendants and Parent.
Is there a better way to write this query?
There can be more than one catalog in XML.
static IEnumerable<Parts> GetAllParts(XDocument doc, string catalog, string groupId, string subGroupId)
{
var parts = (from p in doc.Descendants("ROOT").Descendants("CATALOG").Descendants("GROUP").Descendants("SUBGROUP").Descendants("BOM").Descendants("PARTS")
where (string)p.Parent.Parent.Parent.Parent.Element("IDENT").Value == catalog
&& p.Parent.Parent.Parent.Element("IDENT").Value == groupId
&& p.Parent.Parent.Element("IDENT").Value == subGroupId
select new Parts
{
ObjectId = int.Parse(p.Attribute("OBJECTID").Value),
Ident = p.Element("IDENT").Value,
List = p.Element("LIST").Value,
Descr = p.Element("DESC").Value
});
return parts;
}
}
public class Parts
{
public int ObjectId { get; set;}
public string Descr { get; set; }
public string Ident { get; set; }
public string List { get; set; }
}
Update: XML added.
<ROOT>
<CATALOG>
<OBJECT_ID>001</OBJECT_ID>
<OBJECT_IDENT>X001</OBJECT_IDENT>
<GROUP>
<OBJECT_ID>1001</OBJECT_ID>
<OBJECT_IDENT>01</OBJECT_IDENT>
<NAME>HOUSING</NAME>
<SUBGROUP>
<OBJECT_ID>5001</OBJECT_ID>
<OBJECT_IDENT>01.05</OBJECT_IDENT>
<NAME>DESIGN GROUP 1</NAME>
<BOM>
<OBJECT_ID>6001</OBJECT_ID>
<OBJECT_IDENT>010471</OBJECT_IDENT>
<PARTS>
<OBJECT_ID>2316673</OBJECT_ID>
<OBJECT_IDENT>A002010660</OBJECT_IDENT>
<DESC>SHORT BLOCK</DESC>
<NOTES>
<ROW>
<NOTES>Note 1</NOTES>
<BOM>010471</BOM>
<POS>1</POS>
</ROW>
<ROW>
<NOTES>Note 2</NOTES>
<BOM>010471</BOM>
<POS>2</POS>
</ROW>
</NOTES>
</PARTS>
<PARTS>
</PARTS>
<PARTS>
</PARTS>
</BOM>
</SUBGROUP>
<SUBGROUP>
</SUBGROUP>
<SUBGROUP>
</SUBGROUP>
</GROUP>
<GROUP>
</GROUP>
</CATALOG>
</ROOT>
Some suggestions regarding your question (I hope you'll improve your future posts) :
Your code and the XML posted doesn't work. For instance, the XML has <OBJECT_IDENT> element, but your LINQ has IDENT. Please craft it carefully and make sure it does work to avoid confusion.
Please put some effort in explaining the problem and giving clarification. "I am retrieving Parts data as function name says" is not clear enough as simply getting <Parts> elements doesn't need filtering but your LINQ has where .... clause.
This question seems to suits better in https://codereview.stackexchange.com/
And here is some suggestions regarding your code :
Use .Elements() to get direct child of current node as opposed to Descendants() which get all descendat nodes.
You can use Elements().Where() to filter the element so you can avoid traversing Parents
You can cast XElement to string/int to avoid exception in case such element not found
Example code snippet :
var parts = (from p in doc.Root
.Elements("CATALOG").Where(o => catalog == (string)o.Element("OBJECT_IDENT"))
.Elements("GROUP").Where(o => groupId == (string)o.Element("OBJECT_IDENT"))
.Elements("SUBGROUP").Where(o => subGroupId == (string)o.Element("OBJECT_IDENT"))
.Elements("BOM")
.Elements("PARTS")
select new Parts
{
ObjectId = (int)p.Element("OBJECT_ID"),
Ident = (string)p.Element("OBJECT_IDENT"),
List = (int)p.Element("LIST"),
Descr = (string)p.Element("DESC")
});

C# linq-to-xml, Getting a list with nodes?

This is the test xml that i am using:
<categories>
<category id="1" name="Test1">
<category id="2" name="Test2">
<misc id="1"></misc>
</category>
</category>
<category id="3" name="Test3">
<misc id="2"></misc>
</category>
Now i want to bind that to an ASPX treeview, i want only the elements that have the name category and i want the name of those to appear in the treeview.
Its easy to get the id and names:
var d = from t in data.Descendants("category")
select new { ID = t.Attribute("id").Value, Name = t.Attribute("name").Value };
but how do i keep the structure in the treeview?
This should look like this:
Test1
-> Test2
Test3
Maybe something like this if I understand you correctly? (I have not tested it)
class Category
{
public string ID { get; set; }
public string Name { get; set; }
public IEnumerable<Category> SubCategories { get; set; }
}
IEnumerable<Category> CategoryTreeStructure(XElement e)
{
var d = from t in e.Elements("category")
select new Category()
{
ID = t.Attribute("id").Value,
Name = t.Attribute("name").Value,
SubCategories = CategoryTreeStructure(t)
};
return d;
}
Call it with:
var structure = CategoryTreeStructure(doc.Root);
"i want only the elements that have the name category" - I do not understand what you mean here? But if you only want to select those elements which have a "name" attribute then this should work:
...
var d = from t in e.Elements("category")
where t.Attribute("name") != null
select new Category()
...
I understand that the upper (the "name" attribute part) is not what you wanted but I leave it there. I have tested the code against:
XDocument doc = XDocument.Parse(#"<categories>
<category id=""1"" name=""Test1"">
<category id=""2"" name=""Test2"">
<misc id=""1""></misc>
</category>
</category>
<category id=""3"" name=""Test3"">
<misc id=""2""></misc>
</category>
</categories>");
var structure = CategoryTreeStructure(doc.Root);
Actually, I have found this link which does exactly what you are asking for :) And it is without LINQ, so I thought it deserved another answer.
http://www.15seconds.com/issue/041117.htm

Categories

Resources