2013-02-19 205 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); 

这:-)就是它,希望它会帮助任何人。