2013-02-19 246 views
5

我有一個問題涉及從XML文件中刪除特定的節點。刪除無子節點的父節點

這裏是我的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> 

我想刪除節點nodeB="toRemove",不刪除此節點的兒童。之後我需要用nodeB attribute="placeHolder"做同樣的事情。結果的部分看起來像:

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

我一直是這樣想的代碼才達到的是:

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

我能夠找到節點與期望的屬性的文檔,刪除或佔位符,但我不是能夠將此節點的子節點向上移動一級。在這種情況下你能幫助我嗎?它可以與Linq,XDocument,XmlReader解決,但我更喜歡使用XmlDocument。 感謝您提前給我提供的任何幫助。

編輯:

在這種情況下,我使用了略微修改後的代碼(保存順序)查野人寫道波紋管。一旦刪除

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

,然後做同樣的

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

這裏稍微修改後的代碼由@MiMo提供

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方法是非常有用的,以及在這種情況下。

+0

您的許多'nodeC'元素缺少結束標記。你能用有效的,格式良好的XML更新你的問題嗎? – 2013-02-19 21:26:49

+0

我已經更新了我的簡化的xml文件。感謝提示,現在更容易爲其他人閱讀。 – wariacik 2013-02-19 23:04:35

回答

3

使用LINQ到XML和你的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); 

注:XPath是在System.Xml.XPath

注2可供選擇:你可以轉換到/從XmlDocument使用these extensions,因爲您更喜歡XmlDocument。

+0

這裏的一個缺點是保存的孩子會被添加到包含節點的末尾,而不是留在文檔所在的部分。提問者並沒有說要保留他們的位置是一項要求,但很容易。 – JLRishe 2013-02-20 00:15:36

+0

@JLRishe如果你看看OP代碼,他所做的事情基本上是一樣的,但我喜歡你的觀點。 – 2013-02-20 00:17:33

+0

我真的很喜歡這種方法,但是在這種情況下,保留子節點的位置是一個重要的問題。有沒有辦法讓子節點保留在他們所在位置的文檔中? – wariacik 2013-02-20 08:09:58

4

問題是,您不能在枚舉子節點時修改文檔節點 - 您應該創建新節點而不是嘗試修改現有節點,而使用XmlDocument會變得有點棘手。

做這種轉換的最簡單的方法是使用XSLT,即應用該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> 

輸入文件輸出爲:

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

應用XSLT代碼簡單地說就是:

XslCompiledTransform transform = new XslCompiledTransform(); 
    transform.Load(@"c:\temp\nodes.xslt"); 
    transform.Transform(@"c:\temp\nodes.xml", @"c:\temp\nodes-cleaned.xml"); 

如果不可能(或可取的)使用X的外部文件SLT它可以從一個字符串讀取:

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

感謝您的答案。當我能夠加載其他文件時,我會再次使用這種方法。但是,在這種特殊情況下,我無法使用外部文件。所以在我的情況下加載xslt文件不是一個選項。 – wariacik 2013-02-20 17:38:08

+0

@wariacik:即使沒有外部文件,您仍然可以使用XSLT - 我擴展了我的答案。 XSLT的問題在於如果你不熟悉它們,它們很難使用 - 但是如果你做了大量的XML處理,學習它們是一個很好的投資。 – MiMo 2013-02-20 19:11:17

+0

謝謝。我不知道我可以加載xslt作爲一個字符串。這對我的項目非常有用。 – wariacik 2013-02-20 19:35:11

3

我知道它的一個老問題,但我直接使用XmlDocument寫了這個。

添加,如果有人喜歡做這種方式:

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

這:-)就是它,希望它會幫助任何人。