У меня есть строка 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>
Просто любопытно, вы отправляете этот вывод в файл XML или что-то еще, где отступы действительно имеют значение? Некоторое время назад я был очень обеспокоен форматированием своего XML, чтобы он правильно отображался ... но потратив на это кучу времени, я понял, что мне нужно отправить свой вывод в веб-браузер и любой относительно современный веб-браузер. фактически отобразит XML в красивой древовидной структуре, так что я могу забыть об этой проблеме и двигаться дальше. Я упоминаю об этом на тот случай, если вы (или другой пользователь с той же проблемой) могли упустить ту же деталь.
@Abel, сохранение в текстовые файлы, вставка в текстовые поля HTML и выгрузка в консоль для целей отладки.
"отложено как слишком широкое" - это трудно быть более точным, чем вопрос сейчас!




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. Найдите обходные пути, специфичные для вашей платформы.
Как сделать так, чтобы на выходе не было <?xml version = "1.0" encoding = "UTF-8"?>?
Чтобы опустить декларацию <?xml ...>, добавьте transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
Обычным читателям может быть полезна улучшенная версия решения, описанного здесь (stackoverflow.com/a/33541820/363573).
где определяется doc?
это переменная типа Document, которая содержит ваш xml для преобразования, у вас должен быть он где-то в вашем коде
Это не отвечает на мой вопрос: как отформатировать строку, содержащую XML? В этом ответе уже предполагается, что вы каким-то образом преобразовали объект String в другой объект.
Это не сайт для домашних заданий / фриланса, и решение охватывает наиболее важный аспект проблемы.
Это решение удаляет обертки CDATA. Есть ли флаг, чтобы предотвратить это?
doc можно получить так: DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();DocumentBuilder db = dbf.newDocumentBuilder();InputSource is = new InputSource(new StringReader(in));Document doc = db.parse(is);
Поскольку вы начинаете с 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));
}
«Однако, если вы знаете, что ваша строка XML действительна ...» - хороший аргумент. См. Мое решение, основанное на этом подходе ниже.
Вот ответ на мой вопрос. Я объединил ответы из различных результатов, чтобы написать класс, который хорошо печатает 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));
}
}
Просто обратите внимание, что этот ответ требует использования Xerces. Если вы не хотите добавлять эту зависимость, вы можете просто использовать стандартные библиотеки jdk и javax.xml.transform.Transformer (см. Мой ответ ниже)
В 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 = ....
Я создал небольшой класс, используя пример Apache, на который @GeorgeHawkins дал ссылку. Не хватало способа инициализации переменной document, поэтому я подумал, что могу добавить замедление и сделать из этого быстрый пример. Дай мне знать, если я должен что-то изменить, pastebin.com/XL7932aC
неверно, что вы можете сделать это только с помощью jdk. по крайней мере, не надежно. это зависит от некоторой внутренней реализации реестра, которая по умолчанию не активна с моим jdk7u72. так что вам все равно лучше использовать материал apache напрямую.
Вот решение без каких-либо зависимостей: stackoverflow.com/a/33541820/363573.
На самом деле я поддерживаю проект 2008 года LOL, спасибо!
проблемы с utf-8
Чтобы избежать проверки xml и связанных накладных расходов, используйте builder.setEntityResolver((publicId, systemId) -> {return new InputSource(new StringReader(""));});
Существует очень хорошая утилита командной строки XML под названием xmlstarlet (http://xmlstar.sourceforge.net/), которая может делать множество вещей, которыми пользуются многие люди.
Вы можете выполнить эту программу программно, используя Runtime.exec, а затем прочитать отформатированный выходной файл. У него больше возможностей и лучшая система отчетов об ошибках, чем может дать несколько строк кода Java.
скачать xmlstarlet: http://sourceforge.net/project/showfiles.php?group_id=66612&package_id=64589
Раньше я довольно много печатал, используя метод 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();
}
В моем случае принятое решение не имеет правильного отступа для вложенных тегов, а в этом случае.
Я использовал это вместе с удалением всех конечных пробелов в конце строк: prettyPrintedString.replaceAll("\s+\n", "\n")
Относительно комментария о том, что «сначала вы должны построить дерево DOM»: Нет, вам не нужно и не следует этого делать.
Вместо этого создайте StreamSource (new StreamSource (new StringReader (str)) и передайте его упомянутому преобразователю идентичности. Он будет использовать синтаксический анализатор SAX, и результат будет намного быстрее. В этом случае построение промежуточного дерева - это чистые накладные расходы. В противном случае лучший ответ - это хорошо.
Я полностью согласен: построение промежуточного дерева DOM - пустая трата памяти. Спасибо за ответ.
Если использование сторонней библиотеки 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 или чего-то подобного. Чтобы узнать, как настроить выходной формат по своему вкусу, взгляните на Serializer API.
На самом деле это получилось дольше, чем я думал - потребовалось несколько дополнительных строк, потому что Serializer хочет, чтобы OutputStream мог писать. Но обратите внимание, что здесь очень мало кода для реального тидлинга XML.
(Этот ответ является частью моей оценки XOM, которая была предложенный как один из вариантов в моем вопрос о лучшей библиотеке Java XML для замены dom4j. Для записи, с dom4j вы могли бы добиться этого с аналогичной легкостью, используя XMLWriter и OutputFormat. Редактировать: ... как показано на Ответ mlo55.)
Спасибо, это то, что я искал. Если у вас есть XML, уже проанализированный с помощью XOM в объекте «Документ», вы можете передать его напрямую в serializer.write (document);
более простое решение, основанное на этом ответе:
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);
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>
Это код, который я всегда использовал, но в этой компании он не работал, я предполагаю, что они используют другую библиотеку преобразования XML. Я создал фабрику отдельной линией, потом сделал factory.setAttribute("indent-number", 4);, и теперь он работает.
Как сделать так, чтобы на выходе не было <?xml version = "1.0" encoding = "UTF-8"?>?
@ Гарри: transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
Привет, я использую этот точный код и правильно форматирую, за исключением первого элемента. Итак, это: <?xml version = "1.0" encoding = "UTF-8"?><root> находится в одной строке. Есть идеи, почему?
@dfa: мне понравился комментарий // simple exception handling, please review it. Можете ли вы указать на некоторые ресурсы, которые рекомендуют этот тип обработки исключений? Спасибо.
@Codemiester: Кажется, это ошибка (см. stackoverflow.com/a/18251901/3375325). Добавление transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes"); сработало для меня.
В Java 1.8 он ничего не делает (лишние пробелы не удаляются, отступы / новые строки не добавляются
Этот фрагмент кода уязвим для внедрения XML-внешних сущностей (XXE). См .: cheatsheetseries.owasp.org/cheatsheets/…
Хммм ... столкнулся с чем-то вроде этого и это известный баг ... просто добавьте это OutputProperty ..
transformer.setOutputProperty(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "8");
Надеюсь это поможет ...
Откуда этот OutputPropertiesFactory?
import com.sun.org.apache.xml.internal.serializer. *;
Вот способ сделать это с помощью 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();
У меня это не сработало. У него просто что-то вроде: <?xml version... на одной строке, а все остальное на другой строке.
Кевин Хакансон сказал: "Однако, если вы знаете, что ваша 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
}
Так и должно быть. Форматирование на лету на строковом уровне. Это единственное решение, которое отформатирует недопустимый или неполный XML.
У меня была такая же проблема, и у меня большой успех с 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();
Просто обратите внимание, что ответ с самым высоким рейтингом требует использования 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;
}
}
}
В этом случае левая вкладка не используется. Все теги начинаются с первого символа строки, как и обычный текст.
разве вам не нужно указывать кодировку при преобразовании назад и вперед между байтами и строкой?
Не должно быть необходимости конвертировать из и в байтовые массивы / String. По крайней мере, при этом вам нужно будет указать кодировку. Лучшим вариантом было бы использовать классы StringReader и StringWriter, заключенные в InputSource и StreamResult.
не работает. вам нужно возиться с некоторой внутренней реализацией реестра.
Вот более простой вариант этого решения: stackoverflow.com/a/33541820/363573
Используя 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 состоит из двух целых чисел, первое из которых - максимальная длина строки, а второе - шаг отступа.
Просто для справки в будущем вот решение, которое сработало для меня (благодаря комментарию, который @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());
Я обнаружил, что в 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();
}
Сейчас 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));
}
}
Без отступов, но он работает со следующим: System.setProperty (DOMImplementation Registry.PROPERTY, «com.s un.org.apache.xerces .internal.dom.DOMImp lementationSourceImp l»);
Я попробовал это решение и обнаружил, что возвращаемая строка XML всегда имеет UTF-16. Когда документ сериализуется обратно в строку, появляется следующая строка кода: ser._format.setEncoding("UTF-16"); Сейчас это может быть стандартом, но для системы, с которой я работаю, используется UTF-8. Кто-нибудь знает, как сохранить кодировку из исходной строки XML?
@DanTemple Похоже, вам нужно использовать LSOutput для управления кодировкой. См. chipkillmar.net/2009/03/25/pretty-print-xml-from-a-dom
@JoshuaDavis Спасибо, это работает для установки кодировки ответа. Мне нужно добавить что-то, что передает исходную кодировку красивому принтеру, если я хочу сохранить кодировку исходной строки XML.
@JoshA. Есть однострочный код с использованием XMLBeam: System.out.println(new XBProjector().projectXMLString("<xml><foo><bar/></foo></xml>", DOMAccess.class).asString());
Я обнаружил, что это переносит строки на определенную длину - есть ли способ предотвратить перенос?
это добавляет спецификацию FEFF в начало возвращаемой строки.
Я пытался использовать это в Android, но не могу найти пакет `DOMImplementation Registry. Я использую java 8.
У меня возникли проблемы с этим фрагментом, связанные с экранированными значениями атрибутов. Меньшая escape-последовательность < была правильно сохранена, в то время как последовательность больше чем > была преобразована в фактический знак >.
да, эта работа на jdk8 без исключения вместо следующего рейтингового ответа.
Чтобы установить кодировку: LSOutput output = impl.createLSOutput (); output.setEncoding ("UTF-8"); output.setByteStream (новый ByteArrayOutputStream ()); Writer.write (документ, вывод); вернуть output.getByteStream (). toString ();
Не работал над JDK 8, развернутым в JBoss EAP 7 / Wildfly 10, получить класс не найден исключения com.sun.org.apache.xerces.internal.dom.DOMXSImplementationSourceImpl - похоже, связано с этим issues.jboss.org/browse/WFLY-4416 - но я не собираюсь иметь дело с добавлением дополнительной библиотеки или чего-то еще - мне не нужно, чтобы это было так плохо напечатано .
спасибо, что вы также включили список импорта, так много конфликтующих пакетов доступно, чтобы понять комбинацию, необходимую в противном случае
Для тех, кто ищет быстрое и грязное решение, для которого не требуется, чтобы 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>
Если вы уверены, что у вас есть допустимый 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();
}
где метод repeatString ..?
частный статический String repeatString (int stack) {StringBuilder indent = new StringBuilder (); для (int я = 0; я <стек; я ++) {indent.append (""); } return indent.toString (); }
Да [user1912935], то, что написал @codeskraps, должно быть достаточно простым :)
Конкатенация со StringBuilder внутри цикла: плохая практика.
@ james.garriss Но очень легко разделить на новые строки, это просто иллюстрирует простой подход без каких-либо деревьев DOM.
немного улучшенная версия от Милосмнс ...
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();
}
где repeatString (stack ++); метод ..?
частный статический String repeatString (int stack) {StringBuilder indent = new StringBuilder (); для (int я = 0; я <стек; я ++) {indent.append (""); } return indent.toString (); }
Отступы в конечных тегах работают некорректно. Вам необходимо заменить часть } 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"); } }
Решения, которые я нашел здесь для 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);
}
В качестве альтернативы ответам Максимум, коды, Дэвид Исли и Милосмнс взгляните на мою легкую, высокопроизводительную библиотеку симпатичных принтеров: 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.
Я видел один ответ с использованием 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());
Использование 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)));
Все вышеперечисленные решения у меня не работали, потом я нашел этот 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();
}
Обратите внимание, что использование свойства '{xml.apache.org/xslt} indent-amount' привяжет вас к конкретной реализации преобразователя.
Из всех решений это сработало лучше всего. В моем XML уже были пробелы и новые строки, к тому же я не хотел добавлять больше зависимостей в свой проект. Мне жаль, что мне не пришлось разбирать XML, ну да ладно.
Еще одно решение, которое работает для нас
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();
}
Если вам не нужен такой отступ, а несколько разрывов строк, может быть достаточно просто регулярного выражения ...
String leastPrettifiedXml = uglyXml.replaceAll("><", ">\n<");
Код хорош, а не результат из-за отсутствия отступов.
(Для решений с отступом см. Другие ответы.)
Хммм ... Просто подумай, кому может понадобиться такое решение? Единственная область, которую я вижу, - это данные, которые мы получаем от некоторых веб-сервисов, и просто для проверки этих данных и их достоверности разработчику или тестировщику могут понадобиться такие простые. В противном случае не лучший вариант ....
@SudhakarChavali, я разработчик. мне это может понадобиться для грязных взломов println () и log.debug (); то есть иногда я могу использовать файлы журнала только из ограниченной серверной среды (с интерфейсом веб-администратора вместо доступа к оболочке) вместо разумной пошаговой отладки программы.
Попробуй это:
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();
}
Не вижу разницы с некоторыми из уже опубликованных ответов.
Этот код ниже работает отлично
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);
}
}
Я должен был сначала поискать эту страницу, прежде чем придумывать собственное решение! Во всяком случае, мой использует рекурсию 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");
}
}
Underscore-java, U.formatXml (xml) также не полагается на сторонние библиотеки.
Я смешиваю их все и пишу одну небольшую программу. Он читает из 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();
}
Подчеркивание-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>
Это круто!
Я всегда использую следующую функцию:
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;
}
Это важно: transformer.setOutputProperty ("{xml.apache.org/xslt} indent-a mount", "2"); Если его нет, все элементы выровнены по левому краю
@HankLapidez Это правда.
Я пытался добиться чего-то похожего, но без какой-либо внешней зависимости. Приложение уже использовало 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());
}
Удаляет пробелы в тексте. Пример: <text> \ n некоторый пример lol \ n <text> после преобразования: <text> someexamplelol <test>
да, и у него есть другие дефекты, такие как обработка комментариев, DTD, если таковые имеются, и т. д. Однако, исправив это, я смог получить приемлемый (кроме сложных элементов, таких как <text>, снова некоторый сложный <b> текст < / b> а то ничего </text>) логика работает. У меня нет кода под рукой, найдется немного свободного времени, чтобы написать еще раз
Пожалуйста, укажите, как это решение улучшает уже существующие ответы, в противном случае оно просто добавляет шума.
проверьте этот вопрос: stackoverflow.com/questions/1264849/…