【问题标题】:Special Characters in NSString from HTML来自 HTML 的 NSString 中的特殊字符
【发布时间】:2011-01-16 07:50:04
【问题描述】:

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

é

我没有看到合适的 NSString 方法来进行转换。有什么想法吗?

【问题讨论】:

    标签: iphone xml nsstring


    【解决方案1】:

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

    至少在撰写本文时,以下示例使用未发布的 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)的 &amp;#...; 字符实体,并且始终是“合法的”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)'
    

    注意输出与第一个相比的差异:第一个仍然有&amp;#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 字符代码点时不太正确(实际上可能不是问题)。
    • 处理几个常见的命名字符实体,例如&amp;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);
    }
    

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 谢谢!我在这里搜索过,但找不到。
      猜你喜欢
      • 2011-11-26
      • 1970-01-01
      • 2014-04-25
      • 1970-01-01
      • 2012-05-14
      • 2011-12-28
      • 2012-10-30
      • 1970-01-01
      相关资源
      最近更新 更多