2010-02-12 51 views
5

我從XML源獲取數據並使用tbxml解析數據。 代碼:來自HTML的NSString中的特殊字符

é 

我沒有看到的NSString的適當的方法來進行轉換,直到我得到像「E」它將作爲顯示拉丁字母一切工作正常。有任何想法嗎?

回答

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.

替換塊中間的「三行」確保UTF-32字符的正確轉換爲>0xFFFF。爲了完整性和正確性,我把它放在了這裏。無效的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) { 

你的問題的另一個答案指出你this Stack Overflow Question with an Answer。這個解決方案和該溶液(基於無非是更快速過一遍)之間的差異:

該解決方案:

  • 需要一個外部庫(RegexKitLite)。
  • 使用塊來執行其工作,這是「無處不在」可用。雖然有Plausible Blocks,它可以讓你在Mac OS X 10.5和iPhone OS 2.2+上使用Blocks(我認爲)。他們將10.6 gcc模塊的變更反饋給了他們。

另一種解決方案:

  • 使用標準的基礎類,作品無處不在。
  • 正確處理一些UTF-32字符代碼點(在實踐中可能不是問題)。
  • 處理幾個常見的命名字符實體,如&gt;。雖然這可以很容易地添加到上面。

我沒有任何基準測試的解決方案,但我願意打賭的鉅款,使用RKLRegexEnumerationFastCapturedStringsXXX的RegexKitLite解決方案擊敗褲子脫了NSScanner解決方案。

如果你真的想添加命名字符實體,可以將正則表達式更改爲類似:

NSString *regex = @"&(?:#(?:([0-9]+)|x([0-9a-fA-F]+))|([a-zA-Z][a-zA-Z0-9]+));"; 

注:我沒有測試過上述所有。

捕獲#3應該包含「字符實體名稱」,然後您可以使用它查看。一個非常奇特的做法是將一個NSDictionary包含一個命名字符作爲key和一個NSStringobject包含該名稱映射到的字符。你甚至可以保持整個事情作爲外部.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;匹配文本(即,「什麼都不做」)。