SwiftでHTMLエンティティをデコードするにはどうすればよいですか?


121

サイトからJSONファイルを取得していますが、受け取った文字列の1つは次のとおりです。

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 2014

4
何を言っているのかわかりましたが、拡張機能を無効にすることはできません。
akashivskyy 2014

1
@akashivskyy:非ASCII文字を正しく、この作業を行うには、あなたはNSCharacterEncodingDocumentAttribute、比較追加する必要がありstackoverflow.com/a/27898167/1187415を
マーティンR

13
この方法は非常に重く、テーブルビューやグリッドビューではお勧めしません
グイドロデッティ2015

1
これは素晴らしい!メインスレッドをブロックしますが、バックグラウンドスレッドで実行する方法はありますか?
MMV

78

@akashivskyyの回答はすばらしいものであり、NSAttributedStringHTMLエンティティのデコードにどのように利用するかを示しています。(彼が述べたように)1つの考えられる欠点は、すべての 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。(252個のHTMLエンティティをすべて明示的にリストしたわけではないことに注意してください。)

スウィフト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.  @

スウィフト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
    }
}

スウィフト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エンティティインデックスなど)を正しく調整できます。
マイケルウォーターフォール

3
@MichaelWaterfallとマーティンこれは素晴らしいです!魅力のように動作します!Swift 2 pastebin.com/juHRJ6auの拡張機能を更新していただきありがとうございます。
サンティアゴ

1
私はこの回答をSwift 2と互換性があるように変換し、使いやすさのためにStringExtensionHTMLと呼ばれるCocoaPodにダンプしました。SantiagoのSwift 2バージョンはコンパイル時エラーを修正しますが、strtooul(string, nil, base)完全に削除すると、コードが数字のエンティティで動作せず、認識できないエンティティになるとクラッシュします(正常に失敗するのではなく)。
アデラチャン

1
@AdelaChang:実際には、2015年9月に回答をSwift 2に変換しました。Swift2.2 / Xcode 7.3でも警告なしにコンパイルされます。それとも、マイケルのバージョンのことですか?
マーティンR

1
おかげで、この回答で問題を解決しました。NSAttributedStringを使用すると、パフォーマンスに深刻な問題が発生しました。
Andrea Mugnaini、2018年

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 2016年

フランスのキャラクターにはutf16を使用する必要があります
セバスチャン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;" let finalString = encoding.htmlDecoded
Naishta

2
私はこの答えの単純さが大好きです。ただし、メインスレッドで実行しようとするため、バックグラウンドで実行するとクラッシュします。
ジェレミーヒックス

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

このコードは不完全なため、絶対に避けてください。エラーは適切に処理されていません。実際にエラーコードがあるとクラッシュします。エラーが発生した場合は、少なくともnilを返すようにコードを更新する必要があります。または、元の文字列で初期化することもできます。最後に、エラーを処理する必要があります。そうではありません。うわー!
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インポーターはバックグラウンドスレッドから呼び出すべきではありません(つまり、オプションディクショナリには、htmlの値を持つdocumentTypeが含まれています。メインスレッドと同期しようとして失敗します。タイムアウト。"
MickeDG 2017年

8
、してくださいrawValue構文 NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.documentType.rawValue)NSAttributedString.DocumentReadingOptionKey(rawValue: NSAttributedString.DocumentAttributeKey.characterEncoding.rawValue)恐ろしいです。それを.documentTypeandに置き換えます.characterEncoding
vadian

@MickeDG-このエラーを解決するために何をしたか正確に説明できますか?私はそれを散発的に受けています。
ロスバービッシュ

@RossBarbish-すみません、Rossさん、これは古すぎて、詳細を覚えられません。上記のコメントで提案したこと、つまり、メインスレッドで完全な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’ */

「ザ・ウィークエンド」はない:「週末」
Peter Mortensen、

構文の強調表示は、特に最終行のコメント部分が奇妙に見えます。直せますか?
Peter Mortensen、

「ザ・ウィークエンド」は歌手であり、そうです、それが彼の名前の綴りです。
wLc

5

HTML文字参照にエスケープ/エスケープしない純粋なSwift 3.0ユーティリティ(つまり、macOSとLinuxの両方のサーバー側Swiftアプリ)を探していましたが、包括的なソリューションが見つからなかったため、独自の実装を作成しました:https: //github.com/IBM-Swift/swift-html-entities

パッケージはHTMLEntities、HTML4の名前付き文字参照と16進/ 10進数の文字参照で動作し、W3 HTML5仕様に従って特別な数字の参照を認識します(つまり&#x80;、ユーロ記号(unicode U+20AC)としてエスケープせず、unicodeとしてエスケープしないでください)の文字U+0080、および数値参照の特定の範囲はU+FFFD、エスケープ解除時に置換文字で置き換える必要があります)。

使用例:

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バージョン2.0.0以降、HTML5の名前付き文字参照をサポートするようになりました。仕様に準拠した解析も実装されています。


1
これは常に機能する最も一般的な答えであり、メインスレッドで実行する必要はありません。これは、最も複雑なHTMLエスケープユニコード文字列(など(&nbsp;͡&deg;&nbsp;͜ʖ&nbsp;͡&deg;&nbsp;))でも機能しますが、他の回答ではそれを管理できません。
ステファンCopin

5

スウィフト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

次に、一重引用符(たとえば、「しない」、「しない」だ」など)と次のような改行文字を取り除くために、さらにいくつかのフィルターを適用する必要がありました\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
私はかなりこれ好きではないが、これはスウィフト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のオプションを処理する方法がわからない場合は、自分より先に進んでいることになります。
quemeful

うん、ありました(11月1日22:37に編集され、「単純な使用法」を理解するのがはるかに困難になりました)
2018年

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)恐ろしいです。それを.documentTypeandに置き換えます.characterEncoding
vadian

このソリューションのパフォーマンスは恐ろしいものです。ファイルを解析することはお勧めできません。
Vincent、

0

HTMLStringを見てください-Swiftで記述されたライブラリで、プログラムは文字列内のHTMLエンティティを追加および削除できます。

完全を期すために、主な機能をサイトからコピーしました。

  • ASCIIおよびUTF-8 / UTF-16エンコーディングのエンティティを追加します
  • 2100を超える名前付きエンティティ(&など)を削除します
  • 10進および16進エンティティの削除をサポート
  • 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.