Come si decodificano le entità HTML in Swift?


121

Sto estraendo un file JSON da un sito e una delle stringhe ricevute è:

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

Come posso convertire cose come &#8216 nei caratteri corretti?

Ho creato un Xcode Playground per dimostrarlo:

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

Risposte:


157

Questa risposta è stata rivista l'ultima volta per Swift 5.2 e iOS 13.4 SDK.


Non esiste un modo semplice per farlo, ma puoi usarlo NSAttributedString magia per rendere questo processo il più indolore possibile (tieni presente che questo metodo eliminerà anche tutti i tag HTML).

Ricorda di inizializzare solo NSAttributedStringdal thread principale . Utilizza WebKit per analizzare l'HTML sottostante, quindi il requisito.

// 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
Che cosa? Le estensioni hanno lo scopo di estendere i tipi esistenti per fornire nuove funzionalità.
akashivskyy

4
Capisco cosa stai cercando di dire, ma negare le estensioni non è la strada da percorrere.
akashivskyy

1
@akashivskyy: Per fare questo lavoro correttamente con caratteri non-ASCII si deve aggiungere un NSCharacterEncodingDocumentAttribute, confrontare stackoverflow.com/a/27898167/1187415 .
Martin R

13
Questo metodo è estremamente pesante e non è consigliato in tableviews o gridviews
Guido Lodetti

1
È fantastico! Sebbene blocchi il thread principale, esiste un modo per eseguirlo nel thread in background?
MMV

78

La risposta di @ akashivskyy è ottima e dimostra come utilizzarla NSAttributedStringper decodificare entità HTML. Un possibile svantaggio (come ha affermato) è che anche tutto il markup HTML viene rimosso, quindi

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

diventa

4 < 5 & 3 > 2

Su OS X c'è CFXMLCreateStringByUnescapingEntities()chi fa il lavoro:

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

ma questo non è disponibile su iOS.

Ecco una pura implementazione di Swift. Decodifica i riferimenti alle entità carattere come l' &lt;utilizzo di un dizionario e tutte le entità carattere numerico come &#64o &#x20ac. (Nota che non ho elencato esplicitamente tutte le 252 entità 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
    }
}

Esempio:

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
È fantastico, grazie Martin! Ecco l'estensione con l'elenco completo delle entità HTML: gist.github.com/mwaterfall/25b4a6a06dc3309d9555 L'ho anche leggermente adattata per fornire gli offset di distanza effettuati dalle sostituzioni. Ciò consente la corretta regolazione di eventuali attributi di stringa o entità che potrebbero essere influenzati da queste sostituzioni (ad esempio gli indici di entità di Twitter).
Michael Waterfall

3
@MichaelWaterfall e Martin questo è magnifico! funziona come un fascino! Aggiorna l'estensione per Swift 2 pastebin.com/juHRJ6au Grazie!
Santiago,

1
Ho convertito questa risposta per renderla compatibile con Swift 2 e l'ho scaricata in un CocoaPod chiamato StringExtensionHTML per facilità d'uso. Si noti che la versione Swift 2 di Santiago corregge gli errori in fase di compilazione, ma la rimozione del strtooul(string, nil, base)tutto farà sì che il codice non funzioni con entità di caratteri numerici e si blocchi quando si tratta di un'entità che non riconosce (invece di fallire con grazia).
Adela Chang

1
@AdelaChang: In realtà avevo convertito la mia risposta in Swift 2 già a settembre 2015. Si compila ancora senza avvisi con Swift 2.2 / Xcode 7.3. O ti riferisci alla versione di Michael?
Martin R

1
Grazie, con questa risposta ho risolto i miei problemi: ho avuto seri problemi di prestazioni utilizzando NSAttributedString.
Andrea Mugnaini

27

Versione Swift 3 dell'estensione di @ 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
        }
    }
}

Funziona alla grande. La risposta originale stava causando uno strano incidente. Grazie per l'aggiornamento!
Geoherna

Per i caratteri francesi devo usare utf16
Sébastien REMY

23

Swift 4


  • Variabile calcolata con estensione stringa
  • Senza guardie extra, fai, prendi, ecc ...
  • Restituisce le stringhe originali se la decodifica fallisce

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 ! funziona subito per Swift 4!. Utilizzo // let encoded = "The Weeknd & # 8216; King Of The Fall & # 8217;" let finalString = encoded.htmlDecoded
Naishta

2
Amo la semplicità di questa risposta. Tuttavia, causerà arresti anomali se eseguito in background perché tenta di essere eseguito sul thread principale.
Jeremy Hicks

14

Versione Swift 2 dell'estensione di @ 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
         }
     }
 }

Questo codice è incompleto e dovrebbe essere evitato con tutti i mezzi. L'errore non viene gestito correttamente. Quando c'è in effetti un codice di errore si blocca. Dovresti aggiornare il tuo codice per restituire almeno zero in caso di errore. Oppure potresti semplicemente inizializzare con la stringa originale. Alla fine dovresti gestire l'errore. Non è così. Wow!
oyalhi

9

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

Ottengo "Error Domain = NSCocoaErrorDomain Code = 259" Il file non può essere aperto perché non è nel formato corretto. "" Quando provo a usarlo. Questo scompare se eseguo il blocco completo sul thread principale. L'ho trovato controllando la documentazione di NSAttributedString: "L'importatore HTML non dovrebbe essere chiamato da un thread in background (ovvero, il dizionario delle opzioni include documentType con un valore di html). Proverà a sincronizzarsi con il thread principale, fallirà e tempo scaduto."
MickeDG

8
Per favore, la rawValuesintassi NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)ed NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)è orribile. Sostituiscilo con .documentTypee.characterEncoding
vadian

@MickeDG - Puoi spiegare cosa hai fatto esattamente per risolvere questo errore? Lo ricevo sporaticamente.
Ross Barbish

@ RossBarbish - Scusa Ross, è stato troppo tempo fa, non ricordo i dettagli. Hai provato quello che ti suggerisco nel commento sopra, ovvero di eseguire il full do catch sul thread principale?
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" : Non "The Weekend" ?
Peter Mortensen

L'evidenziazione della sintassi sembra strana, specialmente la parte del commento dell'ultima riga. Puoi aggiustarlo?
Peter Mortensen il

"The Weeknd" è un cantante, e sì, è così che viene scritto il suo nome.
wLc

5

Stavo cercando un'utilità Swift 3.0 pura per sfuggire a / unescape dai riferimenti ai caratteri HTML (ad esempio per le app Swift lato server su macOS e Linux) ma non ho trovato alcuna soluzione completa, quindi ho scritto la mia implementazione: https: //github.com/IBM-Swift/swift-html-entities

Il pacchetto, HTMLEntitiesfunziona con riferimenti a caratteri denominati HTML4 nonché riferimenti a caratteri numerici esadecimali / dec, e riconoscerà riferimenti a caratteri numerici speciali per le specifiche HTML5 di W3 (cioè &#x80;dovrebbe essere senza caratteri di escape come segno Euro (unicode U+20AC) e NON come unicode carattere per U+0080e alcuni intervalli di riferimenti a caratteri numerici devono essere sostituiti con il carattere sostitutivo U+FFFDquando si elimina l'escape).

Esempio di utilizzo:

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

E per l'esempio di 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 "

Modifica: HTMLEntitiesora supporta i riferimenti a caratteri con nome HTML5 a partire dalla versione 2.0.0. Viene implementata anche l'analisi conforme alle specifiche.


1
Questa è la risposta più generica che funziona sempre e non richiede di essere eseguita sul thread principale. Funzionerà anche con le stringhe Unicode con escape HTML più complesse (come (&nbsp;͡&deg;&nbsp;͜ʖ&nbsp;͡&deg;&nbsp;)), mentre nessuna delle altre risposte lo gestisce.
Stéphane Copin

5

Swift 4:

La soluzione totale che alla fine ha funzionato per me con codice HTML e caratteri di nuova riga e virgolette singole

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

Uso:

let yourStringEncoded = yourStringWithHtmlcode.htmlDecoded

Ho quindi dovuto applicare altri filtri per eliminare le virgolette singole (ad esempio, non farlo , non ha , è , ecc.) E i caratteri di nuova riga come \n:

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

Questa è essenzialmente una copia di quest'altra risposta . Tutto quello che hai fatto è aggiungere un po 'di utilizzo che è abbastanza ovvio.
rmaddy

qualcuno ha votato positivamente questa risposta e l'ha trovata davvero utile, cosa ti dice?
Naishta

@Naishta Ti dice che ognuno ha opinioni diverse e va bene
Josh Wolff il

3

Questo sarebbe il mio approccio. Puoi aggiungere il dizionario delle entità da https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555 Michael Waterfall menziona.

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

Esempi utilizzati:

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

O

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

1
Non mi piace molto ma non ho ancora trovato niente di meglio, quindi questa è una versione aggiornata della soluzione Michael Waterfall per Swift 2.0 gist.github.com/jrmgx/3f9f1d330b295cf6b1c6
jrmgx

3

Elegante soluzione Swift 4

Se vuoi una stringa,

myString = String(htmlString: encodedString)

aggiungi questa estensione al tuo progetto:

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

Se desideri una stringa NSAttributedString con grassetto, corsivo, link, ecc.,

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

aggiungi questa estensione al tuo progetto:

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

Versione var calcolata della risposta di @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
}

Sarebbe necessaria una spiegazione. Ad esempio, in che modo è diverso dalle precedenti risposte di Swift 4?
Peter Mortensen il

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
} 

Sarebbe necessaria una spiegazione. Ad esempio, in che modo è diverso dalle risposte precedenti? Quali funzionalità di Swift 4.1 vengono utilizzate? Funziona solo in Swift 4.1 e non nelle versioni precedenti? O funzionerebbe prima di Swift 4.1, diciamo in Swift 4.0?
Peter Mortensen il

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

Utilizzo semplice

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

Posso già sentire le persone che si lamentano della mia forza facoltativa scartata. Se stai cercando la codifica di stringhe HTML e non sai come gestire gli optionals di Swift, sei troppo avanti a te stesso.
simpatico

sì, c'è stato ( modificato il 1 ° novembre alle 22:37 e reso l '"Uso semplice" molto più difficile da comprendere)
simpatico

1

Swift 4

Mi piace molto la soluzione che utilizza documentAttributes. Tuttavia, potrebbe essere troppo lento per l'analisi dei file e / o l'utilizzo nelle celle della vista tabella. Non posso credere che Apple non fornisca una soluzione decente per questo.

Come soluzione alternativa, ho trovato questa estensione di stringa su GitHub che funziona perfettamente ed è veloce per la decodifica.

Quindi, per situazioni in cui la risposta data è quella di rallentare , vedere la soluzione suggerita in questo link: https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555

Nota: non analizza i tag HTML.


1

Risposta aggiornata lavorando su 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

Versione Swift 3.0 con conversione della dimensione del carattere effettiva

Normalmente, se converti direttamente il contenuto HTML in una stringa con attributi, la dimensione del carattere viene aumentata. Puoi provare a convertire una stringa HTML in una stringa attribuita e viceversa per vedere la differenza.

Invece, ecco la conversione della dimensione effettiva che assicura che la dimensione del carattere non cambi, applicando il rapporto 0,75 su tutti i caratteri:

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

Per favore, la rawValuesintassi NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)ed NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)è orribile. Sostituiscilo con .documentTypee.characterEncoding
vadian

Le prestazioni di questa soluzione sono orribili. Forse va bene per caes separati, l'analisi dei file non è consigliata.
Vincent

0

Dai un'occhiata a HTMLString, una libreria scritta in Swift che consente al tuo programma di aggiungere e rimuovere entità HTML in stringhe

Per completezza ho copiato le caratteristiche principali dal sito:

  • Aggiunge entità per le codifiche ASCII e UTF-8 / UTF-16
  • Rimuove più di 2100 entità denominate (come &)
  • Supporta la rimozione di entità decimali ed esadecimali
  • Progettato per supportare Swift Extended Grapheme Clusters (→ 100% a prova di emoji)
  • Unità completamente testata
  • Veloce
  • documentato
  • Compatibile con Objective-C

0

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

Inoltre, se desideri estrarre data, immagini, metadati, titolo e descrizione, puoi utilizzare il mio pod denominato:

] [1].

Kit di leggibilità


Cos'è che non funzionerebbe in alcune versioni precedenti, Swift 5.0, Swift 4.1, Swift 4.0, ecc.?
Peter Mortensen il

Ho trovato un errore durante la decodifica della stringa utilizzando collectionViews
Tung Vu Duc

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.