java中利用freemarker生成样式比较复杂的word
这两天接到一个需求,要在系统中生成word版的需求规格说明书,领导给了个之前的样本给我,要求挺高,必须和给的样本基本一样。
基本样式主要有多级标题、动态图片、页眉页脚等,如下(内容部分因为隐私就不贴出来了):
当时的第一想法是用POI做,花了两个小时时间,果断放弃,POI 功能实现起来也挺简单,但是让我头疼的是样式,比如说行距、缩进、页眉页脚等。因为之前做过freemarker生成PDF、html之类的,所以选择freemarker。
主要步骤如下:
一、把word模板另存为xml格式
我这边是另存为 word2003 XML格式的,网上的说法是如果另存为wordXML的话,会存在2003的兼容性,这个我没去验证,但是我也试了下,如果另存为wordXML格式的话,两种生成模板的方式会略有不同,这里只介绍word2003XML这种方式,
二、生成模板后用notepade++之类的工具打开可以发现里面主要的数据结构都在里面,我这里有部分数据是固定的,所以只需要动态生成部分数据,如下:
这种多级标题需要生成的结构是:
这种数据结构应该都能看懂吧,一个递归搞定,图片问题的稍候再说,
另外再说一句,如果刚开始是另存为wordXML格式的模板,那么数据结构和这种完全不一样,比这个简单的多,但是在生成图片的时候会多操作一点,这个有兴趣的自己去尝试下吧(个人已经试过,比这种简单,但是因为在网上查看说有兼容性问题,所以比较担心,就没用)。
三、后台生成数据,这块不想详细说,说到底就是通过递归或者多层循环的方式做成需要的数据结构就行,简单的上点代码吧
递归的:
public static JSONArray treeRecursionDataList(List<Map<String,Object>> treeList, String parentId) {JSONArray childMenu = new JSONArray();for (Object object : treeList) {JSONObject jsonMenu = JSONObject.parseObject(JSON.toJSONString(object));String menuId = jsonMenu.getString("id");String pid = jsonMenu.getString("pid");if (parentId.equals(pid)) {JSONArray c_node = treeRecursionDataList(treeList, menuId);jsonMenu.put("children", c_node);childMenu.add(jsonMenu);}}return childMenu;
}
两层循环的:
public static List<Map<String,Object>> treeForDataList(List<Map<String,Object>> treeNodes) {List<Map<String,Object>> trees = new ArrayList<Map<String,Object>>();for (Map<String,Object> treeNode : treeNodes) {if ("-1".equals(treeNode.get("pid"))) {trees.add(treeNode);}List<Map<String,Object>> childList = new ArrayList<Map<String,Object>>();for (Map<String,Object> it : treeNodes) {if (it.get("pid").toString().equals(treeNode.get("id").toString())) {childList.add(it);treeNode.put("children",childList);}}}return trees;
}
对于这两种方式: 个人建议如果只是简单的做数据格式,用两层循环就行,特别是你的数据或者层级比较多的情况下,用递归会使用栈内存,比较慢的,我的数据大概有3千条吧,层级稍多,用递归将近70000ms,但是两层循环1000ms左右。
我这里直接map做数据的,如果用实体也行,主要的几个字段无非是 id、pid、level、text、content、children、imgcode(用于展示图片),
最后生成的数据格式类似(比较懒,随便网上找了张图片):
四、后台数据做好了后,下面要做的就是前台渲染,那么如何做成前面所说的数据格式呢,我是用freemarker的宏递归
<#macro tree data>
<#list data as child>
<wx:sub-section>
<#if child?? && child.children?? && (child.children?size gt 0)>
<w:p wsp:rsidR="006A2A1B" wsp:rsidRDefault="006A2A1B" wsp:rsidP="006A2A1B">
。。。。。。。。。。。。。。
</w:p>
</#if>
<@tree data=child.children />
<#else>
<w:p wsp:rsidR="006A2A1B" wsp:rsidRDefault="006A2A1B" wsp:rsidP="006A2A1B">
</w:p>
</#if>
</#if>
</wx:sub-section>
</#list>
</#macro>
<@tree data=reqList/>
主要结构就是这样,一张图片说明下
五、这样话只要一个生成doc的方法就可行了,直接上代码
/*** 以下载的方式生成word,自定义路径* @param dataMap* @param out*/
public void createDoc(Map<String, Object> dataMap,Writer out,String templateFile) {// 设置模本装置方法和路径,FreeMarker支持多种模板装载方法。可以重servlet,classpath,数据库装载,// ftl文件存放路径try {configure.setDirectoryForTemplateLoading(new File("d:/"));Template t = null;t = configure.getTemplate(templateFile);t.setEncoding("utf-8");t.process(dataMap, out);} catch (IOException e) {e.printStackTrace();logger.error("读取文件出错!");} catch (TemplateException e) {e.printStackTrace();logger.error("生成文件出错!");}finally{if (out != null){try {out.close();} catch (IOException e) {logger.error("流关闭出错!");}}}}
dataMap就是传到模板上的数据,我的是dataMap.put("reqList","做好的数据集合");templateFile是你的模板名称:如xxx.xml或者改成XXX.ftl也可以,上面这个方法是流下载的方式,如果生成到本地磁盘的话自己改改就行了,
主体的思路是这样的,这样的话应该可以生成word的了,部分代码就不上了,网上找找都有的
说下我遇到的问题吧:
图片生成系需要在具体的位置增加这段代码就行,
<w:p wsp:rsidR="006A2A1B" wsp:rsidRDefault="009D00C1" wsp:rsidP="006A2A1B">
<w:pPr>
<w:rPr>
<wx:font wx:val="Arial"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<wx:font wx:val="Arial"/>
<w:noProof/>
<w:lang w:bidi="AR-SA"/>
</w:rPr>
<w:pict>
<w:binData w:name="wordml://${child.fid}.jpg" xml:space="preserve">${child.base64code}</w:binData>
<v:shape id="图片 4" o:spid="_x0000_i1026" type="#_x0000_t75" style="width:414.45pt;height:258.55pt;visibility:visible;mso-wrap-style:square">
<v:imagedata src="wordml://${child.fid}.jpg" o:title=""/>
</v:shape>
</w:pict>
</w:r>
</w:p>
这个${child.base64code} 就是图片的base64编码,其中一下几点注意:
1和2出一定需要保持一致并且不能写死,之前我是写死的,生成文档是发现图片都是同一个图片,可以用文件的id替换,style中的width和height树形表示图片的高度和宽度,这块目前我没有做很好的处理,只是固定了一个大小,这样可能会出现部分过小或过大的图片产生变形,目前我测试了几张图片都是按照1.33的比例压缩的,即获取到图片的宽高,除以1.33即可,没想到比较好的方案,但是已经足够满足我的需求。
另外附上获取图片base64码的方法:
public Map<String,Object> getImageStr(String imgFile) {
// InputStream imgin = null;
// BufferedImage img = null;ByteArrayOutputStream data = null;String imgWidth = "";String imgHeight = "";String base64code = "";try {//获取此路径的连接data = new ByteArrayOutputStream();URL url = new URL(imgFile);byte[] by = new byte[1024];// 创建链接 HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.setConnectTimeout(5 * 1000);InputStream is = conn.getInputStream();// 将内容读取内存中 int len = -1;while ((len = is.read(by)) != -1){data.write(by, 0, len);}BASE64Encoder encoder = new BASE64Encoder();base64code = encoder.encode(data.toByteArray());
// imgin = url.openStream();
// img = ImageIO.read(imgin);
// imgWidth = String.valueOf(img.getWidth());
// imgHeight = String.valueOf(img.getHeight());} catch (FileNotFoundException e) {logger.error("未发现该文件!");} catch (IOException e) {logger.error("读取文件出错!");}finally {try {if (data != null){data.close();}} catch (IOException e) {logger.error("流关闭出错!");}}Map<String,Object> result = new HashMap<>();result.put("base64code",base64code);
// result.put("imgWidth",imgWidth);
// result.put("imgHeight",imgHeight);return result;}
2018.12.11.使用xml模板方式生成的word有一个问题就是手机端查看时,全是xml格式的代码,那是因为模板本身就是XML格式文件,freemarker使用的方式是用类型字符串替换的方式,替换掉XML里面的字符然后生成按相同格式生成文件,然后后缀名定为.doc而已。
由于XML文件的头部有<?mso-application progid="Word.Document"?>这样的字符串,所以电脑上的office word读到这个信息后知道按转换xml里标签转换成word的格式。
但手机上的word软件则没有这个功能,所以就打开失败。
解决方法如下:
https://blog.csdn.net/FORLOVEHUAN/article/details/81452169
写的比较乱,但是整体思路就是这些,如果遇到问题可以留言讨论,或者各位有更好的解决方法的话,也请指正
java中利用freemarker生成样式比较复杂的word相关推荐
- Java中利用freemarker模板动态生成word含表格
最近公司有导出word的需求,由于word的样式有的很复杂所以记录一下Java中利用freemarker模板动态生成word含表格,以防以后忘记. 1.word表格的模板 删掉无用的数据留下基础的样式 ...
- spring mvc项目中利用freemarker生成自定义标签
2019独角兽企业重金招聘Python工程师标准>>> spring mvc项目中利用freemarker生成自定义标签 博客分类: java spring mvc +freemar ...
- 利用freemarker生成带fusioncharts图片的word简报
/** * 利用freemarker生成带fusioncharts图片的word简报 * 烟台海颐软件技术论坛 * 作者 牟云飞 新建 * 毕业 ...
- Java中利用freemarker导出word表格并合并单元格
1.word表格的模板 另存为xml格式: 将保存的xml改成.ftl 格式化一下xml,看看文件中的带有${}是否正确 如果出现这种情况,手动修改下(可复制上一个正确的改下名字) 2.Java代码 ...
- Java项目中利用Freemarker模板引擎导出--生成Word文档
应邀写的一篇文章:Java项目中利用Freemarker模板引擎导出--生成Word文档 资源下载:https://download.csdn.net/download/weixin_41367523 ...
- freemarker 生成java_半自动化Java代码生成器[利用freemarker模板生成]
rapid-generator 半自动化Java代码生成器[利用freemarker模板生成] 增加一些定制和扩展, 修改为基于MAVEN的格式. 整体架构保持不变. 增加的特性为: 支持表名前缀去除 ...
- c#获取对象的唯一标识_在 Java 中利用 redis 实现分布式全局唯一标识服务
作者: 杨高超 juejin.im/post/5a4984265188252b145b643e 获取全局唯一标识的方法介绍 在一个IT系统中,获取一个对象的唯一标识符是一个普遍的需求.在以前的单体应用 ...
- Java中 利用继承的思想实现动物的分类,将动物分为水生动物和陆生动物,有其动物的属性和特征,又有其属性和特征,编程模拟。
Java中 利用继承的思想实现动物的分类,将动物分为水生动物和陆生动物,有其动物的属性和特征,又有其属性和特征,编程模拟. 前言 动物在拥有自己本身所具有的特征的同时,还应该具有所在大类所具有的动物特 ...
- Java中利用socket实现简单的服务端与客户端的通信(中级)——实现任意双向通信
本文计划采用socket实现客户端和服务端的任意双向通信,即客户端可以随时给服务端发消息,服务端也可以随时给客户端发消息,最终结果就是一个类似与QQ的聊天软件的功能. 以下代码可以直接拷贝到Eclip ...
最新文章
- Turn over a new leaf
- Scrapy分布式原理及Scrapy-Redis源码解析(待完善)
- Kylin 对维度表的的要求
- HDFS集群常见异常及排查步骤
- LNMP 1.2 Nginx编译安装
- 店宝宝:电商直播被“敲响警钟”了
- linux docker运行exe,在Windows上的Bash上运行Docker容器
- java log4j 路径配置_指定log4j配置文件路径
- java socket.close_java – Socket.close()在Socket.connect()期间无效
- 软件项目文档——WBS
- 如何把握云计算时代风口 怎么能掌握云计算技术
- .ldb文件到底派什么用场得?
- 混凝土抗压弹性模量自动计算表_2011混凝土弹性模量试验.doc
- StringBuffer的理解
- 程序员能有什么好的出路?
- SAP schema增强
- 15个强大的iPad应用程序推荐
- 图像分割—灰度阈值分割
- Ubuntu下使用VSCode编译调试Betaflight飞控
- FlexRay™ 协议控制器 (E-Ray)-06