Угадайте кодировку при создании NSString из NSData

При чтении NSString из файла я могу использовать initWithContentsOfFile:usedEncoding:error:, и он угадает кодировку файла.

Когда я создаю его из NSData, хотя мой единственный вариант - initWithData:encoding:, где я должен явно передавать кодировку. Как я могу надежно угадать кодировку, когда я работаю с NSData вместо файлов?

Ответы

Ответ 1

В общем, вы не можете. Однако вы можете достаточно надежно идентифицировать файлы UTF-8 - если файл действителен UTF-8, его не очень вероятно, что он должен быть любым другим кодированием (кроме случаев, когда все байты находятся в диапазоне ASCII, и в этом случае любой "расширенная кодировка ASCII, включая UTF-8, даст вам тот же результат). Все кодировки Unicode также имеют необязательную спецификацию, которая идентифицирует их. Таким образом, разумный подход:

  • Найдите действительную спецификацию. Если он есть, используйте соответствующую кодировку.
  • В противном случае попробуйте интерпретировать его как UTF-8. Вы можете сделать это, вызвав initWithData:data encoding:NSUTF8StringEncoding и проверить, не является ли результат не нулем.
  • Если это не удается, используйте стандартную 8-битную кодировку, такую ​​как -[NSString defaultCStringEncoding] (которая обеспечивает уместность, соответствующую языку).

Можно попытаться улучшить предположение на последнем шаге, попробовав различные кодировки и выбрав тот, у которого меньше всего букв с нежелательным посередине, где "мусор" - это любой символ, который не является буквой, пробелом или общий знак препинания. Это значительно увеличит сложность, хотя на самом деле не будет надежным.

Короче говоря, чтобы иметь возможность обрабатывать все доступные кодировки, вам нужно сделать то, что делает TextEdit: передать решение пользователю.

О, еще одна вещь: с 10.5 кодировка часто хранится с файлом в недокументированном расширенном атрибуте com.apple.TextEncoding. Если вы откроете файл с помощью +[NSString stringWithContentsOfFile:] или аналогичного, это будет автоматически использоваться, если оно есть.

Ответ 2

В iOS 8 и OS X 10.10 появился новый API на NSString:

Objective-C

+ (NSStringEncoding)stringEncodingForData:(NSData *)data
                          encodingOptions:(NSDictionary *)opts
                          convertedString:(NSString **)string
                      usedLossyConversion:(BOOL *)usedLossyConversion;

Swift

open class func stringEncoding(for data: Data,
                   encodingOptions opts: [StringEncodingDetectionOptionsKey : Any]? = nil, 
                 convertedString string: AutoreleasingUnsafeMutablePointer<NSString?>?, 
                    usedLossyConversion: UnsafeMutablePointer<ObjCBool>?) -> UInt

Теперь вы можете позволить фреймворку сделать предположение и в моем опыте, который работает очень хорошо!

Из заголовка (в документации не указан метод в настоящий момент, но он официально упоминается в WWDC Session 204 (страница 270):

  • массив предложенных строковых кодировок (без указания 3-го параметра в этом списке, все строковые кодировки рассматриваются, но те, которые в массиве будут иметь более высокое предпочтение, более того, порядок кодировок в массиве важен: первое кодирование имеет более высокое предпочтение, чем второе в массиве).
  • массив строковых кодировок не использовать (строковые кодировки в этом списке вообще не рассматриваются)
  • логическая опция, указывающая, считаются ли только предлагаемые строковые кодировки
  • логическая опция, указывающая, разрешена ли потеря.
  • параметр, который дает определенную строку подстановке для тайных байтов
  • текущий пользовательский язык
  • логическая опция, указывающая, генерируются ли данные Windows

Если значения в словаре имеют неправильные типы (например, значение NSStringEncodingDetectionSposedEncodingsKey не является массивом), генерируется исключение.

Если значения в словаре неизвестны (например, значение в массиве предложенных строковых кодировок не является допустимой кодировкой), значения будут проигнорированы.

Пример (Swift):

var convertedString: NSString?
let encoding = NSString.stringEncoding(for: data, encodingOptions: nil, convertedString: &convertedString, usedLossyConversion: nil)

Если вы просто хотите декодированную строку и не заботитесь о кодировке, вы можете удалить let encoding =