Java根据word 模板,生成自定义内容的word 文件
背景
主要是项目中需要定制化一个word,也就是有一部分是固定的,就是有一个底子,框架,里面的内容是需要填充的。然后填充的内容很多,包括文本框、图片、文本、前端传过来的富文本、表格的设计。
然后网上找了很多资料,没有一个比较详细的文档,就决定自己写一份。
在我看来比较复杂的是
1. 整个文本框的模版如何填充占位符
2. 图片的填充如何控制长和宽(如何使用base64填充)
3. 前端传过来的富文本,或者单纯的富文本,如何优化格式(比如表格信息的丢失)
上面是文本还有文本框,下面是表格和图片,
1 使用技术
我这里最后采用的是EasyPoi
填充word模版。
实际使用的是JAVA poi-tl-ext
富文本转word。
使用PictureRenderData
来控制生成图片的大小和base64。
2 实现方法
依赖
首先是依赖,在我看来核心的是
xml
<dependency>
<groupId>io.github.draco1023</groupId>
<artifactId>poi-tl-ext</artifactId>
<version>0.4.15</version>
</dependency>
- poi-tl-ext已经包含了poi,poi-tl等jar包,所以无需重复导入
- poi-tl文档链接
- poi-tl-ext github链接
其他的网上还是蛮多的,这个主要就是渲染HTML的,HTML的代码,或者富文本都可以渲染的。
啊
我最开始用的是EasyPoi的方法进行导入的,也就是网上比较常见的下面这样的方法进行填充word模版,如果你只比较简单的数据,那使用EasyPoi就够了。
java
XWPFDocument doc1 = WordExportUtil.exportWord07(templatePath, params).create;
但是我涉及到了富文本的转换,就是我要把富文本渲染成docx支持的格式,所以我换了种方法。
java
doc = XWPFTemplate.compile(fileInputStream, configure).render(params).getXWPFDocument();
实际上还是获取模版,然后读取占位符,然后
java
核心代码如下:
// html渲染插件
HtmlRenderPolicy htmlRenderPolicy = new HtmlRenderPolicy();
// 第一个案例
Configure configure = Configure.builder()
// 注册html解析插件
.bind("content", htmlRenderPolicy)
// .bind("content2", htmlRenderPolicy)
.build();
// 映射数据Map
Map<String, Object> data = new HashMap<>();
data.put("content", content2Html(你的HTML代码));
// 读取模板文件,并渲染数据
XWPFTemplate template = XWPFTemplate.compile(getResourceInputStream("/html2wordtemplate.docx"), configure).render(data);
// 写入文件
template.writeToFile("demo4.docx");
template.close();
我这里是在静态资源里的模版,你也可以读取自己的其他目录下的文件,主要是只要是InputStream
就都可以。
3 问题
其实到上面,最简单的已经结束了,后面主要是我遇到的设计到HTML富文本中,涉及一些跟表格有关的问题,最开始的时候,就比如最开的时候前端给我的富文本代码如下:
xml
<p style="text-indent: 28pt;"><br></p>
<table style="width: auto;">
<tbody>
<tr>
<td colSpan="1" rowSpan="2" width="226">活动名称</td>
<td colSpan="1" rowSpan="2" width="160">计划举办数</td>
<td colSpan="2" rowSpan="1" width="329">实际举办数</td>
<td colSpan="1" rowSpan="1" width="161">延期数</td>
</tr>
<tr>
<td colSpan="1" rowSpan="1" width="152">已成功举办数</td>
<td colSpan="1" rowSpan="1" width="177">筹备完成待举办</td>
<td colSpan="1" rowSpan="1" width="161">延期</td>
</tr>
<tr>
<td colSpan="1" rowSpan="1" width="226">适配开发</td>
<td colSpan="1" rowSpan="1" width="160">54</td>
<td colSpan="1" rowSpan="1" width="152">46</td>
<td colSpan="1" rowSpan="1" width="177">8</td>
<td colSpan="1" rowSpan="1" width="161">0</td>
</tr>
<tr>
<td colSpan="1" rowSpan="1" width="226">边缘缓存系统</td>
<td colSpan="1" rowSpan="1" width="160">24</td>
<td colSpan="1" rowSpan="1" width="152">11</td>
<td colSpan="1" rowSpan="1" width="177">13</td>
<td colSpan="1" rowSpan="1" width="161">0</td>
</tr>
他的表格宽度是内联在td标签中的,这个在HtmlRenderPolicy
里面实际上渲染后,会出现两个问题。
- width属性会丢失,转换后的结果就是等分的。
- 前端传给我的没有边界线,没有表格的框线。
没有边框
表格等分
针对于这个等分的情况,解决办法就是:
把原始的html格式转变成css进行处理:
其实我做了很多,一方面是
- 原始的html格式转变成css
- 为 <table>和 <td> 标签添加边框
到这其实已经结束了,但是我的需求涉及到HTML----->WORD----->HTML(发送邮件)。
- 所以我多做了一步处理,就是给 <table>标签添加了一个
"width: 100%;"
样式
java
/**
* 处理 HTML:转换 <td> 的宽度为 CSS 样式并为 <table> 和 <td> 标签添加边框
* @param html 原始 HTML 字符串
* @return 修改后的 HTML 字符串
*/
public static String convertTdWidthAndAddBorders(String html) {
// 解析 HTML
Document doc = Jsoup.parse(html);
// 获取所有的 <tr> 标签
Elements trs = doc.select("tr");
// 遍历每个 <tr> 标签
for (Element tr : trs) {
Elements tds = tr.select("td");
// 计算当前行所有 <td> 的宽度总和(只针对数值宽度)
int totalWidth = 0;
for (Element td : tds) {
String widthValue = td.attr("width");
// 累加数值格式的宽度
if (!widthValue.isEmpty() && !widthValue.contains("%")) {
totalWidth += Integer.parseInt(widthValue);
}
}
// 如果该行有宽度总和,继续处理
for (Element td : tds) {
String widthValue = td.attr("width");
String existingStyle = td.attr("style"); // 获取现有的 style 属性
String borderStyle = "border: 1px solid #CCC;"; // 四周边框样式
//String borderStyle = "border-right: 1px solid #CCC; border-bottom: 1px solid #CCC;"; // 边框样式
// 处理百分比格式的宽度
if (!widthValue.isEmpty() && widthValue.contains("%")) {
// 如果已有 style,合并宽度和边框样式
td.removeAttr("width");
td.attr("style", mergeStyles(existingStyle, "width: " + widthValue + ";", borderStyle));
}
// 处理数值格式的宽度
else if (!widthValue.isEmpty()) {
int width = Integer.parseInt(widthValue);
if (totalWidth > 0) {
// 计算百分比
double percentWidth = (double) width / totalWidth * 100;
// 如果已有 style,合并宽度和边框样式
td.removeAttr("width");
td.attr("style", mergeStyles(existingStyle, String.format("width: %.2f%%;", percentWidth), borderStyle));
}
} else {
// 直接添加边框样式,如果没有宽度
td.attr("style", mergeStyles(existingStyle, "", borderStyle));
}
}
}
// 将 <table> 标签添加边框样式
Elements tables = doc.select("table");
for (Element table : tables) {
String existingStyle = table.attr("style"); // 获取现有的 style 属性
//String tableBorderStyle = "border-top: 1px solid #CCC; border-left: 1px solid #CCC;"; // 表格边框样式
String tableBorderStyle = "border: 1px solid #CCC;";
table.attr("style", mergeStyles(existingStyle, "width: 100%;", tableBorderStyle));
}
// 返回修改后的 HTML
return doc.toString();
}
/**
* 合并多个 style 属性
* @param existingStyle 原有的 style 属性
* @param newStyle 新的 style 属性
* @param additionalStyle 其他样式(如边框)
* @return 合并后的 style 字符串
*/
private static String mergeStyles(String existingStyle, String newStyle, String additionalStyle) {
StringBuilder mergedStyle = new StringBuilder();
if (existingStyle != null && !existingStyle.trim().isEmpty()) {
mergedStyle.append(existingStyle.trim());
if (!existingStyle.trim().endsWith(";")) {
mergedStyle.append("; ");
}
}
if (!newStyle.isEmpty()) {
mergedStyle.append(newStyle.trim());
if (!newStyle.trim().endsWith(";")) {
mergedStyle.append("; ");
}
}
if (!additionalStyle.isEmpty()) {
mergedStyle.append(additionalStyle.trim());
if (!additionalStyle.trim().endsWith(";")) {
mergedStyle.append("; ");
}
}
return mergedStyle.toString().trim();
}
转换的结果如下:
xml
<tr>
<td colspan="1" rowspan="1" style="width: 34.76%;border: 1px solid #CCC;">节点下线流程</td>
<td colspan="1" rowspan="1" style="width: 16.95%;border: 1px solid #CCC;">3</td>
<td colspan="1" rowspan="1" style="width: 16.59%;border: 1px solid #CCC;">-3</td>
<td colspan="1" rowspan="1" style="width: 16.22%;border: 1px solid #CCC;">54.12%</td>
<td colspan="1" rowspan="1" style="width: 15.49%;border: 1px solid #CCC;">-0.17pp</td>
</tr>
<tr>
<td colspan="1" rowspan="1" style="width: 34.76%;border: 1px solid #CCC;">节点上线流程</td>
<td colspan="1" rowspan="1" style="width: 16.95%;border: 1px solid #CCC;">8</td>
<td colspan="1" rowspan="1" style="width: 16.59%;border: 1px solid #CCC;">-5</td>
<td colspan="1" rowspan="1" style="width: 16.22%;border: 1px solid #CCC;">36.91%</td>
<td colspan="1" rowspan="1" style="width: 15.49%;border: 1px solid #CCC;">+14.02pp</td>
</tr>
会把原始的内联转换成css的,然后就能成功转换并且成功显示标签了。