¿Cómo decodizo las entidades HTML en forma rápida?

Estoy sacando un archivo JSON de un sitio y una de las cadenas recibidas es:

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

¿Cómo puedo convertir cosas como &#8216 en los caracteres correctos?

Creé un Xcode Playground para demostrarlo:

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

No hay una manera directa de hacerlo, pero puedes utilizar la magia NSAttributedString para hacer que este proceso sea lo menos doloroso posible (ten en cuenta que este método también eliminará todas las tags HTML):

 let encodedString = "The Weeknd ‘King Of The Fall’" // encodedString should = a[0]["title"] in your case guard let data = htmlEncodedString.data(using: .utf8) else { return nil } let options: [String: Any] = [ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue ] guard let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil) else { return nil } let decodedString = attributedString.string // The Weeknd 'King Of The Fall' 

Recuerde inicializar NSAttributedString únicamente desde el hilo principal . Utiliza un poco de magia WebKit debajo, por lo tanto, el requisito.


Puede crear su propia extensión de String para boost la reutilización:

 extension String { init?(htmlEncodedString: String) { guard let data = htmlEncodedString.data(using: .utf8) else { return nil } let options: [String: Any] = [ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: 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 ‘King Of The Fall’" let decodedString = String(htmlEncodedString: encodedString) 

La respuesta de @akashivskyy es excelente y demuestra cómo utilizar NSAttributedString para decodificar entidades HTML. Una posible desventaja (como dijo) es que todas las marcas HTML se eliminan también, por lo que

  4 < 5 & 3 > 2 

se convierte

 4 < 5 & 3 > 2 

En OS X hay CFXMLCreateStringByUnescapingEntities() que hace el trabajo:

 let encoded = " 4 < 5 & 3 > 2 . Price: 12 €. @ " let decoded = CFXMLCreateStringByUnescapingEntities(nil, encoded, nil) as String println(decoded) //  4 < 5 & 3 > 2 . Price: 12 €. @ 

pero esto no está disponible en iOS.

Aquí hay una implementación pura de Swift. Decodifica referencias de entidades de caracteres como < usando un diccionario, y todas las entidades de caracteres numéricos como @ o . (Tenga en cuenta que no enumeré las 252 entidades HTML explícitamente).

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: """ : "\"", "&" : "&", "'" : "'", "<" : "< ", ">" : ">", // HTML character entity references: " " : "\u{00a0}", // ... "♦" : "♦", ] 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, eg // 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("@") --> "@" // decode("€") --> "€" // decode("<") --> "< " // 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 } } 

Ejemplo:

 let encoded = " 4 < 5 & 3 > 2 . Price: 12 €. @ " let decoded = encoded.stringByDecodingHTMLEntities print(decoded) //  4 < 5 & 3 > 2 . 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: """ : "\"", "&" : "&", "'" : "'", "<" : "< ", ">" : ">", // HTML character entity references: " " : "\u{00a0}", // ... "♦" : "♦", ] 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, eg // 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("@") --> "@" // decode("€") --> "€" // decode("<") --> "< " // 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: """ : "\"", "&" : "&", "'" : "'", "<" : "< ", ">" : ">", // HTML character entity references: " " : "\u{00a0}", // ... "♦" : "♦", ] 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, eg // 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("@") --> "@" // decode("€") --> "€" // decode("<") --> "< " // 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 } } 

Swift 3 versión de la extensión de @ 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 } } } 

Swift 2 versión de la extensión de @ 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 } } } 
 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 ‘King Of The Fall’" let foo = encodedString.decodeEnt() // The Weeknd 'King Of The Fall' 

Versión 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] = [ NSAttributedString.DocumentReadingOptionKey(rawValue: .documentType.rawValue): NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey(rawValue: .characterEncoding.rawValue): String.Encoding.utf8.rawValue ] do { let attributedString = try NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil) self = attributedString.string } catch { print("Error: \(error)") self = htmlEncodedString } } } 

Swift 4


  • Var de extensión de cadena calculada
  • Sin guardia / do / catch extra …
  • Devuelve las cadenas originales si falla la deencoding

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

Estaba buscando una utilidad pura de Swift 3.0 para escapar a / unescape de referencias de caracteres HTML (es decir, para aplicaciones Swift del lado del servidor en macOS y Linux) pero no encontré ninguna solución integral, así que escribí mi propia implementación: https: //github.com/IBM-Swift/swift-html-entities

El paquete, HTMLEntities , trabaja con referencias de caracteres nombrados en HTML4, así como referencias de caracteres numéricos hexadecimales y decimales, y reconocerá referencias de caracteres numéricos especiales según la especificación HTML5 de W3 (es decir, debe descartarse como el símbolo del euro ( U+20AC unicode) U+20AC ) y NO como el carácter unicode para U+0080 , y ciertos rangos de referencias de caracteres numéricos deben reemplazarse con el carácter de reemplazo U+FFFD cuando se separa el escaneo).

Ejemplo de uso:

 import HTMLEntities // encode example let html = "" print(html.htmlEscape()) // Prints ”<script>alert("abc")</script>" // decode example let htmlencoded = "<script>alert("abc")</script>" print(htmlencoded.htmlUnescape()) // Prints ”" 

Y para el ejemplo de OP:

 print("The Weeknd ‘King Of The Fall’ [Video Premiere] | @TheWeeknd | #SoPhi ".htmlUnescape()) // prints "The Weeknd 'King Of The Fall' [Video Premiere] | @TheWeeknd | #SoPhi " 

Editar: HTMLEntities ahora es compatible con referencias de personajes nombrados HTML5 a partir de la versión 2.0.0. El análisis sintáctico de especificación también se implementa.

Versión de var computada de la respuesta 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 } } } 

Solución elegante Swift 4

Si quieres una cadena

 myString = String(htmlString: encodedString) 

Agrega esta extensión a tu proyecto

 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 quieres un NSAttributedString con negrita, cursiva, enlaces, etc.

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

Agrega esta extensión a tu proyecto

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

Este sería mi enfoque. Puede agregar el diccionario de entidades desde https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555 Menciones de Michael Waterfall.

 extension String { func htmlDecoded()->String { guard (self != "") else { return self } var newStr = self let entities = [ """ : "\"", "&" : "&", "'" : "'", "<" : "< ", ">" : ">", ] for (name,value) in entities { newStr = newStr.stringByReplacingOccurrencesOfString(name, withString: value) } return newStr } } 

Ejemplos usados:

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

O

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

Respuesta actualizada trabajando en 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) } 

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 } 

Versión de Swift 3.0 con conversión de tamaño de fuente real

Normalmente, si convierte directamente html a una cadena atribuida, se aumenta el tamaño de la fuente. Puede intentar convertir cadena html a cadena atribuida y viceversa para ver la diferencia.

En cambio, aquí está la conversión de tamaño real que asegura que el tamaño de la fuente no cambie, aplicando la proporción de 0.75 en todas las fonts

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

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 simple

 let clean = "string".replacingHTMLEntities! 

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

Swift4

Me gusta mucho la solución que usa documentAttributes, pero puede ralentizarse para analizar archivos y / o usar en celdas de vista de tabla. No puedo creer que Apple no brinde una solución decente para esto.

Como solución alternativa, encontré en GitHub esta extensión de cadena que funciona perfectamente y rápido para decodificar.

https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555

Nota: no analiza las tags HTML.

Eche un vistazo a HTMLString – una biblioteca escrita en Swift que le permite a su progtwig agregar y eliminar entidades HTML en Strings

Para completar, copié las características principales del sitio:

  • Agrega entidades para codificaciones ASCII y UTF-8 / UTF-16
  • Elimina más de 2100 entidades con nombre (como &)
  • Admite eliminar entidades decimales y hexadecimales
  • Diseñado para ser compatible con Swift Extended Grapheme Clusters (→ 100% emoji-proof)
  • Totalmente probado
  • Rápido
  • Documentado
  • Compatible con Objective-C

Swift 4:

La solución total que finalmente funcionó para mí con código html y caracteres de nueva línea y comillas 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 

Luego tuve que aplicar algunos filtros más para deshacerme de las single quotes (por ejemplo: don't, hasn't, It's tengo don't, hasn't, It's etc.), y los nuevos caracteres de línea como \n

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

NSData dataRes = (valor de nsdata)

var resString = NSString (data: dataRes, encoding: NSUTF8StringEncoding)