103 votes

Décodage de caractères HTML en Objective-C / Cocoa Touch

Tout d'abord, j'ai trouvé ceci : Objectif C HTML escape/unescape mais cela ne fonctionne pas pour moi.

Mes caractères encodés (provenant d'un flux RSS, btw) ressemblent à ceci : &

J'ai cherché partout sur le net et j'ai trouvé des discussions à ce sujet, mais pas de solution pour mon encodage particulier, je crois qu'on les appelle des caractères hexadécimaux.

163voto

Michael Waterfall Points 10490

Jetez un coup d'œil à mon Catégorie NSString pour HTML . Voici les méthodes disponibles :

- (NSString *)stringByConvertingHTMLToPlainText;
- (NSString *)stringByDecodingHTMLEntities;
- (NSString *)stringByEncodingHTMLEntities;
- (NSString *)stringWithNewLinesAsBRs;
- (NSString *)stringByRemovingNewLinesAndWhitespace;

52voto

Walty Points 1147

Celui de Daniel est fondamentalement très bien, et j'y ai corrigé quelques problèmes :

  1. suppression du caractère de saut pour NSSCanner (sinon les espaces entre deux entités continues seraient ignorés)

    [scanner setCharactersToBeSkipped:nil] ;

  2. correction de l'analyse lorsqu'il y a des symboles '&' isolés (je ne suis pas sûr de ce qui est le résultat 'correct' pour cela, je l'ai juste comparé à firefox) :

par exemple

    &#ABC DF & B'  & C' Items (288)

Voici le code modifié :

- (NSString *)stringByDecodingXMLEntities {
    NSUInteger myLength = [self length];
    NSUInteger ampIndex = [self rangeOfString:@"&" options:NSLiteralSearch].location;

    // Short-circuit if there are no ampersands.
    if (ampIndex == NSNotFound) {
        return self;
    }
    // Make result string with some extra capacity.
    NSMutableString *result = [NSMutableString stringWithCapacity:(myLength * 1.25)];

    // First iteration doesn't need to scan to & since we did that already, but for code simplicity's sake we'll do it again with the scanner.
    NSScanner *scanner = [NSScanner scannerWithString:self];

    [scanner setCharactersToBeSkipped:nil];

    NSCharacterSet *boundaryCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@" \t\n\r;"];

    do {
        // Scan up to the next entity or the end of the string.
        NSString *nonEntityString;
        if ([scanner scanUpToString:@"&" intoString:&nonEntityString]) {
            [result appendString:nonEntityString];
        }
        if ([scanner isAtEnd]) {
            goto finish;
        }
        // Scan either a HTML or numeric character entity reference.
        if ([scanner scanString:@"&" intoString:NULL])
            [result appendString:@"&"];
        else if ([scanner scanString:@"'" intoString:NULL])
            [result appendString:@"'"];
        else if ([scanner scanString:@""" intoString:NULL])
            [result appendString:@"\""];
        else if ([scanner scanString:@"<" intoString:NULL])
            [result appendString:@"<"];
        else if ([scanner scanString:@"&gt;" intoString:NULL])
            [result appendString:@">"];
        else if ([scanner scanString:@"&#" intoString:NULL]) {
            BOOL gotNumber;
            unsigned charCode;
            NSString *xForHex = @"";

            // Is it hex or decimal?
            if ([scanner scanString:@"x" intoString:&xForHex]) {
                gotNumber = [scanner scanHexInt:&charCode];
            }
            else {
                gotNumber = [scanner scanInt:(int*)&charCode];
            }

            if (gotNumber) {
                [result appendFormat:@"%C", (unichar)charCode];

                [scanner scanString:@";" intoString:NULL];
            }
            else {
                NSString *unknownEntity = @"";

                [scanner scanUpToCharactersFromSet:boundaryCharacterSet intoString:&unknownEntity];

                [result appendFormat:@"&#%@%@", xForHex, unknownEntity];

                //[scanner scanUpToString:@";" intoString:&unknownEntity];
                //[result appendFormat:@"&#%@%@;", xForHex, unknownEntity];
                NSLog(@"Expected numeric character entity but got &#%@%@;", xForHex, unknownEntity);

            }

        }
        else {
            NSString *amp;

            [scanner scanString:@"&" intoString:&amp];  //an isolated & symbol
            [result appendString:amp];

            /*
            NSString *unknownEntity = @"";
            [scanner scanUpToString:@";" intoString:&unknownEntity];
            NSString *semicolon = @"";
            [scanner scanString:@";" intoString:&semicolon];
            [result appendFormat:@"%@%@", unknownEntity, semicolon];
            NSLog(@"Unsupported XML character entity %@%@", unknownEntity, semicolon);
             */
        }

    }
    while (![scanner isAtEnd]);

finish:
    return result;
}

46voto

Matt Bridges Points 14547

Ceux-ci sont appelés Références de l'entité personnage . Lorsqu'ils prennent la forme de &#<number>; ils sont appelés les références numériques des entités . En fait, il s'agit d'une représentation sous forme de chaîne de caractères de l'octet à remplacer. Dans le cas de &#038; Il représente le caractère ayant la valeur 38 dans le système de codage des caractères ISO-8859-1, c'est-à-dire & .

La raison pour laquelle l'esperluette doit être encodée dans RSS est qu'il s'agit d'un caractère spécial réservé.

Ce qu'il faut faire, c'est analyser la chaîne et remplacer les entités par un octet correspondant à la valeur entre &# y ; . Je n'ai pas connaissance d'un moyen efficace de faire cela en Objective C, mais cette question de stack overflow pourrait être utile.

Edit : Depuis que j'ai répondu à cette question il y a environ deux ans, il y a d'excellentes solutions ; voir la réponse de @Michael Waterfall ci-dessous.

46voto

Bryan Luby Points 1186

Depuis iOS 7, vous pouvez décoder les caractères HTML de manière native en utilisant un fichier NSAttributedString avec le NSHTMLTextDocumentType attribut :

NSString *htmlString = @"&#63743; &amp; &#38; &lt; &gt; &trade; &copy; &hearts; &clubs; &spades; &diams;";
NSData *stringData = [htmlString dataUsingEncoding:NSUTF8StringEncoding];

NSDictionary *options = @{NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType};
NSAttributedString *decodedString;
decodedString = [[NSAttributedString alloc] initWithData:stringData
                                                 options:options
                                      documentAttributes:NULL
                                                   error:NULL];

La chaîne attribuée décodée s'affiche désormais comme suit :  & & < > ™ © ♥ ♣ ♠ ♦.

Remarque : Cela ne fonctionnera que si l'appel se fait sur le thread principal.

35voto

Nikita Rybak Points 36641

Personne ne semble mentionner l'une des options les plus simples : Boîte à outils Google pour Mac
(Malgré le nom, cela fonctionne aussi sur iOS).

https://github.com/google/google-toolbox-for-mac/blob/master/Foundation/GTMNSString%2BHTML.h

/// Get a string where internal characters that are escaped for HTML are unescaped 
//
///  For example, '&amp;' becomes '&'
///  Handles &#32; and &#x32; cases as well
///
//  Returns:
//    Autoreleased NSString
//
- (NSString *)gtm_stringByUnescapingFromHTML;

Et je n'ai dû inclure que trois fichiers dans le projet : l'en-tête, l'implémentation et le fichier GTMDefines.h .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X