来自HTML的NSString中的特殊字符

时间:2010-02-12 20:20:47

标签: iphone xml nsstring

我从XML源获取数据并使用tbxml解析它。一切都工作正常,直到我得到像“é”这样的拉丁字母,它将显示为: 代码:

é

我没有看到NSString进行转换的正确方法。有什么想法吗?

2 个答案:

答案 0 :(得分:4)

您可以使用正则表达式。正则表达式是所有问题的解决方案和原因! :)

下面的示例至少在撰写本文时使用了未发布的RegexKitLite 4.0。您可以通过svn获取4.0开发快照:

shell% svn co http://regexkit.svn.sourceforge.net/svnroot/regexkit regexkit

以下示例利用新的4.0 Blocks功能来搜索和替换é个字符实体。

第一个例子是两者中的“更简单”。它处理像é这样的十进制字符实体,而不处理像é这样的十六进制字符实体。如果你可以保证你永远不会有十六进制字符实体,那么这应该没问题:

#import <Foundation/Foundation.h>
#import "RegexKitLite.h"

int main(int argc, char *charv[]) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  NSString *string = @"A test: &#233; and &#xe9; ? YAY! Even >0xffff are handled: &#119808; or &#x1D400;, 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 &#xe9; ? YAY! Even >0xffff are handled:  or &#x1D400;, see? (0x1d400 == MATHEMATICAL BOLD CAPITAL A)'

0x1d4000字符可能不会显示在您的浏览器中,但它在终端窗口中看起来像一个粗体A.

替换块中间的“三行”确保正确转换UTF-32字符&gt; 0xFFFF。为了完整性和正确性,我把它放进去了。无效的UTF-32字符值(0xd800 - 0xdfff)会被转为U+FFFDREPLACEMENT CHARACTER。如果你能“保证”你永远不会有&#...;个字符实体,那么&gt; 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: &#233; and &#xe9; ? YAY! Even >0xffff are handled: &#119808; or &#x1D400;, 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。此解决方案与该解决方案之间的差异(仅基于快速一次):

此解决方案:

  • 需要外部库(RegexKitLite)。
  • 使用Blocks来执行其工作,但“无处不在”。虽然有Plausible Blocks,但它允许你在Mac OS X 10.5和IPhone OS 2.2+上使用Blocks(我认为)。他们向后移动了10.6 gcc Blocks的更改,并将其提供给他们。

另一种解决方案:

  • 使用标准的Foundation类,无处不在。
  • 在处理一些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来获取应用资源目录的路径,但您明白了这一点。然后你将在Block中添加另一个条件检查:

if(capturedRanges[3].location != NSNotFound) {
  NSString *namedCharacter = [namedCharactersDictionary objectForKey:capturedStrings[3]];
  return((namedCharacter == NULL) ? capturedStrings[0] : namedCharacter);
}

如果命名字符在字典中,它将替换它。否则返回完整的&notfound;匹配文本(即“什么都不做”)。

答案 1 :(得分:3)

这似乎是一个非常常见的问题。查看HTML character decoding in Objective-C / Cocoa Touch