当前位置: 首页 > article >正文

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

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();
                // 映射数据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富文本中,涉及一些跟表格有关的问题,最开始的时候,就比如最开的时候前端给我的富文本代码如下:

<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(发送邮件)。
3. 所以我多做了一步处理,就是给 <table>标签添加了一个"width: 100%;"样式
在这里插入图片描述

/**
     * 处理 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();
    }

转换的结果如下:

<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


http://www.kler.cn/news/364667.html

相关文章:

  • 一文详解视频参数——FFmpeg -i选项下的视频参数解析
  • Solon Ioc 的魔法 - 注解注入器(也可叫虚空注入器)
  • sql高级
  • 安全见闻(4)——开阔眼界,不做井底之蛙
  • [Linux关键词]内建命令
  • 蚁剑连接本地木马文件报错
  • Kmeans聚类算法简述
  • 计算机毕业设计Hadoop+Hive+Spark+Flink广告推荐系统 广告预测 广告数据分析可视化 广告爬虫 大数据毕业设计 深度学习 机器学习
  • 异地多活架构
  • 列出机器学习方向的创新点
  • Mac 安装 TIDB 并 启动集群
  • Java 如何确保 JS 不被缓存
  • mac切换java版本
  • 【OceanBase探会】云与 AI 赋能一体化数据库的创新之旅
  • 恋爱脑讲编程:Rust 的所有权概念
  • 2-132基于matlab的一种牛头刨床的运动仿真以及运动学分析
  • Vue学习笔记(三)
  • HarmonyOS第一课——HarmonyOS介绍
  • 云+AI 时代的 OceanBase
  • 雷池社区版有多个防护站点监听在同一个端口上,匹配顺序是怎么样的
  • CTFHUB技能树之XSS——过滤关键词
  • 11544 吃东西
  • 扫雷游戏的分析、设计与代码实现详解
  • 2024香港云服务器推荐选择的5大商家整理(top5)
  • LeetCode 344.反转字符串
  • 书生营L0G3000 Git 基础知识