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

news2024/10/6 22:30:29

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.coloradmin.cn/o/1688312.html

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

相关文章

在全志H616核桃派开发板上配置I2C引脚并读取温度数据

配置引脚 找到板子上的i2c引脚 为了方便查找&#xff0c;我们加入了一个显示功能引脚位置的功能&#xff0c;运行以下命令&#xff0c;查看板子的40pin引脚上有几个可用i2c gpio pin i2c启用i2c 我们使用set-device指令来使能/关闭指定设备的底层驱动&#xff0c;使能后&am…

视频汇聚/云存储/安防监控EasyCVR接入GB28181设备未回复ack信息的原因排查

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。 用户反馈&#xff0c;设备通过国标GB28181注…

Docker 容器间通讯

1、虚拟ip/访问 同一网络 安装docker时&#xff0c;docker会默认创建一个内部的桥接网络docker0&#xff0c;每创建一个容器分配一个虚拟网卡&#xff0c;容器之间(包括宿主机)可以根据分配的ip互相访问(ps:其他主机(包括其他主机的容器)无法ping通docker容器ip无法访问&#…

RepOptimizer原理与代码解析(ICLR 2023)

paper&#xff1a;Re-parameterizing Your Optimizers rather than Architectures offcial implementation&#xff1a;https://github.com/dingxiaoh/repoptimizers 背景 神经网络的结构设计是将先验知识融入模型中。例如将特征转换建模成残差相加的形式&#xff08;\(yf(x…

Zoho CRM怎么样?云衔科技为企业提供采购优惠!

企业对于客户关系管理&#xff08;CRM&#xff09;系统的需求日益增加&#xff0c;Zoho CRM作为一款备受赞誉的国际CRM服务提供商&#xff0c;凭借其全面的功能、出色的用户体验和卓越的性价比&#xff0c;成为了众多企业数字化转型的得力助手。 Zoho CRM是一款覆盖客户全生命…

Vue状态管理深度剖析:Vuex vs Pinia —— 从原理到实践的全面对比

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 &#x1f44b; 引言&#x1f4cc; Vuex 基础知识核心构成要素示例代码 &#x1f4cc; Pinia 基础知识核心构成要素示例代码 &#x1f4cc; Vuex与Pinia的区别&#x1f4cc; 使用示例与对比&#x1f4cc; 总结 &#x1f44b;…

Transormer(2)-位置编码

位置编码公式 偶数位置用sin,奇数位置用cos. d_model 表示token的维度&#xff1b;pos表示token在序列中的位置&#xff1b;i表示每个token编码的第i个位置&#xff0c;属于[0,d_model)。 torch实现 import math import torch from torch import nn from torch.autograd im…

Vue 3 的 setup语法糖工作原理

前言 我们每天写vue3项目的时候都会使用setup语法糖&#xff0c;但是你有没有思考过下面几个问题。setup语法糖经过编译后是什么样子的&#xff1f;为什么在setup顶层定义的变量可以在template中可以直接使用&#xff1f;为什么import一个组件后就可以直接使用&#xff0c;无需…

【如何让论文中摘要后面的内容不出现在目录中】

首先选择摘要二字&#xff0c;设置为一级标题&#xff0c;然后选择摘要后面的内容设置为正文样式&#xff0c;再选择这一部分看一下是不是都是正文大纲级别&#xff0c;如果是那就可以了。 具体流程如下 1、选择摘要二字&#xff0c;设置为一级标题样式 2、选择摘要后面的文…

FreeRTOS学习——FreeRTOS队列(下)之队列创建

本篇文章记录我学习FreeRTOS队列创建的知识。主要分享队列创建需要使用的初始化函数、队列复位函数。 需要进一步了解FreeRTOS队列的相关知识&#xff0c;读者可以参考以下文章&#xff1a; FreeRTOS学习——FreeRTOS队列&#xff08;上&#xff09;_freertos 单元素队列-CSDN博…

scikit-learn机器学习要点总结

目录 一、机器学习总体流程二、引入数据集三、将数据集转换为DataFrame四、可视化数据五、数据预处理&#xff08;一&#xff09;数据标准化&#xff08;二&#xff09;独热编码 六、数据集划分为训练集和测试集七、创建模型估计器(estimator)&#xff08;一&#xff09;用于回…

人力资源(HR)OKR 案例

HR人员 #OKR# 是一个很好的方法来建立一致性&#xff0c;吸引团队成员&#xff0c;并实现高绩效。 在本文中&#xff0c;我们将回答以下问题&#xff1a; 如何写好HR OKR &#xff1f; 什么是好的HR OKR 的例子 &#xff1f; 我应该在我的HR OKR 中填写什么 &#xff1f; 如何…

stream( ).collect ( Collectors.groupingBy ( ) ) 的用法

文章目录 第一种解释1、基本用法2、指定值收集器3、多级分组4、常见应用场景和用处 第二种解释1、基本语法2、示例3、更复杂的用法 第一种解释 Collectors.groupingBy 是 Java 8 引入的 Stream API 中的一个收集器&#xff08;Collector&#xff09;&#xff0c;它用于将流&am…

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

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

16.js数学方法和进制转换

数学方法 &#xff08;1&#xff09;Math.random() 默认生成0-1的随机数 var resMath.random() console.log(res) &#xff08;2&#xff09;Math.round(数字) 取整&#xff1a;正数-四舍五入 负数-5舍6入 var resMath.round(11)console.log(res) //11var res1Math.round(1…

2024-5-6-从0到1手写配置中心Config之实现配置中心客户端

配置加载原理 在Spring中PropertySource类实现了所有属性的实例化。 启动赋值&#xff1a; 定义自定义属性配置源&#xff0c;从config-server获取全局属性&#xff1b;Spring启动时&#xff0c;插入自定义属性配置源&#xff1b;绑定属性会优先使用&#xff0c;给自定义属性…

下拉框操作/键鼠操作/文件上传

在我们做UI自动化测试的时候&#xff0c;会有一些元素需要特殊操作&#xff0c;比如下拉框操作/键鼠操作/文件上传。 下拉框操作 在我们很多页面里有下拉框的选择&#xff0c;这种元素怎么定位呢&#xff1f;下拉框分为两种类型&#xff1a;我们分别针对这两种元素进行定位和…

移动硬盘容量消失无法读取的解决方案

在数字化时代&#xff0c;数据的存储和备份变得尤为重要。移动硬盘作为一种便捷、大容量的存储设备&#xff0c;受到许多人的青睐。然而&#xff0c;有时我们可能会遭遇这样的问题&#xff1a;移动硬盘不显示容量且无法访问。这种情况无疑给我们的数据存储和管理带来了巨大的困…

sequence cache太小导致enq: SQ – contention

当业务卡的时候&#xff0c;发现大量等待事件为enq: SQ – contention&#xff0c;检查awr的top 5事件&#xff1a; sql语句对sequence的调用非常频繁&#xff1a; 对这些语句排查发现sequence cache值均为默认20&#xff0c;调大cache到1000值&#xff1a; SQL> select SE…

基于YOLOV8/YOLOV5的PCB板缺陷检测识别系统

摘要&#xff1a; 本文详细阐述了一个利用深度学习进行PCB板缺陷检测的系统&#xff0c;该系统集成了最新的YOLOv8算法&#xff0c;并与YOLOv7、YOLOv6、YOLOv5等先前版本进行了性能比较。该系统能够在图像、视频、实时视频流和批量文件中精确地识别和分类PCB板缺陷。文中不仅深…