通过 CDATA 从 Java 8 到 Java 11 的 XML 转换来处理换行符中的更改



在Java 9中,OutputKeys.INDENTjavax.xml.transform.Transformer处理CDATA标签的方式发生了变化。简而言之,在Java 8中,包含一些字符数据的名为"test"的标记将导致:

<test><![CDATA[data]]></test>

但是在Java 9中,相同的结果是

<test>
<![CDATA[data]]>
</test>

哪个不是同一个 XML。

我了解到(从不再可用的来源)对于Java 9,有一种解决方法使用带有setIgnoringElementContentWhitespace=trueDocumentBuilderFactory,但这不再适用于Java 11。

有谁知道在 Java 11 中处理这个问题的方法?我要么在寻找一种方法来防止额外的换行符(但仍然能够格式化我的 XML),要么能够在解析 XML 时忽略它们(最好使用 SAX)。

不幸的是,我不知道 CDATA 标签在我的应用程序中实际包含什么。它可能以空格或换行符开头或结尾,因此在读取 XML 或实际设置结果对象中的值时,我不能只是去除它们。

演示问题的示例程序:

public static void main(String[] args) throws TransformerException, ParserConfigurationException, IOException, SAXException
{
String data = "data";
StreamSource source = new StreamSource(new StringReader("<foo><bar><![CDATA[" + data + "]]></bar></foo>"));
StreamResult result = new StreamResult(new StringWriter());
Transformer tform = TransformerFactory.newInstance().newTransformer();
tform.setOutputProperty(OutputKeys.INDENT, "yes");
tform.transform(source, result);
String xml = result.getWriter().toString();
System.out.println(xml); // I expect bar and CDATA to be on same line. This is true for Java 8, false for Java 11

Document document = DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.parse(new InputSource(new StringReader(xml)));
String resultData = document.getElementsByTagName("bar")
.item(0)
.getTextContent();
System.out.println(data.equals(resultData)); // True for Java 8, false for Java 11
}

编辑:为了将来参考,我已经向Oracle提交了一份错误报告,这在Java 14中得到了修复:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8223291

由于您的代码依赖于未指定的行为,因此额外的显式代码似乎更好:

  • 您想要这样的缩进:

    tform.setOutputProperty(OutputKeys.INDENT, "yes");
    tform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
    
  • 但是,对于包含 CDATA 标记的元素,则不然:

    String xml = result.getWriter().toString();
    // No indentation (whitespace) for elements with a CDATA section.
    xml = xml.replaceAll("(?s)>\s*(<\!\[CDATA\[.*?]]>)\s*</", ">$1</");
    

正则表达式使用:

  • (?s)DOT_ALL.匹配任何字符,包括换行符。
  • .*?最短的匹配序列,以不匹配"...]]>...]]>"。

或者:在 DOM 树(保留 CDATA)中,您可以检索每个 XPath 的所有 CDATA 节,并使用父元素删除空格同级元素。

Joop Eggen 的解决方案非常出色。

我只是想稍微扩展一下解决方案。

xml = xml.replaceAll(">\s*(<\!\[CDATA\[(.|\n|\r\n)*?]\]>)\s*</", ">$1</");

在此正则表达式中,我包括允许在 CDATA 标记内添加新行的可能性。所以我正在测试n和窗口式rn

XML 示例:

<test>
<![CDATA[com.foo.test]]>
</test
<test>
<![CDATA[2st Line   
2nd Line]]>
</test>

最新更新