Bagaimana cara mendekode entitas HTML di Swift?


121

Saya menarik file JSON dari situs dan salah satu string yang diterima adalah:

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

Bagaimana saya bisa mengubah hal-hal seperti &#8216menjadi karakter yang benar?

Saya telah membuat Xcode Playground untuk mendemonstrasikannya:

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"])

Jawaban:


157

Jawaban ini terakhir direvisi untuk Swift 5.2 dan iOS 13.4 SDK.


Tidak ada cara langsung untuk melakukan itu, tetapi Anda dapat menggunakan NSAttributedStringsihir untuk membuat proses ini semudah mungkin (diperingatkan bahwa metode ini akan menghapus semua tag HTML juga).

Ingatlah untuk menginisialisasi hanya NSAttributedStringdari utas utama . Ia menggunakan WebKit untuk mem-parsing HTML di bawahnya, demikian persyaratannya.

// 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
Apa? Ekstensi dimaksudkan untuk memperluas jenis yang ada untuk menyediakan fungsionalitas baru.
akashivskyy

4
Saya mengerti apa yang ingin Anda katakan, tetapi meniadakan ekstensi bukanlah cara yang tepat.
akashivskyy

1
@akashivskyy: Untuk membuat ini bekerja dengan benar dengan karakter non-ASCII Anda harus menambahkan NSCharacterEncodingDocumentAttribute, bandingkan stackoverflow.com/a/27898167/1187415 .
Martin R

13
Metode ini sangat berat dan tidak direkomendasikan dalam tampilan
tabel

1
Ini bagus! Meskipun memblokir thread utama, apakah ada cara untuk menjalankannya di thread latar belakang?
MMV

78

@Akashivskyy Jawabannya bagus dan menunjukkan bagaimana memanfaatkan NSAttributedStringuntuk memecahkan kode entitas HTML. Satu kemungkinan kerugian (seperti yang dia nyatakan) adalah bahwa semua markup HTML juga dihapus, jadi

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

menjadi

4 < 5 & 3 > 2

Di OS X ada CFXMLCreateStringByUnescapingEntities()yang berfungsi:

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.  @ 

tapi ini tidak tersedia di iOS.

Ini adalah implementasi Swift murni. Ini menerjemahkan referensi entitas karakter seperti &lt;menggunakan kamus, dan semua entitas karakter numerik seperti &#64atau &#x20ac. (Perhatikan bahwa saya tidak mencantumkan semua 252 entitas HTML secara eksplisit.)

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

Contoh:

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.  @

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

Cepat 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
Ini luar biasa, terima kasih Martin! Berikut ekstensi dengan daftar lengkap entitas HTML: gist.github.com/mwaterfall/25b4a6a06dc3309d9555 Saya juga sedikit mengadaptasinya untuk memberikan offset jarak yang dibuat oleh penggantian. Hal ini memungkinkan penyesuaian yang benar atas setiap atribut atau entitas string yang mungkin terpengaruh oleh penggantian ini (indeks entitas Twitter misalnya).
Air Terjun Michael

3
@MichaelWaterfall dan Martin ini luar biasa! bekerja seperti pesona! Saya memperbarui ekstensi untuk Swift 2 pastebin.com/juHRJ6au Terima kasih!
Santiago

1
Saya mengonversi jawaban ini agar kompatibel dengan Swift 2 dan membuangnya ke dalam CocoaPod yang disebut StringExtensionHTML untuk kemudahan penggunaan. Perhatikan bahwa versi Santiago Swift 2 memperbaiki kesalahan waktu kompilasi, tetapi menghapus strtooul(string, nil, base)seluruhnya akan menyebabkan kode tidak berfungsi dengan entitas karakter numerik dan macet ketika datang ke entitas yang tidak dikenali (bukannya gagal dengan baik).
Adela Chang

1
@AdelaChang: Sebenarnya saya telah mengonversi jawaban saya ke Swift 2 pada September 2015. Itu masih dikompilasi tanpa peringatan dengan Swift 2.2 / Xcode 7.3. Atau apakah Anda mengacu pada versi Michael?
Martin R

1
Terima kasih, dengan jawaban ini saya menyelesaikan masalah saya: Saya mengalami masalah kinerja yang serius menggunakan NSAttributedString.
Andrea Mugnaini

27

Versi Swift 3 dari ekstensi @ 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
        }
    }
}

Bekerja dengan baik. Jawaban asli menyebabkan kecelakaan aneh. Terima kasih atas pembaruannya!
Geoherna

Untuk karakter perancis saya harus menggunakan utf16
Sébastien REMY

23

Cepat 4


  • Variabel yang dihitung ekstensi string
  • Tanpa penjagaan ekstra, lakukan, tangkap, dll ...
  • Mengembalikan string asli jika decoding gagal

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
Wow ! berfungsi langsung dari kotak untuk Swift 4!. Penggunaan // let encoded = "The Weeknd & # 8216; King Of The Fall & # 8217;" let finalString = encoded.htmlDecoded
Naishta

2
Saya suka kesederhanaan dari jawaban ini. Namun, ini akan menyebabkan crash saat dijalankan di latar belakang karena mencoba berjalan di thread utama.
Jeremy Hicks

14

Versi Swift 2 dari ekstensi @ 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
         }
     }
 }

Kode ini tidak lengkap dan harus dihindari dengan segala cara. Kesalahan tidak ditangani dengan benar. Ketika ternyata ada kode kesalahan akan crash. Anda harus memperbarui kode Anda setidaknya mengembalikan nihil ketika ada kesalahan. Atau Anda bisa memulai dengan string asli. Pada akhirnya Anda harus menangani kesalahan tersebut. Yang tidak demikian. Wow!
oyalhi

9

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

Saya mendapatkan "Error Domain = NSCocoaErrorDomain Code = 259" File tidak dapat dibuka karena tidak dalam format yang benar. "" Ketika saya mencoba menggunakan ini. Ini hilang jika saya menjalankan penuh lakukan tangkap di utas utama. Saya menemukan ini dari memeriksa dokumentasi NSAttributedString: "Pengimpor HTML tidak boleh dipanggil dari utas latar belakang (yaitu, kamus opsi menyertakan documentType dengan nilai html). Ini akan mencoba menyinkronkan dengan utas utama, gagal, dan waktu habis."
MickeDG

8
Tolong, rawValuesintaksnya NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)dan NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)itu mengerikan. Gantilah dengan .documentTypedan.characterEncoding
vadian

@MickeDG - Dapatkah Anda menjelaskan apa yang sebenarnya Anda lakukan untuk mengatasi kesalahan ini? Saya mendapatkannya secara sporatis.
Ross Barbish

@RossBarbish - Maaf Ross, ini terlalu lama, tidak dapat mengingat detailnya. Sudahkah Anda mencoba apa yang saya sarankan pada komentar di atas, yaitu menjalankan secara penuh do catch di thread utama?
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" : Bukan "The Weekend" ?
Peter Mortensen

Penyorotan sintaks terlihat aneh, terutama bagian komentar pada baris terakhir. Bisakah Anda memperbaikinya?
Peter Mortensen

"The Weeknd" adalah seorang penyanyi, dan ya, itulah cara namanya dieja.
wLc

5

Saya mencari utilitas Swift 3.0 murni untuk melarikan diri ke / melepaskan diri dari referensi karakter HTML (yaitu untuk aplikasi Swift sisi server di macOS dan Linux) tetapi tidak menemukan solusi yang komprehensif, jadi saya menulis implementasi saya sendiri: https: //github.com/IBM-Swift/swift-html-entities

Paket,, HTMLEntitiesbekerja dengan referensi karakter bernama HTML4 serta referensi karakter numerik hex / dec, dan itu akan mengenali referensi karakter numerik khusus sesuai spesifikasi W3 HTML5 (yaitu &#x80;harus dilepas sebagai tanda Euro (unicode U+20AC) dan BUKAN sebagai unicode karakter untuk U+0080, dan rentang tertentu dari referensi karakter numerik harus diganti dengan karakter pengganti U+FFFDsaat melepas).

Contoh penggunaan:

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

Dan untuk contoh 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 "

Edit: HTMLEntitiessekarang mendukung referensi karakter bernama HTML5 pada versi 2.0.0. Penguraian yang sesuai spesifikasi juga diterapkan.


1
Ini adalah jawaban paling umum yang berfungsi sepanjang waktu, dan tidak perlu dijalankan di utas utama. Ini akan bekerja bahkan dengan string unicode lolos HTML paling kompleks (seperti (&nbsp;͡&deg;&nbsp;͜ʖ&nbsp;͡&deg;&nbsp;)), sedangkan tidak ada jawaban lain yang mengaturnya.
Stéphane Copin

5

Cepat 4:

Solusi total yang akhirnya berhasil untuk saya dengan kode HTML dan karakter baris baru dan tanda kutip tunggal

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

Pemakaian:

let yourStringEncoded = yourStringWithHtmlcode.htmlDecoded

Saya kemudian harus menerapkan beberapa filter lagi untuk menghilangkan tanda kutip tunggal (misalnya, jangan , belum , Ini , dll.), Dan karakter baris baru seperti \n:

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

Ini pada dasarnya adalah salinan dari jawaban lain ini . Yang Anda lakukan hanyalah menambahkan beberapa penggunaan yang cukup jelas.
rmaddy

seseorang telah memberi suara positif pada jawaban ini dan merasa sangat berguna, apa artinya itu bagi Anda?
Naishta

@Naishta Ini memberitahu Anda bahwa setiap orang memiliki pendapat yang berbeda dan tidak apa-apa
Josh Wolff

3

Ini akan menjadi pendekatan saya. Anda dapat menambahkan kamus entitas dari https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555 menyebutkan 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
    }
}

Contoh yang digunakan:

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

ATAU

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

1
Saya tidak begitu suka ini tetapi saya belum menemukan yang lebih baik jadi ini adalah versi terbaru dari solusi Air Terjun Michael untuk Swift 2.0 gist.github.com/jrmgx/3f9f1d330b295cf6b1c6
jrmgx

3

Solusi Swift 4 yang Elegan

Jika Anda menginginkan tali,

myString = String(htmlString: encodedString)

tambahkan ekstensi ini ke proyek Anda:

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

Jika Anda menginginkan NSAttributedString dengan huruf tebal, miring, tautan, dll.,

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

tambahkan ekstensi ini ke proyek Anda:

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

Versi komputasi var dari jawaban @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

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

Penjelasannya akan teratur. Misalnya, apa bedanya dengan jawaban Swift 4 sebelumnya?
Peter Mortensen

1

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

Penjelasannya akan teratur. Misalnya, apa bedanya dengan jawaban sebelumnya? Fitur Swift 4.1 apa yang digunakan? Apakah ini hanya berfungsi di Swift 4.1 dan tidak di versi sebelumnya? Atau apakah itu akan berfungsi sebelum Swift 4.1, katakanlah di Swift 4.0?
Peter Mortensen

1

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

Penggunaan Sederhana

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

Saya sudah bisa mendengar orang-orang mengeluh tentang pilihan saya membuka bungkus paksa. Jika Anda meneliti pengkodean string HTML dan Anda tidak tahu bagaimana menangani opsi Swift, Anda terlalu jauh di depan diri Anda sendiri.
Quemeful

ya, ada ( diedit 1 November pukul 22:37 dan membuat "Penggunaan Sederhana" jauh lebih sulit untuk dipahami)
tenang pada

1

Cepat 4

Saya sangat menyukai solusi yang menggunakan documentAttributes. Namun, ini mungkin terlalu lambat untuk mengurai file dan / atau penggunaan dalam sel tampilan tabel. Saya tidak percaya Apple tidak memberikan solusi yang layak untuk ini.

Sebagai solusinya, saya menemukan Ekstensi String ini di GitHub yang berfungsi dengan sempurna dan cepat untuk decoding.

Jadi untuk situasi di mana jawaban yang diberikan lambat , lihat solusi yang disarankan di tautan ini: https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555

Catatan: ini tidak mengurai tag HTML.


1

Jawaban yang diperbarui bekerja pada 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

Versi Swift 3.0 dengan konversi ukuran font sebenarnya

Biasanya, jika Anda langsung mengonversi konten HTML ke string yang diatribusikan, ukuran font akan bertambah. Anda dapat mencoba mengonversi string HTML menjadi string yang diatribusikan dan kembali lagi untuk melihat perbedaannya.

Sebagai gantinya, berikut adalah konversi ukuran sebenarnya yang memastikan ukuran font tidak berubah, dengan menerapkan rasio 0,75 pada semua font:

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

Cepat 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)")
        }
    }

Tolong, rawValuesintaksnya NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)dan NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)itu mengerikan. Gantilah dengan .documentTypedan.characterEncoding
vadian

Kinerja solusi ini sangat buruk. Mungkin tidak masalah untuk caes terpisah, penguraian file tidak disarankan.
Vincent

0

Lihat HTMLString - pustaka yang ditulis dalam Swift yang memungkinkan program Anda menambah dan menghapus entitas HTML di Strings

Untuk kelengkapan, saya menyalin fitur utama dari situs:

  • Menambahkan entitas untuk encoding ASCII dan UTF-8 / UTF-16
  • Menghapus lebih dari 2100 entitas bernama (seperti &)
  • Mendukung penghapusan entitas desimal dan heksadesimal
  • Didesain untuk mendukung Swift Extended Grapheme Clusters (→ 100% emoji-proof)
  • Unit teruji sepenuhnya
  • Cepat
  • Didokumentasikan
  • Kompatibel dengan Objective-C

0

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

Juga, jika Anda ingin mengekstrak tanggal, gambar, metadata, judul dan deskripsi, Anda dapat menggunakan pod saya yang bernama:

] [1].

Kit keterbacaan


Apa yang tidak membuatnya berfungsi di beberapa versi sebelumnya, Swift 5.0, Swift 4.1, Swift 4.0, dll.?
Peter Mortensen

Saya menemukan kesalahan saat mendekode string menggunakan collectionViews
Tung Vu Duc

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.