Wie dekodiere ich HTML-Entitäten in Swift?


121

Ich ziehe eine JSON-Datei von einer Site und eine der empfangenen Zeichenfolgen lautet:

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

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

Ich habe einen Xcode-Spielplatz erstellt, um dies 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"])

Antworten:


157

Diese Antwort wurde zuletzt für Swift 5.2 und iOS 13.4 SDK überarbeitet.


Es gibt keinen einfachen Weg, dies zu tun, aber Sie können verwenden NSAttributedString Magie verwenden, um diesen Prozess so schmerzlos wie möglich zu gestalten (seien Sie gewarnt, dass diese Methode auch alle HTML-Tags entfernt).

Denken Sie daran, nur vom Hauptthread aus zu initialisierenNSAttributedString . Es verwendet WebKit, um HTML darunter zu analysieren, daher die Anforderung.

// 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
Was? Erweiterungen sind gemeint vorhandene Typen zu erweitern , um neue Funktionalität bereitzustellen.
Akashivskyy

4
Ich verstehe, was Sie sagen wollen, aber das Negieren von Erweiterungen ist nicht der richtige Weg.
Akashivskyy

1
@akashivskyy: Damit dies mit Nicht-ASCII-Zeichen korrekt funktioniert, müssen Sie ein NSCharacterEncodingDocumentAttribute hinzufügen . Vergleichen Sie stackoverflow.com/a/27898167/1187415 .
Martin R

13
Diese Methode ist extrem schwer und wird in Tabellenansichten oder Rasteransichten nicht empfohlen
Guido Lodetti

1
Das ist toll! Gibt es eine Möglichkeit, den Hauptthread im Hintergrundthread auszuführen, obwohl er blockiert wird?
MMV

78

Die Antwort von @ akashivskyy ist großartig und zeigt, wie NSAttributedStringHTML-Entitäten dekodiert werden können. Ein möglicher Nachteil (wie er sagte) ist, dass alle HTML-Markups ebenfalls entfernt werden

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

wird

4 < 5 & 3 > 2

Unter OS X gibt es CFXMLCreateStringByUnescapingEntities()Folgendes:

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

Dies ist jedoch unter iOS nicht verfügbar.

Hier ist eine reine Swift-Implementierung. Es dekodiert Zeichenentitätsreferenzen wie die &lt;Verwendung eines Wörterbuchs und alle numerischen Zeichenentitäten wie &#64oder &#x20ac. (Beachten Sie, dass ich nicht alle 252 HTML-Entitäten explizit aufgelistet habe.)

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

Beispiel:

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
Das ist großartig, danke Martin! Hier ist die Erweiterung mit der vollständigen Liste der HTML-Entitäten: gist.github.com/mwaterfall/25b4a6a06dc3309d9555 Ich habe sie auch leicht angepasst, um die durch die Ersetzungen vorgenommenen Entfernungsversätze bereitzustellen. Dies ermöglicht die korrekte Anpassung von Zeichenfolgenattributen oder Entitäten, die von diesen Ersetzungen betroffen sein könnten (z. B. Twitter-Entitätsindizes).
Michael Wasserfall

3
@ MichaelWaterfall und Martin das ist großartig! klappt wunderbar! Ich aktualisiere die Erweiterung für Swift 2 pastebin.com/juHRJ6au Danke!
Santiago

1
Ich habe diese Antwort so konvertiert, dass sie mit Swift 2 kompatibel ist, und sie zur Vereinfachung in einem CocoaPod namens StringExtensionHTML gespeichert . Beachten Sie, dass die Swift 2-Version von Santiago die Fehler bei der Kompilierung behebt. strtooul(string, nil, base)Wenn Sie den Code jedoch vollständig entfernen, funktioniert der Code nicht mit Entitäten mit numerischen Zeichen und stürzt ab, wenn es sich um eine Entität handelt, die nicht erkannt wird (anstatt ordnungsgemäß zu versagen).
Adela Chang

1
@AdelaChang: Eigentlich hatte ich meine Antwort bereits im September 2015 auf Swift 2 konvertiert. Mit Swift 2.2 / Xcode 7.3 wird sie immer noch ohne Warnungen kompiliert. Oder beziehen Sie sich auf Michaels Version?
Martin R

1
Vielen Dank, mit dieser Antwort habe ich meine Probleme gelöst: Ich hatte ernsthafte Leistungsprobleme mit NSAttributedString.
Andrea Mugnaini

27

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

Funktioniert super. Die ursprüngliche Antwort verursachte einen seltsamen Absturz. Danke für Update!
Geoherna

Für französische Charaktere muss ich utf16
Sébastien REMY

23

Swift 4


  • Berechnete Variable für die Zeichenfolgenerweiterung
  • Ohne zusätzliche Wache tun, fangen, etc ...
  • Gibt die ursprünglichen Zeichenfolgen zurück, wenn die Dekodierung 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
    }
}

1
Wow ! funktioniert sofort für Swift 4! Verwendung // let encoded = "Der Weeknd & # 8216; König des Herbstes & # 8217;" let finalString = encoded.htmlDecoded
Naishta

2
Ich liebe die Einfachheit dieser Antwort. Es führt jedoch zu Abstürzen, wenn es im Hintergrund ausgeführt wird, da versucht wird, es auf dem Hauptthread auszuführen.
Jeremy Hicks

14

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

Dieser Code ist unvollständig und sollte auf jeden Fall vermieden werden. Der Fehler wird nicht richtig behandelt. In der Tat würde ein Fehlercode abstürzen. Sie sollten Ihren Code so aktualisieren, dass er im Fehlerfall mindestens nil zurückgibt. Oder Sie können einfach mit der Originalzeichenfolge initialisieren. Am Ende sollten Sie den Fehler behandeln. Welches ist nicht der Fall. Beeindruckend!
Oyalhi

9

Swift 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] = [
            .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
        }
    }
}

Ich erhalte die Meldung "Error Domain = NSCocoaErrorDomain Code = 259". Die Datei konnte nicht geöffnet werden, da sie nicht im richtigen Format vorliegt. "" Wenn ich versuche, dies zu verwenden. Dies verschwindet, wenn ich den Haupt-Thread vollständig fange. Ich fand dies aus der Überprüfung der NSAttributedString-Dokumentation: "Der HTML-Importer sollte nicht von einem Hintergrund-Thread aufgerufen werden (dh das Optionswörterbuch enthält documentType mit dem Wert html). Es wird versucht, mit dem Haupt-Thread zu synchronisieren, fehlgeschlagen und Auszeit."
MickeDG

8
Bitte die rawValueSyntax NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)und NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)ist schrecklich. Ersetzen Sie es durch .documentTypeund.characterEncoding
vadian

@MickeDG - Können Sie bitte erklären, was genau Sie getan haben, um diesen Fehler zu beheben? Ich bekomme es sporatisch.
Ross Barbish

@ RossBarbish - Sorry Ross, das ist zu lange her, ich kann mich nicht an die Details erinnern. Haben Sie versucht, was ich im obigen Kommentar vorschlage, dh den Haupt-Thread vollständig zu fangen?
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’ */

Zu "The Weeknd" : Nicht "The Weekend" ?
Peter Mortensen

Die Hervorhebung der Syntax sieht seltsam aus, insbesondere der Kommentarteil der letzten Zeile. Kannst du das Reparieren?
Peter Mortensen

"The Weeknd" ist ein Sänger, und ja, so wird sein Name geschrieben.
wLc

5

Ich suchte nach einem reinen Swift 3.0-Dienstprogramm, um HTML-Zeichenreferenzen zu entkommen / zu entkommen (dh für serverseitige Swift-Apps unter MacOS und Linux), fand jedoch keine umfassenden Lösungen und schrieb meine eigene Implementierung: https: //github.com/IBM-Swift/swift-html-entities

Das Paket HTMLEntitiesfunktioniert mit HTML4-Zeichenreferenzen sowie benannten Hex / Dec-Zeichenreferenzen und erkennt spezielle numerische Zeichenreferenzen gemäß der W3-HTML5-Spezifikation (dh &#x80;sollte als Euro-Zeichen (Unicode U+20AC) und NICHT als Unicode entkoppelt werden Zeichen für U+0080und bestimmte Bereiche von numerischen Zeichenreferenzen sollten durch das Ersatzzeichen ersetzt werdenU+FFFD beim Entkuppeln ersetzt werden.

Anwendungsbeispiel:

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

Und zum Beispiel von 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 "

Bearbeiten: HTMLEntitiesUnterstützt jetzt HTML5-Referenzen für benannte Zeichen ab Version 2.0.0. Spezifikationskonformes Parsen ist ebenfalls implementiert.


1
Dies ist die allgemeinste Antwort, die ständig funktioniert und nicht im Hauptthread ausgeführt werden muss. Dies funktioniert auch mit den komplexesten HTML-Escape-Unicode-Zeichenfolgen (z. B. (&nbsp;͡&deg;&nbsp;͜ʖ&nbsp;͡&deg;&nbsp;)), während keine der anderen Antworten dies schafft.
Stéphane Copin

5

Swift 4:

Die Gesamtlösung, die für mich endlich mit HTML-Code, Zeilenumbrüchen und einfachen Anführungszeichen funktioniert 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 einfache Anführungszeichen (z. B. nicht , nicht , es ist 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)

Dies ist im Wesentlichen eine Kopie dieser anderen Antwort . Alles, was Sie getan haben, ist eine Verwendung hinzuzufügen, die offensichtlich genug ist.
rmaddy

Jemand hat diese Antwort positiv bewertet und fand sie wirklich nützlich. Was sagt Ihnen das?
Naishta

@Naishta Es sagt dir, dass jeder unterschiedliche Meinungen hat und das ist in Ordnung
Josh Wolff

3

Dies wäre mein Ansatz. Sie können das Entities-Wörterbuch unter 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 = [
            "&quot;"    : "\"",
            "&amp;"     : "&",
            "&apos;"    : "'",
            "&lt;"      : "<",
            "&gt;"      : ">",
        ]

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

Verwendete Beispiele:

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

ODER

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

1
Ich glaube nicht , ganz wie diese , aber ich habe nichts besser noch finden , so ist dies eine aktualisierte Version von Michael Wasserfall Lösung für Swift 2.0 gist.github.com/jrmgx/3f9f1d330b295cf6b1c6
jrmgx

3

Elegante Swift 4-Lösung

Wenn Sie eine Zeichenfolge möchten,

myString = String(htmlString: encodedString)

Fügen Sie diese Erweiterung 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 Fettdruck, Kursivschrift, Links usw. möchten,

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

Fügen Sie diese Erweiterung 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)
    }

}

2

Berechnete var-Version der Antwort von @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
}

Eine Erklärung wäre angebracht. Wie unterscheidet es sich beispielsweise von früheren Swift 4-Antworten?
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
} 

Eine Erklärung wäre angebracht. Wie unterscheidet es sich beispielsweise von früheren Antworten? Welche Swift 4.1-Funktionen werden verwendet? Funktioniert es nur in Swift 4.1 und nicht in früheren Versionen? Oder würde es vor Swift 4.1 funktionieren, etwa in 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
        }
    }
}

Einfache Verwendung

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

Ich kann bereits Leute hören, die sich optional über meine ausgepackte Kraft beschweren. Wenn Sie sich mit der HTML-Zeichenfolgencodierung befassen und nicht wissen, wie Sie mit Swift-Optionen umgehen sollen, sind Sie sich selbst zu weit voraus.
quemeful

Ja, es gab ( bearbeitet am 1. November um 22:37 Uhr und machte die "einfache Verwendung" viel schwieriger zu verstehen)
quemeful

1

Swift 4

Ich mag die Lösung mit documentAttributes sehr. Es ist jedoch möglicherweise zu langsam, um Dateien zu analysieren und / oder in Zellen der Tabellenansicht zu verwenden. Ich kann nicht glauben, dass Apple dafür keine vernünftige Lösung bietet.

Als Workaround habe ich diese String-Erweiterung auf GitHub gefunden, die perfekt funktioniert und sich schnell dekodieren lässt.

Für Situationen, in denen die angegebene Antwort zu langsam ist , lesen Sie die Lösungsvorschläge in diesem Link: https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555

Hinweis: HTML-Tags werden nicht analysiert.


1

Aktualisierte Antwort für 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

Ziel 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

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

Wenn Sie HTML-Inhalte direkt in eine zugewiesene Zeichenfolge konvertieren, wird normalerweise die Schriftgröße erhöht. Sie können versuchen, eine HTML-Zeichenfolge in eine zugeordnete Zeichenfolge und wieder zurück zu konvertieren, um den Unterschied festzustellen.

Hier ist stattdessen die tatsächliche Größenkonvertierung , die sicherstellt, dass sich die Schriftgröße nicht ändert, indem das Verhältnis 0,75 auf alle Schriftarten 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
    }
}

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

Bitte die rawValueSyntax NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)und NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)ist schrecklich. Ersetzen Sie es durch .documentTypeund.characterEncoding
vadian

Die Leistung dieser Lösung ist schrecklich. Es ist vielleicht in Ordnung für separate Caes, das Parsen von Dateien wird nicht empfohlen.
Vincent

0

Schau es dir an HTMLString an - eine in Swift geschriebene Bibliothek, mit der Ihr Programm HTML-Entitäten in Strings hinzufügen und entfernen kann

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

  • Fügt Entitäten für ASCII- und UTF-8 / UTF-16-Codierungen hinzu
  • Entfernt mehr als 2100 benannte Entitäten (wie &)
  • Unterstützt das Entfernen von dezimalen und hexadezimalen Objekten
  • Entwickelt, um Swift Extended Grapheme Cluster zu unterstützen (→ 100% emoji-sicher)
  • Vollständig einheitlich getestet
  • Schnell
  • Dokumentiert
  • Kompatibel mit Objective-C

0

Swift 5.1 Version

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

Wenn Sie Datum, Bilder, Metadaten, Titel und Beschreibung extrahieren möchten, können Sie auch meinen Pod mit dem Namen verwenden:

] [1].

Lesbarkeits-Kit


Was würde es in einigen früheren Versionen, Swift 5.0, Swift 4.1, Swift 4.0 usw. nicht funktionieren lassen?
Peter Mortensen

Ich habe einen Fehler beim Dekodieren eines Strings mit collectionViews
Tung Vu Duc

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.