Java根据word 模板,生成自定义内容的word 文件

Java根据word 模板,生成自定义内容的word 文件

  • 背景
  • [1 使用技术](#1 使用技术)
  • [2 实现方法](#2 实现方法)
  • [3 问题](#3 问题)
  • 4

背景

主要是项目中需要定制化一个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里面实际上渲染后,会出现两个问题。

  1. width属性会丢失,转换后的结果就是等分的。
  2. 前端传给我的没有边界线,没有表格的框线。

没有边框

表格等分

针对于这个等分的情况,解决办法就是:

把原始的html格式转变成css进行处理:

其实我做了很多,一方面是

  1. 原始的html格式转变成css
  2. 为 <table>和 <td> 标签添加边框

到这其实已经结束了,但是我的需求涉及到HTML----->WORD----->HTML(发送邮件)。

  1. 所以我多做了一步处理,就是给 <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的,然后就能成功转换并且成功显示标签了。

4

相关推荐
canyuemanyue2 分钟前
C++单例模式
开发语言·c++·单例模式
何苏三月4 分钟前
设计模式 - 单例模式(懒汉式、饿汉式、静态内部类、枚举)
java·单例模式
Renas_TJOvO7 分钟前
排序算法汇总
java·数据结构·算法
秋恬意18 分钟前
Java 反射机制详解
java·开发语言
黑不溜秋的20 分钟前
C++ 模板专题 - 标签分派(Tag Dispatching)
开发语言·c++·算法
爱上语文25 分钟前
LeetCode每日一题
java·算法·leetcode
skywind30 分钟前
为什么 C 语言数组是从 0 开始计数的?
c语言·开发语言·网络·c++
ღ᭄ꦿ࿐Never say never꧂34 分钟前
重生之我在Java世界------学工厂设计模式
java·设计模式·简单工厂模式·应用场景
尘浮生1 小时前
Java项目实战II基于Spring Boot的火锅店管理系统设计与实现(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·后端·微信小程序·旅游
wrx繁星点点1 小时前
桥接模式:解耦抽象与实现的利器
android·java·开发语言·jvm·spring cloud·intellij-idea·桥接模式