举荐:亲自体验,数次踩坑,遂撰写此文,以备各位不时之需。
背景
一天,产品经理递给我了一份 word 报告,我定睛一看
这个文档有大大小小的题目层级,还有排版好的段落、各种高深莫测的饼图、走势图,当然还少不了色彩循环交替的报表。粗劣水平不亚于小明同学的学习报告。
筹备
鲁迅:身为一名 Java 程序员,任何时候都不要遗记站在伟人的肩膀上。
通过某歌搜寻关键词:java+word+ 导出,我立马得出了很多成熟的计划,通过横向、纵向比拟,再联合本次报告款式比拟多、用户可灵便抉择不同模块导出的特点,最终,我决定应用Freemarker 动静替换模版数据来导出 word 文档。至于导出文档的最终格局,有两种抉择:
那到底应用 doc 还是 docx 格局的文档?
每当人生当中每次面临抉择我都很谨慎。最终我抉择应用 docx 格局(起因文末会讲),然而为了让大家有更多的抉择,满足更多的业务场景,借此机会,小明会给大家别离介绍应用 freemarker 导出两种格局的 word 文档形式。
思路
FreeMarker 是一个基于 Java 的模板引擎,最后专一于应用 MVC 软件架构生成动静网页。然而,它是一个通用的模板引擎,不依赖于 servlets 或 HTTP 或 HTML,因而它通常还用于生成源代码,配置文件或电子邮件。
此时,咱们用它动静生成 xml 文件,进而导出 word 文档。
整体流程如下:
筹备
- WPS
由金山软件股份有限公司公布,用于办公软件最罕用的文字编辑、表格、演示稿等性能。
对,就是这个国产的办公软件。我也是第一次发现在导出文档这件事上,它如多年好友般敌对。(word 解析后的 xml 文件浏览性很强,个别人我不通知他)
- 开发工具(IDEA、Visual Studio Code 等)
你喜爱的,棘手的,就是最好的。
实现
集成 Freemarker 模版引擎
本次我的项目应用的框架仍旧是 Springboot,这个框架在集成各个组件体现都很便捷,不再赘述,这次集成 Freemarker 也不例外。
- 首先咱们在我的项目中削减依赖
spring-boot-starter-freemarker
pom.xml 文件如下所示:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
- 依照默认约定,咱们能够在 resources 下创立一个 templates 文件夹(查看 FreeMarkerProperties 源码能够发现默认目录就是这个),用于寄存模版文档。
- application.yml 减少配置
spring:
freemarker:
template-loader-path: classpath:/templates
cache: false # 开发环境缓存敞开
suffix: xml
charset: UTF-8
生成 doc 格局的文档
这里先拿应用 freemarker 导出 doc 格局的 word 文档举例。
- 首先将 docxTemplate.docx(调整好款式的模版文档)另存为 WORD 2003 XML 文档(*.xml)
此处命名为 docTemplete.xml,应用编辑工具首次关上时,会发现这个文档外面是压缩的 xml,因而咱们首先须要格式化一下。
留神:如果你应用的是 Visual Studio Code 开发工具,肯定要查看你所应用的 xml 格式化插件,是否会优化你的 xml 标签。比方:
<w:rPr>
会变成<rPr>
。应用Visual Studio Code 的同学,oh my god ! 小明在这里举荐大家应用这个插件:XML Language Support by Red Hat
- 当初,咱们就应用 freemarker 语法编辑 docTemplete.xml,比方应用占位符
${}
替换以后文档中的文本,以达到动静生成文本的目标,间接上代码。
public static Configuration getConfiguration(){
// 创立配置实例
Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
// 设置编码
configuration.setDefaultEncoding("utf-8");
configuration.setClassForTemplateLoading(WordUtil.class, "/templates");
return configuration;
}
/**
* 生成 doc 文件
*
* @param ftlFileName 模板 ftl 文件的名称
* @param params 动静传入的数据参数
* @param outFilePath 生成的最终 doc 文件的保留残缺门路
*/
public void ftlToDoc(String ftlFileName, Map params, String outFilePath) {
try {
/** 加载模板文件 **/
Template template = configuration.getTemplate(ftlFileName);
/** 指定输入 word 文件的门路 **/
File docFile = new File(outFilePath);
FileOutputStream fos = new FileOutputStream(docFile);
Writer bufferedWriter = new BufferedWriter(new OutputStreamWriter(fos, "utf-8"), 10240);
template.process(params, bufferedWriter);
if (bufferedWriter != null) {bufferedWriter.close();
}
} catch (TemplateException e) {e.printStackTrace();
} catch (IOException e) {e.printStackTrace();
}
}
生成 docx 格局的文档
高能预警! 在胜利应用 Freemarker 动静导出 doc 格局的文档之后,置信大家和我的情绪一样十分冲动。但以上操作只是一个小铺垫,接下来咱们来看看如何实现 docx 格局的文档导出,小明置信肯定会让各位看官大跌眼镜!不,大开眼界!
首先,通知大家一个机密:docx 格局的文档其实是一个 ZIP 格局的压缩文件哦! 什么?你不信?验证如下:
- windows 的小伙伴
将 docx 文档批改为 ZIP 格局(批改.docx 后缀名为.zip),而后通过解压工具解压。
- MacOS 的小伙伴
间接应用 unzip
命令解压 word 文档,解压过后咱们会发现该文档其实还有本人的 目录构造 !
当然,这么多文件咱们不用一一知悉,只需关注小明红线标注的文件和目录即可:
- document.xml 文件用于寄存外围数据,文字,表格,图片援用等
- media 目录用于寄存所有文档的图片
- _rels 目录下的 document.xml.rels 里寄存的是配置信息,比方图片援用关系,即在 document.xml 中援用 id 对应 media 中的哪个图片。
- 获取 zip 里的 document.xml 文档以及_rels 文件夹下的 document.xml.rels 文档
- 不言而喻,如果咱们要想依据数据动静导出不同的 word 文档,只须要:通过 freemarker 将本次数据填充到 document.xml 中,并将图片配置信息填充至 document.xml.rels 文档里,再用文件流把本次图片写入到 media 目录下替换曾经存在的图片,最初把填充过内容的 document.xml、document.xml.rels 以及 media 用流的形式写入 zip 即可输入 docx 文档!上代码。
好吧,限于篇幅,代码见文末 Github 地址
问题及解决方案
当然,大家在第一次尝试去干某一件事时,都不肯定是欲速不达的。就比方在导出 word 时,就可能会遇到以下问题。
特殊字符
问题:有些文本数据中不免含有特殊字符,如:< > @ ! $ &
等等。
解决方案 :这些特殊字符如果不进行本义,就会引起 word 打不开的景象,比方表格中的超链接的&
符号,就须要替换为&
,如果你的文档用 office 关上时提醒文件损坏,九成是因为特殊符号引起的,咱们能够关上 documet.xml 定位报错地位;当然还有终极计划,咱们能够利用 Freemarker 的语法间接在模板中应用<![CDATA[]]>
解决。比方:
<w:t><![CDATA[ ${article.title} ]]></w:t>
图片变形
问题:因为 echarts 生成的图表是响应式的,不同的屏幕大小、分辨率,会造成每次前端传过来的图片宽高比例不统一,如果还间接将图片依照之前的比例放进文档,会造成生成后文档中的图片变形。
思路:首先将文档中的图片设置为原图,而后锁定宽高比,将图片调整到适合大小,解压文档从document.xml
,失去此时 word 中该图片宽高对应的值,如下所示:
要想保障不同像素比例的宽高在文档中不变形,咱们须要固定 cy
的值,而后依据固定比例动静求得以后像素比例图片在 word 中代表的宽 cx
的值。计算方法如下所示:
公式:
a/b = x/y
其中,a 示意图片在 word 中宽的数值,b 代表图片在 word 中高的数值,x 示意前端传过来图片的宽(单位:像素),y 示意前端传过来图片的高(单位:像素)。因而,已知 b、x、y, 依据公式,咱们即可求出 a;
我就是文末
当然,还有用一些其余注意事项:
- 如果 word 中的模块比拟多的话,应用 Freemarker 语法要认真一点;
- 为什么小明最终抉择导出 docx 格局的文档呢?(还不是因为产品经理的需要嘛)因为 doc 格局的文档,小明尝试导出后,发现该文档并不是一个非法的 doc 文档,体现在:不能在手机上(微信、钉钉)失常预览,office 提醒以 xml 模式关上等。因而在导出 doc 文档时,通过 Freemaker 填充 document.xml 后失去的并不是一个非法的 word 文档,查了相干材料,还须要借助第三方工具进行签名,而签名还须要在 windows 零碎下能力实现,然而咱们平时用的生产环境都是 Linux……因而,思考再三,再三衡量,最终抉择导出 docx 格局的文档。这种形式再适宜不过,而且还能保障在以后支流 APP 上都能失常预览。
- 敲黑板!导出 docx 文档最重要的一个思维是将本次数据写入笼罩模版文件(在商业中,相当于借壳上市),从新输入一个 zip 格局压缩的文件,这个文件就是咱们最终想要的文档。
以上,就是小明 word 导出的前前后后,如果你也已经遇到过或者当初正好遇到 word 文档导出开发的问题,欢送一起探讨交换。
相干链接
我上传了工具类,蕴含 doc、docx 的导出,以及导出 word 文档时特殊符号本义,还有图片 Base64 转换成文件输入的办法。
GitHub 地址:https://github.com/WhenCoding…
本文可转载,但需申明原文出处。程序员小明,一个很少加班的程序员。欢送关注微信公众号,获取更多优质文章。