2010-02-12 16 views
5

Estoy obteniendo datos de una fuente XML y analizándolos con tbxml. Todo está funcionando bien hasta que llegue a una letra latina como el "E", se mostrará como: Código:Caracteres especiales en NSString de HTML

é 

no veo un método adecuado de NSString para hacer la conversión. ¿Algunas ideas?

Respuesta

4

Puede usar un regex. ¡Una expresión regular es una solución y causa de todos los problemas! :)

El ejemplo siguiente utiliza, al menos hasta este momento, el RegexKitLite 4.0 no publicado. Usted puede obtener la instantánea 4.0 desarrollo a través de SVN:

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

Los ejemplos siguientes se aprovechan de las nuevas 4.0 Bloques de función Para hacer una búsqueda y reemplazo de los é entidades de caracteres.

Este primer ejemplo es el "más simple" de los dos. Es solo maneja entidades de caracteres decimales como é y entidades de caracteres no hexadecimales como é. Si se puede garantizar que nunca tendrá las entidades de caracteres hexadecimales, esto debe estar 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); 
} 

Compilar y correr con:

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

El carácter 0x1d4000 podrían no aparecer en el navegador, pero parece una A en negrita en una ventana de terminal.

Las "tres líneas" en el medio del bloque de reemplazo garantizan la conversión correcta de UTF-32 caracteres que son>0xFFFF. Puse esto en completo y correcto. Los valores de caracteres UTF-32 no válidos (0xd800 - 0xdfff) se activan en U+FFFD o REPLACEMENT CHARACTER. Si se puede "garantizar" que usted nunca tendrá que &#...; entidades de caracteres que son>0xFFFF (o 65535), y está siempre "legales" UTF-32, a continuación, puede eliminar esas líneas y simplificar todo el bloque hacia abajo a algo como:

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

El segundo ejemplo hace dos entidades de caracteres decimales y hexadecimales:

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

Una vez más, compilar y ejecutar con:

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

Tenga en cuenta la diferencia en la salida en comparación con la primera: la primera todavía tenía &#xe9; en ella, y en ésta se reemplaza. De nuevo, es un poco largo, pero elijo ir por lo completo y correcto.

Ambos ejemplos pueden reemplazar el método stringByReplacingOccurrencesOfRegex: por "velocidad extra", pero debe consultar la documentación para ver las advertencias sobre el uso de RKLRegexEnumerationFastCapturedStringsXXX.Es importante tener en cuenta que usarlo en lo anterior no es un problema y es perfectamente seguro (y una de las razones por las que agregué la opción a 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) { 

Otra respuesta a su pregunta que apuntaban a this Stack Overflow Question with an Answer. Las diferencias entre esta solución y esa solución (basado en nada más que un rápido una vez más):

Esta solución:

  • Requiere una biblioteca externa (RegexKitLite).
  • Usa bloques para realizar su trabajo, que aún no está disponible "en todas partes". Aunque hay Plausible Blocks, que le permite usar Blocks en Mac OS X 10.5 y en el sistema operativo para iPhone 2.2+ (creo). Copiaron los cambios de 10.6 gcc Blocks y los pusieron a disposición.

La otra solución:

  • utiliza clases estándar de la Fundación, funciona en todas partes.
  • Un poco menos correcto en el manejo de algunos puntos de código de carácter UTF-32 (probablemente no sea un problema en la práctica).
  • Maneja un par de entidades de caracteres nombrados comunes como &gt;. Sin embargo, esto se puede agregar fácilmente a lo anterior.

no he referenciado cualquiera de las soluciones, pero estaría dispuesto a apostar grandes sumas de dinero que la solución RegexKitLite usando RKLRegexEnumerationFastCapturedStringsXXX supera los pantalones la solución NSScanner.

Y si realmente quiere añadir entidades de caracteres con nombre, usted podría cambiar la expresión regular a algo como:

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

Nota: no he probado lo anterior en absoluto.

Capture # 3 debe contener "el nombre de la entidad del personaje", que luego puede usar para buscar. Una forma realmente elegante de hacer esto sería tener un NSDictionary que contenga un personaje con nombre como key y NSStringobject que contenga el carácter al que ese nombre se asigna. Incluso se puede mantener todo el asunto como un .plist recurso externo y cargarlo con pereza a la carta con algo como:

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

Es obvio que tendría que ajustar a utilizar NSBundle para obtener una ruta de acceso al directorio de aplicaciones de recursos, pero obtener esta idea A continuación, deberá añadir otro cheque condición en el bloque:

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

Si el personaje llamado está en el diccionario, lo reemplazará. De lo contrario, devuelve el texto completo &notfound; coincidente (es decir, "no hace nada").