【问题标题】:Renaming headers of XSSFTable with Apache Poi leads to corrupt XLSX-file使用 Apache Poi 重命名 XSSFTable 的标头会导致 XLSX 文件损坏
【发布时间】:2019-04-05 09:15:30
【问题描述】:

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

目前,我们已经使用 Excel 创建了一个“模板”xlsx-sheet,其中已经包含一个可排序的表(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) 但它没有回答而且很旧。但我试图弄清楚 cmets 可能是什么,并试图展平现有的 XSSFTable,将填充的数据复制到新的工作表中,然后为数据添加一个新的 XSSFTable。可悲的是,这似乎很复杂,所以我回到纠正损坏的标题单元格。 我还尝试使用 POI 创建整个工作表并不再使用该“模板”-xslx,但我无法弄清楚如何实现我们的 XSD 映射(在 Excel 中它的 Developer-Tools -> Source -> Add and then mapping动态表中某些单元格的节点)

在 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 中的标题不仅是工作表中的文本内容,而且是XSSFTableColumns 的名称> 作为列说明符,如结构化引用的[ColumnName] 部分。因此,当需要更改 XSSFTable 的标头时,所有这些都需要更改。
  • 首先我会尝试在工作表中更改标题单元格值后调用XSSFTable.updateHeadersXSSFTable.updateReferences 是否可以解决问题。
  • 我已经尝试过 updateHeaders 和 updateReferences 但遗憾的是它不起作用。桌子仍然腐烂然后打开。是否有任何提示我可以在哪里找到,哪些结构化引用需要更改?
  • “我能在哪里找到任何提示,哪些结构化引用需要更改?”:不,这里没有“模板”xlsx 文件。有很多不同的可能性。
  • 我上传了一个虚拟 xlsx,这基本上是我用于我的项目的:link 在谷歌驱动器上。有一个 XSD 映射到表中,也在“参考”后面。除了更新所有参考资料外,我也对其他解决方法持开放态度,但我现在真的没有想法。我正在尝试解决这个问题一段时间,但我觉得自己真的被卡住了

标签: java excel apache-poi xssf


【解决方案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
}

因此,仅当工作表中的相应行根据Office Open XML 名称空间有效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 更改为

&lt;row r="4" spans="1:3"&gt;

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

【讨论】:

  • 非常感谢!这解决了我的问题 - 我自己永远也猜不到这个......多么棘手!
猜你喜欢
  • 2021-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-02
  • 1970-01-01
  • 1970-01-01
  • 2018-12-06
  • 1970-01-01
相关资源
最近更新 更多