Java导出数据到Word模板中

前言

相信很多人都会遇到Java导出的业务,Java导出主要有导出数据到Excel和Word,但是相比较而言对于导出数据到Excel的实现方案会比较成熟一些,网上的教程也是非常之多的.但是导出到Word的方案还是有所欠缺,一些简单的导出还是可以的,但是遇到一些比较复杂的Word模板导出就会很头疼.比如说:Word模板中有相对复杂的表格样式的时候,处理起来也就相对的复杂一些。
本次项目中,我就遇到了一个需要将数据库的数据导出到Word模板中的需求,而且Word模板中包含比较复杂的关系表格。
由于本人之前没有接触过导出之类的业务,所以走了不少弯路。

网上的方案

经过网上查询发现Java导出Word主要有以下几种方案:
以下内容转自子午线,需要详细参考请跳转

  1. Jacob是Java-COM Bridge的缩写,它在Java与微软的COM组件之间构建一座桥梁。通过Jacob实现了在Java平台上对微软Office的COM接口进行调用。
  2. Apache POI包括一系列的API,它们可以操作基于MicroSoft OLE 2 Compound Document Format的各种格式文件,可以通过这些API在Java中读写Excel、Word等文件。
  3. Java2word是一个在java程序中调用 MS Office Word 文档的组件(类库)。该组件提供了一组简单的接口,以便java程序调用他的服务操作Word 文档。 这些服务包括: 打开文档、新建文档、查找文字、替换文字,插入文字、插入图片、插入表格,在书签处插入文字、插入图片、插入表格等。
  4. FreeMarker生成word文档的功能是由XML+FreeMarker来实现的。先把word文件另存为xml,在xml文件中插入特殊的字符串占位符,将xml翻译为FreeMarker模板,最后用java来解析FreeMarker模板,编码调用FreeMarker实现文本替换并输出Doc。
  5. PageOffice生成word文件。PageOffice封装了微软Office繁琐的vba接口,提供了简洁易用的Java编程对象,支持生成word文件,同时实现了在线编辑word文档和读取word文档内容。

需求介绍

本人这里,是需要将数据库中的数据查询出,并且填充到Word模板中,并提供下载和导出到指定位置。

模板

在这里插入图片描述

简介

在本模板中,红色框中的表格有且可能有多个,蓝色框中的表格行有且可能有多条。需要从数据库中查询出相对应的数据并且填充到Word模板中,模板中红色框中的表格只有一个,需要根据查询出的数据动态的循环出多个,蓝色框中的同理有一个循环出多行。
属于一个典型的一对多对多的数据结构。

使用体验

看到这5中方案的时候,我大概的了解了以下,发现第二种POI是最近比较流行,并且便捷的,为了方便,便毫不犹豫的选择使用POI提供的 poi-tl

poi-tl

poi-tl(poi template language)是基于Apache POI的Word模板引擎。纯Java组件,跨平台,代码短小精悍,通过插件机制使其具有高度扩展性。支持DOCX格式的Word模板。

poi-tl 更为详细的介绍请自行跳转,http://deepoove.com/poi-tl/
我在使用poi-tl 的时候,很快的就将蓝框中的循环行给实现了,红色框内的基本数据可以填充,但是这个时候,我发现我无法实现将红色框内的表格循环多个。这就很蛋疼,我在网上查了好久也没有找到解决方案。于是我自己想到一个比较笨的方案,就是将上面的基础信息表格单独到一个Word模板中,然后将基础信息导出到基础的Word模板中,将下面的红色框中的表格单独到一个Word模板中,然后查询到若干条数据,就导出若干个Word文档,最后将这些Word文档合并为一个文档,然后将生成的多余的Word文档删除。现在想想这种方案也是很傻。但是这种方案也没有实现,应为每次删除文档的时候有权限问题,poi-tl 会将本次服务生成的Word文档抓着不放,只有将服务重启或关闭的时候才会删除掉,这就很鸡肋,为此还咨询了好多大佬,也不了了之。

Freemarker

FreeMarker是一款模板引擎: 即一种基于模板和要改变的数据,并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。

之后我便尝试使用Freemarker来实现此功能,其实之前我是使用过的,但由于了解不是很深,以为Freemarker的list循环只可以循环一层(说起来也是尴尬)。

操作步骤

使用Freemarker首先我们需要将Word模板转换成可以被Freemarker所识别操作的xml文件,我个人感觉最麻烦的就是准备模板,不知道为什么Word文档转换成xml形式的时候里面有些标签会被分隔开来,所以一定要细心。

  1. 首先将需要填充的数据对应的标签填入到模板中。

插入替换标签
2. 将模板另存为xml格式,并将后缀改为(.ftl)。
另存为xml格式
在这里插入图片描述将xml文件后缀更改为ftl在这里插入图片描述
3. 将Word文档另存为xml文件时会出现之前写入的标签位置错乱问题。
在这里插入图片描述
这里我们需要将错误的格式,调整为可识别的状态。
如图。将选中的蓝色部分删除
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
修改完成后,如图。
在这里插入图片描述
注意,修改模板的时候,可能会遇到图片。
在这里插入图片描述
需要将这一长串删除替换为 ${image} 即可。
我们可以将图片存放在项目中,然后获取图片的绝对路径,并将图片进行转码,即可正常显示在导出的Word文档中了。

//获取图片的路径
String imagePath = this.getClass().getClassLoader().getResource("image.jpg").getPath();
//获得图片的Base64码
    public static String getImageBase(String src) throws Exception {
        if (src == null || src == "") {
            return "";
        }
        File file = new File(src);
        if (!file.exists()) {
            return "";
        }
        InputStream in = null;
        byte[] data = null;
        try {
            in = new FileInputStream(file);
            data = new byte[in.available()];
            in.read(data);
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        Base64.Encoder encoder = Base64.getEncoder();

        return String.valueOf(encoder.encode(data));
    }

在修改模板时,我们本次的重点是循环表格。
我们直接在1级列表的最外层添加标签。
嵌套循环在表格中需要再次循环的标签上加标签。

<#-- 循环外层表格 -->
<#list xxxList as listKey>
       <w:tbl>
            <#-- 循环表格内的行 -->
            <#list listKey.aaaaList as act>
                 <w:tr>
                 .........
                 </w:tr>
            </#list>
       </w:tbl>
 </#list>

到这个时候,我们的模板就准备好了。

  1. 写Java代码获取数据并填充。
//这里我就省略部分参数
Map<String, Object> datas = new HashMap<>();
datas.put("qq",qq );
datas.put("ww",ww );
//图片
datas.put("image", getImageBase(imagePath));

List<Map<String, Object>> xxxList = new ArrayList<>();
Map<String, Object> xxxMap = new HashMap<>();
xxxMap.put("aa",aa);
xxxMap.put("ss",ss);

List<Map<String, Object>> aaaaList = new ArrayList<>();
Map<String, Object> aaaaMap = new HashMap<>();
aaaaMap.put("zz",zz);
aaaaMap.put("xx",xx);
aaaaList.add(aaaaMap);
xxxMap.put("aaaaList",aaaaList);
xxxList.add(xxxMap);
datas.put("xxxList",xxxList);

ResponseEntity<byte[]> entity = wordUtil.createDoc(datas, "xxx.doc");


        ```
	//createDoc
    ByteArrayOutputStream outputStream = null;
    OutputStreamWriter writer = null;
    configuration.setClassForTemplateLoading(this.getClass(), "/template");
    configuration.setEncoding(Locale.getDefault(),"utf-8");
    Template t=null;
    try {
        t = configuration.getTemplate("aa.ftl","utf-8");
    } catch (IOException e) {
        e.printStackTrace();
    }
    //输出文档路径及名称
    File outFile = new File(fileName);

    FileOutputStream fos=null;
    try {
        fos = new FileOutputStream(outFile);
        outputStream = new ByteArrayOutputStream();
        writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
    } catch (FileNotFoundException e1) {
        e1.printStackTrace();
    }
    try {
        t.process(dataMap, writer);
        writer.close();
        fos.close();
    } catch (TemplateException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    //设置请求头
    HttpHeaders headers = new HttpHeaders();
    headers.add("Content-Disposition", "attchement;filename=" + fileName);
    headers.add("Access-Control-Expose-Headers", "*");
    //设置请求状态
    HttpStatus statusCode = HttpStatus.OK;
    ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(outputStream.toByteArray(), headers, statusCode);

    return entity;
}

哦,对了,还有的朋友可能会遇到导出后,文件无法打开的问题。
有这么几种可能,就是在修改ftl模板的时候破坏了文档的结构,
还有就是 有几个不能够替换的字符我们需要特殊的处理。

.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;")

单个表格中,换行如何处理。

.replaceAll("\n", "<w:br/>")

总结

至此,我们就完成了一次较为复杂的Word导出任务。
第一次遇到这种需求,所以学习到了很多,也走了很多弯路,希望本文可以帮助到,被类似问题所困惑的小伙伴。
谢谢大家的阅读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值