Làm cách nào để giải mã các thực thể HTML trong Swift?


121

Tôi đang lấy một tệp JSON từ một trang web và một trong các chuỗi nhận được là:

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

Làm cách nào để chuyển đổi những thứ như &#8216thành các ký tự chính xác?

Tôi đã tạo một Sân chơi Xcode để chứng minh điều đó:

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

Câu trả lời:


157

Câu trả lời này đã được sửa đổi lần cuối cho Swift 5.2 và iOS 13.4 SDK.


Không có cách nào đơn giản để làm điều đó, nhưng bạn có thể sử dụng NSAttributedString phép thuật để làm cho quá trình này trở nên dễ dàng nhất có thể (được cảnh báo rằng phương pháp này cũng sẽ loại bỏ tất cả các thẻ HTML).

Hãy nhớ chỉ khởi tạo NSAttributedStringtừ luồng chính . Nó sử dụng WebKit để phân tích cú pháp HTML bên dưới, do đó yêu cầu.

// 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
Gì? Tiện ích mở rộng có nghĩa là mở rộng các loại hiện có để cung cấp chức năng mới.
akashivskyy

4
Tôi hiểu bạn đang muốn nói gì nhưng việc phủ định tiện ích không phải là cách tốt nhất.
akashivskyy

1
@akashivskyy: Để làm cho điều này hoạt động chính xác với các ký tự không phải ASCII, bạn phải thêm NSCharacterEncodingDocumentAttribute, so sánh stackoverflow.com/a/27898167/1187415 .
Martin R

13
Phương pháp này đặc biệt nặng nhọc và không được khuyến khích trong tableviews hoặc GridViews
Guido Lodetti

1
Điều đó thật tuyệt! Mặc dù nó chặn luồng chính, nhưng có cách nào để chạy nó trong luồng nền không?
MMV

78

Câu trả lời của @ akashivskyy rất hay và thể hiện cách sử dụng NSAttributedStringđể giải mã các thực thể HTML. Một bất lợi có thể xảy ra (như anh ấy đã nêu) là tất cả đánh dấu HTML cũng bị xóa, vì vậy

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

trở thành

4 < 5 & 3 > 2

Trên OS X có CFXMLCreateStringByUnescapingEntities()nó thực hiện công việc:

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

nhưng điều này không khả dụng trên iOS.

Đây là một triển khai Swift thuần túy. Nó giải mã các tham chiếu thực thể ký tự như &lt;sử dụng từ điển và tất cả các thực thể ký tự số như &#64hoặc &#x20ac. (Lưu ý rằng tôi đã không liệt kê tất cả 252 thực thể HTML một cách rõ ràng.)

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

Thí dụ:

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
Điều này thật tuyệt vời, cảm ơn Martin! Đây là tiện ích mở rộng với danh sách đầy đủ các thực thể HTML: gist.github.com/mwaterfall/25b4a6a06dc3309d9555 Tôi cũng đã điều chỉnh một chút để cung cấp hiệu số khoảng cách do các phần thay thế thực hiện. Điều này cho phép điều chỉnh chính xác bất kỳ thuộc tính hoặc thực thể chuỗi nào có thể bị ảnh hưởng bởi những thay thế này (ví dụ: chỉ số thực thể Twitter).
Thác nước Michael

3
@MichaelWaterfall và Martin, điều này thật tuyệt vời! hoạt động như một sự quyến rũ! Tôi cập nhật phần mở rộng cho Swift 2 pastebin.com/juHRJ6au Cảm ơn!
Santiago

1
Tôi đã chuyển đổi câu trả lời này để tương thích với Swift 2 và đưa nó vào CocoaPod có tên là StringExtensionHTML để dễ sử dụng. Lưu ý rằng phiên bản Swift 2 của Santiago sửa lỗi thời gian biên dịch, nhưng việc loại bỏ strtooul(string, nil, base)hoàn toàn sẽ khiến mã không hoạt động với các thực thể ký tự số và gặp sự cố khi gặp một thực thể mà nó không nhận ra (thay vì lỗi một cách duyên dáng).
Adela Chang

1
@AdelaChang: Trên thực tế, tôi đã chuyển đổi câu trả lời của mình sang Swift 2 vào tháng 9 năm 2015. Nó vẫn biên dịch mà không có cảnh báo với Swift 2.2 / Xcode 7.3. Hay bạn đang đề cập đến phiên bản của Michael?
Martin R

1
Cảm ơn, với câu trả lời này, tôi đã giải quyết được vấn đề của mình: Tôi gặp sự cố hiệu suất nghiêm trọng khi sử dụng NSAttributedString.
Andrea Mugnaini

27

Phiên bản Swift 3 của phần mở rộng của @ 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
        }
    }
}

Hoạt động tuyệt vời. Câu trả lời ban đầu đã gây ra sự cố kỳ lạ. Cảm ơn đã cập nhật!
Geoherna

Đối với nhân vật Pháp tôi phải sử dụng UTF16
Sébastien REMY

23

Swift 4


  • Biến tính toán của phần mở rộng về chuỗi
  • Nếu không có bảo vệ bổ sung, hãy làm, bắt, v.v.
  • Trả về các chuỗi ban đầu nếu giải mã không thành công

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
Chà! hoạt động ngay lập tức cho Swift 4 !. Cách sử dụng // let encoded = "The Weeknd & # 8216; King Of The Fall & # 8217;" let finalString = encoded.htmlĐã mã hóa
Naishta

2
Tôi thích sự đơn giản của câu trả lời này. Tuy nhiên, nó sẽ gây ra lỗi khi chạy ở chế độ nền vì nó cố gắng chạy trên luồng chính.
Jeremy Hicks

14

Phiên bản Swift 2 của phần mở rộng của @ 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
         }
     }
 }

Mã này không đầy đủ và nên tránh bằng mọi cách. Lỗi không được xử lý đúng cách. Khi thực tế có một mã lỗi sẽ bị lỗi. Bạn nên cập nhật mã của mình để ít nhất trả về nil khi có lỗi. Hoặc bạn có thể chỉ cần nhập bằng chuỗi gốc. Cuối cùng bạn nên xử lý lỗi. Đó không phải là trường hợp. Chà!
oyalhi

9

Phiên bản 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
        }
    }
}

Tôi nhận được "Error Domain = NSCocoaErrorDomain Code = 259" Không thể mở tệp vì nó không có định dạng chính xác. "" Khi tôi cố gắng sử dụng. Điều này sẽ biến mất nếu tôi chạy toàn bộ do bắt được trên chuỗi chính. Tôi tìm thấy điều này khi kiểm tra tài liệu NSAttributedString: "Trình nhập HTML không nên được gọi từ một chuỗi nền (nghĩa là từ điển tùy chọn bao gồm documentType với giá trị là html). Nó sẽ cố gắng đồng bộ hóa với chuỗi chính, không thành công và hết giờ."
MickeDG

8
Xin vui lòng, rawValuecú pháp NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)là kinh khủng. Thay thế nó bằng .documentType.characterEncoding
vadian

@MickeDG - Bạn có thể giải thích chính xác những gì bạn đã làm để giải quyết lỗi này không? Tôi đang nhận được nó một cách rời rạc.
Ross Barbish

@RossBarbish - Xin lỗi Ross, chuyện này lâu quá rồi, không nhớ chi tiết được. Bạn đã thử những gì tôi đề xuất trong nhận xét ở trên, tức là để chạy toàn bộ do catch trên main thread chưa?
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" : Không phải "The Weekend" ?
Peter Mortensen

Đánh dấu cú pháp trông kỳ lạ, đặc biệt là phần bình luận của dòng cuối cùng. Bạn có thể sửa nó không?
Peter Mortensen

"The Weeknd" là một ca sĩ, và vâng, đó là cách viết tên của anh ấy.
wLc

5

Tôi đang tìm kiếm một tiện ích Swift 3.0 thuần túy để thoát khỏi / unescape khỏi các tham chiếu ký tự HTML (tức là cho các ứng dụng Swift phía máy chủ trên cả macOS và Linux) nhưng không tìm thấy bất kỳ giải pháp toàn diện nào, vì vậy tôi đã viết triển khai của riêng mình: https: //github.com/IBM-Swift/swift-html-entities

Gói, HTMLEntitieshoạt động với các tham chiếu ký tự có tên HTML4 cũng như các tham chiếu ký tự số hex / dec, và nó sẽ nhận ra các tham chiếu ký tự số đặc biệt theo thông số W3 HTML5 (tức là &#x80;không được thoát dưới dạng dấu Euro (unicode U+20AC) và KHÔNG phải là unicode ký tự cho U+0080và một số phạm vi tham chiếu ký tự số nhất định phải được thay thế bằng ký tự thay thếU+FFFD khi bỏ thoát).

Ví dụ sử dụng:

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

Và ví dụ của 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 "

Chỉnh sửa: HTMLEntitieshiện hỗ trợ tham chiếu ký tự có tên HTML5 kể từ phiên bản 2.0.0. Phân tích cú pháp tuân thủ thông số kỹ thuật cũng được triển khai.


1
Đây là câu trả lời chung nhất hoạt động mọi lúc và không yêu cầu chạy trên luồng chính. Điều này sẽ hoạt động ngay cả với các chuỗi unicode thoát HTML phức tạp nhất (chẳng hạn như (&nbsp;͡&deg;&nbsp;͜ʖ&nbsp;͡&deg;&nbsp;)), trong khi không có câu trả lời nào khác quản lý điều đó.
Stéphane Copin

5

Swift 4:

Giải pháp tổng thể cuối cùng đã làm việc cho tôi với mã HTML và các ký tự dòng mới và dấu ngoặc kép

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

Sử dụng:

let yourStringEncoded = yourStringWithHtmlcode.htmlDecoded

Sau đó, tôi phải áp dụng thêm một số bộ lọc để loại bỏ các dấu ngoặc kép (ví dụ: không , không , Đó là , v.v.) và các ký tự dòng mới như \n:

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

Đây thực chất là một bản sao của câu trả lời khác này . Tất cả những gì bạn đã làm là thêm một số cách sử dụng là đủ rõ ràng.
rmaddy

một số người đã ủng hộ câu trả lời này và thấy nó thực sự hữu ích, điều đó cho bạn biết điều gì?
Naishta

@Naishta Nó cho bạn biết rằng mọi người đều có ý kiến ​​khác nhau và điều đó không sao cả
Josh Wolff

3

Đây sẽ là cách tiếp cận của tôi. Bạn có thể thêm từ điển thực thể từ https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555 Michael Waterfall đề cập.

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

Các ví dụ được sử dụng:

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

HOẶC LÀ

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

1
Tôi không hoàn toàn thích điều này nhưng tôi chưa tìm thấy gì tốt hơn vì vậy đây là phiên bản cập nhật của giải pháp Michael Waterfall cho Swift 2.0 gist.github.com/jrmgx/3f9f1d330b295cf6b1c6
jrmgx

3

Giải pháp Swift 4 thanh lịch

Nếu bạn muốn một chuỗi,

myString = String(htmlString: encodedString)

thêm tiện ích mở rộng này vào dự án của bạn:

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

Nếu bạn muốn một Chuỗi NSAttributedString in đậm, in nghiêng, liên kết, v.v.,

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

thêm tiện ích mở rộng này vào dự án của bạn:

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

Phiên bản var được tính toán của @yishus 'answer

public extension String {
    /// Decodes string with HTML encoding.
    var htmlDecoded: String {
        guard let encodedData = self.data(using: .utf8) else { return self }

        let attributedOptions: [String : Any] = [
            NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
            NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue]

        do {
            let attributedString = try NSAttributedString(data: encodedData,
                                                          options: attributedOptions,
                                                          documentAttributes: nil)
            return attributedString.string
        } catch {
            print("Error: \(error)")
            return self
        }
    }
}

1

Swift 4

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

    var decodedString: String?

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

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

    return decodedString
}

Một lời giải thích sẽ được theo thứ tự. Ví dụ, nó khác với các câu trả lời Swift 4 trước đó như thế nào?
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
} 

Một lời giải thích sẽ được theo thứ tự. Ví dụ, nó khác với các câu trả lời trước như thế nào? Những tính năng nào của Swift 4.1 được sử dụng? Nó chỉ hoạt động trong Swift 4.1 và không hoạt động trong các phiên bản trước? Hay nó sẽ hoạt động trước Swift 4.1, chẳng hạn như Swift 4.0?
Peter Mortensen

1

Swift 4

extension String {
    var replacingHTMLEntities: String? {
        do {
            return try NSAttributedString(data: Data(utf8), options: [
                .documentType: NSAttributedString.DocumentType.html,
                .characterEncoding: String.Encoding.utf8.rawValue
            ], documentAttributes: nil).string
        } catch {
            return nil
        }
    }
}

Cách sử dụng đơn giản

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

Tôi đã có thể nghe thấy mọi người phàn nàn về lực lượng của tôi không được gói tùy chọn. Nếu bạn đang nghiên cứu về mã hóa chuỗi HTML và bạn không biết cách đối phó với các tùy chọn Swift, thì bạn đang vượt quá xa so với chính mình.
đáng kinh ngạc

vâng, có (được chỉnh sửa vào ngày 1 tháng 11 lúc 22:37 và làm cho "Cách sử dụng đơn giản" khó hiểu hơn nhiều)
đáng kinh ngạc

1

Swift 4

Tôi thực sự thích giải pháp sử dụng documentAttributes. Tuy nhiên, nó có thể quá chậm để phân tích cú pháp tệp và / hoặc việc sử dụng trong ô xem bảng. Tôi không thể tin rằng Apple không cung cấp một giải pháp tốt cho việc này.

Để giải quyết vấn đề, tôi đã tìm thấy Phần mở rộng chuỗi này trên GitHub hoạt động hoàn hảo và nhanh chóng để giải mã.

Vì vậy, đối với các tình huống mà câu trả lời đã cho là chậm , hãy xem giải pháp được đề xuất trong liên kết này: https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555

Lưu ý: nó không phân tích cú pháp thẻ HTML.


1

Câu trả lời cập nhật hoạt động trên 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

Phiên bản Swift 3.0 với chuyển đổi kích thước phông chữ thực tế

Thông thường, nếu bạn chuyển đổi trực tiếp nội dung HTML thành một chuỗi phân bổ, kích thước phông chữ sẽ tăng lên. Bạn có thể thử chuyển đổi một chuỗi HTML thành một chuỗi được phân bổ và quay lại một lần nữa để xem sự khác biệt.

Thay vào đó, đây là chuyển đổi kích thước thực tế để đảm bảo kích thước phông chữ không thay đổi, bằng cách áp dụng tỷ lệ 0,75 trên tất cả các phông chữ:

extension String {
    func htmlAttributedString() -> NSAttributedString? {
        guard let data = self.data(using: String.Encoding.utf16, allowLossyConversion: false) else { return nil }
        guard let attriStr = try? NSMutableAttributedString(
            data: data,
            options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
            documentAttributes: nil) else { return nil }
        attriStr.beginEditing()
        attriStr.enumerateAttribute(NSFontAttributeName, in: NSMakeRange(0, attriStr.length), options: .init(rawValue: 0)) {
            (value, range, stop) in
            if let font = value as? UIFont {
                let resizedFont = font.withSize(font.pointSize * 0.75)
                attriStr.addAttribute(NSFontAttributeName,
                                         value: resizedFont,
                                         range: range)
            }
        }
        attriStr.endEditing()
        return attriStr
    }
}

0

Swift 4

extension String {

    mutating func toHtmlEncodedString() {
        guard let encodedData = self.data(using: .utf8) else {
            return
        }

        let attributedOptions: [NSAttributedString.DocumentReadingOptionKey : Any] = [
            NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue): NSAttributedString.DocumentType.html,
            NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue): String.Encoding.utf8.rawValue
        ]

        do {
            let attributedString = try NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil)
            self = attributedString.string
        }
        catch {
            print("Error: \(error)")
        }
    }

Xin vui lòng, rawValuecú pháp NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)là kinh khủng. Thay thế nó bằng .documentType.characterEncoding
vadian

Hiệu suất của giải pháp này là khủng khiếp. Có thể không sao đối với các caes riêng biệt, không nên phân tích cú pháp tệp.
Vincent

0

Hãy xem HTMLString - một thư viện được viết bằng Swift cho phép chương trình của bạn thêm và xóa các thực thể HTML trong Chuỗi

Để hoàn thiện, tôi đã sao chép các tính năng chính từ trang web:

  • Thêm các thực thể cho mã hóa ASCII và UTF-8 / UTF-16
  • Xóa hơn 2100 thực thể được đặt tên (như &)
  • Hỗ trợ xóa các thực thể thập phân và thập lục phân
  • Được thiết kế để hỗ trợ Cụm đồ thị mở rộng Swift (→ 100% chống biểu tượng cảm xúc)
  • Đã kiểm tra toàn bộ đơn vị
  • Nhanh
  • Được ghi lại
  • Tương thích với Objective-C

0

Phiên bản 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
        }
    }
}

Ngoài ra, nếu bạn muốn trích xuất ngày tháng, hình ảnh, siêu dữ liệu, tiêu đề và mô tả, bạn có thể sử dụng nhóm của tôi có tên:

] [1].

Bộ khả năng đọc


Điều gì khiến nó không hoạt động trong một số phiên bản trước, Swift 5.0, Swift 4.1, Swift 4.0, v.v.?
Peter Mortensen

Tôi đã tìm thấy lỗi khi giải mã chuỗi bằng cách sử dụng collectionViews
Tung Vu Duc

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.