NSString中的HTML特殊字符

6
我正在从XML来源获取数据并使用tbxml解析。一切正常,直到我遇到像“é”这样的拉丁字母时,它将显示为: Code:
é

我看不到NSString的合适方法来进行转换。有什么想法吗?
2个回答

4
你可以使用正则表达式。正则表达式是解决所有问题和导致所有问题的方案!:)
以下示例至少在本文撰写时使用未发布的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: &#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。

替换块中间的“三条线”确保正确转换大于0xFFFFUTF-32字符。我加上这个是为了完整性和正确性。无效的UTF-32字符值(0xd800-0xdfff)会被转换成U+FFFDREPLACEMENT 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: &#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) {

您好,以下是您需要翻译的内容:

另一个回答您的问题指向了这个Stack Overflow帖子和答案。这种解决方案与那种解决方案的不同(只是快速浏览):

这个解决方案:

  • 需要外部库(RegexKitLite)。
  • 使用块执行其任务,在“每个地方”都不可用。但是有Plausible Blocks,可以在Mac OS X 10.5和iPhone OS 2.2+(我想)上使用块。他们回溯了10.6 gcc块更改并使它们可用。

另一个解决方案:

  • 使用标准Foundation类,在任何地方都有效。
  • 在处理一些UTF-32字符代码点时略微不正确(实际上可能不是问题)。
  • 处理一些常见的命名字符实体,例如&gt;。这很容易添加到上面。

我还没有对任何解决方案进行基准测试,但我愿意打赌大量资金,使用RKLRegexEnumerationFastCapturedStringsXXX的RegexKitLite解决方案比NSScanner解决方案更胜一筹。

如果您真的想添加命名字符实体,您可以将正则表达式更改为以下内容:

NSString *regex = @"&(?:#(?:([0-9]+)|x([0-9a-fA-F]+))|([a-zA-Z][a-zA-Z0-9]+));";
注意: 我完全没有测试上述内容。
第三个捕获应该包含“字符实体名称”,然后您可以使用它进行查找。一种非常高级的方法是拥有一个NSDictionary,其中包含命名字符作为key和包含该名称映射到的字符的NSStringobject。您甚至可以将整个内容保存为外部.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;匹配文本(即“不做任何事情”)。

3

谢谢!我在这里搜索了,但没找到。 - adamweeks

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