使用 SXSSFWorkbook 创建 excel 并使用 XSSFWorkbook 修改时,字符串单元格数据在 excel 编辑器中不可见

Posted

技术标签:

【中文标题】使用 SXSSFWorkbook 创建 excel 并使用 XSSFWorkbook 修改时,字符串单元格数据在 excel 编辑器中不可见【英文标题】:String cells data is not visible in excel editor when excel is created using SXSSFWorkbook and modified using XSSFWorkbook 【发布时间】:2019-05-29 10:12:41 【问题描述】:

场景: 1) 使用 SXSSFWorkbook 将 csv 文件转换为 excel 文件。 2) 如果再次从 CSV 文件中读取数据并使用 XSSFWorkbook 将数据写入上述生成的 excel 文件,则字符串数据在 libre office 中不可见,但如果在在线 excel 查看器中打开 excel 文件(某些 excel观众都提到文件已损坏,数据可以恢复)。

使用 SXSSFWorkbook 创建单元: 单元格 = row.createCell(1); cell.setCellValue("某个值");

使用 XSSFWorkbook 更新单元格: 单元格 = row.getCell(1); cell.setCellValue("某个值");

观察: 1)当使用XSSFCell更新单元格值时,单元格的原始值和单元格的字符串值不同。

2) 如果使用 SXSSFWorkbook 生成 excel 文件并使用 XSSFWorkbook 打开,则内部维护的 STCellType 为 STCellType.INLINE_STR,如果使用 XSSFWorkbook 生成 excel 文件,则内部维护的 STCellType 为 STCellType.S(STCellType 用于 XSSFCell 的 CTCell)。

Apache POI 版本:4.1.0

请提出解决方案。

【问题讨论】:

【参考方案1】:

SXSSFWorkbook 默认使用内联字符串,而XSSFWorkbook 默认使用共享字符串表。而XSSFCell.setCellValueImpl 对于内联字符串是不完整的。确实如此:

...
if(_cell.getT() == STCellType.INLINE_STR) 
 //set the 'pre-evaluated result
 _cell.setV(str.getString());

...

因此对于内联字符串,它总是设置包含文本的v 元素。但内联字符串也可能有is 元素,其中t 元素包含文本,甚至is 元素具有不同的富文本运行。不考虑使用XSSFCell

但SXSSFWorkbook 可以构造,因此它也使用共享字符串表。请参阅构造函数SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean compressTmpFiles, boolean useSharedStringsTable)。因此,如果使用以下构造函数:

SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(), 2, true, true);

然后不使用内联字符串,以后使用XSSF 更新不会有问题。


如果SXSSFWorkbook不是使用共享字符串表,而是使用内联字符串,那么以后使用XSSF更新单元格时会出现问题,因为XSSFCell在使用内联字符串时不完整。可能的解决方法是使用自己的代码管理内联字符串更新。

例子:

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

import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.xssf.streaming.*;

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

public class SXSSFTest 

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

  // first create SXSSFTest.xlsx using SXSSF ============================================

  String[][] data1 = new String[][]
   new String[]"A1", "B1", "C1",
   new String[]"A2", "B2", "C2",
   new String[]"A3", "B3", "C3",
   new String[]"A4", "B4", "C4"
  ;

  SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook();
  //SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(), 2, true, true);

  SXSSFSheet sxssfSheet = sxssfWorkbook.createSheet();

  int r = 0;
  for (String[] rowValues : data1) 
   SXSSFRow row = sxssfSheet.createRow(r++);
   int c = 0;
   for (String value : rowValues) 
    SXSSFCell cell = row.createCell(c++);
    cell.setCellValue(value);
   
  

  FileOutputStream outputStream = new FileOutputStream("SXSSFTest.xlsx");
  sxssfWorkbook.write(outputStream);
  outputStream.close();
  sxssfWorkbook.dispose();
  sxssfWorkbook.close();

  // now reread the SXSSFTest.xlsx and update it using XSSF =============================

  String[][] data2 = new String[][]
   new String[]"A2 New", "B2 New", "C2 New",
   new String[]"A3 New", "B3 New", "C3 New"
  ;

  XSSFWorkbook xssfWorkbook = (XSSFWorkbook)WorkbookFactory.create(
                               new FileInputStream("SXSSFTest.xlsx"));

  XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0);

  r = 1;
  for (String[] rowValues : data2) 
   XSSFRow row = xssfSheet.getRow(r++); if (row == null) row = xssfSheet.createRow(r++);
   int c = 0;
   for (String value : rowValues) 
    XSSFCell cell = row.getCell(c++); 
    if (cell != null)  // cell was already there
     if (cell.getCTCell().getT() == STCellType.INLINE_STR)  // cell has inline string in it
      if (cell.getCTCell().isSetIs())  // inline string has is element
       cell.getCTCell().getIs().setT(value); // set t element in is element
       else 
       cell.getCTCell().setV(value); // set v element of inline string
      
      else 
      cell.setCellValue(value); // set shared string cell value
     
     else 
     cell = row.createCell(c++);
     cell.setCellValue(value);
    
   
  

  outputStream = new FileOutputStream("XSSFTest.xlsx");
  xssfWorkbook.write(outputStream);
  outputStream.close();   
  xssfWorkbook.close();

 

之后SXSSFTest.xlsx 在我的LibreOffice Calc 中看起来像这样:

所有单元格中都有内联字符串。

XSSFTest.xlsx 看起来像这样:

现在所有内联字符串都已正确更新。

LibreOffice
Version: 6.0.7.3
Build ID: 1:6.0.7-0ubuntu0.18.04.5

【讨论】:

嗨阿克塞尔,感谢您的回复。 您好阿克塞尔,感谢您的回复。如果使用 xssfworkbook 再次创建行和单元格,则数据在 libre office 中可见。但是,我的问题是不应该创建行和单元格,而是必须从 xssfworkbook 本身读取它们。如果“XSSFRow row = xssfSheet.createRow(r++);”将重现该问题替换为“XSSFRow 行 = xssfSheet.getRow(r++);”和“XSSFCell cell = row.createCell(c++);”替换为“XSSFCell cell = row.getCell(c++);” @Anand Kumar:我现在看到了问题所在。请参阅我编辑的答案。 您好 Axel Richter,感谢您的回复。上面提供的两种解决方案都解决了这个问题。我将继续第一个解决方案

以上是关于使用 SXSSFWorkbook 创建 excel 并使用 XSSFWorkbook 修改时,字符串单元格数据在 excel 编辑器中不可见的主要内容,如果未能解决你的问题,请参考以下文章

无法在java中读取使用SXSSFWorkbook编写的文件

《JAVA》编程中怎么用SXSSFWorkbook对已存在的excel操作进行写数据操作

HSSFWorkbook-SXSSFWorkbook导出excel文件获取大小记录

JAVA编程中用Apache POI 怎么用SXSSFWorkbook对已存在的excel(.xlsx)操作进行写数据操作

java excel poi 中的SXSSFWorkbook wb = new SXSSFWorkbook(workbook, 1000);是啥意思?

Java-API-POI-Excel:SXSSFWorkbook Documentation