9

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>
bobbymcr
  • 23,769
  • 3
  • 56
  • 67
msfanboy
  • 5,273
  • 13
  • 69
  • 120
  • Would have thought this was as straight forward an implementation of recursion as you could get... – cristobalito May 28 '11 at 21:03
  • @christoba but the code for traversing an XDoc/XmlDoc is kind of unwieldy. It is possible though. I suggest replacing it with a Dictionary in my answer. – H H May 28 '11 at 21:14

4 Answers4

15

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();
    }
}
BrokenGlass
  • 158,293
  • 28
  • 286
  • 335
1

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

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;
}
Community
  • 1
  • 1
YetAnotherUser
  • 9,156
  • 3
  • 39
  • 53
1

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.
H H
  • 263,252
  • 30
  • 330
  • 514
1
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())
svick
  • 236,525
  • 50
  • 385
  • 514