¿Cómo imprimir bastante XML desde Java?
pretty-print (30)
Tengo un String de Java que contiene XML, sin feeds de línea o sangría. Me gustaría convertirlo en una cadena con XML bien formateado. ¿Cómo hago esto?
String unformattedXml = "<tag><nested>hello</nested></tag>";
String formattedXml = new [UnknownClass]().format(unformattedXml);
Nota: Mi entrada es una cadena . Mi salida es una cadena .
Ahora que es 2012 y Java puede hacer más de lo que solía hacer con XML, me gustaría agregar una alternativa a mi respuesta aceptada. Esto no tiene dependencias fuera de 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));
}
}
Aquí hay una forma de hacerlo usando dom4j :
Importaciones:
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
Código:
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();
Aquí hay una respuesta a mi propia pregunta. Combiné las respuestas de los diversos resultados para escribir una clase que imprime bastante XML.
No hay garantías sobre cómo responde con XML no válido o documentos grandes.
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));
}
}
Con respecto al comentario de que "primero debe construir un árbol DOM": No, no necesita y no debe hacer eso.
En su lugar, cree un StreamSource (nuevo StreamSource (nuevo StringReader (str)), y aliméntelo al transformador de identidad mencionado. Usará el analizador SAX, y el resultado será mucho más rápido. La construcción de un árbol intermedio es pura sobrecarga para este caso De lo contrario, la respuesta mejor clasificada es buena.
Dado que está comenzando con una String
, debe convertirse en un objeto DOM
(por ejemplo, Node
) antes de poder usar el Transformer
. Sin embargo, si sabe que su cadena XML es válida, y no desea incurrir en la sobrecarga de memoria de analizar una cadena en un DOM, luego ejecute una transformación sobre el DOM para recuperar una cadena; puede hacer algo antiguo. carácter por carácter de análisis. Inserte una nueva línea y espacios después de cada </...>
caracteres, mantenga y sangra el contador (para determinar el número de espacios) que incrementa por cada <...>
y disminuya por cada </...>
que vea.
Descargo de responsabilidad: hice una edición de cortar / pegar / texto de las funciones a continuación, para que no se compilen como están.
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));
}
He impreso bastante en el pasado usando el método 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();
}
Hmmm ... enfrenté algo como esto y es un error conocido ... solo agrega esta propiedad de salida ...
transformer.setOutputProperty(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "8");
Espero que esto ayude ...
Kevin Hakanson dijo: "Sin embargo, si sabe que su cadena XML es válida, y no desea incurrir en la sobrecarga de memoria de analizar una cadena en un DOM, luego ejecute una transformación sobre el DOM para recuperar una cadena. Podría simplemente haga un análisis de carácter por carácter a la antigua. Inserte una nueva línea y espacios después de cada carácter, mantenga y sangra el contador (para determinar el número de espacios) que incrementa por cada <...> y disminuya por cada que vea ".
Convenido. Este enfoque es mucho más rápido y tiene muchas menos dependencias.
Ejemplo de solución:
/**
* 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
}
Prueba esto:
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();
}
Si está bien usar una biblioteca XML de terceros, puede salirse con algo mucho más simple de lo que sugieren las answers actualmente highest-voted .
Se afirmó que tanto la entrada como la salida deberían ser cadenas, así que aquí hay un método de utilidad que hace eso, implementado con la biblioteca 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");
}
Probé que funciona, y los resultados no dependen de su versión JRE ni nada de eso. Para ver cómo personalizar el formato de salida a su gusto, eche un vistazo a la API Serializer
.
Esto realmente salió más tiempo de lo que pensaba: se necesitaron algunas líneas adicionales porque Serializer
quiere que escriba un OutputStream
. Pero tenga en cuenta que aquí hay muy poco código para las variaciones reales de XML.
(Esta respuesta es parte de mi evaluación de XOM, que se suggested como una opción en mi pregunta sobre la mejor biblioteca de Java XML para reemplazar dom4j. Para el registro, con dom4j se puede lograr esto con una facilidad similar utilizando XMLWriter
y OutputFormat
. Edit : ... como se demuestra en la respuesta de mlo55 .
Si está seguro de que tiene un XML válido, este es simple y evita los árboles DOM de XML. Tal vez tiene algunos errores, comenta si ves algo
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();
}
Solo otra solución que funciona para nosotros.
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();
}
Solo para futuras referencias, aquí hay una solución que me funcionó (gracias a un comentario que @George Hawkins publicó en una de las respuestas):
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());
Solo para tener en cuenta que la respuesta mejor calificada requiere el uso de xerces.
Si no desea agregar esta dependencia externa, simplemente puede usar las bibliotecas jdk estándar (que en realidad se crean utilizando xerces internamente).
NB: Hubo un error con la versión 1.5 de jdk en http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6296446 pero se resolvió ahora.
(Tenga en cuenta que si se produce un error esto devolverá el texto original)
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;
}
}
}
Todas las soluciones anteriores no funcionaron para mí, luego encontré este http://myshittycode.com/2014/02/10/java-properly-indenting-xml-string/
La pista es eliminar espacios en blanco con 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();
}
Utilizando scala:
import xml._
val xml = XML.loadString("<tag><nested>hello</nested></tag>")
val formatted = new PrettyPrinter(150, 2).format(xml)
println(formatted)
También puede hacer esto en Java, si depende de scala-library.jar. Se parece a esto:
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);
}
}
El objeto PrettyPrinter
se construye con dos pulgadas, la primera es la longitud máxima de la línea y la segunda el paso de sangría.
Versión ligeramente mejorada de 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();
}
Una solución más simple basada en esta respuesta :
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);
}
caso de prueba:
prettyFormat("<root><child>aaa</child><child/></root>");
devoluciones:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<child>aaa</child>
<child/>
</root>
En caso de que no necesite sangría tanto, pero unos pocos saltos de línea, podría ser suficiente simplemente regex ...
String leastPrettifiedXml = uglyXml.replaceAll("><", ">/n<");
El código es bueno, no el resultado debido a la falta de sangría.
(Para soluciones con sangría, vea otras respuestas).
Este código de abajo funciona perfectamente
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);
}
}
Como alternativa a las respuestas de max , , David Easley y , un vistazo a mi biblioteca de impresoras bonitas de alto rendimiento y peso ligero: xml-formatter
PrettyPrinter prettyPrinter = PrettyPrinterBuilder.newPrettyPrinter().ignoreWhitespace().build();
A veces, como cuando se ejecutan servicios SOAP simulados directamente desde un archivo, es bueno tener una impresora que también maneja XML ya impreso.
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();
}
Como algunos han comentado, la impresión bonita es solo una forma de presentar XML en una forma más legible para el ser humano: el espacio en blanco no pertenece estrictamente a sus datos XML.
La biblioteca está diseñada para la impresión de documentos con fines de registro, y también incluye funciones para el filtrado (eliminación / anonimización de subárbol) y la impresión de archivos XML en CDATA y nodos de texto.
Descubrí que en Java 1.6.0_32 el método normal para imprimir una cadena XML (usando un Transformer con un nulo o xslt de identidad) no se comporta como quisiera si las etiquetas estuvieran separadas por espacios en blanco, en lugar de no tener separación texto. Traté de usar <xsl:strip-space elements="*"/>
en mi plantilla en vano. La solución más sencilla que encontré fue eliminar el espacio de la forma que quería utilizando un filtro SAXSource y XML. Como mi solución era para el registro, también extendí esto para trabajar con fragmentos XML incompletos. Tenga en cuenta que el método normal parece funcionar bien si utiliza un DOMSource pero no quería usarlo debido a la falta de memoria y la sobrecarga de memoria.
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();
}
Las soluciones que he encontrado aquí para Java 1.6+ no reformatean el código si ya está formateado. El que funcionó para mí (y el formato de código ya formateado) fue el siguiente.
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);
}
}
Es una buena herramienta para usar en las pruebas de unidad para la comparación de XML de cadena completa.
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);
}
Los mezclo todos y escribo un pequeño programa. Está leyendo el archivo xml e imprimiendo. Sólo en lugar de xzy dar su ruta de archivo.
// 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
}
Para aquellos que buscan una solución rápida y sucia, que no necesita que el XML sea 100% válido. por ejemplo, en el caso de registro REST / SOAP (nunca se sabe lo que envían los demás ;-))
Encontré y avanzé un código cortado que encontré en línea que creo que todavía falta aquí como un posible enfoque válido:
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));
}
Aquí está la salida:
<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>
Tuve el mismo problema y estoy teniendo un gran éxito con JTidy ( http://jtidy.sourceforge.net/index.html )
Ejemplo:
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();
Usando 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)));
Vi una respuesta usando Scala
, así que aquí hay otra Groovy
, por si alguien la encuentra interesante. La sangría predeterminada es 2 pasos, al XmlNodePrinter
constructor también se le puede pasar otro valor.
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()
Uso desde Java si groovy jar está en classpath
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());
hay una muy buena utilidad de línea de comandos xml llamada xmlstarlet ( http://xmlstar.sourceforge.net/ ) que puede hacer muchas cosas que mucha gente usa.
Podría ejecutar este programa mediante programación utilizando Runtime.exec y luego leer el archivo de salida formateado. Tiene más opciones y mejores informes de errores que algunas líneas de código Java.
descargar xmlstarlet: http://sourceforge.net/project/showfiles.php?group_id=66612&package_id=64589
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);
Nota: Los resultados pueden variar dependiendo de la versión de Java. Busque soluciones específicas para su plataforma.