Концы строк javax.xml.transform.Transformer больше не относятся к системному свойству "line.separator" - PullRequest
0 голосов
/ 03 декабря 2018

Комментарий на Как управлять окончаниями строк, которые создает javax.xml.transform.Transformer? предлагает установить системное свойство "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?

Вот некоторый демонстрационный код, который печатает "... # 13 # 10 ..." под Windows с Java 11, где он должен печатать "... # 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);
        }
    }
}

1 Ответ

0 голосов
/ 18 мая 2019

Насколько я могу судить, единственный способ, которым вы можете управлять разделителем строк, который стандартная реализация 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.

...