如何在序列化之前从 DOM 中去除仅包含空格的文本节点?

Posted

技术标签:

【中文标题】如何在序列化之前从 DOM 中去除仅包含空格的文本节点?【英文标题】:How to strip whitespace-only text nodes from a DOM before serialization? 【发布时间】:2010-11-02 00:05:39 【问题描述】:

我有一些 Java (5.0) 代码从各种(缓存的)数据源构造 DOM,然后删除某些不需要的元素节点,然后使用以下方法将结果序列化为 XML 字符串:

// Serialize DOM back into a string
Writer out = new StringWriter();
Transformer tf = TransformerFactory.newInstance().newTransformer();
tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
tf.setOutputProperty(OutputKeys.INDENT, "no");
tf.transform(new DOMSource(doc), new StreamResult(out));
return out.toString();

但是,由于我要删除几个元素节点,因此最终序列化文档中会出现大量额外的空白。

是否有一种简单的方法可以在将 DOM 序列化为字符串之前(或同时)从 DOM 中删除/折叠多余的空格?

【问题讨论】:

【参考方案1】:

我是这样做的

    private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s*", Pattern.DOTALL);

    private void removeWhitespace(Document doc) 
        LinkedList<NodeList> stack = new LinkedList<>();
        stack.add(doc.getDocumentElement().getChildNodes());
        while (!stack.isEmpty()) 
            NodeList nodeList = stack.removeFirst();
            for (int i = nodeList.getLength() - 1; i >= 0; --i) 
                Node node = nodeList.item(i);
                if (node.getNodeType() == Node.TEXT_NODE) 
                    if (WHITESPACE_PATTERN.matcher(node.getTextContent()).matches()) 
                        node.getParentNode().removeChild(node);
                    
                 else if (node.getNodeType() == Node.ELEMENT_NODE) 
                    stack.add(node.getChildNodes());
                
            
        
    

【讨论】:

【参考方案2】:

以下代码有效:

public String getSoapXmlFormatted(String pXml) 
    try 
        if (pXml != null) 
            DocumentBuilderFactory tDbFactory = DocumentBuilderFactory
                    .newInstance();
            DocumentBuilder tDBuilder;
            tDBuilder = tDbFactory.newDocumentBuilder();
            Document tDoc = tDBuilder.parse(new InputSource(
                    new StringReader(pXml)));
            removeWhitespaces(tDoc);
            final DOMImplementationRegistry tRegistry = DOMImplementationRegistry
                    .newInstance();
            final DOMImplementationLS tImpl = (DOMImplementationLS) tRegistry
                    .getDOMImplementation("LS");
            final LSSerializer tWriter = tImpl.createLSSerializer();
            tWriter.getDomConfig().setParameter("format-pretty-print",
                    Boolean.FALSE);
            tWriter.getDomConfig().setParameter(
                    "element-content-whitespace", Boolean.TRUE);
            pXml = tWriter.writeToString(tDoc);
        
     catch (RuntimeException | ParserConfigurationException | SAXException
            | IOException | ClassNotFoundException | InstantiationException
            | IllegalAccessException tE) 
        tE.printStackTrace();
    
    return pXml;


public void removeWhitespaces(Node pRootNode) 
    if (pRootNode != null) 
        NodeList tList = pRootNode.getChildNodes();
        if (tList != null && tList.getLength() > 0) 
            ArrayList<Node> tRemoveNodeList = new ArrayList<Node>();
            for (int i = 0; i < tList.getLength(); i++) 
                Node tChildNode = tList.item(i);
                if (tChildNode.getNodeType() == Node.TEXT_NODE) 
                    if (tChildNode.getTextContent() == null
                            || "".equals(tChildNode.getTextContent().trim()))
                        tRemoveNodeList.add(tChildNode);
                 else
                    removeWhitespaces(tChildNode);
            
            for (Node tRemoveNode : tRemoveNodeList) 
                pRootNode.removeChild(tRemoveNode);
            
        
    

【讨论】:

这个答案将受益于一些解释。【参考方案3】:

尝试使用以下 XSL 和 strip-space 元素来序列化您的 DOM:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="xml" omit-xml-declaration="yes"/>

  <xsl:strip-space elements="*"/>

  <xsl:template match="@*|node()">
    <xsl:copy>
     <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

http://helpdesk.objects.com.au/java/how-do-i-remove-whitespace-from-an-xml-document

【讨论】:

【参考方案4】:

另一种可能的方法是在删除目标节点的同时删除相邻的空白:

private void removeNodeAndTrailingWhitespace(Node node) 
    List<Node> exiles = new ArrayList<Node>();

    exiles.add(node);
    for (Node whitespace = node.getNextSibling();
            whitespace != null && whitespace.getNodeType() == Node.TEXT_NODE && whitespace.getTextContent().matches("\\s*");
            whitespace = whitespace.getNextSibling()) 
        exiles.add(whitespace);
    

    for (Node exile: exiles) 
        exile.getParentNode().removeChild(exile);
    

这样做的好处是保持现有格式的其余部分不变。

【讨论】:

【参考方案5】:

下面的代码删除了所有空格的注释节点和文本节点。如果文本节点有一些值,值将被修剪

public static void clean(Node node)

  NodeList childNodes = node.getChildNodes();

  for (int n = childNodes.getLength() - 1; n >= 0; n--)
  
     Node child = childNodes.item(n);
     short nodeType = child.getNodeType();

     if (nodeType == Node.ELEMENT_NODE)
        clean(child);
     else if (nodeType == Node.TEXT_NODE)
     
        String trimmedNodeVal = child.getNodeValue().trim();
        if (trimmedNodeVal.length() == 0)
           node.removeChild(child);
        else
           child.setNodeValue(trimmedNodeVal);
     
     else if (nodeType == Node.COMMENT_NODE)
        node.removeChild(child);
  

参考:http://www.sitepoint.com/removing-useless-nodes-from-the-dom/

【讨论】:

该方法适用于小型 xml,但不适用于具有大量嵌套节点的大型 xml。对于 4 K 条记录,处理它大约需要 30 秒。我建议将 xml 读取为字符串,然后使用xmlString.replaceAll("\\pjavaWhitespace+", ""); 这样会很快。【参考方案6】:
transformer.setOutputProperty(OutputKeys.INDENT, "yes");

这将保留 xml 缩进。

【讨论】:

它不会去除多余的空格。【参考方案7】:

您可以使用 XPath 找到空文本节点,然后像这样以编程方式删除它们:

XPathFactory xpathFactory = XPathFactory.newInstance();
// XPath to find empty text nodes.
XPathExpression xpathExp = xpathFactory.newXPath().compile(
        "//text()[normalize-space(.) = '']");  
NodeList emptyTextNodes = (NodeList) 
        xpathExp.evaluate(doc, XPathConstants.NODESET);

// Remove each empty text node from document.
for (int i = 0; i < emptyTextNodes.getLength(); i++) 
    Node emptyTextNode = emptyTextNodes.item(i);
    emptyTextNode.getParentNode().removeChild(emptyTextNode);

如果您希望对节点移除进行比使用 XSL 模板更容易实现的控制,这种方法可能会很有用。

【讨论】:

我更喜欢这种“仅代码”解决方案,甚至比 XSL 解决方案更好,并且就像您所说的,如果需要,可以对节点删除进行更多控制。 顺便说一下,这种方法似乎只有在我在删除节点之前先调用 doc.normalize() 时才有效。我不确定为什么会有所作为。 优秀的答案。即使没有 normalize() 也适用于我。 @MarcNovakowski 需要致电normalize() 的示例案例。在 DOM 对象中加载一些 XML 字符串。调用removeChild() 方法从DOM 对象中取出一些节点。然后尝试像当前答案 (//text()[normalize-space(.) = '']) 中那样去除空格。删除节点的位置出现空行。如果首先调用 normalize(),则不会发生这种情况。

以上是关于如何在序列化之前从 DOM 中去除仅包含空格的文本节点?的主要内容,如果未能解决你的问题,请参考以下文章

PHP DOM获取nodevalue html? (不剥离标签)

怎么用trim函数来去除EXCEL表格中的空格?

cmd去除csv首行

如何从其中有孩子的div中获取文本

如何仅使用BeautifulSoup和Python删除包含空格的HTML标记

oracle如何去除某个字段中两边的空格