如何在序列化之前从 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? (不剥离标签)