Рекурсивное чтение структуры дерева XML в Списке <T>с дочерними списками <T> - PullRequest
9 голосов
/ 29 мая 2011

У меня есть такой XML-код:

И у меня есть класс Member со свойством Name.

Как я могу прочитать каждый юнит и его дочерние юниты в несколько общих List<Unit>, которые могут рекурсивно иметь потомков List<Unit> с использованием новейшей технологии .NET?

<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>

Ответы [ 4 ]

15 голосов
/ 29 мая 2011

Это будет сделано с использованием простой рекурсии:

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();
    }
}
1 голос
/ 29 мая 2011

Задачей было бы написать его как 1 запрос LINQ, но это вне меня. LINQ не прост / подходит для рекурсии.

Я нарисую решение, я не собираюсь его выписывать:

  • читать Xml в XDocument (или XmlDocument)
  • определить class Unit { ... ; ... List<Unit> Children; }
  • определить Units и корневые классы, если это необходимо. Я сплющу эту часть здесь
  • получить список всех тегов юнитов, var units = doc.Descendants("Unit");
  • Перебирая эти элементы, я предполагаю, что родительский узел всегда будет стоять перед вложенным модулем
  • поиск Родителя каждого узла в var Lookup = new Dictionary<XNode, Unit> ();
  • если родитель найден, добавьте текущий узел (новый юнит) к его потомкам
  • еще добавить его в топлист
  • добавить новый модуль и XElement в словарь.
  • словарь поиска нужен только при создании списков.
1 голос
/ 29 мая 2011
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();
    }
}

Вы можете использовать это так:

Unit.Convert(XDocument.Parse(xml).Root.Elements())
1 голос
/ 29 мая 2011

Почему бы вам не реализовать Дерево для хранения юнитов. Это было бы намного проще и естественнее, чем списки.

Посмотрите на этот комментарий для хорошей реализации с использованием LinkedList.

См.

Если у вас ЕСТЬ для использования List, вы можете использовать рекурсию для его построения. Я предполагаю, что у вашего подразделения есть свойство (IList Unit.ChildUnits) для хранения всех дочерних списков. Если нет, вы можете заключить Unit в другой класс, который имеет это.

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;
}
...