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



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

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

我已经在堆栈溢出上找到了这个问题 当我更改标题中任何单元格的值(列标题)时,Excel文件已损坏 但它没有答案,而且很老。但是我试图弄清楚注释可能是关于什么的,并尝试展平现有的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 在其某些映射中保留了对单元格名称的引用,但我无法弄清楚如何全部抓取它们并重命名标题。也许还有另一种翻译标题的方法?

好吧,XSSFTable.updateHeaders应该可以做到这一点,如果apache poi不会失败的话。

以下所有操作都是使用apache poi 4.0.1完成的。

我已经下载了您的dummy_template.xlsx,然后尝试更改工作表中的表格列标题。但即使在调用XSSFTable.updateHeadersXSSFTable中的列名也没有更改。所以我研究了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();
}
}

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

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

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

最新更新