Як розшифрувати сутність 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"])

Відповіді:


157

Ця відповідь востаннє переглянута для Swift 5.2 та iOS 13.4 SDK.


Немає простого способу зробити це, але ви можете використовувати 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
Що? Розширення призначені для розширення існуючих типів, щоб забезпечити нову функціональність.
акашівський

4
Я розумію, що ви намагаєтесь сказати, але нехтувати розширеннями - це не шлях.
акашівський

1
@akashivskyy: Для того, щоб правильно зробити цю роботу з не-ASCII символи , які ви повинні додати NSCharacterEncodingDocumentAttribute, порівняти stackoverflow.com/a/27898167/1187415 .
Мартін Р

13
Цей метод надзвичайно важкий і не рекомендується при перегляді таблиць або сітки
Guido Lodetti

1
Це чудово! Хоча він блокує основну нитку, чи є спосіб запустити її у фонову нитку?
MMV

78

Відповідь акашивського чудова і демонструє, як використовувати 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-об'єктів.)

Швидкий 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.  @

Швидкий 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
    }
}

Швидкий 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 Сантьяго виправляє помилки часу компіляції, але вилучення strtooul(string, nil, base)повністю призведе до того, що код не працюватиме з сукупностями чисельних символів і збивається, коли мова йде про сутність, яку вона не розпізнає (замість того, щоб вийти з ладу витончено).
Адела Чанг

1
@AdelaChang: Насправді я перетворив свою відповідь на Swift 2 вже у вересні 2015 року. Вона все ще збирається без попереджень із Swift 2.2 / Xcode 7.3. Або ви посилаєтесь на версію Майкла?
Мартін Р

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
        }
    }
}

Чудово працює. Оригінальна відповідь спричинила дивні збої. Дякуємо за оновлення!
Геохерна

Для французьких персонажів я повинен використовувати utf16
Sébastien REMY

23

Швидкий 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 !. Використання // нехай кодується = "Тиждень & # 8216; Король осені & # 8217;" нехай 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
         }
     }
 }

Цей код є неповним і його слід уникати будь-якими способами. Помилка не використовується належним чином. Коли насправді код помилки може вийти з ладу. Ви повинні оновити свій код, щоб принаймні повернути нуль, коли є помилка. Або ви могли просто запустити оригінальну струну. Зрештою, вам слід впоратися з помилкою. Що не так. Оце Так!
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" Файл не вдалося відкрити, оскільки він не в правильному форматі. "" Коли я намагаюся використовувати це. Це втрачається, якщо я запускаю повний улов на основну нитку. Я виявив це, перевіривши документацію NSAttributedString: "Імпортер HTML не повинен викликатись із фонового потоку (тобто словник параметрів включає documentType зі значенням html). Він буде намагатися синхронізуватися з основним потоком, не вдатися і час вийшов."
MickeDG

8
Будь ласка, rawValueсинтаксис NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)і NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)жахливий. Замініть його на .documentTypeі.characterEncoding
vadian

@MickeDG - Чи можете ви поясніть, що саме ви зробили для усунення цієї помилки? Я отримую це спорадично.
Росс Барбіш

@RossBarbish - Вибачте Росс, це було дуже давно, не можу згадати подробиці. Ви спробували те, що я пропоную в коментарі вище, тобто виконувати повний улов на основну нитку?
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 Weeknd" - співак, і так, саме так пишеться його ім'я.
wLc

5

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

Пакет HTMLEntitiesпрацює з посиланнями символів на HTML4, а також шістнадцятковими / децифрованими посиланнями символів, і він розпізнає спеціальні числові посилання символів на специфікацію HTML W3 HTML5 (тобто не &#x80;слід розміщувати як знак Євро (unicode 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>"

І для прикладу ОП:

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), уникнуті HTML (&nbsp;͡&deg;&nbsp;͜ʖ&nbsp;͡&deg;&nbsp;), тоді як жоден з інших відповідей не вдається.
Стефан Копін

5

Швидкий 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

хтось схвалив цю відповідь і вважав її дійсно корисною, що це говорить?
Найшта

@Naishta Це говорить вам про те, що всі мають різні думки, і це нормально
Джош Вулф

3

Це був би мій підхід. Ви можете додати словник сутностей із https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555 згадує Michael Waterfall.

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
Мені це не дуже подобається, але нічого кращого я не знайшов, тому це оновлена ​​версія рішення Майкла Водоспаду для 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

Швидкий 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

Швидкий 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

Швидкий 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

Швидкий 4

Мені дуже подобається рішення, використовуючи documentAttributes. Однак це може бути надто повільним для розбору файлів та / або використання в осередках подання таблиці. Я не можу повірити, що Apple не пропонує гідного рішення для цього.

Як вирішення, я знайшов це String Extension на 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

Ціль-С

+(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

Швидкий 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
вадиан

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

0

Погляньте на HTMLString - бібліотеку, написану в Swift, яка дозволяє вашій програмі додавати та видаляти HTML-об’єкти в Strings

Для повноти я скопіював основні функції з сайту:

  • Додає об'єкти для кодувань ASCII і UTF-8 / UTF-16
  • Вилучає понад 2100 названих об'єктів (як &)
  • Підтримує видалення десяткових і шістнадцяткових сутностей
  • Призначений для підтримки розширених кластерних графемів Swift (→ 100% захищений від смайлів)
  • Повністю перевірений блок
  • Швидкий
  • Документовано
  • Сумісний з Objective-C

0

Версія 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.