freemarker实现动态行单元格合并

news/2024/11/20 0:47:32/文章来源:https://www.cnblogs.com/fswhq/p/17681620.html

原文链接: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"));

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/779234.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Xbox Game Pass Ultimate one dollar Trial All In One

Xbox Game Pass Ultimate one dollar Trial All In One XGPU $1 美元试用 Xbox Game Pass Ultimate — 14 Day Trial Recurs MonthlyXbox Game Pass Ultimate one dollar Trial All In OneXGPU $1 美元试用Xbox Game Pass Ultimate — 14 Day Trial Recurs Monthly Xbox Game P…

代码静态测试工具 Helix QAC 2024.1版新功能解读

Helix QAC 2024.1改进了对C++20和C23语言特性的支持,并增加了分析使用多个编译器的项目的新功能。此外,Validate增强了对于搜索功能和角色权限的用户体验,并且包括一个新的问题列表的CSV下载选项。此版本还包括对于C/C++的CWE、C的HKMC和MISRA C++:2023合规模块的扩展执行,…

苹果与英伟达公开分手,谷歌 TPU 芯片成为苹果 AI 训练新利器

苹果公司发布论文公开其 AI 模型的训练细节,放弃英伟达GPU而转向选择谷歌TPU芯片。科技巨头们在尖端 AI 训练方面开始寻求更多元化的算力硬件解决方案。北京时间 7 月 30 日,苹果公司发布了一篇研究论文,论文显示苹果公司使用了谷歌开发的 TPU 芯片而非英伟达的 GPU 芯片来训…

三色法GC总结

作用:因并发标记过程中,程序还在跑。对象间的引用会发生变化,可能会导致漏标或错标情况。因此采用三色法,将扫描的各情况用颜色区别出来。 三种颜色 白色:一开始所有对象的颜色都是白色,即未扫描过的对象。 灰色:表示对象已经扫描到了,但是对象所在的引用对象,还未全部…

Dreamforce 24重磅来袭!年度盛会将有何惊喜?

作为Salesforce的旗舰会议,Dreamforce的历史已有20余年之久,是生态系统中的年度亮点。现如今,Dreamforce已经适应了线上受众的需求,通过Salesforce+提供直播和点播的参与方式。近期,Salesforce宣布Dreamforce 24将于9月17日-19日举行,一年一度的科技盛会又要开始Dreamfor…

笔记:从Aurora 8b/10b 到Aurora 64b/66b (三):自定义PHY层收发

相较于8/10来说没那么复杂,需要考虑的情况只有八种; 但是gearbox的控制需要额外的心思:每三十二周期所有操作都需要停止; 这一点在收发都需要注意; RX: 核心思想是利用header做检测,将夹杂在数据流中的控制包滤除掉;module gt_phy_rx (input wire i_rx_cl…

esp32安装circuitpython

安装Thonny https://thonny.org/ 在Thonny中安装circuitpython 在配置解释器中选择circuitython然后点击右下角“安装或更新...“variant这里我选择了 DOIT esp32 Development Board(试过其他的一些个,不知道为什么安装完之后会无限重启,所以最后选择了这个) 烧录完之后,重…

Linux内核学习—— 1内核体系结构

一 内核体系结构 1内核模式与体系结构 2内核中断概括 3进程管理概括 二 内核源码结构 三 内核引导程序 操作系统结构: 用户应用程序 操作系统服务层 内核(文件系统,内存管理,进程管理,驱动管理) 硬件系统 驱动 操作系统工作方式:把做操作系统从用户态切换到内核态(用户应…

设置windows系统下的tomcat开机自启

设置前的准备工作(配置系统环境变量) 新增CATALINA_HOME变量,变量值为tomcat的安装路径在Path中添加: %CATALINA_HOME%\bin结合安装版本问题,分正常安装版本tomcat和免安装tomcat(无service.bat)两种情况。 一、正常安装版本(tomcat安装文件有service.bat文件) 说明:安装…

基于vscode搭建freertos环境

前言 目前网上windows仿真freertos的资料都是比较久远的,不太适合现有的开发,因此重新整理了一下资料. 目标: 使用Vscode进行FreeRTOS开发和仿真. 关键词: freertos, vscode,llvm,cmake,windows 环境配置 编译器目前使用的是llvm-MinGW-msvcrt:Releases mstorsjo/llvm-mingw (…

QWen2-72B-Instruct模型安装部署过程

最近在给我们的客户私有化部署我们的TorchV系统,客户给的资源足够充裕,借此机会记录下部署千问72B模型的过程,分享给大家! 一、基础信息操作系统:Ubuntu 22.04.3 LTSGPU: A800(80GB) * 8内存:1TB二、软件信息 Python: 3.10 Pytorch:2.3.0 Transformers:4.43.0 vLLM:0.…

塑胶件的结构设计:超声波焊接篇(中)

04 超声波焊接效果的影响因素 一、塑胶材料因素 上篇已经介绍的适合超声波焊接的材料选择,一般情况下两种材料满足Tg或熔点接近、化学相容性良好和熔体流动指数接近这三个条件,基本可认为是可焊接的,但需要注意以下几点: 1、热塑性塑胶又分为非结晶性(也叫无定形)塑胶和…