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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/894099.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WPF常见容器全方位介绍

Windows Presentation Foundation (WPF) 是微软的一种用于构建Windows桌面应用程序的UI框架。WPF的布局系统基于容器&#xff0c;帮助开发者以灵活、响应的方式组织用户界面 (UI) 元素。本篇文章将详细介绍WPF中几种常见的容器&#xff0c;包括Grid、StackPanel、WrapPanel、Do…

基于51单片机的proteus数字时钟仿真设计

注意&#xff1a;本项目是本人大学时期的课设项目&#xff0c;不得在未经本人允许下进行转载或商用 数字钟设计 项目背景与意义 在信息化时代&#xff0c;时间管理成为了我们日常生活中不可或缺的一部分。数字钟作为一种常见的时间显示设备&#xff0c;因其精确、直观、易读等…

如何捕捉行情爆发的前兆

在金融市场的激烈角逐中&#xff0c;每一次行情的爆发都是投资者获取丰厚回报的关键时刻。然而&#xff0c;如何识别并把握这些时刻&#xff0c;却是一门需要深厚金融专业知识和敏锐洞察力的艺术。今天&#xff0c;我们就来深入探讨行情爆发的初期信号&#xff0c;揭示那些能够…

Jlink 直接读取单片机数据

1. 驱动版本 因人而异&#xff0c;这里我使用的是 “J-Flash V6.96” 本人驱动链接&#xff1a;夸克网盘 提取码&#xff1a;rgzk 2. 打开软件 3. 创建jlink工程 4. 选择芯片 此处本人使用芯片 “STM32F103VCT6” 5. 连接单片机 连接成功反馈 6. 读取单片机内部数据 …

【2024|FTransUNet|论文解读1】融合视界:解密FTransUNet在遥感语义分割中的创新突破

【2024|FTransUNet|论文解读1】融合视界&#xff1a;解密FTransUNet在遥感语义分割中的创新突破 【2024|FTransUNet|论文解读1】融合视界&#xff1a;解密FTransUNet在遥感语义分割中的创新突破 文章目录 【2024|FTransUNet|论文解读1】融合视界&#xff1a;解密FTransUNet在遥…

web 0基础第四节 多媒体标签

图片标签 主要是讲解 在html 中 怎么将图片放入其中 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <…

Django模型优化

1、创建一个Django项目 可参考之前的带你快速体验Django web应用 我使用的是mysql数据库。按照上述教程完成准备工作。 2、创建一个app并完成注册 demo主要来完成创建用户、修改用户、查询用户、删除用户的操作。 python manage.py startapp test0023、app的目录 新建templ…

【Spring AI】Java实现类似langchain的第三方函数调用_原理与详细示例

Spring AI 介绍 &#xff1a;简化Java AI开发的统一接口解决方案 在过去&#xff0c;使用Java开发AI应用时面临的主要困境是没有统一且标准的封装库&#xff0c;导致开发者需要针对不同的AI服务提供商分别学习和对接各自的API&#xff0c;这增加了开发难度与迁移成本。而Sprin…

【文献综述】扩散模型在文本生成中的进展

【文献综述】扩散模型在文本生成中的进展 Diffusion models in text generation: a survey 摘要&#xff1a; 扩散模型是一种基于数学的模型&#xff0c;最初应用于图像生成。最近&#xff0c;他们对自然语言生成&#xff08;NLG&#xff09;产生了广泛的兴趣&#xff0c;这是…

一起搭WPF架构之livechart的MVVM使用介绍

一起搭WPF架构之livechart使用介绍 前言ModelViewModelView界面设计界面后端 效果总结 前言 简单的架构搭建已经快接近尾声了&#xff0c;考虑设计使用图表的形式将SQLite数据库中的数据展示出来。前期已经介绍了livechart的安装&#xff0c;今天就详细介绍一下livechart的使用…

03 设计模式-创造型模式-单例模式

单例模式&#xff08;Singleton Pattern&#xff09;是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类&#xff0c;该类负责创建自己的对象&#xff0c;同时确保只有单个对象被创建…

HarmonyOS开发(State模型)

一、State模型概述 FA&#xff08;Feature Ability&#xff09;模型&#xff1a;从API 7开始支持的模型&#xff0c;已经不再主推。 Stage模型&#xff1a;从API 9开始新增的模型&#xff0c;是目前主推且会长期演进的模型。在该模型中&#xff0c;由于提供了AbilityStage、Wi…

【MR开发】在Pico设备上接入MRTK3(二)——在Unity中配置Pico SDK

上一篇文档介绍了 【MR开发】在Pico设备上接入MRTK3&#xff08;一&#xff09;在Unity中导入MRTK3依赖 下面将介绍在Unity中导入Pcio SDK的具体步骤 在Unity中导入Pico SDK 当前Pico SDK版本 Unity交互SDK git仓库&#xff1a; https://github.com/Pico-Developer/PICO-Un…

基于SpringBoot+Vue+uniapp微信小程序的垃圾分类系统的详细设计和实现(源码+lw+部署文档+讲解等)

详细视频演示请联系我获取更详细的演示视频 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不…

面试题:Redis(七)

1. 面试题 2. 缓存预热 当Redis暂时没有数据&#xff0c;但MySQL中有数据时&#xff0c;由程序员、中间件、写段程序提前访问该数据&#xff0c;使得数据进行回写进Redis&#xff0c;从而达到缓存预热的效果&#xff0c;这样可以使得一开始访问页面程序的用户也没有卡顿&#x…

机器学习核心:监督学习与无监督学习

个人主页&#xff1a;chian-ocean 文章专栏 监督学习与无监督学习&#xff1a;深度解析 机器学习是现代人工智能的核心支柱&#xff0c;已广泛应用于从数据挖掘到计算机视觉再到自然语言处理的诸多领域。作为机器学习最主要的两大类型&#xff0c;监督学习&#xff08;Super…

自定义注解和组件扫描在Spring Boot中动态注册Bean(一)

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 在Spring Boot中&#xff0c;自定义注解和组件扫描是两种强大的机制&#xff0c;它们允许开发者以声明性的方式动态注册Bean。这种方式不仅提高了代码的可读性和可维护性&#xff0c;还使得Spring Boot应用的…

【Windows】Devops jenkins pipeline调用powershell脚本 New-PSSession报错 连接到远程服务器 失败 拒绝访问

错误 powershell.exe : New-PSSession : [192.168.1.1] 连接到远程服务器 192.168.1.1 失败&#xff0c;并显示以下错误消息: 拒绝访问 原因 Windows 平台默认安装的jenkins启动用用户是SYSTEM 创建一个用户&#xff08;如&#xff1a; yeqiang&#xff09;隶属于Administra…

嵌入式职业规划

嵌入式职业规划 在嵌入式的软件开发中&#xff0c;可以分为&#xff1a; 嵌入式MCU软件开发工程师&#xff1b; 嵌入式Linux底层&#xff08;BSP&#xff09;软件开发工程师&#xff1b; 嵌入式Linux应用开发工程师&#xff1b; 嵌入式FPGA算法开发工程师 对于前两个阶段 …

FastGPT本地开发 之 通过Navicat管理MongoDB、PostgreSQL数据库

1. 背景 前期已经完成FastGPT的本地化部署工作&#xff0c;通过Docker启动FastGPT的相关容器即可运行。&#xff08;共6个容器&#xff09; 2.本地化开发 2.1 前置依赖 2.2 源码拉取 git clone gitgithub.com:labring/FastGPT.git2.3 数据库管理 本地化运行的FastGPT使用…