在 Excel (XLSX) 中聚焦并确认公式之前,不会对公式单元格进行评估

Formula cell does not get evaluated until formula is focussed and confirmed in Excel (XLSX)

所以我使用 Apache POI(最新稳定版本 5.0.0 中的 poi-ooxml)并打开一个 existing Excel (XSLX) 文件进行编辑 (它基本上是一个用附加数据填充的模板文件)。我添加了多个新数据行并再次导出 Excel。一切正常,只要我只添加常规内容单元格。

现在,我有一列要在其中添加公式单元格,我使用以下内容(针对此示例进行了简化,您可以放心,通常它 compiles/runs 并生成填充的 Excel 文件末尾)这样做的代码:

File excelFileToRead = new File(<some filename here>);
InputStream inp = new FileInputStream(excelFileToRead);
Workbook wb = WorkbookFactory.create(inp);
Sheet sheet = wb.getSheetAt(0);
Row dateRangeRow = sheet.getRow(0);

// fill first cell with some date
Cell cell = row.getCell(0);
if(cell == null) row.createCell(0)
Date someDate = new Date();
cell.setCellValue(someDate);

// add formula to second cell to display the week number
Cell formCell = row.getCell(1);
if(formCell == null) row.createCell(1);
cell.setCellFormula("WEEKNUM(A1)");

// evaluate all formula fields before saving
XSSFFormulaEvaluator.evaluateAllFormulaCells(wb);

//some routine to save as a file follows here, not exactly relevant here

总的来说,这很好用。创建第一个单元格并填充今天的日期,第二个单元格也创建为公式单元格。

现在问题来了:当我打开 Excel 传播sheet 时,我可以看到数据,但在公式单元格中我只看到“#WERT”(使用德语 Excel,我假设在英文版中它会显示类似“#VALUE”)的内容。

当我简单地点击 Excel 中的公式编辑器并再次移开焦点时,它会正确计算公式并且单元格显示正确的周数。

我之前在阅读 Excel 中的预先存在的公式时遇到了一些问题,当我将数据添加到 sheet 时它们没有更新,但这可以通过调用修复至 XSSFFormulaEvaluator.evaluateAllFormulaCells(wb); 出于某种原因,它不会影响我自定义创建的公式单元格。

我也试过在创建后单独评估新创建的公式单元格:

FormulaEvaluator formulaEvaluator = wb.getCreationHelper().createFormulaEvaluator();
formulaEvaluator.evaluateFormulaCell(formCell);

这里也没有产生任何变化。

知道我的代码或方法有什么问题吗?

顺便说一句,我使用 Excel 版本 16.53(Excel 代表 Mac),但我真的希望它与确切的 Excel 版本无关:- )

注意: 我发现了一个旧线程(在 POI 5.0.0 发布之前)似乎讨论了同样的问题,但是使用较旧的 POI 版本并且如上所述,我遵循了调用 evaluateAllFormulaCells(...)在保存之前甚至在每个公式单元格创建之后调用 evaluateFormulaCell(cell):Apache POI formulas not evaluating

这是由于评估 WEEKNUM 函数时 apache poi 中的错误造成的。

如果 [return_type] 被省略,那么 ist 的计算结果总是 #VALUE 错误。但是即使你设置 [return_type] 然后它的评估并不总是正确的。

如果你这样做,你可以看到这个:

...
FormulaEvaluator formulaEvaluator = wb.getCreationHelper().createFormulaEvaluator();
CellValue cellValue = formulaEvaluator.evaluate(formCell);
System.out.println(cellValue);
...

如果 A1 包含日期 9/27/2021 并且 B1 包含公式 =WEEKNUM(A1) 那么 apache poi FormulaEvaluator 计算结果为 #VALUE.如果 B1 包含公式 =WEEKNUM(A1,1),则 apache poi FormulaEvaluator 将其计算为 39,但 Excel 将其计算为 40

要解决此错误,可以强制 Excel 在打开文件时计算所有公式。这可以使用 wb.setForceFormulaRecalculation(true); 来完成。然后 Excel 计算公式,所以结果是正确的。

重现问题的完整示例:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.formula.BaseFormulaEvaluator;

import java.util.GregorianCalendar;

class CreateExcelFormulaWEEKNUM {

 public static void main(String[] args) throws Exception {

  try (
       //Workbook wb  = WorkbookFactory.create(new FileInputStream("./ExcelIn.xls")); FileOutputStream fileout = new FileOutputStream("./ExcelOut.xls");
       Workbook wb  = WorkbookFactory.create(new FileInputStream("./ExcelIn.xlsx")); FileOutputStream fileout = new FileOutputStream("./ExcelOut.xlsx");
        ) {

   Sheet sheet = wb.getSheetAt(0);

   Row row = sheet.getRow(0); if (row == null) row = sheet.createRow(0);
   Cell cell = row.getCell(0); if (cell == null) cell = row.createCell(0);
   cell.setCellValue(new GregorianCalendar(2021, 8, 27));
   CellReference cellReference = new CellReference(cell);
   
   Cell formCell = row.getCell(1); if(formCell == null) formCell = row.createCell(1);
   formCell.setCellFormula("WEEKNUM(" + cellReference.formatAsString() + ")"); // FormulaEvaluator evaluates to #VALUE because of [return_type] is not set
   //formCell.setCellFormula("WEEKNUM(" + cellReference.formatAsString() + ", 1)"); // FormulaEvaluator evaluates to 39 which is wrong as Excel evaluates to 40
   
   FormulaEvaluator formulaEvaluator = wb.getCreationHelper().createFormulaEvaluator();
   CellValue cellValue = formulaEvaluator.evaluate(formCell);
   System.out.println(cellValue);
   
   BaseFormulaEvaluator.evaluateAllFormulaCells(wb);
  
   wb.setForceFormulaRecalculation(true);

   wb.write(fileout);
  }

 }
}