插播一条对于easypoi的,因为这两天正好在用。

java我的项目中可能会碰到生成凭证、对账单之类的需要,用easypoi的模板导出性能应该是一个不错的抉择,因为easypoi简略易用轻量级,学习成本低、容易上手。

然而在应用过程中碰到如下问题(easypoi4.4.0):

  1. 应用foreach后,模板中设置的公式在生成的excel中不见了
  2. 模板中的合并单元格问题
  3. 不能反对excel中同一行搁置两个后果集

foreach公式问题

先形容一下问题。

设置一个模板,非常简单,应用$fe设置foreach输入后果集,而后对某一输入做共计:

保留模板为template1.xlsx,而后写一个简略的测试程序。为了不便调试并解决问题,测试程序的我的项目不是在引入easypoi包的独立我的项目中做的,而是间接从官网下载easypoi 4.4.0的源码,在源码我的项目下新建一个测试class做的。

测试代码比较简单,所以就不贴出残缺的代码了。数据筹备好之后绑定模板、调用exportExcel生成excel文件、保留生成的excel文件。

...TemplateExportParams templateExportParams=new TemplateExportParams("F:/template1.xlsx");        Map<String,Object> map=new HashMap<String,Object>();        map.put("userList",userList);        map.put("userList2",userList2);        map.put("userList3",userList3);        Workbook wk = ExcelExportUtil.exportExcel(templateExportParams,map);        FileOutputStream fo=new FileOutputStream("f:/test1.xlsx");        //wk.setForceFormulaRecalculation(true);        wk.write(fo);        wk.close();

而后关上生成的excel文件后,发现模板中设置的公式不见了:

重复测试后发现是模板的excel版本的问题,如果用excel的晚期版本xls就没有问题,然而如果模板是xlsx,就有问题。

然而发现问题的起因曾经是解决问题之后了。所以如果你也碰到了相似问题,能够抉择应用xls格局的模板,当然也能够尝试找找起因,看看是否能彻底解决问题。

公式失落的起因

所以咱们就肯定要发挥钻牛角尖的程序员精力探索一番公式隐没的起因。

找到了ExcelExportOfTemplateUtil的addListDataToExcel办法,是专门解决foreach的(foreach指的就是模板中的fe、$fe等占位符,easypoi须要将此类占位符对应的数据集的每一行数据都循环写入到excel文件中)。

依据输出的map数据集实现对模板excel文件的shift(也就是在指标excel文件中插入行,以便对数据集中的数据进行循环写入)后,调用了PoiExcelTempUtil.reset对插入行之后的、程序向后shift的原模板内容进行了resest。

      //修复不管前面有没有数据,都应该执行的是插入操作        if (isShift && datas.size() > 1 && datas.size() * rowspan > 1 && cell.getRowIndex() + rowspan <= cell.getRow().getSheet().getLastRowNum()) {            int lastRowNum = cell.getRow().getSheet().getLastRowNum();            int shiftRows  = lastRowNum - cell.getRowIndex() - rowspan;            cell.getRow().getSheet().shiftRows(cell.getRowIndex() + rowspan, lastRowNum, (datas.size() - 1) * rowspan, true, true);            mergedRegionHelper.shiftRows(cell.getSheet(), cell.getRowIndex() + rowspan, (datas.size() - 1) * rowspan, shiftRows);            templateSumHandler.shiftRows(cell.getRowIndex() + rowspan, (datas.size() - 1) * rowspan);            PoiExcelTempUtil.reset(cell.getSheet(), cell.getRowIndex() + rowspan + (datas.size() - 1) * rowspan, cell.getRow().getSheet().getLastRowNum());        }

不太理解reset的用处,试着正文掉这行代码,貌似对我的这个小测试也没有什么影响,而且公式隐没不见的问题的确也解决了。

然而没搞清楚他具体的作用也不敢贸然正文不调用这个办法。所以,就简略看了一下这个办法。

不晓得什么起因,如果是FORMULA的话就没做什么解决。所以就加了解决,为了展现显著一点,上图中正文掉的两句话就是新加的代码。

而后不论是对于xls的模板,还是xlsx的模板,都没有问题了。

模板中合并单元格的问题

这个问题具备肯定的必然性,不太好重现。

咱们把模板批改一下,如下图,只是减少了几个合并单元格:

将数据调整为10行,运行失去的输入excel为:

将数据调整为5行,失去:

有些状况下,模板略微简单一点、输入数据的行数碰巧的话,会抛异样:模板谬误。

所以咱们有必要剖析一下起因。

同样,还是看ExcelExportOfTemplateUtil的addListDataToExcel办法。

shift数据后,调用了mergedRegionHelper的shiftRow办法:

mergedRegionHelper

到这里咱们就须要简略剖析一下mergedRegionHelper类。

有一句正文:合并单元格帮忙类。

就是用来解决合并单元格的,针对咱们的案例(依据模板导出),剖析了一下发现mergedRegionHelper其实次要就是用来解决模板插入数据之后的合并单元格的。

他的次要思维如下:

  1. ExcelExportOfTemplateUtil#parseTemplate解析模板的时候初始化mergedRegionHelper,mergedRegionHelper会读取模板以后sheet的所有合并单元格,缓存到属性mergedCache中。
  2. mergedCache的key值为sheet中有合并单元格的:row_col,value为合并行数、列数组成的数组。
  3. 当模板中针对foreach插入行(shift)之后,调用mergedRegionHelper的shiftRow办法对缓存在mergedCache中的数据做解决。
  4. 而后将数据集中的数据填写到新插入的行中,写入每一个cell之前首先判断单元格如果是合并单元格的话,则对以后正在解决的cell做合并解决。
  5. 判断是否是合并单元格的根据:原来模板中对应的cell是否为合并单元格,就是通过mergedRegionHelper的isMergedRegion办法、其实也就是查看mergedCache中是是否存在以后cell的缓存数据进行判断的。

不晓得上述是否形容分明了,以咱们下面这个测试案例再简略解释一下。

比方咱们模板中第4行、第3列第4列为合并单元格,mergedCache中记录大略是[4_3,[1,2]],意思是第4行第3列是合并单元格,合并了1行、2列(为了容易了解就不思考起始列从0开始这种状况了)。

假如数据集是5条数据,那么须要在Excel中第2行上面插入4行(有1条数据写入到了原来模板的第2行了,所以不须要插入行)。

插入之后原来的第4行就变成第8行了,所以mergedCache缓存的数据就不对了。

mergedRegionHelper的shiftRow办法就是心愿在插入行之后把这个谬误给调整过去。

然而上图的这个调整算法有问题,比方第4行变成第8行的时候,假如模板的第8行原本就有合并单元格,就会存在原来的第8行的数据可能会被笼罩的危险,代码尽管对这种状况做了解决,然而的确没解决好,仍然会有问题。

构想的解决方案之一是:如果缓存mergedCache中的keys能依照行数排倒序的话,从Excel表最上面的行开始由下至上逐渐解决mergedCache中的数据的话,问题应该就能彻底解决。

然而其实这种缓存数据在缓存之后的应用过程中最好就别动了,能够想想是否还有其余方法解决shift之后带来的变动。

所以想到的第二个解决方案是:shift数据的时候不对mergedCache数据做重新处理,只须要让mergedRegionHelper记录下来该sheet以后共插入(shift)多少行,在须要应用mergedCache缓存数据的中央思考这个插入行数就能够了。

采纳第二个解决方案须要特地小心革新所造成的影响,须要认真考查不止是mergedRegionHelper、还有mergedRegionHelper的调用方。

mergedRegionHelper相对来说比较简单,然而其调用方可能绝对简单一点,次要包含:

  1. ExcelToHtmlService
  2. ExcelExportOfTemplateUtil

我的我的项目中临时只用到了ExcelExportOfTemplateUtil,所以咱们临时只剖析ExcelExportOfTemplateUtil。

合并单元格问题解决#mergedRegionHelper的革新

首先须要减少成员变量,初始化为0:
private int rowsShifted=0;

办法shiftrow,批改为简略粗犷的只累加插入行数:

办法getRowAndColSpan:

办法isMergedRegion:

办法isNeedCreate:

批改结束。

合并单元格问题解决#ExcelExportOfTemplateUtil革新

其实按理说咱们对mergedRegionHelper的革新形式不应该造成对调用方的影响,然而测试发现的确还是有影响的。

造成影响的次要在setForeachRowCellValue办法:

//合并对应单元格                boolean isNeedMerge = (params.getRowspan() != 1 || params.getColspan() != 1)                        && !mergedRegionHelper.isMergedRegion(row.getRowNum() + 1, ci);

这里应该是判断新插入的行的每一个cell是否是合并单元格,如果是的话就进行合并。判断的根据是模板中设置了foreach的行对应的cell(曾经解决在params中了)是否是合并单元格。然而源码中除了判断params之外还必须是 !mergedRegionHelper.isMergedRegion(参数是以后cell),直观了解应该是:模板中对应的cell是合并单元格,同时以后cell不在mergedRegionHelper的缓存中。

不太了解加!mergedRegionHelper.isMergedRegion的起因,揣测可能的逻辑是要判断必须是新插入的行、而不是模板中原来的行,才执行单元格的合并。

咱们对mergedRegionHelper做了革新之后,这个中央的判断就必须要调整一下了:

图中红色框柱的局部正文掉就能够了。

从新设置了各种状况下的(当然也很无限了......)几个带有合并单元格的模板,包含持续测试批改之前有问题、报错的我的项目中的模板,都能够失常工作了。

当然,easypoi是要应答各种不同场景的,上述计划临时可能只能应答我我的项目中碰到的、针对我的模板呈现的问题,状况简单一点的话上述计划可能依然有问题。

反对excel中同一行搁置两个后果集

这个问题我不晓得其他人是否遇到过,比方我想用上面的模板导出:

在第一行的左侧3列为后果集userList后果集的数据,右侧3列为后果集userList2的数据。

比方userList设置2条数据,userList2设置3条数据。

启动测试后零碎报错:

想要实现这个指标还是有点难度的,同样须要批改ExcelExportOfTemplateUtil的addListDataToExcel办法,绝对比较复杂一点:

不过批改之后,还是可能满足要求的:

这种状况下如果模板比较复杂的话可能还是会有问题,须要具体问题具体分析、解决。

以上。

上一篇 Quartz - Misfire