freeMarker导出word文档

	<div id="article_content" class="article_content clearfix ****-tracking-statistics" data-pid="blog" data-mod="popu_307" data-dsm="post">
							            <link rel="stylesheet" href="https://****img.cn/release/phoenix/template/css/ck_htmledit_views-bb1edad192.css">
					<div class="htmledit_views">
            <p>今天客户提出要求,要求把表数据导出成为word文档格式。导成word格式的这种需求很寻常,但是看到word模板里面的这种单选框和多选框(如下图),要求实现选中效果,我就有点懵B了。到处百度,google,查询了下,发现好多都是要使用office开发工具来实现,我觉得太麻烦,驾驭不住。</p>

 

freeMarker导出word文档

最后想到有没有单选框或者复选框这样的特殊字符。于是在搜狗输入法里面查询特殊,发现还真有特殊字符,但是字符效果不太符合我要求,单选框选中效果太丑,复选框选中效果是方框里面打叉形式,瞬间就被我PASS掉了。皇天不负有心人,终于让我在word的特殊字符中找到想要的效果( Word > 插入 > 符号 > 其他符号(M)..), 也就是字体 Wingdings有我们需要的特殊字符

freeMarker导出word文档

 

这里先展示下最终导出的显示效果(如下图),可以看到复选框和单选框被选中的效果都已经有了

freeMarker导出word文档

这种方式唯一的弱点就是: 无法导出的doc文档没有真正的点击效果,也就是你打开word文档,点击复选框,复选框不会被置为选中效果,只是使用特殊字符,实现展示上的选中和非选中效果而已,但是对我的需求来说已经够用了。

下面就来看下较为完整的代码实现:

 

一: 首先是创建freemark的word导出模板, word模板如下图所示:

 

freeMarker导出word文档

其中${XXXX} 形式的语法为freemarker的语法,不多介绍了。这里有个问题就是,${survey_date} 这样的语法内容在第二步导出的word xml文档中,容易被打断。有的建议是用类似文本编辑器编写好后一次性黏贴到word里面,不要在word里面一个个字符去输入,但是我发现即使都是复制黏贴进去的也会出现被打断的情况。不过没关系,后面会说明如何校正回来。

二: 将文档保存为word 2003 XML文档格式

freeMarker导出word文档

网络上有些文章是说导出为Word 2003 XML文档格式,可以支持2003版本的word,具体因为我电脑没有2003 word,,没有尝试过,所以我也不确定是不是能兼容2003版本word。

三: 修改XML文档内容

这时候你如果使用寻常的编辑器打开第二个步骤生成的xml文件,发现是一堆没有格式化过的XML文档,非常难处理。有网络大神说使用【firstobject XML Editor】来打开,但是我安装后,打开xml文件,直接卡死。于是我用另一个编辑器【Visual Studio Code】,直接右键点击下 “格式化文件” (如果你没有这个功能,可能要安装下插件,在扩展里面,搜索下 xml format 就会发现有好几个XML format插件。)

通过查找  "${" 发现很多freemark的语法内容被打乱了,如下:

freeMarker导出word文档

调整后效果如下:

freeMarker导出word文档

 

然后我们需要将单选框和复选框的判断条件加上,通过查看XML文档,找到复选框/单选框所在位置:

freeMarker导出word文档

红色部分指定使用Wingdings字体中的特殊字符,通过事先插入特殊字符(单选/复选),我找到分别代表单选框和复选框特殊字符的wordXML内容:

  1. <w:sym w:font="Wingdings" w:char="F0A8"/> 复选框没选中
  2. <w:sym w:font="Wingdings" w:char="F0FE"/> 复选框选中
  3. <w:sym w:font="Wingdings" w:char="F0A1"/> 单选框没选中
  4. <w:sym w:font="Wingdings" w:char="F0A4"/> 单选框选中

 

现在要做的仅仅是使用freemark判断条件,来判断是要展示复选框选中效果还是非选中效果:

freeMarker导出word文档

 

使用Freemark判断条件,来判断单选框选中效果:

freeMarker导出word文档

 然后剩下的无非是重复性劳动,一个个修改过去,反正我是眼睛都瞪累了才全部改完的。

最后记得把生成的XML文件,修改后缀名为 XXXX.ftl 放到你自己Freemarker的模板加载路径中去。

 

四: Spring加入Freemarker配置并导出doc文件

spring,application文件直接加入相关Freemarker配置,并加入freemarker相关jar包,这里简要展示下配置信息:

  1. <bean id="freeMarkerConfigurer" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
  2. <property name="templateLoaderPaths" value="/template/ftl" />
  3. <property name="freemarkerSettings">
  4. <props>
  5. <prop key="defaultEncoding">UTF-8</prop>
  6. <prop key="url_escaping_charset">utf-8</prop>
  7. <prop key="locale">zh_CN</prop>
  8. <!-- FreeMarker默认每隔N秒检查模板是否被更新,如果已经更新了,就会重新加载并分析模板。
  9. 但经常检查模板是否更新可能比较耗时。如果你的应用运行在生产模式下,而且你预期模板不会经常更新,
  10. 则可以将更新的延迟时间延长至一个小时或者更久。 可以通过为freemarkerSettings属性设置template_update_delay达到这一目的 -->
  11. <prop key="template_update_delay">3600</prop>
  12. <prop key="tag_syntax">auto_detect</prop>
  13. <prop key="whitespace_stripping">true</prop>
  14. <prop key="classic_compatible">true</prop>
  15. <prop key="number_format">0.######</prop>
  16. <prop key="boolean_format">true,false</prop>
  17. <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
  18. <prop key="date_format">yyyy-MM-dd</prop>
  19. <prop key="time_format">HH:mm:ss</prop>
  20. </props>
  21. </property>
  22. </bean>

简单展示下Java代码调用Freemarker生成doc:

  1. private String generalDoc(String storePath, String unid, String templateName) throws Exception {
  2. Template template = null;
  3. Writer out = null;
  4. String docName = null; //文件名
  5. String docPath = ResourceUtils.assemblyPath(storePath, docName);; //完整文件路径
  6. try {
  7. // 输出文件
  8. out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(docPath), "UTF-8"));
  9. Map<String, Object> dataModel = getTemplateDataModel(unid); //组织数据
  10. template = freeMarkerConfigurer.getConfiguration().getTemplate(templateName);
  11. //freemark居然这么差,需要手动指定版本?
  12. template.process(dataModel, out);
  13. //template.process(dataModel, out, new BeansWrapperBuilder(Configuration.VERSION_2_3_24).build());
  14. } catch (Exception e) {
  15. throw e;
  16. } finally {
  17. // 关闭流
  18. IOUtils.closeQuietly(out);
  19. }
  20. return docPath;
  21. }

 这里需要注意的可能就是,如果是需要使用JavaBean形式反射机制进行属性访问的话,可能需要使用new BeansWrapperBuilder(Configuration.VERSION_2_3_24).build()  方式构建BeansWrapper。

  1. private Map<String, Object> getTemplateDataModel(String unid) {
  2. Map<String, Object> row = getGermplasmForMap(unid); //根据id获取数据,返回数据为map形式
  3. if(row == null) { //数据不存在
  4. throw new RuntimeException("数据不存在");
  5. }
  6. row.put("no", "1号表");
  7. row.put("dept_province", "福建省");
  8. row.put("survey_place", "延平区大大乡小小村");
  9. ArrayList<String> treeCategoryList = new ArrayList<String>();
  10. treeCategoryList.add("国家Ⅰ级重点保护植物");
  11. treeCategoryList.add("省外引进树种");
  12. row.put("tree_category_list", treeCategoryList);
  13. return row;
  14. }

 

 

=========================================导出异常记录=============================================

1. 导出的文件,无法打开,,

freeMarker导出word文档

解决办法:  将生成的testlpf_b2_10.doc文件,修改为testlpf_b2_10.xml,使用VS Code打开,找到1349行,发现有个特殊字符, 通过转义来解决。

freeMarker导出word文档

 

2. freemarker模板中不支持 直接使用顿号 、

 

 

附录:word2007-xml存储标签属性说明文件

地址: http://www.doc88.com/p-402265130456.html

				<script>
					(function(){
						function setArticleH(btnReadmore,posi){
							var winH = $(window).height();
							var articleBox = $("div.article_content");
							var artH = articleBox.height();
							if(artH > winH*posi){
								articleBox.css({
									'height':winH*posi+'px',
									'overflow':'hidden'
								})
								btnReadmore.click(function(){
									articleBox.removeAttr("style");
									$(this).parent().remove();
								})
							}else{
								btnReadmore.parent().remove();
							}
						}
						var btnReadmore = $("#btn-readmore");
						if(btnReadmore.length>0){
							if(currentUserName){
								setArticleH(btnReadmore,3);
							}else{
								setArticleH(btnReadmore,1.2);
							}
						}
					})()
				</script>