在 Java 中使用 Apache POI 将 16 位字符写入 .xlsx 文件

Write 16 bits character to .xlsx file using Apache POI in Java

我在 Apache POI 中遇到问题。 问题是,我尝试将 16 位字符值(例如 CJK 统一表意文字扩展 B)放入 .xlsx 文件。但是,单元格值在生成的 .xlsx 文件中变成问号(如 ?????)。

有人知道如何处理 Apache POI 中 .xlsx 格式的 16 位字符值吗???

我的POI版本是3.14

代码示例如下:

XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet("Test");

XSSFRow row1 = sheet.createRow(0);
XSSFCell r1c1 = row1.createCell(0);
r1c1.setCellValue(""); // value of CJK Unified Ideographs Extension B
XSSFCell r1c2 = row1.createCell(1);

FileOutputStream fos =new FileOutputStream("D:/temp/test.xlsx");
workbook.write(fos);
fos.close();

谢谢!

问题存在。但不适用于从 0x00000xFFFF 的 16 位(2 字节)Unicode 字符。它是 Unicode 编码中需要超过 2 个字节的字符。这些是在 Java Character 中提到的 Unicode code points 的字符: "Unicode code point is used for character values in the range between U+0000 and U+10FFFF, and Unicode code unit is used for 16-bit char values that are code units of the UTF-16 encoding." Java 平台在 char 数组以及 String 和 StringBuffer [=111] 中使用 UTF-16 表示=].在此表示中,补充字符(代码点大于 U+FFFF 的字符)表示为一对 char 值,第一个来自高代理项范围 (\uD800-\uDBFF),第二个来自低代理项范围代理项范围 (\uDC00-\uDFFF)。

问题出在 org.apache.xmlbeans.impl.store.Saver。这适用于 private char[] _buf。但是由于 char 最大值是 0xFFFF,从 0x100000x10FFFF 的 Unicode 代码点不可能存储在 char 中。所以 将被存储为一对 char 值。

有个方法

    /**
     * Test if a character is valid in xml character content. See
     * http://www.w3.org/TR/REC-xml#NT-Char
     */

    private boolean isBadChar ( char ch )
    {
        return ! (
            (ch >= 0x20 && ch <= 0xD7FF ) ||
            (ch >= 0xE000 && ch <= 0xFFFD) ||
            (ch >= 0x10000 && ch <= 0x10FFFF) ||
            (ch == 0x9) || (ch == 0xA) || (ch == 0xD)
            );
    }

该代码完全是错误的,因为它检查 char 是否在 0x100000x10FFFF 之间。如前所述,这根本不可能。

它还排除了高代理项范围 (\uD800-\uDBFF) 和低代理项范围 (\uDC00-\uDFFF) 作为坏字符。因此将排除作为一对 char 值的代码点表示。

所以问题是由 org.apache.xmlbeans.impl.store.Saver 中的错误引起的。


补丁:

目标:不排除高代理项范围 (\uD800-\uDBFF) 和低代理项范围 (\uDC00-\uDFFF) 作为坏字符。所以U+10000以上的Unicode码点,存储为两个16位chars不会被排除在XML.

下载Saver.java。将 private boolean isBadChar ( char ch ) 更改为

    /**
     * Test if a character is valid in xml character content. See
     * http://www.w3.org/TR/REC-xml#NT-Char
     */
    private boolean isBadChar ( char ch )
    {
        return ! (
            (ch >= 0x20 && ch <= 0xFFFD ) ||
            (ch == 0x9) || (ch == 0xA) || (ch == 0xD)
            );
    }

static final class OptimizedForSpeedSaverstatic final class TextSaver 中。

编译Saver.java

在类路径外的某处存储 xmlbeans-2.6.0.jar 的备份。

xmlbeans-2.6.0.jar -> /org/apache/xmlbeans/impl/store/中的Saver$OptimizedForSpeedSaver.classSaver$TextSaver.class替换为新编译的

现在 U+10000 以上的 Unicode 代码点将存储在 sharedStrings.xml.


免责声明: 这没有经过很好的测试。所以不要在生产中使用它。此处仅显示以描述问题。也许 xmlbeans.apache.org 上的某些程序员会找到时间来正确解决 org.apache.xmlbeans.impl.store.Saver 的问题。


更新 现在有 xmlbeans-2.6.2.jar 可用。这已经包含补丁。


更新 现在有 xmlbeans-3.0.0.jar 可用。这也包含补丁。

确实如此:

/**
 * Test if a character is valid in xml character content. See
 * http://www.w3.org/TR/REC-xml#NT-Char
 */
static boolean isBadChar ( char ch )
{
    return ! (
        Character.isHighSurrogate(ch) ||
        Character.isLowSurrogate(ch) ||
        (ch >= 0x20 && ch <= 0xD7FF ) ||
        (ch >= 0xE000 && ch <= 0xFFFD) ||
        (ch >= 0x10000 && ch <= 0x10FFFF) ||
        (ch == 0x9) || (ch == 0xA) || (ch == 0xD)
    );
}

因此它会检查 char chHighSurrogate 还是 LowSurrogate,如果是,则它不是坏字符。好的。

但是它检查 char ch 是否大于或等于 0x10000。再次重申:这对于 char 是不可能的! char 的最大值是 0xFFFF.