Преобразование 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);
}

0

Решение

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

Один

Вы печатаете данные, не относящиеся к латинице-1, в системе, которая (как свидетельствуют имеющиеся данные) изначально работает на латинице-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, которые могут иметь абсолютно любые байты в своей кодировке, даже если источник содержит совершенно обычные печатаемые символы Юникода. Так что может случиться что угодно.

К счастью, один символ, который вы пытались преобразовать, не содержит вредных байтов в своем представлении 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);
}
}
}

Три

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

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

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

четыре

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

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

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


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


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

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]