javax.xml.transform.Transformer 行尾不再遵循系统 属性 "line.separator"
javax.xml.transform.Transformer line endings no longer respect system property "line.separator"
How to control line endings that javax.xml.transform.Transformer creates? 的评论建议设置系统 属性 "line.separator"。这在 Java 8 (Oracle JDK 1.8.0_171) 中对我有用(并且对于我手头的任务是可以接受的),但在 Java 11 (openjdk 11.0. 1).
根据票证 XALANJ-2137 我做了一个(未受过教育,因为我什至不知道我使用的是哪个 javax.xml 实现)猜测尝试 setOutputProperty("{http://xml.apache.org/xslt}line-separator", ..)
或者 setOutputProperty("{http://xml.apache.org/xalan}line-separator", ..)
,但都不起作用。
如何控制Java11中变压器的断线?
这是一些演示代码,它在 Windows 和 Java 11 下打印“... #13 #10 ...”,它应该打印“... #10 ... " 仅。
package test.xml;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.stream.Collectors;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class TestXmlTransformerLineSeparator {
public static void main(String[] args) throws Exception {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><foo/></root>";
final String lineSep = "\n";
String oldLineSep = System.setProperty("line.separator", lineSep);
try {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", lineSep);
transformer.setOutputProperty("{http://xml.apache.org/xslt}line-separator", lineSep);
StreamSource source = new StreamSource(new StringReader(xml));
StringWriter writer = new StringWriter();
StreamResult target = new StreamResult(writer);
transformer.transform(source, target);
System.out.println(writer.toString().chars().mapToObj(c -> c <= ' ' ? "#" + c : "" + Character.valueOf((char) c))
.collect(Collectors.joining(" ")));
System.out.println(writer);
} finally {
System.setProperty("line.separator", oldLineSep);
}
}
}
据我所知,您可以控制 Java 接口的默认 Java 实现在 Java 11 中使用的行分隔符的唯一方法是设置line.separator 属性 在 Java 命令行上。对于这里的简单示例程序,您可以通过创建一个名为 javaArgs reading
的文本文件来实现
-Dline.separator="\n"
并用命令行执行程序
java @javaArgs TestXmlTransformerLineSeparator
Java9 中引入的@ 语法在这里很有用,因为@-文件的解析方式会将“\n”转换为 LF 行分隔符。没有 @-file 也可以完成同样的事情,但我所知道的唯一方法需要更复杂的 OS-dependent 语法来定义一个变量,该变量包含您想要的行分隔符并具有 java 命令行展开变量。
如果您想要的行分隔符是 CRLF,那么 javaArgs 文件将改为读取
-Dline.separator="\r\n"
在较大的程序中,更改整个应用程序的 line.separator 变量可能是不可接受的。为了避免为整个应用程序设置 line.separator,可以使用刚才讨论的命令行启动一个单独的 Java 进程,但是启动进程和与单独进程通信的开销要转移Transformer
应该写入流的数据可能会使它成为一个不受欢迎的解决方案。
所以实际上,更好的解决方案可能是实现一个 FilterWriter
来过滤输出流以将行分隔符转换为您想要的行分隔符。此解决方案不会更改变压器本身使用的线路分隔符,并且可能被视为 post-processing 变压器的结果,因此从某种意义上说,它不是对您的特定问题的答案,但我认为它确实给出了所需的结果没有很多开销。下面是一个使用 FilterWriter
从输出 writer 中删除所有 CR 字符(即回车符 returns)的示例。
import java.io.FilterWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.stream.Collectors;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class TransformWithFilter {
private static class RemoveCRFilterWriter extends FilterWriter {
RemoveCRFilterWriter(Writer wrappedWriter) {
super(wrappedWriter);
}
@Override
public void write(int c) throws IOException {
if (c != (int)('\r')) {
super.write(c);
}
}
@Override
public void write(char[] cbuf, int offset, int length) throws IOException {
int localOffset = offset;
for (int i = localOffset; i < offset + length; ++i) {
if (cbuf[i] == '\r') {
if (i > localOffset) {
super.write(cbuf, localOffset, i - localOffset);
}
localOffset = i + 1;
}
}
if (localOffset < offset + length) {
super.write(cbuf, localOffset, offset + length - localOffset);
}
}
@Override
public void write(String str, int offset, int length) throws IOException {
int localOffset = offset;
for (int i = localOffset; i < offset + length; ++i) {
if (str.charAt(i) == '\r') {
if (i > localOffset) {
super.write(str, localOffset, i - localOffset);
}
localOffset = i + 1;
}
}
if (localOffset < offset + length) {
super.write(str, localOffset, offset + length - localOffset);
}
}
}
public static void main(String[] args) throws Exception {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><foo/></root>";
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
StreamSource source = new StreamSource(new StringReader(xml));
StringWriter stringWriter = new StringWriter();
FilterWriter writer = new RemoveCRFilterWriter(stringWriter);
StreamResult target = new StreamResult(writer);
transformer.transform(source, target);
System.out.println(stringWriter.toString().chars().mapToObj(c -> c <= ' ' ? "#" + c : "" + Character.valueOf((char) c))
.collect(Collectors.joining(" ")));
System.out.println(stringWriter);
}
}
序列化 XML 问题的另一个实用解决方案是通过使用 Transformer
获得 [=20] 来获得 XML 的 DOM 表示=] 或直接解析为 DOM 并用 LSSerializer
写出 DOM,这为设置行分隔符提供了明确的支持。由于这不再使用 Transformer
并且 Stack Overflow 上还有其他示例,因此我不会在这里进一步讨论它。
不过,回顾一下 Java 11 中的更改以及为什么我认为没有其他方法可以控制 Java 默认实现所使用的行分隔符的原因Transformer
。 Java 对 Transformer
接口的默认实现使用继承自 com.sun.org.apache.xml.internal.serializer.ToStream
的 ToXMLStream
class 并在同一个包中实现。查看 OpenJDK 的提交历史,我发现 src/java.xml/share/classes/com/sun/org/apache/xml/internal/serializer/ToStream.java
已更改 here,从读取系统属性中当前定义的 line.separator
属性 改为读取 System.lineSeparator()
,对应于 Java 虚拟机初始化时的行分隔符。此提交首次发布于 Java 11,因此问题中的代码的行为应与 Java 8 中的行为相同,直至并包括 Java 10。
如果你花一些时间阅读 ToStream.java
,因为它在提交后存在,它改变了行分隔符的读取方式(可访问 here),特别关注第 135 到 140 行和第 508 到 514 行,你会注意到序列化器实现确实支持使用其他行分隔符,事实上,输出 属性 标识为
{http://xml.apache.org/xalan}line-separator
应该是一种控制使用哪个行分隔符的方法。
那么,为什么问题中的示例不起作用?回答:在当前 Java 默认实现的 Transformer
接口中,只有少数特定的用户设置的属性被传输到序列化程序。这些主要是 XSLT 规范中定义的属性,但特殊的 indent-amount
属性 也被转移。但是,行分隔符输出 属性 不是传输到序列化程序的属性之一。
使用 setOutputProperty
在 Transformer 本身上显式设置的输出属性通过 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl
第 1029-1128 行定义的 setOutputProperties
方法传输到序列化程序(可访问 here). If you instead define an explicit XSLT transform and use its <xsl:output>
tag to set the output properties, the properties that are transferred to the serializer are filtered first of all by the parseContents
method defined on lines 139-312 of com.sun.org.apache.xalan.internal.xsltc.compiler.Output
(accessible here) and filtered again in the transferOutputSettings
method defined on lines 671-715 of com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
(accessible here).
总而言之,似乎没有可以在 Transformer
接口的默认 Java 实现上设置的输出 属性 来控制它使用的行分隔符.很可能还有其他 Transformer
实现的提供者确实提供了对行分隔符的控制,但除了默认实现之外,我对 Java 11 中 Transformer
接口的任何实现没有任何经验随 OpenJDK 版本提供。
How to control line endings that javax.xml.transform.Transformer creates? 的评论建议设置系统 属性 "line.separator"。这在 Java 8 (Oracle JDK 1.8.0_171) 中对我有用(并且对于我手头的任务是可以接受的),但在 Java 11 (openjdk 11.0. 1).
根据票证 XALANJ-2137 我做了一个(未受过教育,因为我什至不知道我使用的是哪个 javax.xml 实现)猜测尝试 setOutputProperty("{http://xml.apache.org/xslt}line-separator", ..)
或者 setOutputProperty("{http://xml.apache.org/xalan}line-separator", ..)
,但都不起作用。
如何控制Java11中变压器的断线?
这是一些演示代码,它在 Windows 和 Java 11 下打印“... #13 #10 ...”,它应该打印“... #10 ... " 仅。
package test.xml;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.stream.Collectors;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class TestXmlTransformerLineSeparator {
public static void main(String[] args) throws Exception {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><foo/></root>";
final String lineSep = "\n";
String oldLineSep = System.setProperty("line.separator", lineSep);
try {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", lineSep);
transformer.setOutputProperty("{http://xml.apache.org/xslt}line-separator", lineSep);
StreamSource source = new StreamSource(new StringReader(xml));
StringWriter writer = new StringWriter();
StreamResult target = new StreamResult(writer);
transformer.transform(source, target);
System.out.println(writer.toString().chars().mapToObj(c -> c <= ' ' ? "#" + c : "" + Character.valueOf((char) c))
.collect(Collectors.joining(" ")));
System.out.println(writer);
} finally {
System.setProperty("line.separator", oldLineSep);
}
}
}
据我所知,您可以控制 Java 接口的默认 Java 实现在 Java 11 中使用的行分隔符的唯一方法是设置line.separator 属性 在 Java 命令行上。对于这里的简单示例程序,您可以通过创建一个名为 javaArgs reading
的文本文件来实现-Dline.separator="\n"
并用命令行执行程序
java @javaArgs TestXmlTransformerLineSeparator
Java9 中引入的@ 语法在这里很有用,因为@-文件的解析方式会将“\n”转换为 LF 行分隔符。没有 @-file 也可以完成同样的事情,但我所知道的唯一方法需要更复杂的 OS-dependent 语法来定义一个变量,该变量包含您想要的行分隔符并具有 java 命令行展开变量。
如果您想要的行分隔符是 CRLF,那么 javaArgs 文件将改为读取
-Dline.separator="\r\n"
在较大的程序中,更改整个应用程序的 line.separator 变量可能是不可接受的。为了避免为整个应用程序设置 line.separator,可以使用刚才讨论的命令行启动一个单独的 Java 进程,但是启动进程和与单独进程通信的开销要转移Transformer
应该写入流的数据可能会使它成为一个不受欢迎的解决方案。
所以实际上,更好的解决方案可能是实现一个 FilterWriter
来过滤输出流以将行分隔符转换为您想要的行分隔符。此解决方案不会更改变压器本身使用的线路分隔符,并且可能被视为 post-processing 变压器的结果,因此从某种意义上说,它不是对您的特定问题的答案,但我认为它确实给出了所需的结果没有很多开销。下面是一个使用 FilterWriter
从输出 writer 中删除所有 CR 字符(即回车符 returns)的示例。
import java.io.FilterWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.stream.Collectors;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class TransformWithFilter {
private static class RemoveCRFilterWriter extends FilterWriter {
RemoveCRFilterWriter(Writer wrappedWriter) {
super(wrappedWriter);
}
@Override
public void write(int c) throws IOException {
if (c != (int)('\r')) {
super.write(c);
}
}
@Override
public void write(char[] cbuf, int offset, int length) throws IOException {
int localOffset = offset;
for (int i = localOffset; i < offset + length; ++i) {
if (cbuf[i] == '\r') {
if (i > localOffset) {
super.write(cbuf, localOffset, i - localOffset);
}
localOffset = i + 1;
}
}
if (localOffset < offset + length) {
super.write(cbuf, localOffset, offset + length - localOffset);
}
}
@Override
public void write(String str, int offset, int length) throws IOException {
int localOffset = offset;
for (int i = localOffset; i < offset + length; ++i) {
if (str.charAt(i) == '\r') {
if (i > localOffset) {
super.write(str, localOffset, i - localOffset);
}
localOffset = i + 1;
}
}
if (localOffset < offset + length) {
super.write(str, localOffset, offset + length - localOffset);
}
}
}
public static void main(String[] args) throws Exception {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root><foo/></root>";
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
StreamSource source = new StreamSource(new StringReader(xml));
StringWriter stringWriter = new StringWriter();
FilterWriter writer = new RemoveCRFilterWriter(stringWriter);
StreamResult target = new StreamResult(writer);
transformer.transform(source, target);
System.out.println(stringWriter.toString().chars().mapToObj(c -> c <= ' ' ? "#" + c : "" + Character.valueOf((char) c))
.collect(Collectors.joining(" ")));
System.out.println(stringWriter);
}
}
序列化 XML 问题的另一个实用解决方案是通过使用 Transformer
获得 [=20] 来获得 XML 的 DOM 表示=] 或直接解析为 DOM 并用 LSSerializer
写出 DOM,这为设置行分隔符提供了明确的支持。由于这不再使用 Transformer
并且 Stack Overflow 上还有其他示例,因此我不会在这里进一步讨论它。
不过,回顾一下 Java 11 中的更改以及为什么我认为没有其他方法可以控制 Java 默认实现所使用的行分隔符的原因Transformer
。 Java 对 Transformer
接口的默认实现使用继承自 com.sun.org.apache.xml.internal.serializer.ToStream
的 ToXMLStream
class 并在同一个包中实现。查看 OpenJDK 的提交历史,我发现 src/java.xml/share/classes/com/sun/org/apache/xml/internal/serializer/ToStream.java
已更改 here,从读取系统属性中当前定义的 line.separator
属性 改为读取 System.lineSeparator()
,对应于 Java 虚拟机初始化时的行分隔符。此提交首次发布于 Java 11,因此问题中的代码的行为应与 Java 8 中的行为相同,直至并包括 Java 10。
如果你花一些时间阅读 ToStream.java
,因为它在提交后存在,它改变了行分隔符的读取方式(可访问 here),特别关注第 135 到 140 行和第 508 到 514 行,你会注意到序列化器实现确实支持使用其他行分隔符,事实上,输出 属性 标识为
{http://xml.apache.org/xalan}line-separator
应该是一种控制使用哪个行分隔符的方法。
那么,为什么问题中的示例不起作用?回答:在当前 Java 默认实现的 Transformer
接口中,只有少数特定的用户设置的属性被传输到序列化程序。这些主要是 XSLT 规范中定义的属性,但特殊的 indent-amount
属性 也被转移。但是,行分隔符输出 属性 不是传输到序列化程序的属性之一。
使用 setOutputProperty
在 Transformer 本身上显式设置的输出属性通过 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl
第 1029-1128 行定义的 setOutputProperties
方法传输到序列化程序(可访问 here). If you instead define an explicit XSLT transform and use its <xsl:output>
tag to set the output properties, the properties that are transferred to the serializer are filtered first of all by the parseContents
method defined on lines 139-312 of com.sun.org.apache.xalan.internal.xsltc.compiler.Output
(accessible here) and filtered again in the transferOutputSettings
method defined on lines 671-715 of com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
(accessible here).
总而言之,似乎没有可以在 Transformer
接口的默认 Java 实现上设置的输出 属性 来控制它使用的行分隔符.很可能还有其他 Transformer
实现的提供者确实提供了对行分隔符的控制,但除了默认实现之外,我对 Java 11 中 Transformer
接口的任何实现没有任何经验随 OpenJDK 版本提供。