Java实现导出Word文档功能(XDocReport +FreeMarker)
前言
在日常的开发工作中,我们时常会遇到导出Word文档报表的需求,比如公司的财务报表、医院的患者统计报表、电商平台的销售报表等等。
导出Word方式多种多样,通常有以下几种方式:
-
使用第三方Java工具类库Hutool的Word工具类,参考网址为 https://www.hutool.cn/docs/#/poi/Word生成-Word07Writer ;
-
利用 Apache POI和FreeMarker模板引擎;
-
第三方报表工具。
上面的几种方式虽然可以实现Word文档的导出,但有以下 缺点 :
第一种方式操作简单,但也只能生成简单的Word文档,无法生成有表格的Word文档;
第二种方式可以生成复杂的Word文档,但是还要进行Word转xml,xml转ftl的双重转换,不适合内容经常变更的Word文档;
第三种方式有时候不适合对格式要求严格的文档。
那么,有没有既简单又高效的导出Word的方法呢?答案是肯定有的。接下来我就来介绍一种用 Java 语言实现的,通过 XDocReport
和 FreeMarker
模板引擎生成Word文档的方法。
一、准备环境
开发语言:
Java7及以上的版本。
开发工具:
Eclipse/Idea。
第三方依赖库:
XDocReport、POI、Freemarker。
模板语言:
FreeMarker。
Word编辑器:
Microsoft 365或其他版本较高的Word编辑器。
二、准备Word模板
1.示例Word模板
2.制作模板
Word模板如上图,可以看到,结构比较简单,包括两个部分,第一部分是纯文字和数字,第二部分主要是表格。我们在实际的开发过程中生成的报表几乎都是动态生成的,所以模板中的数字和表格里的数据都要替换成我们后台的实际数据。
替换Word模板中的动态变量,我们需要掌握两个知识点:
1.Word文档中的Word域,word域是引导Word在文档中自动插入文字、图形、页码或其他信息的一组代码。在这里我们可以把 Word域理解成标识符,这个标识符表示Word文档中要被替换的内容;
2.FreeMarker模板下的变量表达式,比如用${title}
替换Word示例模板中的报表
。
了解了以上两个概念后,我们就可以动手编辑Word模板了,步骤如下:
步骤一:
1.首先在Word模板中选中要替换的文本,在这儿拿标题中的标题的报表
字样为例,全选标题然后键盘使用Ctrl + F9
组合键将其设置为域,此时文本会被"{}"
包围,接着鼠标右键选择【编辑域(E)...】
:
步骤二:
2.在弹出的对话框中,类别选择“邮件合并”,在后侧域代码的"MERGEFIELD "
后面编写FreeMarker模板表达式${title}
,点击【确定】按钮:
步骤三:
3.编辑后的效果如下:
步骤四:
4.掌握替换文本的方法后,我们可以把Word模板第文本部分需要替换的内容都替换成模板变量:
Word模板中表格数据的处理(重点)
表格中的数据实质上就是对集合的遍历。
表格数据的处理其实和上面对文本内容的处理是类似的,只不过要在Word模板中加上集合的变量,Java代码中也要有对集合进行特对的处理(这个在后面的代码展示部分会说)。
具体操作步骤如下:
步骤一:
1.选定表格中要替换的文本,然后键盘使用 Ctrl + F9 组合键将其设置为域,接着鼠标右键选择【编辑域( E)…】:
步骤二:
在弹出的对话框中,类别选择“邮件合并”,在后侧域代码的"MERGEFIELD "
后面编写FreeMarker模板表达式${student.id}
,点击【确定】按钮:
步骤三
3.重复步骤2,替换表格中的其他文本内容:
步骤四
四.将模板放到resources目录下:
2.准备后台代码
添加依赖到 pom.xml文件
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls</artifactId>
<version>2.6.0</version>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls-poi</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.core</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.document</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.template</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.document.docx</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
编写Java代码
package word导出;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import fr.opensagres.xdocreport.core.XDocReportException;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.images.FileImageProvider;
import fr.opensagres.xdocreport.document.images.IImageProvider;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import fr.opensagres.xdocreport.template.formatter.FieldsMetadata;
/**
* Created by PRX on 2022/6/18 22:59
*/
public class wordExport {
public static void main(String[] args) throws IOException, XDocReportException {
Test test = new Test();
test.export();
}
}
class Test{
public void export() throws IOException, XDocReportException {
//获取Word模板,模板存放路径在项目的resources目录下
InputStream ins = this.getClass().getResourceAsStream("/word.docx");
//注册xdocreport实例并加载FreeMarker模板引擎
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins,
TemplateEngineKind.Freemarker);
//设置集合数组
List<Student1> list = new ArrayList<Student1>();
Student1 student = new Student1();
student.setId(1);
student.setName("小查");
student.setAge(19);
list.add(student);
Student1 student2 = new Student1();
student2.setId(2);
student2.setName("小红");
student2.setAge(19);
list.add(student2);
//创建xdocreport上下文对象
IContext context = report.createContext();
context.put("title","报表");
context.put("name","彭先生");
context.put("student",list); //加入到上下文对象中
context.put("nian","2022");
context.put("yue","06");
context.put("day","18");
//创建字段元数据
FieldsMetadata fm = report.createFieldsMetadata();
//Word模板中的表格数据对应的集合类型
fm.load("student", Student1.class, true);
report.setFieldsMetadata(fm);
//输出到本地目录
FileOutputStream out = new FileOutputStream(new File("D://报表.docx"));
report.process(context, out);
System.out.println("导出成功");
}
}
3.Word模板中生成序号
给表格数据添加序号是通过后台代码生成的,比如上面的"student.setId(1)"这段代码,其实也可以在Word模板中设置对应的域变量来实现序号的填充。
语法如下:
@before-row[#list list as item] //开头list集合数组
${item?index+1} //数据序号
@after-row[/#list]//结尾
在表格中添加上面的表达式,XDocReport就会自动解析并生成序号,表格中的其他字段也需要进行相应的改动:
注意
语法的开头部分、中间数据部分、结尾部分
分别都要设置单独的Word域
并且在设置Word域的时候要使用" "
包裹住FreeMarker语法 如图所示:
设置完成的结果参考上面表格中的序号表达式,表达式中"item?index+1"是因为序号是从0开始的,所以要加1;
2.表格中除序号的列需要改成item.xxx而不是之前的student.xxx:
3.最后生成效果如下:
建议:序号最好在后台生成,用序号表达式生成的序号列会占用比较大的空间,对资源有所浪费
补充
1.JavaWeb项目中通常是通过浏览器下载的方式来下载Word文档,此时只需要把之前下载到本地的代码改成浏览器端下载的代码即可:
//输出到本地目录
//FileOutputStream out = new FileOutputStream(new File("D://商品销售报表.docx"));
//report.process(context, out);
//浏览器端下载
response.setCharacterEncoding("utf-8");
response.setContentType("application/msword");
String fileName = "商品销售报表.docx";
response.setHeader("Content-Disposition", "attachment;filename="
.concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));
report.process(context, response.getOutputStream());
2.Word模板中的表格的长度最好充满Word文档的左右两边,否则如果表格下面还有其他文本内容,下面的文本内容会自动填充到表格的缝隙处,而且会对下面的文本内容进行覆盖。
添加图片
图片的生成 不使用编辑域,使用模板图片和Word的书签功能,而且需要在元数据中加入图片类型的代码,以下为具体步骤:
1.制作图片
在Word模版中需要插入图片的位置插入一张模版图片,然后鼠标单击模板图片插入一个书签,设置书签名称,比如img1, 最后点击【添加】按钮:
如果需要插入多个图片,就在需要插入图片的位置插入多个模板图片并插入书签设置对应的书签名称即可
2.编写后端代码
package word导出;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import fr.opensagres.xdocreport.core.XDocReportException;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.images.FileImageProvider;
import fr.opensagres.xdocreport.document.images.IImageProvider;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import fr.opensagres.xdocreport.template.formatter.FieldsMetadata;
/**
* Created by PRX on 2022/6/18 22:59
*/
public class wordExport {
public static void main(String[] args) throws IOException, XDocReportException {
Test test = new Test();
test.export();
}
}
class Test{
public void export() throws IOException, XDocReportException {
//获取Word模板,模板存放路径在项目的resources目录下
InputStream ins = this.getClass().getResourceAsStream("/word.docx");
//注册xdocreport实例并加载FreeMarker模板引擎
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(ins,
TemplateEngineKind.Freemarker);
//设置集合数组
List<Student1> list = new ArrayList<Student1>();
Student1 student = new Student1();
student.setId(1);
student.setName("小查");
student.setAge(19);
list.add(student);
Student1 student2 = new Student1();
student2.setId(2);
student2.setName("小红");
student2.setAge(19);
list.add(student2);
//创建xdocreport上下文对象
IContext context = report.createContext();
context.put("title","报表");
context.put("name","彭先生");
context.put("student",list); //加入到上下文对象中
context.put("nian","2022");
context.put("yue","06");
context.put("day","18");
// context.put("img1",getImg("https://cdn.uviewui.com/uview/swiper/swiper1.png"));
//创建字段元数据
FieldsMetadata fm = report.createFieldsMetadata();
//Word模板中的表格数据对应的集合类型
fm.load("student", Student1.class, true);
//替换word模板中的动态图⽚
// IImageProvider 图片 = new FileImageProvider(new File("C:\\Users\\xx\\Pictures\\Screenshots\\屏幕截图 2021-05-16 190200.png"),true); //绝对路径获取图片
fm.addFieldAsImage("img1");
context.put("img1",getImg("https://cdn.uviewui.com/uview/swiper/swiper1.png"));
report.setFieldsMetadata(fm);
//输出到本地目录
FileOutputStream out = new FileOutputStream(new File("D://报表.docx"));
report.process(context, out);
System.out.println("输出成功");
}
//网络编程 处理网络图片
public InputStream getImg(String urls) throws IOException {
URL url = new URL(urls); //解析路径链接 获取指定资源
System.out.println("url====:"+url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); //得到网络对象 协议、请求头、请求体、响应信息等
System.out.println("conn===="+conn);
conn.setRequestMethod("GET"); //设置请求方式
conn.setConnectTimeout(5 * 1000); //设置过期时间
InputStream inStream = conn.getInputStream(); //返回该对象的字节输入流
return inStream;
}
}
3. 导出效果如下
总结
这就是用 Java 语言实现,结合 XDocReport 和 FreeMarker 模板引擎生成Word文档的方法