Как декодировать HTML-объекты в swift?

Я вытаскиваю 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"]) 

Нет простого способа сделать это, но вы можете использовать магию NSAttributedString чтобы сделать этот процесс максимально безболезненным (следует предупредить, что этот метод также удалит все tags HTML):

 let encodedString = "The Weeknd ‘King Of The Fall’" // 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 только из основного streamа . Он использует магию 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 ‘King Of The Fall’" let decodedString = String(htmlEncodedString: encodedString) 

Ответ @ akashivskyy велик и демонстрирует, как использовать NSAttributedString для декодирования объектов HTML. Одним из возможных недостатков (по его словам) является то, что вся разметка HTML также удаляется, поэтому

  4 < 5 & 3 > 2 

становится

 4 < 5 & 3 > 2 

На OS X есть CFXMLCreateStringByUnescapingEntities() который выполняет задание:

 let encoded = " 4 < 5 & 3 > 2 . Price: 12 €. @ " let decoded = CFXMLCreateStringByUnescapingEntities(nil, encoded, nil) as String println(decoded) //  4 < 5 & 3 > 2 . Price: 12 €. @ 

но это не доступно для iOS.

Вот чистая реализация Swift. Он декодирует ссылки на сущности символов, такие как < используя словарь, и все числовые символьные сущности, такие как @ или . (Обратите внимание, что я не перечислял все 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, eg // 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 = " 4 < 5 & 3 > 2 . Price: 12 €. @ " let decoded = encoded.stringByDecodingHTMLEntities print(decoded) //  4 < 5 & 3 > 2 . 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, eg // 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, eg // 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 } } 

Свифт 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 } } } 

Свифт 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

 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


  • Условное расширение строки
  • Без дополнительной защиты / 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 (т. должен быть неэкспонирован как знак евро (unicode U+20AC ), а НЕ как символ U+20AC для U+0080 , а некоторые диапазоны числовых символов должны быть заменены символом замены U+FFFD при снятии).

Пример использования:

 import HTMLEntities // encode example let html = "" print(html.htmlEscape()) // Prints ”<script>alert("abc")</script>" // decode example let htmlencoded = "<script>alert("abc")</script>" print(htmlencoded.htmlUnescape()) // Prints ”" 

И для примера OP:

 print("The Weeknd ‘King Of The Fall’ [Video Premiere] | @TheWeeknd | #SoPhi ".htmlUnescape()) // prints "The Weeknd 'King Of The Fall' [Video Premiere] | @TheWeeknd | #SoPhi " 

Изменить: 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 } } } 

Решение Elegant 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 это расширение строки, которое отлично и быстро работает для декодирования.

https://gist.github.com/mwaterfall/25b4a6a06dc3309d9555

Примечание: он не анализирует tags HTML.

Посмотрите на HTMLString – библиотеку, написанную в Swift, которая позволяет вашей программе добавлять и удалять объекты HTML в Строках

Для полноты я скопировал основные функции с сайта:

  • Добавляет объекты для кодировок ASCII и UTF-8 / UTF-16
  • Удаляет более 2100 названных объектов (например, &)
  • Поддерживает удаление десятичных и шестнадцатеричных объектов
  • Предназначен для поддержки скользящих расширенных кластеров Grapheme (→ 100% emoji-proof)
  • Полностью модульное тестирование
  • Быстро
  • документированный
  • Совместимость с 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 т. Д.) И новых символов строк, таких как \n

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

NSData dataRes = (значение nsdata)

var resString = NSString (данные: dataRes, encoding: NSUTF8StringEncoding)

  • ViewController.Type не имеет члена с именем
  • Расширять типы массивов, используя предложение where в Swift
  • SHA256 в быстрой
  • Как я могу использовать UserDefaults в Swift?
  • Менеджер CLLocation в Swift, чтобы получить местоположение пользователя
  • Кнопка обнаружения проблемы cellForRowAt
  • Как добавить ограничения программно с помощью Swift
  • Передача аргументов в селектор в Swift
  • Как сделать случайное число между диапазоном для arc4random_uniform ()?
  • Ошибка "Thread 1: точка останова 2.1"
  • как resize растрового изображения на iOS
  • Давайте будем гением компьютера.