Jak dekodować encje HTML w Swift?

121

Pobieram plik JSON z witryny, a jeden z otrzymanych ciągów to:

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

Jak mogę zamienić takie rzeczy &#8216na właściwe postacie?

Zrobiłem Xcode Playground, aby to zademonstrować:

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
źródło

Odpowiedzi:

158

Ta odpowiedź została ostatnio poprawiona dla Swift 5.2 i iOS 13.4 SDK.


Nie ma na to prostego sposobu, ale możesz użyć NSAttributedStringmagii, aby uczynić ten proces tak bezbolesnym, jak to tylko możliwe (pamiętaj, że ta metoda usunie również wszystkie znaczniki HTML).

Pamiętaj, aby inicjalizować tylko NSAttributedStringz głównego wątku . Używa WebKit do analizowania kodu HTML pod spodem, stąd wymóg.

// 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
źródło
54
Co? Rozszerzenia mają na celu rozszerzenie istniejących typów, aby zapewnić nowe funkcje.
akashivskyy
4
Rozumiem, co próbujesz powiedzieć, ale negowanie rozszerzeń nie jest właściwą drogą.
akashivskyy
1
@akashivskyy: Aby to działało poprawnie ze znakami spoza ASCII, musisz dodać NSCharacterEncodingDocumentAttribute, porównaj stackoverflow.com/a/27898167/1187415 .
Martin R
13
Ta metoda jest wyjątkowo ciężka i nie jest zalecana w przypadku widoków tabel i widoków siatki
Guido Lodetti
1
To jest świetne! Chociaż blokuje główny wątek, czy istnieje sposób, aby uruchomić go w wątku w tle?
MMV
78

Odpowiedź @ akashivskyy jest świetna i pokazuje, jak wykorzystać NSAttributedStringdo dekodowania encji HTML. Jedną z możliwych wad (jak stwierdził) jest to, że wszystko usuwane są również znaczniki HTML, więc

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

staje się

4 < 5 & 3 > 2

Na OS X jest CFXMLCreateStringByUnescapingEntities() który wykonuje zadanie:

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

ale to nie jest dostępne na iOS.

Oto czysta implementacja Swift. Odszyfrowuje odniesienia do jednostek znakowych, takie jak &lt;użycie słownika, i wszystkie numeryczne jednostki znakowe, takie jak&#64 lub &#x20ac. (Zauważ, że nie wymieniłem wyraźnie wszystkich 252 encji HTML).

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

Przykład:

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.
źródło
10
To jest świetne, dzięki Martin! Oto rozszerzenie z pełną listą encji HTML: gist.github.com/mwaterfall/25b4a6a06dc3309d9555. Również nieznacznie dostosowałem je, aby zapewnić przesunięcia odległości wykonane przez zamienniki. Pozwala to na poprawne dostosowanie wszelkich atrybutów ciągów lub jednostek, na które mogą mieć wpływ te zamiany (na przykład indeksy encji Twittera).
Michael Waterfall
3
@MichaelWaterfall and Martin, to jest wspaniałe! działa jak marzenie! Aktualizuję rozszerzenie dla Swift 2 pastebin.com/juHRJ6au Dzięki!
Santiago,
1
Przekonwertowałem tę odpowiedź na kompatybilną ze Swift 2 i wrzuciłem ją do CocoaPod o nazwie StringExtensionHTML dla łatwości użycia. Zauważ, że wersja Santiago Swift 2 naprawia błędy czasu kompilacji, ale strtooul(string, nil, base)całkowite usunięcie spowoduje, że kod nie będzie działał z numerycznymi jednostkami znakowymi i ulegnie awarii, jeśli chodzi o jednostkę, której nie rozpoznaje (zamiast z wdziękiem).
Adela Chang
1
@AdelaChang: Właściwie przekonwertowałem moją odpowiedź na Swift 2 już we wrześniu 2015. Nadal kompiluje się bez ostrzeżeń ze Swift 2.2 / Xcode 7.3. Czy odnosisz się do wersji Michaela?
Martin R
1
Dzięki tej odpowiedzi rozwiązałem swoje problemy: miałem poważne problemy z wydajnością przy użyciu NSAttributedString.
Andrea Mugnaini
27

Wersja Swift 3 rozszerzenia @ 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
źródło
Działa świetnie. Oryginalna odpowiedź powodowała dziwną awarię. Dzięki za aktualizację!
Geoherna
W przypadku znaków francuskich muszę użyć utf16
Sébastien REMY
23

Szybki 4


  • Zmienna obliczona rozszerzenia ciągu
  • Bez dodatkowej osłony, zrób, złap itp.
  • Zwraca oryginalne ciągi, jeśli dekodowanie nie powiedzie się

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
źródło
1
Łał ! działa od razu po wyjęciu z pudełka dla Swift 4! Użycie // let encoded = "The Weeknd & # 8216; King Of The Fall & # 8217;" let finalString = encoded.htmlDecoded
Naishta
2
Uwielbiam prostotę tej odpowiedzi. Jednak będzie powodować awarie, gdy będzie działać w tle, ponieważ próbuje uruchomić się w głównym wątku.
Jeremy Hicks
14

Wersja Swift 2 rozszerzenia @ 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
źródło
Ten kod jest niekompletny i należy go za wszelką cenę unikać. Błąd nie jest prawidłowo obsługiwany. Gdy faktycznie istnieje, kod błędu ulegnie awarii. Powinieneś zaktualizować swój kod, aby co najmniej zwracał nil, gdy wystąpi błąd. Lub możesz po prostu zainicjować z oryginalnym ciągiem. Na koniec powinieneś sobie poradzić z błędem. A tak nie jest. Łał!
oyalhi
9

Wersja 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
źródło
Otrzymuję komunikat „Error Domain = NSCocoaErrorDomain Code = 259” Nie można otworzyć pliku, ponieważ nie jest w odpowiednim formacie. „”, Gdy próbuję tego użyć. To znika, jeśli uruchomię pełny chwyt na głównym wątku. Znalazłem to po sprawdzeniu dokumentacji NSAttributedString: „Importer HTML nie powinien być wywoływany z wątku działającego w tle (to znaczy słownik opcji zawiera typ documentType z wartością html). Spróbuje zsynchronizować się z głównym wątkiem, zakończy się niepowodzeniem i koniec czasu."
MickeDG,
8
Proszę, rawValueskładnia NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)i NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)jest okropna. Zamień go na .documentTypei.characterEncoding
vadian
@MickeDG - Czy możesz wyjaśnić, co dokładnie zrobiłeś, aby rozwiązać ten błąd? Dostaję to sporatycznie.
Ross Barbish
@RossBarbish - Przepraszam Ross, to było zbyt dawno, nie pamiętam szczegółów. Czy wypróbowałeś to, co sugeruję w komentarzu powyżej, tj. Uruchomienie pełnego do catch w głównym wątku?
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’ */
wLc
źródło
Re „The Weeknd” : a nie „The Weekend” ?
Peter Mortensen
Podświetlanie składni wygląda dziwnie, zwłaszcza część komentarza w ostatniej linii. Czy możesz to naprawić?
Peter Mortensen
„The Weeknd” to piosenkarz i tak, tak zapisuje się jego imię.
wLc
5

Szukałem czystego narzędzia Swift 3.0, aby uciec do / unescape z odniesień znaków HTML (tj. Dla aplikacji Swift po stronie serwera zarówno na macOS, jak i Linux), ale nie znalazłem żadnych kompleksowych rozwiązań, więc napisałem własną implementację: https: //github.com/IBM-Swift/swift-html-entities

Pakiet, HTMLEntitiesdziała z nazwanymi referencjami znakowymi HTML4, jak również numerycznymi referencjami znakowymi hex / dec i rozpoznaje specjalne numeryczne referencje znakowe zgodnie ze specyfikacją W3 HTML5 (tj. &#x80;Powinny być bez znaku zmiany znaczenia jako znak Euro (unicode U+20AC), a NIE jako Unicode znak dla U+0080, a niektóre zakresy numerycznych odniesień do znaków należy zastąpić znakiem zastępczym, U+FFFDgdy nie ma znaku zmiany znaczenia).

Przykład użycia:

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

A na przykład 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 "

Edycja: HTMLEntitiesteraz obsługuje nazwane odwołania do znaków HTML5 od wersji 2.0.0. Zaimplementowano również analizę zgodną ze specyfikacją.

Youming Lin
źródło
1
Jest to najbardziej ogólna odpowiedź, która działa przez cały czas i nie wymaga uruchamiania w głównym wątku. To zadziała nawet z najbardziej złożonymi ciągami znaków Unicode z ucieczką HTML (takimi jak (&nbsp;͡&deg;&nbsp;͜ʖ&nbsp;͡&deg;&nbsp;)), podczas gdy żadna z pozostałych odpowiedzi tego nie zarządza.
Stéphane Copin
5

Swift 4:

Kompletne rozwiązanie, które w końcu zadziałało z kodem HTML, znakami nowej linii i pojedynczymi cudzysłowami

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

Stosowanie:

let yourStringEncoded = yourStringWithHtmlcode.htmlDecoded

Następnie musiałem zastosować więcej filtrów, aby pozbyć się pojedynczych cudzysłowów (na przykład nie , nie ma , to itp.) I nowych znaków wiersza, takich jak \n:

var yourNewString = String(yourStringEncoded.filter { !"\n\t\r".contains($0) })
yourNewString = yourNewString.replacingOccurrences(of: "\'", with: "", options: NSString.CompareOptions.literal, range: nil)
Naishta
źródło
Zasadniczo jest to kopia tej innej odpowiedzi . Wszystko, co zrobiłeś, to dodanie użycia, które jest wystarczająco oczywiste.
rmaddy
ktoś zagłosował za tą odpowiedzią i uznał ją za naprawdę przydatną, co ci to mówi?
Naishta
@Naishta Mówi ci, że każdy ma inne zdanie i to jest OK
Josh Wolff
3

To byłoby moje podejście. Możesz dodać słownik podmiotów z https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555, o którym wspomina Michael Waterfall.

extension String {
    func htmlDecoded()->String {

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

        var newStr = self

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

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

Zastosowane przykłady:

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

LUB

let encoded = "this is so &quot;good&quot;".htmlDecoded() // "this is so "good""
Bseaborn
źródło
1
Nie podoba mi się to, ale nie znalazłem jeszcze nic lepszego, więc jest to zaktualizowana wersja rozwiązania Michael Waterfall dla Swift 2.0 gist.github.com/jrmgx/3f9f1d330b295cf6b1c6
jrmgx
3

Eleganckie rozwiązanie Swift 4

Jeśli chcesz sznur,

myString = String(htmlString: encodedString)

dodaj to rozszerzenie do swojego projektu:

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

Jeśli chcesz NSAttributedString z pogrubieniem, kursywą, linkami itp.,

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

dodaj to rozszerzenie do swojego projektu:

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
źródło
2

Obliczona zmienna wersja odpowiedzi @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
źródło
1

Szybki 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
źródło
Wytłumaczenie byłoby w porządku. Na przykład, czym różni się od poprzednich odpowiedzi w języku 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
źródło
Wytłumaczenie byłoby w porządku. Na przykład, czym różni się od poprzednich odpowiedzi? Jakie funkcje Swift 4.1 są używane? Czy działa tylko w Swift 4.1, a nie w poprzednich wersjach? A może działałoby przed Swift 4.1, powiedzmy w Swift 4.0?
Peter Mortensen
1

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

Proste użycie

let clean = "Weeknd &#8216;King Of The Fall&#8217".replacingHTMLEntities ?? "default value"
pytający
źródło
Słyszę już, jak ludzie narzekają na opcjonalne rozpakowanie mojej siły. Jeśli badasz kodowanie ciągów znaków HTML i nie wiesz, jak radzić sobie z opcjami Swift, jesteś zbyt daleko przed sobą.
quemeful
tak, było ( zredagowane 1 listopada o 22:37 i sprawiło, że „Proste użycie” było znacznie trudniejsze do zrozumienia)
quemeful
1

Szybki 4

Bardzo podoba mi się rozwiązanie wykorzystujące documentAttributes. Jednak może być zbyt wolny do analizowania plików i / lub użycia w komórkach widoku tabeli. Nie mogę uwierzyć, że Apple nie zapewnia na to porządnego rozwiązania.

Aby obejść ten problem, znalazłem to rozszerzenie ciągu na GitHub, które działa doskonale i jest szybkie do dekodowania.

A więc w sytuacjach, w których podana odpowiedź jest wolna , zobacz rozwiązanie sugerowane w tym linku: https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555

Uwaga: nie analizuje tagów HTML.

Vincent
źródło
1

Zaktualizowana odpowiedź działająca w 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
źródło
0

Cel 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
źródło
0

Wersja Swift 3.0 z faktyczną konwersją rozmiaru czcionki

Zwykle, jeśli bezpośrednio konwertujesz zawartość HTML na przypisany ciąg, rozmiar czcionki jest zwiększany. Możesz spróbować przekonwertować ciąg HTML na przypisany ciąg i ponownie, aby zobaczyć różnicę.

Zamiast tego, oto rzeczywista konwersja rozmiaru, która zapewnia, że ​​rozmiar czcionki się nie zmieni, stosując współczynnik 0,75 do wszystkich czcionek:

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
źródło
0

Szybki 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
źródło
Proszę, rawValueskładnia NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)i NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)jest okropna. Zastąp go .documentTypei.characterEncoding
vadian
Wydajność tego rozwiązania jest fatalna. Może to być w porządku w przypadku oddzielnych cez, analizowanie plików nie jest zalecane.
Vincent,
0

Spójrz na HTMLString - bibliotece napisanej w języku Swift, która umożliwia programowi dodawanie i usuwanie encji HTML ciągach znaków

Dla kompletności skopiowałem główne funkcje ze strony:

  • Dodaje jednostki do kodowania ASCII i UTF-8 / UTF-16
  • Usuwa ponad 2100 nazwanych encji (takich jak &)
  • Obsługuje usuwanie jednostek dziesiętnych i szesnastkowych
  • Zaprojektowany do obsługi Swift Extended Grapheme Clusters (→ 100% odporny na emoji)
  • W pełni przetestowane jednostkowo
  • Szybki
  • Udokumentowane
  • Kompatybilny z Objective-C
Despotovic
źródło
0

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

Ponadto, jeśli chcesz wyodrębnić datę, obrazy, metadane, tytuł i opis, możesz użyć mojego modułu o nazwie:

] [1].

Zestaw czytelności

jawadAli
źródło
Co takiego by nie działało w niektórych wcześniejszych wersjach, Swift 5.0, Swift 4.1, Swift 4.0 itd.?
Peter Mortensen
Znalazłem błąd podczas dekodowania ciągu za pomocą collectionViews
Tung Vu Duc
-1

Posługiwać się:

NSData dataRes = (nsdata value )

var resString = NSString(data: dataRes, encoding: NSUTF8StringEncoding)
Półka Yogesh
źródło
Wyjaśnienie byłoby w porządku ( edytując odpowiedź , a nie tutaj w komentarzach).
Peter Mortensen