当 excel 使用 SXSSFWorkbook 创建并使用 XSSFWorkbook 修改时,字符串单元格数据在 excel 编辑器中不可见
String cells data is not visible in excel editor when excel is created using SXSSFWorkbook and modified using XSSFWorkbook
场景:
1) 使用 SXSSFWorkbook 将 csv 文件转换为 excel 文件。
2) 如果数据再次从 CSV 文件中读取并使用 XSSFWorkbook 写入上面生成的 excel 文件,则字符串数据在 libre office 中不可见,但如果 excel 文件在中打开,则数据可见在线 excel 观众(一些 excel 观众提到文件已损坏,数据可以恢复)。
使用 SXSSFWorkbook 创建单元格:
细胞细胞 = row.createCell(1);
cell.setCellValue("Some Value");
使用 XSSFWorkbook 更新单元格:
细胞细胞 = row.getCell(1);
cell.setCellValue("Some Value");
观察:
1)使用XSSFCell更新单元格值时,单元格的原始值和单元格的字符串值不同。
2) 如果 excel 文件是使用 SXSSFWorkbook 生成并使用 XSSFWorkbook 打开,那么内部维护的 STCellType 是 STCellType.INLINE_STR,如果 excel 文件是使用 XSSFWorkbook 生成的,那么内部维护的 STCellType 是 STCellType.S(XSSFCell的CTCell中使用了STCellType)
Apache POI 版本:4.1.0
请提出解决方案。
SXSSFWorkbook
默认使用内联字符串,而 XSSFWorkbook
默认使用共享字符串 table。 XSSFCell.setCellValueImpl 对于内联字符串是不完整的。它确实:
...
if(_cell.getT() == STCellType.INLINE_STR) {
//set the 'pre-evaluated result
_cell.setV(str.getString());
}
...
因此对于内联字符串,它总是设置包含文本的 v
元素。但是内联字符串也可能有 is
元素,其中 t
元素包含文本,甚至 is
元素有不同的富文本运行。这不考虑使用 XSSFCell
.
但是可以构造SXSSFWorkbook所以它也使用共享字符串table。请参阅构造函数 SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean compressTmpFiles, boolean useSharedStringsTable)
。因此,如果使用以下构造函数:
SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(), 2, true, true);
然后不使用内联字符串,以后使用 XSSF
更新不会有问题。
如果SXSSFWorkbook
不是使用共享字符串table而是使用内联字符串,以后使用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
场景:
1) 使用 SXSSFWorkbook 将 csv 文件转换为 excel 文件。
2) 如果数据再次从 CSV 文件中读取并使用 XSSFWorkbook 写入上面生成的 excel 文件,则字符串数据在 libre office 中不可见,但如果 excel 文件在中打开,则数据可见在线 excel 观众(一些 excel 观众提到文件已损坏,数据可以恢复)。
使用 SXSSFWorkbook 创建单元格:
细胞细胞 = row.createCell(1);
cell.setCellValue("Some Value");
使用 XSSFWorkbook 更新单元格:
细胞细胞 = row.getCell(1);
cell.setCellValue("Some Value");
观察:
1)使用XSSFCell更新单元格值时,单元格的原始值和单元格的字符串值不同。
2) 如果 excel 文件是使用 SXSSFWorkbook 生成并使用 XSSFWorkbook 打开,那么内部维护的 STCellType 是 STCellType.INLINE_STR,如果 excel 文件是使用 XSSFWorkbook 生成的,那么内部维护的 STCellType 是 STCellType.S(XSSFCell的CTCell中使用了STCellType)
Apache POI 版本:4.1.0
请提出解决方案。
SXSSFWorkbook
默认使用内联字符串,而 XSSFWorkbook
默认使用共享字符串 table。 XSSFCell.setCellValueImpl 对于内联字符串是不完整的。它确实:
...
if(_cell.getT() == STCellType.INLINE_STR) {
//set the 'pre-evaluated result
_cell.setV(str.getString());
}
...
因此对于内联字符串,它总是设置包含文本的 v
元素。但是内联字符串也可能有 is
元素,其中 t
元素包含文本,甚至 is
元素有不同的富文本运行。这不考虑使用 XSSFCell
.
但是可以构造SXSSFWorkbook所以它也使用共享字符串table。请参阅构造函数 SXSSFWorkbook(XSSFWorkbook workbook, int rowAccessWindowSize, boolean compressTmpFiles, boolean useSharedStringsTable)
。因此,如果使用以下构造函数:
SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(), 2, true, true);
然后不使用内联字符串,以后使用 XSSF
更新不会有问题。
如果SXSSFWorkbook
不是使用共享字符串table而是使用内联字符串,以后使用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