Насколько я могу судить, единственный способ, которым вы можете управлять разделителем строк, который стандартная реализация Java интерфейса Transformer
использует в Java 11, состоит в том, чтобы установить свойство line.separator в командной строке Java.Для простого примера программы здесь вы могли бы сделать это, создав текстовый файл с именем javaArgs, читающий
-Dline.separator="\n"
и выполнив программу с командной строкой
java @javaArgs TestXmlTransformerLineSeparator
Синтаксис @, который былПредставленный в Java 9 полезен здесь, потому что @ -файл анализируется таким образом, что преобразует "\ n" в разделитель строк LF.Можно выполнить то же самое без @ -файла, но единственные известные мне способы требуют более сложного зависящего от ОС синтаксиса для определения переменной, содержащей требуемый разделитель строк, и наличия в командной строке java расширения переменной.
Если требуемый разделитель строк - CRLF, то вместо этого файл javaArgs будет иметь вид
-Dline.separator="\r\n"
В более крупной программе изменение переменной line.separator для всего приложения вполне может быть неприемлемым.Чтобы избежать установки line.separator для всего приложения, можно было бы запустить отдельный процесс Java с только что обсужденной командной строкой, но затрачивались дополнительные затраты на запуск процесса и обмен данными с отдельным процессом для передачи данных, которые Transformer
предполагается, что запись в поток может привести к нежелательному решению.
Таким образом, на самом деле, лучшим решением, вероятно, было бы внедрение FilterWriter
, который фильтрует выходной поток для преобразования разделителя строк в строку.разделитель, который вы хотите.Это решение не меняет разделитель строк, используемый внутри самого трансформатора, и может рассматриваться как пост-обработка результата трансформатора, так что в некотором смысле это не ответ на ваш конкретный вопрос, но я думаю, что он дает желаемый результат безмного накладных расходов.Вот пример, который использует FilterWriter
для удаления всех символов CR (то есть возврата каретки) из модуля записи вывода.
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 - получение DOM.представление XML либо с помощью Transformer
для получения DOMResult
, либо путем прямого анализа в DOM и записи DOM с помощью LSSerializer
, что обеспечивает явную поддержку установки разделителя строк.Так как это отходит от использования Transformer
и есть другие примеры его использования при переполнении стека, я не буду обсуждать его здесь далее.
Что может быть полезным, однако, это рассмотрение того, что изменилось в Java 11, ипочему я думаю, что нет другого способа управления разделителем строк, используемого реализацией Java по умолчанию Transformer
.Реализация Java по умолчанию интерфейса Transformer
использует класс ToXMLStream
, который наследуется от com.sun.org.apache.xml.internal.serializer.ToStream
и реализован в том же пакете.Просматривая историю коммитов OpenJDK, я обнаружил, что src/java.xml/share/classes/com/sun/org/apache/xml/internal/serializer/ToStream.java
был изменен здесь с чтения свойства line.separator
, определенного в системных свойствах, вместо чтения System.lineSeparator()
, что соответствует разделителю строк винициализация виртуальной машины Java.Этот коммит был впервые выпущен в Java 11, поэтому код в вопросе должен вести себя так же, как в Java 8 вплоть до Java 10.
Если вы потратите некоторое время на чтение ToStream.java
в том виде, в каком он существовалпосле коммита, который изменил способ чтения разделителя строк (доступно здесь ), особенно с фокусом на строки 135-140 и 508-514, вы заметите, что реализация сериализатора поддерживает использование других разделителей строк, и вФактически, свойство output, обозначенное как
{http://xml.apache.org/xalan}line-separator
, должно быть способом управления тем, какой разделитель строк используется.
Почему тогда пример в вопросе не работает?Ответ: В текущей реализации Java по умолчанию интерфейса Transformer
только несколько определенных свойств, которые пользователь устанавливает, передаются в сериализатор.Это прежде всего свойства, которые определены в спецификации XSLT, но также передается специальное свойство indent-amount
.Свойство вывода разделителя строк, однако, не является одним из свойств, которые передаются в сериализатор.
Свойства вывода, которые явно установлены на самом трансформаторе с использованием setOutputProperty
, передаются в сериализатор с помощью setOutputProperties
метод, определенный в строках 1029-1128 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl
(доступно здесь ).Если вместо этого вы определяете явное преобразование XSLT и используете его тег <xsl:output>
для установки выходных свойств, свойства, которые передаются в сериализатор, фильтруются в первую очередь методом parseContents
, определенным в строках 139-312 com.sun.org.apache.xalan.internal.xsltc.compiler.Output
(доступно здесь ) и снова отфильтровано методом transferOutputSettings
, определенным в строках 671-715 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
(доступно здесь ).
Итак, подведем итог,похоже, что нет выходного свойства, которое вы можете установить в реализации Java по умолчанию интерфейса Transformer
для управления разделителями строк, которые он использует.Вполне могут быть и другие поставщики реализаций Transformer
, которые обеспечивают управление разделителем строк, но у меня нет опыта работы с любой реализацией интерфейса Transformer
в Java 11, кроме реализации по умолчанию, которая предоставляется в выпуске OpenJDK.