Dekodowanie znaków HTML w Objective-C / Cocoa Touch

103

Przede wszystkim znalazłem to: Cel C HTML escape / unescape , ale nie działa dla mnie.

Moje zakodowane znaki (przy okazji pochodzą z kanału RSS) wyglądają następująco: &

Przeszukałem całą sieć i znalazłem powiązane dyskusje, ale nie poprawiłem mojego konkretnego kodowania, myślę, że nazywa się je znakami szesnastkowymi.

treznik
źródło
3
Ten komentarz pojawił się sześć miesięcy po pierwotnym pytaniu, więc jest bardziej dla tych, którzy natkną się na to pytanie, szukając odpowiedzi i rozwiązania. Niedawno pojawiło się bardzo podobne pytanie, na które odpowiedziałem stackoverflow.com/questions/2254862/ ... Używa RegexKitLite i Blocks do wyszukiwania i zastępowania ciągu &#...;w ciągu odpowiadającym mu znakiem.
johne
Co konkretnie „nie działa”? Nie widzę w tym pytaniu niczego, co nie jest duplikatem tego wcześniejszego pytania.
Peter Hosey
To jest dziesiętne. Szesnastkowy to 8.
kennytm
Różnica między liczbą dziesiętną a szesnastkową to podstawa-10, podczas gdy szesnastkowa to podstawa-16. „38” to inna liczba w każdej bazie; w przypadku podstawy 10 to 3 × 10 + 8 × 1 = trzydzieści osiem, podczas gdy w przypadku podstawy-16 to 3 × 16 + 8 × 1 = pięćdziesiąt sześć. Wyższe cyfry to (wielokrotności) wyższe potęgi podstawy; najniższa cała cyfra to podstawa 0 (= 1), następna wyższa cyfra to podstawa 1 (= podstawa), następna to podstawa ** 2 (= podstawa * podstawa) itd. To jest potęga w działaniu.
Peter Hosey

Odpowiedzi:

46

Nazywa się to odniesieniami do jednostek postaci . Kiedy przyjmują formę &#<number>;, nazywane są numerycznymi odniesieniami do bytów . Zasadniczo jest to ciąg znaków reprezentujący bajt, który należy podstawić. W przypadku &#038;, reprezentuje znak o wartości 38 w schemacie kodowania znaków ISO-8859-1, czyli &.

Powodem, dla którego znak ampersand musi być zakodowany w formacie RSS, jest zastrzeżony znak specjalny.

To, co musisz zrobić, to przeanalizować ciąg i zastąpić jednostki bajtem pasującym do wartości między &#a ;. Nie znam żadnych świetnych sposobów na zrobienie tego w celu C, ale pytanie o przepełnienie stosu może być pomocne.

Edycja: Od czasu odpowiedzi na to pytanie jakieś dwa lata temu pojawiło się kilka świetnych rozwiązań; zobacz odpowiedź @Michael Waterfall poniżej.

Matt Bridges
źródło
2
+1 Miałem właśnie
udzielić
„Zasadniczo jest to ciąg znaków reprezentujący bajt, który należy podstawić”. Bardziej jak charakter. To jest tekst, a nie dane; po konwersji tekstu na dane znak może zajmować wiele bajtów, w zależności od znaku i kodowania.
Peter Hosey
Dziękuję za odpowiedź. Powiedziałeś, że „reprezentuje znak o wartości 38 w schemacie kodowania znaków ISO-8859-1, czyli &”. Czy jesteś tego pewien? Czy masz link do tablicy znaków tego typu? Z tego, co pamiętam, był to pojedynczy cytat.
treznik
en.wikipedia.org/wiki/ISO/IEC_8859-1#ISO-8859-1 lub po prostu wpisz & # 038; do google.
Matt Bridges,
a co z & amp; lub & copy; symbolika?
vokilam
162

Sprawdź moją kategorię NSString dla HTML . Oto dostępne metody:

- (NSString *)stringByConvertingHTMLToPlainText;
- (NSString *)stringByDecodingHTMLEntities;
- (NSString *)stringByEncodingHTMLEntities;
- (NSString *)stringWithNewLinesAsBRs;
- (NSString *)stringByRemovingNewLinesAndWhitespace;
Michael Waterfall
źródło
3
Koleś, doskonałe funkcje. Twoja metoda stringByDecodingXMLEntities zrobiła mój dzień! Dzięki!
Brian Moeskau
3
Żaden problem;) Cieszę się, że to przydatne!
Michael Waterfall
4
Po kilku godzinach poszukiwań wiem, że to jedyny sposób, który naprawdę działa. NSString jest spóźniony w przypadku metody łańcuchowej, która może to zrobić. Dobra robota.
Adam Eberbach
1
Uważam, że (2) na licencji Michaela jest zbyt restrykcyjne dla mojego przypadku, więc użyłem rozwiązania Nikity. Dołączenie trzech plików na licencji Apache-2.0 z zestawu narzędzi Google działa świetnie.
jaime
10
Aktualizacja kodu dla ARC byłaby przydatna. Xcode generuje mnóstwo błędów i ostrzeżeń ARC w kompilacji
Matej
52

Ten autorstwa Daniela jest w zasadzie bardzo fajny i naprawiłem tam kilka problemów:

  1. usunięto znak pomijania dla NSSCanner (w przeciwnym razie spacje między dwoma ciągłymi jednostkami byłyby ignorowane

    [skaner setCharactersToBeSkipped: nil];

  2. naprawiono parsowanie, gdy występują izolowane symbole `` & '' (nie jestem pewien, jakie jest `` prawidłowe '' wyjście dla tego, właśnie porównałem to z firefoxem):

na przykład

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

oto zmodyfikowany kod:

- (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
źródło
To powinna być ostateczna odpowiedź na pytanie !! Dzięki!
boliva
To działało świetnie. Niestety kod najwyżej ocenionej odpowiedzi już nie działa z powodu problemów z ARC, ale tak jest.
Ted Kulp
@TedKulp działa dobrze, wystarczy wyłączyć ARC dla każdego pliku. stackoverflow.com/questions/6646052/…
Kyle
Gdybym mógł, dałbym ci dwa razy kciuk.
Kibitz503,
Szybkie tłumaczenie dla osób nadal odwiedzających to pytanie w 2016+: stackoverflow.com/a/35303635/1153630
Max Chuquimia
46

Począwszy od iOS 7, możesz dekodować znaki HTML natywnie, używając an NSAttributedStringz NSHTMLTextDocumentTypeatrybutem:

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];

Zdekodowany przypisany ciąg będzie teraz wyświetlany jako:  & & <> ™ © ♥ ♣ ♠ ♦.

Uwaga: zadziała tylko wtedy, gdy zostanie wywołany w głównym wątku.

Bryan Luby
źródło
6
najlepsza odpowiedź, jeśli nie potrzebujesz obsługi iOS 6 i starszych
jcesarmobile
1
nie, nie najlepiej, jeśli ktoś chce zakodować to na wątku bg; O
badeleux
4
To działało w przypadku dekodowania jednostki, ale powodowało również zepsucie niekodowanego myślnika.
Andrew,
Jest to wymuszone w głównym wątku. Więc prawdopodobnie nie chcesz tego robić, jeśli nie musisz.
Keith Smiley
Po prostu zawiesza GUI, gdy chodzi o UITableView. Dlatego nie działa poprawnie.
Asif Bilal
35

Wydaje się, że nikt nie wspomina o jednej z najprostszych opcji: Google Toolbox for Mac
(pomimo nazwy działa to również na 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;

W projekcie musiałem uwzględnić tylko trzy pliki: nagłówek, implementację i GTMDefines.h.

Nikita Rybak
źródło
Dołączyłem te trzy skrypty, ale jak mogę ich teraz użyć?
Borut Tomazin
@ borut-t [myString gtm_stringByUnescapingFromHTML]
Nikita Rybak
2
Zdecydowałem się dołączyć tylko te trzy pliki, więc musiałem to zrobić, aby był zgodny z arc: code.google.com/p/google-toolbox-for-mac/wiki/ARC_Compatibility
jaime
Muszę powiedzieć, że jest to zdecydowanie najprostsze i
najlżejsze
Chciałbym móc to całkowicie zadziałać. Wydaje się, że przeskakuje wiele z nich w moich strunach.
Joseph Toronto,
17

Powinienem opublikować to na GitHubie czy coś. To należy do kategorii NSString, używa NSScannerdo implementacji i obsługuje zarówno szesnastkowe, jak i dziesiętne numeryczne jednostki znakowe, a także zwykłe symboliczne.

Ponadto stosunkowo dobrze radzi sobie ze zniekształconymi ciągami znaków (gdy występuje &, po którym występuje nieprawidłowa sekwencja znaków), co okazało się kluczowe w mojej wydanej aplikacji, która używa tego kodu.

- (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
źródło
Bardzo przydatny fragment kodu, jednak zawiera kilka problemów, które zostały rozwiązane przez Walty. Dzięki za udostępnienie!
Michael Waterfall
czy znasz sposób wyświetlania symboli lambda, mu, nu, pi poprzez dekodowanie ich jednostek XML, takich jak & micro; ... ect ????
chinthakad
Powinieneś unikać używania gotos jako jego okropnego stylu kodu. Należy zamienić linię goto finish;z break;.
Stunner
4

Oto sposób, w jaki robię to przy użyciu frameworka 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;  

}

Mam nadzieję, że to komuś pomoże.

realsugar
źródło
4

możesz użyć tej funkcji, aby rozwiązać ten problem.

+ (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
źródło
2

Oto szybka wersja odpowiedzi Walty Yeunga :

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
źródło
1

Właściwie świetny framework MWFeedParser Michaela Waterfall (odniósł się do jego odpowiedzi) został rozwidlony przez rmchaara, który zaktualizował go z obsługą ARC!

Możesz go znaleźć na Github tutaj

Naprawdę działa świetnie, użyłem metody stringByDecodingHTMLEntities i działa bez zarzutu.

angelos.p
źródło
To rozwiązuje problemy z ARC - ale wprowadza pewne ostrzeżenia. Myślę, że można je bezpiecznie zignorować?
Robert J. Clegg
0

Jakbyś potrzebował innego rozwiązania! Ten jest dość prosty i dość skuteczny:

@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
źródło
0

Jeśli masz odniesienie do jednostki znakowej jako ciąg, np. @"2318"Możesz wyodrębnić przekodowany ciąg NSString z poprawnym znakiem Unicode za pomocą 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
źródło
0

Szybka 3 wersja odpowiedzi Jugale'a

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
źródło