Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read a XML tree structure recursively in a List<T> with children lists<T>

I have an XML like this:

And I have a Member class with property Name.

How can I read every Unit and its children Units into multiple generic List<Unit> which can have again children List<Unit> in a recursively way using latest .NET technology ?

<Root>
  <Units Name="Test1">
    <Unit Name="Test11" />
    <Unit Name="Test12">
      <Unit Name="Test21" />
      <Unit Name="Test22" />
      <Unit Name="Test23">
        <Unit Name="Test31" />
        <Unit Name="Test32" />  
        <Unit Name="Test33" />
      </Unit>
      <Unit Name="Test24" />
    </Unit>
  </Units>
  <Units Name="Test2" />
    <!-- ... -->
  <Units Name="Test3" />
    <!-- ... -->
  <Units Name="Test4" />
</Root>
like image 643
msfanboy Avatar asked May 28 '11 20:05

msfanboy


4 Answers

This would do it, using plain recursion:

public class Unit
{
    public string Name { get; set; }
    public List<Unit> Children { get; set; }
}

class Program
{
    public static void Main()
    {
        XDocument doc = XDocument.Load("test.xml");
        List<Unit> units = LoadUnits(doc.Descendants("Units").Elements("Unit"));
    }

    public static List<Unit> LoadUnits(IEnumerable<XElement> units)
    {
        return units.Select( x=> new Unit() 
                                 { Name = x.Attribute("Name").Value, 
                                   Children = LoadUnits(x.Elements("Unit")) 
                                 }).ToList();
    }
}
like image 136
BrokenGlass Avatar answered Nov 03 '22 05:11

BrokenGlass


The challenge would be to write it as 1 LINQ query, but that's beyond me. LINQ isn't easy/suitable for recursion.

I'll sketch a solution, I'm not going to write it out :

  • read the Xml into an XDocument (or XmlDocument)
  • define a class Unit { ... ; ... List<Unit> Children; }
  • define Units and Root classes if desired. I'll flatten that part here
  • get a flat list of all Unit tags, var units = doc.Descendants("Unit");
  • Iterate over those elements, I assume that a parent node will always come before a nested Unit
  • look up the Parent of each node in a var Lookup = new Dictionary<XNode, Unit> ();
  • if a Parent is found, add the current node (new Unit) to its Children
  • else add it to a topList
  • add the new Unit and the XElement to the dictionary.
  • the lookup dictionary is only needed while creating the lists.
like image 38
Henk Holterman Avatar answered Nov 03 '22 05:11

Henk Holterman


class Unit
{
    public string Name;
    public List<Unit> Children;

    public Unit(string name, List<Unit> children)
    {
        Name = name;
        Children = children;
    }

    public static Unit Convert(XElement elem)
    {
        return new Unit(elem.Attribute("Name").Value, Convert(elem.Elements()));
    }

    public static List<Unit> Convert(IEnumerable<XElement> elems)
    {
        return elems.Select(Unit.Convert).ToList();
    }
}

You can use it like this:

Unit.Convert(XDocument.Parse(xml).Root.Elements())
like image 37
svick Avatar answered Nov 03 '22 06:11

svick


Why don't you implement a Tree for storing units. That would be much easier and natural than Lists.

Have a look at this comment for a good implementation using LinkedList.

See

  • MSDN: Algorithms and Data Structures
  • Tree data structure in C#
  • Tree: Implementing a Non-Binary Tree in C#

If you HAVE to use List, then you may use recursion to build it. I'm assuming your Unit has a property (IList Unit.ChildUnits) to hold all children List. If not you may want to wrap Unit into another class that has this.

public List<Unit> LoadAllUnits(XMLNode rootNode){
    List<Unit> allUnits = new List<Unit>();
    foreach(var childNode in rootNode.ChildNodes){
        allUnits.Add(LoadAllSubUnits(childNode);
    }
    return allUnits;
}


private Unit LoadAllSubUnits(XMLNode node){
    Unit u = GetUnitFromCurrentNode(node); // Converts current node into Unit object
    if(root.HasChildNode){
         foreach(var childNode in node.ChildNodes){
             u.ChildUnits.Add(LoadAllSubUnits(childNode);
         }
    }
    return u;
}
like image 1
YetAnotherUser Avatar answered Nov 03 '22 04:11

YetAnotherUser