Como decodificar entidades HTML no Swift?

121

Estou puxando um arquivo JSON de um site e uma das seqüências de caracteres recebidas é:

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

Como posso converter coisas como &#8216nos caracteres corretos?

Eu criei um Xcode Playground para demonstrá-lo:

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

Respostas:

157

Esta resposta foi revisada pela última vez no Swift 5.2 e iOS 13.4 SDK.


Não há uma maneira direta de fazer isso, mas você pode usar NSAttributedString magia para tornar esse processo o mais simples possível (lembre-se de que esse método também removerá todas as tags HTML).

Lembre-se de inicializar apenas a NSAttributedStringpartir do thread principal . Ele usa o WebKit para analisar o HTML abaixo, portanto, o 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)
akashivskyy
fonte
54
O que? As extensões destinam -se a estender os tipos existentes para fornecer nova funcionalidade.
akashivskyy
4
Entendo o que você está tentando dizer, mas negar extensões não é o caminho a percorrer.
akashivskyy
1
@akashivskyy: Para que isso funcione corretamente com caracteres não ASCII, você deve adicionar um NSCharacterEncodingDocumentAttribute, compare stackoverflow.com/a/27898167/1187415 .
Martin R
13
Este método é extremamente pesado e não é recomendado em tableviews ou GridViews
Guido Lodetti
1
Isso é ótimo! Embora bloqueie o thread principal, existe alguma maneira de executá-lo no thread de segundo plano?
MMV
78

A resposta de @ akashivskyy é ótima e demonstra como utilizar NSAttributedStringpara decodificar entidades HTML. Uma possível desvantagem (como ele afirmou) é que toda a marcação HTML também é removida, portanto

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

torna-se

4 < 5 & 3 > 2

No OS X, existe o CFXMLCreateStringByUnescapingEntities()que faz o trabalho:

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

mas isso não está disponível no iOS.

Aqui está uma implementação pura do Swift. Decodifica referências de entidades de caracteres como &lt;usar um dicionário e todas as entidades de caracteres numéricos como &#64ou &#x20ac. (Observe que eu não listei todas as 252 entidades HTML explicitamente.)

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

Exemplo:

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
    }
}
Martin R
fonte
10
Isso é brilhante, obrigado Martin! Aqui está a extensão com a lista completa de entidades HTML: gist.github.com/mwaterfall/25b4a6a06dc3309d9555 Eu também a adaptei levemente para fornecer as compensações de distância feitas pelas substituições. Isso permite o ajuste correto de quaisquer atributos ou entidades de sequência que possam ser afetados por essas substituições (índices de entidade do Twitter, por exemplo).
Michael Waterfall
3
@MichaelWaterfall e Martin isso é magnífico! Funciona como um encanto! Eu atualizo a extensão do Swift 2 pastebin.com/juHRJ6au Obrigado!
Santiago
1
Eu converti essa resposta para ser compatível com o Swift 2 e a joguei em um CocoaPod chamado StringExtensionHTML para facilitar o uso. Observe que a versão Swift 2 de Santiago corrige os erros de tempo de compilação, mas remover strtooul(string, nil, base)completamente fará com que o código não funcione com entidades de caracteres numéricos e trava quando se trata de uma entidade que não reconhece (em vez de falhar normalmente).
Adela Chang
1
@AdelaChang: Na verdade, eu já havia convertido minha resposta para o Swift 2 em setembro de 2015. Ele ainda é compilado sem avisos com o Swift 2.2 / Xcode 7.3. Ou você está se referindo à versão de Michael?
Martin R
1
Obrigado, com esta resposta, resolvi meus problemas: tive sérios problemas de desempenho usando o NSAttributedString.
Andrea Mugnaini
27

Versão Swift 3 da extensão @ 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
        }
    }
}
yishus
fonte
Funciona bem. A resposta original estava causando um acidente estranho. Obrigado pela atualização!
Geoherna
Para caracteres franceses, tenho que usar utf16 #
Sébastien REMY
23

Swift 4


  • Variável calculada da extensão de cadeia
  • Sem guarda extra, faça, pegue, etc ...
  • Retorna as strings originais se a decodificação falhar

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
    }
}
AamirR
fonte
1
Uau ! funciona imediatamente para o Swift 4! Uso // let codificado = "O Rei da Queda do Fim de Semana" # 8217; deixe finalString = encoded.htmlDecoded
Naishta 11/08/19
2
Eu amo a simplicidade desta resposta. No entanto, causará falhas ao executar em segundo plano porque tenta executar no thread principal.
Jeremy Hicks
14

Versão Swift 2 da extensão @ 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
         }
     }
 }
Mohammad Zaid Pathan
fonte
Este código está incompleto e deve ser evitado por todos os meios. O erro não está sendo tratado corretamente. Quando existe de fato, um código de erro falha. Você deve atualizar seu código para retornar pelo menos nulo quando houver um erro. Ou você pode simplesmente iniciar com a string original. No final, você deve lidar com o erro. O que não é o caso. Uau!
Oyalhi
9

Versão 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
        }
    }
}
pipizanzibar
fonte
Recebo "Domínio do erro = Código NSCocoaErrorDomain = 259" O arquivo não pôde ser aberto porque não está no formato correto. "" Quando tento usá-lo. Isso desaparece se eu executar o catch completo no thread principal. Descobri isso ao verificar a documentação NSAttributedString: "O importador HTML não deve ser chamado de um thread em segundo plano (ou seja, o dicionário de opções inclui documentType com um valor de html). Ele tentará sincronizar com o thread principal, falhar e tempo esgotado."
MickeDG
8
Por favor, a rawValuesintaxe NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)e NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)é horrível. Substitua-o por .documentTypee.characterEncoding
vadian
@MickeDG - Você pode explicar o que exatamente você fez para resolver esse erro? Estou entendendo esporaticamente.
Ross Barbish
@ RossBarbish - Desculpe Ross, isso foi há muito tempo, não me lembro dos detalhes. Você já tentou o que eu sugiro no comentário acima, ou seja, para executar a captura completa no segmento principal?
MickeDG 10/03
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’ */
wLc
fonte
Re "The Weeknd" : Não "The Weekend" ?
Peter Mortensen
O destaque da sintaxe parece estranho, especialmente a parte de comentário da última linha. Consegues consertar isso?
Peter Mortensen
"The Weeknd" é um cantor, e sim, é assim que o nome dele é escrito.
wLc 25/01
5

Eu estava procurando por um utilitário Swift 3.0 puro para escapar / remover as referências de caracteres HTML (ou seja, para aplicativos Swift do lado do servidor no macOS e Linux), mas não encontrei nenhuma solução abrangente, então escrevi minha própria implementação: https: //github.com/IBM-Swift/swift-html-entities

O pacote HTMLEntitiesfunciona com referências de caracteres nomeados HTML4, bem como referências de caracteres numéricos hex / dec, e reconhecerá referências de caracteres numéricos especiais de acordo com a especificação HTML5 do W3 (ou seja, &#x80;deve ser sem escape como o sinal Euro (unicode U+20AC) e NOT como o unicode caractere para U+0080, e certos intervalos de referências de caracteres numéricos devem ser substituídos pelo caractere de substituição U+FFFDao retirar o caractere

Exemplo de uso:

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 para o exemplo do 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 "

Editar: HTMLEntitiesagora suporta referências de caracteres nomeados HTML5 a partir da versão 2.0.0. A análise compatível com especificações também é implementada.

Youming Lin
fonte
1
Essa é a resposta mais genérica que funciona o tempo todo e não exige execução no thread principal. Isso funcionará mesmo com as seqüências unicode escapadas HTML mais complexas (como (&nbsp;͡&deg;&nbsp;͜ʖ&nbsp;͡&deg;&nbsp;)), enquanto nenhuma das outras respostas gerencia isso.
Stéphane Copin
5

Swift 4:

A solução total que finalmente funcionou para mim com código HTML, caracteres de nova linha e aspas 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
    }
}

Uso:

let yourStringEncoded = yourStringWithHtmlcode.htmlDecoded

Eu tive que aplicar mais alguns filtros para se livrar de aspas simples (por exemplo, não , não , é , etc.) e novos caracteres de linha, como \n:

var yourNewString = String(yourStringEncoded.filter { !"\n\t\r".contains($0) })
yourNewString = yourNewString.replacingOccurrences(of: "\'", with: "", options: NSString.CompareOptions.literal, range: nil)
Naishta
fonte
Esta é essencialmente uma cópia desta outra resposta . Tudo o que você fez foi adicionar algum uso que seja óbvio o suficiente.
Rddydy #
alguém votou positivamente nessa resposta e achou realmente útil, o que isso lhe diz?
Naishta 2/11
@Naishta Diz a você que todo mundo tem opiniões diferentes e tudo bem
Josh Wolff
3

Essa seria a minha abordagem. Você pode adicionar o dicionário de entidades em https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555 .

extension String {
    func htmlDecoded()->String {

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

        var newStr = self

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

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

Exemplos utilizados:

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""
Bseaborn
fonte
1
Eu faço não é bem assim, mas eu não encontrei nada ainda melhor por isso esta é uma versão atualizada da solução Michael Cachoeira por Swift 2.0 gist.github.com/jrmgx/3f9f1d330b295cf6b1c6
jrmgx
3

Solução elegante Swift 4

Se você quer uma string,

myString = String(htmlString: encodedString)

adicione esta extensão ao seu projeto:

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 você deseja um NSAttributedString com negrito, itálico, links etc.,

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

adicione esta extensão ao seu projeto:

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

}
Sébastien REMY
fonte
2

Versão var computada da resposta do @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
        }
    }
}
Geva
fonte
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
}
Haroldo Gondim
fonte
Uma explicação estaria em ordem. Por exemplo, como é diferente das respostas anteriores do Swift 4?
Peter Mortensen
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
} 
Deepak Singh
fonte
Uma explicação estaria em ordem. Por exemplo, como é diferente das respostas anteriores? Quais recursos do Swift 4.1 são usados? Funciona apenas no Swift 4.1 e não nas versões anteriores? Ou funcionaria antes do Swift 4.1, digamos no Swift 4.0?
Peter Mortensen
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
        }
    }
}

Uso Simples

let clean = "Weeknd &#8216;King Of The Fall&#8217".replacingHTMLEntities ?? "default value"
quemeful
fonte
Já posso ouvir pessoas reclamando da minha força desembrulhada opcional. Se você está pesquisando a codificação de string HTML e não sabe lidar com os opcionais do Swift, está muito à frente de si.
quemeful
sim, existe was ( editado em 1 de novembro às 22:37 e tornou muito mais difícil compreender o "uso simples")
quemeful
1

Swift 4

Eu realmente gosto da solução usando documentAttributes. No entanto, pode ser muito lento para analisar arquivos e / ou uso nas células de exibição de tabela. Não acredito que a Apple não oferece uma solução decente para isso.

Como solução alternativa, encontrei esta Extensão de String no GitHub, que funciona perfeitamente e é rápida para decodificar.

Portanto, para situações nas quais a resposta dada é lenta , consulte a solução sugerida neste link: https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555

Nota: ele não analisa tags HTML.

Vincent
fonte
1

Resposta atualizada trabalhando no 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)
   }
ravalboy
fonte
0

Objetivo-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];
}
Oded Regev
fonte
0

Versão Swift 3.0 com conversão de tamanho de fonte real

Normalmente, se você converter diretamente o conteúdo HTML em uma sequência atribuída, o tamanho da fonte aumentará. Você pode tentar converter uma string HTML em uma string atribuída e vice-versa para ver a diferença.

Em vez disso, aqui está a conversão de tamanho real que garante que o tamanho da fonte não seja alterado, aplicando a proporção de 0,75 em todas as fontes:

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
    }
}
Fangming
fonte
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)")
        }
    }
Omar Freewan
fonte
Por favor, a rawValuesintaxe NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)e NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)é horrível. Substitua-o por .documentTypee.characterEncoding
vadian
O desempenho desta solução é horrível. Talvez seja bom para arquivos separados, a análise de arquivos não é recomendada.
24518 Vincent Vincent
0

Dê uma olhada no HTMLString - uma biblioteca escrita em Swift que permite ao seu programa adicionar e remover entidades HTML em Strings

Para completar, copiei os principais recursos do site:

  • Adiciona entidades para codificações ASCII e UTF-8 / UTF-16
  • Remove mais de 2100 entidades nomeadas (como &)
  • Suporta a remoção de entidades decimais e hexadecimais
  • Projetado para oferecer suporte a Clusters Swift Extended Grapheme (→ 100% à prova de emoji)
  • Totalmente testado
  • Rápido
  • Documentado
  • Compatível com Objective-C
Despotovic
fonte
0

Versão 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
        }
    }
}

Além disso, se você deseja extrair data, imagens, metadados, título e descrição, use meu pod chamado:

] [1].

Kit de legibilidade

jawadAli
fonte
O que é que não o faria funcionar em algumas versões anteriores, Swift 5.0, Swift 4.1, Swift 4.0, etc.?
Peter Mortensen
Encontrei um erro ao decodificar string usando collectionViews
Tung Vu Duc
-1

Usar:

NSData dataRes = (nsdata value )

var resString = NSString(data: dataRes, encoding: NSUTF8StringEncoding)
Yogesh shelke
fonte
Uma explicação estaria em ordem ( editando sua resposta , não aqui nos comentários).
Peter Mortensen