Как декодировать HTML-объекты в swift?
я вытаскиваю файл JSON с сайта, и одна из полученных строк:
The Weeknd ‘King Of The Fall’ [Video Premiere] | @TheWeeknd | #SoPhi
как я могу конвертировать такие вещи, как ‘ в правильные символы?
Я сделал Xcode площадку, чтобы продемонстрировать это:
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"])
20 ответов:
нет простого способа сделать это, но вы можете использовать
NSAttributedStringмагия, чтобы сделать этот процесс максимально безболезненным (имейте в виду, что этот метод также удалит все HTML-теги):let encodedString = "The Weeknd <em>‘King Of The Fall’</em>" // encodedString should = a[0]["title"] in your case guard let data = htmlEncodedString.data(using: .utf8) else { return nil } let options: [String: Any] = [ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue ] guard let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil) else { return nil } let decodedString = attributedString.string // The Weeknd ‘King Of The Fall’не забудьте инициализировать NSAttributedString из основного потока только. Он использует некоторую магию WebKit внизу, таким образом, требование.
вы можете создать свой собственный
Stringрасширение для увеличения возможности повторного использования:extension String { init?(htmlEncodedString: String) { guard let data = htmlEncodedString.data(using: .utf8) else { return nil } let options: [String: Any] = [ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: 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>‘King Of The Fall’</em>" let decodedString = String(htmlEncodedString: encodedString)
@akashivskyy велик и демонстрирует, как использовать
NSAttributedStringдля декодирования HTML-объектов. Один из возможных недостатков (как он заявил) Это все HTML разметка также удаляется, так что<strong> 4 < 5 & 3 > 2</strong>становится
4 < 5 & 3 > 2на OS X есть
CFXMLCreateStringByUnescapingEntities()что делает работу:let encoded = "<strong> 4 < 5 & 3 > 2 .</strong> Price: 12 €. @ " let decoded = CFXMLCreateStringByUnescapingEntities(nil, encoded, nil) as String println(decoded) // <strong> 4 < 5 & 3 > 2 .</strong> Price: 12 €. @но это не доступно на iOS.
вот чистая быстрая реализация. Он декодирует символьные сущности ссылки
<С помощью словаря, и все числовые такие сущности, как@или€. (Обратите внимание, что я не перечислил все 252 HTML-сущности явно.)Swift 4:
// Mapping from XML/HTML character entity reference to character // From http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references private let characterEntities : [ Substring : Character ] = [ // XML predefined entities: """ : "\"", "&" : "&", "'" : "'", "<" : "<", ">" : ">", // HTML character entity references: " " : "\u{00a0}", // ... "♦" : "♦", ] 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("@") --> "@" // decode("€") --> "€" // decode("<") --> "<" // 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 < 5 & 3 > 2 .</strong> Price: 12 €. @ " 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: """ : "\"", "&" : "&", "'" : "'", "<" : "<", ">" : ">", // HTML character entity references: " " : "\u{00a0}", // ... "♦" : "♦", ] 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("@") --> "@" // decode("€") --> "€" // decode("<") --> "<" // 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: """ : "\"", "&" : "&", "'" : "'", "<" : "<", ">" : ">", // HTML character entity references: " " : "\u{00a0}", // ... "♦" : "♦", ] 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("@") --> "@" // decode("€") --> "€" // decode("<") --> "<" // 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 } }
Swift 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 } } }
Swift 2 версия @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 } } }
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 ‘King Of The Fall’" let foo = encodedString.decodeEnt() // The Weeknd ‘King Of The Fall’
Swift 4 Version
extension String { init(htmlEncodedString: String) { self.init() guard let encodedData = htmlEncodedString.data(using: .utf8) else { self = htmlEncodedString return } let attributedOptions: [NSAttributedString.DocumentReadingOptionKey : Any] = [ NSAttributedString.DocumentReadingOptionKey(rawValue: .documentType.rawValue): NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey(rawValue: .characterEncoding.rawValue): String.Encoding.utf8.rawValue ] do { let attributedString = try NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil) self = attributedString.string } catch { print("Error: \(error)") self = htmlEncodedString } } }
Swift 4
- расширение строки вычисляется var
- без дополнительной защиты / do / catch и т. д...
- возвращает исходные строки, если декодирование не удается
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 } }
Я искал чистую утилиту Swift 3.0 для выхода в/unescape из ссылок на символы HTML (т. е. для серверных приложений Swift как на macOS, так и на Linux), но не нашел никаких комплексных решений, поэтому я написал свою собственную реализацию:https://github.com/IBM-Swift/swift-html-entities
пакет,
HTMLEntities, работает с HTML4 именованные ссылки на символы, а также шестнадцатеричные / дек числовые ссылки на символы, и он будет распознавать специальный числовой символ ссылки на спецификацию W3 HTML5 (т. е.€должен быть unescaped как знак евро (unicodeU+20AC) а не как символ Юникода дляU+0080, и некоторые диапазоны ссылок на числовые символы должны быть заменены заменяющим символомU+FFFDкогда невыход).пример использования:
import HTMLEntities // encode example let html = "<script>alert(\"abc\")</script>" print(html.htmlEscape()) // Prints ”<script>alert("abc")</script>" // decode example let htmlencoded = "<script>alert("abc")</script>" print(htmlencoded.htmlUnescape()) // Prints ”<script>alert(\"abc\")</script>"и для примера ОП:
print("The Weeknd ‘King Of The Fall’ [Video Premiere] | @TheWeeknd | #SoPhi ".htmlUnescape()) // prints "The Weeknd ‘King Of The Fall’ [Video Premiere] | @TheWeeknd | #SoPhi "Edit:
HTMLEntitiesтеперь поддерживает HTML5 именованные ссылки на символы с версии 2.0.0. Spec-совместимый синтаксический анализ также реализовать.
вычисленная версия var @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 } } }
Элегантное Решение 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) } }
Это был бы мой подход. Вы можете добавить словарь сущностей изhttps://gist.github.com/mwaterfall/25b4a6a06dc3309d9555 Майкл водопад упоминает.
extension String { func htmlDecoded()->String { guard (self != "") else { return self } var newStr = self let entities = [ """ : "\"", "&" : "&", "'" : "'", "<" : "<", ">" : ">", ] for (name,value) in entities { newStr = newStr.stringByReplacingOccurrencesOfString(name, withString: value) } return newStr } }примеры использовать:
let encoded = "this is so "good"" let decoded = encoded.htmlDecoded() // "this is so "good""или
let encoded = "this is so "good"".htmlDecoded() // "this is so "good""
обновленный ответ работает на 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) }
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 }
версия 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 } }
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 } } }Простота Использования
let clean = "string".replacingHTMLEntities!
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)") } }
Swift4
Мне очень нравится решение с использованием documentAttributes, однако оно может замедляться для разбора файлов и / или использования в ячейках представления таблицы. Я не могу поверить, что Apple не предоставляет достойного решения для этого.
в качестве обходного пути я нашел на GitHub это расширение строки, которое отлично и быстро работает для декодирования.
Примечание: он не анализирует HTML-теги.
для полноты картины я скопировал основные функции с сайта:
- добавляет сущности для кодировок ASCII и UTF-8 / UTF-16
- удаляет более 2100 именованных объектов (например &)
- поддерживает удаление десятичных и шестнадцатеричных сущностей
- разработанный, чтобы поддержать скорейшее расширенный графема кластеров (→ 100% emoji-доказательство)
- полностью подвергнутое испытанию транспортное средство
- быстро
- документирована
- совместим с Objective-C
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затем мне пришлось применить еще несколько фильтров, чтобы избавиться от
single quotes( например:don't, hasn't, It'sи т. д.), и новые символы строки, такие как\nvar yourNewString = String(yourStringEncoded.filter { !"\n\t\r".contains() }) yourNewString = yourNewString.replacingOccurrences(of: "\'", with: "", options: NSString.CompareOptions.literal, range: nil)
NSData dataRes = (значение nsdata)
var resString = NSString (data: dataRes, encoding: NSUTF8StringEncoding)
Comments