LXLS 2.4.0 简介
jxls2.4 是java对excel操作的工具包,可以很方便的实用模板输出excel,根据xml配置读取excel内容。
对比jxls1 现在的版本使用批注的模式运行指令,可以支持excel中使用公式,07的excel。官网
使用:
- maven中添加依赖
<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls</artifactId>
<version>2.4.5</version>
</dependency>
<dependency>
<!-- 可以使用poi的实现也可以用jexcelapi的 -->
<groupId>org.jxls</groupId>
<artifactId>jxls-poi</artifactId>
<version>1.0.15</version>
</dependency>
<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls-jexcel</artifactId>
<version>1.0.7</version>
</dependency>
导出EXCEL
基础介绍
- 使用批注的模式编写指令
- 需在excel的第一个单元格指定整个模板的范围
- 单元格中取值与el表达式类似使用${}来访问传入的变量
- 指令中需要访问对象则不需要使用${} 直接访问即可,需用""包裹
- 官方demo
常用指令说明:
jx:each 循环
示例:jx:each((items="employees" var="employee" lastCell="D4" area="[A4:D4]" select="employee.payment > 2000"))
参数说明:
参数名称 | 示例 | 必填 | 说明 |
---|---|---|---|
items | items="employees" | 必填 | 循环的集合对象 |
var | var="employee" | 必填 | 循环中的变量名,指定之后区域内可以使用该名称访问属性 |
lastCell | lastCell="D4" | 必填 | 指令对应的结束位置 |
direction | direction ="RIGHT" | 输出的方向向下(DOWN)或向右(RIGHT),默认为DOWN | |
area | areas=["A8:F8","A13:F13"] | 循环的区域,一次循环多行则需要填写,可以指定多个区域使用逗号分隔 | |
select | select="employee.payment>2000" | 过滤条件,不需要通过${}来取值 | |
groupBy | groupBy ="name" | jx:each(items="employees" var="nameGroup" groupBy="name" groupOrder="asc" lastCell="D7")依据employee中name进行分组,分组后的集合可通过nameGroup.items来获取,官网示例中后面还有一个each指定的items就是nameGroup.items | |
groupOrder | groupOrder ="asc" | 指定分组排序(‘desc’ or ‘asc’) | |
multisheet | multisheet ="sheets" | 循环的sheet名称集合,指定后会产生多个sheet,指定后each的维度会变为sheet,不需要指定area | |
shiftMode | shiftMode ="adjacent" | adjacent指定后通过添加行的方式向指定方向输出,inner:则为通过添加单元格的方式向指定方向输出,默认为inner | |
cellRefGenerator | ? | ? |
不指定shiftMode ="adjacent"的效果
jx:if 条件判断
示例:jx:if(condition="department.chief.name != 'Betsy' " lastCell="F4" areas=["A3:F4"])
参数名称 | 示例 | 必填 | 说明 |
---|---|---|---|
condition | condition="department.chief.name != 'Betsy' " | 必填 | 判断条件,字符串不需要通过${}来取值,直接访问传递对象的内容 |
ifArea | ifArea =["A3:F4"] | if指令影响的范围 condition结果为true则显示指定范围 | |
elseArea | elseArea=["A3:F4"] | if指令影响的范围 condition结果为false则显示指定范围 |
jx:updateCell 动态输出公式
说明:主要用于动态修改模板中被循环包裹的的公式,demo中的相关例子在org.jxls.demo.SxssfDemo中
示例:jx:updateCell(lastCell="E4" updater="totalCellUpdater")
参数名称 | 示例 | 必填 | 说明 |
---|---|---|---|
updater | updater="totalCellUpdater" | 必填 | 指定具体的操作类需实现CellDataUpdater接口 |
java代码示例:
static class TotalCellUpdater implements CellDataUpdater{
/**
* cellData 批注对应的单元格
* targetCell 输出的单元格
* context 模板中的上下文通过getVar(变量名)来获取传入的对象
*/
@Override
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);
}
}
}
jx:grid 输出一个表格
说明:一次性输出一个表格包含表头表体,demo中的相关例子在org.jxls.demo.GridCommandDemo中
示例:jx:grid(lastCell="A4" headers="headers" data="data" areas=[A3:A3, A4:A4] formatCells="BigDecimal:C1,Date:D1")
参数名称 | 示例 | 必填 | 说明 |
---|---|---|---|
headers | headers="headers" | 必填 | 循环的表头内容为List ,表头和表体没有必然的关系,可以多一列也可以少一列 |
data | data="data" | 必填 | 循环的表体内容为List/List,当为javabean时java代码中需指定读取的属性名称 |
formatCells | formatCells="BigDecimal:C1,Date:D1" | 指定单元格格式化方式 |
java代码示例:
try(InputStream is = GridCommandDemo.class.getResourceAsStream("grid_template.xls")) {
try(OutputStream os = new FileOutputStream("grid_output2.xls")) {
Context context = new Context();
context.putVar("headers", Arrays.asList("Name", "Birthday", "Payment"));
context.putVar("data", employees);
//当循环的表体为javabean时指定读取的属性,Sheet2!A1表示输出开始的位置
JxlsHelper.getInstance().processGridTemplateAtCell(is, os, context, "name,birthDate,payment,bonus", "Sheet2!A1");
}
}
jx:image 输出图片
说明:输出一张图片,demo中的相关例子在org.jxls.demo.ImageDemo中
示例:jx:image(lastCell="D10" src="image" imageType="PNG")
参数名称 | 示例 | 必填 | 说明 |
---|---|---|---|
src | src="image" | 必填 | 输出的图片数据源byte[] |
imageType | imageType="PNG" | 输出的图片格式可不填 |
java代码示例:
public static void execute2() throws IOException {
try(InputStream is = ImageDemo.class.getResourceAsStream(template2)) {
try (OutputStream os = new FileOutputStream(output2)) {
Context context = new Context();
InputStream imageInputStream = ImageDemo.class.getResourceAsStream("business.png");
byte[] imageBytes = Util.toByteArray(imageInputStream);
Department department = new Department("Test Department");
department.setImage(imageBytes);
context.putVar("dep", department);
JxlsHelper.getInstance().processTemplate(is, os, context);
}
}
}
EXCEL中使用公式
使用 来 使 用 中 的 公 式 例 如 [ ] 来 使 用 e x c e l 中 的 公 式 例 如 " [B4(1+${bonus})]"输出后为"=B6(1+0.1)",可以使用excel函数如SUM如选择某一列只需指定变量对应单元格,输出会自动替换为该列,如下图
如公式中不需要访问传入属性直接写=SUM(B4)编译后也会自动替换为=SUM(B4:B8)
支持java手动增加公式详细例子请看demo中org.jxls.demo.FormulaExportDemo
自定义
函数和指令都可以进行扩展,需在导出前进行初始化,原文顺便吐槽一下百度用标题查居然第一个出来的是转发的第三个才是原文。
具体的内容可以参考大神写的这个项目jxlss
函数
函数:在单元格中使用处理具体数据
JxlsHelper jxlsHelper = JxlsHelper.getInstance();
Transformer transformer = jxlsHelper.createTransformer(is, os);
JexlExpressionEvaluator evaluator = (JexlExpressionEvaluator) transformer.getTransformationConfig().getExpressionEvaluator();
Map<String, Object> functions = new HashMap<>(1);
//添加自定义功能
functions.put("util", new JxlsUtil());//此处util为函数前缀,调用时在单元格中使用${util:函数名(参数)}来使用其中参数为当前上下文内容
evaluator.getJexlEngine().setFunctions(functions);
jxlsHelper.processTemplate(context, transformer);
is.close();
//JxlsUtil 格式化工具类
public class JxlsUtil {
/**
* 日期格式化
*
* @param date
* @param fmt
* @return
*/
public String dateFmt(Date date, String fmt) {
if (date == null) {
return null;
}
try {
SimpleDateFormat dateFmt = new SimpleDateFormat(fmt);
return dateFmt.format(date);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 返回第一个不为空的对象
*
* @param objs
* @return
*/
public Object defaultIfNull(Object... objs) {
for (Object o : objs) {
if (o != null) {
return o;
}
}
return null;
}
/**
* if判断
*
* @param b
* @param o1
* @param o2
* @return
*/
public Object ifelse(boolean b, Object o1, Object o2) {
return b ? o1 : o2;
}
}
指令
指令:在批注中使用,可扩展原有jxls中的功能例如循环增加下标.
继承AbstractCommand
//扩展指令
public class ExEachCommand extends AbstractCommand {
//处理内容
public Size applyAt(CellRef cellRef, Context context) {}
....
}
//在具体导出的类中增加静态代码块,由于只需要执行一次所以用静态代码块比较合适
static {
//添加自定义指令(可覆盖jxls原指令)
XlsCommentAreaBuilder.addCommandMapping("each",ExEachCommand.class);
}
读取Excel
通过一个xml来对应excel中内容,读取时传入相应xml和excel来转换为javabean,
转换代码:
try (InputStream xmlInputStream = EquipmentController.class.getResourceAsStream("/excelTemp/equipment_impot.xml")) {
File tempfile = fileService.getFile(data, fileType, file);
XLSReader reader = ReaderBuilder.buildFromXML(xmlInputStream);
try (InputStream xlsInputStream = new FileInputStream(tempfile)) {
List<Equipment> equipments = new ArrayList<>();
Map<String, Object> beans = new HashMap<>(15);
Map<String, String> title = new HashMap<>(3);
beans.put("equipments", equipments);
beans.put("title", title);
//碰到数据类型转换异常时跳过异常,例如数字类型但是单元格中为空,默认是会抛出异常的.设置之后就会设置为null
ReaderConfig.getInstance().setSkipErrors(true);
//从excel中读取对象,名称需和xml中对应
reader.read(xlsInputStream, beans);
if (checkTitle(title)) {
message = equipmentService.importEquipment(equipments, firmInfo);
} else {
throw new BusinessAjaxException(TEMPLET_ERROR);
}
if (StringUtils.isBlank(message)) {
message = SUCCESS_CONTENT;
} else {
data.put("status", HttpStatus.UNPROCESSABLE_ENTITY.value());
}
data.put("message", message);
}
} catch (BusinessAjaxException e) {
throw e;
} catch (XLSDataReadException e) {
throw new BusinessAjaxException(String.format(JXLS_ERROR, e.getCellName()), e);
} catch (Exception e) {
throw new BusinessAjaxException(IMPORT_ERROR, e);
}
对应xml文件:
<?xml version="1.0" encoding="ISO-8859-1"?>
<workbook>
<worksheet name="Sheet1"><!--工作表名称,名称对应不上会读取不到数据,一次要读取多个sheet则重复此标签即可-->
<section startRow="0" endRow="0"/>
<section startRow="1" endRow="1">
<mapping row="1" col="1">title.eqCode</mapping>
<mapping row="1" col="7">title.origin</mapping>
<mapping row="1" col="13">title.remark</mapping>
</section>
<loop startRow="2" endRow="2" items="equipments" var="equipment" varType="com.bmw.frame.entity.firm.Equipment">
<section startRow="2" endRow="2">
<mapping row="2" col="1">equipment.eqCode</mapping>
<mapping row="2" col="2">equipment.eqName</mapping>
<mapping row="2" col="3">equipment.brand</mapping>
<mapping row="2" col="4">equipment.importBuyTime</mapping>
<mapping row="2" col="5">equipment.importSpecification</mapping>
<mapping row="2" col="6">equipment.price</mapping>
<mapping row="2" col="7">equipment.origin</mapping>
<mapping row="2" col="8">equipment.quantity</mapping>
<mapping row="2" col="9">equipment.process</mapping>
<mapping row="2" col="10">equipment.numberUsers</mapping>
<mapping row="2" col="11">equipment.equipmentTrip</mapping>
<mapping row="2" col="12">equipment.eqPrecision</mapping>
<mapping row="2" col="13">equipment.remark</mapping>
</section>
<loopbreakcondition><!--退出读取的标识,可以指定结束读取的关键字,这里用的是空白-->
<rowcheck offset="0">
<cellcheck offset="0"></cellcheck>
</rowcheck>
</loopbreakcondition>
</loop>
</worksheet>
</workbook>
注意事项:
- 读取的数据类型为日期时数据转换很麻烦,所以我在这里是增加了一个字符串属性用于存储读取的内容,然后在java中进行格式转换,如有更好的解决方案望告知
- 如属性中包含非字符串属性,excel中又可以不填需增加设置:ReaderConfig.getInstance().setSkipErrors(true)避免中止导入
- 最好手动捕获XLSDataReadException 异常,e.getCellName()可以拿到具体导入报错的单元格,能精确定位
参考资料:
http://jxls.sourceforge.net/
https://blog.csdn.net/lnktoking/article/details/52932679
https://gitee.com/lnkToKing/jxlss
https://www.cnblogs.com/foxlee1024/p/7616987.html
https://blog.csdn.net/lnkToKing/article/details/52932679