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

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)

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

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

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

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

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

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’ */

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


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)

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

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

}

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

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
}

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
} 

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"

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.


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

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

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

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

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

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


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.