Beetl 模板引擎生成word以及excel总结

Beetl Java模板引擎生成word excel

之前项目中使用freemarker和POI进行word以及excel的模板导出,在使用的过程中为了解决一些小问题,意外的接触了Beetl这款模板生成导出方案,解决了一些痛点,所以这次就进行总结分享下。

maven依赖
           <dependency>
            <groupId>com.ibeetl</groupId>
            <artifactId>beetl</artifactId>
            <version>2.9.3</version>
           </dependency>

模板制作

创建模板 doc或者xls,设置好样式以及在需要动态加载的地方填充好一些数据(替换变量的时候方便定位),然后另存为xml格式文件;用文本编辑器打开进行模板制作。
在这里插入图片描述



根据beetl的语法,在早先设置的数据处替换为变量,然后修改文件后缀名为btl,即制作好了模板。java项目的话,则放在templates目录下。

在这里插入图片描述

beetl工具类



import org.beetl.core.Configuration;
import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.beetl.core.resource.ClasspathResourceLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;

/**
 * Created on 2018/10/16.
 * Beetl生成doc文档工具类
 */
public class BeetlUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(BeetlUtils.class);

    /** 模板路径 */
    private static final String TEMPLATE_PATH = "templates/";
    /** 模板组 */
    private static GroupTemplate gt ;

    /**
     * 获取到模板
     * @param templateName 模板名称
     * @return beetle模板
     * @throws Exception 异常
     */
    public static Template getTemplate(String templateName)throws Exception{
        if (gt==null) {
            ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(TEMPLATE_PATH);
            Configuration cfg = Configuration.defaultConfiguration();
            gt = new GroupTemplate(resourceLoader, cfg);
        }
        return gt.getTemplate(templateName+".btl");
    }


    public static String renderToString(Map<String, Object> data,String templateName) throws Exception{
        Template template =getTemplate(templateName);
        template.binding(data);
        return template.render();
    }

    /**
     * 下载doc文件
     * @param dataMap 模板数据
     * @param template 模板
     * @throws Exception 异常
     */
    public static void exportWord(String fileName, Map<String, Object> dataMap,Template template,HttpServletResponse response) throws Exception {
        setFileDownloadHeader(response, fileName);
        template.binding(dataMap);
        template.renderTo(response.getOutputStream());
    }
    /**
     * 下载doc文件
     * @param dataMap 模板数据
     * @param template 模板
     * @throws Exception 异常
     */
    public static void exportWord(Map<String, Object> dataMap,Template template,OutputStream out) throws Exception {
        template.binding(dataMap);
        template.renderTo(out);
    }
    /**
     *  设置让浏览器弹出下载对话框的Header
     * @param response web响应
     * @param fileName 文件名
     */
    public static void setFileDownloadHeader(HttpServletResponse response, String fileName) {
        try {
            // 中文文件名支持 ContentType 根据下载的文件不同而不同
            String encodedFileName = URLEncoder.encode(fileName, "UTF-8");
            response.setContentType("application/x-xls");
            response.setHeader("Content-Disposition", "attachment; filename=" + encodedFileName);
        } catch (UnsupportedEncodingException e) {
            LOGGER.error(e.getLocalizedMessage(),e);
        }
    }

    /**
     * 将模板渲染到指定文件
     * @param filePath 文件全路径
     * @param dataMap 数据
     * @param templateName 模板名称
     * @throws Exception 异常
     */
    public static void renderToFile(String filePath,Map<String, Object> dataMap,String templateName) throws Exception{
        Template template =getTemplate(templateName);
        template.binding(dataMap);
        try ( FileOutputStream fileOutputStream = new FileOutputStream(filePath)) {

            template.renderTo(fileOutputStream);
        }


    }


}






调用生成

在controller层或者service层,将查询出的数据放进map中,传递给模板;然后写给流导出。


       Map<String, Object> data = new HashMap<>();
        List<Map> list = new ArrayList<>();

        data.put("listPro",list);

        data.put("count",list.size()+12);

        try {
            Template t = BeetlUtils.getTemplate("product");
            BeetlUtils.exportWord("信息统计表.xls",data,t,response);
        } catch (Exception e) {
            e.printStackTrace();
        }


无论是生成word 还是 excel,都是通过一样的思路:

  1. 先制作模板,然后保存为xml格式。制作模板的时候,可以先预填一些数据,以便在添加变量的时候好找到需要动态替换的位置,然后将预填的固定值改为变量。
  2. 编辑xml文件,通过beetl的语法,将模板内需要动态填充的地方进行变量赋值;需要循环追加元素的地方也是一样。循环的时候需要找到正确的循环节点,即xml里面的,比如word表格里面的行节点等。

注:如果只是需要动态的替换掉某些位置的值,只需要在制作模板的时候设置个好记的初始值,生成另存为xml后打开替换为相应的变量即可;如果是需要动态的填充列表或者段落,道理也是一样,唯一需要注意的是,在xml里面找到预设值的地方后,需要找到合适的xml节点(循环节点),可通过生成测试是否正确,例如 word模板里,<w:p></w:p> 是段落,<w:tr></w:tr>是行,循环的时候则需要包裹它。

  1. 将添加好变量的模板 后缀名改为btl,即Beetl识别的格式。至此模板就做好了,下一步就是动态的填充数据进去生成。
beetl 官网

http://ibeetl.com

使用文档 http://ibeetl.com/guide/#/beetl

其他使用

在项目中遇到过需要编写 各种类型的统计jsp页面,根据选择的不同类型的条件以及限定条件,展示不同的统计页面以及筛选条件项;因为页面是根据选择的统计方式而变化的,所以编写固定的jsp页面就很难实现需求。


这时候找到的解决方法就是:制作jsp生成模板,模板内根据传入的参数进行代码判断,生成不同的元素,执行不同的查询和展示;即动态的生成 ‘代码页面’。后续可以加上一些判断,如将生成的jsp页面进行有效的命名,跳转页面的时候进行判断,如果已经生成(生成路径下存在 xx.jsp),则直接返回jsp路径,否则就先调用通过模板生成页面的方法,再返回jsp路径;



<${jsp_char_start} page language="java" pageEncoding="UTF-8"${jsp_char_end}>
<!DOCTYPE html  PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<${jsp_char_start} taglib prefix="ta" tagdir="/WEB-INF/tags/tatags"${jsp_char_end}>
<html>
<head>
    <title></title>
    <${jsp_char_start} include file="/ta/inc.jsp"${jsp_char_end}>
    <script type="text/javascript" src="<${jsp_char_end}=basePath${jsp_char_end}>js/reportout.js"></script>
    <script type="text/javascript" src="<${jsp_char_end}=basePath${jsp_char_end}>js/inputTree.js"></script>
    <style type="text/css">
        .my-tree .fielddiv2{
            border-radius: 10px;

        }
        .my-tree .fielddiv{
            margin-top: 0px !important;
        }

    </style>
</head>
<%
  var length = report_item.~size;
   var is_4 = length%4==0;
%>
<body class="no-scrollbar" >
<ta:pageloading/>
<ta:box cssStyle="padding:10px;" fit="true">
<ta:text id="raqFile" display="false" value="${raq_file}"/>
<ta:text id="saveAsName" display="false" value="${report_name}"/>
<ta:text id="reportId" display="false" value="${report_id}"/>
<% if(length>0){%>
<ta:fieldset cols="${is_4?3:4}" id="reportQuery" key="检索条件" >
<%
for (item in report_item){
%>
    <%
       if (item.condition_type=='select' && item.condition_mb == '2'){

    %>

            <ta:text id="${item.condition_field}" placeholder="请选择" key="${item.condition_name}" span="1"></ta:text>
            <ta:box cssClass="my-tree" cssStyle="height: 300px;width:69%; overflow: auto;position: absolute;z-index: 99999;display:none;left: -235px;
              top: 30px;padding-top:2px; background-color: #ffffff;border: 1px solid #dddddd;" span="1" >
                <ta:fieldset cols="3">
                <ta:text id="treeSearch-in" span="2" placeholder="搜索"></ta:text>
                <ta:button id="treeSearch-btn" key="检索"></ta:button>
                </ta:fieldset>
            <ta:tree showIcon="true"  id="${item.condition_field+'_tree'}" showLine="true" nameKey="name" childKey="id" parentKey="pid"  checkable="true"></ta:tree>
            </ta:box>

<%
    }
 else {

            var dataTypeStr = item.condition_type;
            var selectType = item.condition_mb;
            var dateCondition = item.condition_date;
            var taType = "text";
            var collectionValue;
            var selectCol = "sqlType";
            var dateShowType = '';
            if(dataTypeStr == "date"){
                 taType = "date";
                 if(item.condition_date!='' && item.condition_date != 'date'){

                     var words = strutil.split(dateCondition,"_");
                     if(array.contain(words,"issue")){

                        dateShowType = ' issue ="true"';
                     }else{

                        dateShowType = words[0]+'="true"' ;
                     }

                 }
            }

            if(dataTypeStr == "number"){
                 taType = "number";
            }
            if(dataTypeStr == "select" && selectType == '1'){
                taType = "selectInput";
                selectCol = "colType";
                collectionValue = ' collection="'+item.condition_code+'"' ;
            }
            if(dataTypeStr == "select" && selectType == '0'){
                taType = "selectInput";
            }
%>

         <ta:${taType} id="${item.condition_field}"${selectCol=="colType"?collectionValue:null} ${taType=="date"? dateShowType:null}${taType=="date"?' showSelectPanel="true"':null} key="${item.condition_name}" span="1" />
 <%
    }
 }
%>
        <ta:buttonLayout span="1" align="left" >
            <ta:button key="查询"  onClick="search()"/>
            <ta:button key="导出"  onClick="execute()"/>
        </ta:buttonLayout>

</ta:fieldset>
<%}%>
    <ta:box id="son_box" cssStyle="width:100%;" fit="true">
    <ta:panel >
    <% if(length==0){%>
     <ta:buttonLayout id="_runqian_daochu"  span="1" align="left" cssStyle="">

                <ta:button key="导出"  onClick="execute()"/>
            </ta:buttonLayout>
<%}%>
            <iframe src="" frameborder="0" id="showReportIframe"></iframe>
            </ta:panel>
   </ta:box>
</ta:box>
</body>
</html>

<script type="text/javascript">
    var paramAll = [];
    $(document).ready(function () {
        $("body").taLayout();
          $("#showReportIframe").attr("width",$("#son_box").width());
          $("#showReportIframe").attr("height",$("#son_box").height()-($("#_runqian_daochu").height()?$("#_runqian_daochu").height():0));
          initTreeData();
          noFilter();
          changeStyle();
    });

    function initTreeData() {

       <%
        for (item in report_item){

           if (item.condition_type=='select' && item.condition_mb == '2'){

        %>

            var param={};
            param["dto['condition_field']"] = '${item.condition_field}';
            param["dto['report_id']"] = Base.getValue("reportId");
            Base.submit("" , "toReport!getSqlSelectTree.do" ,param ,null ,null ,function (data) {

               var ${item.condition_field+'_Tree'};
               ${item.condition_field+'_Tree'}=Object.create(InputTree);
               ${item.condition_field+'_Tree'}.init("${item.condition_field}","${item.condition_field+'_tree'}",data.fieldData.treeData,500)
            });

        <%
           }
        }
        %>
    }
    function changeStyle() {

       <%
        for (item in report_item){

           if (item.condition_name=='至'){

        %>
           $('label[for="${item.condition_field}"]').css('width','60px');

        <%
           }
        }
        %>
    }


    //无参报表,自动展示
    function noFilter(){

    <% if(length==0){%>
        search();
    <%}%>

    }

    function search() {

        if (Base.getValue('raqFile') == ''){
            parent.Base.alert("报表文件不存在!" , "error");
            return;
        }

        Base.submit("raqFile,reportId","toReport!checkRaqFileExist.do",null,null,null,function (data) {

            var file_flag = data.fieldData.dataMap.file_flag;
            if(file_flag){
                Base.submit("raqFile,saveAsName,reportId,reportQuery","toReport!showReportData.do",null,null,null,function (data) {

                    //获取到动态生成的jsp文件路径
                    var fileUrl = data.fieldData.dataMap.fileUrl;
                    if(fileUrl){
                       parent.Base.showMask();
                       document.getElementById('showReportIframe').src = "<${jsp_char_end}=basePath${jsp_char_end}>reportnew"+ "/toReport!toSonIframePage.do" + "?dto.fileUrl="+fileUrl;
                    }
                });

            }else{
               parent.Base.alert("报表文件不存在!" , "error");
               return;
            }
        });


    }

        /*导出*/
        function execute(){
            var allParam = [];
            var flag = true;
            var raqFile = Base.getValue('raqFile');
            var saveName = Base.getValue('saveAsName');
            if (raqFile == ''){
                parent.Base.alert("报表文件不存在!" , "error");
                return;
            }
        <%
         for (var i=0;i<report_item.~size;i++){
            var conditionField = report_item[i].condition_field;
            var baseValue = 'Base.getValue("'+conditionField+'")' ;
         %>
            var ${conditionField} = ${baseValue};
            allParam.push({key:"${conditionField}",value:${baseValue}});
         <%}%>

            if (flag) {
                var options = {
                    params: allParam,
                    raq: raqFile,
                    url: "<${jsp_char_end}=basePath${jsp_char_end}>",
                    saveAsName: saveName
                };
                download(options);
            }
        }

</script>
<${jsp_char_start} include file="/ta/incfooter.jsp"${jsp_char_end}>

注:导出生成后的word或者excel,一般常见的问题有:wps可以正常打开而office却打开报错。

一.制作excel模板生成的 xls wps可以打开,office打开报错

参考博文 https://blog.csdn.net/klchht/article/details/52218203

解决测试:

  1. ExpandedRowCount 设置的行数值小于实际行数则报错,修改成 xml文件里默认的ExpandedRowCount+追加的数据行数,但是还是office打不开
  2. 发现另存的xml里还有ActiveRow节点 跟 ExpandedRowCount一致,也修改成默认的ActiveRow数+追加的数据行数,测试office可以打开

二. 生成word数据 设置 ContentType 错误, response.setContentType;需要针对导出 word、还是xls 选择不同的ContentType,可以网上搜索

要点:

  1. 生成的模板最好使用office来进行制作,避免一些未知的错误(适当使用不是最新版本的),
  2. 语法参考Beetl的使用文档

网上关于beetl的生成模板的使用文档比较少,自己最开始使用的时候也是找了很久,踩过很多的坑,当然模板生成的解决方案有很多,freemarker以及poi都可以,综合使用后,还是觉得beetl比较方便,对于生成出的文档的格式,兼容性也不错。有时间的话可以试一试哟。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

soothings

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值