2010-02-12 7 views
5

Je récupère des données à partir d'une source XML et les analyse avec tbxml. Tout fonctionne bien jusqu'à ce que j'arrive à une lettre latine comme le « é », il affiche comme: code:Caractères spéciaux dans NSString à partir de HTML

é 

Je ne vois pas une bonne méthode de NSString pour effectuer la conversion. Des idées?

Répondre

4

Vous pouvez utiliser une regex. Une regex est une solution à, et cause de, tous les problèmes! :)

L'exemple ci-dessous utilise, au moins pour l'instant, le RegexKitLite 4.0 inédit. Vous pouvez obtenir l'instantané 4.0 de développement via svn:

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

Les exemples ci-dessous tirent parti des nouveaux blocs 4.0 fonctionnalité pour faire une recherche et remplacer des entités de caractère é.

Ce premier exemple est le "plus simple" des deux. Il seulement gère les entités de caractères décimales comme é et non des entités de caractères hexadécimaux comme é. Si vous ne pouvez garantir que vous aurez jamais des entités de caractères hexadécimaux, cela devrait être bien:

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

Compile et courir avec:

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

Le caractère 0x1d4000 pourrait ne pas apparaître dans votre navigateur, mais il ressemble à un A gras dans une fenêtre de terminal. Les «trois lignes» au milieu du bloc de remplacement assurent la conversion correcte des caractères UTF-32 qui sont>0xFFFF. Je l'ai mis pour l'intégralité et l'exactitude. Les valeurs de caractères UTF-32 non valides (0xd800 - 0xdfff) sont remplacées par U+FFFD ou REPLACEMENT CHARACTER. Si vous pouvez "garantir" que vous n'aurez jamais &#...; entités de caractères qui sont>0xFFFF (ou 65535), et sont toujours "légaux" UTF-32, alors vous pouvez supprimer ces lignes et simplifier tout le bloc vers quelque chose comme:

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

Le deuxième exemple fait les deux entités de caractère décimal et hexadécimal:

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

Encore une fois, compiler et exécuter avec:

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

Notez la différence dans la sortie par rapport à la première: Le premier avait encore &#xe9;, et dans celui-ci, il est remplacé. Encore une fois, c'est un peu long, mais je choisis d'aller pour l'exhaustivité et la correction.

Les deux exemples peuvent avoir la méthode stringByReplacingOccurrencesOfRegex: remplacée par la suivante pour "vitesse supplémentaire", mais vous devriez vous référer à la documentation pour voir les mises en garde de l'utilisation RKLRegexEnumerationFastCapturedStringsXXX.Il est important de noter que l'utiliser dans ce qui précède n'est pas un problème et parfaitement sûr (et l'une des raisons pour lesquelles j'ai ajouté l'option à 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) { 

Une autre réponse à votre question, vous pointé this Stack Overflow Question with an Answer. Les différences entre cette solution et cette solution (basée sur rien de plus qu'un rapide une fois plus):

Cette solution:

  • Nécessite une bibliothèque externe (RegexKitLite).
  • Utilise des blocs pour effectuer son travail, qui n'est pas encore disponible "partout". Bien qu'il existe Plausible Blocks, ce qui vous permet d'utiliser des blocs sur Mac OS X 10.5 et IPhone OS 2.2+ (je pense). Ils ont rétroporté les changements de blocs de 10.6 gcc et les ont rendus disponibles.

L'autre solution:

  • utilise des classes standards Fondation, fonctionne partout.
  • Un peu moins correct de gérer certains UTF-32 points de code de caractères (probablement pas un problème en pratique).
  • Gère un couple d'entités de caractères nommées communes telles que &gt;. Cela peut être ajouté facilement à ce qui précède, cependant.

Je n'ai pas benchmarkée soit la solution, mais je serais prêt à parier de grosses sommes d'argent que la solution à l'aide RegexKitLite RKLRegexEnumerationFastCapturedStringsXXX bat le pantalon large de la solution NSScanner.

Et si vous voulez vraiment ajouter des entités de caractères nommées, vous pouvez changer la regex à quelque chose comme:

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

Note: Je ne l'ai pas testé ci-dessus du tout. La capture n ° 3 doit contenir "le nom de l'entité", que vous pouvez ensuite utiliser pour faire une recherche. Une façon vraiment fantaisiste de le faire serait d'avoir un NSDictionary qui contient un personnage nommé key et un NSStringobject contenant le caractère que ce nom correspond à. Vous pouvez même garder la chose comme une ressource .plist externe et charger paresseusement sur demande avec quelque chose comme:

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

Vous souhaitez évidemment le tordre à utiliser NSBundle pour obtenir un chemin à votre applications répertoire de ressources, mais vous avoir cette idée. Ensuite, vous souhaitez ajouter une autre vérification de l'état dans le bloc:

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

Si le personnage nommé est dans le dictionnaire, il le remplacera. Sinon, il renvoie le texte complet &notfound; (c'est-à-dire "ne fait rien").

Questions connexes