freemark+dom4j实现自动化word导出
导出word我们常用的是通过POI实现导出。POI最擅长的是EXCEL的操作。word操作起来样式控制还是太繁琐了。今天我们介绍下通过FREEMARK来实现word模板导出。
开发准备
- 本文实现基于springboot,所以项目中采用的都是springboot衍生的产品。首先我们在maven项目中引入freemark坐标。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
- 只需要引入上面的jar包 。 前提是继承springboot坐标。就可以通过freemark进行word的导出了。
模板准备

- 上面是我们导出的一份模板。填写规则也很简单。只需要我们提前准备一份样本文档,然后将需要动态修改的通过
${}进行占位就行了。我们导出的时候提供相应的数据就行了。这里注意一下${c.no}这种格式的其实是我们后期为了做集合遍历的。这里先忽略掉。后面我们会着重介绍。
开发测试
到了这一步说明我们的前期准备就已经完成了。剩下我们就通过freemark就行方法调用导出就可以了。
首先我们构建freemark加载路径。就是设置一下freemark模板路径。模板路径中存放的就是我们上面编写好的模板。只不过这里的模板不是严格意义的word.而是通过word另存为xml格式的文件。

- 配置加载路径
//创建配置实例
Configuration configuration = new Configuration();
//设置编码
configuration.setDefaultEncoding("UTF-8");
//ftl模板文件
configuration.setClassForTemplateLoading(OfficeUtils.class, "/template");
- 获取模板类
Template template = configuration.getTemplate(templateName);
- 构建输出对象
Writer out = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
- 导出数据到out
template.process(dataMap, out);
- 就上面四步骤我们就可以实现导出了。我们可以将加载配置路径的放到全局做一次。剩下也就是我们三行代码就可以搞定导出了。当然我们该做的异常捕获这些还是需要的。点我获取源码
结果检测

功能通用化思考
- 上面我们只是简单介绍一下freemark导出word的流程。关于细节方面我们都没有进行深究。
- 细心的朋友会发现上面的图片并没有进行动态的设置。这样子功能上肯定是说不过去的。图片我们想生成我们自己设置的图片。
- 还有一个细节就是复选框的问题。仔细观察会发现复选框也没有字段去控制。肯定也是没有办法进行动态勾选的。
- 最后就是我们上面提到的就是主要安全措施那块。那块是我们的集合数据。通过模板我们是没法控制的。
- 上面的问题我们freemark的word模板是无法实现的。有问题其实是好事。这样我们才能进步。实际上freemark导出真正是基于ftl格式的文件的。只不过xml和ftl语法很像所以上面我们才说导出模板是xml的。实际上我们需要的ftl文件。如果是ftl文件那么上面的问题的复选框和集合都很好解决了。一个通过if标签一个通过list标签就可以解决了。图片我们还是需要通过人为去替换
<#if checkbox ??&& checkbox?seq_contains('窒息;')?string('true','false')=='true'>0052<#else>00A3</#if>
<#list c as c>
dosomethings()
</#list>
- 上面两段代码就是if 和 list语法
Dom4j实现智能化
- 上面ftl虽然解决了导出的功能问题。但是还是不能实现智能化。我们想做的其实想通过程序自动根据我们word的配置去进行生成ftl文件。经过百度终究还是找到了对应的方法。Dom4j就是我们最终方法。我们可以通过在word进行特殊编写。然后程序通过dom4j进行节点修改。通过dom4j我们的图片问题也就迎刃而解了。下面主要说说针对以上三个问题的具体处理细节
复选框

- 首先我们约定同一类型的复选框前需要
#{}格式编写。里面就是控制复选框的字段名。 - 然后我们通过dom4j解析xml。我们再看看复选框原本的格式在xml中
<w:sym w:font="Wingdings 2" w:char="0052"/>
- 那么我们只需要通过dom4j获取到w:sym标签。在获取到该标签后对应的文本内容即#{zhuyaoweihaiyinsu}窒息;这个内容。
- 匹配出字段名zhuyaoweihaiyinsu进行if标签控制内容
<#if checkbox ??&& checkbox?seq_contains('窒息')?string('true',false')=='true'>0052<#else>00A3</#if>
部分源码
Element root = document.getRootElement();
List<Element> checkList = root.selectNodes("//w:sym");
List<String> nameList = new ArrayList<>();
Integer indext = 1;
for (Element element : checkList) {
Attribute aChar = element.attribute("char");
String checkBoxName = selectCheckBoxNameBySymElement(element.getParent());
aChar.setData(chooicedCheckBox(checkBoxName));
}
集合

- 同样的操作我们通过获取到需要改变的标签就可以了。集合和复选框不一样。集合其实是我们认为规定出来的一种格式。在word中并没有特殊标签标示。所以我们约定的格式是
${a_b}。首先我们通过遍历word中所以文本通过正则验证是否符合集合规范。符合我们获取到当前的行然后在行标签前添加#list标签。 然后将\({a_b}修改成\){a.b} 至于为什么一开始不设置a.b格式的。我这里只想说是公司文化导致的。我建议搭建如果是自己实现这一套功能的话采用a.b格式最好。
部分源码
Element root = document.getRootElement();
//需要获取所有标签内容,判断是否符合
List<Element> trList = root.selectNodes("//w:t");
//rowlist用来处理整行数据,因为符合标准的会有多列, 多列在同一行只需要处理一次。
List<Element> rowList = new ArrayList<>();
if (CollectionUtils.isEmpty(trList)) {
return;
}
for (Element element : trList) {
boolean matches = Pattern.matches(REGEX, element.getTextTrim());
if (!matches) {
continue;
}
//符合约定的集合格式的才会走到这里
//提取出tableId 和columnId
Pattern compile = Pattern.compile(REGEX);
Matcher matcher = compile.matcher(element.getTextTrim());
String tableName = "";
String colName = "";
while (matcher.find()) {
tableName = matcher.group(1);
colName = matcher.group(2);
}
//此时获取的是w:t中的内容,真正需要循环的是w:t所在的w:tr,这个时候我们需要获取到当前的w:tr
List<Element> ancestorTrList = element.selectNodes("ancestor::w:tr[1]");
/*List<Element> tableList = element.selectNodes("ancestor::w:tbl[1]");
System.out.println(tableList);*/
Element ancestorTr = null;
if (!ancestorTrList.isEmpty()) {
ancestorTr = ancestorTrList.get(0);
//获取表头信息
Element titleAncestorTr = DomUtils.getInstance().selectPreElement(ancestorTr);
if (!rowList.contains(ancestorTr)) {
rowList.add(ancestorTr);
List<Element> foreachList = ancestorTr.getParent().elements();
if (!foreachList.isEmpty()) {
Integer ino = 0;
Element foreach = null;
for (Element elemento : foreachList) {
if (ancestorTr.equals(elemento)) {
//此时ancestorTr就是需要遍历的行 , 因为我们需要将此标签扩容到循环标签汇中
foreach = DocumentHelper.createElement("#list");
foreach.addAttribute("name", tableName+" as "+tableName);
Element copy = ancestorTr.createCopy();
replaceLineWithPointForeach(copy);
mergeCellBaseOnTableNameMap(titleAncestorTr,copy,tableName);
foreach.add(copy);
break;
}
ino++;
}
if (foreach != null) {
foreachList.set(ino, foreach);
}
}
} else {
continue;
}
}
}
图片

- 图片和复选框类似。因为在word的xml中是通过特殊标签处理的。但是我们的占位符不能通过以上占位符占位了。需要一张真实的图片进行占位。因为只有是一张图片word才会有图片标签。我们可以在图片后通过
@{imgField}进行占位。然后通过dom4j将图片的base64字节码用${imgField}占位。
部分源码
//图片索引下表
Integer index = 1;
//获取根路径
Element root = document.getRootElement();
//获取图片标签
List<Element> imgTagList = root.selectNodes("//w:binData");
for (Element element : imgTagList) {
element.setText(String.format("${img%s}",index++));
//获取当前图片所在的wp标签
List<Element> wpList = element.selectNodes("ancestor::w:p");
if (CollectionUtils.isEmpty(wpList)) {
throw new DomException("未知异常");
}
Element imgWpElement = wpList.get(0);
while (imgWpElement != null) {
try {
imgWpElement = DomUtils.getInstance().selectNextElement(imgWpElement);
} catch (DomException de) {
break;
}
//获取对应图片字段
List<Element> imgFiledList = imgWpElement.selectNodes("w:r/w:t");
if (CollectionUtils.isEmpty(imgFiledList)) {
continue;
}
String imgFiled = getImgFiledTrimStr(imgFiledList);
Pattern compile = Pattern.compile(REGEX);
Matcher matcher = compile.matcher(imgFiled);
String imgFiledStr = "";
while (matcher.find()) {
imgFiledStr = matcher.group(1);
boolean remove = imgWpElement.getParent().elements().remove(imgWpElement);
System.out.println(remove);
}
if (StringUtils.isNotEmpty(imgFiledStr)) {
element.setText(String.format("${%s}",imgFiledStr));
break;
}
}
}
基于word自动化导出(含源码)
- 以上就是我们实现导出的流程。通过上面的逻辑我们最终可以一套代码复用了。源码下载地址:https://gitee.com/zxhTom/office-multip.git
参考网络文章
dom操作xml
dom生成xml
httpclient获取反应流
获取jar路径
itext实现套打
ftl常见语法
freemark官网
ftl判断非空
freemark自定义函数
freemark自定义函数java
freemark特殊字符转义
java实现word转xml各种格式
# 加入战队
微信公众号

freemark+dom4j实现自动化word导出的更多相关文章
- Java之word导出下载
访问我的博客 前言 最近遇到项目需求需要将数据库中的部分数据导出到 word 中,具体是在一个新闻列表中将选中的新闻导出到一个 word 中.参考了网上一些教程,实现了该功能,在此记录下来. 导出结果 ...
- PowerDesigner-制作Word导出模版
定制导出模版 当然这不是我们想要的word,下面看如何做一个自定义模版 1. 在工具栏中选择[Report -->Reports],如下图 点击第二个图标创建一个Template,如下图 2. ...
- HTML页面的导出,包括Excel和Word导出
//导出到Excel --- 全部导出,可以设置一些隐藏进行导出 protected void btnExport_Click(object sender, EventArgs e) { ...
- PowerDesigner自定义Word导出格式
本文主要讲解如何将PDM的表结构导出成word以及如何自定义导出格式和显示效果,希望对大家有所帮助...(以下步骤以PowerDesigner 15版本为例) 一.新建导出报表,进入报表设计界面: 1 ...
- Java 动态实现word导出功能
1.word模板:xx.ftl生成,ftl文件就是word的源代码,类似html一样是拥有标签和样式的代码. 把需要导出的doc文件模板用office版本的word工具打开. 把doc文件另存为xx. ...
- java web实现在线编辑word,并将word导出(二)
前一篇文章介绍了后台将前台html转为word文档的一种方式,但却有一个问题是没法讲图片放置在生成的word报告中.我在网上找了很多方法,甚至将图片转换成base64编码的方式也不成功.效果如下: 由 ...
- java web实现在线编辑word,并将word导出(一)
前段时间领导交代了一个需求:客户需要一个能够web在线编辑文字,如同编辑word文档一样,同时能够将编辑完成的内容导出为word文档并下载到本地. 我们选择了前台使用富文本插件的形式用于编辑内容,使用 ...
- C#中word导出功能骚操作
马上过牛年了,先祝大家新年好,身体好,心情好!!! 年前最后写一篇之前项目开发的一个功能,自己根据系统业务,想到的一个解决办法,效率还是不错的,废话不多说,开整!!! 需求:企业填报自己的企业信息到系 ...
- IIS 发布 之 Word导出本地测试正常,发布报错
用C#动态生成Word文档功能实现了,在本地的机器运行时是好的,但程序发布 IIS 或 远程服务器 上就报错, 报错信息为:检索 COM 类工厂中 CLSID 为 {000209FF-0000-000 ...
随机推荐
- centos6.X修改ssh默认端口号
先查看下服务器端口号范围: # sysctl -a|grep ip_local_port_range net.ipv4.ip_local_port_range = 32768 61000 新ss ...
- 日日算法:Kruskal算法
介绍 克鲁斯卡尔(Kruskal)算法是用来求出连通图中最小生成树的算法. 连通图:指无向图中任意两点都能相通的图. 最小生成树:指联通图的所有生成树中边权重的总和最小的树(即,找出一个树,让其联通所 ...
- POJ 2136 Vertical Histogram(当时写的比较恶心,优化一下)
Vertical Histogram Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 21223 Accepted: 10048 ...
- 网络流--最大流--Dinic模板矩阵版(当前弧优化+非当前弧优化)
//非当前弧优化版 #include <iostream> #include <cstdio> #include <math.h> #include <cst ...
- 创造DotNet Core轻量级框架【一】
前言 net core 已经出了很久了,网上的各种框架也很多了,但是没看到一个很小很轻的框架,基本都是那种啥功能都有,但是我需要的功能只占他们框架的百分之几,很少很少,所以自己创造一个框架. 因为之前 ...
- 在Maven项目中添加代码目录下的配置文件
问题 Maven 是约定大于配置的一种工具, 通常约定在 src/main/resources 目录下放配置文件, 当我们想要在 src/main/java 代码目录下放置配置文件用来测试, Mave ...
- 基于胜率矩阵的PageRank排序
在做博弈模型评估的时候,遇到一个问题是如何评价多个模型的优劣.例如我有训练好的三个围棋模型A,B,C,两两之间对打之后有一个胜负关系,如何对这三个模型进行排序呢?通常对于人类选手这种水平有波动的情 ...
- 【Linux基础总结】Linux基本环境
Linux基本环境 对Linux的基础认识 虚拟机进入终端: [root@hadoop-senior Desktop] # 用户名 主机名 所在目录名称 #:表示当前用户属于root用户,超级管理员用 ...
- 杂记---主要关于PHP导出excel表格学习
今天上午处理了一下WIN7系统的电脑前置话筒和耳机口无法使用的问题,主要现象是耳机插入后没声音,麦插入话筒说话对方也听不到,后置端口一切正常.刚开始判断肯定是设置的问题,于是用另一台电脑百度搜索“wi ...
- ApplicationInsights迁移国内躺的坑
由于之前有段时间访问国际版的Application Insights实在是慢,而且又不是每个人都有梯子,然后国内版大概在去年(2019)六七月左右已经上线,想着也有一段时日了,要么就迁了吧. 然后实际 ...