Can I iterate through a NodeList using for-each in Java?

JavaXmlDom

Java Problem Overview


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.

NodeList nList = dom.getElementsByTagName("year");
do {
	Element ele = (Element) nList.item(i);
	list.add(ele.getElementsByTagName("MonthId").item(0).getTextContent());
	i++;
} while (i < nList.getLength());

NodeList nList = dom.getElementsByTagName("year");

for (int i = 0; i < nList.getLength(); i++) {
	Element ele = (Element) nList.item(i);
	list.add(ele.getElementsByTagName("MonthId").item(0).getTextContent());
}

Java Solutions


Solution 1 - Java

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"))) {
  …
}

Solution 2 - Java

I know it is late to the party, but...
Since Java-8 you can write @RayHulha's solution even more concisely by using lambda expression (for creating a new Iterable) and default method (for Iterator.remove):

public static Iterable<Node> iterable(final NodeList nodeList) {
    return () -> new Iterator<Node>() {

        private int index = 0;

        @Override
        public boolean hasNext() {
            return index < nodeList.getLength();
        }

        @Override
        public Node next() {
            if (!hasNext())
                throw new NoSuchElementException();
            return nodeList.item(index++); 
        }
    };
}

and then use it like this:

NodeList nodeList = ...;
for (Node node : iterable(nodeList)) {
    // ....
}

or equivalently like this:

NodeList nodeList = ...;
iterable(nodeList).forEach(node -> {
    // ....
});

Solution 3 - Java

public static Iterable<Node> iterable(final NodeList n) {
  return new Iterable<Node>() {
 
    @Override
    public Iterator<Node> iterator() {
 
      return new Iterator<Node>() {
 
        int index = 0;
 
        @Override
        public boolean hasNext() {
          return index < n.getLength();
        }
 
        @Override
        public Node next() {
          if (hasNext()) {
            return n.item(index++);
          } else {
            throw new NoSuchElementException();
          }  
        }
 
        @Override
        public void remove() {
          throw new UnsupportedOperationException();
        }
      };
    }
  };
}

Solution 4 - Java

Adding the happy little kotlin version for sience:

fun NodeList.forEach(action: (Node) -> Unit) {
    (0 until this.length)
            .asSequence()
            .map { this.item(it) }
            .forEach { action(it) }
}

One can then use it with nodeList.forEach { do_something_awesome() }

Solution 5 - Java

As NodeList is just an interface, you could create a class which would implement both NodeList and Iterable, in order to iterate through it.

Solution 6 - Java

NodeList does not implement Iterable, so you cannot use it with the enhanced for loop.

Solution 7 - Java

There are ready to use or copypaste iterator implementations in org.apache.commons.collections4.iterators.NodeListIterator and com.sun.xml.internal.ws.util.xml.NodeListIterator.

Solution 8 - Java

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(...)

Solution 9 - Java

The validated solution is very useful, but here I share an improved solution based on the valid one, this helps you iterate as well, but easy to use, and secure:

public class XMLHelper {
	private XMLHelper() { }

	public static List<Node> getChildNodes(NodeList l) {
		List<Node> children = Collections.<Node>emptyList();
		if (l != null && l.getLength() > 0) {
			if (l.item(0) != null && l.item(0).hasChildNodes()) {
				children = new NodeListWrapper(l.item(0).getChildNodes());
			}
		}
		return children;
	}

	public static List<Node> getChildNodes(Node n) {
		List<Node> children = Collections.<Node>emptyList();
		if (n != null && n.hasChildNodes()) {
			NodeList l = n.getChildNodes();
			if (l != null && l.getLength() > 0) {
				children = new NodeListWrapper(l);
			}
		}
		return children;
	}

	private 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();
		}
	}

}

Usage:

 for (Node inner : XMLHelper.getChildNodes(node)) { ... }

Thanks @Holger.

Solution 10 - Java

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


});

Solution 11 - Java

I want to thank @Calin for the inspiration with the Kotlin code, but I want to go a little bit further and to be able to filter NodeList content by type and subclass in one line

fun <T : Node> NodeList.forEach(clazz : KClass<T>, vararg nodeType: Short, action: (T) -> Unit) {
    (0 until this.length).asSequence().map { this.item(it) }
        .filter { nodeType.isEmpty() || nodeType.contains(it.nodeType)  }
        .filter { clazz.isInstance(it) }.map { clazz.java.cast(it) }
        .forEach { action(it) }
}

// original variant without any filtering, used for node's attributes

fun NamedNodeMap.forEach(action: (Node) -> Unit) {
    (0 until this.length).asSequence().map { this.item(it) }
        .forEach { action(it) }
}

Usage example:

xmlDoc.childNodes.forEach(Element::class, Node.ELEMENT_NODE) {
    println("tag ${it.tagName} with attributes: ") // 'it' is an Element here
    it.attributes.forEach { attr -> println("${attr.nodeName} - ${attr.nodeValue}")}
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser2919834View Question on Stackoverflow
Solution 1 - JavaHolgerView Answer on Stackoverflow
Solution 2 - JavaThomas FritschView Answer on Stackoverflow
Solution 3 - JavaRay HulhaView Answer on Stackoverflow
Solution 4 - JavaCalinView Answer on Stackoverflow
Solution 5 - JavaEel LeeView Answer on Stackoverflow
Solution 6 - Javachrylis -cautiouslyoptimistic-View Answer on Stackoverflow
Solution 7 - JavaVadzimView Answer on Stackoverflow
Solution 8 - JavadfielderView Answer on Stackoverflow
Solution 9 - JavajalopezsuarezView Answer on Stackoverflow
Solution 10 - JavaRadhakrishnan CHView Answer on Stackoverflow
Solution 11 - JavaTachikomaGTView Answer on Stackoverflow