Удаление узлов DOM при прохождении NodeList
Я собираюсь удалить определенные элементы в документе 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? Любые другие оговорки с таким подходом? Если это совершенно неправильно, что это за правильный способ?
Ответы
Ответ 1
Итак, учитывая, что удаление узлов при обходе 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);
}
Ответ 2
Удаление узлов при циклировании приведет к нежелательным результатам, например. либо пропущенных или дублированных результатов. Это даже не проблема синхронизации и безопасности потоков, но если узлы модифицируются самим циклом. В большинстве случаев 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);
}
}
Ответ 3
В соответствии с спецификацией DOM результат вызова node.getElementsByTagName( "..." ) должен быть "живым", то есть любая модификация, внесенная в дерево DOM, будет отражена в Объект NodeList. Ну, для соответствия реализаций, то есть...
Объекты NodeList и NamedNodeMap в DOM живут; т.е. изменения в базовая структура документа отражается во всех соответствующих NodeList и Объекты NamedNodeMap.
(Спецификация DOM)
Итак, когда вы изменяете древовидную структуру, соответствующая реализация изменит NodeList, чтобы отразить эти изменения.
Ответ 4
Библиотека Практическая 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
Ответ 5
Согласно спецификации DOM Level 3 Core,
результат вызова метода node.getElementsByTagName("...")
будет ссылкой на тип live "NodeList
.
Объекты NodeList и NamedNodeMap в DOM являются живыми; то есть изменения в структуре базового документа отражаются во всех соответствующих объектах NodeList и NamedNodeMap.... изменения автоматически отражаются в NodeList, без дополнительных действий с пользовательской частью.
1.1.1 Модель структуры DOM, пара. 2
JavaSE 7 соответствует спецификации DOM Level 3: она реализует интерфейс live NodeList и определяет его как тип; он определяет и предоставляет метод getElementsByTagName
на Элементе интерфейса, который возвращает тип live NodeList
.
Ссылки
W3C - Объектная модель документа (DOM) Уровень 3 Основная спецификация - getElementsByTagName
JavaSE 7 - Элемент интерфейса
JavaSE 7 - Тип NodeList
Ответ 6
Старый пост, но ничего не помечено как ответ. Мой подход заключается в повторении с конца, т.е.
for (int i = nodes.getLength() - 1; i >= 0; i--) {
// do processing, and then
e.getParentNode().removeChild(e);
}
При этом вам не нужно беспокоиться о том, что NodeList становится короче во время удаления.