Преобразование UTF-8 в UCS2 с использованием icu приводит к тарабарщине

Это дополнительный вопрос к предыдущему.

Проблема в этом вопросе была решена, и теперь код работает, как и ожидалось, однако окончательный результат преобразования utf-8 в ucs2 — тарабарщина. Под этим я подразумеваю, что шестнадцатеричные значения окончательного текста никоим образом не соответствуют версии utf-8. Я знаю, что это разные кодировки, но, похоже, между ними нет никакого сопоставления.

Вход в преобразование — «ĩ», выход — «ÿþ)^A». В шестнадцатеричном формате это c4a9 для "ĩ" (значение utf-8) и "00FF 00FE 0029 0001" для "ÿþ)^A" (значения ucs2).

Я надеюсь, что у кого-то есть объяснение этому поведению или он может сказать мне, что я сделал неправильно в коде.

Новый обновленный код:

UErrorCode resultCode = U_ZERO_ERROR;

UConverter* pLatinOneConv = ucnv_open("ISO-8859-1", &resultCode);

// Change the callback to error out instead of the default            
const void* oldContext;
UConverterFromUCallback oldFromAction;
UConverterToUCallback oldToAction;
ucnv_setFromUCallBack(pLatinOneConv, UCNV_FROU_CALLBACK_STOP, NULL, &oldFromAction, &oldContext, &resultCode);
ucnv_setToUCallBack(pLatinOneConv, UCNV_TO_U_CALLBACK_STOP, NULL, &oldToAction, &oldContext, &resultCode);

int32_t outputLength = 0;
int bodySize = uniString.length();
int targetSize = bodySize * 4;
char* target = new char[targetSize];                       

printf("Body: %s\n", uniString.c_str());
if (U_SUCCESS(resultCode))
{
    outputLength = ucnv_fromAlgorithmic(pLatinOneConv, UCNV_UTF8, target, targetSize, uniString.c_str(),
        uniString.length(), &resultCode);
    ucnv_close(pLatinOneConv);
}
printf("ISO-8859-1 just tried to convert '%s' to '%s' with error '%i' and length '%i'", uniString.c_str(), 
    outputLength ? target : "invalid_char", resultCode, outputLength);

if (resultCode == U_INVALID_CHAR_FOUND || resultCode == U_ILLEGAL_CHAR_FOUND || resultCode == U_TRUNCATED_CHAR_FOUND)
{
    if (resultCode == U_INVALID_CHAR_FOUND)
    {
        resultCode = U_ZERO_ERROR;
        printf("Unmapped input character, cannot be converted to Latin1");                    
        // segment Text, if necessary, and add UUIDs copy existing pPdu's addresses and optionals
        UConverter* pUscTwoConv = ucnv_open("UCS-2", &resultCode);
        if (U_SUCCESS(resultCode))
        {
            printf("Text Body: %s\n", uniString.c_str());
            outputLength = ucnv_fromAlgorithmic(pUscTwoConv, UCNV_UTF8, target, targetSize, uniString.c_str(),
                uniString.length(), &resultCode);
            ucnv_close(pUscTwoConv);
        }
        printf("UCS-2 just tried to convert '%s' to '%s' with error '%i' and length '%i'", uniString.c_str(), 
            outputLength ? target : "invalid_char", resultCode, outputLength);

        if (U_SUCCESS(resultCode))
        {
            pdus = SegmentText(target, pPdu, SEGMENT_SIZE_UNICODE_MAX, true);
        }
    }
    else
    {
        printf("DecodeText(): Text contents does not appear to be valid UTF-8");
    }
}
else
{
    printf("DecodeText(): Text successfully converted to Latin1");
    std::string newBody(target, outputLength);
    pdus = SegmentText(newBody, pPdu, SEGMENT_SIZE_MAX);
}

person MumblesCrzy    schedule 06.03.2014    source источник
comment
Что Latin-1 делает в вашем коде, если вы хотите преобразовать utf8 в ucs2 (что бы это ни значило)?   -  person n. 1.8e9-where's-my-share m.    schedule 06.03.2014
comment
В любом случае, U+fffe — это знак порядка байтов, а U+0129 — это i с тильдой, так что ваше преобразование хотя бы частично верно.   -  person n. 1.8e9-where's-my-share m.    schedule 06.03.2014
comment
@н.м. Latin1 там как чек. По сути, мы проверяем, можем ли мы сначала перекодировать в latin1, и если это не удается, и ошибка связана с недопустимым символом, мы вместо этого переводим его в ucs2. Ucs2 — это другое имя для utf-16, по крайней мере, что касается libicu.   -  person MumblesCrzy    schedule 06.03.2014
comment
Я немного посмотрел код. Непонятно, почему вы пытаетесь printf преобразовать строку в кодировке UTF-8 и строку в кодировке UTF-16 в один и тот же файл. Это не будет работать для многих допустимых строк. Как вы смотрите на свои шестнадцатеричные значения? Вы когда-нибудь нуждались в отделении интенсивной терапии? Это очень большая и сложная библиотека. Для простых задач больше подходит libiconv.   -  person n. 1.8e9-where's-my-share m.    schedule 06.03.2014
comment
Прямо сейчас все будет выведено на экран, и я получаю шестнадцатеричные значения, беря вывод и просматривая все вручную. Я использую fileformat.info в качестве ссылки. Что вы имеете в виду, говоря, что это не будет работать для допустимых строк? Я знаю, что с ним есть некоторые проблемы с выводом, но его можно декодировать. Мы используем icu в этом контексте в качестве теста. У нас есть возможность сделать много подобных преобразований в нашем программном обеспечении. Таким образом, libiconv может помочь нам пройти через это, но, скорее всего, не другие. Я также задаюсь вопросом, не является ли это проблемой прямо сейчас.   -  person MumblesCrzy    schedule 06.03.2014
comment
Это не сработает, потому что, например, оно будет содержать встроенные нулевые байты. Я не уверен, что могу сказать о копировании с экрана в информацию о файле и вашей интерпретации результатов. Просто все совершенно неправильно. Я постараюсь дать правильный ответ, когда у меня будет время. Это не тривиально, потому что речь идет не о коде, а о вашем понимании проблемы, и я не совсем уверен, с чего начать и как все это будет соответствовать формату QA этого сайта.   -  person n. 1.8e9-where's-my-share m.    schedule 06.03.2014


Ответы (1)


Преобразование ICU дает вам правильные результаты, но вы не совсем знаете, что с ними делать, и успешно конвертируете их в тарабарщину. Вот то, что вы делаете неправильно, более или менее по порядку.

Один

Вы печатаете данные, отличные от Latin-1, в системе, которая (как показывают имеющиеся данные) изначально работает на Latin-1.

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

UTF-16 (которая, кстати, заменила UCS-2 еще в 1996 году) не так хороша. Строка в кодировке UTF-16 содержит единицы кода длиной два байта. Любой из этих двух байтов вполне может быть равен нулю. (Все символы ASCII, закодированные как UTF-16, имеют нулевой байт). Пока другой байт не равен нулю, весь символ не равен NULL. Однако ваши printf, strlen и так далее не подозревают, это другой байт. Они думают, что вы кормите их латиницей-1, и они остановятся на первом нулевом байте (который они интерпретируют как символ NULL).

К счастью для вас, символ ĩ не имеет нулевого байта в кодировке UTF-16, так что на этот раз вам это сошло с рук.

Как это сделать правильно? Никогда не printf или fputs, а fwrite/std::ostream::write; никогда strcpy, всегда memcpy; никогда не strlen, но всегда держите длину под рукой в ​​отдельной переменной.

Два

Вы печатаете эти данные на экране.

Ваш экран может интерпретировать байты от (предположительно) от 0 до 31, и часто байты, которые следуют за ними, разными и интересными способами. Например, перемещение курсора, звуковой сигнал или изменение цвета текста. Вы печатаете данные UTF-16, которые могут иметь абсолютно любые байты в своей кодировке, даже если источник содержал совершенно обычные печатные символы Unicode. Так что может случиться все что угодно.

К счастью, единственный символ, который вы пытались преобразовать, не содержит вредоносных байтов в своем представлении UTF-16.

Как это сделать правильно? Если вам нужно напечатать что-то для быстрого просмотра, напечатайте шестнадцатеричные коды для всех или только непечатаемых символов.

 void print_bytes (FILE* fp, const unsigned char* s, int len,
                    bool escape_all) {
   // note: explicit length, *never* strlen!
   // note: unsigned char, you need it
   int i;
   for (i = 0; i < len; ++i, ++s)
   {
      if (escape_all || ! isprint(*s)) {
        fprintf ("\\x%02x", *s);
      } 
      else {
        fputc(*s, fp);
      }
   }
 }

Три

Вы ищете символы Latin-1, которые вы получили на своем экране, в файле info и, таким образом, интерпретируете их, как если бы они были символами Unicode, а затем берете их 16-битные коды символов (один 16-битный код на каждый символ) и интерпретируете их. как если бы они были байтами.

Не нужно много говорить об этом. Просто не делай этого. У вас есть функция, которая печатает байты в удобочитаемом шестнадцатеричном представлении. Используй это. В качестве альтернативы используйте любое количество свободно доступных программ, которые отображают или даже позволяют редактировать такое представление.

Это, конечно, не означает, что вы не должны использовать fileinfo. Делайте это правильно, что в основном означает знание вашей кодировки и того, как любая данная кодировка символа отличается (хотя иногда и похожа) на его кодовую точку Unicode.

Четыре

Этот абзац не об ошибках как таковых, а скорее об интуиции разработчика (или ее отсутствии), которая не соответствует ни одному из опубликованных вами кодов.

Несмотря на все вышеперечисленные ошибки, вам удалось получить почти хорошие данные. У вас есть 00 во всех четных местах, что может означать, что что-то не так с размером вашего целого числа в битах, и вам нужно избавиться от этих нулей. После этого у вас останется FFFE в качестве первых двух байтов, которые вы должны были распознать как спецификацию. Вы подозреваете, что у вас есть проблема с порядком байтов, но вы не пытались решить ее, изменив вариант UTF-16 (UTF-16LE против UTF-16BE).

Это то, что любой разработчик Unicode должен уметь применять почти инстинктивно.


Юникод большой и сложный, намного сложнее, чем думает большинство людей. Это только самое начало самого начала.


Предложите улучшения для этого ответа.

person n. 1.8e9-where's-my-share m.    schedule 06.03.2014
comment
Я унаследовал этот код. Поэтому я не удивлен, что на данный момент у меня огромные пробелы в информации. Спасибо за ваш подробный ответ. Многое нужно просеять и понять. Я собираюсь немного переварить это и опубликовать любые последующие изменения, которые я внесу, предполагая, что я заставлю его работать по мере необходимости. - person MumblesCrzy; 07.03.2014