这两天接到一个需求,要在系统中生成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相关推荐

  1. Java中利用freemarker模板动态生成word含表格

    最近公司有导出word的需求,由于word的样式有的很复杂所以记录一下Java中利用freemarker模板动态生成word含表格,以防以后忘记. 1.word表格的模板 删掉无用的数据留下基础的样式 ...

  2. spring mvc项目中利用freemarker生成自定义标签

    2019独角兽企业重金招聘Python工程师标准>>> spring mvc项目中利用freemarker生成自定义标签 博客分类: java spring mvc +freemar ...

  3. 利用freemarker生成带fusioncharts图片的word简报

    /**  * 利用freemarker生成带fusioncharts图片的word简报  *         烟台海颐软件技术论坛  *         作者  牟云飞 新建 *         毕业 ...

  4. Java中利用freemarker导出word表格并合并单元格

    1.word表格的模板 另存为xml格式: 将保存的xml改成.ftl 格式化一下xml,看看文件中的带有${}是否正确 如果出现这种情况,手动修改下(可复制上一个正确的改下名字) 2.Java代码 ...

  5. Java项目中利用Freemarker模板引擎导出--生成Word文档

    应邀写的一篇文章:Java项目中利用Freemarker模板引擎导出--生成Word文档 资源下载:https://download.csdn.net/download/weixin_41367523 ...

  6. freemarker 生成java_半自动化Java代码生成器[利用freemarker模板生成]

    rapid-generator 半自动化Java代码生成器[利用freemarker模板生成] 增加一些定制和扩展, 修改为基于MAVEN的格式. 整体架构保持不变. 增加的特性为: 支持表名前缀去除 ...

  7. c#获取对象的唯一标识_在 Java 中利用 redis 实现分布式全局唯一标识服务

    作者: 杨高超 juejin.im/post/5a4984265188252b145b643e 获取全局唯一标识的方法介绍 在一个IT系统中,获取一个对象的唯一标识符是一个普遍的需求.在以前的单体应用 ...

  8. Java中 利用继承的思想实现动物的分类,将动物分为水生动物和陆生动物,有其动物的属性和特征,又有其属性和特征,编程模拟。

    Java中 利用继承的思想实现动物的分类,将动物分为水生动物和陆生动物,有其动物的属性和特征,又有其属性和特征,编程模拟. 前言 动物在拥有自己本身所具有的特征的同时,还应该具有所在大类所具有的动物特 ...

  9. Java中利用socket实现简单的服务端与客户端的通信(中级)——实现任意双向通信

    本文计划采用socket实现客户端和服务端的任意双向通信,即客户端可以随时给服务端发消息,服务端也可以随时给客户端发消息,最终结果就是一个类似与QQ的聊天软件的功能. 以下代码可以直接拷贝到Eclip ...

最新文章

  1. Turn over a new leaf
  2. Scrapy分布式原理及Scrapy-Redis源码解析(待完善)
  3. Kylin 对维度表的的要求
  4. HDFS集群常见异常及排查步骤
  5. LNMP 1.2 Nginx编译安装
  6. 店宝宝:电商直播被“敲响警钟”了
  7. linux docker运行exe,在Windows上的Bash上运行Docker容器
  8. java log4j 路径配置_指定log4j配置文件路径
  9. java socket.close_java – Socket.close()在Socket.connect()期间无效
  10. 软件项目文档——WBS
  11. 如何把握云计算时代风口 怎么能掌握云计算技术
  12. .ldb文件到底派什么用场得?
  13. 混凝土抗压弹性模量自动计算表_2011混凝土弹性模量试验.doc
  14. StringBuffer的理解
  15. 程序员能有什么好的出路?
  16. SAP schema增强
  17. 15个强大的iPad应用程序推荐
  18. 图像分割—灰度阈值分割
  19. Ubuntu下使用VSCode编译调试Betaflight飞控
  20. FlexRay™ 协议控制器 (E-Ray)-06

热门文章

  1. 疫情之下,多类数据密织“防护网”
  2. css超出滚动时隐藏滚动条
  3. 瓦力 - 基于ElasticSearch的搜房网实战
  4. 树12——构造哈夫曼树并输出哈夫曼编码
  5. 网络上打开别人计算机上的共享文件夹提示没权限访问
  6. 高德地图的地理空间数据可视化(Loca)
  7. 哈希树(HashTree)
  8. RESTful API接口规范
  9. BOFC、BOF与BOVW
  10. FreePBX 命令一览