首页 > 其他分享 >freemarker实现动态行单元格合并

freemarker实现动态行单元格合并

时间:2024-08-07 13:49:44浏览次数:9  
标签:freemarker get 单元格 list fileName put new 动态 dataMap

原文链接:https://www.cnblogs.com/10158wsj/p/11211471.html

https://blog.csdn.net/weixin_43667830/article/details/106936546

项目需求:项目中有个需求,需要将一些数据库中的数据根据需求导出,生成一个word,研究了一些技术,其中包括POI、freemaker,对比了一下实现过程及技术难度没最终使用了freemaker;

原始文件

效果:

实现过程大概分为三步,第一步:根据word文件做模板,修改模板,导出word。这里主要记录一下过程中遇到的一些情况。

一、制作模板

 打开word文件,为要添加数据的地方打标签,,使用${key}的方式,key将来就是数据Map中的key,需要保持一致。然后另存为XML,因为word本质上就是个XML

 

 

二、处理XML文件

 使用FirstObject XML Edito或者其他工具打开,我是用的是NotePad++,打开需要格式话一下,要不然没法看,打开主要是处理以下的情况:

 

 这个是分开的,需要将中间的部分删除,处理后如下:

全部处理好之后,文件另存为ftl文件,这个就是我们制作完成的模板。

三、数据处理、打标签

 这里主要说的是需要循环并且还需要合并单元格的情况。如果没有这些情况,直接打标签,封装数据就可以了。

第一个:list

主要是将数据封装到list中,list中是若干个Map,如图:

注意:list一定要放在要循环行开始的位置,及前一行结束的位置:

list结束位置:

四、数据准备

 数据封装,数据放在Map中,这里Map中的key就是${key}中的key:

public void exportWord(HttpServletRequest request, HttpServletResponse response){
Map<String, Object> dataMap = new HashMap<>(16);
dataMap.put("total", "10");
List<Map<String,String>> list=new ArrayList<>( 16 );
for(int i=0;i<3;i++){
Map<String, String> listMap= new HashMap<>(16);
listMap.put( "no","10000"+i );
listMap.put( "name","test"+i );
listMap.put( "introduce","介绍"+i );
list.add( listMap );
}
dataMap.put( "whyc",checkList( list ) );

System.out.println(dataMap);
WordUtil.exportMillCertificateWord(response,dataMap,"test.docx","test.ftl");
}


public List<Map<String, String>> checkList(List<Map<String, String>> list) {
String start = "<w:vMerge w:val='restart'/>";
String end = "<w:vMerge/>";
list.get(0).put("start", start);
for (int i = 1; i < list.size(); i++) {
list.get(i).put("end", end);
}
return list;
}

复制代码
 public void exportWord(HttpServletRequest request, HttpServletResponse response){
        Map<String, Object> dataMap = new HashMap<>(16);
        dataMap.put("total", "10");
        List<Map<String,String>> list=new ArrayList<>( 16 );
        for(int i=0;i<3;i++){
            Map<String, String>  listMap= new HashMap<>(16);
            listMap.put( "no","10000"+i );
            listMap.put( "name","test"+i );
            listMap.put( "introduce","介绍"+i );
            list.add( listMap );
        }
        dataMap.put( "whyc",checkList( list ) );

        System.out.println(dataMap);
        WordUtil.exportMillCertificateWord(response,dataMap,"test.docx","test.ftl");
    }


    public List<Map<String, String>> checkList(List<Map<String, String>> list) {
        String start = "<w:vMerge w:val='restart'/>";
        String end = "<w:vMerge/>";
        list.get(0).put("start", start);
        for (int i = 1; i < list.size(); i++) {
                list.get(i).put("end", end);
        }
        return list;
    }
复制代码 下面重点来了,合并单元格,如何合并单元格,其实很简单,用到的就是:"<w:vMerge w:val='restart'/>"和"<w:vMerge/>",这两个命令放的位置很重要,否则不会合并成功!

原则一、第一行数据只放"<w:vMerge w:val='restart'/>",从第二行开始,所有要合并的单元格放"<w:vMerge/>"。比如,我这个total这一行要合并,所以要这么处理:

public List<Map<String, String>> checkList(List<Map<String, String>> list) {
String start = "<w:vMerge w:val='restart'/>";
String end = "<w:vMerge/>";
list.get(0).put("start", start);
for (int i = 1; i < list.size(); i++) {
list.get(i).put("end", end);
}
return list;
}

复制代码
public List<Map<String, String>> checkList(List<Map<String, String>> list) {
        String start = "<w:vMerge w:val='restart'/>";
        String end = "<w:vMerge/>";
        list.get(0).put("start", start);
        for (int i = 1; i < list.size(); i++) {
                list.get(i).put("end", end);
        }
        return list;
    }
复制代码

数据输出是这样的:

 

原则二、编辑ftl模板文件,start和end的位置,一定放在行循环开始的<w:tcPr>中:

最后是文件导出及下载的代码:

package com.thupdi.project.utils;

import freemarker.template.Configuration;
import freemarker.template.Template;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.Map;

/**
* @Author wangshuaijun
* @Date 2019/7/18 13:59
* @Version 1.0
*/
public class WordUtil {
private static Configuration configure;


static {
configure= new Configuration();
configure.setDefaultEncoding("utf-8");
// configure.setClassForTemplateLoading(WordUtil.class,"com/thupdi/project/templates");
try {
configure.setDirectoryForTemplateLoading(new File( "D:/develop/IJWorkspaces/fzlsmc/src/main/resources/wordteplate" ));
} catch (IOException e) {
e.printStackTrace();
}
}
public WordUtil() {
super();
}

/**
* 根据Doc模板生成word文件
* @param fileName 文件名称
* @throws IOException
*/
public static void exportMillCertificateWord(HttpServletResponse response,
Map<String,Object> map, String fileName, String ftlFile){

Template freemarkerTemplate = null;
try {
freemarkerTemplate = configure.getTemplate(ftlFile);
} catch (IOException e1) {
e1.printStackTrace();
}

File file = null;
InputStream fin = null;
ServletOutputStream out = null;
try {
// 调用工具类的createDoc方法生成Word文档
file = createDoc(map,freemarkerTemplate,fileName);
fin = new FileInputStream(file);

response.setCharacterEncoding("utf-8");
response.setContentType("application/msword");
// 设置浏览器以下载的方式处理该文件名
fileName = fileName + ".doc";
response.setHeader("Content-Disposition", "attachment;filename="
.concat(String.valueOf( URLEncoder.encode(fileName, "UTF-8"))));

out = response.getOutputStream();
byte[] buffer = new byte[512]; // 缓冲区
int bytesToRead = -1;
// 通过循环将读入的Word文件的内容输出到浏览器中
while((bytesToRead = fin.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fin != null) {
try {
fin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 删除临时文件
if (file != null) {
file.delete();
}
}
}
/**
* 生成word
* @param dataMap
* @param template
* @param fileName
* @return
*/
private static File createDoc(Map<?, ?> dataMap, Template template,String fileName) {
File f = new File(fileName);
Template t = template;
try {
// 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
t.process(dataMap, w);
w.close();
} catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
return f;
}
}

复制代码
package com.thupdi.project.utils;

import freemarker.template.Configuration;
import freemarker.template.Template;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.Map;

/**
 * @Author wangshuaijun
 * @Date 2019/7/18 13:59
 * @Version 1.0
 */
public class WordUtil {
    private static Configuration configure;


    static {
        configure= new Configuration();
        configure.setDefaultEncoding("utf-8");
//        configure.setClassForTemplateLoading(WordUtil.class,"com/thupdi/project/templates");
        try {
            configure.setDirectoryForTemplateLoading(new File( "D:/develop/IJWorkspaces/fzlsmc/src/main/resources/wordteplate" ));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public WordUtil() {
        super();
    }

    /**
     * 根据Doc模板生成word文件
     * @param fileName 文件名称
     * @throws IOException
     */
    public static void exportMillCertificateWord(HttpServletResponse response,
                                                 Map<String,Object> map, String fileName, String ftlFile){

        Template freemarkerTemplate = null;
        try {
            freemarkerTemplate = configure.getTemplate(ftlFile);
        } catch (IOException e1) {
            e1.printStackTrace();
        }

        File file = null;
        InputStream fin = null;
        ServletOutputStream out = null;
        try {
            // 调用工具类的createDoc方法生成Word文档
            file = createDoc(map,freemarkerTemplate,fileName);
            fin = new FileInputStream(file);

            response.setCharacterEncoding("utf-8");
            response.setContentType("application/msword");
            // 设置浏览器以下载的方式处理该文件名
            fileName = fileName + ".doc";
            response.setHeader("Content-Disposition", "attachment;filename="
                    .concat(String.valueOf( URLEncoder.encode(fileName, "UTF-8"))));

            out = response.getOutputStream();
            byte[] buffer = new byte[512];  // 缓冲区
            int bytesToRead = -1;
            // 通过循环将读入的Word文件的内容输出到浏览器中
            while((bytesToRead = fin.read(buffer)) != -1) {
                out.write(buffer, 0, bytesToRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fin != null) {
                try {
                    fin.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            // 删除临时文件
            if (file != null) {
                file.delete();
            }
        }
    }
    /**
     * 生成word
     * @param dataMap
     * @param template
     * @param fileName
     * @return
     */
    private static File createDoc(Map<?, ?> dataMap, Template template,String fileName) {
        File f = new File(fileName);
        Template t = template;
        try {
            // 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
            Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
            t.process(dataMap, w);
            w.close();
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
        return f;
    }
}
复制代码

Maven依赖:

<!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.20</version>
</dependency>

4、循环导出并合并单元格
在要合并的一列上加入下面截图中的标签

测试:我这里合并的是星期一列

xml中的判断:


<w:tcPr>
<w:tcW w:w="2130" w:type="dxa"/>
<#if user.strMap.now == "1">
<#if user.strMap.pre == "0">
<w:vMerge w:val="restart"/>
<#else>
<w:vMerge/>
</#if>
<#else>
<#if user.strMap.pre != "0">
<w:vMerge/>
</#if>
</#if>
</w:tcPr>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Java代码:

public static void main(String[] args) {
OaMeetingMinutesServiceImpl service = new OaMeetingMinutesServiceImpl();
// 要填入模本的数据文件
Map<String,Object> dataMap = new HashMap<>();
List<User> list = new ArrayList<>();
User user = new User();
user.setWeek("周一");
user.setName("张三");
user.setDate("6.22");
user.setContent("畜栏里");
list.add(user);

User user1 = new User();
user1.setWeek("周一");
user1.setName("李四");
user1.setDate("6.23");
user1.setContent("畜栏d地方里");
list.add(user1);

User user2 = new User();
user2.setWeek("周一");
user2.setName("李四1");
user2.setDate("6.211");
user2.setContent("畜栏d地方1里");
list.add(user2);

User user3 = new User();
user3.setWeek("周二");
user3.setName("李四11");
user3.setDate("6.2111");
user3.setContent("畜栏d地方11里");
list.add(user3);

User user4 = new User();
user4.setWeek("周二");
user4.setName("李四111");
user4.setDate("6.21111");
user4.setContent("畜栏d地方111里");
list.add(user4);

for(int i =0;i<list.size();i++){
list.get(i).getStrMap().put("pre","0");
list.get(i).getStrMap().put("now","0");
list.get(i).getStrMap().put("next","0");
if(i<list.size()-1){
//当前和下一个相同就合并,将now设置为1
if(list.get(i).getWeek().equals(list.get(i+1).getWeek())){
list.get(i).getStrMap().put("now","1");
}
}
}

for(int i=0;i<list.size();i++){
//保存他的前一个元素是否需要合并
if(i>0){
list.get(i).getStrMap().put("pre",list.get(i-1).getStrMap().get("now"));
}
//保存他的后一个元素是否需要合并
if(i<list.size()-1){
list.get(i).getStrMap().put("next",list.get(i+1).getStrMap().get("now"));
}
}

dataMap.put("userList", list);
service.createDoc(dataMap,"test.ftl","D:/test.doc");
System.out.println("end");
}

说明:我这里采用了两次循环的方法合并,仅供参考

测试结果:

 

 config.setDirectoryForTemplateLoading(new File(System.getProperty("user.dir")+"/template"));

 

标签:freemarker,get,单元格,list,fileName,put,new,动态,dataMap
From: https://www.cnblogs.com/fswhq/p/17681620.html

相关文章

  • 「代码随想录算法训练营」第三十一天 | 动态规划 part4
    1049.最后一块石头的重量II题目链接:https://leetcode.cn/problems/last-stone-weight-ii/题目难度:中等文章讲解:https://programmercarl.com/1049.最后一块石头的重量II.html视频讲解:https://www.bilibili.com/video/BV14M411C7oV/题目状态:看题解过思路:本题本质上就是将......
  • 网课-动态规划学习笔记2
    记忆化搜索记忆化搜索是一种DP的实现方法。相同点:DP中同一局部问题只计算一次——搜索的记忆化不处理对答案没有贡献的情况——对应搜索的剪枝不同点:遍历顺序优化复杂度。按数组顺序进行的DP,经常可以配合一些优化技巧进一步降低复杂度。“DP是一种算法,......
  • Python动态规划
    Python动态规划动态规划(DynamicProgramming,简称DP)是解决多阶段决策过程最优化问题的一种方法。动态规划算法的基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,......
  • 动态规划之——背包DP(进阶篇)
    文章目录概要说明多重背包(朴素算法)模板例题思路code多重背包(二进制优化)模板例题思路code多重背包(队列优化)模板例题思路混合背包模板例题思路code1code2二维费用背包模板例题思路code概要说明本文讲多重背包、混合背包以及二维费用背包,至于其他背包问题后续......
  • 动态贝叶斯网络DBN介绍
    动态贝叶斯网络DBN介绍1.引言2.贝叶斯网络与动态贝叶斯网络2.1贝叶斯网络简介2.2动态贝叶斯网络详细介绍2.3两种网络对比3.搭建动态贝叶斯网络的方法3.1定义网络结构3.2参数学习3.3推理3.4结构学习和参数学习的方法3.4.1结构学习3.4.2参数学习4.总结5.......
  • 【Spring源码分析】Spring Scope功能中的动态代理 - Scoped Proxy
    本文基于Springboot3.3.2及Springcloud2023.0.1版本编写。SpringScopedProxy是什么在使用Springcloud配置中心动态配置更新功能时,笔者发现在给一个类加上@RefreshScope注解后,其中@Value注入的字段会被自动更新。起初笔者以为Spring在收到配置更新事件后会自动设置该bean的......
  • Excel 根据单元格值设置行颜色
     开始》条件格式》管理规则》新建格式规则》使用公式确定要设置格式的单元格只为满足以下条件的单元格设置格式:=SEARCH("进行中",$E5)>0;(注释:此处筛选的是包含进行中的数据)格式:选择满足条件的单元格设置什么格式 应用于:选择此行需要改颜色格式的单元格  对某个单元格......
  • 帝国CMS自定义页面动态设置
    帝国CMS提供灵活的自定义页面功能,允许用户创建和管理动态内容页面。以下步骤介绍如何设置帝国CMS自定义页面动态:1.创建自定义页面在帝国CMS管理后台,导航到"栏目">>"自定义页面">>"添加自定义页面"。输入页面标题和页面内容,然后单击"提交"保存页面。2.定义字段转到"栏目">>"......
  • 让dedecms变成全站动态浏览有利于企业站
    首页动态化 我们只要把网站根目录下面生成的index.html删除, 然后把index.php里面的代码修改成 代码如下:<?phpif(!file_exists(dirname(__FILE__).&#39;/data/common.inc.php&#39;)){header(&#39;Location:install/index.php&#39;);exit();}require_once(dir......
  • pytorch OSError: [WinError 1114] 动态链接库(DLL)初始化例程失败”原因分析
    动态链接库失败“OSError:[WinError1114]动态链接库(DLL)初始化例程失败。Errorloading"cublas64_12.dll"oroneofitsdependencies”原因分析出错情况:在importtorch中直接被抛出异常环境探讨【问题复现】:因为使用了新的torch-gpu环境【name称为torch】,固怀疑......