2010-02-12 42 views
5

Tôi đang tìm nạp dữ liệu từ một nguồn XML và phân tích cú pháp thông qua nó bằng tbxml. Tất cả mọi thứ đang làm việc tốt cho đến khi tôi nhận được một bức thư Latin như "e" nó sẽ hiển thị như sau: Code:Các ký tự đặc biệt trong NSString từ HTML

é 

Tôi không thấy một phương pháp thích hợp của NSString để làm việc chuyển đổi. Bất kỳ ý tưởng?

Trả lời

4

Bạn có thể sử dụng regex. Một regex là một giải pháp cho, và nguyên nhân của, tất cả các vấn đề! :)

Ví dụ dưới đây sử dụng, ít nhất là bằng văn bản này, RegexKitLite 4.0 chưa được phát hành. Bạn có thể lấy ảnh chụp 4,0 phát triển qua svn:

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

Các ví dụ dưới đây tận dụng lợi thế của 4,0 Blocks tính năng mới để thực hiện tìm kiếm và thay thế của é đơn vị nhân vật.

Ví dụ đầu tiên này là "đơn giản hơn" của cả hai. Chỉ chỉ xử lý các đối tượng ký tự thập phân như é và không phải là đối tượng ký tự thập lục phân như é. Nếu bạn có thể đảm bảo rằng bạn sẽ không bao giờ có thực thể vật thập lục phân, điều này nên được tốt:

#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); 
} 

Biên dịch và chạy với:

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)' 

Nhân vật 0x1d4000 có thể không hiển thị trong trình duyệt của bạn, nhưng nó trông giống như chữ A đậm trong cửa sổ đầu cuối.

"Ba dòng" ở giữa khối thay thế đảm bảo chuyển đổi chính xác UTF-32 ký tự>0xFFFF. Tôi đặt cái này vào vì đầy đủ và đúng đắn. Giá trị ký tự UTF-32 không hợp lệ (0xd800 - 0xdfff) được chuyển thành U+FFFD hoặc REPLACEMENT CHARACTER. Nếu bạn có thể "đảm bảo" rằng bạn sẽ không bao giờ có các đối tượng nhân vật &#...;>0xFFFF (hoặc 65535) và luôn "hợp pháp" UTF-32, thì bạn có thể xóa các dòng đó và đơn giản hóa toàn bộ khối thành một cái gì đó như:

return([NSString stringWithFormat:@"%C", [capturedStrings[1] integerValue]]); 

Ví dụ thứ hai hiện cả hai chữ số thập phân và thập lục phân tổ chức nhân vật:

#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); 
} 

Một lần nữa, biên dịch và chạy với:

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)' 

Lưu ý sự khác biệt trong đầu ra so với đầu tiên: Đầu tiên vẫn có &#xe9; trong đó, và trong phần này nó được thay thế. Một lần nữa, đó là một chút khao khát, nhưng tôi chọn để đi cho đầy đủ và chính xác.

Cả hai ví dụ có thể có phương pháp stringByReplacingOccurrencesOfRegex: được thay thế bằng "tốc độ bổ sung" sau, nhưng bạn nên tham khảo tài liệu để xem các cảnh báo sử dụng RKLRegexEnumerationFastCapturedStringsXXX.Điều quan trọng cần lưu ý là việc sử dụng nó ở trên không phải là vấn đề và hoàn toàn an toàn (và một trong những lý do tại sao tôi đã thêm tùy chọn vào 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) { 

Một câu trả lời khác cho câu hỏi của bạn đã chỉ cho bạn this Stack Overflow Question with an Answer. Sự khác nhau giữa giải pháp này và rằng giải pháp (dựa trên không có gì hơn một cách nhanh chóng một lần trở lên):

Giải pháp này:

  • Yêu cầu một thư viện bên ngoài (RegexKitLite).
  • Sử dụng các khối để thực hiện tác phẩm của nó, hiện chưa có "ở mọi nơi". Mặc dù có Plausible Blocks, cho phép bạn sử dụng Chặn trên Mac OS X 10.5 và IPhone OS 2.2+ (tôi nghĩ). Họ quay trở lại các thay đổi Blocks 10.6 gcc và làm cho chúng có sẵn.

Các giải pháp khác:

  • Sử dụng lớp Foundation tiêu chuẩn, hoạt động ở khắp mọi nơi.
  • Ít chính xác hơn trong việc xử lý một số điểm mã ký tự UTF-32 (có thể không phải là vấn đề trong thực tế).
  • Xử lý một vài thực thể ký tự được đặt tên phổ biến như &gt;. Tuy nhiên, điều này có thể dễ dàng được thêm vào ở trên.

Tôi chưa đánh giá được giải pháp, nhưng tôi sẵn sàng đặt cược số tiền lớn mà giải pháp RegexKitLite sử dụng RKLRegexEnumerationFastCapturedStringsXXX đánh bại quần khỏi giải pháp NSScanner.

Và nếu bạn thực sự muốn thêm đối tượng nhân vật được đặt tên, bạn có thể thay đổi regex để cái gì đó như:

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

Lưu ý: tôi đã không kiểm tra trên cả.

Chụp # 3 phải chứa "tên đối tượng ký tự", sau đó bạn có thể sử dụng để tìm kiếm. Một cách thực sự thú vị để thực hiện việc này là có một số NSDictionary chứa ký tự có tên là key và một số NSStringobject chứa ký tự mà tên đó ánh xạ tới. Bạn thậm chí có thể giữ toàn bộ điều như một .plist nguồn lực bên ngoài và uể oải tải nó theo yêu cầu với một cái gì đó như:

NSDictionary *namedCharactersDictionary = [NSDictionary dictionaryWithContentsOfFile:@"namedCharacters.plist"]; 

Bạn muốn rõ ràng tinh chỉnh nó để sử dụng NSBundle để có được một đường dẫn đến thư mục tài nguyên ứng dụng của bạn, nhưng bạn hiểu ý tưởng này Sau đó, bạn sẽ thêm một kiểm tra điều kiện khác trong Chặn:

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

Nếu ký tự được đặt tên trong từ điển, ký tự đó sẽ thay thế. Nếu không, nó sẽ trả về toàn bộ văn bản phù hợp &notfound; (nghĩa là "không có gì").

Các vấn đề liên quan