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

3

我在使用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();

谢谢!


1
请问您能分享一些将这些字符写入Excel的代码吗? - Sanjeev
感谢您的回复。已添加示例代码。 - leonlai
1
尝试使用转义文本 "\u4e03"(七)- 这样Java源编码就不会产生干扰。 - Joop Eggen
1
@AxelRichter U+20000 == "\ud840\udc00"(2个UTF-16字符)== new String(new int[] { 0x20000 }, 0, 1)。不过还是谢谢,确实可能存在关于两个字符的错误:单个字符处理等等。(然而我的评论只是为了简单地排除不太可能的错误,即Java编译器使用与编辑器不同的编码方式。) - Joop Eggen
1
@Joop Eggen:这是一个很好的观点,谢谢。我没有想到过这一点。所以我们可以修补org.apache.xmlbeans.impl.store.Saver,它不应该将高代理范围(\uD800-\uDBFF)和低代理范围(\uDC00-\uDFFF)排除为坏字符。今天下午会尝试一下。 - Axel Richter
显示剩余7条评论
1个回答

3
问题存在。但不是针对16位(2字节)Unicode字符,范围从0x00000xFFFF。问题在于需要超过2个字节的字符在Unicode编码中。这些字符被称为Java Character中的“Unicode代码点”:“Unicode代码点用于U+0000至U+10FFFF之间的字符值,并且Unicode代码单元用于UTF-16编码的16位char值的代码单元。” Java平台在char数组以及String和StringBuffer类中使用UTF-16表示法。在该表示法中,补充字符(其代码点大于U+FFFF的字符)表示为一对char值,第一个来自高代理范围(\uD800-\uDBFF),第二个来自低代理范围(\uDC00-\uDFFF)。

问题出现在org.apache.xmlbeans.impl.store.Saver中。它使用了一个private char[] _buf。但是由于char的最大值是0xFFFF,所以无法存储Unicode代码点在0x100000x10FFFF之间的字符。因此,它们将被存储为一对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)作为坏字符排除。因此,在XML中存储的两个16位chars表示的Unicode代码点将不会被排除。

下载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中的Saver$OptimizedForSpeedSaver.classSaver$TextSaver.class替换为新编译的文件->/org/apache/xmlbeans/impl/store/
现在,Unicode代码点U+10000以上的内容将存储在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 ch 是否是 HighSurrogateLowSurrogate ,如果是,那么它不是坏字符。好的。
但尽管如此,它仍然检查 char ch 是否大于或等于 0x10000。再次强调:这对于 char 是不可能的!char 的最大值是 0xFFFF

嗨,Axel Ricter。感谢您的解释!这是否意味着使用POI无法将16位字符放入.xlsx文件中? - leonlai
@leonlai:16位字符没有问题。Unicode字符需要超过16位,这是有问题的。请看我在你的问题下面的最后一条评论。今晚我会尝试修补。 - Axel Richter
嗨,Axel Ricter。感谢您的解释,但我们如何解决org.apache.xmlbeans.impl.store.Saver中的问题? - leonlai
抱歉,我可以问一下如何将这些类替换为xmlbeans-2.6.0.jar吗?我尝试替换这些文件,但它显示以下消息:0错误:重复的文件名:1 org\apache\xmlbeans\xml\stream\Location.class 2 org\apache\xmlbeans\xml\stream\Location.class - leonlai
@Alex Ricter:感谢您的帮助。现在没问题了。我已经获取了xmlbeans源代码,更新了Saver.java并构建了新的jar包。但是,我可以问一下为什么在更改了您建议的代码后,这可以解决问题吗? - leonlai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接