【问题标题】:Remove empty nodes from a XML recursively递归地从 XML 中删除空节点
【发布时间】:2012-09-13 12:48:36
【问题描述】:

我想从 XML 元素中删除空节点。 此 xml 是从供应商生成的,我无法控制 xml 的生成。但由于 XML 的空节点很少,我需要递归删除这些空节点。

这个 xml 是从 OMElement 获得的,我使用 [XMLUtils][1] 从这个对象中获得一个元素 示例 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。 我正在尝试使用 Node 中的 removeChild() 方法来做到这一点

但到目前为止,我无法递归删除它们。 有什么递归删除它们的建议吗?

我正在递归地尝试获取节点和节点长度。但是节点长度没有帮助

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

               }

问候
迪拉吉·乔希

【问题讨论】:

  • 你为此编写的代码是什么
  • 如果childNode.getChildNodes().getLength() == 0,表示没有子节点。如果要查找空节点,则尝试获取没有子节点的节点值,如果返回 null 或等效值,则可以删除该节点。
  • 没有。 nodevalue 对于所有元素都为空。所以检查节点值是没有帮助的

标签: java xml xmlnode


【解决方案1】:

我没有足够的代表评论@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);
        }
    }

}

【讨论】:

  • 我不敢相信这个答案没有得到太多的支持,工作得非常好,而且没有任何转换操纵 xml。我搜索了很多解决方案,这个对我来说非常有用.. 谢谢
【解决方案2】:

这行得通,只需创建一个“深入”的递归函数,然后在“备份树”的过程中删除空节点,这将具有删除 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>

【讨论】:

  • 感谢您的回复,但如果 为空,我希望将其删除。但是根据您的代码和我拥有的代码,我仍然得到 空节点
  • @DheerajJoshi 我已经添加了我的解决方案的输出,它确实删除了 D2 节点...
  • @Adam 您的解决方案有小错误:node.getChildNodes().getLength() == 0 &amp;&amp; node.getNodeType() == Node.ELEMENT_NODE) 它不适用于带有一些(可能只有一个空格)文本的节点。因为解析器将为此类节点添加类型为 Node.TEXT_NODE 的内部节点。正确的条件应包括附加检查|| node.getNodeType() == Node.TEXT_NODE &amp;&amp; StringUtils.isBlank(node.getNodeValue()))
  • @user1516873 我已经更新了我的解决方案来处理包含空白的节点。
【解决方案3】:

在 DOM 的顶级元素上使用 getTextContent()。如果方法返回空字符串或null,则可以删除该节点,因为该节点和所有子节点都是空的。如果方法getTextContent()返回非空字符串,则在当前节点的每个子节点上调用getTextContent,依此类推。
documentation

【讨论】:

    【解决方案4】:

    只使用字符串:

        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;
    

    【讨论】:

      【解决方案5】:
      public class RemoveEmprtElement {
      
      public static void main(String[] args) {
          ReadFile readFile =new ReadFile();
          String strXml=readFile.readFileFromPath(new File("sampleXml4.xml"));
          RemoveEmprtElement elementEmprtElement=new RemoveEmprtElement();
          DocumentBuilder dBuilder = null;
          Document doc = null;
          try {
              dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
              doc = dBuilder.parse(new ByteArrayInputStream(strXml.getBytes()));
      
              elementEmprtElement.getEmptyNodes(doc);
              TransformerFactory tf = TransformerFactory.newInstance();
              Transformer trans = tf.newTransformer();
              StreamResult result = new StreamResult(new StringWriter());
              trans.transform(new DOMSource(doc), result);
              System.out.println(result.getWriter().toString());
      
          }catch(Exception e) {
              e.printStackTrace();
          }
      }
      
      private void getEmptyNodes(Document doc){
      
          try {
              XPathFactory factory = XPathFactory.newInstance();
              XPath xpath = factory.newXPath();
              XPathExpression expr = xpath.compile("//*[not(*)]");
              Object resultNS = expr.evaluate(doc, XPathConstants.NODESET);
              NodeList nodes = (NodeList) resultNS;
              for(int i =0 ; i < nodes.getLength() ; i++){
                  Node node = nodes.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) {
                      xmlNodeRemove(doc,findPath(node));
                      getEmptyNodes(doc);
                  }
              } 
          }catch(Exception e) {
              e.printStackTrace();
          }
      
      }
      
      private void xmlNodeRemove(Document doc,String xmlNodeLocation){
      
          try {
              XPathFactory factory = XPathFactory.newInstance();
              XPath xpath = factory.newXPath();
              XPathExpression expr = xpath.compile(xmlNodeLocation);
              Object resultNS = expr.evaluate(doc, XPathConstants.NODESET);
              NodeList nodes = (NodeList) resultNS;
              Node node =nodes.item(0);
              if(node!=null && node.getParentNode()!=null && node.getParentNode().hasChildNodes()){
              node.getParentNode().removeChild(node);
              }
          }catch(Exception e) {
              e.printStackTrace();
          }
      }
      
      private String findPath(Node n) {
          String path="";
          if(n==null){
              return path;
          }else if(n.getNodeName().equals("#document")){
              return "";
          }
              else{
                  path=n.getNodeName();
                  path=findPath(n.getParentNode())+"/"+path;
              }
              return path;
          }
      
      }
      

      【讨论】:

        猜你喜欢
        • 2016-07-19
        • 2012-07-29
        • 2023-02-23
        • 1970-01-01
        • 2022-07-08
        • 1970-01-01
        • 2017-09-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多