在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", ..)
,但是都没有有效。
如何在Java 11中控制转换器的换行符?
这里有一些演示代码,在带有Java 11的Windows下打印“ ...#13#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);
}
}
}
答案 0 :(得分:1)
据我所知,控制Transformer
接口的默认Java实现在Java 11中使用的行分隔符的唯一方法是在Java命令行上设置line.separator属性。对于此处的简单示例程序,您可以通过创建一个名为javaArgs reading的文本文件来做到这一点
-Dline.separator="\n"
并使用命令行执行程序
java @javaArgs TestXmlTransformerLineSeparator
在Java 9中引入的@语法在这里很有用,因为@-文件的解析方式会将“ \ n”转换为LF行分隔符。不用@文件也可以完成相同的事情,但是我所知道的唯一方法是需要更复杂的OS依赖语法来定义一个包含所需行分隔符的变量,并使用java命令行扩展该变量。>
如果所需的行分隔符为CRLF,则javaArgs文件将改为读取
-Dline.separator="\r\n"
在较大的程序中,更改整个应用程序的line.separator变量可能是不可接受的。为了避免为整个应用程序设置line.separator,可以使用刚刚讨论过的命令行启动一个单独的Java进程,但是启动该进程以及与该单独的进程进行通信以传输{{ 1}}应该写到流中可能会使该解决方案不受欢迎。
因此,实际上,一个更好的解决方案可能是实现Transformer
来过滤输出流,以将行分隔符转换为所需的行分隔符。此解决方案不会更改变压器本身中使用的行分隔符,并且可能会被视为对变压器的结果进行后处理,因此在某种意义上,它并不是您所要解决的特定问题的答案,但我认为它确实可以提供所需的结果很多开销。这是一个示例,该示例使用FilterWriter
从输出编写器中删除所有CR字符(即回车)。
FilterWriter
解决XML序列化问题的另一种实用解决方案是通过使用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);
}
}
获得Transformer
或通过直接解析为DOM并写出DOM来获取XML的DOM表示形式。 DOMResult
,它为设置行分隔符提供了明确的支持。由于该方法不再使用LSSerializer
,并且在Stack Overflow上还有其他示例,因此在此不再赘述。
但是,可能有用的是回顾Java 11中的更改以及为什么我认为没有其他方法来控制Java的Transformer
默认实现所使用的行分隔符。 Java的Transformer
接口的默认实现使用从Transformer
继承并在同一包中实现的ToXMLStream
类。回顾OpenJDK的提交历史,我发现here已从com.sun.org.apache.xml.internal.serializer.ToStream
更改为here,从读取系统属性中当前定义的src/java.xml/share/classes/com/sun/org/apache/xml/internal/serializer/ToStream.java
属性改为读取line.separator
,这对应于Java虚拟机初始化时的行分隔符。该提交最初是在Java 11中发布的,因此问题中的代码的行为应与Java 8(包括Java 10)相同。
如果您花了一些时间阅读System.lineSeparator()
,因为它在提交后改变了行分隔符的读取方式(可访问here),特别是着眼于第135至140和508至514行,将注意到,序列化程序实现确实支持使用其他行分隔符,实际上,输出属性标识为
ToStream.java
应该是控制使用哪个行分隔符的一种方法。
那么问题中的示例为什么不起作用?答案:在{http://xml.apache.org/xalan}line-separator
接口的当前Java默认实现中,仅用户设置的某些特定属性被传输到序列化程序。这些主要是XSLT规范中定义的属性,但是特殊的Transformer
属性也会被传送。但是,行分隔符的输出属性不是转移到序列化程序的属性之一。
使用indent-amount
在Transformer本身上明确设置的输出属性通过setOutputProperty
的第1029-1128行定义的setOutputProperties
方法(可访问{{3} }。如果改为定义一个显式XSLT转换并使用其com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl
标记来设置输出属性,则首先通过第139-312行中定义的<xsl:output>
方法对传输到序列化程序的属性进行过滤的parseContents
(可访问的here)中进行过滤,并使用com.sun.org.apache.xalan.internal.xsltc.compiler.Output
的第671-715行(可访问的here)中定义的transferOutputSettings
方法再次进行过滤。
总而言之,似乎没有可以在com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
接口的默认Java实现上设置的输出属性来控制它使用的行分隔符。可能还有其他Transformer
实现的提供程序确实提供了对行分隔符的控制,但是我没有Java 11中Transformer
接口的任何实现的经验,只有随附的默认实现OpenJDK版本。