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

szx的开发笔记 2024-07-25 16:35:01 阅读 88

Java操作Word文档

poi-tl介绍

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

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

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

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

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

<code><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

加载远程模板文件

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

下面是示例代码

<code>@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}>code>

<ProFormText name="name" label="名称" placeholder="请输入名称" />code>

<ProFormDigit type={ 'number'} name="age" label="年龄" placeholder="请输入年龄" />code>

</ProForm>

</>

);

};

export default Report;

image-20240523132308219

下载的文件内容

image-20240523101613378

图片

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

<code>@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}}

<code>// 插入表格

@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

表格行循环

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

货物明细需要展示所有货物,<code>{ {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



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。