Java填充Word模板

文章目录


前言

最近有个Java填充Word模板的需求,包括文本,列表和复选框勾选,写一个工具类,以此记录。


提示:以下是本篇文章正文内容,下面案例可供参考

一、设置word模板

选择文档中要填充的地方点击->选择插入->文档部件->域->域名(mergeFeild)->填写变量名称.

普通字段

在这里插入图片描述


在这里插入图片描述


填充完毕:

在这里插入图片描述

列表字段

操作和普通字段一样,区别是需要在首行第一列插入列表开始域,首行最后一列插入结束域,中间正常字段。格式为:StartTable:<数组字段名>,EndTable:<数组字段名>

在这里插入图片描述

复选框

复选框找了好多种方法尝试没有成功,最后取巧,和普通字段一样设置占位符,通过代码逻辑处理.

在这里插入图片描述

二、代码

1. 引入POM

poi/hutool/aspose-words/gson:

<!-- hutool工具类 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.14</version></dependency><!--word模板数据解析--><dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.9.0-beta</version></dependency><!-- word/pdf操作 --><dependency><groupId>com.aspose</groupId><artifactId>aspose-words</artifactId><version>18.8</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.9</version></dependency>

2. 模板放入项目

这里是放在项目里,也可以放在云上存储。

在这里插入图片描述


破解文件放入resouce,否则会有水印,aspose-words在maven仓库中也没有,需要下载后安装在本地仓库。

安装命令

mvn install:install-file -Dfile=路径/aspose-words-18.8.jar -DgroupId=com.aspose -DartifactId=aspose-words -Dversion=18.8-Dpackaging=jar 

引入破解文件

在这里插入图片描述
<License><Data><Products><Product>Aspose.Total for Java</Product><Product>Aspose.Words for Java</Product></Products><EditionType>Enterprise</EditionType><SubscriptionExpiry>20991231</SubscriptionExpiry><LicenseExpiry>20991231</LicenseExpiry><SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber></Data><Signature>sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=</Signature></License>

jar包和license下载地址

3.代码

实体类

 import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Arrays; import java.util.List; @Data @AllArgsConstructor @NoArgsConstructor public class FillWordDTO implements Serializable { private String name; private String age; private String yuyan; private String yingyu; private String deyu; private String fayu; private String zhengshu; private String yiji; private String erji; private List<ExperienceList> experienceList; public static FillWordDTO create(){ FillWordDTO fillWordDTO = new FillWordDTO(); fillWordDTO.setName("小王"); fillWordDTO.setAge("18"); fillWordDTO.setYuyan("☑"); fillWordDTO.setYingyu("☑"); fillWordDTO.setDeyu("□"); fillWordDTO.setFayu("☑"); fillWordDTO.setZhengshu("☑"); fillWordDTO.setYiji("☑"); fillWordDTO.setErji("□"); fillWordDTO.setExperienceList(Arrays.asList( new ExperienceList("小王","2020-01-01","2020-01-01","小王"), new ExperienceList("小王","2020-01-01","2020-01-01","小王")));return fillWordDTO;}} @Data @AllArgsConstructor @NoArgsConstructor class ExperienceList { private String school; private String startTime; private String endTime; private String remark;}

工具类

 import com.aspose.words.*; import com.aspose.words.net.System.Data.DataRow; import com.aspose.words.net.System.Data.DataTable; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.WritableRaster; import java.beans.PropertyDescriptor; import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Map; public class ContractUtil { private ContractUtil(){}/** * 调整bufferedimage大小 * @param source BufferedImage 原始image * @param targetW int 目标宽 * @param targetH int 目标高 * @param flag boolean 是否同比例调整 * @return BufferedImage 返回新image */ public static BufferedImage resizeBufferedImage(BufferedImage source,int targetW,int targetH, boolean flag){int type = source.getType(); BufferedImage target = null;double sx =(double) targetW / source.getWidth();double sy =(double) targetH / source.getHeight();if(flag && sx > sy){ sx = sy; targetW =(int)(sx * source.getWidth());}elseif(flag && sx <= sy){ sy = sx; targetH =(int)(sy * source.getHeight());}if(type == BufferedImage.TYPE_CUSTOM){// handmade ColorModel cm = source.getColorModel(); WritableRaster raster = cm.createCompatibleWritableRaster(targetW, targetH); boolean alphaPremultiplied = cm.isAlphaPremultiplied(); target = new BufferedImage(cm, raster, alphaPremultiplied, null);}else{ target = new BufferedImage(targetW, targetH, type);} Graphics2D g = target.createGraphics(); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g.drawRenderedImage(source, AffineTransform.getScaleInstance(sx, sy)); g.dispose();return target;}/** * 填充 word 模板(object数据格式) * * @param modelWordByte word模版二进制文件 * @param obj 要填充的数据 * @return 组合数据之后的word二进制 */ public static byte[]fillWordDataByDomain(byte[] modelWordByte, Object obj){ try { Class<?> aClass = obj.getClass(); Field[] fields = aClass.getDeclaredFields(); Map<String, Object> data = new HashMap<>(fields.length);for(Field field : fields){ PropertyDescriptor pd = new PropertyDescriptor(field.getName(), aClass); Method method = pd.getReadMethod(); String key = field.getName(); Object value = method.invoke(obj);if(value != null){ data.put(key, value);}}returnfillWordDataByMap(modelWordByte, data);}catch(Exception e){ e.printStackTrace();return new byte[0];}}/** * 填充 word 模板(map数据格式) * * @param file word二进制 * @param data 要填充的数据 * @return 组合数据之后的word二进制 */ public static byte[]fillWordDataByMap(byte[] file, Map<String, Object> data) throws Exception { byte[] ret = null;if(data == null || data.isEmpty()){return ret;}try(InputStream is = new ByteArrayInputStream(file); ByteArrayOutputStream out = new ByteArrayOutputStream()){ Document doc = new Document(is); DocumentBuilder builder = new DocumentBuilder(doc); Map<String, String> toData = new HashMap<>();for(Map.Entry<String, Object> entry : data.entrySet()){ String key = entry.getKey(); Object value = entry.getValue();// 处理表格数据if(value instanceof List &&!key.equals("checkboxOptions")){ DataTable dataTable =fillListData((List) value, key, builder); doc.getMailMerge().executeWithRegions(dataTable);}// 图片插入elseif(value instanceof BufferedImage){ builder.moveToMergeField(key); builder.insertImage((BufferedImage) value);}// 其他普通字段正常填充else{ String valueStr = String.valueOf(value);if(value != null &&!"null".equals(valueStr)){ toData.put(key, valueStr);}}}// 执行普通字段合并 String[] fieldNames = new String[toData.size()]; String[] values = new String[toData.size()];int i =0;for(Map.Entry<String, String> entry : toData.entrySet()){ fieldNames[i]= entry.getKey(); values[i]= entry.getValue(); i++;} doc.getMailMerge().execute(fieldNames, values); doc.save(out, SaveOptions.createSaveOptions(SaveFormat.DOCX)); ret = out.toByteArray();}return ret;}/** * 勾选段落中的复选框字段(适用于 Aspose.Words 18.8) */ private staticvoidcheckTheCheckbox(Paragraph paragraph) throws Exception { FieldCollection fields = paragraph.getRange().getFields();int count = fields.getCount();for(int i =0; i < count; i++){ com.aspose.words.Field field = fields.get(i);if(field.getType()== FieldType.FIELD_FORM_CHECK_BOX){// 设置字段结果为 "✓" 表示勾选(或根据模板实际显示字符调整)setCheckboxChecked(field, true);}}} private staticvoidsetCheckboxChecked(com.aspose.words.Field field, boolean checked) throws Exception {if(checked){ field.setResult("✓");// 根据模板中实际勾选状态设置}else{ field.setResult("□");// 可选:取消勾选}}/** * 封装 list 数据到 word 模板中(word表格) * * @param list 数据 * @param tableName 表格列表变量名称 * @return word表格数据DataTable */ private static DataTable fillListData(List<Object> list, String tableName, DocumentBuilder builder) throws Exception {//创建DataTable,并绑定字段 DataTable dataTable = new DataTable(tableName);for(Object obj : list){//创建DataRow,封装该行数据 DataRow dataRow = dataTable.newRow(); Class<?> objClass = obj.getClass(); Field[] fields = objClass.getDeclaredFields();for(int i =0; i < fields.length; i++){ Field field = fields[i]; dataTable.getColumns().add(fields[i].getName()); PropertyDescriptor pd = new PropertyDescriptor(field.getName(), objClass); Method method = pd.getReadMethod(); dataRow.set(i, method.invoke(obj));} dataTable.getRows().add(dataRow);}return dataTable;}// private static License license = null;/** * 加载 license * 由于 aspose是收费的,若没有 license,则会出现水印。 */static{ try { InputStream is = ContractUtil.class.getResourceAsStream("/License.xml"); License license = new License(); license.setLicense(is);}catch(Exception e){ throw new RuntimeException("自动加载aspose证书文件失败!");}}}

三、测试

main方法测试,可以根据实际需求改为response输出或者上传到存储服务器后返回链接地址。

 public staticvoidmain(String[] args) throws IOException { FillWordDTO fillWordDTO = FillWordDTO.create();// 读取模板文件 byte[] modelByte = Files.readAllBytes(Paths.get("E:\\project\\spring-demo\\src\\main\\resources\\templates\\test.docx"));// 调用工具类,获取填充数据后的文件 byte[] resultByte = ContractUtil.fillWordDataByDomain(modelByte, fillWordDTO);// 处理该二进制文件,此处处理为输出到桌面 File resultFile = new File("C:\\Users\\Lenovo\\Desktop\\demo.docx"); FileOutputStream fos = new FileOutputStream(resultFile); fos.write(resultByte); fos.close();}

四、运行结果

在这里插入图片描述

五、注意事项

在编辑word域代码时,有时会有隐藏的代码导致填充失败

Found end of mail merge region 'experienceList' that does not match start of mail merge region 'jlList'.

出现以上错误或者想查看域代码,按如下操作

在word中依次点击「文件→选项→高级」,在「显示文档内容」区域勾选「显示域代码而非域值」,找到报错域代码后删除,重新添加域就解决了。

Read more

C++ 继承入门(上):从基础概念定义到默认成员函数,吃透类复用的核心逻辑

C++ 继承入门(上):从基础概念定义到默认成员函数,吃透类复用的核心逻辑

🔥小叶-duck:个人主页 ❄️个人专栏:《Data-Structure-Learning》 《C++入门到进阶&自我学习过程记录》《算法题讲解指南》--从优选到贪心 ✨未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游 目录 前言 一. 继承的概念与定义   1、继承的核心概念   2、继承的定义格式   3、继承方式与成员访问权限 二. 基类与派生类的转换:子类对象能当父类用吗? 三. 继承中的作用域:同名成员会冲突吗?   1、变量隐藏   2、函数隐藏 四、派生类的默认成员函数:构造、拷贝、析构怎么写?   1、构造函数:先调用父类构造,再初始化子类成员   2、拷贝构造:先拷贝父类,再拷贝子类   3、 赋值重载:

By Ne0inhk
【C++】类和对象(中)

【C++】类和对象(中)

一、类的默认成员函数 编译器会自动生成的成员函数称为默认成员函数。一个类,不写的情况下编译器会默认生成以下6个默认成员函数。另外在C++11中,增加了两个默认成员函数,移动构造和移动赋值。默认成员函数从两方面学习: 1. 我们不写时,编译器默认生成的函数行为是啥?满足我们的需求吗? 编译器默认生成的函数不满足我们的需求,那如何自己实现? 二、构造函数 构造函数主要任务是对象实例化时初始化对象。就像每次写栈或队列时需要初始化Stack Init()、Queue Init(),用了构造函数就不需要写这一步。 构造函数的特点:函数名与类名相同:类class Stack,类中的函数Stack()无返回值。也无void对象实例化时系统会自动调用对应的构造函数构造函数可以重载如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧

By Ne0inhk

CCF-GESP计算机学会等级考试2025年12月六级C++T2 道具商店

P14920 [GESP202512 六级] 道具商店 题目描述 道具商店里有 nnn 件道具可供挑选。第 iii 件道具可为玩家提升 aia_iai 点攻击力,需要 cic_ici 枚金币才能购买,每件道具只能购买一次。现在你有 kkk 枚金币,请问你最多可以提升多少点攻击力? 输入格式 第一行,两个正整数 n,kn,kn,k,表示道具数量以及你所拥有的金币数量。 接下来 nnn 行,每行两个正整数 ai,cia_i,c_iai ,ci ,表示道具所提升的攻击力点数,以及购买所需的金币数量。 输出格式 输出一行,一个整数,表示最多可以提升的攻击力点数。 输入输出样例 #1 输入

By Ne0inhk
认识Java中的异常

认识Java中的异常

1. 异常的概念与体系结构 异常不同于编译错误,语法错误 算数异常: 数组下标越界异常: 空指针异常: 异常其实就是一个一个的类,所有异常都继承了Throwable类,其派生出两个重要的子类, Error 和 Exception 其中Exception又分为两大类,一类是运行时异常/非受查异常,另一类为编译时异常/受查异常。 (1)运行时异常就是代码还没运行就报错了 对于这种异常有一个很明显的特点就是要处理掉异常才能继续运行 (2)编译时异常指的是Java虚拟机无法解决的严重问题 这种情况需要程序员手动去处理错误 总结: 1. Throwable:是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception  2. Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,典型代表: StackOverflowError和OutOfMemoryError,一旦发生回力乏术。 3. Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。我们平时所说 的异常就是Exc

By Ne0inhk