I want to iterate through a NodeList
using a for-each loop in Java. I have it working with a for loop and a do-while loop but not for-each.
Node
NodeList
does not implement Iterable
, so you cannot use it with the enhanced for
loop.
If the current DOM element is removed (via JavaScript) while iterating a NodeList (created from getElementsByTagName() and maybe others), the element will disappear from the NodeList. This makes correct iteration of the NodeList more tricky.
public class IteratableNodeList implements Iterable<Node> {
final NodeList nodeList;
public IteratableNodeList(final NodeList _nodeList) {
nodeList = _nodeList;
}
@Override
public Iterator<Node> iterator() {
return new Iterator<Node>() {
private int index = -1;
private Node lastNode = null;
private boolean isCurrentReplaced() {
return lastNode != null && index < nodeList.getLength() &&
lastNode != nodeList.item(index);
}
@Override
public boolean hasNext() {
return index + 1 < nodeList.getLength() || isCurrentReplaced();
}
@Override
public Node next() {
if (hasNext()) {
if (isCurrentReplaced()) {
// It got removed by a change in the DOM.
lastNode = nodeList.item(index);
} else {
lastNode = nodeList.item(++index);
}
return lastNode;
} else {
throw new NoSuchElementException();
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public Stream<Node> stream() {
Spliterator<Node> spliterator =
Spliterators.spliterator(iterator(), nodeList.getLength(), 0);
return StreamSupport.stream(spliterator, false);
}
}
Then use it like this:
new IteratableNodeList(doc.getElementsByTagName(elementType)).
stream().filter(...)
Or:
new IteratableNodeList(doc.getElementsByTagName(elementType)).forEach(...)
The workaround for this problem is straight-forward, and, thankfully you have to implements it only once.
import java.util.*;
import org.w3c.dom.*;
public final class XmlUtil {
private XmlUtil(){}
public static List<Node> asList(NodeList n) {
return n.getLength()==0?
Collections.<Node>emptyList(): new NodeListWrapper(n);
}
static final class NodeListWrapper extends AbstractList<Node>
implements RandomAccess {
private final NodeList list;
NodeListWrapper(NodeList l) {
list=l;
}
public Node get(int index) {
return list.item(index);
}
public int size() {
return list.getLength();
}
}
}
Once you have added this utility class to your project and added a static
import
for the XmlUtil.asList
method to your source code you can use it like this:
for(Node n: asList(dom.getElementsByTagName("year"))) {
…
}
One can use the Java8 stream to iterate the NodeList.
NodeList filterList = source.getChildNodes();
IntStream.range(0, filterList.getLength()).boxed().map(filterList::item).forEach(node -> {
});