Limpieza de cadenas UTF8 con formatting incorrecto

Fondo

Con Swift, estoy tratando de search HTML a través de URLSession lugar de cargarlo primero en un WKWebView ya que solo necesito el HTML y ninguno de los subresources. Me encuentro con un problema con ciertas páginas que funcionan cuando se cargan en WKWebView pero cuando se cargan mediante URLSession (o incluso una simple NSString(contentsOf: url, encoding String.Encoding.utf8.rawValue) ) falla la conversión UTF-8.

Como reproducir

Esto falla (imprime "nil"):

print(try? NSString(contentsOf: URL(string: "http://www.huffingtonpost.jp/techcrunch-japan/amazon-is-gobbling-whole-foods-for-a-reported-13-7-billion_b_17171132.html?utm_hp_ref=japan&ir=Japan")!, encoding: String.Encoding.utf8.rawValue))

Pero cambiando la URL a la página de inicio del sitio, tiene éxito:

print(try? NSString(contentsOf: URL(string: "http://www.huffingtonpost.jp")!, encoding: String.Encoding.utf8.rawValue))

Pregunta

¿Cómo puedo "limpiar" los datos devueltos por una URL que contiene UTF-8 con formatting incorrecto? Me gustaría eliminar o replace cualquier secuencia inválida en el UTF-8 con formatting incorrecto para que el rest pueda verse. WKWebView puede hacer que la página esté bien (y afirma que es también contenido UTF-8), como puede ver visitando la URL: http://www.huffingtonpost.jp/techcrunch-japan/amazon-is-gobbling- whole-foods-for-a-reported-13-7-billion_b_17171132.html? utm_hp_ref = japan & ir = Japón

Aquí hay un enfoque para crear una String de (posiblemente) datos UTF-8 malformados:

  • Lea los contenidos del website en un object de Data .
  • Añada un byte de 0 para convertirlo en una "cadena de C"
  • Use String(cString:) para la conversión. Este inicializador reemplaza las secuencias de unidades de código UTF-8 mal formadas con el carácter de reemploop de Unicode ( "\u{FFFD}" ).
  • Opcionalmente: elimine todas las apariciones del personaje de reemploop.

Ejemplo para el process de "limpieza":

 var data = Data(bytes: [65, 66, 200, 67]) // malformed UTF-8 data.append(0) let s = data.withUnsafeBytes { (p: UnsafePointer<CChar>) in String(cString: p) } let clean = s.replacingOccurrences(of: "\u{FFFD}", with: "") print(clean) // ABC 

Por supuesto, esto se puede definir como un método init personalizado:

 extension String { init(malformedUTF8 data: Data) { var data = data data.append(0) self = data.withUnsafeBytes { (p: UnsafePointer<CChar>) in String(cString: p).replacingOccurrences(of: "\u{FFFD}", with: "") } } } 

Uso:

 let data = Data(bytes: [65, 66, 200, 67]) let s = String(malformedUTF8: data) print(s) // ABC 

La limpieza se puede hacer más "directamente" usando transcode con

 extension String { init(malformedUTF8 data: Data) { var utf16units = [UInt16]() utf16units.reserveCapacity(data.count) // A rough estimate _ = transcode(data.makeIterator(), from: UTF8.self, to: UTF16.self, stoppingOnError: false) { code in if code != 0xFFFD { utf16units.append(code) } } self = String(utf16CodeUnits: utf16units, count: utf16units.count) } } 

Esto es esencialmente lo que hace String(cString:) , compara CString.swift y StringBuffer.swift .

Otra opción más es usar el códecs UTF8 decode() e ignorar los errores:

 extension String { init(malformedUTF8 data: Data) { var str = "" var iterator = data.makeIterator() var utf8codec = UTF8() var done = false while !done { switch utf8codec.decode(&iterator) { case .emptyInput: done = true case let .scalarValue(val): str.unicodeScalars.append(val) case .error: break // ignore errors } } self = str } }