【发布时间】:2011-01-16 07:50:04
【问题描述】:
我正在从 XML 源中获取数据并使用 tbxml 对其进行解析。一切正常,直到我得到一个像“é”这样的拉丁字母,它将显示为: 代码:
é
我没有看到合适的 NSString 方法来进行转换。有什么想法吗?
【问题讨论】:
我正在从 XML 源中获取数据并使用 tbxml 对其进行解析。一切正常,直到我得到一个像“é”这样的拉丁字母,它将显示为: 代码:
é
我没有看到合适的 NSString 方法来进行转换。有什么想法吗?
【问题讨论】:
您可以使用正则表达式。正则表达式是所有问题的解决方案和原因! :)
至少在撰写本文时,以下示例使用未发布的 RegexKitLite 4.0。可以通过svn获取4.0开发快照:
shell% svn co http://regexkit.svn.sourceforge.net/svnroot/regexkit regexkit
以下示例利用新的 4.0 块功能来搜索和替换 é 字符实体。
第一个示例是两者中“更简单”的一个。它仅处理像é 这样的十进制字符实体,而不像é 这样的十六进制字符实体。如果你能保证你永远不会有十六进制字符实体,这应该没问题:
#import <Foundation/Foundation.h>
#import "RegexKitLite.h"
int main(int argc, char *charv[]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *string = @"A test: é and é ? YAY! Even >0xffff are handled: 𝐀 or 𝐀, see? (0x1d400 == MATHEMATICAL BOLD CAPITAL A)";
NSString *regex = @"&#([0-9]+);";
NSString *replacedString = [string stringByReplacingOccurrencesOfRegex:regex usingBlock:^NSString *(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop) {
NSUInteger u16Length = 0UL, u32_ch = [capturedStrings[1] integerValue];
UniChar u16Buffer[3];
if (u32_ch <= 0xFFFFU) { u16Buffer[u16Length++] = ((u32_ch >= 0xD800U) && (u32_ch <= 0xDFFFU)) ? 0xFFFDU : u32_ch; }
else if (u32_ch > 0x10FFFFU) { u16Buffer[u16Length++] = 0xFFFDU; }
else { u32_ch -= 0x0010000UL; u16Buffer[u16Length++] = ((u32_ch >> 10) + 0xD800U); u16Buffer[u16Length++] = ((u32_ch & 0x3FFUL) + 0xDC00U); }
return([NSString stringWithCharacters:u16Buffer length:u16Length]);
}];
NSLog(@"replaced: '%@'", replacedString);
return(0);
}
编译并运行:
shell% gcc -arch i386 -g -o charReplace charReplace.m RegexKitLite.m -framework Foundation -licucore
shell% ./charReplace
2010-02-13 22:51:48.909 charReplace[35527:903] replaced: 'A test: é and é ? YAY! Even >0xffff are handled: ? or 𝐀, see? (0x1d400 == MATHEMATICAL BOLD CAPITAL A)'
0x1d4000 字符可能不会显示在您的浏览器中,但它在终端窗口中看起来像一个粗体 A。
替换块中间的“三行”确保正确转换> 0xFFFF 的UTF-32 字符。为了完整性和正确性,我将其放入。无效的 UTF-32 字符值 (0xd800 - 0xdfff) 被转入 U+FFFD 或 REPLACEMENT CHARACTER。如果您可以“保证”您永远不会拥有 > 0xFFFF(或 65535)的 &#...; 字符实体,并且始终是“合法的”UTF-32,那么您可以删除这些行并简化整体阻止类似:
return([NSString stringWithFormat:@"%C", [capturedStrings[1] integerValue]]);
第二个例子同时处理十进制和十六进制字符实体:
#import <Foundation/Foundation.h>
#import "RegexKitLite.h"
int main(int argc, char *charv[]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *string = @"A test: é and é ? YAY! Even >0xffff are handled: 𝐀 or 𝐀, see? (0x1d400 == MATHEMATICAL BOLD CAPITAL A)";
NSString *regex = @"&#(?:([0-9]+)|x([0-9a-fA-F]+));";
NSString *replacedString = [string stringByReplacingOccurrencesOfRegex:regex usingBlock:^NSString *(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop) {
NSUInteger u16Length = 0UL, u32_ch = 0UL;
UniChar u16Buffer[3];
CFStringRef cfSelf = (capturedRanges[1].location != NSNotFound) ? (CFStringRef)capturedStrings[1] : (CFStringRef)capturedStrings[2];
UInt8 buffer[64];
const char *cptr;
if((cptr = CFStringGetCStringPtr(cfSelf, kCFStringEncodingMacRoman)) == NULL) {
CFRange range = CFRangeMake(0L, CFStringGetLength(cfSelf));
CFIndex usedBytes = 0L;
CFStringGetBytes(cfSelf, range, kCFStringEncodingUTF8, '?', false, buffer, 60L, &usedBytes);
buffer[usedBytes] = 0;
cptr = (const char *)buffer;
}
u32_ch = strtoul(cptr, NULL, (capturedRanges[1].location != NSNotFound) ? 10 : 16);
if (u32_ch <= 0xFFFFU) { u16Buffer[u16Length++] = ((u32_ch >= 0xD800U) && (u32_ch <= 0xDFFFU)) ? 0xFFFDU : u32_ch; }
else if (u32_ch > 0x10FFFFU) { u16Buffer[u16Length++] = 0xFFFDU; }
else { u32_ch -= 0x0010000UL; u16Buffer[u16Length++] = ((u32_ch >> 10) + 0xD800U); u16Buffer[u16Length++] = ((u32_ch & 0x3FFUL) + 0xDC00U); }
return([NSString stringWithCharacters:u16Buffer length:u16Length]);
}];
NSLog(@"replaced: '%@'", replacedString);
return(0);
}
再次,编译并运行:
shell% gcc -arch i386 -g -o charReplace charReplace.m RegexKitLite.m -framework Foundation -licucore
shell% ./charReplace
2010-02-13 22:52:02.182 charReplace[35540:903] replaced: 'A test: é and é ? YAY! Even >0xffff are handled: ? or ?, see? (0x1d400 == MATHEMATICAL BOLD CAPITAL A)'
注意输出与第一个相比的差异:第一个仍然有&#xe9;,而在这个中它被替换了。同样,它有点冗长,但我选择追求完整性和正确性。
两个示例都可以将 stringByReplacingOccurrencesOfRegex: 方法替换为以下“额外速度”,但您应该参考文档以查看使用 RKLRegexEnumerationFastCapturedStringsXXX 的注意事项。需要注意的是,在上面使用它不是问题并且非常安全(也是我向 RegexKitLite 添加选项的原因之一)。
NSString *replacedString = [string stringByReplacingOccurrencesOfRegex:regex options:RKLNoOptions inRange:NSMakeRange(0UL, [string length]) error:NULL enumerationOptions:RKLRegexEnumerationFastCapturedStringsXXX usingBlock:^NSString *(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop) {
您的问题的另一个答案将您指向this Stack Overflow Question with an Answer。此解决方案与该解决方案之间的差异(仅基于快速浏览):
这个解决方案:
另一种解决方案:
UTF-32 字符代码点时不太正确(实际上可能不是问题)。&gt;。不过,这可以很容易地添加到上述内容中。我没有对这两种解决方案进行基准测试,但我愿意打赌,使用 RKLRegexEnumerationFastCapturedStringsXXX 的 RegexKitLite 解决方案胜过NSScanner 解决方案。
如果您真的想添加命名字符实体,您可以将正则表达式更改为:
NSString *regex = @"&(?:#(?:([0-9]+)|x([0-9a-fA-F]+))|([a-zA-Z][a-zA-Z0-9]+));";
注意:以上我都没有测试过。
Capture #3 应包含“角色实体名称”,然后您可以使用它进行查找。一个非常好的方法是有一个 NSDictionary 包含一个命名字符作为 key 和一个 NSString object 包含该名称映射到的字符。您甚至可以将整个内容保留为外部 .plist 资源,并通过以下方式按需加载:
NSDictionary *namedCharactersDictionary = [NSDictionary dictionaryWithContentsOfFile:@"namedCharacters.plist"];
您显然会对其进行调整以使用NSBundle 来获取您的应用程序资源目录的路径,但您明白了这个想法。然后你会在块中添加另一个条件检查:
if(capturedRanges[3].location != NSNotFound) {
NSString *namedCharacter = [namedCharactersDictionary objectForKey:capturedStrings[3]];
return((namedCharacter == NULL) ? capturedStrings[0] : namedCharacter);
}
如果命名字符在字典中,它将替换它。否则,它会返回完整的 &notfound; 匹配文本(即“什么都不做”)。
【讨论】:
这似乎是一个很常见的问题。查看HTML character decoding in Objective-C / Cocoa Touch
【讨论】: