java对word文档预设参数填值并生成

目录

(1)定义word文档模板

(2)模板二次处理

处理模板图片,不涉及图片可以跳过

 处理模板内容

(3)java对word模板填值

(4)Notepad++的XML Tools插件安装


工作上要搞一个合同签署功能,小程序上登录人进入功能会对合同进行电子签名,然后后端根据登录人信息和合同word文档模板生成一个合同word文档并保存,踩了不少坑。一开始是用了apache的poi,使用简单,读取word模板,然后遍历每一个段落和节点,判断节点是不是我定义的参数名,是就替换文本值,本以为会很简单就能搞定,结果每次遍历获取的节点经常不完整,${name}获取的时候可能会被分成三段,【${】【name】【}】,导致参数替换不上去,想着直接获取所有文本信息,然后直接string的replace方法替换,结果也不行,他不给直接对段落进行修改,直接麻了,而且pio包引入后,如果当前springboot版本太低,有些包就会出问题,又得特别处理,贼麻烦,后来就换成了freemarker,支持替换图片,不过步骤有些繁琐。


(1)定义word文档模板


既然是对word文档进行填值,那肯定得先定义一个word文档模板了,建一个docx后缀的word文档,并设置好格式,参数等,比如我建了一个word文件名为:wordTemplate.docx,内容是这样的,参数名是用${}包起来


(2)模板二次处理


格式全部调好后保存,并把文件后缀名改成zip,让它变成压缩包,记住你的word模板一定要是docx后缀的,doc后缀是老版的,搞不了。

打开压缩包是这个样子。

进入word文件夹里面是这样的,我们只需要关注【document.xml】【_rels】【media】,如果你不打算放图片,那只关注【document.xml】就行了。


处理模板图片,不涉及图片可以跳过


打开_rels文件夹是这样的。

【document.xml.rels】文件就是存放图片和文档之间的关系,我们把它解压出来并打开。

【打开xml时没有格式化过的,我用了Notepad++打开的,并装了XML Tools插件,然后把它格式化后才成了这个样子的】具体插件安装在文章最后面讲。


接着讲,image1名称是word文档生成的,为了后面填值方便区别,把 media/image1.png 改成 media/headImage.png,然后保存,回到压缩包里面,把原来的【document.xml.rels】替换掉,再打开【media】文件夹,会发现里面有个图片,名为image1.png,就是我们提前放入的头像,我们得把它改名成【headImage.png】,因为上一步,我们修改了【document.xml.rels】文件的映射,这里也得改。

这时候再把压缩包后缀改成docx,你会发现一样还能打开,不过这里我们改回去打开检查没问题后,再把后缀改回成zip。


这里再说一个点,如果模板图片太多,势必会造成模板文件很大,我们可以找一个透明或纯白色的宽高都是1px的图片, 反正很小的图片就行,名字改成【media】里面的图片名,放入【media】里面,相当于占位。也可以一开始创建word模板的时候,放图片直接放这个小体积的图片,把宽高调整到合适的就行。


 处理模板内容


打开压缩包把【document.xml】模板内容文件解压出来打开并xml格式化,找到你设置的参数名。

这时候会发现,参数名乱了,${name}可能被分隔七零八落,我们需要重新调一下,确保参数名完整,然后再保存。

这里再讲讲图片跟【document.xml】的关联,我们通过【document.xml.rels】图片映射文件可以看到,每一个图片标签都会有一个id,这个头像这个图片的id是rId4。

我们复制它去【document.xml】里面找,就会发现这个图片的标签以及格式了。

了解一下就行了,回归正题,我们调整好【document.xml】文件并保存,这个文件暂时不需要放回压缩包替换原来文件,到这一步,我们就有两个文件了,这两个文件缺一不可。


找一张图片替换模板里的头像。


(3)java对word模板填值


处理完word模板后,来到java代码。 

先引入包。不推荐用最新的包,我引了最新的包,用main方法测试的时候没问题,结果启动spring服务的时候,就不行了,报找不到那个版本的参数,后来降级就可以了。

<dependency>
	<groupId>org.freemarker</groupId>
	<artifactId>freemarker</artifactId>
	<version>2.3.28</version>
</dependency>

代码大致思路是这样的:

  1. 读取模板【document.xml】
  2. 值填充
  3. 重新生成一个【document.xml】
  4. 把这个新的【document.xml】和头像图片写入定义好的压缩包模板,也就是【wordTemplate.zip】
  5. 然后把压缩包输出成word文档

写一个main方法测试。


public static void main (String[] args) {
	// 模板存放路径
	String templastPath = "D:/A";
	// zip模板名称
	String zipTemplastName = "wordTemplate.zip";
	// zip模板存放路径
	String zipTemplastPath = new StringBuffer(templastPath).append(File.separator).append(zipTemplastName).toString();
	// docx文档模板名称
	String docxTemplateName = "document.xml";
	// 输出路径
	String outPath = "D:/A/out";
	// docx模板填充后输出路径,这里的【document.xml】不能改
	String outputDocxTemplatePath = new StringBuffer(outPath).append(File.separator).append("document.xml").toString();
	// 最终生成的docx文档输出路径,这里的word文档输出文件名随意
	String outputDocxFilePath = new StringBuffer(outPath).append(File.separator).append("output.docx").toString();
	File outPathFile = new File(outPath);
	if (!outPathFile.exists() && !outPathFile.mkdirs()) {
		throw new RuntimeException("输出路径创建失败");
	}
	try (
		FileOutputStream out = new FileOutputStream(outputDocxTemplatePath);
		OutputStreamWriter outputStreamWriter = new OutputStreamWriter(out);
		BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
		// zip模板压缩包输入流
		FileInputStream zipTemplastInput = new FileInputStream(zipTemplastPath);
		// 最终docx文档输出流
		FileOutputStream finalDocxOutput = new FileOutputStream(outputDocxFilePath);
	){
		// 将要替换的值
		Map<String, Object> map = new HashMap<String, Object>() {{
			put("name", "韩西景");
			put("sex", "男");
			put("age", "51");
			put("homeAddress", "广东省广州市白云区景山路3612号");
		}};

		//创建配置实例 VERSION_2_3_28是pom文件引入时的版本号
		Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
		//设置编码
		configuration.setDefaultEncoding("UTF-8");
		// 设置模板路径
		configuration.setDirectoryForTemplateLoading(new File(templastPath));
		// 获取xml模板
		Template template = configuration.getTemplate(docxTemplateName);
		// 参数值填充
		template.process(map, bufferedWriter);
		// 读取模板zip压缩包
		ZipInputStream zipInputStream = ZipUtils.wrapZipInputStream(zipTemplastInput);
		// 最终生成的word文档输出流
		ZipOutputStream zipOutputStream = ZipUtils.wrapZipOutputStream(finalDocxOutput);
		// zip压缩包要替换的项
		Map<String, String> replaceItemMap = new HashMap<String, String>(){{
			// 替换图片,本地路径可以,网络路径不行
			// put("word/media/headImage.png", "D:/A/headImage.png");
			// 替换图片,base64写入
			put("word/media/headImage.png", new StringBuffer("data:image/png;base64,").append(imageToBase64("D:/A/headImage.png")).toString());
			// 替换内容
			put("word/document.xml", outputDocxTemplatePath);
		}};
		ZipUtils.replaceItem(zipInputStream, zipOutputStream, replaceItemMap);
	} catch (Exception e) {
		System.out.println("报错了,这里自己打log啥的,该处理的处理");
	} finally {
		// 删除填充后xml模板
		new File(outputDocxTemplatePath).delete();
	}
}

/**
 * 图片转base64
 * @param inputPath 图片路径
 * @return base64字符串
 */
private static String imageToBase64(String inputPath) {
	File file = new File(inputPath);
	ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
	try(
		FileInputStream fileInputStream = new FileInputStream(inputPath);
		BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
	) {
		int b;
		byte[] bytes = new byte[4096];
		while ((b = bufferedInputStream.read(bytes)) > -1) {
			byteBuffer.put(bytes, 0, b);
		}
		return Base64.encodeBase64String(byteBuffer.array());
	} catch (Exception e) {
		System.out.println("报错了,这里自己打log啥的,该处理的处理");
	}
	return null;
}

执行之后,可以看到生成word文档了。


部署到服务器的话,就把模板文件放到服务器上面,然后配置模板路径,具体使用引入模板路径就行了。

至于word文档里面的表格,暂时没有研究。 


(4)Notepad++的XML Tools插件安装

仅用于格式化模板xml文件,方便调整xml文件,可装可不装,问题不大。 

插件安装:顶部菜单栏【插件】->【插件管理】打开后


好了,到这结束了,真够累人,这该死的996,永无止境的打工。


码字不易,于你有利,勿忘点赞 

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

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

相关文章

Yolo v5实现细节(2)

Yolo v5代码实现细节 IOU系列损失 在之前的yolo v3中我们使用的定位损失主要使用的是差值平方的形式&#xff0c;通过预测边界框的参数和真实边界框的参数来进行计算求解的。 定位损失 L loc ( t , g ) ∑ i ∈ pos ( σ ( t x i ) − g ^ x i ) 2 ( σ ( t y i ) − g ^ …

c语言学习记录(十)———函数

文章目录 前言一、函数的基本用法二、函数的参数传递1.基本方式2 数组在函数中的传参 前言 一个学习C语言的小白~ 有问题评论区或私信指出~ 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、函数的基本用法 函数是一个完成特定功能的代码模块&…

【Linux】锁|死锁|生产者消费者模型

&#x1f525;博客主页&#xff1a; 我要成为C领域大神&#x1f3a5;系列专栏&#xff1a;【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 ​ ​ 访问互斥 …

modelsim做后仿真的一点思路

这是以TD_5.6.3_Release_88061生成的网表文件&#xff08;其他工具生成的网表文件类似&#xff09;&#xff0c;与modelsim联合进行门级仿真的样例&#xff0c;时序仿真与门级仿真的方法类似&#xff0c;只是增加了标准延时文件。 1、建立门级仿真工程 将门级网表和testbench添…

深度学习31-33

1.负采样方案 &#xff08;1&#xff09;为0是负样本&#xff0c;负样本是认为构造出来的。正样本是有上下文关系 负采样的target是1&#xff0c;说明output word 在input word之后。 2.简介与安装 &#xff08;1&#xff09;caffe:比较经常用于图像识别&#xff0c;有卷积网…

一文详细了解Bootloader

Bootloader是什么 bootloader是一个引导加载程序&#xff0c;它的主要作用是初始化硬件设备、设置硬件参数&#xff0c;并加载操作系统内核。在嵌入式系统中&#xff0c;bootloader是硬件启动后第一个被执行的程序&#xff0c;它位于操作系统和硬件之间&#xff0c;起到桥梁的…

操作符详解(上) (C语言)

操作符详解&#xff08;上&#xff09; 一. 进制转换1. 二进制2. 二进制的转换 二. 原码 补码 反码三. 操作符的分类四. 结构成员访问操作符1. 结构体的声明2. 结构体成员访问操作符 一. 进制转换 1. 二进制 在学习操作符之前&#xff0c;我们先了解一些2进制、8进制、10进制…

魔众一物一码溯源防伪系统——守护品牌,守护信任!

在这个充满竞争的市场上&#xff0c;如何确保你的产品不被仿冒&#xff0c;如何赢得消费者的信任&#xff1f;魔众一物一码溯源防伪系统&#xff0c;为你提供一站式解决方案&#xff0c;守护你的品牌&#xff0c;守护消费者的信任&#xff01; &#x1f50d;魔众一物一码溯源防…

Node.js全栈指南:浏览器显示一个网页

上一章&#xff0c;我们了解到&#xff0c;如何通过第二章的极简 Web 的例子来演示如何查看官方文档。为什么要把查阅官方文档放在前面的章节说明呢&#xff1f;因为查看文档是一个很重要的能力&#xff0c;就跟查字典一样。 回想一下&#xff0c;我们读小学&#xff0c;初中的…

防火墙双机热备

防火墙双机热备 随着移动办公、网上购物、即时通讯、互联网金融、互联网教育等业务蓬勃发展&#xff0c;网络承载的业务越来越多&#xff0c;越来越重要。所以如何保证网络的不间断传输成为网络发展过程中急需解决的一个问题。 防火墙部署在企业网络出口处&#xff0c;内外网之…

windows系统修改克隆虚拟机的SID(报错:尝试将此计算机配置为域控制器时出错)

当我们用克隆虚拟机加入域的时候&#xff0c;可能会出现图下所示报错。这时我们可以用微软自带的工具sysprep来修改机器的SID来解决该问题 注意&#xff1a;用sysprep修改SID之后&#xff0c;系统会自动重启&#xff0c;之前配置好的网络、修改过的机器名会重置。所以&#xff…

6.2 通过构建情感分类器训练词向量

在上一节中&#xff0c;我们简要地了解了词向量&#xff0c;但并没有去实现它。在本节中&#xff0c;我们将下载一个名为IMDB的数据集(其中包含了评论)&#xff0c;然后构建一个用于计算评论的情感是正面、负面还是未知的情感分类器。在构建过程中&#xff0c;还将为 IMDB 数据…

Windows上PyTorch3D安装踩坑记录

直入正题&#xff0c;打开命令行&#xff0c;直接通过 pip 安装 PyTorch3D : (python11) F:\study\2021-07\python>pip install pytorch3d Looking in indexes: http://mirrors.aliyun.com/pypi/simple/ ERROR: Could not find a version that satisfies the requirement p…

JS(JavaScript)入门指南(DOM、事件处理、BOM、数据校验)

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。 玉阶生白露,夜久侵罗袜。 却下水晶帘,玲珑望秋月。 ——《玉阶怨》 文章目录 一、DOM操作1. D…

从零开始做题:有手就行

1 题目 2 解题 ARPHCR工具破解 得到flag DASCTF{2b3767763885a019b65bbfe9d1136c3b}

从零开始学docker(四)-安装mysql及主从配置(一)

mysql MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c;属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的 RDBMS (Relational Database Management System&#xff0c;关…

仿Photoshop利用曲线对图像调整亮度与色彩

曲线调整是Photoshop的最常用的重要功能之一。对于一个RGB图像, 可以对R, G, B 通道进行独立的曲线调整&#xff0c;即&#xff0c;对三个通道分别使用三条曲线&#xff08;Curve&#xff09;。还可以再增加一条曲线对 三个通道进行整体调整。 因此&#xff0c;对一个图像&a…

C++初学者指南-2.输入和输出---流输入和输出

C初学者指南-2.输入和输出—流输入和输出 文章目录 C初学者指南-2.输入和输出---流输入和输出1.定制输入/输出1.1 示例&#xff1a;点坐标输入/输出1.2 流操作符1.3&#xff08;一部分&#xff09;标准库流类型 2. 工具2.1 用getline读取行 2.2 用ignore进行跳转2.3 格式化操作…

武汉星起航:全球化舞台,中国跨境电商品牌力与竞争力双提升

随着全球化步伐的加快和数字技术的迅猛发展&#xff0c;跨境出口电商模式已经成为中国企业海外拓展的重要战略选择。这一模式不仅为中国的中小型企业提供了进军全球市场的机会&#xff0c;更为它们在全球舞台上展示独特的竞争优势提供了强有力的支撑。武汉星起航将从市场拓宽、…

STL迭代器的基础应用

STL迭代器的应用 迭代器的定义方法&#xff1a; 类型作用定义方式正向迭代器正序遍历STL容器容器类名::iterator 迭代器名常量正向迭代器以只读方式正序遍历STL容器容器类名::const_iterator 迭代器名反向迭代器逆序遍历STL容器容器类名::reverse_iterator 迭代器名常量反向迭…