Wie dekodiere ich HTML-Entitäten in swift?

Ich ziehe eine JSON-Datei von einer Website und eine der empfangenen Zeichenfolgen ist:

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

Wie kann ich Dinge wie &#8216 in die richtigen Zeichen konvertieren?

Ich habe einen Xcode Playground gemacht, um es zu demonstrieren:

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

   

Es gibt keine einfache Möglichkeit, dies zu tun, aber Sie können NSAttributedString Magie verwenden, um diesen process so schmerzlos wie möglich zu machen (seien Sie gewarnt, dass diese Methode auch alle HTML-Tags NSAttributedString wird):

 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' 

Denken Sie daran, NSAttributedString nur vom Haupt-Thread zu initialisieren . Es verwendet einige WebKit Magie darunter, also die Anforderung.


Sie können eine eigene String Erweiterung erstellen, um die Wiederverwendbarkeit zu erhöhen:

 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) 

@ akashivskyys Antwort ist großartig und zeigt, wie man mit NSAttributedString HTML-Entitäten entschlüsselt. Ein möglicher Nachteil (wie er sagte) ist, dass alle HTML Markups ebenfalls entfernt werden

  4 < 5 & 3 > 2 

wird

 4 < 5 & 3 > 2 

Auf OS X gibt es CFXMLCreateStringByUnescapingEntities() die den Job CFXMLCreateStringByUnescapingEntities() :

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

aber das ist nicht auf iOS verfügbar.

Hier ist eine reine Swift-Implementierung. Es dekodiert Zeichen-Entity-Referenzen wie < Verwenden eines Wörterbuchs und aller numerischen Zeichenentitäten wie @ oder . (Beachten Sie, dass ich nicht alle 252 HTML-Entities explizit aufgelistet habe.)

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

Beispiel:

 let encoded = " 4 < 5 & 3 > 2 . Price: 12 €. @ " let decoded = encoded.stringByDecodingHTMLEntities print(decoded) //  4 < 5 & 3 > 2 . Price: 12 €. @ 

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

Schnell 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 Version von @ akashivskyys Erweiterung ,

 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 Version von @ akashivskyys Erweiterung,

  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' 

Schnelle 4 Version

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

Schnell 4


  • String-Erweiterung berechnet var
  • Ohne zusätzlichen Schutz / Do / Catch etc …
  • Gibt die ursprünglichen Zeichenfolgen zurück, wenn die Decodierung fehlschlägt

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

Ich war auf der Suche nach einem reinen Swift 3.0-Programm, um aus HTML-Zeichenreferenzen (z. B. für serverseitige Swift-Anwendungen auf macOS und Linux) zu entkommen, fand jedoch keine umfassenden Lösungen. Daher habe ich meine eigene Implementierung geschrieben: https: //github.com/IBM-Swift/swift-html-entities

Das Paket, HTMLEntities , arbeitet mit HTML4 benannten Zeichenreferenzen sowie Hex / Dez numerischen Zeichenreferenzen, und es erkennt spezielle numerische Zeichenreferenzen nach der W3 HTML5 Spezifikation (dh sollte als das Eurozeichen (Unicode U+20AC ) und NICHT als Unicode-Zeichen für U+0080 , und bestimmte Bereiche numerischer Zeichenreferenzen sollten beim Unscaping durch das Ersatzzeichen U+FFFD FFFD ersetzt werden.

Verwendungsbeispiel:

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

Und für OPs Beispiel:

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

Edit: HTMLEntities unterstützt nun HTML5 benannte Zeichenreferenzen ab Version 2.0.0. Spec-compliant Parsing ist ebenfalls implementiert.

Berechnete var Version von @yishus ‘Antwort

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

Elegante Swift 4 Lösung

Wenn Sie eine Zeichenfolge möchten

 myString = String(htmlString: encodedString) 

Fügen Sie diese Erweiterung zu Ihrem Projekt hinzu

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

Wenn Sie einen NSAttributedString mit Fett, Kursiv, Links usw. möchten:

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

Fügen Sie diese Erweiterung zu Ihrem Projekt hinzu

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

Das wäre mein Ansatz. Sie könnten das Entitätswörterbuch von https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555 hinzufügen, das Michael Waterfall erwähnt.

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

Beispiele verwendet:

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

ODER

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

Aktualisierte Antwort, die an Swift 3 arbeitet

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

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

Swift 3.0-Version mit tatsächlicher Schriftgrößenkonvertierung

Normalerweise wird die Schriftgröße erhöht, wenn Sie html direkt in eine attributierte Zeichenfolge konvertieren. Sie können versuchen, die HTML-Zeichenfolge in die attributierte Zeichenfolge und wieder zurück zu konvertieren, um den Unterschied zu sehen.

Stattdessen ist hier die tatsächliche Größenkonvertierung , die sicherstellen, dass sich die Schriftgröße nicht ändert, indem das 0,75-Verhältnis für alle fonts angewendet wird

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

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

Einfache Verwendung

 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

Ich mag die Lösung, die documentAttributes verwendet, mag es jedoch zu langsam für das Analysieren von Dateien und / oder die Verwendung in Tabellenansichtszellen. Ich kann nicht glauben, dass Apple dafür keine vernünftige Lösung bietet.

Als Workaround habe ich auf GitHub diese String Extension gefunden, die perfekt und schnell zum Decodieren funktioniert.

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

Hinweis: HTML-Tags werden nicht analysiert.

Sehen Sie sich HTMLString an – eine in Swift geschriebene Bibliothek, mit der Ihr Programm HTML-Objekte in Strings hinzufügen und entfernen kann

Der Vollständigkeit halber habe ich Hauptfunktionen von der Site kopiert:

  • Fügt Entitäten für ASCII- und UTF-8 / UTF-16-Kodierungen hinzu
  • Entfernt mehr als 2100 benannte Entitäten (wie &)
  • Unterstützt das Entfernen von dezimalen und hexadezimalen Entitäten
  • Entwickelt zur Unterstützung von Swift Extended Grapheme Clustern (→ 100% emoji-proof)
  • Vollständig getestet
  • Schnell
  • Dokumentiert
  • Kompatibel mit Objective-C

Schnell 4:

Die Gesamtlösung, die schließlich für mich mit HTML-Code und Newline-Zeichen und einfachen Anführungszeichen gearbeitet hat

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

Verwendung:

 let yourStringEncoded = yourStringWithHtmlcode.htmlDecoded 

Ich musste dann einige weitere Filter anwenden, um single quotes (zB: don't, hasn't, It's usw.) und neue Zeilenzeichen wie \n

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

NSData dataRes = (nsdata-Wert)

var resString = NSString (Daten: dataRes, Codierung: NSUTF8StringEncoding)