2. 问题介绍

当需要手动读取XML文件时,我们通常希望看到格式化后的内容。许多文本编辑器或IDE可以重新格式化XML文档,在Linux环境下也可以通过命令行美化XML。但有时我们需要在Java程序中将原始XML字符串转换为美化格式,例如在用户界面展示以提高可读性。

本文将使用以下非格式化的emails.xml作为输入示例:

<emails> <email> <from>Kai</from> <to>Amanda</to> <time>2018-03-05</time>
<subject>I am flying to you</subject></email> <email>
<from>Jerry</from> <to>Tom</to> <time>1992-08-08</time> <subject>Hey Tom, catch me if you can!</subject>
</email> </emails>

虽然XML结构完整,但混乱的格式严重影响可读性。我们的目标是创建方法,将原始XML字符串转换为美化格式。同时需要支持两个常用配置:

  1. 缩进大小(整数)
  2. 是否抑制XML声明(布尔值)

典型的XML声明如下:

<?xml version="1.0" encoding="UTF-8"?>

接下来将介绍两种实现方案:标准Java API和第三方库。

3. 使用Transformer类美化XML

Java API提供了Transformer类处理XML转换。

3.1. 使用默认Transformer

核心实现代码如下:

public static String prettyPrintByTransformer(String xmlString, int indent, boolean ignoreDeclaration) {
    try {
        InputSource src = new InputSource(new StringReader(xmlString));
        Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(src);

        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, ignoreDeclaration ? "yes" : "no");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");

        Writer out = new StringWriter();
        transformer.transform(new DOMSource(document), new StreamResult(out));
        return out.toString();
    } catch (Exception e) {
        throw new RuntimeException("Error occurs when pretty-printing xml:\n" + xmlString, e);
    }
}

实现要点:

  1. 解析XML字符串获取Document对象
  2. 配置TransformerFactory的缩进属性
  3. 设置输出属性(编码、声明抑制、缩进开关)
  4. 转换为美化字符串

替代方案:也可直接在transformer实例设置缩进量:

transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(indent));

3.2. 测试方法

测试代码(缩进2空格且跳过XML声明):

public static void main(String[] args) throws IOException {
    InputStream inputStream = XmlPrettyPrinter.class.getResourceAsStream("/xml/emails.xml");
    String xmlString = readFromInputStream(inputStream);
    System.out.println("Pretty printing by Transformer");
    System.out.println("=============================================");
    System.out.println(prettyPrintByTransformer(xmlString, 2, true));
}

Java 8输出结果

<emails>
  <email>
    <from>Kai</from>
    <to>Amanda</to>
    <time>2018-03-05</time>
    <subject>I am flying to you</subject>
  </email>
  <email>
    <from>Jerry</from>
    <to>Tom</to>
    <time>1992-08-08</time>
    <subject>Hey Tom, catch me if you can!</subject>
  </email>
</emails>

踩坑警告:在Java 9+中运行会产生额外空行:

<emails>
   
  <email>
     
    <from>Kai</from>
    <!-- ... 额外空行 ... -->

原因:Java 9+的Transformer对空白节点处理方式变化(详见JDK-8262285)。原始XML中的空白字符(如<emails> <email>)会被保留。

3.3. 提供XSLT文件

为解决版本兼容性问题,创建XSLT文件prettyprint.xsl

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <xsl:output method="xml" encoding="UTF-8"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

关键点:<xsl:strip-space elements="*"/>会移除所有空白节点。

修改Java代码使用XSLT:

Transformer transformer = transformerFactory.newTransformer(
    new StreamSource(new StringReader(readPrettyPrintXslt()))
);

效果:Java 8和9+均产生一致的输出格式。

4. 使用Dom4j库美化XML

Dom4j是流行的XML处理库,首先添加Maven依赖:

<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.3</version>
</dependency>

核心实现代码:

public static String prettyPrintByDom4j(String xmlString, int indent, boolean skipDeclaration) {
    try {
        OutputFormat format = OutputFormat.createPrettyPrint();
        format.setIndentSize(indent);
        format.setSuppressDeclaration(skipDeclaration);
        format.setEncoding("UTF-8");

        org.dom4j.Document document = DocumentHelper.parseText(xmlString);
        StringWriter sw = new StringWriter();
        XMLWriter writer = new XMLWriter(sw, format);
        writer.write(document);
        return sw.toString();
    } catch (Exception e) {
        throw new RuntimeException("Error occurs when pretty-printing xml:\n" + xmlString, e);
    }
}

优势OutputFormat.createPrettyPrint()直接提供预配置的美化格式,只需设置:

  • 缩进大小
  • 是否抑制声明
  • 编码格式

测试代码(缩进8空格且保留XML声明):

System.out.println("Pretty printing by Dom4j");
System.out.println("=============================================");
System.out.println(prettyPrintByDom4j(xmlString, 8, false));

输出结果

<?xml version="1.0" encoding="UTF-8"?>

<emails> 
        <email> 
                <from>Kai</from>  
                <to>Amanda</to>  
                <time>2018-03-05</time>  
                <subject>I am flying to you</subject>
        </email>  
        <email> 
                <from>Jerry</from>  
                <to>Tom</to>  
                <time>1992-08-08</time>  
                <subject>Hey Tom, catch me if you can!</subject> 
        </email> 
</emails>

5. 总结

两种方案对比:

方案 优势 注意事项
Transformer 标准API,无依赖 需处理Java版本差异(推荐使用XSLT)
Dom4j API简洁,版本兼容稳定 需引入第三方依赖

推荐选择

  • 简单粗暴选Dom4j:代码更简洁,无版本兼容坑
  • 严格无依赖选Transformer:记得搭配XSLT文件

完整代码示例可在GitHub仓库获取。


原始标题:Pretty-Print XML in Java | Baeldung