Рекурсивно удалить пустые узлы из XML

Я хочу удалить пустые узлы из элемента XML. Этот xml генерируется поставщиком, и я не могу контролировать генерацию xml. Но поскольку в XML мало пустых узлов, мне нужно рекурсивно удалить эти пустые узлы.

Этот xml получен из OMElement, и я получаю элемент из этого объекта, используя [XMLUtils][1] Sample XML

<A>
  <B>
    <C>
      <C1>
        <C11>something</C11>
        <C12>something</C12>
      </C1>
    </C>
    <D>
      <D1>
        <D11>
          <D111 operation="create">
            <Node>something else</Node>
          </D11>
        </D11>
      </D1>
      <D2>
        <D21>

        </D21>
      </D2>
    </D>
  </B>
</A> 

Поскольку D21 является пустым узлом, я хочу удалить D21, а поскольку теперь D2 является пустым узлом, я хочу удалить D2, но поскольку D имеет D1, я не хочу удалять D.

Точно так же возможно, что я могу получить

<A>
  <B>
    <C>

    </C>
  </B>
</A>

Теперь, поскольку C пуст, я хочу удалить C, затем B и, наконец, узел A. Я пытаюсь сделать это с помощью метода removeChild() в Узел

Но пока я не могу удалить их рекурсивно. Любые предложения по их рекурсивному удалению?

Я рекурсивно пытаюсь получить длину узла и узла. Но длина узла не помогает

if(childNode.getChildNodes().getLength() == 0 ){
       childNode.getParentNode().removeChild(childNode);

               }

С уважением
Дирадж Джоши


person Dheeraj Joshi    schedule 21.09.2012    source источник
comment
и какой код вы написали для этого   -  person Satya    schedule 21.09.2012
comment
Если childNode.getChildNodes().getLength() == 0, это означает, что у него нет дочерних узлов. Если вы хотите найти пустой узел, попробуйте получить значение узла без дочернего элемента, и если он возвращает нулевое или эквивалентное значение, вы можете удалить этот узел.   -  person specialscope    schedule 21.09.2012
comment
Значение узла равно null для всех элементов. Так что проверка значения узла бесполезна   -  person Dheeraj Joshi    schedule 21.09.2012


Ответы (4)


Это работает, просто создайте рекурсивную функцию, которая сначала «уходит вглубь», а затем удаляет пустые узлы на пути «резервного копирования дерева», это приведет к удалению как D21, так и D2.

public static void main(String[] args) throws Exception {

    DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
    String input = "<A><B><C><C1><C11>something</C11><C12>something</C12></C1></C><D><D1><D11><D111 operation=\"create\"><Node>something else</Node></D111></D11></D1><D2><D21></D21></D2></D></B></A>";

    Document document = builder.parse(new InputSource(new StringReader(
            input)));

    removeNodes(document);

    Transformer transformer = TransformerFactory.newInstance()
            .newTransformer();
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    StreamResult result = new StreamResult(new StringWriter());
    transformer.transform(new DOMSource(document), result);
    System.out.println(result.getWriter().toString());
}

public static void removeNodes(Node node) {
    NodeList list = node.getChildNodes();
    for (int i = 0; i < list.getLength(); i++) {
        removeNodes(list.item(i));
    }
    boolean emptyElement = node.getNodeType() == Node.ELEMENT_NODE
            && node.getChildNodes().getLength() == 0;
    boolean emptyText = node.getNodeType() == Node.TEXT_NODE
            && node.getNodeValue().trim().isEmpty();
    if (emptyElement || emptyText) {
        node.getParentNode().removeChild(node);
    }
}

Вывод

<A>
<B>
<C>
<C1>
<C11>something</C11>
<C12>something</C12>
</C1>
</C>
<D>
<D1>
<D11>
<D111 operation="create">
<Node>something else</Node>
</D111>
</D11>
</D1>
</D>
</B>
</A>
person Adam    schedule 21.09.2012
comment
спасибо за ответ, но я надеялся удалить ‹D2›‹/D2›, если он пуст. Но согласно вашему коду и коду, который у меня есть, я все еще получаю пустые узлы ‹D2›‹/D2›. - person Dheeraj Joshi; 21.09.2012
comment
@DheerajJoshi Я добавил вывод своего решения, он удаляет узел D2... - person Adam; 21.09.2012
comment
@ Адам, в вашем решении есть небольшая ошибка: node.getChildNodes().getLength() == 0 && node.getNodeType() == Node.ELEMENT_NODE) Оно не будет работать для узлов с некоторым текстом (возможно, всего с одним пробелом). Потому что парсер создаст для таких узлов дополнительный внутренний узел с типом Node.TEXT_NODE. Правильное условие должно включать дополнительную проверку || node.getNodeType() == Node.TEXT_NODE && StringUtils.isBlank(node.getNodeValue())) - person user1516873; 21.09.2012
comment
@user1516873 user1516873 Я обновил свое решение для работы с узлами, содержащими пробелы. - person Adam; 21.09.2012

У меня недостаточно представителей, чтобы прокомментировать решение @Adam, но у меня возникла проблема, когда после удаления узла последний брат этого узла был перемещен в нулевой индекс, из-за чего он не полностью удалял пустые элементы. Исправление заключалось в использовании списка для хранения всех узлов, которые мы хотим рекурсивно вызывать для удаления.

Также была ошибка, из-за которой удалялись пустые элементы, у которых были атрибуты.

Решение обоих вопросов:

public static void removeEmptyNodes(Node node) {

    NodeList list = node.getChildNodes();
    List<Node> nodesToRecursivelyCall = new LinkedList();

    for (int i = 0; i < list.getLength(); i++) {
        nodesToRecursivelyCall.add(list.item(i));
    }

    for(Node tempNode : nodesToRecursivelyCall) {
        removeEmptyNodes(tempNode);
    }

    boolean emptyElement = node.getNodeType() == Node.ELEMENT_NODE 
          && node.getChildNodes().getLength() == 0;
    boolean emptyText = node.getNodeType() == Node.TEXT_NODE 
          && node.getNodeValue().trim().isEmpty();

    if (emptyElement || emptyText) {
        if(!node.hasAttributes()) {
            node.getParentNode().removeChild(node);
        }
    }

}
person fazed    schedule 10.11.2014
comment
Я не могу поверить, что этот ответ не получил много голосов, отлично работает и без каких-либо преобразований, манипулирующих xml. Я искал так много решений, это сработало идеально для меня .. спасибо - person Akhil Jain; 21.07.2017

Используйте getTextContent() для элемента верхнего уровня DOM. Если метод возвращает пустую строку или ноль, вы можете удалить этот узел, поскольку этот узел и все дочерние узлы пусты. Если метод getTextContent() возвращает непустую строку, вызовите getTextContent для каждого дочернего элемента текущего узла и т. д.
См. документация.

person user1516873    schedule 21.09.2012

Просто работайте со строками:

    Pattern emptyValueTag = Pattern.compile("\\s*<\\w+/>");
    Pattern emptyTagMultiLine = Pattern.compile("\\s*<\\w+>\n*\\s*</\\w+>");

    xml = emptyValueTag.matcher(xml).replaceAll("");

    while (xml.length() != (xml = emptyTagMultiLine.matcher(xml).replaceAll("")).length()) {
    }

    return xml;
person nigi    schedule 14.03.2018