Перейти к основному содержимому

Pretty-Print XML в Java

· 7 мин. чтения

1. Обзор

Когда нам нужно прочитать файл XML вручную, обычно мы хотели бы прочитать содержимое в удобном для печати формате. Многие текстовые редакторы или IDE могут переформатировать XML-документы. Если мы работаем в Linux, мы можем красиво распечатать XML-файлы из командной строки .

Однако иногда у нас есть требования преобразовать необработанную строку XML в красиво напечатанный формат в нашей программе Java. Например, мы можем захотеть показать красиво напечатанный XML-документ в пользовательском интерфейсе для лучшего визуального понимания.

В этом руководстве мы рассмотрим, как красиво печатать XML в Java.

2. Введение в проблему

Для простоты возьмем в качестве входных данных неформатированный файл emails.xml :

<emails> <email> <from>Kai</from> <to>Amanda</to> <time>2018-03-05</time>
<subject>I am flying to you</subject></email> <email>
<from>Jerry</from> <to>Tom</to> <time>1992-08-08</time> <subject>Hey Tom, catch me if you can!</subject>
</email> </emails>

Как мы видим, файл emails.xml имеет правильный формат. Однако читать его нелегко из-за грязного формата.

Наша цель — создать метод для преобразования этой уродливой необработанной XML-строки в красиво отформатированную строку.

Далее мы обсудим настройку двух общих выходных свойств: размер отступа ( целое число ) и подавление объявления XML ( логическое значение ).

Свойство indent-size довольно простое: это количество пробелов для отступа (на уровень). С другой стороны, опция подавления объявления XML решает, хотим ли мы иметь тег объявления XML в сгенерированном XML. Типичное объявление XML выглядит так:

<?xml version="1.0" encoding="UTF-8"?>

В этом руководстве мы рассмотрим решение со стандартным Java API и еще один подход с использованием внешней библиотеки.

Далее давайте посмотрим на них в действии.

3. Прекрасная печать XML с помощью класса Transformer

Java API предоставляет класс Transformer для преобразования XML.

3.1. Использование трансформатора по умолчанию ``

Во-первых, давайте посмотрим на красивое решение с использованием класса Transformer :

public static String prettyPrintByTransformer(String xmlString, int indent, boolean ignoreDeclaration) {

try {
InputSource src = new InputSource(new StringReader(xmlString));
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(src);

TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setAttribute("indent-number", indent);
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, ignoreDeclaration ? "yes" : "no");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");

Writer out = new StringWriter();
transformer.transform(new DOMSource(document), new StreamResult(out));
return out.toString();
} catch (Exception e) {
throw new RuntimeException("Error occurs when pretty-printing xml:\n" + xmlString, e);
}
}

Теперь давайте быстро пройдемся по методу и выясним, как он работает:

  • Сначала мы анализируем необработанную строку XML и получаем объект Document .
  • Затем мы получаем экземпляр TransformerFactory и устанавливаем необходимый атрибут размера отступа.
  • Затем мы можем получить экземпляр преобразователя по умолчанию из настроенного объекта transformerFactory .
  • Объект преобразования поддерживает различные выходные свойства. Чтобы решить, хотим ли мы пропустить объявление, мы устанавливаем атрибут OutputKeys.OMIT_XML_DECLARATION .
  • Поскольку мы хотели бы иметь хорошо отформатированный объект String , наконец, мы преобразуем () проанализированный XML- документ в StringWriter и возвращаем преобразованный String .

Мы установили размер отступа для объекта TransformerFactory в методе выше. В качестве альтернативы мы также можем определить свойство indent-amount для экземпляра трансформатора :

transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(indent));

Далее давайте проверим, работает ли метод должным образом.

3.2. Тестирование метода

Наш Java-проект — это проект Maven, и мы поместили файл emails.xml в src/main/resources/xml/email.xml . Мы создали метод readFromInputStream для чтения входного файла в виде строки . Но мы не будем вдаваться в подробности этого метода, так как он не имеет большого отношения к нашей теме. Допустим, мы хотим установить indent-size=2 и пропустить объявление XML в результате:

public static void main(String[] args) throws IOException {
InputStream inputStream = XmlPrettyPrinter.class.getResourceAsStream("/xml/emails.xml");
String xmlString = readFromInputStream(inputStream);
System.out.println("Pretty printing by Transformer");
System.out.println("=============================================");
System.out.println(prettyPrintByTransformer(xmlString, 2, true));
}

Как показывает основной метод, мы читаем входной файл как строку , а затем вызываем наш метод prettyPrintByTransformer , чтобы получить красиво напечатанную строку XML .

Далее запустим основной метод с Java 8 :

Pretty printing by Transformer
=============================================
<emails>
<email>
<from>Kai</from>
<to>Amanda</to>
<time>2018-03-05</time>
<subject>I am flying to you</subject>
</email>
<email>
<from>Jerry</from>
<to>Tom</to>
<time>1992-08-08</time>
<subject>Hey Tom, catch me if you can!</subject>
</email>
</emails>

Как видно из приведенного выше вывода, наш метод работает так, как ожидалось.

Однако, если мы еще раз проверим его с помощью Java 9 или более поздней версии, мы можем увидеть другой результат.

Далее давайте посмотрим, что получится, если мы запустим его с Java 9 :

Pretty printing by Transformer
=============================================
<emails>

<email>

<from>Kai</from>

<to>Amanda</to>

<time>2018-03-05</time>

<subject>I am flying to you</subject>
</email>

<email>

<from>Jerry</from>

<to>Tom</to>

<time>1992-08-08</time>

<subject>Hey Tom, catch me if you can!</subject>

</email>

</emails>

=============================================

Как видно из приведенного выше вывода, в выводе есть неожиданные пустые строки.

Это связано с тем, что наш необработанный ввод содержит пробелы между элементами, например:

<emails> <email> <from>Kai</from> ...

Начиная с Java 9 функция красивой печати класса Transformer не определяет фактический формат. Следовательно, также будут выводиться узлы, содержащие только пробелы. Это обсуждалось в этом сообщении об ошибке JDK . Кроме того, в примечании к выпуску Java 9 это объясняется в разделе xml/jaxp.

Если мы хотим, чтобы наш метод красивой печати всегда генерировал один и тот же формат в разных версиях Java, нам нужно предоставить файл таблицы стилей.

Затем давайте создадим простой файл xsl для достижения этой цели.

3.3. Предоставление XSLT-файла

Во-первых, давайте создадим файл prettyprint.xsl , чтобы определить выходной формат:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" encoding="UTF-8"/>

<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>

</xsl:stylesheet>

Как мы видим, в файле prettyprint.xsl мы использовали элемент < xsl:strip-space/> для удаления узлов, состоящих только из пробелов, чтобы они не отображались в выводе .

Далее нам все еще нужно внести небольшое изменение в наш метод. Мы больше не будем использовать преобразователь по умолчанию. Вместо этого мы создадим объект Transformer с нашим XSLT-документом :

Transformer transformer = transformerFactory.newTransformer(new StreamSource(new StringReader(readPrettyPrintXslt())));

Здесь метод readPrettyPrintXslt() считывает содержимое prettyprint.xsl .

Теперь, если мы протестируем метод в Java 8 и Java 9, оба выведут один и тот же результат:

Pretty printing by Transformer
=============================================
<emails>
<email>
<from>Kai</from>
<to>Amanda</to>
<time>2018-03-05</time>
<subject>I am flying to you</subject>
</email>
...
</emails>

Мы решили проблему со стандартным Java API. Далее, давайте распечатаем emails.xml , используя внешнюю библиотеку.

4. Прекрасная печать XML с помощью библиотеки Dom4j

Dom4j — популярная XML-библиотека. Это позволяет нам легко печатать документы XML.

Во-первых, давайте добавим зависимость Dom4j в наш pom.xml :

<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>

В качестве примера мы использовали версию 2.1.3. Мы можем найти последнюю версию в репозитории Maven Central.

Далее давайте посмотрим, как красиво напечатать XML с помощью библиотеки Dom4j:

public static String prettyPrintByDom4j(String xmlString, int indent, boolean skipDeclaration) {
try {
OutputFormat format = OutputFormat.createPrettyPrint();
format.setIndentSize(indent);
format.setSuppressDeclaration(skipDeclaration);
format.setEncoding("UTF-8");

org.dom4j.Document document = DocumentHelper.parseText(xmlString);
StringWriter sw = new StringWriter();
XMLWriter writer = new XMLWriter(sw, format);
writer.write(document);
return sw.toString();
} catch (Exception e) {
throw new RuntimeException("Error occurs when pretty-printing xml:\n" + xmlString, e);
}
}

Класс OutputFormat D0m4j предоставляет метод createPrettyPrint для создания предопределенного объекта OutputFormat красивой печати . Как показано в приведенном выше методе, мы можем добавить некоторые настройки в формат красивой печати по умолчанию. В этом случае мы устанавливаем размер отступа и решаем, хотим ли мы включить объявление в результат.

Затем мы анализируем необработанную строку XML и создаем объект XMLWritter с подготовленным экземпляром OutputFormat .

Наконец, объект XMLWriter запишет проанализированный XML-документ в требуемом формате.

Затем давайте проверим, может ли он красиво распечатать файл emails.xml . На этот раз, скажем, мы хотели бы включить объявление и получить отступ размером 8 в результате:

System.out.println("Pretty printing by Dom4j");
System.out.println("=============================================");
System.out.println(prettyPrintByDom4j(xmlString, 8, false));

Когда мы запустим метод, мы увидим вывод:

Pretty printing by Dom4j
=============================================
<?xml version="1.0" encoding="UTF-8"?>

<emails>
<email>
<from>Kai</from>
<to>Amanda</to>
<time>2018-03-05</time>
<subject>I am flying to you</subject>
</email>
<email>
<from>Jerry</from>
<to>Tom</to>
<time>1992-08-08</time>
<subject>Hey Tom, catch me if you can!</subject>
</email>
</emails>

Как видно из приведенного выше вывода, метод решил проблему.

5. Вывод

В этой статье мы рассмотрели два подхода к красивой печати XML-файла в Java.

Мы можем красиво печатать XML, используя стандартный Java API. Однако мы должны помнить, что объект Transformer может давать разные результаты в зависимости от версии Java. Решение состоит в том, чтобы предоставить файл XSLT.

В качестве альтернативы, библиотека Dom4j может решить проблему напрямую.

Как всегда, полная версия кода доступна на GitHub .