Как декодировать HTML-объекты в Swift?


121

Я извлекаю файл JSON с сайта, и одна из полученных строк:

The Weeknd ‘King Of The Fall’ [Video Premiere] | @TheWeeknd | #SoPhi

Как я могу преобразовать такие вещи &#8216в правильные символы?

Я сделал площадку Xcode, чтобы продемонстрировать это:

import UIKit

var error: NSError?
let blogUrl: NSURL = NSURL.URLWithString("http://sophisticatedignorance.net/api/get_recent_summary/")
let jsonData = NSData(contentsOfURL: blogUrl)

let dataDictionary = NSJSONSerialization.JSONObjectWithData(jsonData, options: nil, error: &error) as NSDictionary

var a = dataDictionary["posts"] as NSArray

println(a[0]["title"])

Ответы:


158

Последний раз этот ответ редактировался для SDK Swift 5.2 и iOS 13.4.


Нет простого способа сделать это, но вы можете использовать NSAttributedStringмагию, чтобы сделать этот процесс максимально безболезненным (имейте в виду, что этот метод также удалит все теги HTML).

Не забудьте инициализировать только NSAttributedStringиз основного потока . Он использует WebKit для анализа HTML внизу, поэтому это требование.

// This is a[0]["title"] in your case
let encodedString = "The Weeknd <em>&#8216;King Of The Fall&#8217;</em>"

guard let data = htmlEncodedString.data(using: .utf8) else {
    return
}

let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
    .documentType: NSAttributedString.DocumentType.html,
    .characterEncoding: String.Encoding.utf8.rawValue
]

guard let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil) else {
    return
}

// The Weeknd ‘King Of The Fall’
let decodedString = attributedString.string
extension String {

    init?(htmlEncodedString: String) {

        guard let data = htmlEncodedString.data(using: .utf8) else {
            return nil
        }

        let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
            .documentType: NSAttributedString.DocumentType.html,
            .characterEncoding: String.Encoding.utf8.rawValue
        ]

        guard let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil) else {
            return nil
        }

        self.init(attributedString.string)

    }

}

let encodedString = "The Weeknd <em>&#8216;King Of The Fall&#8217;</em>"
let decodedString = String(htmlEncodedString: encodedString)

54
Какой? Расширения предназначены для расширения существующих типов с целью предоставления новых функций.
akashivskyy 01

4
Я понимаю, что вы пытаетесь сказать, но отрицать расширения - не лучший вариант.
akashivskyy 01

1
@akashivskyy: Чтобы это работало правильно с символами, отличными от ASCII, вам необходимо добавить атрибут NSCharacterEncodingDocumentAttribute, сравните stackoverflow.com/a/27898167/1187415 .
Martin R

13
Этот метод чрезвычайно тяжелый и не рекомендуется в таблицах и сетках
Гвидо

1
Это круто! Хотя он блокирует основной поток, есть ли способ запустить его в фоновом потоке?
MMV

78

Ответ @ akashivskyy отличный и демонстрирует, как использовать NSAttributedStringдля декодирования объектов HTML. Одним из возможных недостатков (как он заявил) является то, что вся разметка HTML также удаляется, поэтому

<strong> 4 &lt; 5 &amp; 3 &gt; 2</strong>

становится

4 < 5 & 3 > 2

В OS X есть то, CFXMLCreateStringByUnescapingEntities()что выполняет эту работу:

let encoded = "<strong> 4 &lt; 5 &amp; 3 &gt; 2 .</strong> Price: 12 &#x20ac;.  &#64; "
let decoded = CFXMLCreateStringByUnescapingEntities(nil, encoded, nil) as String
println(decoded)
// <strong> 4 < 5 & 3 > 2 .</strong> Price: 12.  @ 

но это недоступно в iOS.

Вот чистая реализация Swift. Он декодирует ссылки на символьные сущности, такие как &lt;использование словаря, и все числовые символьные сущности, такие как &#64или &#x20ac. (Обратите внимание, что я не перечислил все 252 объекта HTML явно.)

Swift 4:

// Mapping from XML/HTML character entity reference to character
// From http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
private let characterEntities : [ Substring : Character ] = [
    // XML predefined entities:
    "&quot;"    : "\"",
    "&amp;"     : "&",
    "&apos;"    : "'",
    "&lt;"      : "<",
    "&gt;"      : ">",

    // HTML character entity references:
    "&nbsp;"    : "\u{00a0}",
    // ...
    "&diams;"   : "♦",
]

extension String {

    /// Returns a new string made by replacing in the `String`
    /// all HTML character entity references with the corresponding
    /// character.
    var stringByDecodingHTMLEntities : String {

        // ===== Utility functions =====

        // Convert the number in the string to the corresponding
        // Unicode character, e.g.
        //    decodeNumeric("64", 10)   --> "@"
        //    decodeNumeric("20ac", 16) --> "€"
        func decodeNumeric(_ string : Substring, base : Int) -> Character? {
            guard let code = UInt32(string, radix: base),
                let uniScalar = UnicodeScalar(code) else { return nil }
            return Character(uniScalar)
        }

        // Decode the HTML character entity to the corresponding
        // Unicode character, return `nil` for invalid input.
        //     decode("&#64;")    --> "@"
        //     decode("&#x20ac;") --> "€"
        //     decode("&lt;")     --> "<"
        //     decode("&foo;")    --> nil
        func decode(_ entity : Substring) -> Character? {

            if entity.hasPrefix("&#x") || entity.hasPrefix("&#X") {
                return decodeNumeric(entity.dropFirst(3).dropLast(), base: 16)
            } else if entity.hasPrefix("&#") {
                return decodeNumeric(entity.dropFirst(2).dropLast(), base: 10)
            } else {
                return characterEntities[entity]
            }
        }

        // ===== Method starts here =====

        var result = ""
        var position = startIndex

        // Find the next '&' and copy the characters preceding it to `result`:
        while let ampRange = self[position...].range(of: "&") {
            result.append(contentsOf: self[position ..< ampRange.lowerBound])
            position = ampRange.lowerBound

            // Find the next ';' and copy everything from '&' to ';' into `entity`
            guard let semiRange = self[position...].range(of: ";") else {
                // No matching ';'.
                break
            }
            let entity = self[position ..< semiRange.upperBound]
            position = semiRange.upperBound

            if let decoded = decode(entity) {
                // Replace by decoded character:
                result.append(decoded)
            } else {
                // Invalid entity, copy verbatim:
                result.append(contentsOf: entity)
            }
        }
        // Copy remaining characters to `result`:
        result.append(contentsOf: self[position...])
        return result
    }
}

Пример:

let encoded = "<strong> 4 &lt; 5 &amp; 3 &gt; 2 .</strong> Price: 12 &#x20ac;.  &#64; "
let decoded = encoded.stringByDecodingHTMLEntities
print(decoded)
// <strong> 4 < 5 & 3 > 2 .</strong> Price: 12.  @

Swift 3:

// Mapping from XML/HTML character entity reference to character
// From http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
private let characterEntities : [ String : Character ] = [
    // XML predefined entities:
    "&quot;"    : "\"",
    "&amp;"     : "&",
    "&apos;"    : "'",
    "&lt;"      : "<",
    "&gt;"      : ">",

    // HTML character entity references:
    "&nbsp;"    : "\u{00a0}",
    // ...
    "&diams;"   : "♦",
]

extension String {

    /// Returns a new string made by replacing in the `String`
    /// all HTML character entity references with the corresponding
    /// character.
    var stringByDecodingHTMLEntities : String {

        // ===== Utility functions =====

        // Convert the number in the string to the corresponding
        // Unicode character, e.g.
        //    decodeNumeric("64", 10)   --> "@"
        //    decodeNumeric("20ac", 16) --> "€"
        func decodeNumeric(_ string : String, base : Int) -> Character? {
            guard let code = UInt32(string, radix: base),
                let uniScalar = UnicodeScalar(code) else { return nil }
            return Character(uniScalar)
        }

        // Decode the HTML character entity to the corresponding
        // Unicode character, return `nil` for invalid input.
        //     decode("&#64;")    --> "@"
        //     decode("&#x20ac;") --> "€"
        //     decode("&lt;")     --> "<"
        //     decode("&foo;")    --> nil
        func decode(_ entity : String) -> Character? {

            if entity.hasPrefix("&#x") || entity.hasPrefix("&#X"){
                return decodeNumeric(entity.substring(with: entity.index(entity.startIndex, offsetBy: 3) ..< entity.index(entity.endIndex, offsetBy: -1)), base: 16)
            } else if entity.hasPrefix("&#") {
                return decodeNumeric(entity.substring(with: entity.index(entity.startIndex, offsetBy: 2) ..< entity.index(entity.endIndex, offsetBy: -1)), base: 10)
            } else {
                return characterEntities[entity]
            }
        }

        // ===== Method starts here =====

        var result = ""
        var position = startIndex

        // Find the next '&' and copy the characters preceding it to `result`:
        while let ampRange = self.range(of: "&", range: position ..< endIndex) {
            result.append(self[position ..< ampRange.lowerBound])
            position = ampRange.lowerBound

            // Find the next ';' and copy everything from '&' to ';' into `entity`
            if let semiRange = self.range(of: ";", range: position ..< endIndex) {
                let entity = self[position ..< semiRange.upperBound]
                position = semiRange.upperBound

                if let decoded = decode(entity) {
                    // Replace by decoded character:
                    result.append(decoded)
                } else {
                    // Invalid entity, copy verbatim:
                    result.append(entity)
                }
            } else {
                // No matching ';'.
                break
            }
        }
        // Copy remaining characters to `result`:
        result.append(self[position ..< endIndex])
        return result
    }
}

Swift 2:

// Mapping from XML/HTML character entity reference to character
// From http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
private let characterEntities : [ String : Character ] = [
    // XML predefined entities:
    "&quot;"    : "\"",
    "&amp;"     : "&",
    "&apos;"    : "'",
    "&lt;"      : "<",
    "&gt;"      : ">",

    // HTML character entity references:
    "&nbsp;"    : "\u{00a0}",
    // ...
    "&diams;"   : "♦",
]

extension String {

    /// Returns a new string made by replacing in the `String`
    /// all HTML character entity references with the corresponding
    /// character.
    var stringByDecodingHTMLEntities : String {

        // ===== Utility functions =====

        // Convert the number in the string to the corresponding
        // Unicode character, e.g.
        //    decodeNumeric("64", 10)   --> "@"
        //    decodeNumeric("20ac", 16) --> "€"
        func decodeNumeric(string : String, base : Int32) -> Character? {
            let code = UInt32(strtoul(string, nil, base))
            return Character(UnicodeScalar(code))
        }

        // Decode the HTML character entity to the corresponding
        // Unicode character, return `nil` for invalid input.
        //     decode("&#64;")    --> "@"
        //     decode("&#x20ac;") --> "€"
        //     decode("&lt;")     --> "<"
        //     decode("&foo;")    --> nil
        func decode(entity : String) -> Character? {

            if entity.hasPrefix("&#x") || entity.hasPrefix("&#X"){
                return decodeNumeric(entity.substringFromIndex(entity.startIndex.advancedBy(3)), base: 16)
            } else if entity.hasPrefix("&#") {
                return decodeNumeric(entity.substringFromIndex(entity.startIndex.advancedBy(2)), base: 10)
            } else {
                return characterEntities[entity]
            }
        }

        // ===== Method starts here =====

        var result = ""
        var position = startIndex

        // Find the next '&' and copy the characters preceding it to `result`:
        while let ampRange = self.rangeOfString("&", range: position ..< endIndex) {
            result.appendContentsOf(self[position ..< ampRange.startIndex])
            position = ampRange.startIndex

            // Find the next ';' and copy everything from '&' to ';' into `entity`
            if let semiRange = self.rangeOfString(";", range: position ..< endIndex) {
                let entity = self[position ..< semiRange.endIndex]
                position = semiRange.endIndex

                if let decoded = decode(entity) {
                    // Replace by decoded character:
                    result.append(decoded)
                } else {
                    // Invalid entity, copy verbatim:
                    result.appendContentsOf(entity)
                }
            } else {
                // No matching ';'.
                break
            }
        }
        // Copy remaining characters to `result`:
        result.appendContentsOf(self[position ..< endIndex])
        return result
    }
}

10
Это великолепно, спасибо, Мартин! Вот расширение с полным списком объектов HTML: gist.github.com/mwaterfall/25b4a6a06dc3309d9555 Я также немного адаптировал его, чтобы обеспечить смещение расстояния, сделанное заменами. Это позволяет правильно настроить любые строковые атрибуты или объекты, на которые могут повлиять эти замены (например, индексы объектов Twitter).
Майкл Уотерфолл,

3
@MichaelWaterfall и Мартин, это великолепно! работает как шарм! Обновляю расширение для Swift 2 pastebin.com/juHRJ6au Спасибо!
Сантьяго

1
Я преобразовал этот ответ, чтобы он был совместим со Swift 2, и сбросил его в CocoaPod под названием StringExtensionHTML для простоты использования. Обратите внимание, что версия Swift 2 от Santiago исправляет ошибки времени компиляции, но полное исключение strtooul(string, nil, base)кода приведет к тому, что код не будет работать с числовыми символьными объектами и выйдет из строя, когда дело доходит до объекта, который он не распознает (вместо изящного отказа).
Адела Чанг

1
@AdelaChang: На самом деле я преобразовал свой ответ на Swift 2 уже в сентябре 2015 года. Он по-прежнему компилируется без предупреждений с Swift 2.2 / Xcode 7.3. Или вы имеете в виду версию Майкла?
Martin R

1
Спасибо, с этим ответом я решил свои проблемы: у меня были серьезные проблемы с производительностью при использовании NSAttributedString.
Андреа Мугнаини

27

Swift 3 версия расширения @akashivskyy ,

extension String {
    init(htmlEncodedString: String) {
        self.init()
        guard let encodedData = htmlEncodedString.data(using: .utf8) else {
            self = htmlEncodedString
            return
        }

        let attributedOptions: [String : Any] = [
            NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
            NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue
        ]

        do {
            let attributedString = try NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil)
            self = attributedString.string
        } catch {
            print("Error: \(error)")
            self = htmlEncodedString
        }
    }
}

Прекрасно работает. Исходный ответ вызывал странный сбой. Спасибо за обновление!
Geoherna

Для французских символов я должен использовать utf16
Себастьян

23

Swift 4


  • Вычисляемая переменная с расширением строки
  • Без лишней защиты делай, лови и тд ...
  • Возвращает исходные строки, если декодирование не удалось

extension String {
    var htmlDecoded: String {
        let decoded = try? NSAttributedString(data: Data(utf8), options: [
            .documentType: NSAttributedString.DocumentType.html,
            .characterEncoding: String.Encoding.utf8.rawValue
        ], documentAttributes: nil).string

        return decoded ?? self
    }
}

1
Вот Это Да ! работает прямо из коробки для Swift 4 !. Использование // let encoded = "The Weeknd & # 8216; King Of The Fall & # 8217;" let finalString = encoded.htmlDecoded
Naishta

2
Мне нравится простота этого ответа. Однако это вызовет сбои при работе в фоновом режиме, потому что он пытается работать в основном потоке.
Джереми Хикс

14

Swift 2 версия расширения @akashivskyy,

 extension String {
     init(htmlEncodedString: String) {
         if let encodedData = htmlEncodedString.dataUsingEncoding(NSUTF8StringEncoding){
             let attributedOptions : [String: AnyObject] = [
            NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
            NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding
        ]

             do{
                 if let attributedString:NSAttributedString = try NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil){
                     self.init(attributedString.string)
                 }else{
                     print("error")
                     self.init(htmlEncodedString)     //Returning actual string if there is an error
                 }
             }catch{
                 print("error: \(error)")
                 self.init(htmlEncodedString)     //Returning actual string if there is an error
             }

         }else{
             self.init(htmlEncodedString)     //Returning actual string if there is an error
         }
     }
 }

Этот код неполный, и его следует всеми способами избегать. Ошибка не обрабатывается должным образом. Когда действительно есть код ошибки, произойдет сбой. Вы должны обновить свой код, чтобы по крайней мере возвращать nil при возникновении ошибки. Или вы можете просто инициализировать исходную строку. В конце концов, вы должны обработать ошибку. Но это не так. Вот Это Да!
oyalhi

9

Версия Swift 4

extension String {

    init(htmlEncodedString: String) {
        self.init()
        guard let encodedData = htmlEncodedString.data(using: .utf8) else {
            self = htmlEncodedString
            return
        }

        let attributedOptions: [NSAttributedString.DocumentReadingOptionKey : Any] = [
            .documentType: NSAttributedString.DocumentType.html,
            .characterEncoding: String.Encoding.utf8.rawValue
        ]

        do {
            let attributedString = try NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil)
            self = attributedString.string
        } 
        catch {
            print("Error: \(error)")
            self = htmlEncodedString
        }
    }
}

Я получаю сообщение «Error Domain = NSCocoaErrorDomain Code = 259». Файл не может быть открыт, потому что он не в правильном формате. »« Когда я пытаюсь использовать это. Это исчезнет, ​​если я запустил полный do catch в основном потоке. Я обнаружил это, проверив документацию NSAttributedString: «Импортер HTML не должен вызываться из фонового потока (то есть словарь параметров включает documentType со значением html). Он попытается синхронизироваться с основным потоком, потерпит неудачу и Тайм-аут ".
MickeDG

8
Пожалуйста, rawValueсинтаксис NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)и NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)ужасен. Заменить на .documentTypeи.characterEncoding
vadian 04

@MickeDG - Не могли бы вы объяснить, что именно вы сделали для устранения этой ошибки? Я получаю это время от времени.
Росс Барбиш,

@RossBarbish - Извини, Росс, это было слишком давно, не могу вспомнить подробности. Вы пробовали то, что я предлагаю в комментарии выше, то есть запускать полный do catch в основном потоке?
MickeDG

7
extension String{
    func decodeEnt() -> String{
        let encodedData = self.dataUsingEncoding(NSUTF8StringEncoding)!
        let attributedOptions : [String: AnyObject] = [
            NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
            NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding
        ]
        let attributedString = NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil, error: nil)!

        return attributedString.string
    }
}

let encodedString = "The Weeknd &#8216;King Of The Fall&#8217;"

let foo = encodedString.decodeEnt() /* The Weeknd ‘King Of The Fall’ */

Re "The Weeknd" : а не "The Weekend" ?
Питер Мортенсен

Подсветка синтаксиса выглядит странно, особенно часть комментария в последней строке. Ты можешь починить это?
Питер Мортенсен

«The Weeknd» - певец, и да, именно так пишется его имя.
wLc

5

Я искал чистую утилиту Swift 3.0 для выхода / unescape из ссылок на символы HTML (то есть для серверных приложений Swift как в macOS, так и в Linux), но не нашел никаких всеобъемлющих решений, поэтому я написал свою собственную реализацию: https: //github.com/IBM-Swift/swift-html-entities

Пакет HTMLEntities,, работает со ссылками на именованные символы HTML4, а также с ссылками на шестнадцатеричные / десятичные числовые символы, и он распознает специальные числовые ссылки на символы в соответствии со спецификацией W3 HTML5 (т. &#x80;Е. Не должно быть экранировано как знак евро (юникод U+20AC), а НЕ как юникод символ для U+0080, и определенные диапазоны ссылок на числовые символы должны быть заменены символом замены U+FFFDпри отмене экранирования).

Пример использования:

import HTMLEntities

// encode example
let html = "<script>alert(\"abc\")</script>"

print(html.htmlEscape())
// Prints ”&lt;script&gt;alert(&quot;abc&quot;)&lt;/script&gt;"

// decode example
let htmlencoded = "&lt;script&gt;alert(&quot;abc&quot;)&lt;/script&gt;"

print(htmlencoded.htmlUnescape())
// Prints<script>alert(\"abc\")</script>"

И для примера OP:

print("The Weeknd &#8216;King Of The Fall&#8217; [Video Premiere] | @TheWeeknd | #SoPhi ".htmlUnescape())
// prints "The Weeknd ‘King Of The Fall’ [Video Premiere] | @TheWeeknd | #SoPhi "

Изменить: HTMLEntitiesтеперь поддерживает ссылки на именованные символы HTML5 начиная с версии 2.0.0. Также реализован синтаксический анализ, соответствующий спецификации.


1
Это наиболее общий ответ, который работает все время и не требует запуска в основном потоке. Это будет работать даже с самыми сложными строками Unicode с экранированием HTML (такими как (&nbsp;͡&deg;&nbsp;͜ʖ&nbsp;͡&deg;&nbsp;)), тогда как ни один из других ответов не справляется с этим.
Стефан

5

Swift 4:

Полное решение, которое, наконец, сработало для меня с кодом HTML, символами новой строки и одинарными кавычками

extension String {
    var htmlDecoded: String {
        let decoded = try? NSAttributedString(data: Data(utf8), options: [
            .documentType: NSAttributedString.DocumentType.html,
            .characterEncoding: String.Encoding.utf8.rawValue
            ], documentAttributes: nil).string

        return decoded ?? self
    }
}

Использование:

let yourStringEncoded = yourStringWithHtmlcode.htmlDecoded

Затем мне пришлось применить еще несколько фильтров , чтобы избавиться от одинарных кавычек (например, не , не имеет , это и т.д.), и символы новой строки , как \n:

var yourNewString = String(yourStringEncoded.filter { !"\n\t\r".contains($0) })
yourNewString = yourNewString.replacingOccurrences(of: "\'", with: "", options: NSString.CompareOptions.literal, range: nil)

По сути, это копия этого другого ответа . Все, что вы сделали, это добавили использование, что достаточно очевидно.
rmaddy 01

Кто-то проголосовал за этот ответ и нашел его действительно полезным, о чем он вам говорит?
Naishta

@Naishta Это говорит вам, что у всех разные мнения, и это нормально
Джош Вольф

3

Это был бы мой подход. Вы можете добавить словарь сущностей из https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555, упомянутого Майклом Уотерфоллом.

extension String {
    func htmlDecoded()->String {

        guard (self != "") else { return self }

        var newStr = self

        let entities = [
            "&quot;"    : "\"",
            "&amp;"     : "&",
            "&apos;"    : "'",
            "&lt;"      : "<",
            "&gt;"      : ">",
        ]

        for (name,value) in entities {
            newStr = newStr.stringByReplacingOccurrencesOfString(name, withString: value)
        }
        return newStr
    }
}

Использованные примеры:

let encoded = "this is so &quot;good&quot;"
let decoded = encoded.htmlDecoded() // "this is so "good""

ИЛИ

let encoded = "this is so &quot;good&quot;".htmlDecoded() // "this is so "good""

1
Мне это не совсем нравится, но я пока не нашел ничего лучше, так что это обновленная версия решения Michael Waterfall для Swift 2.0 gist.github.com/jrmgx/3f9f1d330b295cf6b1c6
jrmgx

3

Элегантное решение Swift 4

Если вам нужна строка,

myString = String(htmlString: encodedString)

добавьте это расширение в свой проект:

extension String {

    init(htmlString: String) {
        self.init()
        guard let encodedData = htmlString.data(using: .utf8) else {
            self = htmlString
            return
        }

        let attributedOptions: [NSAttributedString.DocumentReadingOptionKey : Any] = [
           .documentType: NSAttributedString.DocumentType.html,
           .characterEncoding: String.Encoding.utf8.rawValue
        ]

        do {
            let attributedString = try NSAttributedString(data: encodedData,
                                                          options: attributedOptions,
                                                          documentAttributes: nil)
            self = attributedString.string
        } catch {
            print("Error: \(error.localizedDescription)")
            self = htmlString
        }
    }
}

Если вам нужна NSAttributedString с полужирным шрифтом, курсивом, ссылками и т. Д.,

textField.attributedText = try? NSAttributedString(htmlString: encodedString)

добавьте это расширение в свой проект:

extension NSAttributedString {

    convenience init(htmlString html: String) throws {
        try self.init(data: Data(html.utf8), options: [
            .documentType: NSAttributedString.DocumentType.html,
            .characterEncoding: String.Encoding.utf8.rawValue
            ], documentAttributes: nil)
    }

}

2

Вычисленная вариация ответа @yishus

public extension String {
    /// Decodes string with HTML encoding.
    var htmlDecoded: String {
        guard let encodedData = self.data(using: .utf8) else { return self }

        let attributedOptions: [String : Any] = [
            NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
            NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue]

        do {
            let attributedString = try NSAttributedString(data: encodedData,
                                                          options: attributedOptions,
                                                          documentAttributes: nil)
            return attributedString.string
        } catch {
            print("Error: \(error)")
            return self
        }
    }
}

1

Swift 4

func decodeHTML(string: String) -> String? {

    var decodedString: String?

    if let encodedData = string.data(using: .utf8) {
        let attributedOptions: [NSAttributedString.DocumentReadingOptionKey : Any] = [
            .documentType: NSAttributedString.DocumentType.html,
            .characterEncoding: String.Encoding.utf8.rawValue
        ]

        do {
            decodedString = try NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil).string
        } catch {
            print("\(error.localizedDescription)")
        }
    }

    return decodedString
}

Объяснение было бы в порядке. Например, чем он отличается от предыдущих ответов Swift 4?
Питер Мортенсен

1

Swift 4.1+

var htmlDecoded: String {


    let attributedOptions: [NSAttributedString.DocumentReadingOptionKey : Any] = [

        NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html,
        NSAttributedString.DocumentReadingOptionKey.characterEncoding : String.Encoding.utf8.rawValue
    ]


    let decoded = try? NSAttributedString(data: Data(utf8), options: attributedOptions
        , documentAttributes: nil).string

    return decoded ?? self
} 

Объяснение было бы в порядке. Например, чем он отличается от предыдущих ответов? Какие функции Swift 4.1 используются? Это работает только в Swift 4.1, а не в предыдущих версиях? Или он будет работать до Swift 4.1, скажем, в Swift 4.0?
Питер Мортенсен

1

Swift 4

extension String {
    var replacingHTMLEntities: String? {
        do {
            return try NSAttributedString(data: Data(utf8), options: [
                .documentType: NSAttributedString.DocumentType.html,
                .characterEncoding: String.Encoding.utf8.rawValue
            ], documentAttributes: nil).string
        } catch {
            return nil
        }
    }
}

Простое использование

let clean = "Weeknd &#8216;King Of The Fall&#8217".replacingHTMLEntities ?? "default value"

Я уже слышу, как люди жалуются на мою необязательную развёрнутую силу. Если вы изучаете кодировку строк HTML и не знаете, как работать с опциями Swift, вы слишком далеко опережаете себя.
quemeful

да, есть ( отредактировано 1 ноября в 22:37 и сделало "Простое использование" намного труднее для понимания)
quemeful

1

Swift 4

Мне очень нравится решение с использованием documentAttributes. Однако он может быть слишком медленным для анализа файлов и / или использования в ячейках табличного представления. Не могу поверить, что Apple не предлагает для этого достойного решения.

В качестве обходного пути я нашел это расширение String на GitHub, которое отлично работает и быстро декодируется.

Поэтому для ситуаций, в которых данный ответ является медленным , см. Предлагаемое решение по этой ссылке: https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555

Примечание: он не анализирует HTML-теги.


1

Обновленный ответ, работающий над Swift 3

extension String {
    init?(htmlEncodedString: String) {
        let encodedData = htmlEncodedString.data(using: String.Encoding.utf8)!
        let attributedOptions = [ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType]

        guard let attributedString = try? NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil) else {
            return nil
        }
        self.init(attributedString.string)
   }

0

Objective-C

+(NSString *) decodeHTMLEnocdedString:(NSString *)htmlEncodedString {
    if (!htmlEncodedString) {
        return nil;
    }

    NSData *data = [htmlEncodedString dataUsingEncoding:NSUTF8StringEncoding];
    NSDictionary *attributes = @{NSDocumentTypeDocumentAttribute:     NSHTMLTextDocumentType,
                             NSCharacterEncodingDocumentAttribute:     @(NSUTF8StringEncoding)};
    NSAttributedString *attributedString = [[NSAttributedString alloc]     initWithData:data options:attributes documentAttributes:nil error:nil];
    return [attributedString string];
}

0

Версия Swift 3.0 с преобразованием фактического размера шрифта

Обычно при прямом преобразовании содержимого HTML в строку с атрибутами размер шрифта увеличивается. Вы можете попробовать преобразовать строку HTML в строку с атрибутами и обратно, чтобы увидеть разницу.

Вместо этого, вот преобразование фактического размера, которое гарантирует, что размер шрифта не изменится, путем применения коэффициента 0,75 ко всем шрифтам:

extension String {
    func htmlAttributedString() -> NSAttributedString? {
        guard let data = self.data(using: String.Encoding.utf16, allowLossyConversion: false) else { return nil }
        guard let attriStr = try? NSMutableAttributedString(
            data: data,
            options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
            documentAttributes: nil) else { return nil }
        attriStr.beginEditing()
        attriStr.enumerateAttribute(NSFontAttributeName, in: NSMakeRange(0, attriStr.length), options: .init(rawValue: 0)) {
            (value, range, stop) in
            if let font = value as? UIFont {
                let resizedFont = font.withSize(font.pointSize * 0.75)
                attriStr.addAttribute(NSFontAttributeName,
                                         value: resizedFont,
                                         range: range)
            }
        }
        attriStr.endEditing()
        return attriStr
    }
}

0

Swift 4

extension String {

    mutating func toHtmlEncodedString() {
        guard let encodedData = self.data(using: .utf8) else {
            return
        }

        let attributedOptions: [NSAttributedString.DocumentReadingOptionKey : Any] = [
            NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue): NSAttributedString.DocumentType.html,
            NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue): String.Encoding.utf8.rawValue
        ]

        do {
            let attributedString = try NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil)
            self = attributedString.string
        }
        catch {
            print("Error: \(error)")
        }
    }

Пожалуйста, rawValueсинтаксис NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)и NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)ужасен. Заменить на .documentTypeи.characterEncoding
vadian

Производительность этого решения ужасна. Возможно, это нормально для отдельных приложений, разбор файлов не рекомендуется.
Винсент

0

Взгляните на HTMLString - библиотеку, написанную на Swift, которая позволяет вашей программе добавлять и удалять объекты HTML в строках.

Для полноты картины я скопировал с сайта основные функции:

  • Добавляет объекты для кодировок ASCII и UTF-8 / UTF-16
  • Удаляет более 2100 именованных объектов (например, &)
  • Поддерживает удаление десятичных и шестнадцатеричных чисел
  • Разработан для поддержки Swift Extended Grapheme Clusters (→ 100% защита от смайликов)
  • Полностью модульное тестирование
  • Быстро
  • документированный
  • Совместим с Objective-C

0

Версия Swift 5.1

import UIKit

extension String {

    init(htmlEncodedString: String) {
        self.init()
        guard let encodedData = htmlEncodedString.data(using: .utf8) else {
            self = htmlEncodedString
            return
        }

        let attributedOptions: [NSAttributedString.DocumentReadingOptionKey : Any] = [
            .documentType: NSAttributedString.DocumentType.html,
            .characterEncoding: String.Encoding.utf8.rawValue
        ]

        do {
            let attributedString = try NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil)
            self = attributedString.string
        } 
        catch {
            print("Error: \(error)")
            self = htmlEncodedString
        }
    }
}

Кроме того, если вы хотите извлечь дату, изображения, метаданные, заголовок и описание, вы можете использовать мой модуль с именем:

] [1],

Комплект для чтения


Что же не позволяет ему работать в некоторых предыдущих версиях, Swift 5.0, Swift 4.1, Swift 4.0 и т. Д.?
Питер Мортенсен

Я обнаружил ошибку при декодировании строки с помощью collectionViews
Tung Vu Duc

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.