C ++ UTF-16 для преобразования символов (Linux / Ubuntu)

Я пытаюсь помочь другу с проектом, который должен был быть 1-м, а теперь уже 3 дня. Само собой разумеется, я чувствую себя очень расстроенным и сердитым 😉 ooooouuuu … Я дышу.

Таким образом, программа, написанная на C ++, просто читает кучу файла и обрабатывает их. Проблема в том, что моя программа читает файлы, которые используют кодировку UTF-16 (потому что файлы содержат слова, написанные на разных языках), и простое использование ifstream просто не работает (оно читает и выводит мусор). Мне потребовалось некоторое время, чтобы понять, что это связано с тем, что файлы были в UTF-16.

Теперь я провел буквально весь день в Интернете, пытаясь найти информацию о READING UTF16 файлах и преобразовании содержимого строки UTF16 в char! Я просто не могу показаться! Это кошмар. Я пытаюсь узнать о и , wstring и т. Д., я никогда раньше не использовал (я специализируюсь на графических приложениях, а не на настольных приложениях). Я просто не могу понять.

Это то, что я сделал так (но не работает):

 std::wifstream file2(fileFullPath); std::locale loc (std::locale(), new std::codecvt_utf16); std::cout.imbue(loc); while (!file2.eof()) { std::wstring line; std::getline(file2, line); std::wcout << line << std::endl; } 

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

ТАК ПОЖАЛУЙСТА, ПОЖАЛУЙСТА, ПОМОГИТЕ! Это действительно сходит с ума, что я даже могу читать текстовый файл G *** D ***.

Наверху мой друг использует Ubuntu (я использую clang ++), и этот код требует -stdlib = libc ++, который, похоже, не поддерживается gcc на его стороне (хотя он использует довольно продвинутую версию gcc, которая равна 4.6.3 я верю). Поэтому я даже не уверен, что использование codecvt и locale – хорошая идея (как в «возможно»). Был бы лучший (другой) вариант.

Если я конвертирую все файлы в utf-8 только из командной строки (используя команду linux), я собираюсь потенциально потерять информацию?

Большое спасибо, я буду благодарен вам, если вы поможете мне в этом.

Если я конвертирую все файлы в utf-8 только из командной строки (используя команду linux), я собираюсь потенциально потерять информацию?

Нет, все данные UTF-16 могут быть без потерь преобразованы в UTF-8. Это, наверное, лучше всего.


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

 std::wofstream wout("output.txt"); wout << L"Hello"; // the output file will just be ASCII (assuming the platform uses ASCII). std::wifstream win("ascii.txt"); std::wstring s; wout >> s; // the ascii in the file is converted to wide characters. 

Конечно, фактическое кодирование зависит от codecvt в codecvt локали streamа, но то, что делает stream, использует codecvt для преобразования из wchar_t в char используя этот грань при записи, и конвертирует из char в wchar_t при чтении.


Однако, поскольку некоторые люди начали писать файлы в UTF-16, другим людям приходилось иметь дело с этим. То, как они делают это с streamами C ++, – это создание codecvt которые будут обрабатывать char как часть кода UTF-16, что и делает codecvt_utf16 .

Итак, с этим объяснением, вот проблемы с вашим кодом:

 std::wifstream file2(fileFullPath); // UTF-16 has to be read in binary mode std::locale loc (std::locale(), new std::codecvt_utf16); // do you really want char32_t data? or do you want wchar_t? std::cout.imbue(loc); // You're not even using cout, so why are you imbuing it? // You need to imbue file2 here, not cout. while (!file2.eof()) { // Aside from your UTF-16 question, this isn't the usual way to write a getline loop, and it doesn't behave quite correctly std::wstring line; std::getline(file2, line); std::wcout << line << std::endl; // wcout is not imbued with a locale that will correctly display the original UTF-16 data } 

Вот один из способов переписать выше:

 // when reading UTF-16 you must use binary mode std::wifstream file2(fileFullPath, std::ios::binary); // ensure that wchar_t is large enough for UCS-4/UTF-32 (It is on Linux) static_assert(WCHAR_MAX >= 0x10FFFF, "wchar_t not large enough"); // imbue file2 so that it will convert a UTF-16 file into wchar_t data. // If the UTF-16 files are generated on Windows then you probably want to // consume the BOM Windows uses std::locale loc( std::locale(), new std::codecvt_utf16); file2.imbue(loc); // imbue wcout so that wchar_t data printed will be converted to the system's // encoding (which is probably UTF-8). std::wcout.imbue(std::locale("")); // Note that the above is doing something that one should not do, strictly // speaking. The wchar_t data is in the wide encoding used by `codecvt_utf16`, // UCS-4/UTF-32. This is not necessarily compatible with the wchar_t encoding // used in other locales such as std::locale(""). Fortunately locales that use // UTF-8 as the narrow encoding will generally also use UTF-32 as the wide // encoding, coincidentally making this code work std::wstring line; while (std::getline(file2, line)) { std::wcout << line << std::endl; } 

Я адаптировал, исправил и протестировал впечатляющее решение Матс Петерсон.

 int utf16_to_utf32(std::vector &coded) { int t = coded[0]; if (t & 0xFC00 != 0xD800) { return t; } int charcode = (coded[1] & 0x3FF); // | ((t & 0x3FF) << 10); charcode += 0x10000; return charcode; } #ifdef __cplusplus // If used by C++ code, extern "C" { // we need to export the C interface #endif void convert_utf16_to_utf32(UTF16 *input, size_t input_size, UTF32 *output) { const UTF16 * const end = input + 1 * input_size; while (input < end){ const UTF16 uc = *input++; std::vector vec; // endianess vec.push_back(U16_LEAD(uc) & oxFF); printf("LEAD + %.4x\n",U16_LEAD(uc) & 0x00FF); vec.push_back(U16_TRAIL(uc) & oxFF); printf("TRAIL + %.4x\n",U16_TRAIL(uc) & 0x00FF); *output++ = utf16_to_utf32(vec); } } #ifdef __cplusplus } #endif 

UTF-8 способен отображать все допустимые символы Юникода (кодовые точки), что лучше, чем UTF-16 (который охватывает первые 1,1 миллиона кодовых точек). [Хотя, как поясняет комментарий, нет допустимых кодовых кодов Unicode, которые превышают значение в 1,1 миллиона, поэтому UTF-16 является «безопасным» для всех доступных в настоящее время кодовых пунктов – и, вероятно, в течение длительного времени, если только мы получаем дополнительных наземных посетителей, которые имеют очень сложный язык для написания …]

Он делает это при необходимости, используя несколько байтов / слов для хранения одной кодовой точки (что мы будем называть символом). В UTF-8 это обозначается максимальным битом, установленным – в первом байте «многобайтового» символа, устанавливаются верхние два бита, а в следующем байте (-ях) устанавливается верхний бит, а следующий от вершины равна нулю.

Чтобы преобразовать произвольную кодовую точку в UTF-8, вы можете использовать код в предыдущем ответе от меня. (Да, этот вопрос говорит об обратном то, о чем вы просите, но код в моем ответе охватывает оба направления конверсии)

Преобразование из UTF16 в «integer» будет аналогичным методом, за исключением длины ввода. Если вам повезет, вы, возможно, даже избегаете этого, не делая этого …

UTF16 использует диапазон D800-DBFF в качестве первой части, которая содержит 10 бит данных, а затем следующий элемент DC00-DFFF, содержащий следующие 10 бит данных.

Код для 16-бит для последующего …

Код для 16-битного до 32-битного преобразования (я только немного проверил это, но он работает нормально):

 std::vector utf32_to_utf16(int charcode) { std::vector r; if (charcode < 0x10000) { if (charcode & 0xFC00 == 0xD800) { std::cerr << "Error bad character code" << std::endl; exit(1); } r.push_back(charcode); return r; } charcode -= 0x10000; if (charcode > 0xFFFFF) { std::cerr << "Error bad character code" << std::endl; exit(1); } int coded = 0xD800 | ((charcode >> 10) & 0x3FF); r.push_back(coded); coded = 0xDC00 | (charcode & 0x3FF); r.push_back(coded); return r; } int utf16_to_utf32(std::vector &coded) { int t = coded[0]; if (t & 0xFC00 != 0xD800) { return t; } int charcode = (coded[1] & 0x3FF) | ((t & 0x3FF) << 10); charcode += 0x10000; return charcode; }