Java根据word 模板,生成自定义内容的word 文件
- 背景
- 1 使用技术
- 2 实现方法
- 依赖
- 啊
- 3 问题
- 4
背景
主要是项目中需要定制化一个word,也就是有一部分是固定的,就是有一个底子,框架,里面的内容是需要填充的。然后填充的内容很多,包括文本框、图片、文本、前端传过来的富文本、表格的设计。
然后网上找了很多资料,没有一个比较详细的文档,就决定自己写一份。
在我看来比较复杂的是
1. 整个文本框的模版如何填充占位符2. 图片的填充如何控制长和宽(如何使用base64填充)3. 前端传过来的富文本,或者单纯的富文本,如何优化格式(比如表格信息的丢失)
上面是文本还有文本框,下面是表格和图片,
1 使用技术
我这里最后采用的是EasyPoi
填充word模版。
实际使用的是JAVA poi-tl-ext
富文本转word。
使用PictureRenderData
来控制生成图片的大小和base64。
2 实现方法
依赖
首先是依赖,在我看来核心的是
<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就够了。
XWPFDocument doc1 = WordExportUtil.exportWord07(templatePath, params).create;
但是我涉及到了富文本的转换,就是我要把富文本渲染成docx支持的格式,所以我换了种方法。
doc = XWPFTemplate.compile(fileInputStream, configure).render(params).getXWPFDocument();
实际上还是获取模版,然后读取占位符,然后
核心代码如下:
// html渲染插件HtmlRenderPolicy htmlRenderPolicy = new HtmlRenderPolicy();// 第一个案例Configure configure = Configure.builder()// 注册html解析插件.bind("content", htmlRenderPolicy)// .bind("content2", htmlRenderPolicy).build();// 映射数据MapMap<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富文本中,涉及一些跟表格有关的问题,最开始的时候,就比如最开的时候前端给我的富文本代码如下:
<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(发送邮件)。
3. 所以我多做了一步处理,就是给 <table>标签添加了一个"width: 100%;"
样式
/*** 处理 HTML:转换 <td> 的宽度为 CSS 样式并为 <table> 和 <td> 标签添加边框* @param html 原始 HTML 字符串* @return 修改后的 HTML 字符串*/public static String convertTdWidthAndAddBorders(String html) {// 解析 HTMLDocument 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));}// 返回修改后的 HTMLreturn 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();}
转换的结果如下:
<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的,然后就能成功转换并且成功显示标签了。