• 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自动化导出(含源码)

    参考网络文章

    dom操作xml
    dom生成xml
    httpclient获取反应流
    获取jar路径
    itext实现套打
    ftl常见语法
    freemark官网
    ftl判断非空
    freemark自定义函数
    freemark自定义函数java
    freemark特殊字符转义
    java实现word转xml各种格式

    加入战队

    # 加入战队

    微信公众号

    微信公众号

  • 相关阅读:
    结构化程序的三种基本逻辑结构
    总结程序设计几大原则
    [转]AutoResetEvent 与 ManualResetEvent区别
    ASP.NET高并发解决方案
    关于SQL SERVER高并发解决方案
    【转】sql server开启全文索引方法
    SQL Server技术问题之自定义函数优缺点
    SQL Server技术问题之视图优缺点
    SQL Server技术问题之触发器优缺点
    SQL Server技术问题之索引优缺点
  • 原文地址:https://www.cnblogs.com/zhangxinhua/p/12954773.html
Copyright © 2020-2023  润新知