乐趣区

关于java:easypoi-模板导出时的公式及foreach合并单元格问题

插播一条对于 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

退出移动版