遍历NodeList时删除DOM节点

时间:2009-09-03 15:20:28

标签: java xml dom

我即将删除XML文档中的某些元素,使用如下代码:

NodeList nodes = ...;
for (int i = 0; i < nodes.getLength(); i++) {
  Element e = (Element)nodes.item(i);
  if (certain criteria involving Element e) {
    e.getParentNode().removeChild(e);
  }
}

这会干扰NodeList的正确遍历吗?用这种方法还有其他注意事项吗?如果这是完全错误的,那么正确的做法是什么?

7 个答案:

答案 0 :(得分:11)

因此,假设在遍历NodeList时删除节点将导致NodeList更新以反映新的现实,我假设我的索引将变为无效,这将无效。

因此,似乎解决方案是在遍历期间跟踪要删除的元素,并在不再使用NodeList后将其全部删除。

NodeList nodes = ...;
Set<Element> targetElements = new HashSet<Element>();
for (int i = 0; i < nodes.getLength(); i++) {
  Element e = (Element)nodes.item(i);
  if (certain criteria involving Element e) {
    targetElements.add(e);
  }
}
for (Element e: targetElements) {
  e.getParentNode().removeChild(e);
}

答案 1 :(得分:9)

循环时删除节点会导致不良结果,例如错过或重复的结果。这甚至不是同步和线程安全的问题,但是如果节点由循环本身修改。在这种情况下,大多数Java的Iterator都会抛出一个ConcurrentModificationException,这是NodeList没有考虑到的。

可以通过递减NodeList大小和同时递减iteraror指针来修复它。只有当我们为每个循环迭代进行一次删除操作时,才能使用此解决方案。

NodeList nodes = ...;
for (int i = nodes.getLength() - 1; i >= 0; i--) {
  Element e = (Element)nodes.item(i);
   if (certain criteria involving Element e) {
    e.getParentNode().removeChild(e);
  }
}

答案 2 :(得分:7)

根据DOM规范,调用 node.getElementsByTagName(“...”)的结果应该是“实时”,即对DOM树进行的任何修改将反映在 NodeList 对象中。那么,对于符合要求的实现,那就是......

  

中的NodeList和NamedNodeMap对象   DOM是活的;也就是说,改变了   底层文档结构是   反映在所有相关的NodeList和   NamedNodeMap对象。

DOM Specification

因此,当您修改树结构时,符合标准的实现将更改 NodeList 以反映这些更改。

答案 3 :(得分:1)

Practical XML库现在包含NodeListIterator,它包装NodeList并提供完整的Iterator支持(这似乎是比发布我们在评论中讨论的代码更好的选择)。如果您不想使用完整的库,请随意复制该类:http://practicalxml.svn.sourceforge.net/viewvc/practicalxml/trunk/src/main/java/net/sf/practicalxml/util/NodeListIterator.java?revision=125&view=markup

答案 4 :(得分:0)

根据DOM Level 3 Core规范,

调用方法node.getElementsByTagName("...")的结果将是对“ 实时 NodeList类型的引用。

  

DOM中的NodeList和NamedNodeMap对象是实时的;也就是说,对底层文档结构的更改将反映在所有相关的NodeList和NamedNodeMap对象中。   ...更改会自动反映在NodeList中,而无需用户的进一步操作。

     1.1.1 The DOM Structure Model, para. 2

JavaSE 7符合DOM Level 3规范:它实现 live NodeList接口并将其定义为类型;它在Interface Element上定义并公开getElementsByTagName方法,该方法返回 实时 NodeList类型。

参考

W3C - Document Object Model (DOM) Level 3 Core Specification - getElementsByTagName

JavaSE 7 - Interface Element

JavaSE 7 - NodeList Type

答案 5 :(得分:0)

旧帖子,但没有标记为答案。我的方法是从最后迭代,即

for (int i = nodes.getLength() - 1; i >= 0; i--) {
    // do processing, and then
    e.getParentNode().removeChild(e);
}

使用此功能,您无需担心删除时NodeList会变短。

答案 6 :(得分:0)

如前所述,删除元素会减小列表的大小,但计数器仍在增加(i ++):

[element 1] <- Delete 
[element 2]
[element 3]
[element 4]
[element 5]

[element 2]  
[element 3] <- Delete
[element 4]
[element 5]
--

[element 2]  
[element 4] 
[element 5] <- Delete
--
--

[element 2]  
[element 4] 
--
--
--

我认为,最简单的解决方案是在循环中删除i ++部分,并在未删除迭代元素时根据需要执行。

NodeList nodes = ...;
for (int i = 0; i < nodes.getLength();) {
  Element e = (Element)nodes.item(i);
  if (certain criteria involving Element e) {
    e.getParentNode().removeChild(e);        
  } else {
    i++;
  }
}

删除迭代元素后,指针停留在同一位置。列表会自行移动。

[element 1] <- Delete 
[element 2]
[element 3]
[element 4]
[element 5]

[element 2] <- Leave
[element 3]
[element 4]
[element 5]
--

[element 2] 
[element 3] <- Leave
[element 4]
[element 5]
--

[element 2] 
[element 3] 
[element 4] <- Delete
[element 5]
--

[element 2] 
[element 3] 
[element 5] <- Delete
--
--

[element 2] 
[element 3] 
--
--
--