1. 简介

本文将介绍如何使用 Java 中常见的库和模板引擎,将 XML 数据转换为 HTML 页面。我们将涵盖以下几种主流方案:

✅ JAXP(DOM 解析)
✅ StAX(流式解析)
✅ Freemarker 模板引擎
✅ Mustache 模板引擎

这些方法各有适用场景:DOM 适合小文件、结构清晰的数据;StAX 更适合大文件或需要精细控制解析过程的场景;而模板引擎则更适合需要频繁生成 HTML、邮件或配置文件的业务系统。


2. 示例 XML 数据

我们统一使用以下 Jenkins 构建通知作为输入 XML,确保所有示例保持一致:

<?xml version="1.0" encoding="UTF-8"?>
<notification>
    <from>jenkins@company.com</from>
    <heading>Build #7 passed</heading>
    <content>Success: The Jenkins CI build passed</content>
</notification>

目标很明确:

  • 所有示例共用同一份 XML
  • 输出符合 HTML5 标准的完整文档结构
  • 将 XML 元素内容提取并渲染成纯文本展示

最终期望生成的 HTML 如下(提前剧透):

<!DOCTYPE html>
<html lang="en">
<head>
    <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Build #7 passed</title>
</head>
<body>
<p>from: jenkins@company.com</p>
<p>Success: The Jenkins CI build passed</p>
</body>
</html>

3. 使用 JAXP(DOM 方式)

JAXP(Java API for XML Processing)是 Java 内置的标准 XML 处理框架,支持 DOM 和 SAX 解析。这里我们使用 DOM 方式进行演示。

3.1 Maven 依赖

<dependency>
    <groupId>javax.xml</groupId>
    <artifactId>jaxp-api</artifactId>
    <version>1.4.2</version>
</dependency>

⚠️ 注意:虽然 jaxp-api 是标准包,但在现代项目中通常已默认包含,无需显式引入。

3.2 使用 DOM 解析 XML

先将 XML 解析为 Document 对象,再提取所需字段:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

Document input = factory
  .newDocumentBuilder()
  .parse(new File(resourcePath));
Element root = input.getDocumentElement();

📌 安全提示:禁用 DTD 是防止 XXE 攻击的关键步骤,生产环境务必加上。

3.3 提取数据到 Map

Map<String, String> map = new HashMap<>();
map.put("heading", 
  root.getElementsByTagName("heading").item(0).getTextContent());
map.put("from", "from: " + 
  root.getElementsByTagName("from").item(0).getTextContent());
map.put("content", 
  root.getElementsByTagName("content").item(0).getTextContent());

简单粗暴,直接按标签名取值,适合结构固定的 XML。

3.4 使用 DOM 构建 HTML

接下来用 DOM API 手动生成 HTML 文档:

Document doc = factory.newDocumentBuilder().newDocument();

Element html = doc.createElement("html");
html.setAttribute("lang", "en");

Element head = doc.createElement("head");
Element title = doc.createElement("title");
title.setTextContent(map.get("heading"));
head.appendChild(title);
html.appendChild(head);

Element body = doc.createElement("body");

Element from = doc.createElement("p");
from.setTextContent(map.get("from"));
body.appendChild(from);

Element content = doc.createElement("p");
content.setTextContent(map.get("content"));
body.appendChild(content);

html.appendChild(body);
doc.appendChild(html);

3.5 输出 HTML 字符串

最后通过 Transformer 将 DOM 树转为字符串:

TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");

try (Writer output = new StringWriter()) {
    Transformer transformer = transformerFactory.newTransformer();
    transformer.transform(new DOMSource(doc), new StreamResult(output));
    String htmlString = output.toString(); // 获取结果
}

✅ 优点:API 稳定,适合结构化强的小型 XML
❌ 缺点:内存占用高,不适合大文件


4. 使用 StAX(流式解析)

StAX(Streaming API for XML)是一种“拉模式”解析器,允许我们逐个读取事件,适合处理大型 XML 文件。

4.1 Maven 依赖

<dependency>
    <groupId>javax.xml.stream</groupId>
    <artifactId>stax-api</artifactId>
    <version>1.0-2</version>
</dependency>

4.2 使用 StAX 解析 XML

XMLInputFactory factory = XMLInputFactory.newInstance();
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);

XMLStreamReader reader = null;
try (FileInputStream file = new FileInputStream(resourcePath)) {
    reader = factory.createXMLStreamReader(file);
    Map<String, String> map = new HashMap<>();

    while (reader.hasNext()) {
        reader.next();
        if (reader.isStartElement()) {
            switch (reader.getLocalName()) {
                case "heading":
                    map.put("heading", reader.getElementText());
                    break;
                case "from":
                    map.put("from", "from: " + reader.getElementText());
                    break;
                case "content":
                    map.put("content", reader.getElementText());
                    break;
            }
        }
    }
} finally {
    if (reader != null) reader.close();
}

📌 踩坑提醒:一定要关闭 XMLStreamReader,否则可能泄露文件句柄。

4.3 使用 StAX 生成 HTML

虽然 StAX 主要用于读取,但 XMLOutputFactory 也可以用来写入:

try (Writer output = new StringWriter()) {
    XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(output);

    writer.writeDTD("<!DOCTYPE html>");
    writer.writeStartElement("html");
    writer.writeAttribute("lang", "en");

    writer.writeStartElement("head");
    writer.writeDTD("<META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">");
    writer.writeStartElement("title");
    writer.writeCharacters(map.get("heading"));
    writer.writeEndElement(); // title
    writer.writeEndElement(); // head

    writer.writeStartElement("body");

    writer.writeStartElement("p");
    writer.writeCharacters(map.get("from"));
    writer.writeEndElement();

    writer.writeStartElement("p");
    writer.writeCharacters(map.get("content"));
    writer.writeEndElement();

    writer.writeEndElement(); // body
    writer.writeEndElement(); // html
    writer.writeEndDocument();
    writer.flush();

    String html = output.toString();
}

✅ 优点:内存友好,可处理大文件
❌ 缺点:代码略繁琐,容易漏写 writeEndElement


5. 使用模板引擎

如果你需要频繁生成 HTML 或邮件内容,强烈建议使用模板引擎。它们更易维护、扩展性更好。

5.1 使用 Freemarker

Freemarker 是 Java 圈最流行的模板引擎之一,功能强大,支持复杂逻辑。

Maven 依赖

<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.29</version>
</dependency>

模板文件(template.ftl

<!DOCTYPE html>
<html lang="en">
<head>
    <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>${heading}</title>
</head>
<body>
<p>${from}</p>
<p>${content}</p>
</body>
</html>

Java 渲染代码

Configuration cfg = new Configuration(Configuration.VERSION_2_3_29);
cfg.setDirectoryForTemplateLoading(new File(templateDirectory));
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setLogTemplateExceptions(false);
cfg.setWrapUncheckedExceptions(true);

Template template = cfg.getTemplate("template.ftl");
try (StringWriter output = new StringWriter()) {
    template.process(map, output);
    String html = output.toString();
}

📌 提示:process() 第二个参数可以是 Map、POJO 或任何可迭代对象。


5.2 使用 Mustache

Mustache 是“逻辑-less”模板引擎,强调简洁,适合不想在模板里写逻辑的场景。

Maven 依赖

<dependency>
    <groupId>com.github.spullara.mustache.java</groupId>
    <artifactId>compiler</artifactId>
    <version>0.9.6</version>
</dependency>

模板文件(template.mustache

<!DOCTYPE html>
<html lang="en">
<head>
    <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>{{heading}}</title>
</head>
<body>
<p>{{from}}</p>
<p>{{content}}</p>
</body>
</html>

Java 渲染代码

MustacheFactory mf = new DefaultMustacheFactory();
Mustache mustache = mf.compile("template.mustache");

try (StringWriter output = new StringWriter()) {
    mustache.execute(output, map).flush();
    String html = output.toString();
}

✅ 优点:模板干净,不易写出复杂逻辑
❌ 缺点:灵活性较低,条件判断需依赖外部数据预处理


6. 最终输出结果

无论使用哪种方式,最终生成的 HTML 都是一致的:

<!DOCTYPE html>
<html lang="en">
<head>
    <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Build #7 passed</title>
</head>
<body>
<p>from: jenkins@company.com</p>
<p>Success: The Jenkins CI build passed</p>
</body>
</html>

7. 总结

方法 适用场景 推荐指数
JAXP (DOM) 小型、结构固定 XML ⭐⭐⭐
StAX 大文件、需精细控制解析 ⭐⭐⭐⭐
Freemarker 需要频繁生成 HTML/邮件 ⭐⭐⭐⭐⭐
Mustache 模板简单、避免逻辑嵌入 ⭐⭐⭐⭐

📌 核心建议:

  • 如果只是偶尔转换一次,用 DOM 最快上手
  • 处理大 XML 文件?选 StAX,避免 OOM
  • 涉及邮件、通知模板?直接上 Freemarker,维护成本低得多

所有完整示例代码已上传至 GitHub:https://github.com/baeldung/tutorials/tree/master/xml

更多 XML 相关文章推荐:


原始标题:Convert XML to HTML in Java | Baeldung