Правильно ли читать текстовый файл utf-16 в строку без внешних библиотек?
Я использую StackOverflow с самого начала и время от времени испытываю искушение опубликовать вопросы, но я всегда либо выяснял их сам, либо находил ответы, опубликованные в конце концов... до сих пор. Это кажется довольно простым, но я часами бродил по интернету без успеха, поэтому я перехожу сюда:
У меня есть довольно стандартный текстовый файл utf-16, со смесью английских и китайских символов. Я бы хотел, чтобы эти персонажи оказались в string (технически, wstring). Я видел многосвязанных вопросов, на которые были даны ответы (здесь и в других местах), но они либо пытаются решить гораздо более сложную проблему чтения произвольных файлов, не зная кодировки, или преобразования между кодировками, или просто обычно путают "Unicode" с диапазоном кодировок. Я знаю источник текстового файла, который я пытаюсь прочитать, он всегда будет UTF16, у него есть BOM и все такое, и он может оставаться таким.
Я был используя решение, описанное здесь , которое работало для текстовых файлов, которые были полностью английскими, но после обнаружения определенных символов он перестал читать файл. Единственное другое предложение, которое я нашел, было использоватьICU , что, вероятно, сработает, но я бы предпочел не включать всю большую библиотеку в приложение для распространения, а просто читать один текстовый файл в одном месте. Впрочем, я не забочусь о независимости системы - она нужна мне только для компиляции и работы в Windows. Решение это не зависело от этого факта, конечно, было быкрасивее , но я был бы так же счастлив для решения, которое использовало stl, полагаясь на предположения об архитектуре Windows, или даже решения, которые включали функции win32 или ATL; я просто не хочу включать еще одну большую стороннюю библиотеку, такую как ICU. Неужели мне по-прежнему не везет, если я не хочу сам все переделать?
Edit: я застрял, используя VS2008 для этого конкретного проекта, поэтому код C++11, к сожалению, не будет помощь.
Edit 2: я понял, что код , который я заимствовал раньше, не потерпел неудачу на неанглийских символах, как я думал. Скорее всего, он не работает на определенных символах в моем тестовом документе, среди которых ':' (двоеточие полной ширины, U+FF1A) и ')' (правая скобка полной ширины, U+FF09). опубликованное решение bames53 также в основном работает, но ставится в тупик теми же персонажами?
Правка 3 (и ответ!): исходный код, который я использовал-в основном работал-как bames53 помогло мне обнаружить, что ifstream просто нужно было открыть в двоичном режиме, чтобы он работал.
3 ответа:
Когда вы открываете файл для UTF-16, вы должны открыть его в двоичном режиме. Это происходит потому, что в текстовом режиме некоторые символы интерпретируются специально - в частности, 0x0d отфильтровывается полностью, а 0x1a отмечает конец файла. Есть некоторые символы UTF-16, которые будут иметь один из этих байтов как половину символьного кода и испортят чтение файла. Это не ошибка, это преднамеренное поведение и является единственной причиной наличия отдельного текста и двоичного файла. режимы.
По причине того, что 0x1a считается концом файла, смотрите этот пост в блоге от Raymond Chen , отслеживающий историю Ctrl-Z. Это в основном обратная совместимость run amok.
Решение C++11 (поддерживаемое, на вашей платформе, Visual Studio с 2010 года, насколько мне известно), было бы:
#include <fstream> #include <iostream> #include <locale> #include <codecvt> int main() { // open as a byte stream std::wifstream fin("text.txt", std::ios::binary); // apply BOM-sensitive UTF-16 facet fin.imbue(std::locale(fin.getloc(), new std::codecvt_utf16<wchar_t, 0x10ffff, std::consume_header>)); // read for(wchar_t c; fin.get(c); ) std::cout << std::showbase << std::hex << c << '\n'; }
Правка:
Таким образом, похоже, что проблема заключалась в том, что Windows обрабатывает определенные последовательности магических байтов как конец файла в текстовом режиме. Это решается с помощью двоичного режима для чтения файла
std::ifstream fin("filename", std::ios::binary);
, а затем копирования данных в строку wstring, как вы уже это делаете.
Самым простым и непереносимым решением было бы просто скопировать данные файла в массив wchar_t. Это основано на том факте, что wchar_t в Windows составляет 2 байта и использует UTF-16 в качестве своего кодирование.
Вам будет немного трудно преобразовать UTF-16 в кодировку wchar_t, специфичную для локали, полностью переносимым способом.
Вот функциональность преобразования unicode, доступная в стандартной библиотеке C++ (хотя VS 10 и 11 реализуют только элементы 3, 4 и 5)
codecvt<char32_t,char,mbstate_t>
codecvt<char16_t,char,mbstate_t>
- codecvt_utf8
- codecvt_utf16
- codecvt_utf8_utf16
- c32rtomb/mbrtoc32
- c16rtomb/mbrtoc16
И что каждый из них делает
- фасет codecvt, который всегда преобразуется между UTF-8 и UTF-32
- преобразование между UTF-8 и UTF-16
- преобразует между UTF-8 и UCS-2 или UCS-4 в зависимости от размера целевого элемента (символы вне BMP, вероятно, усеченный)
- преобразует последовательность символов с использованием схемы кодирования UTF-16 и UCS-2 или UCS-4
- преобразование между UTF-8 и UTF-16
- Если макрос
__STDC_UTF_32__
определен, эти функции преобразуются между кодировкой char текущей локали и UTF-32- Если макрос
__STDC_UTF_16__
определен, эти функции преобразуются между кодировкой char текущей локали и UTF-16Если
К сожалению, нет ничего определенного, что идет непосредственно от UTF-16 к wchar_t. можно перейти к UTF-16 - > UCS-4 - > mb (if__STDC_ISO_10646__
определен, то преобразование непосредственно с помощьюcodecvt_utf16<wchar_t>
должно быть прекрасным, так как это макрос указывает, что значения wchar_t во всех локалях соответствуют коротким именам символов Юникода (и таким образом подразумевает, что wchar_t достаточно велик, чтобы содержать любое такое значение).__STDC_UTF_32__
) - > wc, но вы потеряете все, что не представляется в многобайтовой кодировке локали. И конечно, несмотря ни на что, преобразование из UTF-16 в wchar_t потеряет все не представляется в кодировке wchar_t локали.
Поэтому, вероятно, не стоит быть переносимым, и вместо этого вы можете просто прочитать данные в массив wchar_t или использовать какой-либо другой специфический для Windows механизм, такой как режим _O_U16TEXT для файлов.
Это должно строить и работать где угодно, но делает кучу предположений, чтобы на самом деле работать:
#include <fstream> #include <sstream> #include <iostream> int main () { std::stringstream ss; std::ifstream fin("filename"); ss << fin.rdbuf(); // dump file contents into a stringstream std::string const &s = ss.str(); if (s.size()%sizeof(wchar_t) != 0) { std::cerr << "file not the right size\n"; // must be even, two bytes per code unit return 1; } std::wstring ws; ws.resize(s.size()/sizeof(wchar_t)); std::memcpy(&ws[0],s.c_str(),s.size()); // copy data into wstring }
Вероятно, вам следует, по крайней мере, добавить код для обработки endianess и 'BOM'. Кроме того, новые строки Windows не преобразуются автоматически, поэтому вам нужно сделать это вручную.