对于超过3.0的Unicode版本,如何将Java字符串转换为xml实体?

时间:2014-07-01 00:51:25

标签: java unicode unicode-string xml-entities

要将java字符转换为xml实体,我可以对字符串中的每个字符执行以下操作:

buf.append("&#x"+ Integer.toHexString(c | 0x10000).substring(1) +";");

但是,根据其他stackoverflow问题,这仅适用于Unicode 3.0。

如果我使用UTF-8 Reader读取字符串,那么可能是String包含通过Unicode 6.0处理的格式的字符(因为Java 7根据javadoc支持Unicode 6.0)。

一旦我有了String,我怎么能把它写成xml实体?理想情况下,我会使用一些随着新版unicode问世而继续工作的api。

2 个答案:

答案 0 :(得分:4)

要么您没有使用正确的术语,要么存在很多混淆。

&#x字符引用表示法只指定一个数字代码点;它独立于任何读者或解析器使用的Unicode版本。

您的代码实际上只与Unicode 1.x兼容,因为它假定字符的数值小于2 16 。从Unicode 2.0开始,这不是一个正确的假设。一些字符由单个Java char表示,而其他字符由两个Java char表示(称为surrogates)。

我不确定" UTF-8读卡器"是。 Reader仅读取char值,并且不知道UTF-8或任何其他字符集,InputStreamReader除外,CharsetDecoder使用Formatter将字节转换为字符使用UTF-8编码(或特定CharsetDecoder使用的任何编码)。

无论如何,没有Reader会解析XML &#x字符引用表示法。您必须使用XML解析器。

没有Reader或XML解析器受Java已知的Unicode版本的影响,因为没有Reader或XML解析器以任何方式查询Unicode数据库。在解析字符时,字符仅被视为数值。它们是否与任何Unicode版本中的已分配代码点相对应,从不考虑它们。

最后,要将String写为XML,您可以使用{{3}}:

static String toXML(String s) {
    Formatter formatter = new Formatter();
    int len = s.length();
    for (int i = 0; i < len; i = s.offsetByCodePoints(i, 1)) {
        int c = s.codePointAt(i);
        if (c < 32 || c > 126 || c == '&' || c == '<' || c == '>') {
            formatter.format("&#x%x;", c);
        } else {
            formatter.format("%c", c);
        }
    }
    return formatter.toString();
}

如您所见,没有代码依赖于Unicode版本,因为字符只是数值。每个数值是否都是指定的Unicode代码点是不相关的。

(我的第一个倾向是使用XMLStreamWriter类,但事实证明使用非Unicode编码的XMLStreamWriter如ISO-8859-1或US-ASCII不能将代理对正确输出为单个字符实体,如Java 1.8.0_05。)

答案 1 :(得分:2)

最初Java支持Unicode 1.0,使 char 类型长16位,但Unicode 2.0引入了代理字符机制来支持比16位允许的数字更多的字符,因此Java字符串变为UTF- 16编码;这意味着某些角色需要表示两个Java字符,它们被称为高代理字符和低代理字符。

要知道字符串中的哪些字符实际上是高/低代理对,您可以使用Character中的实用程序方法:

Character.isHighSurrogate(myChar); // returns true if myChar is a high surrogate
Character.isLowSurrogate(myChar); // same for low surrogate

Character.isSurrogate(myChar); // just to know if myChar is a surrogate

一旦你知道哪些字符是高代理或低代理,你需要使用这种方法将每对字符转换为unicode代码点:

int codePoint = Character.toCodePoint(highSurrogate, lowSurrogate);

由于一段代码胜过千言万语,这是一个示例方法,用于替换字符串中的非us-ascii字符的xml字符引用:

public static String replaceToCharEntities(String str) {
    StringBuilder result = new StringBuilder(str.length());

    char surrogate = 0;
    for(char c: str.toCharArray()) {

        // if char is a high surrogate, keep it to match it
        // against the next char (low surrogate)
        if(Character.isHighSurrogate(c)) {
            surrogate = c;
            continue;
        }

        // get codePoint
        int codePoint;
        if(surrogate != 0) {
            codePoint = Character.toCodePoint(surrogate, c);
            surrogate = 0;
        } else {
            codePoint = c;
        }

        // decide wether using just a char or a character reference
        if(codePoint < 0x20 || codePoint > 0x7E || codePoint == '<'
                || codePoint == '>' || codePoint == '&' || codePoint == '"'
                || codePoint == '\'') {
            result.append(String.format("&#x%x;", codePoint));
        } else {
            result.append(c);
        }
    }

    return result.toString();
}

下一个字符串示例是一个很好的测试用例,因为它包含一个非ascii字符,可以用16位值表示,还有一个带有高/低代理项对的字符:

String myString = "text with some non-US chars: 'Ñ' and ''";