Как красиво распечатать XML с Java?

У меня есть строка Java, содержащая XML, без перевода строки или отступов. Я хотел бы превратить его в строку с хорошо отформатированным XML. Как мне это сделать?

String unformattedXml = "<tag><nested>hello</nested></tag>";
String formattedXml = new [UnknownClass]().format(unformattedXml);

Примечание. Я ввел строку. Мой вывод - Строка.

(Базовый) фиктивный результат:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <tag>
    <nested>hello</nested>
  </tag>
</root>

person Steve McLeod    schedule 26.09.2008    source источник
comment
проверьте этот вопрос: stackoverflow.com/questions/1264849/   -  person dfa    schedule 12.08.2009
comment
Просто любопытно, вы отправляете этот вывод в файл XML или что-то еще, где отступы действительно имеют значение? Некоторое время назад я был очень обеспокоен форматированием своего XML, чтобы он правильно отображался ... но потратив на это кучу времени, я понял, что мне нужно отправить свой вывод в веб-браузер и любой относительно современный веб-браузер. фактически отобразит XML в красивой древовидной структуре, так что я могу забыть об этой проблеме и двигаться дальше. Я упоминаю об этом на тот случай, если вы (или другой пользователь с той же проблемой) могли упустить ту же деталь.   -  person Abel Morelos    schedule 06.10.2010
comment
@Abel, сохранение в текстовые файлы, вставка в текстовые поля HTML и выгрузка в консоль для целей отладки.   -  person Steve McLeod    schedule 07.10.2010
comment
отложено как слишком широкое - трудно быть более точным, чем вопрос в настоящее время!   -  person Steve McLeod    schedule 16.07.2018


Ответы (34)


Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
// initialize StreamResult with File object to save to file
StreamResult result = new StreamResult(new StringWriter());
DOMSource source = new DOMSource(doc);
transformer.transform(source, result);
String xmlString = result.getWriter().toString();
System.out.println(xmlString);

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

person Lorenzo Boccaccia    schedule 26.09.2008
comment
Как сделать так, чтобы на выходе не было <?xml version="1.0" encoding="UTF-8"?>? - person Thang Pham; 19.07.2011
comment
Чтобы опустить объявление <?xml ...>, добавьте transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes") - person rustyx; 25.08.2015
comment
Обычным читателям может быть полезна улучшенная версия описанного здесь решения (stackoverflow.com/a/33541820/363573). - person Stephan; 05.11.2015
comment
где определяется doc? - person Florian F; 26.10.2017
comment
это переменная типа Document, которая содержит ваш xml для преобразования, у вас должен быть он где-то в вашем коде - person Lorenzo Boccaccia; 26.10.2017
comment
Это не отвечает на мой вопрос: как отформатировать строку, содержащую XML? В этом ответе уже предполагается, что вы каким-то образом преобразовали объект String в другой объект. - person Steve McLeod; 11.07.2018
comment
Это не сайт для домашних заданий / фриланса, и решение охватывает наиболее важный аспект проблемы. - person Lorenzo Boccaccia; 09.10.2019
comment
Это решение удаляет CDATA обертки. Есть ли флаг, чтобы предотвратить это? - person Marinos An; 21.07.2020
comment
doc можно получить так: DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); InputSource is = new InputSource(new StringReader(in)); Document doc = db.parse(is); - person PunchyRascal; 18.09.2020

Вот ответ на мой вопрос. Я объединил ответы из различных результатов, чтобы написать класс, который хорошо печатает XML.

Никаких гарантий относительно того, как он ответит неверным XML или большими документами.

package ecb.sdw.pretty;

import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;

/**
 * Pretty-prints xml, supplied as a string.
 * <p/>
 * eg.
 * <code>
 * String formattedXml = new XmlFormatter().format("<tag><nested>hello</nested></tag>");
 * </code>
 */
public class XmlFormatter {

    public XmlFormatter() {
    }

    public String format(String unformattedXml) {
        try {
            final Document document = parseXmlFile(unformattedXml);

            OutputFormat format = new OutputFormat(document);
            format.setLineWidth(65);
            format.setIndenting(true);
            format.setIndent(2);
            Writer out = new StringWriter();
            XMLSerializer serializer = new XMLSerializer(out, format);
            serializer.serialize(document);

            return out.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private Document parseXmlFile(String in) {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            InputSource is = new InputSource(new StringReader(in));
            return db.parse(is);
        } catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        } catch (SAXException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        String unformattedXml =
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?><QueryMessage\n" +
                        "        xmlns=\"http://www.SDMX.org/resources/SDMXML/schemas/v2_0/message\"\n" +
                        "        xmlns:query=\"http://www.SDMX.org/resources/SDMXML/schemas/v2_0/query\">\n" +
                        "    <Query>\n" +
                        "        <query:CategorySchemeWhere>\n" +
                        "   \t\t\t\t\t         <query:AgencyID>ECB\n\n\n\n</query:AgencyID>\n" +
                        "        </query:CategorySchemeWhere>\n" +
                        "    </Query>\n\n\n\n\n" +
                        "</QueryMessage>";

        System.out.println(new XmlFormatter().format(unformattedXml));
    }

}
person Steve McLeod    schedule 26.09.2008
comment
Просто обратите внимание, что этот ответ требует использования Xerces. Если вы не хотите добавлять эту зависимость, вы можете просто использовать стандартные библиотеки jdk и javax.xml.transform.Transformer (см. Мой ответ ниже) - person khylo; 17.12.2010
comment
В 2008 году это был хороший ответ, но теперь все это можно сделать с помощью стандартных классов JDK, а не классов Apache. См. xerces.apache.org/xerces2-j/faq-general .html # faq-6. Да, это FAQ Xerces, но ответ касается стандартных классов JDK. В первоначальной реализации этих классов в версии 1.5 было много проблем, но начиная с версии 1.6 все работает нормально. Скопируйте пример LSSerializer из FAQ, отрежьте бит ... и добавьте writer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE); после строки LSSerializer writer = .... - person George Hawkins; 04.05.2011
comment
Я создал небольшой класс, используя пример Apache, на который @GeorgeHawkins дал ссылку. Не хватало способа инициализации переменной document, поэтому я подумал, что могу добавить замедление и сделать из этого быстрый пример. Сообщите мне, если мне нужно что-то изменить, pastebin.com/XL7932aC - person samwell; 16.07.2012
comment
неверно, что вы можете сделать это только с помощью jdk. по крайней мере, не надежно. это зависит от некоторой внутренней реализации реестра, которая по умолчанию не активна с моим jdk7u72. так что вам все равно лучше использовать материал apache напрямую. - person user1050755; 17.11.2014
comment
Вот решение без каких-либо зависимостей: stackoverflow.com/a/33541820/363573. - person Stephan; 05.11.2015
comment
На самом деле я поддерживаю проект 2008 года LOL, спасибо! - person MewX; 05.10.2018
comment
проблемы с ут-8 - person Alberto Acuña; 27.06.2019
comment
Чтобы избежать проверки xml и связанных накладных расходов, используйте builder.setEntityResolver((publicId, systemId) -> {return new InputSource(new StringReader(""));}); - person Olivier Faucheux; 01.12.2020

более простое решение на основе на этот ответ:

public static String prettyFormat(String input, int indent) {
    try {
        Source xmlInput = new StreamSource(new StringReader(input));
        StringWriter stringWriter = new StringWriter();
        StreamResult xmlOutput = new StreamResult(stringWriter);
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        transformerFactory.setAttribute("indent-number", indent);
        transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
        Transformer transformer = transformerFactory.newTransformer(); 
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.transform(xmlInput, xmlOutput);
        return xmlOutput.getWriter().toString();
    } catch (Exception e) {
        throw new RuntimeException(e); // simple exception handling, please review it
    }
}

public static String prettyFormat(String input) {
    return prettyFormat(input, 2);
}

прецедент:

prettyFormat("<root><child>aaa</child><child/></root>");

возвращает:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <child>aaa</child>
  <child/>
</root>

// Игнорировать: в исходном редактировании просто нужно пропустить s в имени класса в коде. добавлены шесть избыточных символов для проверки более 6 символов на SO

person dfa    schedule 12.08.2009
comment
Это код, который я всегда использовал, но в этой компании он не работал, я предполагаю, что они используют другую библиотеку преобразования XML. Я создал фабрику отдельной строкой, потом сделал factory.setAttribute("indent-number", 4);, и теперь она работает. - person Adrian Smith; 21.10.2010
comment
Как сделать так, чтобы на выходе не было <?xml version="1.0" encoding="UTF-8"?>? - person Thang Pham; 19.07.2011
comment
@ Гарри: transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - person jjmontes; 07.10.2011
comment
Привет, я использую этот точный код и правильно форматирую, за исключением первого элемента. Итак, это: <?xml version="1.0" encoding="UTF-8"?><root> находится в одной строке. Есть идеи, почему? - person CodyK; 10.03.2015
comment
@dfa: понравился комментарий // simple exception handling, please review it. Можете ли вы указать на некоторые ресурсы, которые рекомендуют этот тип обработки исключений? Спасибо. - person John; 23.09.2015
comment
@Codemiester: похоже, ошибка (см. stackoverflow.com/a/18251901/3375325). Добавление transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes"); сработало для меня. - person jansohn; 10.08.2016
comment
В Java 1.8 он ничего не делает (лишние пробелы не удаляются, отступы / новые строки не добавляются - person azis.mrazish; 17.01.2020
comment
Этот фрагмент кода уязвим для внедрения XML eXternal Entity Injection (XXE). См .: cheatsheetseries.owasp.org/cheatsheets/ - person fanbondi; 09.02.2021

Сейчас 2012 год, и Java может делать больше, чем раньше, с XML, я хотел бы добавить альтернативу моему принятому ответу. Это не имеет зависимостей вне Java 6.

import org.w3c.dom.Node;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;

/**
 * Pretty-prints xml, supplied as a string.
 * <p/>
 * eg.
 * <code>
 * String formattedXml = new XmlFormatter().format("<tag><nested>hello</nested></tag>");
 * </code>
 */
public class XmlFormatter {

    public String format(String xml) {

        try {
            final InputSource src = new InputSource(new StringReader(xml));
            final Node document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(src).getDocumentElement();
            final Boolean keepDeclaration = Boolean.valueOf(xml.startsWith("<?xml"));

        //May need this: System.setProperty(DOMImplementationRegistry.PROPERTY,"com.sun.org.apache.xerces.internal.dom.DOMImplementationSourceImpl");


            final DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
            final DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
            final LSSerializer writer = impl.createLSSerializer();

            writer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE); // Set this to true if the output needs to be beautified.
            writer.getDomConfig().setParameter("xml-declaration", keepDeclaration); // Set this to true if the declaration is needed to be outputted.

            return writer.writeToString(document);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        String unformattedXml =
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?><QueryMessage\n" +
                        "        xmlns=\"http://www.SDMX.org/resources/SDMXML/schemas/v2_0/message\"\n" +
                        "        xmlns:query=\"http://www.SDMX.org/resources/SDMXML/schemas/v2_0/query\">\n" +
                        "    <Query>\n" +
                        "        <query:CategorySchemeWhere>\n" +
                        "   \t\t\t\t\t         <query:AgencyID>ECB\n\n\n\n</query:AgencyID>\n" +
                        "        </query:CategorySchemeWhere>\n" +
                        "    </Query>\n\n\n\n\n" +
                        "</QueryMessage>";

        System.out.println(new XmlFormatter().format(unformattedXml));
    }
}
person Steve McLeod    schedule 17.07.2012
comment
Без отступа, но он работает с этим: System.setProperty (DOMImplementationRegistry.PROPERTY, com.sun.org.apache.xerces.internal.dom.DOMImplementationSourceImpl); - person ggb667; 20.03.2013
comment
Я попробовал это решение и обнаружил, что возвращаемая строка XML всегда имеет UTF-16. Когда документ сериализуется обратно в строку, появляется такая строка кода: ser._format.setEncoding("UTF-16"); Сейчас это может быть некоторым стандартом, но для системы, с которой я работаю, используется UTF-8. Кто-нибудь знает, как сохранить кодировку из исходной строки XML? - person Dan Temple; 25.02.2014
comment
@DanTemple Похоже, вам нужно использовать LSOutput для управления кодировкой. См. chipkillmar.net/2009/03/ 25 / pretty-print-xml-from-a-dom. - person Joshua Davis; 12.03.2014
comment
@JoshuaDavis Спасибо, это работает для установки кодировки ответа. Мне нужно добавить что-то, что передает исходную кодировку красивому принтеру, если я хочу сохранить кодировку исходной строки XML. - person Dan Temple; 13.03.2014
comment
@JoshA. Есть однострочный код с использованием XMLBeam: System.out.println(new XBProjector().projectXMLString("<xml><foo><bar/></foo></xml>", DOMAccess.class).asString()); - person Cfx; 27.10.2014
comment
Я обнаружил, что это переносит строки на определенную длину - есть ли способ предотвратить перенос? - person eeijlar; 05.02.2015
comment
это добавляет спецификацию FEFF в начало возвращаемой строки. - person Marcus Junius Brutus; 08.03.2015
comment
Я пытался использовать это в Andriod, но не могу найти пакет `DOMImplementationRegistry. Я использую java 8. - person Chintan Soni; 10.05.2015
comment
У меня возникли проблемы с этим фрагментом, связанные с экранированными значениями атрибутов. Управляющая последовательность «меньше чем» &lt; была правильно сохранена, в то время как последовательность «больше чем» &gt; была преобразована в фактический знак >. - person Julian Sievers; 20.01.2016
comment
да, эта работа на jdk8 без исключения вместо следующего рейтингового ответа. - person Yura; 18.05.2016
comment
Чтобы установить кодировку: LSOutput output = impl.createLSOutput (); output.setEncoding (UTF-8); output.setByteStream (новый ByteArrayOutputStream ()); Writer.write (документ, вывод); вернуть output.getByteStream (). toString (); - person Ali Cheaito; 01.09.2017
comment
Не работал над JDK 8, развернутым в JBoss EAP 7 / Wildfly 10, получить класс не найден исключения com.sun.org.apache.xerces.internal.dom.DOMXSImplementationSourceImpl - похоже, связано с этим issues.jboss.org/browse/WFLY-4416 - но я не собираюсь заниматься добавлением дополнительной библиотеки или чего-то еще - мне не нужно, чтобы это было так плохо напечатано. - person JGlass; 12.07.2018
comment
спасибо, что вы также включили список импорта, так много конфликтующих пакетов доступно, чтобы понять комбинацию, необходимую в противном случае. - person Leon; 28.03.2019

Просто обратите внимание, что ответ с самым высоким рейтингом требует использования xerces.

Если вы не хотите добавлять эту внешнюю зависимость, вы можете просто использовать стандартные библиотеки jdk (которые на самом деле построены с использованием xerces внутри).

N.B. Ошибка jdk версии 1.5 см. http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6296446, но теперь проблема решена.,

(Обратите внимание, что при возникновении ошибки будет возвращен исходный текст)

package com.test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.stream.StreamResult;

import org.xml.sax.InputSource;

public class XmlTest {
    public static void main(String[] args) {
        XmlTest t = new XmlTest();
        System.out.println(t.formatXml("<a><b><c/><d>text D</d><e value='0'/></b></a>"));
    }

    public String formatXml(String xml){
        try{
            Transformer serializer= SAXTransformerFactory.newInstance().newTransformer();
            serializer.setOutputProperty(OutputKeys.INDENT, "yes");
            //serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            //serializer.setOutputProperty("{http://xml.customer.org/xslt}indent-amount", "2");
            Source xmlSource=new SAXSource(new InputSource(new ByteArrayInputStream(xml.getBytes())));
            StreamResult res =  new StreamResult(new ByteArrayOutputStream());            
            serializer.transform(xmlSource, res);
            return new String(((ByteArrayOutputStream)res.getOutputStream()).toByteArray());
        }catch(Exception e){
            //TODO log error
            return xml;
        }
    }

}
person khylo    schedule 17.12.2010
comment
В этом случае левая вкладка не используется. Все теги начинаются с первого символа строки, как и обычный текст. - person Ruslan; 23.12.2010
comment
разве вам не нужно указывать кодировку при преобразовании назад и вперед между байтами и строкой? - person Will Glass; 02.12.2011
comment
Не должно быть необходимости конвертировать из и в байтовые массивы / String. По крайней мере, при этом вам нужно будет указать кодировку. Лучшим вариантом было бы использовать классы StringReader и StringWriter, заключенные в InputSource и StreamResult. - person maximdim; 21.12.2012
comment
не работает. вам нужно повозиться с реализацией внутреннего реестра. - person user1050755; 17.11.2014
comment
Вот более простой вариант этого решения: stackoverflow.com/a/33541820/363573 - person Stephan; 05.11.2015

Раньше я довольно много печатал, используя метод org.dom4j.io.OutputFormat.createPrettyPrint ()

public String prettyPrint(final String xml){  

    if (StringUtils.isBlank(xml)) {
        throw new RuntimeException("xml was null or blank in prettyPrint()");
    }

    final StringWriter sw;

    try {
        final OutputFormat format = OutputFormat.createPrettyPrint();
        final org.dom4j.Document document = DocumentHelper.parseText(xml);
        sw = new StringWriter();
        final XMLWriter writer = new XMLWriter(sw, format);
        writer.write(document);
    }
    catch (Exception e) {
        throw new RuntimeException("Error pretty printing xml:\n" + xml, e);
    }
    return sw.toString();
}
person mlo55    schedule 03.11.2008
comment
В моем случае в принятом решении не выполняется правильный отступ для вложенных тегов, а в этом случае. - person Chase Seibert; 06.11.2008
comment
Я использовал это вместе с удалением всех конечных пробелов в конце строк: prettyPrintedString.replaceAll("\\s+\n", "\n") - person jediz; 05.09.2017

Вот способ сделать это с помощью dom4j:

Импорт:

import org.dom4j.Document;  
import org.dom4j.DocumentHelper;  
import org.dom4j.io.OutputFormat;  
import org.dom4j.io.XMLWriter;

Код:

String xml = "<your xml='here'/>";  
Document doc = DocumentHelper.parseText(xml);  
StringWriter sw = new StringWriter();  
OutputFormat format = OutputFormat.createPrettyPrint();  
XMLWriter xw = new XMLWriter(sw, format);  
xw.write(doc);  
String result = sw.toString();
person Mark Pope    schedule 08.04.2010
comment
У меня это не сработало. Он просто выдал что-то вроде: <?xml version... в одной строке, а все остальное в другой строке. - person sixtyfootersdude; 04.02.2012

Поскольку вы начинаете с String, вам нужно скрыться до объекта DOM (например, Node), прежде чем вы сможете использовать Transformer. Однако, если вы знаете, что ваша XML-строка действительна, и вы не хотите нести накладные расходы памяти на синтаксический анализ строки в DOM, а затем запускать преобразование через DOM для возврата строки - вы можете просто сделать несколько старомодных посимвольный разбор. Вставляйте новую строку и пробелы после каждых </...> символов, сохраняйте счетчик отступов (для определения количества пробелов), который вы увеличиваете для каждого <...> и уменьшаете для каждого </...>, которое вы видите.

Заявление об ограничении ответственности - я вырезал / вставил / отредактировал следующие функции, чтобы они не компилировались как есть.

public static final Element createDOM(String strXML) 
    throws ParserConfigurationException, SAXException, IOException {

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setValidating(true);
    DocumentBuilder db = dbf.newDocumentBuilder();
    InputSource sourceXML = new InputSource(new StringReader(strXML));
    Document xmlDoc = db.parse(sourceXML);
    Element e = xmlDoc.getDocumentElement();
    e.normalize();
    return e;
}

public static final void prettyPrint(Node xml, OutputStream out)
    throws TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException {
    Transformer tf = TransformerFactory.newInstance().newTransformer();
    tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
    tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
    tf.setOutputProperty(OutputKeys.INDENT, "yes");
    tf.transform(new DOMSource(xml), new StreamResult(out));
}
person Kevin Hakanson    schedule 26.09.2008
comment
Однако, если вы знаете, что ваша строка XML действительна ... хороший аргумент. Смотрите мое решение, основанное на этом подходе ниже. - person David Easley; 27.05.2010

Кевин Хакансон сказал: «Однако, если вы знаете, что ваша XML-строка действительна, и вы не хотите нести накладные расходы на память, связанные с синтаксическим анализом строки в DOM, а затем запуском преобразования над DOM для возврата строки - вы могли бы просто сделайте какой-нибудь старомодный символ путем синтаксического анализа символов. Вставьте новую строку и пробелы после каждого символа, сохраните счетчик отступов (для определения количества пробелов), который вы увеличиваете для каждого ‹...> и уменьшаете для каждого видимого вами."

Согласовано. Такой подход намного быстрее и имеет гораздо меньше зависимостей.

Пример решения:

/**
 * XML utils, including formatting.
 */
public class XmlUtils
{
  private static XmlFormatter formatter = new XmlFormatter(2, 80);

  public static String formatXml(String s)
  {
    return formatter.format(s, 0);
  }

  public static String formatXml(String s, int initialIndent)
  {
    return formatter.format(s, initialIndent);
  }

  private static class XmlFormatter
  {
    private int indentNumChars;
    private int lineLength;
    private boolean singleLine;

    public XmlFormatter(int indentNumChars, int lineLength)
    {
      this.indentNumChars = indentNumChars;
      this.lineLength = lineLength;
    }

    public synchronized String format(String s, int initialIndent)
    {
      int indent = initialIndent;
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < s.length(); i++)
      {
        char currentChar = s.charAt(i);
        if (currentChar == '<')
        {
          char nextChar = s.charAt(i + 1);
          if (nextChar == '/')
            indent -= indentNumChars;
          if (!singleLine)   // Don't indent before closing element if we're creating opening and closing elements on a single line.
            sb.append(buildWhitespace(indent));
          if (nextChar != '?' && nextChar != '!' && nextChar != '/')
            indent += indentNumChars;
          singleLine = false;  // Reset flag.
        }
        sb.append(currentChar);
        if (currentChar == '>')
        {
          if (s.charAt(i - 1) == '/')
          {
            indent -= indentNumChars;
            sb.append("\n");
          }
          else
          {
            int nextStartElementPos = s.indexOf('<', i);
            if (nextStartElementPos > i + 1)
            {
              String textBetweenElements = s.substring(i + 1, nextStartElementPos);

              // If the space between elements is solely newlines, let them through to preserve additional newlines in source document.
              if (textBetweenElements.replaceAll("\n", "").length() == 0)
              {
                sb.append(textBetweenElements + "\n");
              }
              // Put tags and text on a single line if the text is short.
              else if (textBetweenElements.length() <= lineLength * 0.5)
              {
                sb.append(textBetweenElements);
                singleLine = true;
              }
              // For larger amounts of text, wrap lines to a maximum line length.
              else
              {
                sb.append("\n" + lineWrap(textBetweenElements, lineLength, indent, null) + "\n");
              }
              i = nextStartElementPos - 1;
            }
            else
            {
              sb.append("\n");
            }
          }
        }
      }
      return sb.toString();
    }
  }

  private static String buildWhitespace(int numChars)
  {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < numChars; i++)
      sb.append(" ");
    return sb.toString();
  }

  /**
   * Wraps the supplied text to the specified line length.
   * @lineLength the maximum length of each line in the returned string (not including indent if specified).
   * @indent optional number of whitespace characters to prepend to each line before the text.
   * @linePrefix optional string to append to the indent (before the text).
   * @returns the supplied text wrapped so that no line exceeds the specified line length + indent, optionally with
   * indent and prefix applied to each line.
   */
  private static String lineWrap(String s, int lineLength, Integer indent, String linePrefix)
  {
    if (s == null)
      return null;

    StringBuilder sb = new StringBuilder();
    int lineStartPos = 0;
    int lineEndPos;
    boolean firstLine = true;
    while(lineStartPos < s.length())
    {
      if (!firstLine)
        sb.append("\n");
      else
        firstLine = false;

      if (lineStartPos + lineLength > s.length())
        lineEndPos = s.length() - 1;
      else
      {
        lineEndPos = lineStartPos + lineLength - 1;
        while (lineEndPos > lineStartPos && (s.charAt(lineEndPos) != ' ' && s.charAt(lineEndPos) != '\t'))
          lineEndPos--;
      }
      sb.append(buildWhitespace(indent));
      if (linePrefix != null)
        sb.append(linePrefix);

      sb.append(s.substring(lineStartPos, lineEndPos + 1));
      lineStartPos = lineEndPos + 1;
    }
    return sb.toString();
  }

  // other utils removed for brevity
}
person David Easley    schedule 27.05.2010
comment
Так и должно быть. Форматирование на лету на строковом уровне. Это единственное решение, которое отформатирует недопустимый или неполный XML. - person Florian F; 26.10.2017

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

Было указано, что и ввод, и вывод должны быть строками, поэтому вот служебный метод, который делает именно это, реализованный с помощью XOM библиотека:

import nu.xom.*;
import java.io.*;

[...]

public static String format(String xml) throws ParsingException, IOException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    Serializer serializer = new Serializer(out);
    serializer.setIndent(4);  // or whatever you like
    serializer.write(new Builder().build(xml, ""));
    return out.toString("UTF-8");
}

Я проверил, что он работает, и результаты не зависят от вашей версии JRE или чего-то подобного. Чтобы узнать, как настроить выходной формат по своему вкусу, ознакомьтесь с _2 _ API.

На самом деле это получилось дольше, чем я думал - потребовалось несколько дополнительных строк, потому что Serializer хочет OutputStream для записи. Но обратите внимание, что здесь очень мало кода для реального тидлинга XML.

(Этот ответ является частью моей оценки XOM, которая была предлагается в качестве одного из вариантов в моем вопрос о лучшей библиотеке Java XML для замены dom4j. Для справки, с dom4j вы можете добиться этого с такой же легкостью, используя _ 5_ и _ 6_. Изменить: ... как показано в ответ mlo55.)

person Jonik    schedule 07.06.2009
comment
Спасибо, это то, что я искал. Если у вас есть XML, уже проанализированный с помощью XOM в объекте Document, вы можете передать его напрямую в serializer.write (document); - person Thibault D.; 13.08.2013

Хммм ... столкнулся с чем-то вроде этого, и это известная ошибка ... просто добавьте это OutputProperty ..

transformer.setOutputProperty(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "8");

Надеюсь это поможет ...

person Sandeep Phukan    schedule 22.12.2009
comment
Откуда этот OutputPropertiesFactory? - person helenov; 20.10.2016
comment
import com.sun.org.apache.xml.internal.serializer. *; - person Gaurav; 07.02.2020

Относительно комментария о том, что «сначала вы должны построить дерево DOM»: Нет, вам не нужно и не следует этого делать.

Вместо этого создайте StreamSource (new StreamSource (new StringReader (str)) и передайте его упомянутому преобразователю идентичности. Он будет использовать синтаксический анализатор SAX, и результат будет намного быстрее. Построение промежуточного дерева в этом случае является чистыми накладными расходами. В противном случае лучший ответ - это хорошо.

person StaxMan    schedule 26.02.2009
comment
Я полностью согласен: построение промежуточного дерева DOM - пустая трата памяти. Спасибо за такой ответ. - person Florian F; 26.10.2017

Используя scala:

import xml._
val xml = XML.loadString("<tag><nested>hello</nested></tag>")
val formatted = new PrettyPrinter(150, 2).format(xml)
println(formatted)

Вы можете сделать это и на Java, если вы зависите от scala-library.jar. Это выглядит так:

import scala.xml.*;

public class FormatXML {
    public static void main(String[] args) {
        String unformattedXml = "<tag><nested>hello</nested></tag>";
        PrettyPrinter pp = new PrettyPrinter(150, 3);
        String formatted = pp.format(XML.loadString(unformattedXml), TopScope$.MODULE$);
        System.out.println(formatted);
    }
}

Объект PrettyPrinter состоит из двух целых чисел, первое из которых является максимальной длиной строки, а второе - шагом отступа.

person Synesso    schedule 08.03.2011

немного улучшенная версия от milosmns ...

public static String getPrettyXml(String xml) {
    if (xml == null || xml.trim().length() == 0) return "";

    int stack = 0;
    StringBuilder pretty = new StringBuilder();
    String[] rows = xml.trim().replaceAll(">", ">\n").replaceAll("<", "\n<").split("\n");

    for (int i = 0; i < rows.length; i++) {
        if (rows[i] == null || rows[i].trim().length() == 0) continue;

        String row = rows[i].trim();
        if (row.startsWith("<?")) {
            pretty.append(row + "\n");
        } else if (row.startsWith("</")) {
            String indent = repeatString(--stack);
            pretty.append(indent + row + "\n");
        } else if (row.startsWith("<") && row.endsWith("/>") == false) {
            String indent = repeatString(stack++);
            pretty.append(indent + row + "\n");
            if (row.endsWith("]]>")) stack--;
        } else {
            String indent = repeatString(stack);
            pretty.append(indent + row + "\n");
        }
    }

    return pretty.toString().trim();
}

private static String repeatString(int stack) {
     StringBuilder indent = new StringBuilder();
     for (int i = 0; i < stack; i++) {
        indent.append(" ");
     }
     return indent.toString();
} 
person codeskraps    schedule 09.07.2014
comment
где repeatString (stack ++); метод ..? - person user1912935; 25.06.2015
comment
частный статический String repeatString (int stack) {StringBuilder indent = new StringBuilder (); для (int я = 0; я ‹стек; я ++) {indent.append (); } return indent.toString (); } - person codeskraps; 25.06.2015
comment
Отступы в закрывающих тегах работают некорректно. Вам нужно изменить } else if (row.startsWith("</")) { часть на эту: else if (row.startsWith("</")) { String indent = repeatIdent(--stack); if (pretty.charAt(pretty.length() - 1) == '\n') { pretty.append(indent + row + "\n"); } else { pretty.append(row + "\n"); } } - person Csaba Tenkes; 28.12.2018

Просто для справки в будущем вот решение, которое сработало для меня (благодаря комментарию, который @George Hawkins опубликовал в одном из ответов):

DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
LSSerializer writer = impl.createLSSerializer();
writer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE);
LSOutput output = impl.createLSOutput();
ByteArrayOutputStream out = new ByteArrayOutputStream();
output.setByteStream(out);
writer.write(document, output);
String xmlStr = new String(out.toByteArray());
person Michael    schedule 10.10.2011

Если вы уверены, что у вас есть допустимый XML, этот простой и позволяет избежать деревьев XML DOM. Возможно, есть какие-то ошибки, комментируйте, если что-то видите

public String prettyPrint(String xml) {
            if (xml == null || xml.trim().length() == 0) return "";

            int stack = 0;
            StringBuilder pretty = new StringBuilder();
            String[] rows = xml.trim().replaceAll(">", ">\n").replaceAll("<", "\n<").split("\n");

            for (int i = 0; i < rows.length; i++) {
                    if (rows[i] == null || rows[i].trim().length() == 0) continue;

                    String row = rows[i].trim();
                    if (row.startsWith("<?")) {
                            // xml version tag
                            pretty.append(row + "\n");
                    } else if (row.startsWith("</")) {
                            // closing tag
                            String indent = repeatString("    ", --stack);
                            pretty.append(indent + row + "\n");
                    } else if (row.startsWith("<")) {
                            // starting tag
                            String indent = repeatString("    ", stack++);
                            pretty.append(indent + row + "\n");
                    } else {
                            // tag data
                            String indent = repeatString("    ", stack);
                            pretty.append(indent + row + "\n");
                    }
            }

            return pretty.toString().trim();
    }
person milosmns    schedule 12.02.2014
comment
где метод repeatString ..? - person user1912935; 25.06.2015
comment
частный статический String repeatString (int stack) {StringBuilder indent = new StringBuilder (); для (int я = 0; я ‹стек; я ++) {indent.append (); } return indent.toString (); } - person codeskraps; 25.06.2015
comment
Да [user1912935], то, что написал @codeskraps, должно быть достаточно простым :) - person milosmns; 25.06.2015
comment
Конкатенация со StringBuilder внутри цикла: плохая практика. - person james.garriss; 22.12.2015
comment
@ james.garriss Но очень легко разделить на новые строки, это просто иллюстрирует простой подход без каких-либо деревьев DOM. - person milosmns; 30.11.2018

Все вышеперечисленные решения не сработали для меня, тогда я нашел этот http://myshittycode.com/2014/02/10/java-properly-indenting-xml-string/

Подсказка - удалить пробелы с помощью XPath

    String xml = "<root>" +
             "\n   " +
             "\n<name>Coco Puff</name>" +
             "\n        <total>10</total>    </root>";

try {
    Document document = DocumentBuilderFactory.newInstance()
            .newDocumentBuilder()
            .parse(new InputSource(new ByteArrayInputStream(xml.getBytes("utf-8"))));

    XPath xPath = XPathFactory.newInstance().newXPath();
    NodeList nodeList = (NodeList) xPath.evaluate("//text()[normalize-space()='']",
                                                  document,
                                                  XPathConstants.NODESET);

    for (int i = 0; i < nodeList.getLength(); ++i) {
        Node node = nodeList.item(i);
        node.getParentNode().removeChild(node);
    }

    Transformer transformer = TransformerFactory.newInstance().newTransformer();
    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

    StringWriter stringWriter = new StringWriter();
    StreamResult streamResult = new StreamResult(stringWriter);

    transformer.transform(new DOMSource(document), streamResult);

    System.out.println(stringWriter.toString());
}
catch (Exception e) {
    e.printStackTrace();
}
person Georgy Gobozov    schedule 13.05.2015
comment
Обратите внимание, что использование свойства '{xml.apache.org/xslt} indent-amount' приведет к привяжет вас к конкретной реализации трансформатора. - person vallismortis; 17.06.2015
comment
Из всех решений это сработало лучше всего. В моем XML уже были пробелы и новые строки, к тому же я не хотел добавлять больше зависимостей в свой проект. Мне жаль, что мне не пришлось разбирать XML, ну да ладно. - person Fabio; 10.03.2016

Этот код ниже работает отлично

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

String formattedXml1 = prettyFormat("<root><child>aaa</child><child/></root>");

public static String prettyFormat(String input) {
    return prettyFormat(input, "2");
}

public static String prettyFormat(String input, String indent) {
    Source xmlInput = new StreamSource(new StringReader(input));
    StringWriter stringWriter = new StringWriter();
    try {
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", indent);
        transformer.transform(xmlInput, new StreamResult(stringWriter));

        String pretty = stringWriter.toString();
        pretty = pretty.replace("\r\n", "\n");
        return pretty;              
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
person maks tkach    schedule 02.06.2016

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

    public static void main(String[] args) throws Exception {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setValidating(false);
    DocumentBuilder db = dbf.newDocumentBuilder();
    Document doc = db.parse(new FileInputStream(new File("C:/Users/xyz.xml")));
    prettyPrint(doc);

}

private static String prettyPrint(Document document)
        throws TransformerException {
    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(OutputKeys.ENCODING, "UTF-8");
    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
    DOMSource source = new DOMSource(document);
    StringWriter strWriter = new StringWriter();
    StreamResult result = new StreamResult(strWriter);transformer.transform(source, result);
    System.out.println(strWriter.getBuffer().toString());

    return strWriter.getBuffer().toString();

}
person user3083990    schedule 10.07.2018

Еще одно решение, которое работает для нас

import java.io.StringWriter;
import org.dom4j.DocumentHelper;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;

**
 * Pretty Print XML String
 * 
 * @param inputXmlString
 * @return
 */
public static String prettyPrintXml(String xml) {

    final StringWriter sw;

    try {
        final OutputFormat format = OutputFormat.createPrettyPrint();
        final org.dom4j.Document document = DocumentHelper.parseText(xml);
        sw = new StringWriter();
        final XMLWriter writer = new XMLWriter(sw, format);
        writer.write(document);
    }
    catch (Exception e) {
        throw new RuntimeException("Error pretty printing xml:\n" + xml, e);
    }
    return sw.toString();
}
person Anand    schedule 22.07.2015

Использование jdom2: http://www.jdom.org/

import java.io.StringReader;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;

String prettyXml = new XMLOutputter(Format.getPrettyFormat()).
                         outputString(new SAXBuilder().build(new StringReader(uglyXml)));
person BijanE    schedule 11.05.2015

В качестве альтернативы ответам max codekraps, Дэвид Исли и milosmns, взгляните на мою легкую, высокопроизводительную библиотеку красивого принтера: XML-форматировщик

// construct lightweight, threadsafe, instance
PrettyPrinter prettyPrinter = PrettyPrinterBuilder.newPrettyPrinter().build();

StringBuilder buffer = new StringBuilder();
String xml = ..; // also works with char[] or Reader

if(prettyPrinter.process(xml, buffer)) {
     // valid XML, print buffer
} else {
     // invalid XML, print xml
}

Иногда, например, при запуске смоделированных служб SOAP непосредственно из файла, хорошо иметь симпатичный принтер, который также обрабатывает уже хорошо напечатанный XML:

PrettyPrinter prettyPrinter = PrettyPrinterBuilder.newPrettyPrinter().ignoreWhitespace().build();

Как отмечали некоторые, красивая печать - это просто способ представления XML в более удобочитаемой форме - пробелы строго не относятся к вашим данным XML.

Библиотека предназначена для красивой печати для целей ведения журнала, а также включает функции для фильтрации (удаление поддерева / анонимизация) и красивой печати XML в узлах CDATA и Text.

person ThomasRS    schedule 28.12.2014

У меня была та же проблема, и я добился большого успеха с JTidy (http://jtidy.sourceforge.net/index.html)

Пример:

Tidy t = new Tidy();
t.setIndentContent(true);
Document d = t.parseDOM(
    new ByteArrayInputStream("HTML goes here", null);

OutputStream out = new ByteArrayOutputStream();
t.pprint(d, out);
String html = out.toString();
person Kristoffer Lindvall    schedule 11.06.2010

Я всегда использую следующую функцию:

public static String prettyPrintXml(String xmlStringToBeFormatted) {
    String formattedXmlString = null;
    try {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setValidating(true);
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        InputSource inputSource = new InputSource(new StringReader(xmlStringToBeFormatted));
        Document document = documentBuilder.parse(inputSource);

        Transformer transformer = TransformerFactory.newInstance().newTransformer();
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");

        StreamResult streamResult = new StreamResult(new StringWriter());
        DOMSource dOMSource = new DOMSource(document);
        transformer.transform(dOMSource, streamResult);
        formattedXmlString = streamResult.getWriter().toString().trim();
    } catch (Exception ex) {
        StringWriter sw = new StringWriter();
        ex.printStackTrace(new PrintWriter(sw));
        System.err.println(sw.toString());
    }
    return formattedXmlString;
}
person Benson    schedule 06.05.2019
comment
Это важно: transformer.setOutputProperty ({xml.apache.org/xslt} indent-amount, 2 ); Если его нет, все элементы выровнены по левому краю - person Hank Lapidez; 07.02.2021
comment
@HankLapidez Это правда. - person Benson; 08.02.2021

Я обнаружил, что в Java 1.6.0_32 обычный метод красивой печати строки XML (с использованием преобразователя с нулевым значением или идентификатором xslt) ведет себя не так, как хотелось бы если теги просто разделены пробелом, в отличие от отсутствия разделительного текста. Я безуспешно пытался использовать <xsl:strip-space elements="*"/> в своем шаблоне. Самым простым решением, которое я нашел, было разделить пространство так, как я хотел, с помощью SAXSource и XML-фильтра. Поскольку мое решение было для ведения журнала, я также расширил его для работы с неполными фрагментами XML. Обратите внимание, что обычный метод работает нормально, если вы используете DOMSource, но я не хотел использовать его из-за неполноты и накладных расходов на память.

public static class WhitespaceIgnoreFilter extends XMLFilterImpl
{

    @Override
    public void ignorableWhitespace(char[] arg0,
                                    int arg1,
                                    int arg2) throws SAXException
    {
        //Ignore it then...
    }

    @Override
    public void characters( char[] ch,
                            int start,
                            int length) throws SAXException
    {
        if (!new String(ch, start, length).trim().equals("")) 
               super.characters(ch, start, length); 
    }
}

public static String prettyXML(String logMsg, boolean allowBadlyFormedFragments) throws SAXException, IOException, TransformerException
    {
        TransformerFactory transFactory = TransformerFactory.newInstance();
        transFactory.setAttribute("indent-number", new Integer(2));
        Transformer transformer = transFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
        StringWriter out = new StringWriter();
        XMLReader masterParser = SAXHelper.getSAXParser(true);
        XMLFilter parser = new WhitespaceIgnoreFilter();
        parser.setParent(masterParser);

        if(allowBadlyFormedFragments)
        {
            transformer.setErrorListener(new ErrorListener()
            {
                @Override
                public void warning(TransformerException exception) throws TransformerException
                {
                }

                @Override
                public void fatalError(TransformerException exception) throws TransformerException
                {
                }

                @Override
                public void error(TransformerException exception) throws TransformerException
                {
                }
            });
        }

        try
        {
            transformer.transform(new SAXSource(parser, new InputSource(new StringReader(logMsg))), new StreamResult(out));
        }
        catch (TransformerException e)
        {
            if(e.getCause() != null && e.getCause() instanceof SAXParseException)
            {
                if(!allowBadlyFormedFragments || !"XML document structures must start and end within the same entity.".equals(e.getCause().getMessage()))
                {
                    throw e;
                }
            }
            else
            {
                throw e;
            }
        }
        out.flush();
        return out.toString();
    }
person JFK    schedule 05.07.2012

Решения, которые я нашел здесь для Java 1.6+, не переформатируют код, если он уже отформатирован. Тот, который сработал для меня (и переформатировал уже отформатированный код), был следующим.

import org.apache.xml.security.c14n.CanonicalizationException;
import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.c14n.InvalidCanonicalizerException;
import org.w3c.dom.Element;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.IOException;
import java.io.StringReader;

public class XmlUtils {
    public static String toCanonicalXml(String xml) throws InvalidCanonicalizerException, ParserConfigurationException, SAXException, CanonicalizationException, IOException {
        Canonicalizer canon = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_OMIT_COMMENTS);
        byte canonXmlBytes[] = canon.canonicalize(xml.getBytes());
        return new String(canonXmlBytes);
    }

    public static String prettyFormat(String input) throws TransformerException, ParserConfigurationException, IOException, SAXException, InstantiationException, IllegalAccessException, ClassNotFoundException {
        InputSource src = new InputSource(new StringReader(input));
        Element document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(src).getDocumentElement();
        Boolean keepDeclaration = input.startsWith("<?xml");
        DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
        DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
        LSSerializer writer = impl.createLSSerializer();
        writer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE);
        writer.getDomConfig().setParameter("xml-declaration", keepDeclaration);
        return writer.writeToString(document);
    }
}

Это хороший инструмент для использования в ваших модульных тестах для полнострочного сравнения xml.

private void assertXMLEqual(String expected, String actual) throws ParserConfigurationException, IOException, SAXException, CanonicalizationException, InvalidCanonicalizerException, TransformerException, IllegalAccessException, ClassNotFoundException, InstantiationException {
    String canonicalExpected = prettyFormat(toCanonicalXml(expected));
    String canonicalActual = prettyFormat(toCanonicalXml(actual));
    assertEquals(canonicalExpected, canonicalActual);
}
person Wojtek    schedule 14.10.2014

Для тех, кто ищет быстрое и грязное решение, для которого не требуется, чтобы XML был на 100% достоверным. например в случае ведения журнала REST / SOAP (никогда не знаешь, что отправляют другие ;-))

Я нашел и продвинул фрагмент кода, который я нашел в Интернете, который, как мне кажется, все еще отсутствует здесь как допустимый возможный подход:

public static String prettyPrintXMLAsString(String xmlString) {
    /* Remove new lines */
    final String LINE_BREAK = "\n";
    xmlString = xmlString.replaceAll(LINE_BREAK, "");
    StringBuffer prettyPrintXml = new StringBuffer();
    /* Group the xml tags */
    Pattern pattern = Pattern.compile("(<[^/][^>]+>)?([^<]*)(</[^>]+>)?(<[^/][^>]+/>)?");
    Matcher matcher = pattern.matcher(xmlString);
    int tabCount = 0;
    while (matcher.find()) {
        String str1 = (null == matcher.group(1) || "null".equals(matcher.group())) ? "" : matcher.group(1);
        String str2 = (null == matcher.group(2) || "null".equals(matcher.group())) ? "" : matcher.group(2);
        String str3 = (null == matcher.group(3) || "null".equals(matcher.group())) ? "" : matcher.group(3);
        String str4 = (null == matcher.group(4) || "null".equals(matcher.group())) ? "" : matcher.group(4);

        if (matcher.group() != null && !matcher.group().trim().equals("")) {
            printTabs(tabCount, prettyPrintXml);
            if (!str1.equals("") && str3.equals("")) {
                ++tabCount;
            }
            if (str1.equals("") && !str3.equals("")) {
                --tabCount;
                prettyPrintXml.deleteCharAt(prettyPrintXml.length() - 1);
            }

            prettyPrintXml.append(str1);
            prettyPrintXml.append(str2);
            prettyPrintXml.append(str3);
            if (!str4.equals("")) {
                prettyPrintXml.append(LINE_BREAK);
                printTabs(tabCount, prettyPrintXml);
                prettyPrintXml.append(str4);
            }
            prettyPrintXml.append(LINE_BREAK);
        }
    }
    return prettyPrintXml.toString();
}

private static void printTabs(int count, StringBuffer stringBuffer) {
    for (int i = 0; i < count; i++) {
        stringBuffer.append("\t");
    }
}

public static void main(String[] args) {
    String x = new String(
            "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><soap:Fault><faultcode>soap:Client</faultcode><faultstring>INVALID_MESSAGE</faultstring><detail><ns3:XcbSoapFault xmlns=\"\" xmlns:ns3=\"http://www.someapp.eu/xcb/types/xcb/v1\"><CauseCode>20007</CauseCode><CauseText>INVALID_MESSAGE</CauseText><DebugInfo>Problems creating SAAJ object model</DebugInfo></ns3:XcbSoapFault></detail></soap:Fault></soap:Body></soap:Envelope>");
    System.out.println(prettyPrintXMLAsString(x));
}

вот результат:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <soap:Fault>
        <faultcode>soap:Client</faultcode>
        <faultstring>INVALID_MESSAGE</faultstring>
        <detail>
            <ns3:XcbSoapFault xmlns="" xmlns:ns3="http://www.someapp.eu/xcb/types/xcb/v1">
                <CauseCode>20007</CauseCode>
                <CauseText>INVALID_MESSAGE</CauseText>
                <DebugInfo>Problems creating SAAJ object model</DebugInfo>
            </ns3:XcbSoapFault>
        </detail>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>
person max    schedule 07.10.2013

Я видел один ответ с использованием Scala, так что вот еще один в Groovy, на случай, если кто-то сочтет это интересным. По умолчанию отступ составляет 2 шага, конструктору XmlNodePrinter также можно передать другое значение.

def xml = "<tag><nested>hello</nested></tag>"
def stringWriter = new StringWriter()
def node = new XmlParser().parseText(xml);
new XmlNodePrinter(new PrintWriter(stringWriter)).print(node)
println stringWriter.toString()

Использование из Java, если groovy jar находится в пути к классам

  String xml = "<tag><nested>hello</nested></tag>";
  StringWriter stringWriter = new StringWriter();
  Node node = new XmlParser().parseText(xml);
  new XmlNodePrinter(new PrintWriter(stringWriter)).print(node);
  System.out.println(stringWriter.toString());
person vsnyc    schedule 01.05.2015

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

String leastPrettifiedXml = uglyXml.replaceAll("><", ">\n<");

Код хорош, а не результат из-за отсутствия отступов.


(Для решений с отступом см. Другие ответы.)

person comonad    schedule 20.08.2015
comment
Хмммм ... Просто подумай, кому может понадобиться такое решение? Единственная область, которую я вижу, - это данные, которые мы получаем от некоторых веб-сервисов, и просто для проверки этих данных и их достоверности разработчику или тестировщику могут понадобиться такие простые. В противном случае не лучший вариант .... - person Sudhakar Chavali; 08.02.2018
comment
@SudhakarChavali, я разработчик. мне это может понадобиться для грязных взломов println () и log.debug (); то есть иногда я могу использовать файлы журнала только из ограниченной серверной среды (с интерфейсом веб-администратора вместо доступа к оболочке) вместо разумной пошаговой отладки программы. - person comonad; 19.02.2018

Существует очень хорошая утилита командной строки XML под названием xmlstarlet (http://xmlstar.sourceforge.net/) это может делать много вещей, которыми пользуются многие люди.

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

загрузить xmlstarlet: http://sourceforge.net/project/showfiles.php?group_id=66612&package_id=64589

person anjanb    schedule 26.09.2008

Underscore-java имеет статический метод U.formatXml(string). Живой пример

import com.github.underscore.lodash.U;

public class MyClass {
    public static void main(String args[]) {
        String xml = "<tag><nested>hello</nested></tag>";

        System.out.println(U.formatXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>" + xml + "</root>"));
    }
}

Выход:

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <tag>
      <nested>hello</nested>
   </tag>
</root>
person Valentyn Kolesnikov    schedule 14.02.2019
comment
Это круто! - person senyor; 02.10.2019

Попробуй это:

 try
                    {
                        TransformerFactory transFactory = TransformerFactory.newInstance();
                        Transformer transformer = null;
                        transformer = transFactory.newTransformer();
                        StringWriter buffer = new StringWriter();
                        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
                        transformer.transform(new DOMSource(element),
                                  new StreamResult(buffer)); 
                        String str = buffer.toString();
                        System.out.println("XML INSIDE IS #########################################"+str);
                        return element;
                    }
                    catch (TransformerConfigurationException e)
                    {
                        e.printStackTrace();
                    }
                    catch (TransformerException e)
                    {
                        e.printStackTrace();
                    }
person Sireesh Yarlagadda    schedule 14.05.2016
comment
Не вижу разницы с некоторыми из уже опубликованных ответов. - person Olivier Cailloux; 14.07.2020

Я должен был сначала поискать эту страницу, прежде чем придумывать собственное решение! Во всяком случае, мой использует рекурсию Java для анализа xml-страницы. Этот код полностью самодостаточен и не полагается на сторонние библиотеки. Также .. он использует рекурсию!

// you call this method passing in the xml text
public static void prettyPrint(String text){
    prettyPrint(text, 0);
}

// "index" corresponds to the number of levels of nesting and/or the number of tabs to print before printing the tag
public static void prettyPrint(String xmlText, int index){
    boolean foundTagStart = false;
    StringBuilder tagChars = new StringBuilder();
    String startTag = "";
    String endTag = "";
    String[] chars = xmlText.split("");
    // find the next start tag
    for(String ch : chars){
        if(ch.equalsIgnoreCase("<")){
            tagChars.append(ch);
            foundTagStart = true;
        } else if(ch.equalsIgnoreCase(">") && foundTagStart){
            startTag = tagChars.append(ch).toString();
            String tempTag = startTag;
            endTag = (tempTag.contains("\"") ? (tempTag.split(" ")[0] + ">") : tempTag).replace("<", "</"); // <startTag attr1=1 attr2=2> => </startTag>
            break;
        } else if(foundTagStart){
            tagChars.append(ch);
        }
    }
    // once start and end tag are calculated, print start tag, then content, then end tag
    if(foundTagStart){
        int startIndex = xmlText.indexOf(startTag);
        int endIndex = xmlText.indexOf(endTag);
        // handle if matching tags NOT found
        if((startIndex < 0) || (endIndex < 0)){
            if(startIndex < 0) {
                // no start tag found
                return;
            } else {
                // start tag found, no end tag found (handles single tags aka "<mytag/>" or "<?xml ...>")
                printTabs(index);
                System.out.println(startTag);
                // move on to the next tag
                // NOTE: "index" (not index+1) because next tag is on same level as this one
                prettyPrint(xmlText.substring(startIndex+startTag.length(), xmlText.length()), index);
                return;
            }
        // handle when matching tags found
        } else {
            String content = xmlText.substring(startIndex+startTag.length(), endIndex);
            boolean isTagContainsTags = content.contains("<"); // content contains tags
            printTabs(index);
            if(isTagContainsTags){ // ie: <tag1><tag2>stuff</tag2></tag1>
                System.out.println(startTag);
                prettyPrint(content, index+1); // "index+1" because "content" is nested
                printTabs(index);
            } else {
                System.out.print(startTag); // ie: <tag1>stuff</tag1> or <tag1></tag1>
                System.out.print(content);
            }
            System.out.println(endTag);
            int nextIndex = endIndex + endTag.length();
            if(xmlText.length() > nextIndex){ // if there are more tags on this level, continue
                prettyPrint(xmlText.substring(nextIndex, xmlText.length()), index);
            }
        }
    } else {
        System.out.print(xmlText);
    }
}

private static void printTabs(int counter){
    while(counter-- > 0){ 
        System.out.print("\t");
    }
}
person Steve T    schedule 04.11.2017
comment
Underscore-java, U.formatXml (xml) также не полагается на сторонние библиотеки. - person Valentyn Kolesnikov; 29.06.2019

Я пытался добиться чего-то похожего, но без какой-либо внешней зависимости. Приложение уже использовало DOM для форматирования только для записи XML!

Вот мой образец фрагмента

public void formatXML(final String unformattedXML) {
    final int length = unformattedXML.length();
    final int indentSpace = 3;
    final StringBuilder newString = new StringBuilder(length + length / 10);
    final char space = ' ';
    int i = 0;
    int indentCount = 0;
    char currentChar = unformattedXML.charAt(i++);
    char previousChar = currentChar;
    boolean nodeStarted = true;
    newString.append(currentChar);
    for (; i < length - 1;) {
        currentChar = unformattedXML.charAt(i++);
        if(((int) currentChar < 33) && !nodeStarted) {
            continue;
        }
        switch (currentChar) {
        case '<':
            if ('>' == previousChar && '/' != unformattedXML.charAt(i - 1) && '/' != unformattedXML.charAt(i) && '!' != unformattedXML.charAt(i)) {
                indentCount++;
            }
            newString.append(System.lineSeparator());
            for (int j = indentCount * indentSpace; j > 0; j--) {
                newString.append(space);
            }
            newString.append(currentChar);
            nodeStarted = true;
            break;
        case '>':
            newString.append(currentChar);
            nodeStarted = false;
            break;
        case '/':
            if ('<' == previousChar || '>' == unformattedXML.charAt(i)) {
                indentCount--;
            }
            newString.append(currentChar);
            break;
        default:
            newString.append(currentChar);
        }
        previousChar = currentChar;
    }
    newString.append(unformattedXML.charAt(length - 1));
    System.out.println(newString.toString());
}
person Faisal K    schedule 10.06.2019
comment
Удаляет пробелы в тексте. Пример: ‹text› \ n какой-то пример lol \ n ‹text› после преобразования: ‹text› someexamplelol ‹test› - person Maciej Pulikowski; 12.01.2020
comment
да, и у него есть и другие дефекты, такие как обработка комментариев, DTD, если таковые имеются, и т. д. Однако, исправив это, я смог получить приемлемый (кроме сложных элементов, таких как ‹text› некоторый сложный ‹b› текст снова ‹‹ / b ›а то ничего ‹/text›) логика работает. У меня нет кода под рукой, найдется немного свободного времени, чтобы написать еще раз - person Faisal K; 13.01.2020
comment
Пожалуйста, укажите, как это решение улучшает уже существующие ответы, в противном случае оно просто добавляет шума. - person Olivier Cailloux; 14.07.2020