使用Apache Poi重命名XSSFTable标头会导致损坏的XLSX文件

时间:2019-04-05 09:15:31

标签: java excel apache-poi xssf

我正在尝试重命名现有xlsx文件的标头。想法是要有一个excel文件,以便在某些用户进行调整后将数据从XML导出到excel并重新导入XML。

目前,我们已经使用Excel创建了一个“模板” xlsx工作表,该工作表已经包含一个可排序的表(poi中为XSSFTable)和到XSD源的映射。然后,我们通过POI导入它,将XML数据映射到其中并保存。为了调整工作表以适合用户,我们希望将此现有表的标题/列名翻译成不同的语言。它可以与POI 3.10-FINAL一起使用,但是由于升级到4.0.1后,打开时会导致损坏的xlsx文件。

我已经在stackoverflow上发现了这个问题 Excel file gets corrupted when i change the value of any cell in the header (Columns Title) 但是没有答案,而且很旧。但是我试图弄清楚这些注释可能是关于什么的,并试图展平现有的XSSFTable,将填充的数据复制到新的工作表中,然后将新的XSSFTable放入该数据。可悲的是,这似乎很复杂,因此我要纠正损坏的标头单元。 我还尝试使用POI创建整个工作表,并远离使用该“模板” -xslx,但是我无法弄清楚如何实现XSD映射(在Excel中,其开发人员工具->源->添加,然后映射动态表中某些单元格的节点)

在poi升级之前一直有效的代码是这样的:

//Sheet is the current XSSFSheet
//header is a Map with the original header-name from the template mapped to a the new translated name
//headerrownumber is the row containing the tableheader to be translated

 public static void translateHeaders(Sheet sheet,final Map<String,String> header,int headerrownumber) {
  CellRangeAddress address = new CellRangeAddress(headerrownumber,headerrownumber,0,sheet.getRow(headerrownumber).getLastCellNum());  //Cellrange is the header-row

        MyCellWalk cellWalk = new MyCellWalk (sheet,address);
        cellWalk.traverse(new CellHandler() {
            public void onCell(Cell cell, CellWalkContext ctx) {
                String val = cell.getStringCellValue();
                if (header.containsKey(val)) {
                    cell.setCellValue(header.get(val));
                }
            }
        });
}

MyCellWalk是一个org.apache.poi.ss.util.cellwalk.CellWalk,它遍历从左上角到右下角的单元格范围。

据我所知,它不足以简单地更改单元格的固定值,因为xlsx在其某些映射中保留了对单元名称的引用,但是我无法弄清楚如何全部抓住它们并重命名标头。也许还有另一种翻译标头名称的方法?

1 个答案:

答案 0 :(得分:1)

好吧,如果apache poi不会失败,XSSFTable.updateHeaders应该会成功。

以下所有操作均通过apache poi 4.0.1完成。

我已经下载了您的dummy_template.xlsx,然后尝试更改工作表中的表格列标题。但是,即使在调用XSSFTable.updateHeaders之后,XSSFTable中的列名称也没有更改。因此,我查看了XSSFTable.java -> updateHeaders,以确定为什么没有发生这种情况。在那里我们找到:

if (row != null && row.getCTRow().validate()) {
 //do changing the column names
}

因此,仅根据XML命名空间,如果工作表中的对应行有效Office Open XML时,列名才会更改。但是在更高版本的Excel中(2007年之后),添加了其他名称空间。在这种情况下,行的XML如下:

<row r="4" spans="1:3" x14ac:dyDescent="0.25">

请注意其他x14ac:dyDescent属性。这就是row.getCTRow().validate()返回false的原因。

以下代码获取您的dummy_template.xlsx,重命名工作表中的列标题,然后调用撤防版本static void updateHeaders(XSSFTable table)。之后,result.xlsxExcel中有效。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.*;
import org.apache.poi.ss.util.cellwalk.*;

import org.apache.poi.xssf.usermodel.*;

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

import java.io.*;
import java.util.*;

class ExcelRenameTableColumns {

 static void translateHeaders(Sheet sheet, final Map<String,String> header, int headerrownumber) {
  CellRangeAddress address = new CellRangeAddress(
   headerrownumber, headerrownumber, 
   0, sheet.getRow(headerrownumber).getLastCellNum());

  CellWalk cellWalk = new CellWalk (sheet, address);
  cellWalk.traverse(new CellHandler() {
   public void onCell(Cell cell, CellWalkContext ctx) {
    String val = cell.getStringCellValue();
    if (header.containsKey(val)) {
     cell.setCellValue(header.get(val));
    }
   }
  });
 }

 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();

System.out.println(row.getCTRow().validate()); // false!

  if (row != null /*&& row.getCTRow().validate()*/) {
   int cellnum = firstHeaderColumn;
   CTTableColumns ctTableColumns = table.getCTTable().getTableColumns();
   if(ctTableColumns != null) {
    for (CTTableColumn col : ctTableColumns.getTableColumnList()) {
     XSSFCell cell = row.getCell(cellnum);
     if (cell != null) {
      col.setName(formatter.formatCellValue(cell));
     }
     cellnum++;
    }
   }
  }
 }

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

  String templatePath = "dummy_template.xlsx";
  String outputPath = "result.xlsx";

  FileInputStream inputStream = new FileInputStream(templatePath);
  Workbook workbook = WorkbookFactory.create(inputStream);
  Sheet sheet = workbook.getSheetAt(0);

  Map<String, String> header = new HashMap<String, String>();
  header.put("textone", "Spalte eins");
  header.put("texttwo", "Spalte zwei");
  header.put("textthree", "Spalte drei");

  translateHeaders(sheet, header, 3);

  XSSFTable table = ((XSSFSheet)sheet).getTables().get(0);

  updateHeaders(table);

  FileOutputStream outputStream = new FileOutputStream(outputPath);
  workbook.write(outputStream);
  outputStream.close();
  workbook.close();

 }
}

如果我使用dummy_template.xlsx打开Excel 2007,然后另存为dummy_template2007.xlsx,则该行的XML更改为

<row r="4" spans="1:3">

现在使用此dummy_template2007.xlsx时,无需手动调用XSSFTable.updateHeaders。由XSSFTable.commit调用的XSSFTable.writeTo自动执行此操作。