java xml dom pretty-print transformer

Impresión bonita XML en java 8



dom pretty-print (7)

Tengo un archivo XML almacenado como un documento DOM y me gustaría imprimirlo en la consola, preferiblemente sin usar una biblioteca externa. Soy consciente de que esta pregunta se ha formulado varias veces en este sitio, sin embargo, ninguna de las respuestas anteriores me ha funcionado. Estoy usando Java 8, ¿entonces tal vez aquí es donde mi código difiere de las preguntas anteriores? También he intentado configurar el transformador manualmente usando el código encontrado en la web, sin embargo, esto solo provocó un error not found .

Aquí está mi código que actualmente solo genera cada elemento xml en una nueva línea a la izquierda de la consola.

import java.io.*; import javax.xml.parsers.*; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.xml.sax.SAXException; public class Test { public Test(){ try { //java.lang.System.setProperty("javax.xml.transform.TransformerFactory", "org.apache.xalan.xsltc.trax.TransformerFactoryImpl"); DocumentBuilderFactory dbFactory; DocumentBuilder dBuilder; Document original = null; try { dbFactory = DocumentBuilderFactory.newInstance(); dBuilder = dbFactory.newDocumentBuilder(); original = dBuilder.parse(new InputSource(new InputStreamReader(new FileInputStream("xml Store - Copy.xml")))); } catch (SAXException | IOException | ParserConfigurationException e) { e.printStackTrace(); } StringWriter stringWriter = new StringWriter(); StreamResult xmlOutput = new StreamResult(stringWriter); TransformerFactory tf = TransformerFactory.newInstance(); //tf.setAttribute("indent-number", 2); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.transform(new DOMSource(original), xmlOutput); java.lang.System.out.println(xmlOutput.getWriter().toString()); } catch (Exception ex) { throw new RuntimeException("Error converting to String", ex); } } public static void main(String[] args){ new Test(); } }


Crear archivo xml:

new FileInputStream("xml Store - Copy.xml") ;// result xml file format incorrect !

de modo que, cuando analice el contenido de la fuente de entrada dada como un documento XML y devuelva un nuevo objeto DOM.

Document original = null; ... original.parse("data.xml");//input source as an XML document


En respuesta al comentario de Espinosa, aquí hay una solución cuando " el xml original aún no está sangrado (parcialmente) o no contiene nuevas líneas ".

Fondo

Extracto del artículo (ver Referencias a continuación) que inspira esta solución:

Según la especificación de DOM, los espacios en blanco fuera de las etiquetas son perfectamente válidos y se conservan correctamente. Para eliminarlos, podemos usar el espacio de normalización de XPath para ubicar todos los nodos de espacios en blanco y eliminarlos primero.

Código Java

public static String toPrettyString(String xml, int indent) { try { // Turn xml string into a document Document document = DocumentBuilderFactory.newInstance() .newDocumentBuilder() .parse(new InputSource(new ByteArrayInputStream(xml.getBytes("utf-8")))); // Remove whitespaces outside tags document.normalize(); 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); } // Setup pretty print options TransformerFactory transformerFactory = TransformerFactory.newInstance(); transformerFactory.setAttribute("indent-number", indent); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); // Return pretty print xml string StringWriter stringWriter = new StringWriter(); transformer.transform(new DOMSource(document), new StreamResult(stringWriter)); return stringWriter.toString(); } catch (Exception e) { throw new RuntimeException(e); } }

Uso de la muestra

String xml = "<root>" + // "/n " + // "/n<name>Coco Puff</name>" + // "/n <total>10</total> </root>"; System.out.println(toPrettyString(xml, 4));

Salida

<root> <name>Coco Puff</name> <total>10</total> </root>

Referencias


Esto funciona en Java 8:

public static void main (String[] args) throws Exception { String xmlString = "<hello><from>ME</from></hello>"; DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.parse(new InputSource(new StringReader(xmlString))); pretty(document, System.out, 2); } private static void pretty(Document document, OutputStream outputStream, int indent) throws Exception { TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); if (indent > 0) { transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", Integer.toString(indent)); } Result result = new StreamResult(outputStream); Source source = new DOMSource(document); transformer.transform(source, result); }


He escrito una clase simple para eliminar espacios en blanco en documentos: admite la línea de comandos y no usa DOM / XPath.

Edición: Pensándolo bien, el proyecto también contiene una bonita impresora que maneja los espacios en blanco existentes:

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


No me gustó ninguna de las soluciones comunes de formato XML porque eliminan más de 1 carácter de línea nueva consecutiva (por alguna razón, la eliminación de espacios / tabulaciones y la eliminación de nuevos caracteres de línea son inseparables ...). Aquí está mi solución, que en realidad fue hecha para XHTML, pero también debería hacer el trabajo con XML:

public String GenerateTabs(int tabLevel) { char[] tabs = new char[tabLevel * 2]; Arrays.fill(tabs, '' ''); //Or: //char[] tabs = new char[tabLevel]; //Arrays.fill(tabs, ''/t''); return new String(tabs); } public String FormatXHTMLCode(String code) { // Split on new lines. String[] splitLines = code.split("//n", 0); int tabLevel = 0; // Go through each line. for (int lineNum = 0; lineNum < splitLines.length; ++lineNum) { String currentLine = splitLines[lineNum]; if (currentLine.trim().isEmpty()) { splitLines[lineNum] = ""; } else if (currentLine.matches(".*<[^/!][^<>]+?(?<!/)>?")) { splitLines[lineNum] = GenerateTabs(tabLevel) + splitLines[lineNum]; ++tabLevel; } else if (currentLine.matches(".*</[^<>]+?>")) { --tabLevel; if (tabLevel < 0) { tabLevel = 0; } splitLines[lineNum] = GenerateTabs(tabLevel) + splitLines[lineNum]; } else if (currentLine.matches("[^<>]*?/>")) { splitLines[lineNum] = GenerateTabs(tabLevel) + splitLines[lineNum]; --tabLevel; if (tabLevel < 0) { tabLevel = 0; } } else { splitLines[lineNum] = GenerateTabs(tabLevel) + splitLines[lineNum]; } } return String.join("/n", splitLines); }

Se hace una suposición : que no hay caracteres <> excepto aquellos que comprenden las etiquetas XML / XHTML.


Supongo que el problema está relacionado con los nodos de texto en blanco (es decir, los nodos de texto con espacios en blanco solamente) en el archivo original. Debe intentar eliminarlos mediante programación justo después del análisis, utilizando el siguiente código. Si no los eliminas, el Transformer conservará.

original.getDocumentElement().normalize(); XPathExpression xpath = XPathFactory.newInstance().newXPath().compile("//text()[normalize-space(.) = '''']"); NodeList blankTextNodes = (NodeList) xpath.evaluate(original, XPathConstants.NODESET); for (int i = 0; i < blankTextNodes.getLength(); i++) { blankTextNodes.item(i).getParentNode().removeChild(blankTextNodes.item(i)); }


Underscore-java tiene el método estático U.formatXml (cadena). Soy el mantenedor del proyecto. Ejemplo vivo

import com.github.underscore.lodash.U; public class MyClass { public static void main(String args[]) { String xml = "<root>" + // "/n " + // "/n<name>Coco Puff</name>" + // "/n <total>10</total> </root>"; System.out.println(U.formatXml(xml)); } }