Правильно ли читать текстовый файл 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 6

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)

  1. codecvt<char32_t,char,mbstate_t>
  2. codecvt<char16_t,char,mbstate_t>
  3. codecvt_utf8
  4. codecvt_utf16
  5. codecvt_utf8_utf16
  6. c32rtomb/mbrtoc32
  7. c16rtomb/mbrtoc16

И что каждый из них делает

  1. фасет codecvt, который всегда преобразуется между UTF-8 и UTF-32
  2. преобразование между UTF-8 и UTF-16
  3. преобразует между UTF-8 и UCS-2 или UCS-4 в зависимости от размера целевого элемента (символы вне BMP, вероятно, усеченный)
  4. преобразует последовательность символов с использованием схемы кодирования UTF-16 и UCS-2 или UCS-4
  5. преобразование между UTF-8 и UTF-16
  6. Если макрос __STDC_UTF_32__ определен, эти функции преобразуются между кодировкой char текущей локали и UTF-32
  7. Если макрос __STDC_UTF_16__ определен, эти функции преобразуются между кодировкой char текущей локали и UTF-16

Если __STDC_ISO_10646__ определен, то преобразование непосредственно с помощью codecvt_utf16<wchar_t> должно быть прекрасным, так как это макрос указывает, что значения wchar_t во всех локалях соответствуют коротким именам символов Юникода (и таким образом подразумевает, что wchar_t достаточно велик, чтобы содержать любое такое значение).

К сожалению, нет ничего определенного, что идет непосредственно от UTF-16 к wchar_t. можно перейти к UTF-16 - > UCS-4 - > mb (if __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 не преобразуются автоматически, поэтому вам нужно сделать это вручную.