Java操作Word文档,根据模板生成文件

Java操作Word文档

poi-tl介绍

官方文档:https://deepoove.com/poi-tl/

poi-tl(poi template language)是Word模板引擎,使用模板和数据创建很棒的Word文档

在文档的任何地方做任何事情(Do Anything Anywhere)是poi-tl的星辰大海。

方案移植性功能性易用性
Poi-tlJava跨平台Word模板引擎,基于Apache POI,提供更友好的API低代码,准备文档模板和数据即可
Apache POIJava跨平台Apache项目,封装了常见的文档操作,也可以操作底层XML结构文档不全,这里有一个教程:Apache POI Word快速入门
FreemarkerXML跨平台仅支持文本,很大的局限性不推荐,XML结构的代码几乎无法维护
OpenOffice部署OpenOffice,移植性较差-需要了解OpenOffice的API
HTML浏览器导出依赖浏览器的实现,移植性较差HTML不能很好的兼容Word的格式,样式糟糕-
Jacob、winlibWindows平台-复杂,完全不推荐使用

poi-tl是一个基于Apache POI的Word模板引擎,也是一个免费开源的Java类库,你可以非常方便的加入到你的项目中,并且拥有着让人喜悦的特性。

Word模板引擎功能描述
文本将标签渲染为文本
图片将标签渲染为图片
表格将标签渲染为表格
列表将标签渲染为列表
图表条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、饼图(3D饼图)、散点图等图表渲染
If Condition判断根据条件隐藏或者显示某些文档内容(包括文本、段落、图片、表格、列表、图表等)
Foreach Loop循环根据集合循环某些文档内容(包括文本、段落、图片、表格、列表、图表等)
Loop表格行循环复制渲染表格的某一行
Loop表格列循环复制渲染表格的某一列
Loop有序列表支持有序列表的循环,同时支持多级列表
Highlight代码高亮word中代码块高亮展示,支持26种语言和上百种着色样式
Markdown将Markdown渲染为word文档
Word批注完整的批注功能,创建批注、修改批注等
Word附件Word中插入附件
SDT内容控件内容控件内标签支持
Textbox文本框文本框内标签支持
图片替换将原有图片替换成另一张图片
书签、锚点、超链接支持设置书签,文档内锚点和超链接功能
Expression Language完全支持SpringEL表达式,可以扩展更多的表达式:OGNL, MVEL…
样式模板即样式,同时代码也可以设置样式
模板嵌套模板包含子模板,子模板再包含子模板
合并Word合并Merge,也可以在指定位置进行合并
用户自定义函数(插件)插件化设计,在文档任何位置执行函数

快速上手

Maven

<dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.12.2</version>
</dependency>

准备一个模板文件,占位符使用双大括号占位

你好,我是{{name}},今年{{age}}

然后将模板放在 resources 目录下,编写代码

@Test
void test1() {// 定义模板对应的数据HashMap<String, Object> data = new HashMap<>();data.put("name", "张三");data.put("age", 18);// 加载本地模板文件InputStream inputStream = getClass().getResourceAsStream("/演示模板1.docx");// 渲染模板XWPFTemplate template = XWPFTemplate.compile(inputStream).render(data);try {// 写出到文件template.writeAndClose(new FileOutputStream("output.docx"));} catch (IOException e) {throw new RuntimeException(e);}
}

效果展示

image-20240523094721108

加载远程模板文件

在实际业务场景中,模板可能会有很多,并且不会保存在本地,这时就需要加载远程模板来进行处理

下面是示例代码

@Test
void test2() {try {// 加载远程模板String templateUrl ="https://szx-bucket1.oss-cn-hangzhou.aliyuncs.com/picgo/%E6%BC%94%E7%A4%BA%E6%A8%A1%E6%9D%BF1.docx";URL url = new URL(templateUrl);HttpURLConnection conn = (HttpURLConnection) url.openConnection();InputStream inputStream = conn.getInputStream();// 定义模板对应的数据HashMap<String, Object> data = new HashMap<>();data.put("name", "张三");data.put("age", 18);// 渲染模板XWPFTemplate template = XWPFTemplate.compile(inputStream).render(data);// 写出到文件template.writeAndClose(new FileOutputStream("output2.docx"));} catch (Exception e) {throw new RuntimeException(e);}
}

编写接口返回处理后的文件

下面我们来实现编写一个接口,前端访问时携带参数,后端完成编译后返回文件给前端下载

@Api(tags = "模板管理")
@RestController
@RequestMapping("/word")
public class WordController {@GetMapping("getWord")public void getWord(String name, Integer age, HttpServletResponse response) {// 定义模板对应的数据HashMap<String, Object> data = new HashMap<>();data.put("name", name);data.put("age", age);// 加载本地模板文件InputStream inputStream = getClass().getResourceAsStream("/演示模板1.docx");// 渲染模板XWPFTemplate template = XWPFTemplate.compile(inputStream).render(data);// 设置响应头,指定文件类型和内容长度response.setContentType("application/octet-stream");response.setHeader("Content-Disposition", "attachment; filename=output.docx");try {// 返回网络流ServletOutputStream out = response.getOutputStream();BufferedOutputStream bos = new BufferedOutputStream(out);template.write(bos);bos.flush();out.flush();// 关闭流PoitlIOUtils.closeQuietlyMulti(template, bos, out);} catch (IOException e) {throw new RuntimeException(e);}}
}

前端代码编写

定义接口地址,并且请求中声明 responseType

import { request } from '@umijs/max';// 下载报告
export async function getWordFun(age, name) {return request(`/word/getWord?age=${age}&name=${name}`, {method: 'get',responseType: 'blob', // 使用blob下载});
}

然后响应拦截器中判断 responseType

requestErrorConfig.ts

/*** @name 错误处理* pro 自带的错误处理, 可以在这里做自己的改动* @doc https://umijs.org/docs/max/request#配置*/
export const errorConfig: RequestConfig = {// 响应拦截器responseInterceptors: [(response) => {// 拦截响应数据,进行个性化处理const res = response as unknown as ResponseStructure;// 判断流数据if (res.request.responseType === 'blob') {return response;}// 判断状态码if (!sucCodes.includes(res.data?.code)) {return Promise.reject(res.data);}return response;},],
};

编写页面代码

import React from 'react';
import { ProForm, ProFormDigit, ProFormText } from '@ant-design/pro-components';
import { getWordFun } from '@/services/ant-design-pro/reportApi';const Report = () => {const onFinish = async (values) => {let res = await getWordFun(values.age, values.name);// 接收流文件数据并下载const blob = new Blob([res], {type: res.type,});const link = document.createElement('a');link.href = URL.createObjectURL(blob);link.download = 'test.docx';link.click();};return (<><ProForm title="新建表单" onFinish={onFinish}><ProFormText name="name" label="名称" placeholder="请输入名称" /><ProFormDigit type={'number'} name="age" label="年龄" placeholder="请输入年龄" /></ProForm></>);
};export default Report;

image-20240523132308219

下载的文件内容

image-20240523101613378

图片

图片标签以@开始:{{@var}}

@Test
void test3() {// 定义模板对应的数据HashMap<String, Object> data = new HashMap<>();data.put("name", "张三");data.put("age", 18);data.put("img", Pictures.ofUrl("http://deepoove.com/images/icecream.png").size(100, 100).create());// 加载本地模板文件InputStream inputStream = getClass().getResourceAsStream("/演示模板1.docx");// 渲染模板XWPFTemplate template = XWPFTemplate.compile(inputStream).render(data);try {// 写出到文件template.writeAndClose(new FileOutputStream("output.docx"));} catch (IOException e) {throw new RuntimeException(e);}
}

image-20240523134209524

image-20240523134123298

表格

表格标签以#开始:{{#var}}

// 插入表格
@Test
void test4() {// 定义模板对应的数据HashMap<String, Object> data = new HashMap<>();data.put("name", "张三");data.put("age", 18);data.put("img", Pictures.ofUrl("http://deepoove.com/images/icecream.png").size(100, 100).create());// 第0行居中且背景为蓝色的表格RowRenderData row0 =Rows.of("学历", "时间").textColor("FFFFFF").bgColor("4472C4").center().create();RowRenderData row1 = Rows.create("本科", "2015~2019");RowRenderData row2 = Rows.create("研究生", "2019~2021");data.put("eduList", Tables.create(row0, row1, row2));// 加载本地模板文件InputStream inputStream = getClass().getResourceAsStream("/演示模板1.docx");// 渲染模板XWPFTemplate template = XWPFTemplate.compile(inputStream).render(data);try {// 写出到文件template.writeAndClose(new FileOutputStream("output.docx"));} catch (IOException e) {throw new RuntimeException(e);}
}

image-20240523135141764

image-20240523135112583

表格行循环

我们希望根据一个集合的内容来决定表格的行数,这是就用到表格行循环

货物明细需要展示所有货物,{{goods}} 是个标准的标签,将 {{goods}} 置于循环行的上一行,循环行设置要循环的标签和内容,注意此时的标签应该使用 [] ,以此来区别poi-tl的默认标签语法。

示例代码

// 循环行表格
@Test
void test5() {Good good = new Good();good.setName("小米14");good.setPrice("4599");good.setColor("黑色");good.setTime("2024-05-23");Good good2 = new Good();good2.setName("苹果15");good2.setPrice("7599");good2.setColor("黑色");good2.setTime("2024-05-23");Good good3 = new Good();good3.setName("华为Meta60");good3.setPrice("7999");good3.setColor("白色");good3.setTime("2024-05-23");ArrayList<Good> goods = new ArrayList<>();goods.add(good);goods.add(good2);goods.add(good3);// 定义模板对应的数据HashMap<String, Object> data = new HashMap<>();data.put("name", "张三");data.put("age", 18);data.put("img", Pictures.ofUrl("http://deepoove.com/images/icecream.png").size(100, 100).create());// 第0行居中且背景为蓝色的表格RowRenderData row0 =Rows.of("学历", "时间").textColor("FFFFFF").bgColor("4472C4").center().create();RowRenderData row1 = Rows.create("本科", "2015~2019");RowRenderData row2 = Rows.create("研究生", "2019~2021");data.put("eduList", Tables.create(row0, row1, row2));// 添加采购列表数据data.put("goods", goods);// 加载本地模板文件InputStream inputStream = getClass().getResourceAsStream("/演示模板1.docx");// 定义行循环插件LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();// 绑定插件Configure config = Configure.builder().bind("goods", policy).build();// 渲染模板XWPFTemplate template = XWPFTemplate.compile(inputStream, config).render(data);try {// 写出到文件template.writeAndClose(new FileOutputStream("output.docx"));} catch (IOException e) {throw new RuntimeException(e);}
}@Data
public class Good {private String name;private String price;private String color;private String time;
}

image-20240523142219092

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

el-select可选择可搜索可输入新内容

需求&#xff1a;el-form-item添加el-select&#xff0c;并且el-select可选择可搜索可输入新内容&#xff0c;并且和其他的el-input做联动&#xff0c;如果是选择&#xff0c;那么el-input自动回填数据并且不可编辑&#xff0c;如果el-select输入新的内容&#xff0c;那么el-in…

【NumPy】关于numpy.transpose()函数,看这一篇文章就够了

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

如何使用 CapSolver 扩展找到 Google reCAPTCHA 站点密钥?

网站安全性在当今至关重要&#xff0c;Google reCAPTCHA 作为防止垃圾邮件和滥用行为的前线防御系统起着关键作用。reCAPTCHA 站点密钥是确保网站交互由人类驱动的唯一标识符。了解如何找到这个密钥对于网站管理员和开发人员来说至关重要。 什么是 reCAPTCHA 站点密钥 reCAPT…

Unity开发——好用的数值概率公式

1、血量、伤害两个因素作用&#xff0c;击杀目标 正常状态下&#xff1a;hp - attackValue; 特殊状态下&#xff1a;attackValue *2; //伤害翻倍 如飞机/坦克大战中&#xff0c;击杀对方&#xff1b;受到伤害时&#xff0c;装备道具磨损失效&#xff1b; public int…

MySQL主从复制(一):主备一致

MySQL主备的基本原理 如图所示就是基本的主备切换流程&#xff1a; 在状态1中&#xff0c; 客户端的读写都直接访问节点A&#xff0c; 而节点B是A的备库&#xff0c; 只是将A的更新都同步过来&#xff0c; 到本地执行。 这样可以保持节点B和A的数据是相同的。 当需要切换的时候…

spark的简单学习一

一 RDD 1.1 RDD的概述 1.RDD&#xff08;Resilient Distributed Dataset&#xff0c;弹性分布式数据集&#xff09;是Apache Spark中的一个核心概念。它是Spark中用于表示不可变、可分区、里面的元素可并行计算的集合。RDD提供了一种高度受限的共享内存模型&#xff0c;即RD…

IDEA连接MySQL后如何管理数据库

上一节讲解了IDEA如何连接MySQL数据库管理系统&#xff0c;接下来我们就可以在IDEA里使用MySQL来管理数据库了。那么如果我们现在还没有创建需要的数据库怎么办&#xff1f;本节就来教大家如何在IDEA连接MySQL后管理数据库(创建/修改/删除数据库、创建/修改/删除表、插入/更新/…

电子招投标系统源码实现与立项流程:基于Spring Boot、Mybatis、Redis和Layui的企业电子招采平台

随着企业的快速发展&#xff0c;招采管理逐渐成为企业运营中的重要环节。为了满足公司对内部招采管理提升的要求&#xff0c;建立一个公平、公开、公正的采购环境至关重要。在这个背景下&#xff0c;我们开发了一款电子招标采购软件&#xff0c;以最大限度地控制采购成本&#…

【k8s】存储 pvc 参数列表

相关文章&#xff1a; 【K8s】初识PV和PVC 【k8s】存储 pv 参数列表 【k8s】存储 pvc 参数列表 1. pv概述 2. 参数列表 [rootpaas-controller-3:/home/ubuntu]$ kubectl explain pvc.spec KIND: PersistentVolumeClaim VERSION: v1RESOURCE: spec <Object>DESCRI…

【Vue2.x】props技术详解

1.什么是prop&#xff1f; 定义&#xff1a;组件标签上注册的一些自定义属性作用&#xff1a;向子组件传递数据特点 可以传递任意数量的prop可以传递任意类型的prop 2.prop校验 为了避免乱传数据&#xff0c;需要进行校验 完整写法 将之前props数组的写法&#xff0c;改为对象…

【SQL Server001】SQLServer2016常用函数实战总结(已更新)

1.熟悉、梳理、总结下SQL Server相关知识体系。 2.日常研发过程中使用较少&#xff0c;随着时间的推移&#xff0c;很快就忘得一干二净&#xff0c;所以梳理总结下&#xff0c;以备日常使用参考 3.欢迎批评指正&#xff0c;跪谢一键三连&#xff01; 总结源文件资源下载地址&am…

鸿蒙应用开发系列 篇六:鸿蒙系统应用生态与发布、推广

文章目录 系列文章开发过程的准备发布应用分发AGC发布应用(APK)应用资质审核要求发布HarmonyOS应用应用资质审核要求鸿蒙生态推广与变现系列文章 鸿蒙应用开发系列 篇一:鸿蒙系统概述 鸿蒙应用开发系列 篇二:鸿蒙系统开发工具与环境

Ubuntu切换内核版本

#安装内核安装工具 sudo apt-get install software-properties-common sudo add-apt-repository ppa:cappelikan/ppa sudo apt-get update sudo apt-get install mainline#安装指定内核版本(有些版本并不能安装成功) mainline install 5.14.10#更新GRUB配置 sudo update-grub#查…

如何查看CO凭证号码及明细

如何查看CO凭证号码及明细: 1)原始凭证上点菜单栏上的“环境”,选择“会计核算凭证”,控制凭证就是CO的凭证号码. 2)KSB5 查看CO凭证明细, 3)FB03处查看原始凭证&#xff0c;选菜单上环境--会计核算--控制凭证. CO凭证相关的主要表&#xff1a;COBK&#xff08;凭证头&#xff…

PE文件(六)新增节-添加代码

本节的目的是在所有节的空白区都不够存放我们要添加的代码时&#xff0c;教会我们新增一个足够大的节来添加代码 添加节 一.判断是否有足够的空间可以添加一个节表&#xff1a;新增节需要新增一个节表来记录此节信息 判断方法&#xff1a;SizeOfHeader - (DOS 垃圾数据 PE…

java常见算法

这里写目录标题 1.二分查询法2.冒泡排序算法3.快速查询算法 1.二分查询法 要求待查询的序列是有序数列, 每个取中间位置的值与目标值进行比较 如果目标值 > 中间位置, 目标值在右部分, 起始索引更新为mid 1如果目标值 中间位置, 则直接返回如果目标值 < 中间位置, 目…

全网最全爬取-b站爬取弹幕+评论之js逆向与xml降本增效

&#x1f31f; ❤️ 作者&#xff1a;yueji0j1anke 首发于公号&#xff1a;剑客古月的安全屋 字数&#xff1a;801 阅读时间: 10min 声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及…

【C语言深度解剖】(14):结构体内存对齐(详细配图讲解)

&#x1f921;博客主页&#xff1a;醉竺 &#x1f970;本文专栏&#xff1a;《C语言深度解剖》 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多C语言深度解剖点击专栏链接查看&…

只有题目和摘要,能匹配到适合发发表的期刊吗

美国时间5月20日更新的Journal List中&#xff0c;SCIE期刊数据库剔除(Dropped)或停止检索(Ceased)了这2本期刊&#xff01;我们整理了期刊的详细信息供大家参考&#xff01; 01《EUROPEAN REVIEW FOR MEDICAL AND PHARMACOLOGICAL SCIENCES》 ISSN&#xff1a;1128-3602 出…

椋鸟C++笔记#3:类的默认成员函数

文章目录 类的默认成员函数构造函数&#xff08;Constructor&#xff09;构造函数的特点 析构函数 &#xff08;Destructor&#xff09;析构函数的特点 拷贝构造函数&#xff08;Copy Constructor&#xff09;拷贝构造函数的特点 运算符重载&#xff08;Operator Overloading&a…