Decodificación de caracteres HTML en Objective-C / Cocoa Touch

103

En primer lugar, encontré esto: Objective C HTML escape / unescape , pero no funciona para mí.

Mis caracteres codificados (provienen de una fuente RSS, por cierto) se ven así: &

Busqué en toda la red y encontré discusiones relacionadas, pero no hay solución para mi codificación particular, creo que se llaman caracteres hexadecimales.

treznik
fuente
3
Este comentario es seis meses después de la pregunta original, por lo que es más para aquellos que se encuentran con esta pregunta en busca de una respuesta y una solución. Recientemente surgió una pregunta muy similar que respondí stackoverflow.com/questions/2254862/… Utiliza RegexKitLite y Blocks para hacer una búsqueda y reemplazar el &#...;en una cadena con su carácter equivalente.
johne
¿Qué específicamente "no funciona"? No veo nada en esta pregunta que no sea un duplicado de la pregunta anterior.
Peter Hosey
Es decimal. Hexadecimal es 8.
kennytm
La diferencia entre decimal y hexadecimal es que el decimal es base-10, mientras que el hexadecimal es base-16. “38” es un número diferente en cada base; en base 10, es 3 × 10 + 8 × 1 = treinta y ocho, mientras que en base-16, es 3 × 16 + 8 × 1 = cincuenta y seis. Los dígitos más altos son (múltiplos de) potencias más altas de la base; el dígito entero más bajo es la base 0 (= 1), el siguiente dígito más alto es la base 1 (= base), el siguiente es la base ** 2 (= base * base), etc. Esta es la exponencia en el trabajo.
Peter Hosey

Respuestas:

46

Esas se denominan referencias de entidad de carácter . Cuando toman la forma de &#<number>;se denominan referencias numéricas de entidad . Básicamente, es una representación de cadena del byte que debe sustituirse. En el caso de &#038;, representa el carácter con el valor de 38 en el esquema de codificación de caracteres ISO-8859-1, que es &.

La razón por la que el ampersand debe codificarse en RSS es que es un carácter especial reservado.

Lo que debe hacer es analizar la cadena y reemplazar las entidades con un byte que coincida con el valor entre &#y ;. No conozco ninguna manera excelente de hacer esto en el objetivo C, pero esta pregunta de desbordamiento de pila podría ser de alguna ayuda.

Editar: Desde que respondí a esto hace unos dos años, hay algunas soluciones excelentes; vea la respuesta de @Michael Waterfall a continuación.

Matt Bridges
fuente
2
+1 Estaba a punto de enviar exactamente la misma respuesta (¡incluidos los mismos enlaces, nada menos!)
e.James
"Básicamente, es una representación de cadena del byte que debe sustituirse". Más como personaje. Esto es texto, no datos; al convertir el texto en datos, el carácter puede ocupar varios bytes, según el carácter y la codificación.
Peter Hosey
Gracias por la respuesta. Dijiste "representa el carácter con el valor de 38 en el esquema de codificación de caracteres ISO-8859-1, que es &". ¿Estás seguro de eso? ¿Tiene un enlace a una tabla de caracteres de este tipo? Porque por lo que recuerdo fue una sola cita.
treznik
en.wikipedia.org/wiki/ISO/IEC_8859-1#ISO-8859-1 o simplemente escriba & # 038; en google.
Matt Bridges
y ¿qué pasa con & amp; o & copy; símbolos?
vokilam
162

Consulte mi categoría NSString para HTML . Estos son los métodos disponibles:

- (NSString *)stringByConvertingHTMLToPlainText;
- (NSString *)stringByDecodingHTMLEntities;
- (NSString *)stringByEncodingHTMLEntities;
- (NSString *)stringWithNewLinesAsBRs;
- (NSString *)stringByRemovingNewLinesAndWhitespace;
Cascada de Michael
fuente
3
Amigo, excelentes funciones. ¡Tu método stringByDecodingXMLEntities me alegró el día! ¡Gracias!
Brian Moeskau
3
No hay problema;) ¡Me alegra que lo hayas encontrado útil!
Michael Waterfall
4
Después de unas horas de búsqueda, sé que esta es la única forma de hacerlo que realmente funciona. NSString está atrasado para un método de cadena que puede hacer esto. Bien hecho.
Adam Eberbach
1
Encontré (2) en la licencia de Michael demasiado restrictiva para mi caso de uso, así que usé la solución de Nikita. Incluir tres archivos con licencia Apache-2.0 de Google Toolbox funciona muy bien para mí.
jaime
10
La actualización del código para ARC sería útil ... Xcode está arrojando toneladas de errores y advertencias de ARC en la compilación
Matej
52

El de Daniel es básicamente muy bueno, y solucioné algunos problemas allí:

  1. eliminó el carácter de omisión para NSSCanner (de lo contrario, los espacios entre dos entidades continuas se ignorarían

    [escáner setCharactersToBeSkipped: nil];

  2. Se corrigió el análisis cuando hay símbolos '&' aislados (no estoy seguro de cuál es la salida 'correcta' para esto, simplemente la comparé con Firefox):

p.ej

    &#ABC DF & B&#39;  & C&#39; Items (288)

aquí está el código modificado:

- (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:@"&amp;" intoString:NULL])
            [result appendString:@"&"];
        else if ([scanner scanString:@"&apos;" intoString:NULL])
            [result appendString:@"'"];
        else if ([scanner scanString:@"&quot;" intoString:NULL])
            [result appendString:@"\""];
        else if ([scanner scanString:@"&lt;" 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;
}
Walty Yeung
fuente
¡Esta debería ser la respuesta definitiva a la pregunta! ¡Gracias!
boliva
Esto funcionó muy bien. Desafortunadamente, el código de respuesta mejor calificado ya no funciona debido a problemas de ARC, pero esto funciona.
Ted Kulp
@TedKulp funciona bien, solo necesita deshabilitar ARC por archivo. stackoverflow.com/questions/6646052/…
Kyle
Te aprobaría dos veces si pudiera.
Kibitz503
Traducción rápida para las personas que siguen visitando esta pregunta en 2016+: stackoverflow.com/a/35303635/1153630
Max Chuquimia
46

A partir de iOS 7, puede decodificar caracteres HTML de forma nativa utilizando un NSAttributedStringcon el NSHTMLTextDocumentTypeatributo:

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 cadena con atributos decodificados ahora se mostrará como:  & & <> ™ © ♥ ♣ ♠ ♦.

Nota: Esto solo funcionará si se llama en el hilo principal.

Bryan Luby
fuente
6
la mejor respuesta si no necesita ser compatible con iOS 6 y
versiones
1
no, no es el mejor si alguien quiere codificarlo en el hilo bg; O
badeleux
4
Esto funcionó para decodificar una entidad, pero también estropeó un guión no codificado.
Andrew
Esto está obligado a suceder en el hilo principal. Así que probablemente no quieras hacer esto si no es necesario.
Keith Smiley
Simplemente cuelga la GUI cuando se trata de UITableView. Por lo tanto, no funciona correctamente.
Asif Bilal
35

Nadie parece mencionar una de las opciones más simples: Google Toolbox para Mac
(a pesar del nombre, esto también funciona en 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;

Y tuve que incluir solo tres archivos en el proyecto: encabezado, implementación y GTMDefines.h.

Nikita Rybak
fuente
He incluido estos tres scripts, pero ¿cómo puedo usarlos ahora?
Borut Tomazin
@ borut-t [myString gtm_stringByUnescapingFromHTML]
Nikita Rybak
2
Elegí incluir solo esos tres archivos, por lo que necesitaba hacer esto para que fuera compatible con arc: code.google.com/p/google-toolbox-for-mac/wiki/ARC_Compatibility
jaime
Debo
Ojalá pudiera hacer que esto funcione completamente. Parece que se saltan muchos de ellos en mis cadenas.
Joseph Toronto
17

Debería publicar esto en GitHub o algo así. Esto entra en una categoría de NSString, se usa NSScannerpara la implementación y maneja entidades de caracteres numéricos hexadecimales y decimales, así como las habituales simbólicas.

Además, maneja cadenas mal formadas (cuando tiene un & seguido de una secuencia de caracteres no válida) con relativa elegancia, lo que resultó ser crucial en mi aplicación publicada que usa este código.

- (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];
    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:@"&amp;" intoString:NULL])
            [result appendString:@"&"];
        else if ([scanner scanString:@"&apos;" intoString:NULL])
            [result appendString:@"'"];
        else if ([scanner scanString:@"&quot;" intoString:NULL])
            [result appendString:@"\""];
        else if ([scanner scanString:@"&lt;" 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", charCode];
            }
            else {
                NSString *unknownEntity = @"";
                [scanner scanUpToString:@";" intoString:&unknownEntity];
                [result appendFormat:@"&#%@%@;", xForHex, unknownEntity];
                NSLog(@"Expected numeric character entity but got &#%@%@;", xForHex, unknownEntity);
            }
            [scanner scanString:@";" intoString:NULL];
        }
        else {
            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;
}
Daniel Dickison
fuente
Pieza de código muy útil, sin embargo, tiene un par de problemas que fueron abordados por Walty. ¡Gracias por compartir!
Michael Waterfall
¿Conoce una forma de mostrar los símbolos lambda, mu, nu, pi decodificando sus entidades XML como & micro; ... ect ????
chinthakad
Debe evitar usar gotos como su estilo de código terrible. Deberías reemplazar la línea goto finish;con break;.
Stunner
4

Esta es la forma en que lo hago usando el marco RegexKitLite :

-(NSString*) decodeHtmlUnicodeCharacters: (NSString*) html {
NSString* result = [html copy];
NSArray* matches = [result arrayOfCaptureComponentsMatchedByRegex: @"\\&#([\\d]+);"];

if (![matches count]) 
    return result;

for (int i=0; i<[matches count]; i++) {
    NSArray* array = [matches objectAtIndex: i];
    NSString* charCode = [array objectAtIndex: 1];
    int code = [charCode intValue];
    NSString* character = [NSString stringWithFormat:@"%C", code];
    result = [result stringByReplacingOccurrencesOfString: [array objectAtIndex: 0]
                                               withString: character];      
}   
return result;  

}

Espero que esto ayude a alguien.

azúcar real
fuente
4

puede usar solo esta función para resolver este problema.

+ (NSString*) decodeHtmlUnicodeCharactersToString:(NSString*)str
{
    NSMutableString* string = [[NSMutableString alloc] initWithString:str];  // #&39; replace with '
    NSString* unicodeStr = nil;
    NSString* replaceStr = nil;
    int counter = -1;

    for(int i = 0; i < [string length]; ++i)
    {
        unichar char1 = [string characterAtIndex:i];    
        for (int k = i + 1; k < [string length] - 1; ++k)
        {
            unichar char2 = [string characterAtIndex:k];    

            if (char1 == '&'  && char2 == '#' ) 
            {   
                ++counter;
                unicodeStr = [string substringWithRange:NSMakeRange(i + 2 , 2)];    
                // read integer value i.e, 39
                replaceStr = [string substringWithRange:NSMakeRange (i, 5)];     //     #&39;
                [string replaceCharactersInRange: [string rangeOfString:replaceStr] withString:[NSString stringWithFormat:@"%c",[unicodeStr intValue]]];
                break;
            }
        }
    }
    [string autorelease];

    if (counter > 1)
        return  [self decodeHtmlUnicodeCharactersToString:string]; 
    else
        return string;
}
Krishna Gupta
fuente
2

Aquí hay una versión rápida de la respuesta de Walty Yeung :

extension String {
    static private let mappings = ["&quot;" : "\"","&amp;" : "&", "&lt;" : "<", "&gt;" : ">","&nbsp;" : " ","&iexcl;" : "¡","&cent;" : "¢","&pound;" : " £","&curren;" : "¤","&yen;" : "¥","&brvbar;" : "¦","&sect;" : "§","&uml;" : "¨","&copy;" : "©","&ordf;" : " ª","&laquo" : "«","&not" : "¬","&reg" : "®","&macr" : "¯","&deg" : "°","&plusmn" : "±","&sup2; " : "²","&sup3" : "³","&acute" : "´","&micro" : "µ","&para" : "¶","&middot" : "·","&cedil" : "¸","&sup1" : "¹","&ordm" : "º","&raquo" : "»&","frac14" : "¼","&frac12" : "½","&frac34" : "¾","&iquest" : "¿","&times" : "×","&divide" : "÷","&ETH" : "Ð","&eth" : "ð","&THORN" : "Þ","&thorn" : "þ","&AElig" : "Æ","&aelig" : "æ","&OElig" : "Œ","&oelig" : "œ","&Aring" : "Å","&Oslash" : "Ø","&Ccedil" : "Ç","&ccedil" : "ç","&szlig" : "ß","&Ntilde;" : "Ñ","&ntilde;":"ñ",]

    func stringByDecodingXMLEntities() -> String {

        guard let _ = self.rangeOfString("&", options: [.LiteralSearch]) else {
            return self
        }

        var result = ""

        let scanner = NSScanner(string: self)
        scanner.charactersToBeSkipped = nil

        let boundaryCharacterSet = NSCharacterSet(charactersInString: " \t\n\r;")

        repeat {
            var nonEntityString: NSString? = nil

            if scanner.scanUpToString("&", intoString: &nonEntityString) {
                if let s = nonEntityString as? String {
                    result.appendContentsOf(s)
                }
            }

            if scanner.atEnd {
                break
            }

            var didBreak = false
            for (k,v) in String.mappings {
                if scanner.scanString(k, intoString: nil) {
                    result.appendContentsOf(v)
                    didBreak = true
                    break
                }
            }

            if !didBreak {

                if scanner.scanString("&#", intoString: nil) {

                    var gotNumber = false
                    var charCodeUInt: UInt32 = 0
                    var charCodeInt: Int32 = -1
                    var xForHex: NSString? = nil

                    if scanner.scanString("x", intoString: &xForHex) {
                        gotNumber = scanner.scanHexInt(&charCodeUInt)
                    }
                    else {
                        gotNumber = scanner.scanInt(&charCodeInt)
                    }

                    if gotNumber {
                        let newChar = String(format: "%C", (charCodeInt > -1) ? charCodeInt : charCodeUInt)
                        result.appendContentsOf(newChar)
                        scanner.scanString(";", intoString: nil)
                    }
                    else {
                        var unknownEntity: NSString? = nil
                        scanner.scanUpToCharactersFromSet(boundaryCharacterSet, intoString: &unknownEntity)
                        let h = xForHex ?? ""
                        let u = unknownEntity ?? ""
                        result.appendContentsOf("&#\(h)\(u)")
                    }
                }
                else {
                    scanner.scanString("&", intoString: nil)
                    result.appendContentsOf("&")
                }
            }

        } while (!scanner.atEnd)

        return result
    }
}
Max Chuquimia
fuente
1

En realidad, rmchaara ha bifurcado el gran marco MWFeedParser de Michael Waterfall (referido a su respuesta), ¡y lo ha actualizado con el soporte de ARC!

Puedes encontrarlo en Github aquí

Realmente funciona muy bien, utilicé el método stringByDecodingHTMLEntities y funciona perfectamente.

angelos.p
fuente
Eso soluciona los problemas de ARC, pero introduce algunas advertencias. ¿Creo que es seguro ignorarlos?
Robert J. Clegg
0

¡Como si necesitaras otra solución! Este es bastante simple y bastante efectivo:

@interface NSString (NSStringCategory)
- (NSString *) stringByReplacingISO8859Codes;
@end


@implementation NSString (NSStringCategory)
- (NSString *) stringByReplacingISO8859Codes
{
    NSString *dataString = self;
    do {
        //*** See if string contains &# prefix
        NSRange range = [dataString rangeOfString: @"&#" options: NSRegularExpressionSearch];
        if (range.location == NSNotFound) {
            break;
        }
        //*** Get the next three charaters after the prefix
        NSString *isoHex = [dataString substringWithRange: NSMakeRange(range.location + 2, 3)];
        //*** Create the full code for replacement
        NSString *isoString = [NSString stringWithFormat: @"&#%@;", isoHex];
        //*** Convert to decimal integer
        unsigned decimal = 0;
        NSScanner *scanner = [NSScanner scannerWithString: [NSString stringWithFormat: @"0%@", isoHex]];
        [scanner scanHexInt: &decimal];
        //*** Use decimal code to get unicode character
        NSString *unicode = [NSString stringWithFormat:@"%C", decimal];
        //*** Replace all occurences of this code in the string
        dataString = [dataString stringByReplacingOccurrencesOfString: isoString withString: unicode];
    } while (TRUE); //*** Loop until we hit the NSNotFound

    return dataString;
}
@end
mpemburn
fuente
0

Si tiene la Referencia de entidad de carácter como una cadena, por ejemplo @"2318", puede extraer una NSString recodificada con el carácter Unicode correcto usando strtoul;

NSString *unicodePoint = @"2318"
unichar iconChar = (unichar) strtoul(unicodePoint.UTF8String, NULL, 16);
NSString *recoded = [NSString stringWithFormat:@"%C", iconChar];
NSLog(@"recoded: %@", recoded");
// prints out "recoded: ⌘"
Henrik Hartz
fuente
0

Swift 3 versión de la respuesta de Jugale

extension String {
    static private let mappings = ["&quot;" : "\"","&amp;" : "&", "&lt;" : "<", "&gt;" : ">","&nbsp;" : " ","&iexcl;" : "¡","&cent;" : "¢","&pound;" : " £","&curren;" : "¤","&yen;" : "¥","&brvbar;" : "¦","&sect;" : "§","&uml;" : "¨","&copy;" : "©","&ordf;" : " ª","&laquo" : "«","&not" : "¬","&reg" : "®","&macr" : "¯","&deg" : "°","&plusmn" : "±","&sup2; " : "²","&sup3" : "³","&acute" : "´","&micro" : "µ","&para" : "¶","&middot" : "·","&cedil" : "¸","&sup1" : "¹","&ordm" : "º","&raquo" : "»&","frac14" : "¼","&frac12" : "½","&frac34" : "¾","&iquest" : "¿","&times" : "×","&divide" : "÷","&ETH" : "Ð","&eth" : "ð","&THORN" : "Þ","&thorn" : "þ","&AElig" : "Æ","&aelig" : "æ","&OElig" : "Œ","&oelig" : "œ","&Aring" : "Å","&Oslash" : "Ø","&Ccedil" : "Ç","&ccedil" : "ç","&szlig" : "ß","&Ntilde;" : "Ñ","&ntilde;":"ñ",]

    func stringByDecodingXMLEntities() -> String {

        guard let _ = self.range(of: "&", options: [.literal]) else {
            return self
        }

        var result = ""

        let scanner = Scanner(string: self)
        scanner.charactersToBeSkipped = nil

        let boundaryCharacterSet = CharacterSet(charactersIn: " \t\n\r;")

        repeat {
            var nonEntityString: NSString? = nil

            if scanner.scanUpTo("&", into: &nonEntityString) {
                if let s = nonEntityString as? String {
                    result.append(s)
                }
            }

            if scanner.isAtEnd {
                break
            }

            var didBreak = false
            for (k,v) in String.mappings {
                if scanner.scanString(k, into: nil) {
                    result.append(v)
                    didBreak = true
                    break
                }
            }

            if !didBreak {

                if scanner.scanString("&#", into: nil) {

                    var gotNumber = false
                    var charCodeUInt: UInt32 = 0
                    var charCodeInt: Int32 = -1
                    var xForHex: NSString? = nil

                    if scanner.scanString("x", into: &xForHex) {
                        gotNumber = scanner.scanHexInt32(&charCodeUInt)
                    }
                    else {
                        gotNumber = scanner.scanInt32(&charCodeInt)
                    }

                    if gotNumber {
                        let newChar = String(format: "%C", (charCodeInt > -1) ? charCodeInt : charCodeUInt)
                        result.append(newChar)
                        scanner.scanString(";", into: nil)
                    }
                    else {
                        var unknownEntity: NSString? = nil
                        scanner.scanUpToCharacters(from: boundaryCharacterSet, into: &unknownEntity)
                        let h = xForHex ?? ""
                        let u = unknownEntity ?? ""
                        result.append("&#\(h)\(u)")
                    }
                }
                else {
                    scanner.scanString("&", into: nil)
                    result.append("&")
                }
            }

        } while (!scanner.isAtEnd)

        return result
    }
}
Xzya
fuente