zoukankan      html  css  js  c++  java
  • java使用freemarker模板导出word(带有合并单元格)文档

    来自:https://blog.csdn.net/qq_33195578/article/details/73790283

    前言:最近要做一个导出word功能,其实网上有很多的例子,但是我需要的是合并单元格的,可是查了好久都没有自己想要的。研究了几天其实挺简单的,在这儿我就简单的介绍一下吧!(此方法只是一种思路,借鉴者还有根据需求来具体写代码)

    一、准备工作

    1、jar包:freemarker-2.3.20.jar

    2、模板:word.ftl

    2.1:这个word.ftl怎么来?

    首先准备一份要导出的word.doc文档

    这是一个事先写好的一个word模板,我们需要做的就是把需要导出的数据相应的插入${}到里面(其实如果你了解 freemarker就会明白的,${}是个占位符,来放数据的,这里不详细介绍)。

    如果下面的表格是固定的,比如像我们的课程表,那种是固定的,就特别简单了。我要做的就是下面的表格需要根据 数据的不同,实现合并单元格,先看一下生成以后的效果

    大家可以看一下,前面三个格子是合并以后的,当然,如果后面两个格子的数据只有一行,那么也就是一行就行 了。

    2.2 将word.doc 转化成word.xml

    个人觉得,其实word就是一个xml文件。打开这个word,然后另存为xml格式,然后你再用office打开这个xml,也是可 打开的。

    然后用notepad(其他软件随意,不过最好可以格式化这个xml或者ftl文件,否则你会头炸的,啥都看不清)打开这个

    word.xml。然后你会发现,可能解析的时候,会把我们的"${}"这些给分开,没事,你手动再把他们拼好就行了。

    切记:拼接的时候,千万要注意不要删除或修改了里面的什么结构,后果自负(呵呵,其实就是格式错误了,就算你 导出成功,也不会打开的,因为你已经损害了这个word)

    2.3 完成这个xml以后,把这个word.xml 修改成后缀为.ftl的模板文件,到此,这个模板就算完成了。

    二、这里先说根据模板word.ftl将导出新的new.doc

    1、其实这一步,网上多的是,我唯一有点不满意的就是,它获取模板的方法好像只有通过路径来获取,也许还有别的方法,欢 迎知情者可以告诉我,哈哈!

    2、先创建一个类(个人随意,实现功能就行。)DocmentHandler.class

    1. public class DocumentHandler {  
    2.   
    3.     private Configuration configuration = null;  
    4.       
    5.     public DocumentHandler(){  
    6.         configuration = new Configuration();  
    7.         configuration.setDefaultEncoding("UTF-8");  
    8.     }  
    9.       
    10.     public byte[] createDocArea (Map<String, Object> dataMap ,String outFilePath,String fileName) throws Exception{  
    11.         //this.configuration.setClassForTemplateLoading(DocumentHandler.class, "D:\");//第一种模板路径  
    12.         System.out.println("---进入createDocArea---");  
    13.         this.configuration.setDirectoryForTemplateLoading(new File("/template/"));//第二种模板路径  
    14.         Template t = null;  
    15.         File outFile = null;  
    16.         byte[] bFile = null;  
    17.         try {  
    18.             t = this.configuration.getTemplate(fileName,"UTF-8");  
    19.         } catch (Exception e) {  
    20.             e.printStackTrace();  
    21.             return null;  
    22.         }  
    23.           
    24.         outFile = new File(outFilePath);  
    25.         Writer w = null;  
    26.         FileOutputStream fos = null;  
    27.           
    28.         try {  
    29.             fos = new FileOutputStream(outFile);  
    30.             OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");  
    31.               
    32.             w = new BufferedWriter(osw);  
    33.         } catch (Exception e) {  
    34.             e.printStackTrace();  
    35.             return null;  
    36.         }  
    37.           
    38.         try {  
    39.             t.process(dataMap, w);  
    40.             if(outFile!=null){  
    41.                 FileInputStream fis = new FileInputStream(outFile);  
    42.                  bFile = new byte[(int) outFile.length()];  
    43.                  fis.read(bFile);  
    44.                  fis.close();  
    45.             }  
    46.             System.out.println("--写入完成---");  
    47.         } catch (Exception e) {  
    48.             e.printStackTrace();  
    49.             return null;  
    50.         }finally{  
    51.             w.close();  
    52.             fos.close();  
    53.         }  
    54.         return bFile;  
    55.     }  
    56. }  

    3、简单介绍一下思路:它是通过这个configuration来获取模板的,目前我就知道这两种,一个是通过File,另一个种是通过
    Class,不过我试了一下,不过这两种用哪一种,其实都是通过路径来获取的,然后它会根据在这个路径下,来通过你给的模板
    名称来找这个模板。所以这里路径下一定要有相应的模板。然后获取模板以后,你再定义一个File,把封装好的Map(这个map就
    是你要填充的数据)传过来,它会通过流,结合模板,把数据写入到你的新File中。至于你怎么处理这个File,我就不管了。

    三、封装数据(关键

    1、其实很好理解,填充模板数据的方式就是用key-value这个格式写的(这个应该好理解吧!freemark),定义一个Map,map 的key就是模板里面的${key}这个属性,一定要跟模板里面的对应起来,value就是你要写的数据;如果你不需要合并单元格,你 就无脑的按照模板里${key},对应的放数据就行,最后把map给第二步的导出方法;

    2、如果你的word里需要循环的,那就再map里放个list,至于模板里怎么循环,这就是涉及到freemarker的知识了,我就不解释 了,自己百度去;(哎,给个例子吧!)

    一定要判断你传的这个是否为空,否则要是空的话,会报错(它会用英文提示你,这个是空,你要判断空的情况,利用<#if> <#else></#if>,自己理解去吧!);

    3、哈哈这儿才是重点:

            1、其实这个word合并单元格挺简单的,就拿我上图的那个结果来说,1到4(算上 前面的那个序列号)个格子是需要合并的,后面的三个不需要。上图是一个IP对应 后面五个单元格,所以其实它并不是一行,它是5行,(在这里)ip是在第一行, 然后第2到第4行的ip是空的。先理解这个!

    2、合并需要的属性值:<w:vMerge w:val='restart'/>和<w:vMerge/>

    这俩才是控制合并单元格的罪魁祸首;

    3、怎么用?(看上图)当循环第一行的时候,有IP、地域、重要攻击、单位、时 间;第二行,有空、空、空、单位、时间;第三行。。。。

    4、然后在模板里的第一行(也就是循环第一次的时候),添加<w:vMerge w:val='restart'/>(只在需要合并单元格的地方添加,看下图)

    简单的解释一下,一行的开始是以<w:tr w:rsidR="00806640" w:rsidRPr="00914E05" w:rsidTr="00806640">这个开始的,以</w:tr>结束的,这 个是完整的一行,里面的<w:tc>是代表一个格子,算是前面的序列号,我上图也就 是6个<w:tc>放那个合并属性的时候千万别放错地方了,我的是前四个(算上序列 号)需要合并,所以我就在前四个<w:tc>里放了这个;

    然后再第二行(三行。。。循环)的时候,前面的ip是空的,这个时候要放 <w:vMerge/>这个属性,放的位置跟第一行的方法一样,只是这个熟悉变了;

    这样的话就实现了合并单元格了;这样第一个IP对应的单元格就完成了,第二个IP 就循环着来呗!

    4、以上就是一个完整的导出过程了,当然,不管是封装数据,还是合并单元格, 我说的可能更多的只是个思路,具体怎么封装数据,怎么想方设法的把数据放进模 板,仁者见者,智者见智吧!就到此为止吧,希望我说的大家可以看的明白,如果 哪儿不明白的,或者哪儿写的不对的,欢迎吐槽!

  • 相关阅读:
    平衡二叉树
    Vue--钩子函数,$refs,$nextTick
    Vue--组件,props传参,过滤器,侦听属性
    Vue--指令系统
    Vue-es6语法
    Python常用内建模块-itertools
    Python常用内建模块-hashlib
    Python常用内建模块-struct
    Python常用内建模块-base64
    Python常用内建模块-collections
  • 原文地址:https://www.cnblogs.com/duende99/p/9227707.html
Copyright © 2011-2022 走看看