使用 Apache Poi 重命名 XSSFTable headers 会导致损坏 XLSX-file
Renaming headers of XSSFTable with Apache Poi leads to corrupt XLSX-file
我正在尝试重命名现有 xlsx-file 的 header。这个想法是让 excel-file 将数据从 XML 导出到 excel,并在某些用户进行调整后重新导入 XML。
目前我们已经创建了一个 "template" xlsx-sheet 和 Excel,其中已经包含一个 sortable table(poi 中的 XSSFTable)和一个映射到 XSD-source。然后我们通过POI导入,将XML数据映射进去保存。为了将 sheet 调整为我们想要将此现有 table 的 headers/column-names 翻译成不同语言的用户。它适用于 POI 3.10-FINAL,但自升级到 4.0.1 后,打开时会导致损坏 xlsx-file。
我已经在 Whosebug 上找到了这个问题
Excel file gets corrupted when i change the value of any cell in the header (Columns Title)
但它没有回答而且很旧。但我试图弄清楚评论可能是关于什么的,并试图展平现有的 XSSFTable,将填充的数据复制到新的 sheet 并将新的 XSSFTable 放在数据上。可悲的是,这似乎相当复杂,所以我又回来纠正损坏的 header-cells。
我还尝试用 POI 创建整个 sheet 并放弃使用那个 "template"-xslx,但我不知道如何实现我们的 XSD-Mapping(在 Excel 它的Developer-Tools -> Source -> 添加然后将节点映射到动态 table)
中的一些单元格
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 在他们的一些地图中保留了对单元格名称的引用,但我不知道如何抓住它们并重命名header。也许还有另一种翻译 header 名称的方法?
好吧,如果 apache poi
不会失败的话,XSSFTable.updateHeaders 应该可以解决问题。
以下全部使用apache poi 4.0.1
完成。
我已经下载了您的 dummy_template.xlsx
,然后尝试更改 sheet 中的 table 列 headers。但即使在调用 XSSFTable.updateHeaders
之后,XSSFTable
中的列名也没有改变。所以我查看了 XSSFTable.java -> updateHeaders 以确定为什么不会发生这种情况。我们发现:
if (row != null && row.getCTRow().validate()) {
//do changing the column names
}
因此,只有在 sheet 中的相应行根据 Office Open XML
名称空间有效 XML
时,才会更改列名称。但在后来的 Excel
版本中(2007 年之后)添加了额外的名称空间。在这种情况下,该行的 XML
看起来像:
<row r="4" spans="1:3" x14ac:dyDescent="0.25">
注意附加的 x14ac:dyDescent
属性。这就是为什么 row.getCTRow().validate()
returns false
.
以下代码获取您的 dummy_template.xlsx
,重命名 sheet 中的列 headers,然后调用解除武装的版本 static void updateHeaders(XSSFTable table)
。之后 result.xlsx
在 Excel
.
中打开有效
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 会自动执行此操作。
我正在尝试重命名现有 xlsx-file 的 header。这个想法是让 excel-file 将数据从 XML 导出到 excel,并在某些用户进行调整后重新导入 XML。
目前我们已经创建了一个 "template" xlsx-sheet 和 Excel,其中已经包含一个 sortable table(poi 中的 XSSFTable)和一个映射到 XSD-source。然后我们通过POI导入,将XML数据映射进去保存。为了将 sheet 调整为我们想要将此现有 table 的 headers/column-names 翻译成不同语言的用户。它适用于 POI 3.10-FINAL,但自升级到 4.0.1 后,打开时会导致损坏 xlsx-file。
我已经在 Whosebug 上找到了这个问题 Excel file gets corrupted when i change the value of any cell in the header (Columns Title) 但它没有回答而且很旧。但我试图弄清楚评论可能是关于什么的,并试图展平现有的 XSSFTable,将填充的数据复制到新的 sheet 并将新的 XSSFTable 放在数据上。可悲的是,这似乎相当复杂,所以我又回来纠正损坏的 header-cells。 我还尝试用 POI 创建整个 sheet 并放弃使用那个 "template"-xslx,但我不知道如何实现我们的 XSD-Mapping(在 Excel 它的Developer-Tools -> Source -> 添加然后将节点映射到动态 table)
中的一些单元格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 在他们的一些地图中保留了对单元格名称的引用,但我不知道如何抓住它们并重命名header。也许还有另一种翻译 header 名称的方法?
好吧,如果 apache poi
不会失败的话,XSSFTable.updateHeaders 应该可以解决问题。
以下全部使用apache poi 4.0.1
完成。
我已经下载了您的 dummy_template.xlsx
,然后尝试更改 sheet 中的 table 列 headers。但即使在调用 XSSFTable.updateHeaders
之后,XSSFTable
中的列名也没有改变。所以我查看了 XSSFTable.java -> updateHeaders 以确定为什么不会发生这种情况。我们发现:
if (row != null && row.getCTRow().validate()) {
//do changing the column names
}
因此,只有在 sheet 中的相应行根据 Office Open XML
名称空间有效 XML
时,才会更改列名称。但在后来的 Excel
版本中(2007 年之后)添加了额外的名称空间。在这种情况下,该行的 XML
看起来像:
<row r="4" spans="1:3" x14ac:dyDescent="0.25">
注意附加的 x14ac:dyDescent
属性。这就是为什么 row.getCTRow().validate()
returns false
.
以下代码获取您的 dummy_template.xlsx
,重命名 sheet 中的列 headers,然后调用解除武装的版本 static void updateHeaders(XSSFTable table)
。之后 result.xlsx
在 Excel
.
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 会自动执行此操作。