Ищу istreambuf_iterator & lt; wchar_t & gt; разъяснения, чтение полного текстового файла символов Unicode

В книге «Эффективный STL» Скотта Мейерса есть хороший пример чтения всего текстового файла в объект std :: string:

std::string sData;

/*** Open the file for reading, binary mode ***/
std::ifstream ifFile (“MyFile.txt”, std::ios_base::binary); // Open for input, binary mode

/*** Read in all the data from the file into one string object ***/
sData.assign (std::istreambuf_iterator <char> (ifFile),
std::istreambuf_iterator <char> ());

Обратите внимание, что он читает его как 8-байтовые символы. Это работает очень хорошо. Хотя недавно мне нужно было прочитать файл, содержащий текст в кодировке Unicode (то есть два байта на символ). Однако, когда я пытаюсь (наивно) изменить его, чтобы прочитать данные из текстового файла Unicode в объект std :: wstring, вот так:

std::wstring wsData;

/*** Open the file for reading, binary mode ***/
std::wifstream ifFile (“MyFile.txt”, std::ios_base::binary); // Open for input, binary mode

/*** Read in all the data from the file into one string object ***/
wsData.assign (std::istreambuf_iterator <wchar_t> (ifFile),
std::istreambuf_iterator <wchar_t> ());

Строка, которую я получаю, хотя и имеет широкие символы, все еще имеет альтернативные нули. Например, если файл содержит строку Unicode «ABC», байты файла (без учета начальных байтов Unicode 0xFF, 0xFE):
<«A»> <0> <«B»> <0> <«C»> <0>

Первый фрагмент кода, приведенный выше, будет корректно приводить к следующему содержимому строки (char):
sData [0] = ‘A’
sData [1] = 0x00
sData [2] = ‘B’
sData [3] = 0x00
sData [4] = ‘C’
sData [5] = 0x00

Однако, когда запускается второй фрагмент кода, это нежелательно приводит к следующему содержанию строки (wchar_t):
wsData [0] = L‘A ’
wsData [1] = 0x0000
wsData [2] = L‘B ’
wsData [3] = 0x0000
wsData [4] = L‘C ’
wsData [5] = 0x0000

Как будто файл все еще читается побайтово, а затем просто переводится в отдельные символы wchar_t.

Я бы подумал, что std :: istreambuf_iterator, специализирующийся на wchar_t, должен был приводить к чтению файла по два байта за раз, не так ли? Если нет, тогда какова его цель?

Я проследил в шаблонах (нет простого навыка ;-), и итератор действительно все еще читает файл побайтно и передает его в свою внутреннюю процедуру преобразования, которая должным образом заявляет, что преобразование выполняется после каждого байта (не только после получения 2 байтов).

Я искал несколько сайтов в Интернете (включая этот) для этой, казалось бы, тривиальной задачи, но не нашел объяснения этому поведению или хорошей альтернативы, которая не включает в себя больше кода, чем я считаю нужным (например, Google поиск в Интернете производит тот же второй фрагмент кода, что и жизнеспособный фрагмент кода).

Единственное, что я обнаружил, что это работает, — это следующее, и я считаю, что это обман, поскольку он требует прямого доступа к внутреннему буферу wstring, а затем принудительно вводит его при этом.

std::wstring wsData;

/*** Open the file for reading, binary mode ***/
std::wifstream ifFile (“MyFile.txt”, std::ios_base::binary); // Open for input, binary mode

wsData.resize (<Size of file in bytes> / sizeof (wchar_t));

ifFile.read ((char *) &wsData [0], <Size of file in bytes>);

О, и чтобы предупредить неизбежный вопрос «Зачем открывать файл в двоичном режиме, а не в текстовом режиме», это открытие является преднамеренным, как если бы файл был открыт в текстовом режиме (по умолчанию), это означает, что CR / LF («\ Последовательности r \ n «или 0x0D0A) будут преобразованы в последовательности LF (» \ n «или 0x0A), тогда как чтение файла в чистом байте сохранит их. Несмотря на это, для тех несгибаемых, изменение, которое, что неудивительно, не имело никакого эффекта.

Итак, два вопроса: почему второй случай не работает так, как можно было ожидать (то есть, что происходит с этими итераторами), и какой ваш любимый «кошерный STL-способ» загрузки файла символов Unicode в строку wstring? ?

Что мне здесь не хватает; это должно быть что-то глупое.

Крис

11

Решение

Вы должны быть разочарованы SO, чтобы не получить ответы на свой первый вопрос после
4-и с половиной месяцев. Это хороший вопрос, и ответы на большинство хороших вопросов
(хорошо или плохо) в течение нескольких минут. Две вероятные причины пренебрежения вашей:

  • Вы не пометили его как «C ++», поэтому многие программисты на C ++, которые могли бы помочь, никогда не получат
    заметил это. (Я теперь отметил это «C ++».)

  • Ваш вопрос связан с обработкой потока Unicode, которая не является идеей классного кодирования.

Заблуждение, которое помешало вашим расследованиям, выглядит следующим образом:
считаю, что поток широких символов, std::wfstreamи строка широких символов, std::wstring,
соответственно такие же, как «поток Unicode» и «строка Unicode», и, в частности, что
они соответственно такие же, как поток UTF-16 и строка UTF-16. Ни одна из этих вещей не является правдой.

std::wifstream (std::basic_ifstream<wchar_t>) является входным потоком, который преобразует
внешняя последовательность байтов для внутренней последовательности wchar_t, в соответствии с указанным
или кодировка внешней последовательности по умолчанию
.

Аналогично std::wofstream (std::basic_ofstream<wchar_t>) является выходным потоком, который
преобразует внутреннюю последовательность wchar_t на внешнюю последовательность байтов, в соответствии с
заданная или заданная по умолчанию кодировка внешней последовательности
.

И std::wstring (std::basic_string<wchar_t>) это строковый тип, который просто хранит
последовательность wchar_tбез знания кодировки, если таковой имеется, из которой они получены.

Unicode это семейство кодировок байтовых последовательностей — UTF-8 / -16 / -32 и некоторые другие неясные —
связано с тем, что UTF-N кодирует алфавиты, используя последовательность из 1 или более
N-битовые единицы на символ. UTF-16 — это кодировка, которую вы пытаетесь прочитать.
в std::wstring, Ты говоришь:

Я бы подумал, что std :: istreambuf_iterator, специализирующийся на wchar_t, должен был приводить к чтению файла по два байта за раз, не так ли? Если нет, то какова его цель тогда?

Но как только ты это знаешь wchar_t не обязательно 2 байта в ширину (это в библиотеках C Microsoft,
32- и 64-разрядные, но в GCC он имеет ширину 4 байта), а также кодовую точку UTF-16 (символ)
не нужно вписываться в 2 байта (это может потребовать 4), вы увидите, что это указывает на извлечение
единица измерения wchar_t не может быть все, что нужно для декодирования потока UTF-16.

Когда вы создаете и открываете входной поток с помощью:

std::wifstream ifFile ("MyFile.txt", std::ios_base::binary);

Он подготовлен для извлечения символов (некоторого алфавита) из «MyFile.txt» в значения
типа wchar_t и он извлечет эти символы из последовательности байтов в
файл в соответствии с кодировкой, указанной std::locale
это работает в потоке, когда это делает извлечение.

Ваш код не указывает std::locale для вашего потока, поэтому библиотека по умолчанию вступает в силу.
По умолчанию используется глобальная локаль C ++, которая по умолчанию является
«C» локаль; и «C» локаль предполагает
«кодирование идентичности» последовательностей байтов ввода / вывода, т.е. 1 байт = 1 символ (
за исключением исключения новой строки для ввода-вывода в текстовом режиме).

Таким образом, когда вы используете свой std::istreambuf_iterator<wchar_t> в
извлекать символы, извлечение продолжается путем преобразования каждого байта
в файле к wchar_t который он добавляет к std::wstring wsData, Байты
в файле, как вы говорите:

0xFF, 0xFE, «A», 0x00, «B», 0x00, «C», 0x00

Первые два, которые вы игнорируете как «ведущие байты Юникода», действительно
UTF-16 метка порядка байтов (BOM), но в кодировке по умолчанию они просто такие, какие они есть.

Соответственно широкие символы, назначенные wsData как вы заметили:

0x00FF, 0x00FE, L’A ‘, 0x0000, L’B’, 0x0000, L’C ‘, 0x0000

Это как если бы файл все еще читался побайтово, а затем просто переводился в отдельные символы wchar_t.

потому что это именно то, что происходит.

Чтобы это не произошло, вам нужно что-то сделать, прежде чем вы начнете извлекать символы из потока.
сказать ему, что он должен декодировать последовательность символов UTF-16. Способ сделать это
концептуально довольно извилистый. Вам нужно imbue
поток с std::locale который обладает
std::locale::facet это экземпляр
std::codecvt<InternT, ExternT, StateT> (или происходит от такого)
который предоставит потоку правильные методы из декодирования UTF-16 в wchar_t,

Но суть в том, что вам нужно подключить правильный кодер / декодер UTF-16 к потоку и
на практике это (или должно быть) достаточно просто. Я предполагаю, что ваш компилятор является недавним MS VC ++.
Если это так, то вы можете исправить свой код:

  • Добавление #include <locale> а также #include <codecvt> к вашим заголовкам
  • Добавление строки:

    ifFile.imbue(std::locale(ifFile.getloc(),new std::codecvt_utf16<wchar_t,0x10ffff,std::little_endian>));

сразу после:

std::wifstream ifFile ("MyFile.txt", std::ios_base::binary);

Эффект этой новой линии заключается в том, чтобы «наполнить» ifFile с новым языком, который такой же
как тот, который он уже имел — ifFile.getloc() — но с измененным аспектом кодера / декодера
std::codecvt_utf16<wchar_t,0x10ffff,std::little_endian>, это codecvt аспект
тот, который будет декодировать символы UTF-16 с максимальным значением 0x10ffff в порядке байтов
wchar_t ценности (0x10ffff максимальное значение кодовых точек UTF-16).

Когда вы выполните отладку в коде, в который внесены поправки, вы обнаружите wsData только 4 широких символа
и что эти персонажи:

0xFEFF, L'A', L'B', L'C'

как и следовало ожидать, первым из которых является спецификация UTF-16 с прямым порядком байтов.

Обратите внимание, что порядок FE,FF обратное тому, что было до применения
из codecvt фасет, показывающий, что декодирование с прямым порядком байтов выполнено в соответствии с запросом.
И это должно было быть. Просто отредактируйте новую строку, удалив std::little_endian,
отладить его снова, и вы обнаружите, что первый элемент wsData становится 0xFFFE
и что три других широких символа становятся пиктограммами
IICore пиктографический
набор символов (если ваш отладчик может их отобразить). (Теперь, когда коллега
с изумлением жалуется, что их код превращает английский Unicode в «китайский»,
вы будете знать вероятное объяснение.)

Если вы хотите заселить wsData без ведущей спецификации вы можете сделать это
внесение изменений в новую строку и замена std::little_endian с
std::codecvt_mode(std::little_endian|std::consume_header)

Наконец, вы, возможно, хорошо заметили ошибку в новом коде, а именно, что 2-байтовый wchar_t
недостаточно широк для представления кодовых точек UTF-16 между 0x100000 и 0x10ffff
это можно прочитать.

Вам это сойдет с рук до тех пор, пока все кодовые точки, которые вы должны прочитать, лежат в
UTF-16 Базовая многоязычная плоскость,
который охватывает [0,0xffff], и вы можете знать, что все входные данные будут всегда подчиняться
ограничение. В противном случае 16-битный wchar_t не подходит для цели. Заменить:

  • wchar_t с char32_t
  • std::wstring с std::basic_string<char32_t>
  • std::wifstream с std::basic_ifstream<char32_t>

и код полностью подходит для считывания произвольного файла в кодировке UTF-16 в строку.

(Читатели, которые работают с библиотекой GNU C ++, обнаружат, что начиная с v4.7.2
это еще не обеспечивает <codecvt> стандартный заголовок. Заголовок <bits/codecvt.h> существует и, предположительно, когда-нибудь станет <codecvt>, но на данный момент это только
экспортирует специализации class codecvt<char, char, mbstate_t> а также
class codecvt<wchar_t, char, mbstate_t>, которые соответственно идентичности
преобразование и преобразование между ASCII / UTF-8 и wchar_t, Чтобы решить проблему ОП
вам нужно подкласс std::codecvt<wchar_t,char,std::char_traits<wchar_t>::state_type>
себя согласно этот ответ)

11

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

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

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