导读

由于项目需要,需要在页面上实现导出功能,根据查询条件来导出。前端在搜索的表单中添加导出按钮。我这边提供对应的接口。

  • 通过百度,发现以下几种方式

JXLS介绍

  • jxls是一个简单的、轻量级的excel导出库,使用特定的标记在excel模板文件中来定义输出格式和布局。
  • 官网:http://jxls.sourceforge.net/
  • excel excel文件的三个特性:
  • 源文件(.xls) :Workbook 使用来获取工作薄对象需要使用的类.
  • 表(sheet) : Sheet 接收对应的 Sheet 文件
  • 单元格(cell):Cell 接收对应的Cell。
  • exce操作:新建、读取和修改。
  • 在使用jXLS时,你所需要做的就是在一个Excel模板中定义所有报表格式和数据布局,并运行jXLS引擎,为它提供数据以填充模板。在大多数情况下,你需要编写的唯一代码是使用适当配置的jXLS引擎的简单调用。

主要概念

Area(区域)

  • Area代表Excel文件中的矩形区域。可以使用单元格范围或使用开始单元格和区域大小(行和列数)定义矩形区域。Area包括特定范围的所有Excel单元格。

  • 每个Area可以关联一组命令,jXLS引擎处理区域时执行和区域相关的命令。Area可以嵌套子区域。每个子区域也是一个Area,也可以包含自己的命令和自己的子区域。

  • Area可以使用以下方式定义:

    • 在Excel模板文件中使用特定标记语法。
    • 使用XML配置。
    • 使用jXLS Java API。

Command(命令)

  • 命令代表一个或多个Area的转换动作。Command接口如下所示:

    1
    2
    3
    4
    5
    6
    7
    public interface Command {
    String getName();
    List<Area> getAreaList();
    Command addArea(Area area);
    Size applyAt(CellRef cellRef, Context context);
    void reset();
    }
  • Command的主要方法是Size applyAt(CellRef cellRef, Context context)

    • 该方法在单元格cellRef执行命令动作。
    • context类似于一个map,用于为命令传递数据。
    • 该方法返回转换后区域的大小作为Size对象。
  • 当前,jXLS提供以下内置命令:

    • Each
    • If
    • Image

Transformer

  • Transformer接口允许Area与特定Excel实现无关。这意味着通过提供不同的Transformer接口实现,我们可以使用不同的底层Java->Excel库。

  • Transformer接口如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public interface Transformer {
    void transform(CellRef srcCellRef, CellRef targetCellRef, Context context, boolean updateRowHeight);

    void setFormula(CellRef cellRef, String formulaString);

    Set<CellData> getFormulaCells();

    CellData getCellData(CellRef cellRef);

    List<CellRef> getTargetCellRef(CellRef cellRef);

    void resetTargetCellRefs();

    void resetArea(AreaRef areaRef);

    void clearCell(CellRef cellRef);

    List<CellData> getCommentedCells();

    void addImage(AreaRef areaRef, byte[] imageBytes, ImageType imageType);

    void write() throws IOException;

    TransformationConfig getTransformationConfig();

    void setTransformationConfig(TransformationConfig transformationConfig);

    boolean deleteSheet(String sheetName);

    void setHidden(String sheetName, boolean hidden);

    void updateRowHeight(String srcSheetName, int srcRowNum, String targetSheetName, int targetRowNum);
    }
  • 尽管Transformer接口看起来有很多方法,但大多数方法已经在AbstractTransformer基础抽象类中实现,如果需要提供新Java->Excel实现,只需继承抽象类即可。

  • 当前,jXLS提供两种Transformer接口实现:

    • PoiTransformer
    • JexcelTransformer
  • PoiTransformer使用Apache POI类库生成Excel文件。JexcelTransformer基于较老的Java Excel API类库。

Excel生成示例

Maven引入

  • jXLS对Apache POI和Java Excel API进行高级封装,因此,要使用jXLS必须引入底层Apache POI或Java Excel API,,Apache POI是Java开源社区最强Excel解决方案,选择使用Apache POI作为jXLS的底层API。

    1
    2
    3
    4
    5
    6
    7
    <!-- 基于Java Excel API的转换器实现 -->
    <!-- https://mvnrepository.com/artifact/org.jxls/jxls-jexcel -->
    <dependency>
    <groupId>org.jxls</groupId>
    <artifactId>jxls-jexcel</artifactId>
    <version>${jxls-jexcel.version}</version>
    </dependency>
  • 完整的xml文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    <poi.version>4.1.2</poi.version>
    <jxls.version>2.9.0</jxls.version>
    <jxls-jexcel.version>1.0.9</jxls-jexcel.version>
    <jxls-reader.version>2.0.6</jxls-reader.version>
    <jxl.version>2.6.12</jxl.version>
    <!-- 核心Jxls模块 -->
    <dependency>
    <groupId>org.jxls</groupId>
    <artifactId>jxls</artifactId>
    <version>${jxls.version}</version>
    </dependency>
    <!-- Jxls转换引擎的实现 jXLS-POI适配器 -->
    <dependency>
    <groupId>org.jxls</groupId>
    <artifactId>jxls-poi</artifactId>
    <version>${jxls.version}</version>
    </dependency>
    <!--选择使用Apache POI作为jXLS的底层API -->
    <!-- Apache POI依赖包 -->
    <dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>${poi.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>${poi.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-scratchpad</artifactId>
    <version>${poi.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml-schemas</artifactId>
    <version>${poi.version}</version>
    </dependency>
    <dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-excelant</artifactId>
    <version>${poi.version}</version>
    </dependency>

准备模板

  • 实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Employee {

    private String name;// 员工名称
    private String sex;// 性别
    private String telephone;// 手机号码
    private Date birthday;// 生日
    private BigDecimal payment;// 工资

    // ... 构造函数
    // ... getters/setters

    }
  • 创建Excel模板

  • 模板是使用特定标记规定jXLS应该如何输出数据的Excel文件。j

  • jXLS提供一些内置标记处理器解析Excel模板和提取控制命令。如有需要可以自定义标记处理器。

  • 可以为Excel模板定义自己的标记,并以适当的方式进行解析,以创建jXLS命令结构。

  • 默认,jXLS以Apache JEXL作为表达式语言在Excel模板中引用Java对象属性和方法。对象必须在jXLS上下文中某一键上可用。为了在单元格中输出员工名称,可以在单元格中放入${employee.name}。使用${}环绕JEXL表达式。假设在上下文中employee键下有一个Employee对象。

  • 模板中第三行的单元格使用JEXL表达式引用employee对象的属性。

  • 单元格A1包含的Excel注释jx:area(lastCell="E3")定义模板根区域为A1:E3。

  • 单元格A3包含的Excel注释jx:each(items="employees" var="employee" lastCell="E3")定义jXLS Each命令。

    • Each命令迭代employees(由items属性定义)集合的条目到上下文的employee(由var属性定义)键。
    • 执行Each命令的区域是A3:E3(有lastCell属性定义),该区域将会被克隆,并使用上下文中的每个新的Employee对象处理。

生成xls文件

  • 使用API生成文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static void main(String[] args) throws IOException {
    List<Employee> data = new ArrayList<Employee>();
    for (int i = 0; i < 4; i++) {
    data.add(new Employee("张"+i,i%2==0?"男":"女","1300000000"+i, LocalDate.now(),new BigDecimal( i+"000")));
    }
    InputStream is = Application.class.getClassLoader().getResourceAsStream("employee_template.xls");
    FileOutputStream os = new FileOutputStream("target/employee_output.xls");
    Context context = new Context();
    context.putVar("employees", data);
    JxlsHelper.getInstance().processTemplate(is, os, context);
    }
  • 在target目录下可以看到生产的文件,打开如下:

Area(区域)

  • Area代表Excel文件中需要转换的一个矩形区域。每个Area有一组与之关联的转换命令和一组嵌套子区域。
  • 每个子区域也是一个Area,有自己的一组命令和嵌套子区域。顶层的Area没有父区域(没有嵌套在任意其它Area中)。
  • 构建Area的方式:jXLS使用AreaBuilder接口构建Area,jXLS提供了两个实现类:XmlAreaBuilder和XlsCommentAreaBuilder,这两种构建方式都是基于解析各自的配置使用Java API构建Area。
    • XmlAreaBuilder:基于XML配置文件构建Area
    • XlsCommentAreaBuilder(默认):基于Excel模板中的单元格注释构建Area
    • 通过JxlsHelper.setAreaBuilder()方法切换构建Area的方式

使用Java API构建Area(推荐)

  • Area的实现类为XlsArea,XlsArea有以下构造函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 顶层Area:必须提供一个Transformer实例
    public XlsArea(AreaRef areaRef, Transformer transformer);
    public XlsArea(String areaRef, Transformer transformer);

    //区域Area
    public XlsArea(CellRef startCell, CellRef endCell, Transformer transformer);
    public XlsArea(CellRef startCellRef, Size size, List<CommandData> commandDataList, Transformer transformer);
    public XlsArea(CellRef startCellRef, Size size);
    public XlsArea(CellRef startCellRef, Size size, Transformer transformer);
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 创建Transformer实例
    // 创建顶级区域
    XlsArea xlsArea = new XlsArea("Sheet1!A1:G15", transformer);
    // 创建'department'区域
    XlsArea departmentArea = new XlsArea("Sheet1!A2:G13", transformer);
    // 创建'EachCommand'迭代departments
    EachCommand departmentEachCommand = new EachCommand("department", "departments", departmentArea);
    // 创建'employee'区域
    XlsArea employeeArea = new XlsArea("Sheet1!A9:F9", transformer);
    // 为'IfCommand'创建区域
    XlsArea ifArea = new XlsArea("Sheet1!A18:F18", transformer);
    // 使用指定区域创建'if'命令
    IfCommand ifCommand = new IfCommand("employee.payment <= 2000", ifArea, new XlsArea("Sheet1!A9:F9", transformer));
    // 添加'if'命令实例到employee区域
    employeeArea.addCommand(new AreaRef("Sheet1!A9:F9"), ifCommand);
    // 创建employee 'each'命令,添加到department区域
    Command employeeEachCommand = new EachCommand( "employee", "department.staff", employeeArea);
    departmentArea.addCommand(new AreaRef("Sheet1!A9:F9"), employeeEachCommand);
    // 添加department 'each'命令到顶级区域
    xlsArea.addCommand(new AreaRef("Sheet1!A2:F12"), departmentEachCommand);

使用Excel标记构建Area(常用)

  • 标记放置在区域中的第一个单元格的Excel注释中 jx:area(lastCell = "<AREA_LAST_CELL>"),<AREA_LAST_CELL>定义区域的最后一个单元格引用

  • 为了解析标记并创建XlsArea对象,我们应该使用XlsCommentAreaBuilder类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 获取模板输入流
    InputStream is = Application.class.getClassLoader().getResourceAsStream("employee_template.xls");
    // 创建POI Workbook
    Workbook workbook = WorkbookFactory.create(is);
    // 使用POI Workbook创建PoiTransformer
    PoiTransformer transformer = PoiTransformer.createTransformer(workbook);
    // 创建XlsCommentAreaBuilder实例
    AreaBuilder areaBuilder = new XlsCommentAreaBuilder(transformer);
    // 使用AreaBuilder构造一个Area列表
    List<Area> xlsAreaList = areaBuilder.build();
    // 获取主Area
    Area xlsArea = xlsAreaList.get(0);

    // jXLS API的JxlsHelper类使用XlsCommentAreaBuilder作为默认AreaBuilder实现

使用XML配置

  • 创建XML配置文件定义Area,根元素是xls。然后列出大量顶级area。

    • 使用each xml元素定义了each命令 ,使用ref属性关联每个命令相关的区域
    • 在each命令中有一个嵌套区域参数
    1
    2
    3
    4
    5
    6
    7
    8
    <xls>
    <!-- 顶级区域A1:E3 -->
    <area ref="Sheet1!A1:E3">
    <each items="employees" var="employee" ref="Sheet1!A1:E3">
    <area ref="Sheet1!A1:E3"/>
    </each>
    </area>
    </xls>

jXLS Excel标记

  • jXLS的Excel标记分为3种:

    • Bean属性标记
    • 区域标记
    • 命令标记
  • jXLS提供XlsCommentAreaBuilder类从Excel单元格注释读取标记。XlsCommentAreaBuilder实现通用AreaBuilder接口。AreaBuilder接口如下所示:

    1
    2
    3
    4
    // 返回一个Area对象列表
    public interface AreaBuilder {
    List<Area> build();
    }
  • 如果你想要定义自己的标记,可以创建自己的AreaBuilder实现,解释输入Excel模板或任意其它输入。

Bean属性标记

  • jXLS使用Apache JEXL表达式语言处理Bean属性标记。在未来版本中,可以配置表达式语言引擎,以便在需要时可以用任何其他表达式引擎替换JEXL。
  • 默认,jXLS期望JEXL表达式放在模板文件中的${和}之间。
    • 例如,以下单元格内容${department.chief.age} years告诉jXLS,
    • 假设,在Context中有一个department对象有一个chief对象有一个age属性,使用JEXL计算department.chief.age
    • 如果表达式department.getChief().getAge()计算等于35,jXLS将放置35 years在单元格中。

区域标记

  • jXLS区域标记用于定义jXLS引擎处理的根XlsArea。XlsCommentAreaBuilder支持的Excel单元格注释区域定义语法:jx:area(lastCell="<LAST_CELL>")

  • 定义矩形区域右下角单元格。第一个单元格是定义Excel注释的单元格。因此,假设我们有一个注释jx:area(lastCell="G12")在单元格A1中,根区域将读取A1:G12

  • 读取所有区域到xlsAreaList,然后保存第一个区域到xlsArea变量:

    1
    2
    3
    AreaBuilder areaBuilder = new XlsCommentAreaBuilder(transformer);
    List<Area> xlsAreaList = areaBuilder.build();
    Area xlsArea = xlsAreaList.get(0);

命令标记

  • 命令应该定义在XlsArea中。XlsCommentAreaBuilder接收以下命令符创建Excel单元格注释:

    1
    jx:<command_name>(attr1='val1' attr2='val2' ... attrN='valN' lastCell=<last_cell> areas=["<command_area1>", "<command_area2", ... "<command_areaN>"])
    • <command_name>是在XlsCommentAreaBuilder中预注册或手动注册的命令名称。当前,预注册了以下命令:each,if,image
    • attr1, attr2,…, attrN是命令属性。if命令有condition属性设置条件表达式。
    • <last_cell>定义命令区域的右下角单元格。右上角由注释所在单元格决定。
    • <command_area1>, <command_area2>, … <command_areaN>——区域作为参数传递给命令。
  • 使用XlsCommentAreaBuilder的static void addCommandMapping(String commandName, Class clazz)方法注册自定义命令。

  • 示例

    • 显示工资小于等于2000的信息

Each命令

  • 用于迭代集合,克隆命令XlsArea。类似于Java语言的for操作符。

命令属性

Each命令有以下属性:

  • var:迭代时,将集合中的每个条目放入Context中的那个键上。

  • items:要迭代的集合在Context中的键值。

  • area:Each命令体的XlsArea。

  • direction:Direction枚举,有效值是DOWN和RIGHT,表示如何(通过行或列)重复命令体。默认为DOWN。

    • 默认,Each命令direction属性设置为DOWN表示命令体基于Excel行向下克隆。
    • 如果你需要通过列克隆区域,应该设置属性值为RIGHT。
  • select:迭代过程中过滤集合条目的选择表达式。

  • groupBy:分组属性。

    • Each命令支持通过groupBy属性分组。groupOrder属性设置排序方式,有效值为desc或asc。

      1
      2
      3
      4
      5
      6
      7
      jx:each(items="employees" var="myGroup" groupBy="name" groupOrder="asc" lastCell="D6")
      # 每个组可以使用myGroup引用。
      # 当前组条目可以使用myGroup.item引用。因此,可以使用以下方式引用员工姓名:
      ${myGroup.item.name}
      # 通过items属性访问分组中的所有条目:
      jx:each(items="myGroup.items" var="employee" lastCell="D6")
      # 也可以跳过var属性,默认组变量名称为_group。
  • groupOrder:分组排序方式(‘desc’或‘asc’)。

  • cellRefGenerator:是否创建目标单元格引用的自定义策略。

  • multisheet:Context中sheet名称列表的键。

  • lastCell:命令指向的最后一个单元格。

  • var和items是必须的。

UpdateCell

  • UpdateCell命令允许你自定义处理特定单元格。

命令属性

UpdateCell命令有以下属性:

  • updater:Context中CellDataUpdater实现的键名。
  • lastCell:命令体区域的最后一个单元格。

解释

  • CellDataUpdater接口如下所示:

    • 转换UpdateCell命令区域之前,调用UpdateCellData方法传入当前的CellData,目标单元格和当前Context。
    • 实现可以更新CellData来设置单元格值。
    1
    2
    3
    public interface CellDataUpdater {
    void updateCellData(CellData cellData, CellRef targetCell, Context context);
    }
  • 例如,下面类更新统计公式:

    • 关键代码是cellData.setEvaluationResult(resultFormula),使用目标公式更新单元格数据。
    1
    2
    3
    4
    5
    6
    7
    8
    class TotalCellUpdater implements CellDataUpdater{
    public void updateCellData(CellData cellData, CellRef targetCell, Context context) {
    if( cellData.isFormulaCell() && cellData.getFormula().equals("SUM(E2)")){
    String resultFormula = String.format("SUM(E2:E%d)", targetCell.getRow());
    cellData.setEvaluationResult(resultFormula);
    }
    }
    }
  • 为了在Excel模板中创建UpdateCell命令,创建一个单元格注释:

    1
    2
    3
    # lastCell属性设置命令体区域最后一个单元格。
    # updater属性设置为totalCellUpdater,处理之前,必须将totalCellUpdater实现加入Context。
    jx:updateCell(lastCell="E4" updater="totalCellUpdater")
    1
    2
    Context context = new Context();
    context.putVar("totalCellUpdater", new TotalCellUpdater());

Grid命令

  • 使用表头信息和数据行区域生成动态表格。
  • 表头作为字符串集合传入,数据行作为对象集合或列表传入。

命令使用

Grid命令有以下属性:

  • headers:表头集合键名。
  • data:数据集合键名。
  • props:逗号分隔的每个grid行的对象属性(如果grid行是对象时必须)。
  • formatCells:逗号分隔的单元格类型映射信息,例如,formatCells=“Double:E1, Date:F1”。
  • headerArea:源表头区域。
  • bodyArea:源体区域。
  • lastCell:命令体区域最后一个单元格。

data属性可以是以下两种类型:

  1. Collection<Collection<Object>>:每个内嵌Collection包含一行的一个单元格值。
  2. Collection<Object>:每个Object包含一行。props用于设置单元格对应的Object属性。

Image命令

  • 用于在Excel报表中输出图片。

用法

  • 在想要输出图片的单元格上添加以下注释:

    1
    jx:image(lastCell="D10" src="image" imageType="PNG")
  • lastCell定义图片包含区域的右下角单元格。如果注释放置在A1单元格,那么图片将放在输出Excel的A1:D10区域。

  • src定义图片二进制在Context上的键。

  • imageType定义图片类型,可用值为:PNG、JPEG、EMF、WMF、PICT、DIB。

  • imageType属性的默认值为PNG。

  • Java代码应该包含这样的内容,以便将所需的图片放到Context中的image属性上:

    1
    2
    3
    InputStream imageInputStream = ImageDemo.class.getResourceAsStream("business.png");
    byte[] imageBytes = Util.toByteArray(imageInputStream);
    context.putVar("image", imageBytes);

jXLS Reader读取

  • jXLS提供jxls-reader模块,用于读取XLS文件并使用Excel数据填充Java Beans。
  • XML配置文件用于表示如何解析Excel文件,如何填充数据。

Maven依赖

1
2
3
4
5
<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls-reader</artifactId>
<version>2.0.3</version>
</dependency>

使用XML配置文件构建XLSReader

  • 为了使用jXLS-Reader解析Excel文件,使用读取的数据填充Java数据,首先,你需要构建一个XLSReader对象。
  • 最简单的方式使用特定XML配置文件构建XLSReader对象。

xml映射文件

  • xml文件的根元素为workbook,可以包含任意数量的子worksheet元素,worksheet标签应该包含name属性表示Excel Sheet的名称(我们的例子中为Sheet1)。

  • worksheet元素可以包含任意数量的section和loop子元素。

    • section元素表示一个简单的单元格块。块的第一和最后一行使用startRow和endRow属性指定。
    • 你必须为整个Excel Sheet指定section,因此,它完全被分割成多个section。这意味着,即使你不打算读取,例如第一行,也需要创建一个空section(section没有mapping,但指定startRow/endRow属性),以至于这些行在XML文件中体现。不需要的行将直接跳过,所有其他部分将按要求阅读。
  • XML单元格到Java Bean属性的映射使用mapping标签定义:

    1
    <mapping cell="B1">department.name</mapping>
  • 你也可以使用cell属性指定映射的单元格,使用标签体作为完整属性名。所谓完整属性名,类似于department.name或department.chief.payment这种Java对象属性导航。其它单元格选项使用单元格行和列数(基于0)。

    1
    2
    # 这是定义单元格E4与属性department.chief.bonus的映射。
    <mapping row="3" col="4">department.chief.bonus</mapping>
  • loop元素定义Excel行循环块。该元素应该包含startRow和endRow属性指定循环块的开始和结束行。使用循环快数据填充context中的items属性名的集合。

    • var属性指定在迭代内部section时如何引用该集合中的item。
    • varTyp属性定义集合item的完整Java类名。
    1
    <loop startRow="7" endRow="7" items="department.staff" var="employee" varType="org.jxls.reader.sample.Employee">
  • loop元素可以包含任意数量的内部section和loop元素,并且必须包含loopbreakcondition定义。该标签标示中断循环的条件。在我们的例子中,该值为“Employee Payment Totals:”。

    1
    2
    3
    4
    5
    6
    <loopbreakcondition>
    <!-- 循环到下一行为 “Employee Payment Totals:“的使用停止-->
    <rowcheck offset="0">
    <cellcheck offset="0">Employee Payment Totals:</cellcheck>
    </rowcheck>
    </loopbreakcondition>

    示例

    • 下面是使用ReaderBuilder类使用XML映射文件和departmentdata.xls构建XLSReader类读取XLS数据填充相应Java Bean的代码:

    • xml文件如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      <?xml version="1.0" encoding="ISO-8859-1"?>
      <workbook>
      <worksheet name="Sheet1">
      <section startRow="0" endRow="6">
      <mapping cell="B1">department.name</mapping>
      <mapping cell="A4">department.chief.name</mapping>
      <mapping cell="B4">department.chief.age</mapping>
      <mapping cell="D4">department.chief.payment</mapping>
      <mapping row="3" col="4">department.chief.bonus</mapping>
      </section>
      <loop startRow="7" endRow="7" items="department.staff" var="employee" varType="org.jxls.reader.sample.Employee">
      <section startRow="7" endRow="7">
      <mapping row="7" col="0">employee.name</mapping>
      <mapping row="7" col="1">employee.age</mapping>
      <mapping row="7" col="3">employee.payment</mapping>
      <mapping row="7" col="4">employee.bonus</mapping>
      </section>
      <loopbreakcondition>
      <rowcheck offset="0">
      <cellcheck offset="0">Employee Payment Totals:</cellcheck>
      </rowcheck>
      </loopbreakcondition>
      </loop>
      </worksheet>
      </workbook>
    • Java代码如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      InputStream inputXML = new BufferedInputStream(getClass().getResourceAsStream(xmlConfig));
      XLSReader mainReader = ReaderBuilder.buildFromXML( inputXML );
      InputStream inputXLS = new BufferedInputStream(getClass().getResourceAsStream(dataXLS));
      Department department = new Department();
      Department hrDepartment = new Department();
      List departments = new ArrayList();
      Map beans = new HashMap();
      beans.put("department", department);
      beans.put("hrDepartment", hrDepartment);
      beans.put("departments", departments);
      XLSReadStatus readStatus = mainReader.read( inputXLS, beans);

通过sheet索引映射

  • Jxls Reader支持通过sheet索引映射。

    • 使用worksheet标签的idx属性替代name属性指定sheet索引。sheet索引从0开始。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <workbook>
    <worksheet idx="0">
    <section startRow="0" endRow="6">
    <mapping cell="B1">department.name</mapping>
    <mapping cell="A4">department.chief.name</mapping>
    <mapping cell="B4">department.chief.age</mapping>
    <mapping cell="D4">department.chief.payment</mapping>
    <mapping row="3" col="4">department.chief.bonus</mapping>
    </section>
    <loop startRow="7" endRow="7" items="department.staff" var="employee" varType="org.jxls.reader.sample.Employee">
    <section startRow="7" endRow="7">
    <mapping row="7" col="0">employee.name</mapping>
    <mapping row="7" col="1">employee.age</mapping>
    <mapping row="7" col="3">employee.payment</mapping>
    <mapping row="7" col="4">employee.bonus</mapping>
    </section>
    <loopbreakcondition>
    <rowcheck offset="0">
    <cellcheck offset="0">Employee Payment Totals:</cellcheck>
    </rowcheck>
    </loopbreakcondition>
    </loop>
    </worksheet>
    </workbook>

错误处理

  • 默认,如果jXLS读取某些单元格值失败将会停止进一步处理抛出XLSDataReadException。

  • 你可以使用ReaderConfig类的setSkipErrors(true)方法覆盖该行为,允许跳过错误并继续处理。使用ReaderConfig.getInstance()方法获取ReaderConfig类的实例。

    1
    ReaderConfig.getInstance().setSkipErrors( true );
  • 所有的错误消息存储在XLSReadStatus对象由read方法返回。XLSDataReadException可以包含该对象引用。我们可以使用getReadMessages()方法从XLSReadStatus分析XLSReadMessage对象。

转换机制

  • Jxls Reader继承Apache Commons Beanutils转换工具类执行Excel单元格值到Bean属性的实际转换。更多细节见ConvertUtil类。

  • Jxls Reader使用org.apache.commons.beanutils.converters包为原始类型标准转换器。J

  • xls Reader为java.util.Date类型提供自定义DataConverter,使用Apache POI工具方法从Excel数据表现形式转换为Date类。

  • BeanUtils转换器转换错误时为原始类型返回一个默认值。在ReaderConfig类中注册这些类抛出一个ConversionException,覆盖这些行为。

  • 如果喜欢使用默认值,我们可以使用ReaderConfig类的setUseDefaultValuesForPrimitiveTypes(true)方法。例如:

    1
    ReaderConfig.getInstance().setUseDefaultValuesForPrimitiveTypes( true );
  • 为了自定义转换器从Excel单元格到自定义属性类型,我们应该事先Converter接口,使用ConvertUtils类注册。

导出添加自定义函数

  • 代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    public static void exportExcel(InputStream is, OutputStream os, Object model)
    throws IOException {
    JxlsHelper jxlsHelper = JxlsHelper.getInstance();
    Context context = initJxlsContext(model);

    // 必须要这个,否则表格函数统计会错乱
    jxlsHelper.setUseFastFormulaProcessor(false);

    Transformer transformer = jxlsHelper.createTransformer(is, os);
    initFunctions(transformer);
    jxlsHelper.processTemplate(context, transformer);

    }

    private static void initFunctions(Transformer transformer) {
    // 设置静默模式,不报警告
    // evaluator.getJexlEngine().setSilent(true);
    // 函数强制,自定义功能
    Map<String, Object> functionMap = new HashMap<String, Object>();
    // 添加自定义功能
    functionMap.put("dutils", new DateHelper()); // 添加自定义功能
    functionMap.put("cutils", new CommonHelper()); // 添加自定义功能

    // 获得配置
    JexlExpressionEvaluator evaluator = new JexlExpressionEvaluator();
    if (transformer == null) {
    evaluator =
    (JexlExpressionEvaluator) transformer.getTransformationConfig().getExpressionEvaluator();
    }
    evaluator.setJexlEngine(new JexlBuilder().namespaces(functionMap).create());
    }


    public class CommonHelper {
    /**
    *
    * if判断,如果b为true,则返回o1,否则返回o2.
    *
    * @param b
    * @param o1
    * @param o2
    * @return o1 or o2
    */
    public Object ifelse(boolean b, Object o1, Object o2) {
    return b ? o1 : o2;
    }
    }

    public class DateHelper {
    /**
    *
    * 日期格式化.
    *
    * @param date
    * @param fmt
    * @return 格式化后的字符串
    */
    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 "";
    }
    }
  • 使用

    1
    2
    ${dutils:dateFmt(user.createTime,"yyyy-MM-dd HH:mm:ss")}
    ${cutils:dateFmt(user.sex==0,"女","男")}

参考

https://gitee.com/lnkToKing/jxlss

https://my.oschina.net/leeck?tab=newest&catalogId=5926605