删除没有子节点的父节点

Posted

技术标签:

【中文标题】删除没有子节点的父节点【英文标题】:remove parent node without childs nodes 【发布时间】:2013-02-04 17:06:42 【问题描述】:

我有一个关于从 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);

我能够找到具有所需属性 toRemove 或 placeHolder 的节点,但是我无法将此节点的子节点上移一级。在这种情况下你能帮我吗?它可以是 Linq、XDocument、XmlReader 的解决方案,但我更喜欢使用 XmlDocument。 感谢您提前为我提供的任何帮助。

编辑:

在这种情况下,我使用了 Chuck Savage 在下面编写的稍微修改过的代码(以保持顺序)。一次删除

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

然后对

做同样的事情
  <nodeB attribute="placeHolder"></nodeB>

这里是稍微修改的代码

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

@MiMo 提供的 xslt 方法在这种情况下也非常有用。

【问题讨论】:

您的许多 nodeC 元素缺少结束标记。你能用有效的、格式正确的 xml 更新你的问题吗? 我已经更新了我的简化 xml 文件。感谢您的提示,现在其他人更容易阅读。 【参考方案1】:

问题是您不能在枚举其子节点时修改文档节点 - 您应该创建新节点而不是尝试修改现有节点,而使用 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");

如果不可能(或不希望)为 XSLT 使用外部文件,则可以从字符串中读取它:

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

【讨论】:

感谢您的回答。下次我将能够加载其他文件时,我将使用这种方法。但是在这种特定情况下,我不能使用外部文件。所以在我的情况下加载 xslt 文件不是一个选项。 @wariacik:即使没有外部文件,您仍然可以使用 XSLT - 我扩展了我的答案。 XSLT 的问题在于,如果您不熟悉它们,它们很难使用 - 但如果您进行大量 XML 处理,学习它们是一项不错的投资。 谢谢。我不知道我可以将 xslt 作为字符串加载。这对我的项目非常有用。【参考方案2】:

使用 Linq-to-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:您可以使用 these extensions 与 XmlDocument 进行转换,因为您更喜欢 XmlDocument。

【讨论】:

这里的一个缺点是保留的子节点将被添加到包含节点的末尾,而不是留在文档所在的部分。提问者没有说保留他们的位置是一项要求,但很容易做到。 @JLRishe 如果您查看 OPs 代码,他基本上在做同样的事情,但我喜欢您的观点。 我真的很喜欢这种方法,但是在这种情况下,需要保留子节点的位置。有什么方法可以将子节点保留在文档的某个位置? 编辑:看起来像 node.Parent.AddAfterSelf(node.Elements()); 而不是 .Add 方法可以完成工作。我稍后会测试它并确保我得到正确的输出。 @wariacik 我调整了答案。你想要的是node.AddBeforeSelf(node.Elements())【参考方案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);

就是这样:-),希望对任何人都有帮助。

【讨论】:

以上是关于删除没有子节点的父节点的主要内容,如果未能解决你的问题,请参考以下文章

Scenekit - 将子节点(平面节点)添加到相机前面的父节点(球体节点)

sql根据子节点查出所有的父节点的

求教二叉树遍历,父节点出错

SQL (根据子节点查询父节点信息)

如何根据子节点删除父节点和/或 css 样式

仅选择与选择器匹配的父节点