简体   繁体   English

如果工作表包含表格,XSSFWorkbook cloneSheet 会损坏工作簿

[英]XSSFWorkbook cloneSheet corrupts workbook if sheet contains a table

I want to clone an Excel sheet and all of its contents.我想克隆 Excel 工作表及其所有内容。 I tried the XSSFWorkbook cloneSheet method , but it seems the workbook is corrupted if my sheet contains an Excel table.我尝试了XSSFWorkbook cloneSheet 方法,但如果我的工作表包含 Excel 表,则工作簿似乎已损坏。 See the examle workbook below with a simple table:请参阅下面的示例工作簿,其中包含一个简单的表格:

在此处输入图像描述

When I try to open the output workbook, I get a prompt telling me that the file is broken and needs to be repaired.当我尝试打开 output 工作簿时,我收到一条提示,告诉我文件已损坏,需要修复。 If I recover the workbook, it is clear the table has not been copied correctly;如果我恢复工作簿,很明显该表没有被正确复制; the original totals row is now a data row.原来的总计行现在是一个数据行。

在此处输入图像描述

try (InputStream is = Table.class.getResourceAsStream("table.xlsx")) {
    XSSFWorkbook workbook = new XSSFWorkbook(is);

    workbook.cloneSheet(0, "Test");

    try (OutputStream fileOut = new FileOutputStream("table-2.xlsx")) {
        workbook.write(fileOut);
    }

} catch (IOException e) {
    e.printStackTrace();
}

How I would go about copying this sheet?我将如何 go 关于复制此表? Any help is appreciated!任何帮助表示赞赏!

XSSFWorkbook.cloneSheet clones a sheet. XSSFWorkbook.cloneSheet克隆工作表。 But it does not considering the possible defined tables in it.但它没有考虑其中可能定义的表。 It simply clones the table references.它只是克隆表引用。 But two table-ranges in sheets cannot refer to the same table reference.但是工作表中的两个表范围不能引用同一个表引用。 The tables itself needs to be cloned.表本身需要克隆。 That's why the corrupted workbook as result.这就是损坏工作簿的原因。

I've tried to solve this by programming a method cloneTables(XSSFSheet sheet) which simply creates clones of each table in a sheet which then refer to their own table reference each.我试图通过编写一个方法cloneTables(XSSFSheet sheet)来解决这个问题,该方法只是在工作表中创建每个表的克隆,然后每个表引用它们自己的表引用。 I consider table styles, auto-filter, a totals-row and calculated column formulas.我考虑表 styles、自动过滤器、总计行和计算列公式。 I hope I have not overlooked something, but I doubt that.我希望我没有忽略什么,但我对此表示怀疑。

The code its tested and works using current apache poi 5.2.2 .该代码使用当前的apache poi 5.2.2进行了测试和工作。

It contains fixes for following bugs too:它还包含以下错误的修复:

XSSFTable.updateHeaders fails in Excel workbooks created using current Excel versions. XSSFTable.updateHeaders在使用当前 Excel 版本创建的 Excel 工作簿中失败。 This is because of the test row.getCTRow().validate() which always will be false because of the usage of new name spaces.这是因为测试row.getCTRow().validate()由于使用了新的名称空间,它总是为假。 See Renaming headers of XSSFTable with Apache Poi leads to corrupt XLSX-file .请参阅Renaming headers of XSSFTable with Apache Poi 导致损坏的 XLSX-file

XSSFSheet.removeTable does not remove the links to the table part reference from the sheet. XSSFSheet.removeTable不会从工作表中删除指向表格部件参考的链接。

Complete example to test:完整的示例进行测试:

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.util.*;
import org.apache.poi.ss.SpreadsheetVersion;

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

import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;

class ExcelCloneSheetHavingTable {
        
 static void updateHeaders(XSSFTable table) {
  XSSFSheet sheet = (XSSFSheet)table.getParent();
  CellReference ref = table.getStartCellReference();

  if (ref == null) return;

  int headerRow = ref.getRow();
  int firstHeaderColumn = ref.getCol();
  XSSFRow row = sheet.getRow(headerRow);
  DataFormatter formatter = new DataFormatter();
  
  if (row != null /*&& row.getCTRow().validate()*/) { // see bug: https://stackoverflow.com/questions/55532006/renaming-headers-of-xssftable-with-apache-poi-leads-to-corrupt-xlsx-file/55539181#55539181
   int cellnum = firstHeaderColumn;
   org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumns ctTableColumns = table.getCTTable().getTableColumns();
   if(ctTableColumns != null) {
    for (org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn col : ctTableColumns.getTableColumnList()) {
     XSSFCell cell = row.getCell(cellnum);
     if (cell != null) {
      String colName = formatter.formatCellValue(cell);
      colName = colName.replace("\n", "_x000a_");
      colName = colName.replace("\r", "_x000d_");
      col.setName(colName);
     }
     cellnum++;
    }
   }
  }
  //tableColumns = null;
  //columnMap = null;
  //xmlColumnPrs = null;
  //commonXPath = null;
  try {
   java.lang.reflect.Field tableColumns = XSSFTable.class.getDeclaredField("tableColumns");
   tableColumns.setAccessible(true);
   tableColumns.set(table, null);
   java.lang.reflect.Field columnMap = XSSFTable.class.getDeclaredField("columnMap");
   columnMap.setAccessible(true);
   columnMap.set(table, null);
   java.lang.reflect.Field xmlColumnPrs = XSSFTable.class.getDeclaredField("xmlColumnPrs");
   xmlColumnPrs.setAccessible(true);
   xmlColumnPrs.set(table, null);
   java.lang.reflect.Field commonXPath = XSSFTable.class.getDeclaredField("commonXPath");
   commonXPath.setAccessible(true);
   commonXPath.set(table, null);
  } catch (Exception ex) {
   ex.printStackTrace();   
  }
 }
 
 static String getSubtotalFormulaStartFromTotalsRowFunction(int intTotalsRowFunction) {
  final int INT_NONE = 1;
  final int INT_SUM = 2;
  final int INT_MIN = 3;
  final int INT_MAX = 4;
  final int INT_AVERAGE = 5;
  final int INT_COUNT = 6;
  final int INT_COUNT_NUMS = 7;
  final int INT_STD_DEV = 8;
  final int INT_VAR = 9;
  final int INT_CUSTOM = 10;
  String subtotalFormulaStart = null;
  switch (intTotalsRowFunction) {
   case INT_NONE:
    subtotalFormulaStart = null;
    break;
   case INT_SUM:
    subtotalFormulaStart = "SUBTOTAL(109";
    break;
   case INT_MIN:
    subtotalFormulaStart = "SUBTOTAL(105";
    break;
   case INT_MAX:
    subtotalFormulaStart = "SUBTOTAL(104";
    break;
   case INT_AVERAGE:
    subtotalFormulaStart = "SUBTOTAL(101";
    break;
   case INT_COUNT:
    subtotalFormulaStart = "SUBTOTAL(103";
    break;
   case INT_COUNT_NUMS:
    subtotalFormulaStart = "SUBTOTAL(102";
    break;
   case INT_STD_DEV:
    subtotalFormulaStart = "SUBTOTAL(107";
    break;
   case INT_VAR:
    subtotalFormulaStart = "SUBTOTAL(110";
    break;
   case INT_CUSTOM:
    subtotalFormulaStart = null;
    break;
   default:
    subtotalFormulaStart = null;   
  }
  return subtotalFormulaStart;  
 }
    
 static void cloneTables(XSSFSheet sheet) {
  for (XSSFTable table : sheet.getTables()) {
   // clone table
   XSSFTable clonedTable = sheet.createTable(table.getArea());
   //clonedTable.updateHeaders(); // don't work, see bug: https://stackoverflow.com/questions/55532006/renaming-headers-of-xssftable-with-apache-poi-leads-to-corrupt-xlsx-file/55539181#55539181
   updateHeaders(clonedTable);
   
   // clone style
   clonedTable.setStyleName(table.getStyleName());
   XSSFTableStyleInfo style = (XSSFTableStyleInfo)table.getStyle();
   XSSFTableStyleInfo clonedStyle = (XSSFTableStyleInfo)clonedTable.getStyle();
   if (style != null && clonedStyle != null) {
    clonedStyle.setShowColumnStripes(style.isShowColumnStripes());
    clonedStyle.setShowRowStripes(style.isShowRowStripes());
    clonedStyle.setFirstColumn(style.isShowFirstColumn());
    clonedStyle.setLastColumn(style.isShowLastColumn());
   }
   
   //clone autofilter
   clonedTable.getCTTable().setAutoFilter(table.getCTTable().getAutoFilter());
   
   //clone totalsrow
   int totalsRowCount = table.getTotalsRowCount();
   if (totalsRowCount == 1) { // never seen more than one totals row
    XSSFRow totalsRow = sheet.getRow(clonedTable.getEndCellReference().getRow());
    if (clonedTable.getCTTable().getTableColumns().getTableColumnList().size() > 0) {
     clonedTable.getCTTable().setTotalsRowCount(totalsRowCount);
     for (int i = 0; i < clonedTable.getCTTable().getTableColumns().getTableColumnList().size(); i++) {
      org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn tableCol = table.getCTTable().getTableColumns().getTableColumnList().get(i);
      org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn clonedTableCol = clonedTable.getCTTable().getTableColumns().getTableColumnList().get(i);
      clonedTableCol.setTotalsRowFunction(tableCol.getTotalsRowFunction());
      int intTotalsRowFunction = clonedTableCol.getTotalsRowFunction().intValue();
      sheet.getWorkbook().setCellFormulaValidation(false);
      if (intTotalsRowFunction == 10) { //custom
       org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableFormula totalsRowFormula = tableCol.getTotalsRowFormula();
       clonedTableCol.setTotalsRowFormula(totalsRowFormula);
       totalsRow.getCell(clonedTable.getStartCellReference().getCol()+i).setCellFormula(totalsRowFormula.getStringValue());
      } else if (intTotalsRowFunction == 1) { //none
       //totalsRow.getCell(clonedTable.getStartCellReference().getCol()+i).setBlank();
      } else {
       String subtotalFormulaStart = getSubtotalFormulaStartFromTotalsRowFunction(intTotalsRowFunction);
       if (subtotalFormulaStart != null) 
        totalsRow.getCell(clonedTable.getStartCellReference().getCol()+i).setCellFormula(subtotalFormulaStart + "," + clonedTable.getName() +"[" + clonedTableCol.getName()+ "])");
      }
     }
    }
   }
   
   // clone calculated column formulas
   if (clonedTable.getCTTable().getTableColumns().getTableColumnList().size() > 0) {
    clonedTable.getCTTable().setTotalsRowCount(totalsRowCount);
    for (int i = 0; i < clonedTable.getCTTable().getTableColumns().getTableColumnList().size(); i++) {
     org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn tableCol = table.getCTTable().getTableColumns().getTableColumnList().get(i);
     org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn clonedTableCol = clonedTable.getCTTable().getTableColumns().getTableColumnList().get(i);
     if (tableCol.getCalculatedColumnFormula() != null) {
      clonedTableCol.setCalculatedColumnFormula(tableCol.getCalculatedColumnFormula());
      org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableFormula calculatedColumnFormula = clonedTableCol.getCalculatedColumnFormula();
      String formula = tableCol.getCalculatedColumnFormula().getStringValue();
      String clonedFormula = formula.replace(table.getName(), clonedTable.getName());
      calculatedColumnFormula.setStringValue(clonedFormula);
      int rFirst = clonedTable.getStartCellReference().getRow() + clonedTable.getHeaderRowCount();
      int rLast = clonedTable.getEndCellReference().getRow() - clonedTable.getTotalsRowCount();
      int c = clonedTable.getStartCellReference().getCol() + i;
      sheet.getWorkbook().setCellFormulaValidation(false);
      for (int r = rFirst; r <= rLast; r++) {
       sheet.getRow(r).getCell(c).setCellFormula(clonedFormula);
      }       
     }
    }
   }
   
   // remove old table   
   String rId = sheet.getRelationId(table);
   sheet.removeTable(table);
   // remove links to the table part reference
   org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableParts tblParts = sheet.getCTWorksheet().getTableParts();
   if (tblParts != null &&  tblParts.getTablePartList().size() > 0) {
    for (int i = 0; i < tblParts.getTablePartList().size(); i++) {
     org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTablePart tblPart = tblParts.getTablePartArray​(i);    
     if(tblPart.getId().equals(rId)) {
      tblParts.removeTablePart​(i);
     }   
    }     
   }
   
  }
 }

 public static void main(String[] args) throws Exception {
  try (Workbook workbook = WorkbookFactory.create(new FileInputStream("SAMPLE.xlsx"));
       FileOutputStream out = new FileOutputStream("SAMPLE_NEW.xlsx")) {

   XSSFSheet sheet = ((XSSFWorkbook)workbook).cloneSheet(0, "Test");
   cloneTables(sheet);
   
   workbook.write(out);
  } 
 }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM