Je veux parcourir une NodeList
en utilisant une boucle for-each en Java. Je l'ai avec une boucle for et une boucle do-while mais pas pour chacun.
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());
}
La solution de contournement de ce problème est simple et, heureusement, vous ne devez l’appliquer qu’une seule fois.
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();
}
}
}
Une fois que vous avez ajouté cette classe d’utilitaire à votre projet et ajouté une static
import
pour la méthode XmlUtil.asList
à votre code source, vous pouvez l’utiliser comme ceci:
for(Node n: asList(dom.getElementsByTagName("year"))) {
…
}
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();
}
};
}
};
}
Je sais qu'il est tard pour la fête, mais ...
Depuis Java-8, vous pouvez écrire la solution de @ RayHulha de manière encore plus concise en utilisant une expression lambda (pour créer une nouvelle méthode Iterable
) (par 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++);
}
};
}
et ensuite l'utiliser comme ça:
NodeList nodeList = ...;
for (Node node : iterable(nodeList)) {
// ....
}
ou équivalent comme ceci:
NodeList nodeList = ...;
iterable(nodeList).forEach(node -> {
// ....
});
Comme NodeList
n’est qu’une interface, vous pouvez créer une classe qui implémenterait à la fois NodeList
et Iterable
, afin de pouvoir la parcourir.
Ajout de la petite version kotlin pour sience:
fun NodeList.forEach(action: (Node) -> Unit) {
(0 until this.length)
.asSequence()
.map { this.item(it) }
.forEach { action(it) }
}
On peut alors l'utiliser avec nodeList.forEach { do_something_awesome() }
NodeList
n'implémente pas Iterable
, vous ne pouvez donc pas l'utiliser avec la boucle for
améliorée.
La solution validée est très utile, mais je partage ici une solution améliorée basée sur la solution valide. Elle vous aide également à effectuer une itération, mais elle est simple à utiliser et sécurisée:
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)) { ... }
Merci @ Holger.
Si l'élément DOM actuel est supprimé (via JavaScript) lors de l'itération d'une liste de noeuds (créée à partir de getElementsByTagName () et peut-être d'autres), l'élément disparaîtra de la liste de noeuds. Cela rend l'itération correcte de NodeList plus délicate.
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);
}
}
Puis utilisez-le comme ceci: new IteratableNodeList(doc.getElementsByTagName(elementType)).
stream().filter(...)
Ou: new IteratableNodeList(doc.getElementsByTagName(elementType)).forEach(...)