NSString initWithData возвращает ноль - PullRequest
25 голосов
/ 15 августа 2010

Я извлекаю данные с веб-сайта через NSURLConnection и сохраняю полученные данные в экземпляре NSMutableData.В методе делегата connectionDidFinishLoading данные преобразуются в строку с вызовом соответствующего метода NSString:

NSString *result = [[NSString alloc] initWithData:data 
                                     encoding:NSUTF8StringEncoding]

Результирующая строка оказывается пустой.Однако, если я использую NSASCIIStringEncoding, я получаю соответствующую строку, хотя и с символами Юникода, искаженными, как и ожидалось.Заголовок Content-Type на сервере не определяет кодировку UTF-8, но я пробовал несколько разных сайтов с похожим сценарием, и там преобразование строк происходит просто отлично.Кажется, что проблема относится только к данному веб-сервису, но я понятия не имею, почему.

В дополнение к этому, извлекает веб-страницы и данные из передовой практики API, т.е.строка, и манипулирование строкой впоследствии?

Очень ценится!

Ответы [ 5 ]

27 голосов
/ 15 августа 2010

Вы говорите, что это «определенно UTF-8», но без заголовка Content-Type вы на самом деле этого не знаете.(И даже если у вас есть заголовок, говорящий об этом, он все равно может быть неправильным.)

Я предполагаю, что ваши данные обычно ASCII, который всегда правильно анализируется как UTF-8, но иногда вы пытаетесьанализировать данные, которые фактически закодированы в ISO 8859-1 или кодовой странице Windows 1252. Такие данные обычно будут в основном ASCII, но с некоторыми байтами за пределами диапазона 0–127, определяемого ASCII.UTF-8 ожидал бы, что такие байты будут формировать последовательность единиц кода в пределах определенной последовательности диапазонов, но в других кодировках любой байт, независимо от значения, сам по себе является полным символом.Попытка интерпретировать данные не-ASCII, не-UTF-8 как UTF-8 почти всегда даст вам либо неверные результаты (неправильные символы), либо вообще никаких результатов (не может декодировать; декодер возвращает nil), потому что данные никогда не кодировалисьв первую очередь в UTF-8.

Сначала вы должны попробовать UTF-8, а в случае неудачи использовать ISO 8859-1.Если вы позволяете пользователю получать любую веб-страницу, вы должны позволить ему изменить кодировку, которую вы используете для декодирования данных, на случай, если они обнаружат, что это на самом деле 8859-9 или codepage-1252 или какая-то другая 8-битная кодировка.

Если вы загружаете данные с определенного сервера, и особенно если у вас есть влияние на то, что выполняется на этом сервере, вы должны заставить его отображать точный заголовок Content-Type и / или исправлять любую ошибку, вызывающую ошибкуэто для обслуживания текста, которого нет в UTF-8.

8 голосов
/ 13 февраля 2011

Как сказал Питер, заголовок типа контента - это просто "подсказка" о том, каким будет ожидаемый контент. На стороне сервера вы можете установить любой тип контента и отправить любые байтовые последовательности, которые могут быть недопустимыми.

У меня была точно такая же проблема, связанная с неверными данными UTF-8, которая включала символы ISO-8859-1 (Latin-1) (французские акценты).

Википедия о UTF-8 стоит прочитать, чтобы понять эту проблему и как справиться с ошибками кодирования.

Дело в том, что NSString initWithData:encoding: строгая реализация просто возвращает ноль, когда происходит ошибка декодирования. (в отличие от java, например, который использует символ замены)

Решение Питера по преобразованию в основном данных UTF-8 в Latin-1 меня не удовлетворяло. (Все символы UTF-8 становятся неправильными, только для одного латинского 1 ошибочного символа)

Лучшим вариантом будет исправление на стороне сервера, конечно, но я не несу ответственности за эту сторону ...

Итак, я посмотрел глубже и нашел решение, используя библиотеку GNU libiconv C (доступна для OSX и iOS). Принцип заключается в использовании iconv для удаления недопустимых символов, не относящихся к UTF-8 (т. Е. «Prété» станет «prt»)

Вот пример кода, эквивалентный командной строке iconv -c -f UTF-8 -t UTF-8 invalid.txt > cleaned.txt

#include "iconv.h"

- (NSData *)cleanUTF8:(NSData *)data {
  iconv_t cd = iconv_open("UTF-8", "UTF-8"); // convert to UTF-8 from UTF-8
  int one = 1;
  iconvctl(cd, ICONV_SET_DISCARD_ILSEQ, &one); // discard invalid characters

  size_t inbytesleft, outbytesleft;
  inbytesleft = outbytesleft = data.length;
  char *inbuf  = (char *)data.bytes;
  char *outbuf = malloc(sizeof(char) * data.length);
  char *outptr = outbuf;
  if (iconv(cd, &inbuf, &inbytesleft, &outptr, &outbytesleft)
      == (size_t)-1) {
    NSLog(@"this should not happen, seriously");
    return nil;
  }
  NSData *result = [NSData dataWithBytes:outbuf length:data.length - outbytesleft];
  iconv_close(cd);
  free(outbuf);
  return result;
}

Затем полученный NSData можно безопасно декодировать, используя NSUTF8StringEncoding

Обратите внимание, что последние iconv также позволяют использовать запасные методы, используя:

iconvctl(cd, ICONV_SET_FALLBACKS, &fallbacks);

Используя запасной вариант при ошибках Юникода, вы можете использовать заменяющий символ или, что лучше, попробовать другую кодировку. В моем случае мне удалось откатиться до LATIN-1, где произошел сбой UTF-8, что привело к 99% положительным конверсиям. Посмотрите на исходный код iconv, чтобы понять его.

5 голосов
/ 15 августа 2010

Кодировка по умолчанию для HTTP, если ничего не указано, - ISO-8859-1. Если ответ HTTP совместим с HTTP / 1.1 и не указывает кодировку набора символов, это кодировка, которую он использует.

Попробуйте декодировать строку с этим NSISOLatin1StringEncoding.

3 голосов
/ 15 августа 2010

Данные могли быть в другой кодировке Unicode, такой как UTF16, или в некоторых совершенно других кодировках.

Существуют библиотеки, которые могут угадать кодировку, используемую в данных, но это должно быть последним средством. Если вы используете веб-сервис, этот веб-сервис должен иметь документацию, в которой говорится, какую кодировку он использует. Ищите его или спросите поставщика веб-службы, какую кодировку он использует. Если ни один из них недоступен, вам следует попытаться получить пример данных и определить кодировку для этого и использовать это в программе.

Кстати, извлекает ли веб-страницы и данные из передовой практики API, т. Е. Буферизует данные, преобразует их в строку и впоследствии манипулирует строкой?

Это зависит от размера данных. Если он маленький, это было бы прекрасно. Если он большой, лучше разобраться с частями данных.

0 голосов
/ 22 июня 2011

Минуточку, ОП читает из сети в первую очередь, верно?почему бы не использовать NSString stringWithContentsOfURL:usedEncoding:error: Возвращает строку, созданную путем чтения данных с заданного URL-адреса, и возвращает по ссылке кодировку, используемую для интерпретации данных.

+ (id)stringWithContentsOfURL:(NSURL *)url usedEncoding:(NSStringEncoding *)enc error:(NSError **)error

страниц n страниц, уменьшенных до одной строки хе ...если, конечно, я не ошибаюсь, конечно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...