2010-09-07 3 views
4

J'ai un menu XML "plat" que j'ai besoin de structurer.Génération d'arborescence XML

arbre XML actuel:

<root> 
    <nodes> 
     <node> 
      <id>5</id> 
      <parent>1</parent> 
     </node> 
     <node> 
      <id>8</id> 
      <parent>5</parent> 
     </node> 
     <node> 
      <id>14</id> 
      <parent>8</parent> 
     </node> 
     <node> 
      <id>26</id> 
      <parent>1</parent> 
     </node> 
    </nodes>  
</root> 

Cet arbre XML doivent être reodered d'avoir des relations correctes entre ID: s et ParentID: S

<root> 
    <nodes> 
     <node> 
      <id>5</id> 
      <parent>1</parent> 
      <node> 
       <id>8</id> 
       <parent>5</parent> 
       <node> 
        <id>14</id> 
        <parent>8</parent> 
       </node> 
      </node> 
     </node>    
     <node> 
      <id>26</id> 
      <parent>1</parent> 
     </node> 
    </nodes>  
</root> 

Iv obtenu le code suivant pour essayer d'accomplir cela:

public XmlDocument SortXmlNodeTree(XmlDocument udoc) 
{ 
    XmlDocument sortedDoc = new XmlDocument(); 
    sortedDoc.LoadXml(xmlStartString); 

    //select top nodes 
    //top node -> find all siblings. For each sibling add sibling.siblings. ->loop    
    XmlNode nodes = udoc.DocumentElement.LastChild; 
    foreach(XmlNode n in nodes) 
    { 
     //get top nodes and check if they are folders 
     if (n["parent"].InnerText.Equals("1") && n["type"].InnerText.Equals("2")) 
     { 
      XmlNode newNode = sortedDoc.ImportNode(n, true);       
      GetNodeSiblings(ref nodes, newNode, ref sortedDoc);  
      SortedDoc.DocumentElement.FirstChild.AppendChild(newNode);      
     } 
    } 
    return sortedDoc; 
} 

public XmlNode GetNodeSiblings(ref XmlNode nodes, XmlNode currentNode, ref XmlDocument tree) 
{ 
    if (!nodes.HasChildNodes) 
    { 
     return null; 
    } 

    foreach (XmlNode n in nodes) 
    { 
     // if we have a folder and parent is currentNode, go deeper 
     if (n["type"].InnerText.Equals("2") && n["parent"].InnerText.Equals(currentNode["id"].InnerText)) 
     { 
      XmlNode newNode = tree.ImportNode(n, true);      
      GetNodeSiblings(ref nodes, newNode, ref tree); 
      currentNode.AppendChild(newNode); 
     } 
     // if we have a product that has currentNode as parent, add it. 
     else if (!n["type"].InnerText.Equals("2") && n["parent"].InnerText.Equals(currentNode["id"].InnerText)) 
     { 
      XmlNode newNode = tree.ImportNode(n, true); 
      nodes.RemoveChild(n); 
      currentNode.AppendChild(newNode); 
     } 
    } 
    return null; 
} 

Comme vous pouvez le voir, mes nœuds contiennent également "type" et "name". Les types sont utilisés pour déterminer si un noeud est un "dossier" ou un "produit".

Mon problème est que ceci ne renvoient pas le bon XML. Si je supprime les nodes.RemoveChild (n) dans la dernière section alors ça marche super bien mais je veux enlever les enfants (products, type = 1) que je connais n'ont pas d'enfants.

Si ce code est exécuté. Je n'ai que quelques nœuds.

Répondre

2

ce code fait le travail. Espérons que c'est assez

public class Node 
{ 
    public Node() 
    { 
     Children = new List<Node>(); 
    } 

    public int Id; 

    public int ParentId; 

    public List<Node> Children; 

    public Node Parent; 

    public static Node Deserialize(XmlElement xNode) 
    { 
     Node n = new Node(); 
     XmlElement xId = xNode.SelectSingleNode("id") as XmlElement; 
     n.Id = int.Parse(xId.InnerText); 
     XmlElement xParent = xNode.SelectSingleNode("parent") as XmlElement; 
     n.ParentId = int.Parse(xParent.InnerText); 
     return n; 
    } 

    public void AddChild(Node child) 
    { 
     Children.Add(child); 
     child.Parent = this; 
    } 

    public void Serialize(XmlElement xParent) 
    { 
     XmlElement xNode = xParent.OwnerDocument.CreateElement("node"); 
     XmlElement xId = xParent.OwnerDocument.CreateElement("id"); 
     xId.InnerText = Id.ToString(); 
     xNode.AppendChild(xId); 
     XmlElement xParentId = xParent.OwnerDocument.CreateElement("parent"); 
     xParentId.InnerText = ParentId.ToString(); 
     xNode.AppendChild(xParentId); 
     foreach (Node child in Children) 
      child.Serialize(xNode); 
     xParent.AppendChild(xNode); 
    } 
} 

public static XmlDocument DeserializeAndReserialize(XmlDocument flatDoc) 
{ 
    Dictionary<int, Node> nodes = new Dictionary<int, Node>(); 
    foreach (XmlElement x in flatDoc.SelectNodes("//node")) 
    { 
     Node n = Node.Deserialize(x); 
     nodes[n.Id] = n; 
    } 

    // at the end, retrieve parents for each node 
    foreach (Node n in nodes.Values) 
    { 
     Node parent; 
     if (nodes.TryGetValue(n.ParentId, out parent)) 
     { 
      parent.AddChild(n); 
     } 
    } 

    XmlDocument orderedDoc = new XmlDocument(); 
    XmlElement root = orderedDoc.CreateElement("root"); 
    orderedDoc.AppendChild(root); 
    XmlElement xnodes = orderedDoc.CreateElement("nodes"); 
    foreach (Node n in nodes.Values) 
    { 
     if (n.Parent == null) 
      n.Serialize(xnodes); 
    } 
    root.AppendChild(xnodes); 
    return orderedDoc; 
} 
+0

qui l'a fait! Merci, ravi de voir comment les pros s'en sortent! – Marthin

+0

Je voulais être rapide et utiliser le code que je connais par cœur. Avec plus de temps j'aurais essayé de jouer avec System.Xml.Linq. Il est beaucoup plus facile de manipuler Xml avec cet assemblage – PierrOz

4

Je prendrais une approche différente du problème. Vous essayez maintenant de modifier un document existant en déplaçant des nœuds autour desquels il est plutôt complexe. Je voudrais analyser le document original, le stocker dans une structure de données et l'écrire à nouveau à un autre endroit.

Votre structure de données ressemblerait à quelque chose comme ceci:

public class Node 
{ 
    public SomeClass NodeData { get ; set; } 
    public List<Node> Children { get; } 
} 

SomeClass est un objet typé qui contient les données pertinentes pour un seul nœud. Et puis votre code devrait ressembler à ceci:

Node rootNode = ParseXml(...); 
WriteStructuredXml(rootNode); 

Ces deux méthodes ne sont pas difficiles à écrire. De cette façon, vous divisez le problème en deux problèmes plus petits et plus faciles.

+0

Vous avez raison, cela devrait être traité de la manière que vous proposez. Merci pour le conseil! – Marthin

0

clair est ici un code qui obtient tous les noeuds avec le nom « nœud »:

public static IEnumerable<XmlNode> GetNodes(XmlDocument xdoc) 
    { 
     var nodes = new List<XmlNode>(); 

     Queue<XmlNode> toProcess = new Queue<XmlNode>(); 

     if (xdoc != null) 
     { 
      foreach (var node in GetChildElements(xdoc)) 
      { 
       toProcess.Enqueue(node); 
      } 
     } 

     do 
     { 
      //get a node to process 
      var node = toProcess.Dequeue(); 

      // add node to found list if name matches 
      if (node.Name == "node") 
      { 
       nodes.Add(node); 
      } 

      // get the node's children 
      var children = GetChildElements(node); 

      // add children to queue. 
      foreach (var n in children) 
      { 
       toProcess.Enqueue(n); 
      } 

      // continue while queue contains items. 
     } while (toProcess.Count > 0); 


     return nodes; 
    } 

    private static IEnumerable<XmlNode> GetChildElements(XmlNode node) 
    { 
     if (node == null || node.ChildNodes == null) return new List<XmlNode>(); 

     return node.ChildNodes.Cast<XmlNode>().Where(n=>n.NodeType == XmlNodeType.Element); 
    } 

Ensuite, vous aurez besoin de déplacer les nœuds autour en fonction de rel parent-enfant » navires. Voir la réponse de @ PierrOz.

Questions connexes