2013-02-19 8 views
5

Ich habe eine Frage zum Entfernen bestimmter Knoten aus der XML-Datei.Elterknoten ohne childs-Knoten entfernen

Hier ist meine Probe von XML:

<?xml version="1.0" encoding="UTF-8"?> 
<root> 
    <nodeA attribute="1"> 
    <nodeB attribute="table"> 
     <nodeC attribute="500"></nodeC> 
     <nodeC attribute="5"></nodeC> 
    </nodeB> 
    <nodeB attribute="3"> 
     <nodeC attribute="4"></nodeC> 
     <nodeC attribute="5"></nodeC> 
     <nodeC attribute="5"></nodeC> 
    </nodeB> 
    <nodeB attribute="placeHolder"> 
    <nodeB attribute="toRemove"> 
     <nodeB attribute="glass"></nodeB> 
     <nodeE attribute="7"></nodeE> 
     <nodeB attribute="glass"></nodeB> 
     <nodeB attribute="glass"></nodeB> 
    </nodeB> 
    </nodeB> 
    <nodeB attribute="3"> 
     <nodeC attribute="4"></nodeC> 
     <nodeC attribute="5"></nodeC> 
     <nodeC attribtue="5"></nodeC> 
    </nodeB> 
    <nodeB attribute="placeHolder"> 
    <nodeB attribute="toRemove"> 
     <nodeB attribute="glass"></nodeB> 
     <nodeE attribute="7"></nodeE> 
     <nodeB attribute="glass"></nodeB> 
     <nodeB attribute="glass"></nodeB> 
    </nodeB> 
    </nodeB> 
    </nodeA> 
</root> 

Ich mag würde Knoten entfernen nodeB="toRemove" ohne Kinder dieses Knotens zu entfernen. Danach muss ich dasselbe mit nodeB attribute="placeHolder" machen. Ein Teil Ergebnis würde so aussehen:

 <nodeB attribute="3"> 
     <nodeC attribute="4"></nodeC> 
     <nodeC attribute="5"></nodeC> 
     <nodeC attribtue="5"></nodeC> 
    </nodeB> 
    <nodeB attribute="glass"></nodeB> 
     <nodeE attribute="7"></nodeE> 
    <nodeB attribute="glass"></nodeB> 
    <nodeB attribute="glass"></nodeB> 

Ich habe wie dieser Code versucht, das achive:

 XmlNodeList nodeList = doc.SelectNodes("//nodeB[@attribute=\"toRemove\"]"); 

     foreach (XmlNode node in nodeList) 
     { 
      foreach (XmlNode child in node.ChildNodes) 
      { 
       node.ParentNode.AppendChild(child); 
      } 
      node.ParentNode.RemoveChild(node); 
     } 
     doc.Save(XmlFilePathSource); 

Ich bin in der Lage Knoten mit dem gewünschten Attribut toremove oder Platzhalter zu finden, aber ich bin nicht in der Lage, Kinder dieser Knoten um eine Ebene nach oben zu verschieben. Könnten Sie mir in diesem Fall helfen? Es kann eine Lösung mit Linq, XDocument, XmlReader sein, aber ich bevorzuge die Arbeit mit XmlDocument. Vielen Dank für jede Hilfe, die Sie mir im Voraus geben könnten.

EDIT:

In diesem Fall habe ich etwas modifizierten Code verwendet wird (um zu erhalten), die Chuck Savage unten geschrieben. Sobald

<nodeB attribute="toRemove"> </nodeB> 

zu entfernen und dann das Gleiche tun mit

<nodeB attribute="placeHolder"></nodeB> 

Hier etwas Code geändert wird

XElement root = XElement.Load(XmlFilePathSource); 
    var removes = root.XPathSelectElements("//nodeB[@attribute=\"toRemove\"]"); 
    foreach (XElement node in removes.ToArray()) 
    { 
    node.Parent.AddAfterSelf(node.Elements()); 
    node.Remove(); 
    } 
    root.Save(XmlFilePathSource); 

Xslt Ansatz von @MiMo vorgesehen ist sehr nützlich, wie auch in diesem Fall.

+0

Viele Ihrer 'nodeC' Elemente fehlen ihre schließenden Tags. Können Sie Ihre Frage mit einer gültigen, wohlgeformten XML-Datei aktualisieren? –

+0

Ich habe meine vereinfachte XML-Datei aktualisiert. Danke für den Hinweis, es ist jetzt einfacher für andere zu lesen. – wariacik

Antwort

3

Mit Linq-to-XML und XPath,

XElement root = XElement.Load(XmlFilePathSource); // or .Parse(string) 
var removes = root.XPathSelectElements("//nodeB[@attribute=\"toRemove\"]"); 
foreach (XElement node in removes.ToArray()) 
{ 
    node.AddBeforeSelf(node.Elements()); 
    node.Remove(); 
} 
root.Save(XmlFilePathSource); 

Hinweis: XPath in System.Xml.XPath

Note2 verfügbar ist: Sie konvertieren können/from XmlDocument mit these extensions, da Sie XmlDocument bevorzugen.

+0

Ein Nachteil hier ist, dass die beibehaltenen Kinder am Ende des enthaltenden Knotens hinzugefügt werden, anstatt in dem Teil des Dokuments, wo die sind. Der Fragesteller hat nicht gesagt, dass die Erhaltung seiner Position eine Voraussetzung ist, aber es könnte leicht sein. – JLRishe

+0

@JLRishe Wenn man sich OPs Code ansieht, macht er im Grunde dasselbe, aber ich mag deinen Standpunkt. –

+0

Ich mag diesen Ansatz, aber in diesem Fall ist es wichtig, den Standort der Kindknoten zu erhalten. Gibt es eine Möglichkeit, untergeordnete Knoten in einem Teil des Dokuments zu speichern, in dem sie sich befinden? – wariacik

4

Das Problem ist, dass Sie Dokumentknoten beim Aufzählen auf ihren Kindern nicht ändern können - Sie sollten neue Knoten statt versuchen, die vorhandenen zu ändern, und das wird ein bisschen schwierig mit XmlDocument.

Der einfachste Weg, diese Art von Transformation zu tun XSLT verwendet, dh die Anwendung dieser XSLT:

<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

    <xsl:output method="xml" indent="yes"/> 

    <xsl:template match="nodeB[@attribute='toRemove' or @attribute='placeHolder']"> 
    <xsl:apply-templates/> 
    </xsl:template> 

    <xsl:template match="text()"> 
    </xsl:template> 

    <xsl:template match="@* | *"> 
    <xsl:copy> 
     <xsl:apply-templates select="@* | node()"/> 
    </xsl:copy> 
    </xsl:template> 

</xsl:stylesheet> 

an den Eingang der Ausgabedatei ist:

<root> 
    <nodeA attribute="1"> 
    <nodeB attribute="table"> 
     <nodeC attribute="500" /> 
     <nodeC attribute="5" /> 
    </nodeB> 
    <nodeB attribute="3"> 
     <nodeC attribute="4" /> 
     <nodeC attribute="5" /> 
     <nodeC attribute="5" /> 
    </nodeB> 
    <nodeB attribute="glass" /> 
    <nodeE attribute="7" /> 
    <nodeB attribute="glass" /> 
    <nodeB attribute="glass" /> 
    <nodeB attribute="3"> 
     <nodeC attribute="4" /> 
     <nodeC attribute="5" /> 
     <nodeC attribtue="5" /> 
    </nodeB> 
    <nodeB attribute="glass" /> 
    <nodeE attribute="7" /> 
    <nodeB attribute="glass" /> 
    <nodeB attribute="glass" /> 
    </nodeA> 
</root> 

Der Code des XSLT anwenden ist einfach:

Wenn es nicht möglich (oder wünschenswert) ist, eine externe Datei für das X zu verwenden SLT kann es aus einem String gelesen werden:

string xsltString = 
    @"<xsl:stylesheet 
     version='1.0' 
     xmlns:xsl='http://www.w3.org/1999/XSL/Transform'> 

     <xsl:output method=""xml"" indent=""yes""/> 

     <xsl:template match=""nodeB[@attribute='toRemove' or @attribute='placeHolder']""> 
     <xsl:apply-templates/> 
     </xsl:template> 

     <xsl:template match=""text()""> 
     </xsl:template> 

     <xsl:template match=""@* | *""> 
     <xsl:copy> 
      <xsl:apply-templates select=""@* | node()""/> 
     </xsl:copy> 
     </xsl:template> 

    </xsl:stylesheet>"; 
    XslCompiledTransform transform = new XslCompiledTransform(); 
    using (StringReader stringReader = new StringReader(xsltString)) 
    using (XmlReader reader = XmlReader.Create(stringReader)) { 
    transform.Load(reader); 
    } 
    transform.Transform(@"c:\temp\nodes.xml", @"c:\temp\nodes-cleaned.xml");  
+0

danke für die Antwort. Ich werde diese Art von Ansatz ein anderes Mal verwenden, wenn ich in der Lage sein werde, zusätzliche Dateien zu laden. In diesem speziellen Fall kann ich jedoch keine externen Dateien verwenden. Das Laden von XSLT-Dateien ist in meinem Fall keine Option. – wariacik

+0

@wariacik: Sie können immer noch eine XSLT auch ohne eine externe Datei verwenden - ich erweiterte meine Antwort. Das Problem mit XSLT ist, dass sie schwierig zu verwenden sind, wenn Sie sie nicht bereits kennen - aber wenn Sie eine Menge XML-Verarbeitung machen, ist das eine gute Investition. – MiMo

+0

Danke. Ich wusste nicht, dass ich xslt als String laden könnte. Dies wird sehr nützlich in meinen Projekten sein. – wariacik

3

Ich weiß, es ist eine alte Frage, aber ich schrieb dies mit XmlDocument direkt.

es Hinzufügen, wenn jemand zieht es auf diese Weise zu tun:

XmlNode child_to_remove = parent.ChildNodes[i]; // get the child to remove 

// move all the children of "child_to_remove" to be the child of their grandfather (== parent) 
while(child_to_remove.HasChildNodes) 
    parent.InsertBefore(child_to_remove.ChildNodes[0], child_to_remove); 

parent.RemoveChild(child_to_remove); 

Das ist es :-) ist, hoffen, dass es jemanden helfen wird.