Comment décoder des entités HTML dans Swift?


121

Je tire un fichier JSON d'un site et l'une des chaînes reçues est:

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

Comment puis-je convertir des éléments tels que &#8216les caractères corrects?

J'ai créé un Xcode Playground pour le démontrer:

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

Réponses:


157

Cette réponse a été révisée pour la dernière fois pour Swift 5.2 et iOS 13.4 SDK.


Il n'y a pas de moyen simple de le faire, mais vous pouvez utiliser la NSAttributedStringmagie pour rendre ce processus aussi indolore que possible (sachez que cette méthode supprimera également toutes les balises HTML).

N'oubliez pas d' initialiser à NSAttributedStringpartir du thread principal uniquement . Il utilise WebKit pour analyser le HTML en dessous, donc l'exigence.

// 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
Quoi? Les extensions sont destinées à étendre les types existants pour fournir de nouvelles fonctionnalités.
akashivskyy

4
Je comprends ce que vous essayez de dire, mais l'annulation des extensions n'est pas la solution.
akashivskyy

1
@akashivskyy: Pour que cela fonctionne correctement avec des caractères non ASCII, vous devez ajouter un NSCharacterEncodingDocumentAttribute, comparez stackoverflow.com/a/27898167/1187415 .
Martin R

13
Cette méthode est extrêmement lourde et n'est pas recommandée dans les vues de table ou de grille
Guido Lodetti

1
C'est bien! Bien qu'il bloque le thread principal, existe-t-il un moyen de l'exécuter dans le thread d'arrière-plan?
MMV

78

La réponse de @ akashivskyy est excellente et montre comment utiliser NSAttributedStringpour décoder des entités HTML. Un inconvénient possible (comme il l'a dit) est que tout le balisage HTML est également supprimé, donc

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

devient

4 < 5 & 3 > 2

Sur OS X, il y a CFXMLCreateStringByUnescapingEntities()ce qui fait le travail:

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

mais ce n'est pas disponible sur iOS.

Voici une implémentation pure de Swift. Il décode les références d'entités de caractères comme l' &lt;utilisation d'un dictionnaire, et toutes les entités de caractères numériques comme &#64ou &#x20ac. (Notez que je n'ai pas répertorié les 252 entités HTML explicitement.)

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

Exemple:

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
C'est génial, merci Martin! Voici l'extension avec la liste complète des entités HTML: gist.github.com/mwaterfall/25b4a6a06dc3309d9555 Je l'ai également légèrement adaptée pour fournir les décalages de distance effectués par les remplacements. Cela permet l'ajustement correct de tout attribut de chaîne ou entité susceptible d'être affecté par ces remplacements (index d'entité Twitter par exemple).
Michael Waterfall

3
@MichaelWaterfall et Martin c'est magnifique! fonctionne comme un charme! Je mets à jour l'extension pour Swift 2 pastebin.com/juHRJ6au Merci!
Santiago du

1
J'ai converti cette réponse pour qu'elle soit compatible avec Swift 2 et l'ai sauvegardée dans un CocoaPod appelé StringExtensionHTML pour une facilité d'utilisation. Notez que la version Swift 2 de Santiago corrige les erreurs de compilation, mais en supprimant strtooul(string, nil, base)entièrement le code, le code ne fonctionnera pas avec les entités de caractères numériques et plantera lorsqu'il s'agit d'une entité qu'il ne reconnaît pas (au lieu d'échouer gracieusement).
Adela Chang

1
@AdelaChang: En fait, j'avais déjà converti ma réponse en Swift 2 en septembre 2015. Il se compile toujours sans avertissement avec Swift 2.2 / Xcode 7.3. Ou faites-vous référence à la version de Michael?
Martin R

1
Merci, avec cette réponse, j'ai résolu mes problèmes: j'ai eu de sérieux problèmes de performances en utilisant NSAttributedString.
Andrea Mugnaini

27

Version Swift 3 de l'extension @ 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
        }
    }
}

Fonctionne très bien. La réponse originale provoquait un crash étrange. Merci pour la mise à jour!
Geoherna

Pour les caractères français je dois utiliser utf16
Sébastien REMY

23

Swift 4


  • Variable calculée d'extension de chaîne
  • Sans garde supplémentaire, faites, attrapez, etc ...
  • Renvoie les chaînes d'origine si le décodage échoue

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
Hou la la ! fonctionne dès la sortie de la boîte pour Swift 4!. Utilisation // let encoded = "The Weeknd & # 8216; King Of The Fall & # 8217;" let finalString = encoded.htmlDecoded
Naishta

2
J'adore la simplicité de cette réponse. Cependant, il provoquera des plantages lorsqu'il est exécuté en arrière-plan car il tente de s'exécuter sur le thread principal.
Jeremy Hicks

14

Version Swift 2 de l'extension @ 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
         }
     }
 }

Ce code est incomplet et doit être évité par tous les moyens. L'erreur n'est pas gérée correctement. Quand il y a en fait un code d'erreur se planterait. Vous devez mettre à jour votre code pour au moins renvoyer nil en cas d'erreur. Ou vous pouvez simplement lancer avec la chaîne d'origine. En fin de compte, vous devez gérer l'erreur. Ce qui n'est pas le cas. Hou la la!
oyalhi

9

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

J'obtiens "Error Domain = NSCocoaErrorDomain Code = 259" Le fichier n'a pas pu être ouvert car il n'est pas dans le format correct. "" Quand j'essaye d'utiliser ceci. Cela disparaît si je lance la capture complète sur le fil principal. J'ai trouvé ceci en vérifiant la documentation NSAttributedString: "L'importateur HTML ne doit pas être appelé à partir d'un thread d'arrière-plan (c'est-à-dire que le dictionnaire d'options inclut documentType avec une valeur html). Il essaiera de se synchroniser avec le thread principal, échouera et temps libre."
MickeDG

8
S'il vous plaît, la rawValuesyntaxe NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)et NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)c'est horrible. Remplacez-le par .documentTypeet.characterEncoding
vadian

@MickeDG - Pouvez-vous expliquer ce que vous avez fait exactement pour résoudre cette erreur? Je reçois sporatiquement.
Ross Barbish

@RossBarbish - Désolé Ross, c'était il y a trop longtemps, je ne me souviens plus des détails. Avez-vous essayé ce que je suggère dans le commentaire ci-dessus, c'est-à-dire pour exécuter la capture complète sur le fil principal?
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" : Pas "The Weekend" ?
Peter Mortensen le

La coloration syntaxique semble bizarre, en particulier la partie commentaire de la dernière ligne. Peux-tu le réparer?
Peter Mortensen le

"The Weeknd" est un chanteur, et oui, c'est ainsi que son nom est orthographié.
wLc

5

Je cherchais un utilitaire Swift 3.0 pur pour échapper à / unescape des références de caractères HTML (c'est-à-dire pour les applications Swift côté serveur sur macOS et Linux) mais je n'ai trouvé aucune solution complète, j'ai donc écrit ma propre implémentation: https: //github.com/IBM-Swift/swift-html-entities

Le package HTMLEntities,, fonctionne avec les références de caractères nommés HTML4 ainsi que les références de caractères numériques hexadécimaux / déc, et il reconnaîtra les références de caractères numériques spéciales selon la spécification W3 HTML5 (c'est-à-dire qu'il &#x80;ne doit pas être échappé comme signe Euro (unicode U+20AC) et PAS comme unicode caractère pour U+0080, et certaines plages de références de caractères numériques doivent être remplacées par le caractère de remplacement U+FFFDlors de la désactivation).

Exemple d'utilisation:

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

Et pour l'exemple d'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: HTMLEntitiesprend désormais en charge les références de caractères nommés HTML5 à partir de la version 2.0.0. L'analyse conforme aux spécifications est également implémentée.


1
C'est la réponse la plus générique qui fonctionne tout le temps et qui ne nécessite pas d'être exécutée sur le thread principal. Cela fonctionnera même avec les chaînes unicode d'échappement HTML les plus complexes (telles que (&nbsp;͡&deg;&nbsp;͜ʖ&nbsp;͡&deg;&nbsp;)), alors qu'aucune des autres réponses ne gère cela.
Stéphane Copin

5

Swift 4:

La solution totale qui a finalement fonctionné pour moi avec du code HTML, des caractères de nouvelle ligne et des guillemets simples

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

Usage:

let yourStringEncoded = yourStringWithHtmlcode.htmlDecoded

J'ai ensuite dû appliquer quelques filtres supplémentaires pour me débarrasser des guillemets simples (par exemple, ne pas , n'a pas , c'est , etc.), et des caractères de nouvelle ligne comme \n:

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

Ceci est essentiellement une copie de cette autre réponse . Tout ce que vous avez fait est d'ajouter une utilisation qui est assez évidente.
rmaddy

quelqu'un a voté cette réponse et l'a trouvée vraiment utile, qu'est-ce que cela vous dit?
Naishta

@Naishta Cela vous dit que tout le monde a des opinions différentes et c'est OK
Josh Wolff

3

Ce serait mon approche. Vous pouvez ajouter le dictionnaire d'entités à partir de https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555 mentions 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
    }
}

Exemples utilisés:

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

OU

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

1
Je n'aime pas tout à fait cela mais je n'ai rien trouvé de mieux pour le moment, il s'agit donc d'une version mise à jour de la solution Michael Waterfall pour Swift 2.0 gist.github.com/jrmgx/3f9f1d330b295cf6b1c6
jrmgx

3

Solution élégante Swift 4

Si vous voulez une chaîne,

myString = String(htmlString: encodedString)

ajoutez cette extension à votre projet:

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

Si vous voulez un NSAttributedString avec gras, italique, liens, etc.,

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

ajoutez cette extension à votre projet:

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

Version var calculée de la réponse de @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
}

Une explication s'impose. Par exemple, en quoi est-ce différent des réponses précédentes de Swift 4?
Peter Mortensen le

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
} 

Une explication s'impose. Par exemple, en quoi est-ce différent des réponses précédentes? Quelles fonctionnalités Swift 4.1 sont utilisées? Cela fonctionne-t-il uniquement dans Swift 4.1 et pas dans les versions précédentes? Ou cela fonctionnerait-il avant Swift 4.1, par exemple dans Swift 4.0?
Peter Mortensen le

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

Utilisation simple

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

J'entends déjà les gens se plaindre de ma force déballée en option. Si vous recherchez l'encodage de chaînes HTML et que vous ne savez pas comment gérer les options Swift, vous êtes trop en avance sur vous-même.
quemeful

oui, il y a eu ( édité le 1 novembre à 22:37 et a rendu le "simple usage" beaucoup plus difficile à comprendre)
quemeful

1

Swift 4

J'aime vraiment la solution utilisant documentAttributes. Cependant, il est peut-être trop lent pour l'analyse des fichiers et / ou l'utilisation dans les cellules de vue tableau. Je ne peux pas croire qu'Apple ne propose pas de solution décente pour cela.

Pour contourner ce problème, j'ai trouvé cette extension de chaîne sur GitHub qui fonctionne parfaitement et est rapide pour le décodage.

Donc, pour les situations dans lesquelles la réponse donnée est de ralentir , voir la solution suggérée dans ce lien: https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555

Remarque: il n'analyse pas les balises HTML.


1

Réponse mise à jour fonctionnant sur 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

Objectif 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

Version Swift 3.0 avec conversion de la taille de police réelle

Normalement, si vous convertissez directement le contenu HTML en chaîne attribuée, la taille de la police est augmentée. Vous pouvez essayer de convertir une chaîne HTML en chaîne attribuée et inversement pour voir la différence.

Au lieu de cela, voici la conversion de taille réelle qui garantit que la taille de la police ne change pas, en appliquant le ratio de 0,75 sur toutes les polices:

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

S'il vous plaît, la rawValuesyntaxe NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)et NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)c'est horrible. Remplacez-le par .documentTypeet.characterEncoding
vadian

Les performances de cette solution sont horribles. C'est peut-être correct pour des caes séparés, l'analyse des fichiers n'est pas conseillée.
Vincent

0

Jetez un œil à HTMLString - une bibliothèque écrite en Swift qui permet à votre programme d'ajouter et de supprimer des entités HTML dans Strings

Par souci d'exhaustivité, j'ai copié les principales fonctionnalités du site:

  • Ajoute des entités pour les encodages ASCII et UTF-8 / UTF-16
  • Supprime plus de 2100 entités nommées (comme &)
  • Prend en charge la suppression des entités décimales et hexadécimales
  • Conçu pour prendre en charge les clusters de graphèmes étendus Swift (→ 100% résistant aux emojis)
  • Entièrement testé à l'unité
  • Vite
  • Documenté
  • Compatible avec Objective-C

0

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

De plus, si vous souhaitez extraire la date, les images, les métadonnées, le titre et la description, vous pouvez utiliser mon pod nommé:

][1].

Kit de lisibilité


Qu'est-ce qui ne le ferait pas fonctionner dans certaines versions antérieures, Swift 5.0, Swift 4.1, Swift 4.0, etc.?
Peter Mortensen le

J'ai trouvé une erreur lors du décodage de la chaîne à l'aide de collectionViews
Tung Vu Duc

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.