Decodificação de caracteres HTML em Objective-C / Cocoa Touch

103

Em primeiro lugar, descobri o seguinte: Objetivo C HTML escapar / unescape , mas não funciona para mim.

Meus caracteres codificados (vêm de um feed RSS, aliás) são assim: &

Eu procurei por toda a rede e encontrei discussões relacionadas, mas nenhuma correção para minha codificação particular, eu acho que eles são chamados de caracteres hexadecimais.

treznik
fonte
3
Este comentário é seis meses após a pergunta original, então é mais para aqueles que se deparam com esta pergunta em busca de uma resposta e uma solução. Uma pergunta muito semelhante surgiu recentemente que eu respondi stackoverflow.com/questions/2254862/… Ele usa RegexKitLite e Blocks para fazer uma pesquisa e substituição de &#...;em uma string por seu caractere equivalente.
johne
O que especificamente “não funciona”? Não vejo nada nesta pergunta que não seja uma duplicata da pergunta anterior.
Peter Hosey,
É decimal. Hexadecimal é 8.
kennytm,
A diferença entre decimal e hexadecimal sendo que decimal é de base 10, enquanto hexadecimal é de base 16. “38” é um número diferente em cada base; na base 10, é 3 × 10 + 8 × 1 = trinta e oito, enquanto na base 16, é 3 × 16 + 8 × 1 = cinquenta e seis. Os dígitos mais altos são (múltiplos de) potências superiores da base; o dígito inteiro mais baixo é a base 0 (= 1), o próximo dígito mais alto é a base 1 (= base), o próximo é a base ** 2 (= base * base), etc. Esta é a exponentação em ação.
Peter Hosey,

Respostas:

46

Essas são chamadas de referências de entidade de personagem . Quando assumem a forma de &#<number>;, são chamadas de referências numéricas de entidades . Basicamente, é uma representação em string do byte que deve ser substituído. No caso de &#038;, representa o caractere com o valor 38 no esquema de codificação de caracteres ISO-8859-1, que é &.

O motivo pelo qual o e comercial precisa ser codificado em RSS é que ele é um caractere especial reservado.

O que você precisa fazer é analisar a string e substituir as entidades por um byte que corresponda ao valor entre &#e ;. Não conheço nenhuma ótima maneira de fazer isso no objetivo C, mas essa questão de estouro de pilha pode ser de alguma ajuda.

Edit: Desde a resposta a isso há cerca de dois anos, existem algumas grandes soluções; veja a resposta de @Michael Waterfall abaixo.

Matt Bridges
fonte
2
1 Eu estava prestes a enviar exatamente a mesma resposta (incluindo os mesmos links, nada menos!)
e.James
“Basicamente, é uma representação de string do byte que deve ser substituído.” Mais como personagem. Isso é texto, não dados; ao converter o texto em dados, o caractere pode ocupar vários bytes, dependendo do caractere e da codificação.
Peter Hosey
Obrigado pela resposta. Você disse "ele representa o caractere com o valor 38 no esquema de codificação de caracteres ISO-8859-1, que é &". Você tem certeza sobre isso? Você tem um link para uma tabela de caracteres desse tipo? Porque, pelo que me lembro, foi uma citação única.
treznik
en.wikipedia.org/wiki/ISO/IEC_8859-1#ISO-8859-1 ou simplesmente digite & # 038; no google.
Matt Bridges
e quanto ao & amp; ou & copy; símbolos?
vokilam
162

Verifique minha categoria NSString para HTML . Aqui estão os métodos disponíveis:

- (NSString *)stringByConvertingHTMLToPlainText;
- (NSString *)stringByDecodingHTMLEntities;
- (NSString *)stringByEncodingHTMLEntities;
- (NSString *)stringWithNewLinesAsBRs;
- (NSString *)stringByRemovingNewLinesAndWhitespace;
Cachoeira Michael
fonte
3
Cara, funções excelentes. Seu método stringByDecodingXMLEntities fez meu dia! Obrigado!
Brian Moeskau
3
Sem problemas;) Que bom que você achou útil!
Michael Waterfall
4
Depois de algumas horas pesquisando, sei que essa é a única maneira que realmente funciona. NSString está atrasado para um método de string que pode fazer isso. Bem feito.
Adam Eberbach
1
Achei (2) na licença de Michael muito restritiva para o meu caso de uso, então usei a solução da Nikita. Incluir três arquivos licenciados pelo Apache-2.0 da caixa de ferramentas do Google funciona muito bem para mim.
jaime
10
A atualização do código para ARC seria útil .. O Xcode está lançando uma tonelada de erros e avisos ARC na compilação
Matej
52

O de Daniel é basicamente muito bom, e resolvi alguns problemas lá:

  1. removeu o caractere de salto para NSSCanner (caso contrário, os espaços entre duas entidades contínuas seriam ignorados

    [scanner setCharactersToBeSkipped: nil];

  2. consertou a análise quando há símbolos '&' isolados (não tenho certeza de qual é a saída 'correta' para isso, apenas comparei com o firefox):

por exemplo

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

aqui está o 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
fonte
Esta deve ser a resposta definitiva para a pergunta !! Obrigado!
boliva
Isso funcionou muito bem. Infelizmente, o código da resposta com classificação mais alta não funciona mais devido a problemas de ARC, mas funciona.
Ted Kulp
@TedKulp funciona perfeitamente, você só precisa desabilitar o ARC por arquivo. stackoverflow.com/questions/6646052/…
Kyle
Eu faria o polegar para cima duas vezes, se pudesse.
Kibitz503,
Tradução rápida para pessoas que ainda estão visitando esta pergunta em 2016+: stackoverflow.com/a/35303635/1153630
Max Chuquimia
46

A partir do iOS 7, você pode decodificar caracteres HTML nativamente usando um NSAttributedStringcom o 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];

A string atribuída decodificada agora será exibida como:  & & <> ™ © ♥ ♣ ♠ ♦.

Observação: isso só funcionará se for chamado no tópico principal.

Bryan Luby
fonte
6
melhor resposta se você não precisa de suporte para iOS 6 e anteriores
jcesarmobile
1
não, não é o melhor se alguém quiser codificá-lo no thread de bg; O
badeleux
4
Isso funcionou para decodificar uma entidade, mas também bagunçou um traço não codificado.
Andrew
Isso é forçado a acontecer no thread principal. Portanto, você provavelmente não vai querer fazer isso se não for necessário.
Keith Smiley
Ele apenas trava a GUI quando se trata de UITableView. Portanto, não está funcionando corretamente.
Asif Bilal
35

Ninguém parece mencionar uma das opções mais simples: Google Toolbox for Mac
(apesar do nome, também funciona no 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;

E eu tive que incluir apenas três arquivos no projeto: cabeçalho, implementação e GTMDefines.h.

Nikita Rybak
fonte
Eu incluí esses três scripts, mas como posso usá-los agora?
Borut Tomazin
@ borut-t [myString gtm_stringByUnescapingFromHTML]
Nikita Rybak
2
Optei por incluir apenas esses três arquivos, então precisei fazer isso para torná-lo compatível com arc: code.google.com/p/google-toolbox-for-mac/wiki/ARC_Compatibility
jaime
devo
Eu gostaria de poder fazer isso funcionar completamente. Parece pular muitos deles nas minhas cordas.
Joseph Toronto
17

Devo postar isso no GitHub ou algo assim. Isso vai em uma categoria de NSString, usa NSScannerpara a implementação e lida com entidades de caracteres numéricos hexadecimais e decimais, bem como os simbólicos usuais.

Além disso, ele lida com strings malformadas (quando você tem um & seguido por uma sequência inválida de caracteres) de forma relativamente elegante, o que acabou sendo crucial em meu aplicativo lançado que usa esse 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
fonte
Um pedaço de código muito útil, no entanto, ele tem alguns problemas que foram resolvidos por Walty. Obrigado por compartilhar!
Cachoeira Michael
você conhece uma maneira de mostrar os símbolos lambda, mu, nu, pi decodificando suas entidades XML como & micro; ... ect ????
chinthakad de
Você deve evitar usar gotos como seu péssimo estilo de código. Você deve substituir a linha goto finish;por break;.
Stunner
4

É assim que faço isso usando a estrutura 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 isso ajude alguém.

açúcar real
fonte
4

você pode usar apenas esta função 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
fonte
2

Aqui está uma versão rápida da resposta 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
fonte
1

Na verdade, o ótimo framework MWFeedParser de Michael Waterfall (referido à sua resposta) foi bifurcado por rmchaara que o atualizou com suporte ARC!

Você pode encontrar no Github aqui

Realmente funciona muito bem, usei o método stringByDecodingHTMLEntities e funciona perfeitamente.

angelos.p
fonte
Isso corrige os problemas do ARC - mas apresenta alguns avisos. Eu acho que é seguro ignorá-los?
Robert J. Clegg
0

Como se precisasse de outra solução! Este é muito simples e bastante eficaz:

@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
fonte
0

Se você tiver o Character Entity Reference como uma string, por exemplo @"2318", você pode extrair um NSString recodificado com o caractere Unicode correto 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
fonte
0

Versão Swift 3 da resposta 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
fonte