java使用freemarker模板导出word(带有合并单元格)

一、准备工作

1、jar包:freemarker-2.3.20.jar
maven:

org.freemarker
freemarker
2.3.20

2、模板:word.ftl

2.1:这个word.ftl怎么来?
首先准备一份要导出的word.doc文档;

在这里插入图片描述

这是一个事先写好的一个word模板,我们需要做的就是把需要导出的数据相应的插入 到 里 面 ( 其 实 如 果 你 了 解 f r e e m a r k e r 就 会 明 白 的 , {}到里面(其实如果你了解 freemarker就会明白的, 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、先创建一个类(个人随意,实现功能就行。)下面的ftl模板是放在resource下的获取方法。

public class wordUtil {

/**
 * 生成word文件
 * @param dataMap word中需要展示的动态数据,用map集合来保存
 * @param templateName word模板名称,例如:test.ftl
 * @param filePath 文件生成的目标路径,例如:D:/wordFile/
 * @param fileName 生成的文件名称,例如:test.doc
 */
@SuppressWarnings("unchecked")
public static void createWord(Map dataMap,String templateName,String filePath,String fileName){
    try {
        //创建配置实例
        Configuration configuration = new Configuration();

        //设置编码
        configuration.setDefaultEncoding("UTF-8");

        //ftl模板文件
        configuration.setClassForTemplateLoading(wordUtil.class,"/");

        //获取模板
        Template template = configuration.getTemplate(templateName);

        //输出文件
        File outFile = new File(filePath+File.separator+fileName);

        //如果输出目标文件夹不存在,则创建
        if (!outFile.getParentFile().exists()){
            outFile.getParentFile().mkdirs();
        }

        //将模板和数据模型合并生成文件
        Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile),"UTF-8"));


        //生成文件
        template.process(dataMap, out);

        //关闭流
        out.flush();
        out.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

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

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

private void putWordTemplateIntoChildDirectory(CaimeiBasic cb, String childFileUrl) {
        /** 用于组装word页面需要的数据 */
        Map<String, Object> dataMap = new HashMap<String, Object>();
		/*下面只是胡乱弄得点数据进行测试*/
        String id = "abd0b729bfe5479498180564186eda7e";
        JuejinBasic juejinBasic = juejinBasicService.queryById(id);
        JuejinDriving driving = juejinDrivingService.queryById("c85434d247e7484187a5de53fa13b6a9");
        JuejinQuestion juejinQuestion = juejinQuestionService.queryById("0af806b65374471da79b27486a37ca3e");
        JuejinRisk juejinRisk = juejinRiskService.queryById(4);
        JuejinRisk juejinRisktwo = juejinRiskService.queryById(2);
        JuejinRisk juejinRiskthree = juejinRiskService.queryById(5);
        JuejinInfo juejinInfo = juejinInfoService.queryById("00d57c84937c4e4caee90740ea6df5c6");
		/*下面只是胡乱弄得点数据进行测试  end*/
        /** 组装数据 */
        //不循环的数据直接进行添加
        dataMap.put("company", cb.getCompany());
        dataMap.put("workface", cb.getWorkFace());
		//循环的数据放入List<Map<String,Object>>
        List<Map<String, Object>> basicTable = new ArrayList<Map<String, Object>>();
        List<Map<String, Object>> dringTable = new ArrayList<Map<String, Object>>();
        List<Map<String, Object>> questionTable = new ArrayList<Map<String, Object>>();
        List<Map<String, Object>> riskTable = new ArrayList<Map<String, Object>>();
        List<Map<String, Object>> infoTable = new ArrayList<Map<String, Object>>();

        Map<String, Object> stringObjectMap = util.setConditionMap(juejinBasic);
        Map<String, Object> drivingMap = util.setConditionMap(driving);
        dringTable.add(drivingMap);
        Map<String, Object> questionMap = util.setConditionMap(juejinQuestion);
        questionTable.add(questionMap);
        Map<String, Object> riskMap = util.setConditionMap(juejinRisk);
        Map<String, Object> stringObjectMap1 = util.setConditionMap(juejinRisktwo);
        Map<String, Object> stringObjectMap2 = util.setConditionMap(juejinRiskthree);
        riskTable.add(stringObjectMap1);
        riskTable.add(stringObjectMap2);
        riskTable.add(riskMap);
        Map<String, Object> infoMap = util.setConditionMap(juejinInfo);
        String completeDate = util.dateToString((Date) 
        infoTable.add(infoMap);
     
        dataMap.put("basicTable",basicTable);
        dataMap.put("dringTable",dringTable);
        dataMap.put("questionTable",questionTable);
        dataMap.put("riskTable",riskTable);
        dataMap.put("infoTable",infoTable);

        //文件唯一名称
        String fileOnlyName ="生成的文档名称"+ ".doc";

  
        /** 生成word */
        wordUtil.createWord(dataMap, "word.ftl", childFileUrl, fileOnlyName);

    }

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/>
<w:vMerge/>或者使用<w:vmerge w:val=“continue”/>

3、怎么用?(看上图)当循环第一行的时候,前三个字段有值;第二行,有空、空、空、后面的字段;第三行。。。。

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

在这里插入图片描述

简单的解释一下,一行的开始是以<w:tr>这个开始的,以</w:tr>结束的,这 个是完整的一行,里面的<w:tc>是代表一个格子,我的是前三个需要合并,所以我就在前三个个<w:tc>里放了这个;

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

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

我的参考博文:https://blog.csdn.net/qq_33195578/article/details/73790283

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值