将NSString转换为UTF32格式,以及从UTF32格式转换回NSString。

6
我正在使用一个包含UTF32字符的数据库进行工作。我想将这些字符存储在NSString中。我需要编写转换的例程,以便能够双向转换。
要将NSString的第一个字符转换为Unicode值,似乎可以使用以下例程:
const unsigned char *cs = (const unsigned char *)
    [s cStringUsingEncoding:NSUTF32StringEncoding];
uint32_t code = 0;
for ( int i = 3 ; i >= 0 ; i-- ) {
    code <<= 8;
    code += cs[i];
}
return code;

然而,我无法做到相反的操作(即将单个代码转换为NSString)。我以为我可以通过简单地创建一个包含UTF32字符的c字符串,并按正确顺序排列字节,然后使用正确的编码从中创建一个NSString来实现上述操作的相反过程。
然而,对于我来说,转换为/从c字符串似乎是不可逆的。
例如,我尝试了这段代码,但是“tmp”字符串与原始字符串“s”并不相等。
char *cs = [s cStringUsingEncoding:NSUTF32StringEncoding];
NSString *tmp = [NSString stringWithCString:cs encoding:NSUTF32StringEncoding];

我做错了什么?我应该使用"wchar_t"来代替char *作为cstring吗?
2个回答

16
你有几个合理的选择。

1. 转换

第一种方法是将你的UTF32转换为UTF16并与NSString一起使用,因为UTF16是NSString的“本地”编码。这并不是特别难。如果UTF32字符在BMP中(例如,它的高两字节为0),你可以直接将其强制转换为unichar。如果它在任何其他平面上,则可以将其转换为UTF16字符对的代理项。你可以在维基百科页面找到相关规则。但是一个快速的(未经测试的)转换看起来像这样:

UTF32Char inputChar = // my UTF-32 character
inputChar -= 0x10000;
unichar highSurrogate = inputChar >> 10; // leave the top 10 bits
highSurrogate += 0xD800;
unichar lowSurrogate = inputChar & 0x3FF; // leave the low 10 bits
lowSurrogate += 0xDC00;

现在您可以同时使用两个字符创建一个NSString:

NSString *str = [NSString stringWithCharacters:(unichar[]){highSurrogate, lowSurrogate} length:2];

要倒序处理,可以使用 [NSString getCharacters:range:] 获取 unichar,然后反转代理对算法以获取 UTF32 字符(任何不在范围 0xD800-0xDFFF 内的字符应直接转换为 UTF32)。

2. 字节缓冲区

您还有另一种选择,即让 NSString 直接进行转换而不使用 cStrings。要将 UTF32 值转换为 NSString,可以使用以下代码:

UTF32Char inputChar = // input UTF32 value
inputChar = NSSwapHostIntToLittle(inputChar); // swap to little-endian if necessary
NSString *str = [[[NSString alloc] initWithBytes:&inputChar length:4 encoding:NSUTF32LittleEndianStringEncoding] autorelease];

要再次获取它,您可以使用

UTF32Char outputChar;
if ([str getBytes:&outputChar maxLength:4 usedLength:NULL encoding:NSUTF32LittleEndianStringEncoding options:0 range:NSMakeRange(0, 1) remainingRange:NULL]) {
    outputChar = NSSwapLittleIntToHost(outputChar); // swap back to host endian
    // outputChar now has the first UTF32 character
}

非常感谢您的回复!!!我不得不稍微修改您的最后一个例程,以使用rangeOfComposedCharacterSequenceAtIndex:的结果作为范围,因为有些单个字符实际上由两个unichars组成。在阅读了您的回复之后,我还发现了CFStringGetLongCharacterForSurrogatePair()和CFStringGetSurrogatePairForLongCharacter()这两个例程,它们似乎也很有用。然而,您上面的代码中有一个奇怪的结果,就是在您上面的示例中,initWithBytes:和stringWithCharacters:都对我的“大”utf32值返回了nil。 - Ron
@Ron:我实际上没有测试选项2,只测试了选项1。不过,我很惊讶你得到了nil的返回值。你测试的是什么值?请注意,UTF32只能到0x10FFFF,所以如果你尝试一个更高的值,它就不应该工作。我刚刚也遇到了CFStringGetSurrogatePairForLongCharacter,考虑重写我的答案来包括它,但听起来你已经知道如何使用它了。 - Lily Ballard
@Kevin:我非常确信我传入的代码是一个有效的Unicode值(它是我刚用你的代码将字符转换为Unicode得到的值,我也查过确认是有效的)。我看的一个值是0x26951。没有NSLog消息或其他任何东西...只是返回了nil。不管怎样,多亏了你的帮助,我的代码现在完美运行。非常感谢你抽出时间来回复! - Ron
@Ron:啊哈,这是字节序问题。NSUTF32StringEncoding没有声明它的字节序。在小端机器上(例如现代 Mac),您可以使用NSUTF32LittleEndianStringEncoding。您还可以使用NSSwapHostIntToLittle()来确保输入以小端格式呈现。 - Lily Ballard
@Kevin - 嗯...可能是字节序的问题。不过,NSUTF32StringEncoding似乎不应该是不一致的。我尝试了你上面提到的后两种解决方案之一,即使用NSUTF32StringEncoding将其转换为字节,然后立即尝试使用相同的编码进行转换回来。你会认为字节序应该是相同的。无论如何,我非常感谢你的答案,并且我很高兴现在我已经测试了所有我关心的字符的两种方式的解决方案。谢谢! - Ron
显示剩余3条评论

1

这里有两个问题:

1:

第一个问题是,无论是使用[NSString cStringUsingEncoding:]还是[NSString getCString:maxLength:encoding:],当使用NSUTF32StringEncodingNSUTF16StringEncoding时,返回的C字符串都是本机字节顺序(小端)而没有添加BOM

Unicode标准规定:(参见,“我应该如何处理BOM”)

"如果没有BOM,则应将文本解释为big-endian(大端)的格式。"

NSString的文档中也有相同的说明:(参见,“解释UTF-16编码的数据”)

"...如果未明确指定字节顺序,NSString会假定UTF-16字符使用big-endian(大端)字节顺序,除非存在BOM(字节顺序标记),在这种情况下,BOM决定了字节顺序。"

虽然它们提到的是UTF-16,但对于UTF-32也适用。

2:

第二个问题是[NSString stringWithCString:encoding:]在内部使用CFStringCreateWithCString来创建C字符串。这个问题在于CFStringCreateWithCString只接受使用8位编码的字符串。根据文档:(参见“参数”部分)

字符串必须使用8位编码。

解决这个问题的方法:

  1. 明确指定你想要使用的编码方式(NSString -> C字符串C字符串 -> NSString
  2. 当尝试从以UTF-32或UTF-16编码的C字符串创建NSString时,使用[NSString initWithBytes:length:encoding:]

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