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字符串转换为美化格式。同时需要支持两个常用配置:
- 缩进大小(整数)
- 是否抑制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);
}
}
实现要点:
- 解析XML字符串获取
Document
对象 - 配置
TransformerFactory
的缩进属性 - 设置输出属性(编码、声明抑制、缩进开关)
- 转换为美化字符串
替代方案:也可直接在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仓库获取。