2010-04-01 2 views
4

j'ai un document XML comme ceci:XML: comment supprimer tous les nœuds qui ont aucun attribut ni élément enfant

<Node1 attrib1="abc"> 
    <node1_1> 
     <node1_1_1 attrib2 = "xyz" /> 
    </ node1_1> 
</Node1> 

<Node2 />  

Ici <node2 /> est le nœud que je veux supprimer, car il n'a pas les enfants/éléments ni aucun les attributs.

+1

Si le noeud 1_1_1 est supprimé, node1_1 doit-il également être supprimé car il n'aura alors aucun enfant/élément ni aucun attribut? –

+0

Bonne question. Mon tort ici ,,, Non, seulement doit être retiré. En fait, le scénario réel est quelque chose comme: Alors est celui qui doit être retiré. J'ai mis à jour la question. – mishal153

Répondre

4

En utilisant une expression XPath il est possible de trouver tous les nœuds qui ont pas d'attributs ou les enfants. Ceux-ci peuvent ensuite être supprimés du fichier XML. Comme Sani le souligne, vous devrez peut-être procéder de manière récursive car node_1_1 devient vide si vous supprimez son noeud interne.

var xmlDocument = new XmlDocument(); 
xmlDocument.LoadXml(
@"<Node1 attrib1=""abc""> 
     <node1_1> 
      <node1_1_1 /> 
     </node1_1> 
    </Node1> 
    "); 

// select all nodes without attributes and without children 
var nodes = xmlDocument.SelectNodes("//*[count(@*) = 0 and count(child::*) = 0]"); 

Console.WriteLine("Found {0} empty nodes", nodes.Count); 

// now remove matched nodes from their parent 
foreach(XmlNode node in nodes) 
    node.ParentNode.RemoveChild(node); 

Console.WriteLine(xmlDocument.OuterXml); 
Console.ReadLine(); 
+0

Merci, cela fonctionne bien pour moi :) – mishal153

+1

Je veux juste ajouter une chose de plus.Je me rends compte que j'ai aussi besoin de couvrir la situation où un nœud est comme bonjour. Ici le nœud n'a pas d'enfant et pas d'attributs mais il a du texte, et donc je ne veux pas qu'il soit filtré et supprimé. Donc la bonne solution pour moi était: XmlNodeList list = document.SelectNodes ("// * [count (@ *) = 0 et compte (enfant :: *) = 0 et pas (text())]"); – mishal153

+3

Vous pouvez simplifier cette expression XPATH en utilisant 'node()' pour combiner les tests de '*' et 'text()' et en utilisant une union '|' pour fusionner les tests des attributs et des nœuds pour les critères du compte: '//* [count (enfant :: node() | @ *) = 0] ' –

1

Smething comme cela devrait le faire:

XmlNodeList nodes = xmlDocument.GetElementsByTagName("Node1"); 

foreach(XmlNode node in nodes) 
{ 
    if(node.ChildNodes.Count == 0) 
     node.RemoveAll; 
    else 
    { 
     foreach (XmlNode n in node) 
     { 
      if(n.InnerText==String.Empty && n.Attributes.Count == 0) 
      { 
       n.RemoveAll; 

      } 
     } 
    } 
} 
+0

Les noms de nœuds que j'ai mentionnés sont juste pour expliquer ce que je veux. Ils ne sont pas les vrais noms de nœuds. Je veux faire quelque chose de générique. Je crois que XPath sera utile ici, mais je ne sais pas comment utiliser XPath. Je lis à ce sujet :). Merci pour la réponse si. – mishal153

0

Cette feuille de style utilise une transformation d'identité avec un des éléments correspondants de modèle vides sans noeuds ou les attributs qui les empêchera d'être copiés sur la sortie:

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> 

    <!--Identity transform copies all items by default --> 
    <xsl:template match="@* | node()"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()"/> 
     </xsl:copy> 
    </xsl:template> 

    <!--Empty template to match on elements without attributes or child nodes to prevent it from being copied to output --> 
    <xsl:template match="*[not(child::node() | @*)]"/> 

</xsl:stylesheet> 
0

Pour le faire pour tous les nœuds enfants vides , utilisez une boucle for (pas foreach) et dans l'ordre inverse. Je résolus comme:

var xmlDocument = new XmlDocument(); 
xmlDocument.LoadXml(@"<node1 attrib1=""abc""> 
         <node1_1> 
          <node1_1_1 /> 
         </node1_1> 
         <node1_2 /> 
         <node1_3 /> 
         </node1> 
         <node2 /> 
"); 
RemoveEmptyNodes(xmlDocument); 

private static bool RemoveEmptyNodes(XmlNode node) 
{ 
    if (node.HasChildNodes) 
    { 
     for(int I = node.ChildNodes.Count-1;I >= 0;I--) 
      if (RemoveEmptyNodes(node.ChildNodes[I])) 
       node.RemoveChild(node.ChildNodes[I]); 
    } 
    return 
     (node.Attributes == null || 
      node.Attributes.Count == 0) && 
     node.InnerText.Trim() == string.Empty; 
} 

Les appels récursifs (de façon similaire à d'autres solutions) éliminer le traitement des documents en double de l'approche XPath. Plus important encore, le code est plus lisible et plus facilement modifiable. Win-Win. Ainsi, cette solution supprimera <node2>, mais supprimera également correctement <node1_2> et <node1_3>.

Mise à jour: Nous avons constaté une augmentation notable des performances en utilisant l'implémentation Linq suivante.

string myXml = @"<node1 attrib1=""abc""> 
         <node1_1> 
          <node1_1_1 /> 
         </node1_1> 
         <node1_2 /> 
         <node1_3 /> 
         </node1> 
         <node2 /> 
"); 
XElement xElem = XElement.Parse(myXml); 
RemoveEmptyNodes2(xElem); 

private static void RemoveEmptyNodes2(XElement elem) 
{ 
    int cntElems = elem.Descendants().Count(); 
    int cntPrev; 
    do 
    { 
     cntPrev = cntElems; 
     elem.Descendants() 
      .Where(e => 
       string.IsNullOrEmpty(e.Value.Trim()) && 
       !e.HasAttributes).Remove(); 
     cntElems = elem.Descendants().Count(); 
    } while (cntPrev != cntElems); 
} 

La boucle gère les cas où un parent doit être supprimé car son seul enfant a été supprimé. L'utilisation de XContainer ou de produits dérivés a tendance à avoir des performances similaires en raison des implémentations IEnumerable en coulisses. C'est ma nouvelle chose préférée.

Sur un fichier xml arbitraire de 68 Mo, le RemoveEmptyNodes tend à prendre environ 90sec, alors que RemoveEmptyNodes2 prend environ 1sec.

Questions connexes