ฉันจะถอดรหัสเอนทิตี HTML ใน Swift ได้อย่างไร


121

ฉันกำลังดึงไฟล์ JSON จากไซต์และหนึ่งในสตริงที่ได้รับคือ:

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

ฉันจะแปลงสิ่งต่างๆเช่น&#8216เป็นอักขระที่ถูกต้องได้อย่างไร

ฉันได้สร้าง Xcode Playground เพื่อแสดงให้เห็น:

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

คำตอบ:


157

คำตอบนี้ได้รับการแก้ไขล่าสุดสำหรับ Swift 5.2 และ iOS 13.4 SDK


ไม่มีวิธีที่ตรงไปตรงมาในการทำเช่นนั้น แต่คุณสามารถใช้NSAttributedStringเวทมนตร์เพื่อทำให้กระบวนการนี้ไม่เจ็บปวดมากที่สุด (โปรดเตือนว่าวิธีนี้จะตัดแท็ก HTML ทั้งหมดออกไปด้วย)

จำไว้ว่าให้เริ่มต้นNSAttributedStringจากหัวข้อหลักเท่านั้น ใช้ WebKit เพื่อแยกวิเคราะห์ HTML ด้านล่างดังนั้นจึงเป็นข้อกำหนด

// 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
อะไร? ส่วนขยายของความหมายที่จะขยายประเภทที่มีอยู่เพื่อให้การทำงานใหม่
akashivskyy

4
ฉันเข้าใจสิ่งที่คุณพยายามจะพูด แต่การปฏิเสธส่วนขยายไม่ใช่วิธีที่จะไป
akashivskyy

1
@akashivskyy: เพื่อให้การทำงานนี้อย่างถูกต้องกับตัวละครที่ไม่ใช่ ASCII คุณต้องเพิ่ม NSCharacterEncodingDocumentAttribute เปรียบเทียบstackoverflow.com/a/27898167/1187415
Martin R

13
วิธีนี้หนักมากและไม่แนะนำใน tableviews หรือ gridviews
Guido Lodetti

1
นี่มันเยี่ยมมาก! แม้ว่ามันจะบล็อกเธรดหลัก แต่มีวิธีใดในการเรียกใช้ในเธรดพื้นหลังหรือไม่?
MMV

78

คำตอบของ @ akashivskyy นั้นยอดเยี่ยมและแสดงให้เห็นถึงวิธีการใช้NSAttributedStringเพื่อถอดรหัสเอนทิตี HTML ข้อเสียที่เป็นไปได้อย่างหนึ่ง (ตามที่เขาระบุ) คือมาร์กอัป HTML ทั้งหมดจะถูกลบออกด้วยเช่นกัน

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

กลายเป็น

4 < 5 & 3 > 2

บน OS X มีCFXMLCreateStringByUnescapingEntities()งานอะไร:

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

แต่ไม่สามารถใช้ได้บน iOS

นี่คือการใช้งาน Swift ที่แท้จริง มันถอดรหัสการอ้างอิงเอนทิตีอักขระเช่นการ&lt;ใช้พจนานุกรมและเอนทิตีอักขระตัวเลขทั้งหมดเช่น&#64หรือ&#x20acหรือ(โปรดทราบว่าฉันไม่ได้แสดงรายการเอนทิตี HTML ทั้งหมด 252 รายการอย่างชัดเจน)

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

ตัวอย่าง:

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
ยอดเยี่ยมมากขอบคุณมาร์ติน! นี่คือส่วนขยายที่มีรายการเอนทิตี HTML ทั้งหมด: gist.github.com/mwaterfall/25b4a6a06dc3309d9555ฉันได้ปรับเปลี่ยนเล็กน้อยเพื่อให้มีการชดเชยระยะทางที่เกิดจากการแทนที่ สิ่งนี้ช่วยให้สามารถปรับแอตทริบิวต์สตริงหรือเอนทิตีที่อาจได้รับผลกระทบจากการแทนที่เหล่านี้ได้อย่างถูกต้อง (เช่นดัชนีเอนทิตี Twitter)
Michael Waterfall

3
@MichaelWaterfall และ Martin นี่คือความงดงาม! ใช้งานได้อย่างมีเสน่ห์! ฉันอัปเดตส่วนขยายสำหรับ Swift 2 pastebin.com/juHRJ6auขอบคุณ!
Santiago

1
ฉันแปลงคำตอบนี้ให้เข้ากันได้กับ Swift 2 และทิ้งลงใน CocoaPod ชื่อStringExtensionHTMLเพื่อความสะดวกในการใช้งาน โปรดทราบว่าเวอร์ชัน Swift 2 ของ Santiago แก้ไขข้อผิดพลาดเวลาคอมไพล์ แต่การลบstrtooul(string, nil, base)ทั้งหมดจะทำให้โค้ดไม่ทำงานกับเอนทิตีที่เป็นอักขระตัวเลขและหยุดทำงานเมื่อพูดถึงเอนทิตีที่ไม่รู้จัก (แทนที่จะล้มเหลวอย่างสง่างาม)
Adela Chang

1
@AdelaChang: อันที่จริงฉันได้แปลงคำตอบเป็น Swift 2 ไปแล้วในเดือนกันยายน 2015 มันยังคงรวบรวมโดยไม่มีคำเตือนด้วย Swift 2.2 / Xcode 7.3 หรือคุณหมายถึงเวอร์ชั่นของไมเคิล?
Martin R

1
ขอบคุณสำหรับคำตอบนี้ฉันแก้ไขปัญหาของฉันได้: ฉันมีปัญหาด้านประสิทธิภาพที่ร้ายแรงโดยใช้ NSAttributedString
Andrea Mugnaini

27

สวิฟท์ 3รุ่นของส่วนขยายของ @ 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
        }
    }
}

ใช้งานได้ดี คำตอบเดิมทำให้เกิดความผิดพลาดแปลก ๆ ขอบคุณสำหรับการอัปเดต!
Geoherna

สำหรับตัวอักษรฝรั่งเศสฉันต้องใช้ utf16
Sébastien REMY

23

สวิฟต์ 4


  • ตัวแปรที่คำนวณส่วนขยายสตริง
  • โดยไม่ต้องมีการป้องกันพิเศษทำจับ ฯลฯ ...
  • ส่งคืนสตริงเดิมหากการถอดรหัสล้มเหลว

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
ว้าว ! ใช้งานได้ทันทีสำหรับ Swift 4! การใช้งาน // ให้เข้ารหัส = "The Weeknd & # 8216; King Of The Fall & # 8217;" ให้ finalString = encoded.htmlDecoded
Naishta

2
ฉันชอบความเรียบง่ายของคำตอบนี้ อย่างไรก็ตามจะทำให้เกิดข้อขัดข้องเมื่อทำงานในพื้นหลังเนื่องจากพยายามทำงานบนเธรดหลัก
Jeremy Hicks

14

ส่วนขยายของ@ akashivskyyเวอร์ชัน Swift 2

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

รหัสนี้ไม่สมบูรณ์และควรหลีกเลี่ยงด้วยประการทั้งปวง ข้อผิดพลาดไม่ได้รับการจัดการอย่างเหมาะสม เมื่อมีรหัสข้อผิดพลาดจะผิดพลาด คุณควรอัปเดตรหัสของคุณเป็นอย่างน้อยคืนค่าศูนย์เมื่อมีข้อผิดพลาด หรือคุณสามารถเริ่มต้นด้วยสตริงดั้งเดิม ท้ายที่สุดคุณควรจัดการกับข้อผิดพลาด ซึ่งไม่เป็นเช่นนั้น ว้าว!
oyalhi

9

เวอร์ชัน 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
        }
    }
}

ฉันได้รับ "Error Domain = NSCocoaErrorDomain Code = 259" ไม่สามารถเปิดไฟล์ได้เนื่องจากไม่อยู่ในรูปแบบที่ถูกต้อง "" เมื่อฉันพยายามใช้สิ่งนี้ สิ่งนี้จะหายไปถ้าฉันเรียกใช้ do catch ทั้งหมดบนเธรดหลัก ฉันพบสิ่งนี้จากการตรวจสอบเอกสาร NSAttributedString: "ไม่ควรเรียกตัวนำเข้า HTML จากเธรดพื้นหลัง (นั่นคือพจนานุกรมตัวเลือกรวมถึง documentType ที่มีค่า html) มันจะพยายามซิงโครไนซ์กับเธรดหลักล้มเหลวและ หมดเวลา."
MickeDG

8
กรุณาrawValueไวยากรณ์ NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)และ NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)น่ากลัว แทนที่ด้วย.documentTypeและ.characterEncoding
Vadian

@MickeDG - คุณช่วยอธิบายได้ไหมว่าคุณทำอะไรเพื่อแก้ไขข้อผิดพลาดนี้? ฉันได้รับมันอย่างไม่ใยดี
Ross Barbish

@RossBarbish - ขอโทษรอสนี่นานเกินไปจำรายละเอียดไม่ได้ คุณได้ลองสิ่งที่ฉันแนะนำในความคิดเห็นด้านบนหรือไม่เช่นการเรียกใช้ do catch ทั้งหมดในเธรดหลัก
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" : ไม่ใช่"The Weekend" ?
Peter Mortensen

การเน้นไวยากรณ์ดูแปลก ๆ โดยเฉพาะส่วนความคิดเห็นของบรรทัดสุดท้าย คุณซ่อมได้หรือไม่?
Peter Mortensen

"The Weeknd" เป็นนักร้องและใช่นั่นคือสิ่งที่สะกดชื่อของเขา
wLc

5

ฉันกำลังมองหายูทิลิตี้ Swift 3.0 ที่บริสุทธิ์เพื่อหลบหนีไปที่ / unescape จากการอ้างอิงอักขระ HTML (เช่นสำหรับแอพ Swift ฝั่งเซิร์ฟเวอร์ทั้งบน macOS และ Linux) แต่ไม่พบโซลูชันที่ครอบคลุมดังนั้นฉันจึงเขียนการใช้งานของฉันเอง: https: //github.com/IBM-Swift/swift-html-entities

แพคเกจ HTMLEntitiesทำงานร่วมกับการอ้างอิงอักขระที่มีชื่อ HTML4 เช่นเดียวกับการอ้างอิงอักขระตัวเลขฐานสิบหก / dec และจะรับรู้การอ้างอิงอักขระตัวเลขพิเศษตามข้อกำหนด W3 HTML5 (กล่าวคือ&#x80;ควรไม่ใช้ Escape เป็นเครื่องหมายยูโร (unicode U+20AC) และไม่เป็น Unicode อักขระสำหรับU+0080และบางช่วงของการอ้างอิงอักขระตัวเลขควรถูกแทนที่ด้วยอักขระแทนที่U+FFFDเมื่อไม่ใช้ Escape)

ตัวอย่างการใช้งาน:

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

และสำหรับตัวอย่างของ 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 "

แก้ไข: HTMLEntitiesตอนนี้รองรับ HTML5 ที่มีการอ้างอิงอักขระที่เป็นเวอร์ชัน 2.0.0 นอกจากนี้ยังใช้การแยกวิเคราะห์ตามข้อกำหนด


1
นี่เป็นคำตอบทั่วไปที่สุดที่ใช้ได้ตลอดเวลาและไม่จำเป็นต้องรันบนเธรดหลัก สิ่งนี้จะใช้ได้แม้กับสตริง Unicode ที่ใช้ Escape HTML ที่ซับซ้อนที่สุด (เช่น(&nbsp;͡&deg;&nbsp;͜ʖ&nbsp;͡&deg;&nbsp;)) ในขณะที่ไม่มีคำตอบอื่นใดจัดการได้
Stéphane Copin

5

Swift 4:

โซลูชันทั้งหมดที่ได้ผลสำหรับฉันในที่สุดด้วยโค้ด HTML และอักขระขึ้นบรรทัดใหม่และเครื่องหมายคำพูดเดี่ยว

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

การใช้งาน:

let yourStringEncoded = yourStringWithHtmlcode.htmlDecoded

จากนั้นฉันต้องใช้ตัวกรองเพิ่มเติมเพื่อกำจัดเครื่องหมายคำพูดเดี่ยว (ตัวอย่างเช่นdon't , not , it'sฯลฯ ) และอักขระบรรทัดใหม่เช่น\n:

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

นี้เป็นหลักสำเนาของคำตอบอื่นสิ่งที่คุณทำคือเพิ่มการใช้งานบางอย่างซึ่งชัดเจนเพียงพอ
rmaddy

มีบางคนโหวตคำตอบนี้และพบว่ามีประโยชน์จริง ๆ สิ่งนี้บอกอะไรคุณบ้าง?
Naishta

@Naishta บอกคุณว่าทุกคนมีความคิดเห็นที่แตกต่างกันและไม่เป็นไร
Josh Wolff

3

นี่คงเป็นแนวทางของฉัน คุณสามารถเพิ่มพจนานุกรมเอนทิตีได้จากhttps://gist.github.com/mwaterfall/25b4a6a06dc3309d9555 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
    }
}

ตัวอย่างที่ใช้:

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

หรือ

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

1
ฉันไม่ค่อยชอบสิ่งนี้ แต่ฉันไม่พบอะไรที่ดีกว่านี้ดังนั้นนี่คือเวอร์ชันล่าสุดของโซลูชัน Michael Waterfall สำหรับ Swift 2.0 gist.github.com/jrmgx/3f9f1d330b295cf6b1c6
jrmgx

3

โซลูชัน Swift 4 ที่หรูหรา

ถ้าคุณต้องการสตริง

myString = String(htmlString: encodedString)

เพิ่มส่วนขยายนี้ในโครงการของคุณ:

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

หากคุณต้องการ NSAttributedString ที่มีตัวหนาตัวเอียงลิงก์ ฯลฯ

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

เพิ่มส่วนขยายนี้ในโครงการของคุณ:

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

คำตอบของ@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

สวิฟต์ 4

func decodeHTML(string: String) -> String? {

    var decodedString: String?

    if let encodedData = string.data(using: .utf8) {
        let attributedOptions: [NSAttributedString.DocumentReadingOptionKey : Any] = [
            .documentType: NSAttributedString.DocumentType.html,
            .characterEncoding: String.Encoding.utf8.rawValue
        ]

        do {
            decodedString = try NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil).string
        } catch {
            print("\(error.localizedDescription)")
        }
    }

    return decodedString
}

คำอธิบายจะเป็นไปตามลำดับ ตัวอย่างเช่นมันแตกต่างจากคำตอบของ Swift 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
} 

คำอธิบายจะเป็นไปตามลำดับ ตัวอย่างเช่นมันแตกต่างจากคำตอบก่อนหน้าอย่างไร? ใช้ฟีเจอร์ Swift 4.1 อะไรบ้าง? ใช้งานได้เฉพาะใน Swift 4.1 ไม่ใช่เวอร์ชันก่อนหน้าหรือไม่ หรือจะใช้งานได้ก่อน Swift 4.1 พูดใน Swift 4.0?
Peter Mortensen

1

สวิฟต์ 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
        }
    }
}

ใช้งานง่าย

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

ฉันได้ยินเสียงคนบ่นเกี่ยวกับกองกำลังของฉันที่ไม่จำเป็น หากคุณกำลังค้นคว้าเกี่ยวกับการเข้ารหัสสตริง HTML และคุณไม่รู้วิธีจัดการกับตัวเลือก Swift แสดงว่าคุณล้ำหน้าตัวเองเกินไป
ดับ

ใช่มี ( แก้ไขเมื่อวันที่ 1 พ.ย. เวลา 22:37 น.และทำให้ "การใช้งานแบบง่าย" เข้าใจยากขึ้นมาก)
ดับ

1

สวิฟต์ 4

ฉันชอบวิธีแก้ปัญหาโดยใช้ documentAttributes มาก อย่างไรก็ตามอาจช้าเกินไปสำหรับการแยกวิเคราะห์ไฟล์และ / หรือการใช้งานในเซลล์มุมมองตาราง ฉันไม่อยากจะเชื่อเลยว่า Apple ไม่มีทางออกที่ดีสำหรับเรื่องนี้

เป็นวิธีแก้ปัญหาชั่วคราวฉันพบว่าส่วนขยายสตริงนี้บน GitHub ซึ่งทำงานได้อย่างสมบูรณ์และรวดเร็วสำหรับการถอดรหัส

ดังนั้นสำหรับสถานการณ์ที่คำตอบช้าโปรดดูคำแนะนำการแก้ปัญหาในลิงค์นี้: https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555

หมายเหตุ: ไม่แยกวิเคราะห์แท็ก HTML


1

คำตอบที่อัปเดตทำงานบน 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

Objective-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 พร้อมการแปลงขนาดตัวอักษรจริง

โดยปกติหากคุณแปลงเนื้อหา HTML เป็นสตริงที่มีการระบุโดยตรงขนาดตัวอักษรจะเพิ่มขึ้น คุณสามารถลองแปลงสตริง HTML เป็นสตริงที่มีการระบุแหล่งที่มาและย้อนกลับอีกครั้งเพื่อดูความแตกต่าง

นี่คือการแปลงขนาดจริงที่ทำให้แน่ใจว่าขนาดฟอนต์ไม่เปลี่ยนแปลงโดยใช้อัตราส่วน 0.75 กับฟอนต์ทั้งหมด:

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

สวิฟต์ 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)")
        }
    }

กรุณาrawValueไวยากรณ์ NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)และ NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)น่ากลัว แทนที่ด้วย.documentTypeและ.characterEncoding
Vadian

ประสิทธิภาพของโซลูชันนี้แย่มาก อาจเป็นเรื่องปกติสำหรับซีเอสแยกกันไม่แนะนำให้แยกวิเคราะห์ไฟล์
Vincent

0

ดูHTMLString - ไลบรารีที่เขียนด้วย Swift ที่ช่วยให้โปรแกรมของคุณเพิ่มและลบเอนทิตี HTML ใน Strings

เพื่อความสมบูรณ์ฉันคัดลอกคุณสมบัติหลักจากเว็บไซต์:

  • เพิ่มเอนทิตีสำหรับการเข้ารหัส ASCII และ UTF-8 / UTF-16
  • ลบเอนทิตีที่มีชื่อมากกว่า 2100 รายการ (เช่น &)
  • รองรับการลบเอนทิตีทศนิยมและเลขฐานสิบหก
  • ออกแบบมาเพื่อรองรับ Swift Extended Grapheme Clusters (→ป้องกันอิโมจิ 100%)
  • ทดสอบหน่วยอย่างเต็มที่
  • รวดเร็ว
  • เอกสาร
  • เข้ากันได้กับ Objective-C

0

เวอร์ชัน 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
        }
    }
}

นอกจากนี้หากคุณต้องการแยกวันที่รูปภาพข้อมูลเมตาชื่อและคำอธิบายคุณสามารถใช้พ็อดของฉันที่ชื่อ:

] [1].

ชุดอ่านค่า


อะไรที่จะไม่ทำให้มันใช้งานได้ในเวอร์ชันก่อนหน้าบางอย่าง Swift 5.0, Swift 4.1, Swift 4.0 และอื่น ๆ
Peter Mortensen

ฉันพบข้อผิดพลาดเมื่อถอดรหัสสตริงโดยใช้ collectionViews
Tung Vu Duc

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.