jxls2.x 模板导出 excel

      这段时间因为公司业务需求,需要每天、每周为运营人员做个数据统计,需要按照给定的模板,定时将数据导出并发送给指定的人,如果纯粹用POI去做的话,生成指定模板格式的excel会很复杂,所以在玩网上搜了一下,看看有没有好的现成的工具,结果让我发现的一个非常好用又非常简单的工具,那就是jxls,刚开始用的jxls1.x,但是功能还不能满足我的所有要求,所以后来升级换成了现在使用的jxls2.x版本,现在将jxls2.x的使用通过博客记录下来,以后如果还需要用到,直接看自己写的博客就可以了。

       jxls使用非常简单,只需要事先做好需要生成的excel样式,再加入jxls的指令和表达式即可做成模板文件,根据模板文件可以原样输出excel文件,特别是对于复杂样式的模板,简直不要太爽~~~

jxls官网地址:http://jxls.sourceforge.net/index.html

       最开始是按照李狐同学的博客:https://blog.csdn.net/sinat_15769727/article/details/78898894 完成的第一版,但是没有合并单元格的功能,之后找到 lnktoking 写的开源jxls 增强版 jxlss:https://gitee.com/lnkToKing/jxlss,将其中新功能之一的合并单元格功能整合到自己的项目中,非常感谢两位大神的贡献。

话不多说,接下来直接上代码

首先导入jxls的依赖:


    org.jxls
    jxls
    2.4.6


    org.jxls
    jxls-poi
    1.0.12


    org.jxls
    jxls-jexcel
    1.0.6

使用的jxls的版本是2.4.6,jxls-poi的版本是1.0.12,有了这两个依赖就可以完成excel的导出了,但是如果需要动态合并单元格的话就需要导入jxls-jexcel的依赖

站在巨人的肩膀上,导出使用的封装工具类:

package com.xixi.demo;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.jxls.builder.xls.XlsCommentAreaBuilder;
import org.jxls.common.Context;
import org.jxls.expression.JexlExpressionEvaluator;
import org.jxls.transform.Transformer;
import org.jxls.transform.poi.PoiTransformer;
import org.jxls.util.JxlsHelper;

public class JxlsUtils{
    
	static{ 
		//添加自定义指令(可覆盖jxls原指令) 
		//合并单元格(模板已经做过合并单元格操作的单元格无法再次合并)
		XlsCommentAreaBuilder.addCommandMapping("merge", MergeCommand.class); 
	}
	
    public static void exportExcel(InputStream is, OutputStream os, Map 
 model) throws IOException{
        Context context = PoiTransformer.createInitialContext();
        if (model != null) {
        	for (Map.Entry entry : model.entrySet()){
        		context.putVar(entry.getKey(), entry.getValue());
        	}
        }
        JxlsHelper jxlsHelper = JxlsHelper.getInstance();
        Transformer transformer  = jxlsHelper.createTransformer(is, os);
        //获得配置
        JexlExpressionEvaluator evaluator = (JexlExpressionEvaluator)transformer.getTransformationConfig().getExpressionEvaluator();
        //设置静默模式,不报警告
        evaluator.getJexlEngine().setSilent(true);
        //函数强制,自定义功能
        Map funcs = new HashMap();
        funcs.put("jx", new JxlsUtils());    //添加自定义功能
        evaluator.getJexlEngine().setFunctions(funcs);
        //必须要这个,否者表格函数统计会错乱
        jxlsHelper.setUseFastFormulaProcessor(false).processTemplate(context, transformer);
    }

    public static void exportExcel(File xls, File out, Map model) throws FileNotFoundException, IOException {
            exportExcel(new FileInputStream(xls), new FileOutputStream(out), model);
    }
    
    public static void exportExcel(String templatePath, OutputStream os, Map model) throws Exception {
        File template = getTemplate(templatePath);
        if(template != null){
            exportExcel(new FileInputStream(template), os, model);    
        } else {
            throw new Exception("Excel 模板未找到。");
        }
    }
    
    //获取jxls模版文件
    public static File getTemplate(String path){
        File template = new File(path);
        if(template.exists()){
            return template;
        }
        return null;
    }    
    
    // 日期格式化
    public String dateFmt(Date date, String fmt) {
        if (date == null) {
            return "";
        }
        try {
            SimpleDateFormat dateFmt = new SimpleDateFormat(fmt);
            return dateFmt.format(date);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
    
    // if判断
    public Object ifelse(boolean b, Object o1, Object o2) {
        return b ? o1 : o2;
    }
}

合并单元格功能实现:

package com.xixi.demo;

import jxl.write.WriteException;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.jxls.area.Area;
import org.jxls.command.AbstractCommand;
import org.jxls.command.Command;
import org.jxls.common.CellRef;
import org.jxls.common.Context;
import org.jxls.common.Size;
import org.jxls.transform.Transformer;
import org.jxls.transform.jexcel.JexcelTransformer;
import org.jxls.transform.poi.PoiCellData;
import org.jxls.transform.poi.PoiTransformer;

/**
 * 

合并单元格

* jx:merge( * lastCell="单元格" * [, cols="合并的列数"] * [, rows="合并的行数"] * [, minCols="最小合并的列数"] * [, minRows="最小合并的行数"] * ) * @author Victor * @date 2018年12月6日 */ public class MergeCommand extends AbstractCommand{ private String cols; //合并的列数 private String rows; //合并的行数 private String minCols; //最小合并的列数 private String minRows; //最小合并的行数 private CellStyle cellStyle;//第一个单元格的样式 private Area area; @Override public String getName() { return "merge"; } @Override public Command addArea(Area area) { if (super.getAreaList().size() >= 1) { throw new IllegalArgumentException("You can add only a single area to 'merge' command"); } this.area = area; return super.addArea(area); } @Override public Size applyAt(CellRef cellRef, Context context) { int rows = area.getSize().getHeight(); int cols = area.getSize().getWidth(); rows = Math.max(getVal(this.rows, context), rows); cols = Math.max(getVal(this.cols, context), cols); rows = Math.max(getVal(this.minRows, context), rows); cols = Math.max(getVal(this.minCols, context), cols); if(rows > 1 || cols > 1){ Transformer transformer = this.getTransformer(); if(transformer instanceof PoiTransformer){ poiMerge(cellRef, context, (PoiTransformer)transformer, rows, cols); }else if(transformer instanceof JexcelTransformer){ jexcelMerge(cellRef, context, (JexcelTransformer)transformer, rows, cols); } } area.applyAt(cellRef, context); return new Size(cols, rows); } protected Size poiMerge(CellRef cellRef, Context context, PoiTransformer transformer, int rows, int cols){ Sheet sheet = transformer.getWorkbook().getSheet(cellRef.getSheetName()); CellRangeAddress region = new CellRangeAddress( cellRef.getRow(), cellRef.getRow() + rows - 1, cellRef.getCol(), cellRef.getCol() + cols - 1); sheet.addMergedRegion(region); //合并之后单元格样式会丢失,以下操作将合并后的单元格恢复成合并前第一个单元格的样式 area.applyAt(cellRef, context); if(cellStyle == null){ PoiCellData cellData = (PoiCellData)transformer.getCellData(area.getStartCellRef()); if(cellData != null){ cellStyle = cellData.getCellStyle(); } } setRegionStyle(cellStyle, region, sheet); return new Size(cols, rows); } protected Size jexcelMerge(CellRef cellRef, Context context, JexcelTransformer transformer, int rows, int cols){ try { transformer.getWritableWorkbook().getSheet(cellRef.getSheetName()) .mergeCells( cellRef.getRow(), cellRef.getCol(), cellRef.getRow() + rows - 1 , cellRef.getCol() + cols - 1); area.applyAt(cellRef, context); } catch (WriteException e) { throw new IllegalArgumentException("合并单元格失败"); } return new Size(cols, rows); } private static void setRegionStyle(CellStyle cs, CellRangeAddress region, Sheet sheet) { for (int i = region.getFirstRow(); i <= region.getLastRow(); i++) { Row row = sheet.getRow(i); if (row == null) { row = sheet.createRow(i); }for (int j = region.getFirstColumn(); j <= region.getLastColumn(); j++) { Cell cell = row.getCell(j); if (cell == null) { cell = row.createCell(j); } if (cs == null){ cell.getCellStyle().setAlignment(CellStyle.ALIGN_CENTER); cell.getCellStyle().setVerticalAlignment(CellStyle.VERTICAL_CENTER); }else { cell.setCellStyle(cs); } } } } private int getVal(String expression, Context context){ if(StringUtils.isNotBlank(expression)){ Object obj = getTransformationConfig().getExpressionEvaluator().evaluate(expression, context.toMap()); try { return Integer.parseInt(obj.toString()); } catch (NumberFormatException e) { throw new IllegalArgumentException("表达式:" + expression + " 解析失败"); } } return 0; } public String getCols() { return cols; } public void setCols(String cols) { this.cols = cols; } public String getRows() { return rows; } public void setRows(String rows) { this.rows = rows; } public String getMinCols() { return minCols; } public void setMinCols(String minCols) { this.minCols = minCols; } public String getMinRows() { return minRows; } public void setMinRows(String minRows) { this.minRows = minRows; } }

其中:

  1. 自定义功能就是jxlss中的的合并单元格功能,想整合其他功能在静态代码块中注册对应的功能实现就可以了,本文只自定义了合并单元格功能
  2. 静默模式是在导出excel模板时候,如果模板中标签名没有在传入的map中找到数值,会打印报告某某某标签没有赋值。如果开启静默模式后则不会报告。但是出现严重异常还是会正常报错的
  3. 函数统计错乱,这个必须设置(setUseFastFormulaProcessor(false)),如果不设置成false,用excel自带函数统计相加会加错地方,笔者目前没有用到这项功能

 好了,工具封装好了,那接下来就是需要导出的数据以及根据数据格式写好excel模板,也不一个一个功能单独说了,想一步一步来可以去看李狐同学的教程, 直接上一个综合的demo

数据准备&&main方法:

package com.xixi.demo;

import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class App 
{
    public static void main( String[] args ) throws Exception {

        List students = new ArrayList<>();

        Student student = new Student();
        student.setName("张三");
        student.setGender("男");
        student.setGradeClass("初一一班");
        List courses = new ArrayList<>();
        Course course = new Course();
        course.setCourseName("语文");
        course.setCourseScore("98");
        courses.add(course);
        course = new Course();
        course.setCourseName("数学");
        course.setCourseScore("105");
        courses.add(course);
        course = new Course();
        course.setCourseName("物理");
        course.setCourseScore("80");
        courses.add(course);

        student.setCourses(courses);
        students.add(student);


        student = new Student();
        student.setName("王丽丽");
        student.setGender("女");
        student.setGradeClass("初一二班");
        courses = new ArrayList<>();
        course = new Course();
        course.setCourseName("语文");
        course.setCourseScore("102");
        courses.add(course);
        course = new Course();
        course.setCourseName("数学");
        course.setCourseScore("110");
        courses.add(course);
        student.setCourses(courses);
        students.add(student);

        student = new Student();
        student.setName("李梅");
        student.setGender("女");
        student.setGradeClass("初一三班");
        courses = new ArrayList<>();
        course = new Course();
        course.setCourseName("语文");
        course.setCourseScore("110");
        courses.add(course);
        course = new Course();
        course.setCourseName("数学");
        course.setCourseScore("100");
        courses.add(course);
        course = new Course();
        course.setCourseName("物理");
        course.setCourseScore("85");
        courses.add(course);
        student.setCourses(courses);
        students.add(student);

        //模板里展示的数据
        Map data = new HashMap<>();
        data.put("students", students);

        // 模板路径和输出流
        String templatePath = "E:\\jxls\\studentTemplate.xlsx";
        OutputStream os = new FileOutputStream("E:\\jxls\\student.xls");

        //调用封装的工具类,传入模板路径,输出流,和装有数据的Map,按照模板导出
        JxlsUtils.exportExcel(templatePath, os, data);
        os.close();
    }
}

Student类:

package com.xixi.demo;

import java.util.ArrayList;
import java.util.List;

/**
 * @Auther: Victor
 * @Date: 2018/12/6 11:04
 * @Description:
 */
public class Student {

    private String name;

    private String gender;

    private String gradeClass;

    private List courses = new ArrayList<>();

    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", gender='" + gender + '\'' + ", gradeClass='" + gradeClass + '\'' + ", courses=" + courses + '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getGradeClass() {
        return gradeClass;
    }

    public void setGradeClass(String gradeClass) {
        this.gradeClass = gradeClass;
    }

    public List getCourses() {
        return courses;
    }

    public void setCourses(List courses) {
        this.courses = courses;
    }
}

Course类:

package com.xixi.demo;

/**
 * @Auther: Victor
 * @Date: 2018/12/6 11:08
 * @Description:
 */
public class Course {

    private String courseName;

    private String courseScore;

    @Override
    public String toString() {
        return "Course{" + "courseName='" + courseName + '\'' + ", courseScore='" + courseScore + '\'' + '}';
    }

    public String getCourseName() {
        return courseName;
    }

    public void setCourseName(String courseName) {
        this.courseName = courseName;
    }

    public String getCourseScore() {
        return courseScore;
    }

    public void setCourseScore(String courseScore) {
        this.courseScore = courseScore;
    }
}

最后导出模板studentTemplate.xlsx:

jxls2.x 模板导出 excel_第1张图片

模板的注解含义在这里就不多说了,直接看官网或者看上面两位大神的介绍,注解中的items就是数据data中的key,表达式${}中的值就是data中数据对象的属性

好了,模板写好了,直接执行上面的main方法

导出结果:

jxls2.x 模板导出 excel_第2张图片

怎么样,简单吧,比起直接用POI导出工作量不知道减少多少

友情提示:

  1. 模板的第一个单元格的注释中,lastCell的值区域可以设置大些,防止之后新增列或者其他选项导致区域变大,而忘记了调整lastCell的值而导致报错,大些不会出错,但是小了,空指针异常就来了
  2. 合并单元格功能jxlss目前有个问题,就是需要合并单元格的单元格在模板中不能进行合并单元格操作,如果做了该操作,导出时单元格不会再做合并操作,但是为了模板好看,通常会做合并单元格操作,我的项目就是这样,已给jxlss作者留言,期待后续能够解决~~~

demo展示的是纵向循环数据输出,横向循环输出也是可以的,需要在注解里加一个属性direction="RIGHT"就行了:

jx:each(items="students" var="student" lastCell="E3" direction="RIGHT")

横向输出demo这里就不再陈述了,可以自行去实践研究,导出图片和其他功能可以参阅开源项目jxlss

最后,这是笔者第一次写的第一篇博客,写得不清楚的地方还望各位看官见谅,有什么错误的地方欢迎指正,大家一起学习,共同进步,由于篇幅的问题,这里写的是综合多个功能点的demo,想看详细的教程请移步上面两位大神的博客或者开源项目中详阅。

demo源码就不贴了,上面展示的就是所有源码

参考博客:

https://blog.csdn.net/sinat_15769727/article/details/78898894

https://blog.csdn.net/lnktoking/article/details/79195500

 

你可能感兴趣的:(java学习笔记)