我们正在尝试在java maven项目中使用poi评估xlsx公式,但是如果公式具有范围或数组,则输出值是重复的。
以下是预期与 POI 输出:
预期产量
poi输出
J 和 K 列没有任何公式单元格。 L 和 M 列具有依赖于 J 和 K 数据的公式单元格。
这是在 J 和 K 列的 7 行中定义的公式:
Column J :
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(J6)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J6))))
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(J7)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J7))))
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(J8)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J8))))
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(J9)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J9))))
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(J10)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J10))))
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(J11)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J11))))
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(J12)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J12))))
Column K :
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(K6)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K6))))
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(K7)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K7))))
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(K8)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K8))))
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(K9)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K9))))
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(K10)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K10))))
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(K11)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K11))))
=IF(COUNTIF($J$6:$J$62,"?*")<ROW(K12)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K12))))
如果我们比较上面的图像(预期与 poi 输出),那么我们会发现 poi 给出了错误的结果并重复 J 和 K 中的项目,而 MS excel 能够正确计算。
尝试过公式计算器
evaluateAll
和 evaluateFormulaCell
。不会产生正确的结果。evaluator.clearAllCachedResultValues();
和 evaluator.notifySetFormula(cell);
看来 poi 不支持这些公式。
使用XSSFWorkBook。
FileInputStream fis = new FileInputStream(inputFile);
Workbook workbook = new XSSFWorkbook(fis);
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
// some code in between, loops etc
if (cell.getCellType() == CellType.FORMULA) {
evaluator.evaluateFormulaCell(cell);
}
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.4</version>
</dependency>
这里的主要问题是 Apache POI 没有提供
ArrayFunction
那样的所有功能。在这种特殊情况下,Excel 函数 ROW 由 Java
函数 org.apache.poi.ss.formula.functions.RowFunc 表示,该函数尚未准备好在数组上下文中运行。
可以通过更改该
Java
函数的代码来更改。该代码需要考虑“如果引用是单元格范围,并且如果 ROW 作为垂直数组输入,则 ROW 将引用的行号作为垂直数组返回。”。我的函数 RowFuncArrayReady
就是这样做的。
import org.apache.poi.ss.formula.functions.*;
import org.apache.poi.ss.formula.eval.AreaEval;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.RefEval;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.CacheAreaEval;
/**
* Implementation for the Excel function ROW ready for usage as ArrayFunction
*/
public final class RowFuncArrayReady implements Function, ArrayFunction {
@Override
public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
if (args.length > 1) {
return ErrorEval.VALUE_INVALID;
}
if (args.length == 0) {
return new NumberEval(srcRowIndex + 1.);
} else {
return evaluate(srcRowIndex, srcColumnIndex, args[0]);
}
}
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0) {
int rnum;
if (arg0 instanceof AreaEval) {
rnum = ((AreaEval)arg0).getFirstRow();
} else if (arg0 instanceof RefEval) {
rnum = ((RefEval)arg0).getRow();
} else {
// anything else is not valid argument
return ErrorEval.VALUE_INVALID;
}
return new NumberEval(rnum + 1.);
}
@Override
public ValueEval evaluateArray(ValueEval[] args, int srcRowIndex, int srcColumnIndex) {
if (args.length > 1) {
return ErrorEval.VALUE_INVALID;
}
if (args.length == 0) {
return new NumberEval(srcRowIndex + 1.);
} else if (args[0] instanceof AreaEval) {
return evaluateAreaEval((AreaEval)args[0], srcRowIndex, srcColumnIndex);
} else if (args[0] instanceof RefEval) {
return evaluate(srcRowIndex, srcColumnIndex, (RefEval)args[0]);
} else {
// anything else is not valid argument
return ErrorEval.VALUE_INVALID;
}
}
private ValueEval evaluateAreaEval(AreaEval ae, int srcRowIndex, int srcColumnIndex) {
int w1, w2, h1, h2;
int a1FirstCol = 0, a1FirstRow = 0;
w1 = ae.getWidth();
h1 = ae.getHeight();
a1FirstCol = ae.getFirstColumn();
a1FirstRow = ae.getFirstRow();
w2 = 1;
h2 = 1;
int width = Math.max(w1, w2);
int height = Math.max(h1, h2);
ValueEval[] vals = new ValueEval[height * width];
int idx = 0;
for(int i = 0; i < height; i++){
for(int j = 0; j < width; j++){
vals[idx++] = evaluate(srcRowIndex, srcColumnIndex, ae.offset(i, i, j, j));
}
}
if (vals.length == 1) {
return vals[0];
}
return new CacheAreaEval(srcRowIndex, srcColumnIndex, srcRowIndex + height - 1, srcColumnIndex + width - 1, vals);
}
}
要使该新函数成为 Excel ROW 函数的默认表示形式,它需要位于索引 8 处的
Function[] functions
org.apache.poi.ss.formula.eval.FunctionEval 数组中。在以下代码中,方法 prepareFunctionEval
当被称为 prepareFunctionEval(8, new RowFuncArrayReady());
时就会这样做。
import org.apache.poi.ss.usermodel.*;
import java.io.FileInputStream;
class ExcelEvaluateROWFormulaAsArray {
static void prepareFunctionEval(int pos, org.apache.poi.ss.formula.functions.Function function) throws Exception {
java.lang.reflect.Field _functions = Class.forName("org.apache.poi.ss.formula.eval.FunctionEval").getDeclaredField("functions");
_functions.setAccessible(true);
org.apache.poi.ss.formula.functions.Function[] functions = (org.apache.poi.ss.formula.functions.Function[])_functions.get(null);
functions[pos] = function;
}
public static void main(String[] args) throws Exception {
prepareFunctionEval(8, new RowFuncArrayReady());
Workbook workbook = WorkbookFactory.create(new FileInputStream("./ExcelUsingRowFormulaInArrayContext.xlsx"));
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
evaluator.setDebugEvaluationOutputForNextEval(true);
Sheet sheet = workbook.getSheetAt(0);
for (Row row : sheet) {
for (Cell cell : row) {
if (CellType.FORMULA == cell.getCellType()) {
System.out.println(cell.getCellFormula());
CellValue cellValue = evaluator.evaluate(cell);
System.out.println(cellValue);
}
}
}
workbook.close();
}
}
我的
ExcelUsingRowFormulaInArrayContext.xlsx
看起来像这样:
注意,公式
{=IF(COUNTIF($J$6:$J$62,"?*")<ROW(J6)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J6))))}
是使用CtrlShiftEnter输入的数组公式。 Apache POI 无法评估 Excel 365 的新动态数组公式和溢出数组行为。
我的
ExcelEvaluateROWFormulaAsArray
然后打印:
IF(COUNTIF($J$6:$J$62,"?*")<ROW(J6)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J6))))
org.apache.poi.ss.usermodel.CellValue ["Text 1"]
IF(COUNTIF($J$6:$J$62,"?*")<ROW(K6)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K6))))
org.apache.poi.ss.usermodel.CellValue [1.0]
IF(COUNTIF($J$6:$J$62,"?*")<ROW(J7)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J7))))
org.apache.poi.ss.usermodel.CellValue ["Text 2"]
IF(COUNTIF($J$6:$J$62,"?*")<ROW(K7)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K7))))
org.apache.poi.ss.usermodel.CellValue [2.0]
IF(COUNTIF($J$6:$J$62,"?*")<ROW(J8)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J8))))
org.apache.poi.ss.usermodel.CellValue ["Text 3"]
IF(COUNTIF($J$6:$J$62,"?*")<ROW(K8)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K8))))
org.apache.poi.ss.usermodel.CellValue [3.0]
IF(COUNTIF($J$6:$J$62,"?*")<ROW(J9)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J9))))
org.apache.poi.ss.usermodel.CellValue ["Text 4"]
IF(COUNTIF($J$6:$J$62,"?*")<ROW(K9)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K9))))
org.apache.poi.ss.usermodel.CellValue [4.0]
IF(COUNTIF($J$6:$J$62,"?*")<ROW(J10)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J10))))
org.apache.poi.ss.usermodel.CellValue ["Text 5"]
IF(COUNTIF($J$6:$J$62,"?*")<ROW(K10)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K10))))
org.apache.poi.ss.usermodel.CellValue [5.0]
IF(COUNTIF($J$6:$J$62,"?*")<ROW(J11)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J11))))
org.apache.poi.ss.usermodel.CellValue [""]
IF(COUNTIF($J$6:$J$62,"?*")<ROW(K11)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K11))))
org.apache.poi.ss.usermodel.CellValue [""]
IF(COUNTIF($J$6:$J$62,"?*")<ROW(J12)-5,"",INDEX(J:J,SMALL(IF(J$6:J$62<>"",ROW(J$6:J$62)),ROWS(J$6:J12))))
org.apache.poi.ss.usermodel.CellValue [""]
IF(COUNTIF($J$6:$J$62,"?*")<ROW(K12)-5,"",INDEX(K:K,SMALL(IF(K$6:K$62<>"",ROW(K$6:K$62)),ROWS(K$6:K12))))
org.apache.poi.ss.usermodel.CellValue [""]
这与 Excel 的计算结果相同。