在企业级系统开发中,XML报表是数据交互和归档的常用格式,需要严格遵循预设的标签结构和数据规范。单独使用Velocity模板引擎虽然能完成变量替换,但难以处理复杂的节点嵌套和格式校验;仅用DOM操作生成XML又会导致代码冗余,维护难度大。将两者深度结合,既能发挥Velocity的模板复用优势,又能通过DOM操作保障XML结构的合规性。

核心思路概述
整个实现流程分为三个核心步骤:首先定义符合企业规范的XML模板,使用Velocity语法标记动态数据占位符;然后将业务数据封装为上下文传入Velocity引擎,生成初步的XML字符串;最后通过DOM解析器加载该字符串,对节点进行二次校验、补充固定属性或调整嵌套结构,输出最终合规的XML报表。
环境准备与依赖引入
需要引入Velocity引擎核心依赖和XML DOM操作相关依赖,以Maven项目为例,在pom.xml中添加如下配置:
<dependencies>
<!-- Velocity模板引擎依赖 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<!-- XML DOM解析依赖 -->
<dependency>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.4.01</version>
</dependency>
</dependencies>
Velocity模板定义
首先定义企业级XML报表的基础模板,模板中预留动态数据占位符,同时保留固定的报表头、尾结构。示例如下,模板文件命名为report_template.vm:
<?xml version="1.0" encoding="UTF-8"?>
<enterprise_report>
<report_header>
<report_id>${reportId}</report_id>
<report_name>${reportName}</report_name>
<generate_time>${generateTime}</generate_time>
</report_header>
<report_body>
<data_list>
#foreach($item in $dataList)
<data_item>
<item_id>${item.id}</item_id>
<item_name>${item.name}</item_name>
<item_value>${item.value}</item_value>
<item_status>${item.status}</item_status>
</data_item>
#end
</data_list>
</report_body>
<report_footer>
<total_count>${totalCount}</total_count>
</report_footer>
</enterprise_report>
Velocity引擎初始化与数据填充
初始化Velocity引擎,将业务数据封装到上下文对象中,执行模板渲染得到初步的XML字符串。代码实现如下:
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class XmlReportGenerator {
// 初始化Velocity引擎
private static VelocityEngine initVelocityEngine() {
VelocityEngine velocityEngine = new VelocityEngine();
Properties props = new Properties();
// 设置模板加载路径为classpath下的templates目录
props.setProperty("resource.loader", "class");
props.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
velocityEngine.init(props);
return velocityEngine;
}
// 封装业务数据
static class ReportDataItem {
private String id;
private String name;
private String value;
private String status;
public ReportDataItem(String id, String name, String value, String status) {
this.id = id;
this.name = name;
this.value = value;
this.status = status;
}
// getter方法省略
}
// 生成初步XML字符串
public static String generateTempXml() {
VelocityEngine engine = initVelocityEngine();
Template template = engine.getTemplate("templates/report_template.vm", "UTF-8");
VelocityContext context = new VelocityContext();
// 填充报表基础信息
context.put("reportId", "REP-20240501-001");
context.put("reportName", "月度销售数据报表");
context.put("generateTime", "2024-05-01 14:30:00");
// 填充报表数据列表
List<ReportDataItem> dataList = new ArrayList<>();
dataList.add(new ReportDataItem("001", "产品A", "150000", "已审核"));
dataList.add(new ReportDataItem("002", "产品B", "230000", "已审核"));
dataList.add(new ReportDataItem("003", "产品C", "98000", "待审核"));
context.put("dataList", dataList);
context.put("totalCount", dataList.size());
StringWriter writer = new StringWriter();
template.merge(context, writer);
return writer.toString();
}
}
DOM节点深度处理与合规校验
得到Velocity渲染后的XML字符串后,使用DOM解析器加载文档,对节点进行深度处理,比如补充固定属性、校验必填节点、调整嵌套结构,确保最终XML符合企业规范。
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
public class DomXmlProcessor {
// 处理XML字符串并输出合规报表
public static String processXml(String tempXml) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
// 将Velocity生成的XML字符串转为Document对象
Document document = builder.parse(new ByteArrayInputStream(tempXml.getBytes("UTF-8")));
// 1. 为根节点补充企业要求的固定属性
Element rootElement = document.getDocumentElement();
rootElement.setAttribute("xmlns", "http://ipipp.com/report/schema");
rootElement.setAttribute("version", "1.0");
// 2. 校验必填节点是否存在,不存在则补充默认值
NodeList reportHeaderList = document.getElementsByTagName("report_header");
if (reportHeaderList.getLength() == 0) {
Element reportHeader = document.createElement("report_header");
rootElement.appendChild(reportHeader);
}
// 3. 处理数据节点,为状态为待审核的节点添加标记属性
NodeList dataItemList = document.getElementsByTagName("data_item");
for (int i = 0; i < dataItemList.getLength(); i++) {
Element dataItem = (Element) dataItemList.item(i);
NodeList statusList = dataItem.getElementsByTagName("item_status");
if (statusList.getLength() > 0) {
String status = statusList.item(0).getTextContent();
if ("待审核".equals(status)) {
dataItem.setAttribute("need_check", "true");
}
}
}
// 将处理后的Document转回XML字符串
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty("encoding", "UTF-8");
transformer.setOutputProperty("indent", "yes");
DOMSource source = new DOMSource(document);
StringWriter resultWriter = new StringWriter();
StreamResult result = new StreamResult(resultWriter);
transformer.transform(source, result);
return resultWriter.toString();
}
// 完整流程调用示例
public static void main(String[] args) throws Exception {
String tempXml = XmlReportGenerator.generateTempXml();
String finalXml = processXml(tempXml);
System.out.println(finalXml);
}
}
常见问题与解决方案
- 模板中特殊字符转义问题:如果XML模板中包含
<、>等特殊字符,需要在Velocity模板中使用转义语法,或者在渲染后对字符串做二次替换,避免DOM解析报错。 - 数据为空时的节点处理:可以在Velocity模板中使用
#if指令判断数据是否为空,也可以在DOM处理阶段删除空节点,保证报表结构整洁。 - 编码一致性问题:Velocity渲染、DOM解析、最终输出三个环节的编码需要统一设置为UTF-8,避免出现中文乱码。
方案优势总结
这种结合方式既保留了Velocity模板的灵活性,业务方可以直接调整模板内容而不需要修改代码,又通过DOM操作实现了XML结构的严格校验,满足企业级报表的格式合规要求。同时模板和逻辑分离,降低了后续维护成本,适合需要频繁调整报表格式的业务场景。