当单元格具有公式时,shiftColumn 方法不起作用

shiftColumn method is not working when cell has formula

当 Excel sheet 有公式时,

sheet.shiftColumns(int start, int end, int shift) 不起作用。此操作后 Excel 被损坏。当单元格中没有公式时,它工作得很好。

对此有什么想法吗?

代码是这样的:

Sheet sheet = workbook.getSheet("sheet姓名"); sheet.shiftColumn(3,5,1); // 这个调用破坏了 Excel sheet

我用来创建工作簿的 excel sheet 有 5 列,有些单元格有公式。

此问题仅在使用 XSSF 时出现。如果您在移动列后打开 *.xlsx 并选择是,那么 Excel 会尝试修复,您会被告知公式记录已从 /xl/calcChain.xml-Part 中删除。所以calcChain.xml移位后没有更新。

什么是CalculationChain?见 https://docs.microsoft.com/en-us/office/open-xml/working-with-the-calculation-chain:

The Calculation Chain part specifies the order in which cells in the workbook were last calculated. It only records information about cells containing formulas.

但是在移动具有公式的单元格之后,calcChain.xml 现在包含没有公式的单元格,明确地是那些被移动到另一个地方的单元格。所以 calcChain.xml 需要更新。至少需要删除现在不再包含公式的单元格。

最简单的解决方法就是删除整个 calcChain.xml。这是可能的,因为 Excel 仅使用计算链来优化重新计算。它不一定需要计算链,但如果计算链中有错误的条目,当然优化过程会失败。

calcChain.xml 中删除所有条目的方法可能如下所示:

 private static void removeCalcChain(XSSFWorkbook workbook) throws Exception {
  CalculationChain calcchain = workbook.getCalculationChain();
  Method removeRelation = POIXMLDocumentPart.class.getDeclaredMethod("removeRelation", POIXMLDocumentPart.class); 
  removeRelation.setAccessible(true); 
  removeRelation.invoke(workbook, calcchain);
 }

完整示例:

import java.io.FileInputStream;
import java.io.FileOutputStream;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.model.CalculationChain;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import java.lang.reflect.Method;

public class ExcelShiftColums {
                
 private static void removeCalcChain(XSSFWorkbook workbook) throws Exception {
  CalculationChain calcchain = workbook.getCalculationChain();
  Method removeRelation = POIXMLDocumentPart.class.getDeclaredMethod("removeRelation", POIXMLDocumentPart.class); 
  removeRelation.setAccessible(true); 
  removeRelation.invoke(workbook, calcchain);
 }

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

  String inFilePath = "./ExcelExampleIn.xlsx";
  String outFilePath = "./ExcelExampleOut.xlsx";

  //String inFilePath = "./ExcelExampleIn.xls";
  //String outFilePath = "./ExcelExampleOut.xls";


  try (Workbook workbook = WorkbookFactory.create(new FileInputStream(inFilePath));
       FileOutputStream out = new FileOutputStream(outFilePath ) ) {

   Sheet sheet = workbook.getSheetAt(0);

   sheet.shiftColumns(3, 4, 1);
  
   if (workbook instanceof XSSFWorkbook) removeCalcChain((XSSFWorkbook)workbook);

   workbook.write(out);
  }
 }
}

为什么这不是使用 shiftRows 的问题?

当使用 XSSFSheet.createRow 创建新行时,使用 XSSFRow.removeCell 删除该行中所有以前的单元格。当前一个单元格包含公式时,将调用此 XSSFWorkbook.onDeleteFormula。这也更新了计算链。

但是 XSSFRow.createCell 并没有删除之前的单元格,而是简单地创建了一个新单元格来代替原来的单元格。所以 XSSFWorkbook.onDeleteFormula 没有被调用,计算链仍然是错误的。

这意味着可以通过以下方式简单地创建一个具有错误计算链的损坏 *.xlsx

...
XSSFRow row = sheet.getRow(2);
row.createCell(2);
...

C3 是包含公式的单元格时。

@Apache POI:请修补 XSSFRow.createCell 这样也会删除以前的单元格(如果有的话)。