Декодирование символов HTML в Objective-C / Cocoa Touch

103

Прежде всего, я нашел это: Objective C HTML escape / unescape , но у меня это не работает.

Мои закодированные символы (взятые из RSS-канала, кстати) выглядят так: &

Я поискал по всей сети и нашел связанные обсуждения, но не исправил мою конкретную кодировку, я думаю, что они называются шестнадцатеричными символами.

трезник
источник
3
Этот комментарий написан через шесть месяцев после исходного вопроса, поэтому он больше подходит для тех, кто наткнулся на этот вопрос в поисках ответа и решения. Совсем недавно возник очень похожий вопрос, на который я ответил stackoverflow.com/questions/2254862/… Он использует RegexKitLite и Blocks для поиска и замены &#...;в строке эквивалентным символом.
johne
Что конкретно «не работает»? Я не вижу в этом вопросе ничего, что не дублировало бы предыдущий вопрос.
Питер Хози,
Это десятичное число. Шестнадцатеричный 8.
kennytm 03
Разница между десятичным и шестнадцатеричным числами в том, что десятичное число имеет основание 10, а шестнадцатеричное - основание 16. «38» - это разные числа в каждой базе; в базе 10 это 3 × 10 + 8 × 1 = тридцать восемь, тогда как в базе 16 это 3 × 16 + 8 × 1 = пятьдесят шесть. Старшие цифры (кратны) старшим степеням основания; самая низкая целая цифра - это основание 0 (= 1), следующая более высокая цифра - это основание 1 (= основание), следующая - основание ** 2 (= основание * основание) и т. д. Это возведение в степень в действии.
Питер Хози,

Ответы:

46

Они называются ссылками на сущности символов . Когда они принимают форму, &#<number>;они называются ссылками на числовые объекты . По сути, это строковое представление байта, который следует заменить. В случае &#038;, он представляет символ со значением 38 в схеме кодировки символов ISO-8859-1, то есть &.

Причина, по которой амперсанд должен быть закодирован в RSS, заключается в том, что это зарезервированный специальный символ.

Что вам нужно сделать, так это проанализировать строку и заменить объекты байтом, соответствующим значению между &#и ;. Я не знаю каких-либо отличных способов сделать это в цели C, но этот вопрос о переполнении стека может помочь.

Изменить: после ответа на этот вопрос около двух лет назад есть несколько отличных решений; см. ответ @Michael Waterfall ниже.

Мэтт Бриджес
источник
2
+1 Я как раз собирался отправить точно такой же ответ (включая те же ссылки, не меньше!)
e.James 09
«По сути, это строковое представление байта, которое следует заменить». Больше похоже на персонажа. Это текст, а не данные; после преобразования текста в данные символ может занимать несколько байтов, в зависимости от символа и кодировки.
Питер Хози,
Спасибо за ответ. Вы сказали: «это символ со значением 38 в схеме кодировки символов ISO-8859-1, то есть &». Вы уверены, что? У вас есть ссылка на таблицу символов этого типа? Потому что, насколько я помню, это была единственная цитата.
treznik
en.wikipedia.org/wiki/ISO/IEC_8859-1#ISO-8859-1 или просто введите & # 038; в гугл.
Мэтт Бриджес,
а что насчет & amp; или & скопировать; символы?
vokilam
162

Проверьте мою категорию NSString для HTML . Вот доступные методы:

- (NSString *)stringByConvertingHTMLToPlainText;
- (NSString *)stringByDecodingHTMLEntities;
- (NSString *)stringByEncodingHTMLEntities;
- (NSString *)stringWithNewLinesAsBRs;
- (NSString *)stringByRemovingNewLinesAndWhitespace;
Майкл водопад
источник
3
Чувак, отличные функции. Ваш метод stringByDecodingXMLEntities сделал мне день! Спасибо!
Брайан Моэскау
3
Нет проблем;) Рад, что вы нашли это полезным!
Michael Waterfall
4
После нескольких часов поиска я знаю, что это единственный способ сделать это, что действительно работает. NSString просрочен для строкового метода, который может это сделать. Отлично сработано.
Адам Эбербах
1
Я обнаружил (2) в лицензии Майкла слишком ограничивающим для моего варианта использования, поэтому я использовал решение Никиты. Включение трех файлов с лицензией Apache-2.0 из панели инструментов Google отлично подходит для меня.
jaime 08
10
Обновление кода для ARC было бы удобно. Xcode выдает массу ошибок и предупреждений ARC при сборке
Матей
52

Книга Даниэля в основном очень хороша, и я исправил там несколько проблем:

  1. удален пропускающий символ для NSSCanner (в противном случае пробелы между двумя непрерывными объектами будут игнорироваться

    [сканер setCharactersToBeSkipped: nil];

  2. исправлен синтаксический анализ, когда есть изолированные символы '&' (я не уверен, какой для этого 'правильный' вывод, я просто сравнил его с firefox):

например

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

вот модифицированный код:

- (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;
}
Уолти Юнг
источник
Это должен быть однозначный ответ на вопрос !! Спасибо!
boliva
Это отлично сработало. К сожалению, код ответа с наивысшей оценкой больше не работает из-за проблем с ARC, но это работает.
Ted Kulp
@TedKulp работает нормально, вам просто нужно отключить ARC для каждого файла. stackoverflow.com/questions/6646052/…
Кайл
Если бы мог, я бы поставил тебе палец дважды.
Kibitz503
Быстрый перевод для людей, которые все еще посещают этот вопрос в 2016+: stackoverflow.com/a/35303635/1153630
Макс Чукимия,
46

Начиная с iOS 7, вы можете декодировать символы HTML изначально, используя атрибут NSAttributedStringс NSHTMLTextDocumentTypeатрибутом:

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

Декодированная строка с атрибутами теперь будет отображаться как:  & & <> ™ © ♥ ♣ ♠ ♦.

Примечание. Это будет работать, только если вызывается в основном потоке.

Брайан Луби
источник
6
лучший ответ, если вам не нужна поддержка iOS 6 и старше
jcesarmobile
1
нет, не лучший
вариант,
4
Это сработало для декодирования объекта, но также испортило некодированное тире.
Эндрю
Это принудительно происходит в основном потоке. Так что вы, вероятно, не захотите этого делать, если вам не нужно.
Кейт Смайли
Он просто зависает в графическом интерфейсе, когда дело касается UITableView. Следовательно, не работает правильно.
Асиф Билал
35

Кажется, никто не упоминает один из самых простых вариантов: Google Toolbox для Mac
(несмотря на название, это работает и на 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;

И мне пришлось включить в проект всего три файла: заголовок, реализацию и GTMDefines.h.

Никита Рыбак
источник
Я включил эти три сценария, но как я могу их использовать сейчас?
Борут Томазин
@ borut-t [myString gtm_stringByUnescapingFromHTML]
Никита Рыбак
2
Я решил включить только эти три файла, поэтому мне нужно было сделать это, чтобы сделать его совместимым с arc: code.google.com/p/google-toolbox-for-mac/wiki/ARC_Compatibility
jaime
Я должен сказать, что это самое простое и легкое решение на сегодняшний день
Lensovet
Хотел бы я заставить это работать полностью. Кажется, многие из них пропущены в моих строках.
Джозеф Торонто
17

Я должен опубликовать это на GitHub или что-то в этом роде. Это входит в категорию NSString, используется NSScannerдля реализации и обрабатывает как шестнадцатеричные, так и десятичные числовые символы, а также обычные символьные.

Кроме того, он относительно изящно обрабатывает искаженные строки (когда у вас есть &, за которым следует недопустимая последовательность символов), что оказалось решающим в моем выпущенном приложении, которое использует этот код.

- (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;
}
Дэниел Дикисон
источник
Очень полезный фрагмент кода, однако в нем есть несколько проблем, которые были устранены Уолти. Спасибо, что поделился!
Майкл Уотерфолл,
знаете ли вы, как отображать символы лямбда, мю, ню и пи, декодируя их XML-объекты, такие как & micro; ... и т. д. ????
chinthakad
Вам следует избегать использования gotos как ужасного стиля кода. Вы должны заменить строку goto finish;на break;.
Stunner
4

Я делаю это с помощью фреймворка 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;  

}

Надеюсь, это кому-то поможет.

реальный сахар
источник
4

вы можете использовать только эту функцию для решения этой проблемы.

+ (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;
}
Кришна Гупта
источник
2

Вот версия ответа Уолти Юнга на Swift :

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
    }
}
Макс Чукимия
источник
1

На самом деле отличный фреймворк MWFeedParser Майкла Уотерфолла (ссылался на его ответ) был разветвлен rmchaara, который обновил его с поддержкой ARC!

Вы можете найти его в Github здесь

Он действительно отлично работает, я использовал метод stringByDecodingHTMLEntities и работает безупречно.

angelos.p
источник
Это устраняет проблемы с ARC, но вносит некоторые предупреждения. Думаю, их можно игнорировать?
Роберт Дж. Клегг,
0

Как будто вам нужно другое решение! Это довольно просто и довольно эффективно:

@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
источник
0

Если у вас есть ссылка на символьную сущность в виде строки, например @"2318", вы можете извлечь перекодированную NSString с правильным символом Юникода, используя strtoul;

NSString *unicodePoint = @"2318"
unichar iconChar = (unichar) strtoul(unicodePoint.UTF8String, NULL, 16);
NSString *recoded = [NSString stringWithFormat:@"%C", iconChar];
NSLog(@"recoded: %@", recoded");
// prints out "recoded: ⌘"
Хенрик Харц
источник
0

Swift 3 версия ответа 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
источник