如何从Java中漂亮地打印XML?

499

我有一个包含XML代码的Java字符串,其中没有换行或缩进。我想将其转换为格式良好的XML字符串。如何做到这一点?

String unformattedXml = "<tag><nested>hello</nested></tag>";
String formattedXml = new [UnknownClass]().format(unformattedXml);
注意:我的输入是一个字符串(String)。我的输出也是一个字符串(String)。
(基础)模拟结果:
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <tag>
    <nested>hello</nested>
  </tag>
</root>

请查看这个问题:https://dev59.com/lXM_5IYBdhLWcg3wslfs - dfa
10
只是好奇,你是将这个输出发送到XML文件或其他需要缩进很重要的地方吗?之前我非常关注格式化我的XML以便正确显示它...但在花费了大量时间后,我意识到我必须将输出发送到一个Web浏览器,并且任何相对现代的Web浏览器都可以以漂亮的树形结构显示XML,所以我可以忘记这个问题并继续前进。我提到这个只是为了防止你(或其他有同样问题的用户)可能会忽略同样的细节。 - Abel Morelos
4
@Abel,将数据保存到文本文件中,插入到HTML文本区域中,并将其倒出到控制台以进行调试。 - Steve McLeod
6
“put on hold as too broad” 的意思是“因问题过于广泛而被搁置”,目前很难比问题更加精确明了! - Steve McLeod
34个回答

10

嗯...我遇到过类似的问题,这是一个已知的bug... 只需添加这个OutputProperty即可...

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

希望这有所帮助...


3
这个OutputPropertiesFactory是从哪里来的? - helenov
import com.sun.org.apache.xml.internal.serializer.*; 导入 com.sun.org.apache.xml.internal.serializer 包中的类; - Gaurav

10
关于“必须首先构建DOM树”的评论:不,你不需要也不应该这样做。 相反,创建一个StreamSource(new StreamSource(new StringReader(str)),并将其提供给提到的标识转换器。这将使用SAX解析器,结果会更快。 对于这种情况,构建中间树是纯粹的开销。否则,排名最高的答案很好。

2
我完全同意:构建中间DOM树是浪费内存的行为。感谢您的回答。 - Florian F

9

使用Scala:

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

如果你依赖于scala-library.jar,你也可以在Java中实现这一点。代码如下:
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);
    }
}
< p > PrettyPrinter 对象由两个整数构建,第一个是最大行长度,第二个是缩进步长。 < /p >

8

作为以后的参考,这是一个对我有效的解决方案(感谢@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());

8

这是一个稍微改进过的版本,参考了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();
} 

repeatString(stack++); 方法在哪里? - user1912935
2
私有静态字符串 repeatString(int stack) { StringBuilder indent = new StringBuilder(); for (int i = 0; i < stack; i++){ indent.append(" "); } return indent.toString(); } - codeskraps
缩进在结束标签处无法正常工作。您需要将 } 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"); } } - Csaba Tenkes
1
不要用这种方式手动解析XML。如果XML中包含注释内的“<”符号,你的代码将会破坏XML的结构。 - Michael Kay

6
所有以上的解决方案对我都没有用,后来我找到了这个链接: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();
}

1
请注意,使用“{http://xml.apache.org/xslt}indent-amount”属性将使您与特定的转换器实现绑定。 - Parker
2
从所有的解决方案中,这个是最好的。我的 XML 中已经有了空格和换行符,而且我不想在我的项目中添加更多的依赖项。我希望我不必解析 XML,但没办法。 - Fabio

6

下面的代码完美运行

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);
    }
}

6

我将它们混合在一起并编写了一个小程序。它从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();

}

在此处构建DOM是不必要且低效的。使用StreamSource。 - Michael Kay

5
如果您确定自己有一个有效的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();
    }

2
repeatString方法在哪里? - user1912935
3
私有静态字符串 repeatString(int stack) { StringBuilder indent = new StringBuilder(); for (int i = 0; i < stack; i++){ indent.append(" "); } return indent.toString(); } 该段代码用于生成重复空格的字符串,其中参数 stack表示需要重复的次数,返回一个由空格组成的字符串。 - codeskraps
是的[user1912935],@codeskraps写的应该足够简单了 :) - milosmns
在循环内使用StringBuilder进行字符串拼接:不良实践。 - james.garriss
@james.garriss 但是将其拆分为新行非常容易,这只是说明了一种简单的方法,没有任何DOM树。 - milosmns

5

这只是我们用来解决问题的另一种方案。

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();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接