Как читать файл utf-16 в строку utf-8 std::string строка за строкой
Я работаю с кодом, который ожидает utf8-кодированные переменные std::string. Я хочу иметь возможность обрабатывать предоставленный пользователем файл, который потенциально имеет кодировку utf-16 (я не знаю кодировку во время разработки, но в конечном итоге хочу иметь возможность иметь дело с utf8/16/32), прочитайте его строку -by-line и пересылают каждую строку остальной части кода как std::string в кодировке utf8.
У меня есть C++11 (действительно, текущее подмножество MSVC в C++11) и буст 1.55.0 для работы. Мне понадобится код, чтобы работать как на Linux, так и на Windows. Сейчас я просто создаю прототипы в Windows с помощью Visual Studio 2013 Update 4, работающей в Windows 7. Я открыт для дополнительных зависимостей, но им нужно иметь установленный кроссплатформенный (то есть windows и *nix) трек запись, и не должно быть GPL/LGPL.
Я делал предположения, что я не могу найти способ проверки, и у меня есть код, который не работает.
Одно из предположений состоит в том, что, поскольку я в конечном итоге хочу, чтобы каждая строка из этих файлов была в переменной std::string, я должен работать с std::ifstream, пропитанным правильно сконструированным codecvt, чтобы входящий поток utf16 можно было преобразовать в utf8.
Это предположение реалистично? Я подумал, что альтернативой будет то, что мне придется выполнить некоторые проверки кодировки в текстовом файле, а затем выбрать wifstream/wstring или ifstream/string на основе результатов, что кажется более непривлекательным, чем я хотел бы начать с, Конечно, если это правильный (или единственный реалистичный) путь, я открыт для него.
Я понимаю, что мне, вероятно, в любом случае, вероятно, понадобится выполнить какое-то обнаружение кодирования, но сейчас я не так обеспокоен частью обнаружения кодирования, просто сосредоточив внимание на получении содержимого файла utf16 в utf8 std::string.
Я пробовал множество различных комбинаций локали и codecvt, ни одна из которых не работала. Ниже приведено последнее воплощение того, что, как я думал, может сработать, но не:
void
SomeRandomClass::readUtf16LeFile( const std::string& theFileName )
{
boost::locale::generator gen;
std::ifstream file( theFileName );
auto utf8Locale = gen.generate( "UTF-8" );
std::locale cvtLocale( utf8Locale,
new std::codecvt_utf8_utf16<char>() );
file.imbue( utf8Locale );
std::string line;
std::cout.imbue( utf8Locale );
for ( int i = 0; i < 3; i++ )
{
std::getline( file, line );
std::cout << line << std::endl;
}
}
Поведение, которое я вижу в этом коде, заключается в том, что результатом каждого вызова getline() является пустая строка, независимо от содержимого файла.
Этот же код работает нормально (то есть каждый вызов getline() возвращает правильно закодированную непустую строку) в версии того же файла в кодировке utf8, если я опущу строки 3 и 5 вышеупомянутого метода.
По какой-то причине я не смог найти нигде здесь, на SO или на http://en.cppreference.com/, или где-либо еще в дикой природе, примеров того, кто пытается сделать то же самое.
Все идеи / предложения (в соответствии с требованиями выше) приветствуются.
1 ответ
Чтение UTF-16, написание UTF-8
Первый вопрос, который вы должны уточнить, касается того, какой вариант UTF16 вы читаете:
- это UTF-16LE (то есть сгенерированный под windows)?
- это UTF-16BE (по умолчанию генерируется wstream)?
- это UTF16 с спецификацией?
Следующий вопрос заключается в том, чтобы узнать, действительно ли вы можете вывести свой UTF8 или UTF16 на консоль, зная, что консоль Windows по умолчанию действительно может вызвать головокружение от этого.
Шаг 1: Убедитесь, что проблема не связана с консолью win
Итак, небольшой код для чтения UTF-16LE и проверки содержимого с помощью встроенной функции Windows (вам просто нужно включить <windows.h>
в вашем консольном приложении):
wifstream is16(filename);
is16.imbue(locale(is16.getloc(), new codecvt_utf16<wchar_t, 0x10ffff, little_endian>()));
wstring wtext, wline;
for (int i = 0; i < 10 && getline(is16, wline); i++)
wtext += wline + L"\n";
MessageBoxW(NULL, wtext.c_str(), L"UTF16-Little Endian", MB_OK);
Если ваш файл UTF-16 с спецификацией, просто замените litte_endian
с consume_header
,
Шаг 2. Преобразуйте строку utf16 обратно в строку utf8.
Вы должны использовать конвертер строк:
wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;
wifstream is16(filename);
is16.imbue(locale(is16.getloc(), new codecvt_utf16<wchar_t, 0x10ffff, little_endian>()));
wstring wline;
string u8line;
for (int i = 0; i < 10 && getline(is16, wline); i++) {
u8line = converter.to_bytes(wline);
cout << u8line<<endl;
}
Это хорошо покажет вам ascii caracters на консоли win. Однако все кодировки utf8 будут отображаться как мусор (если вы не более успешны, чем я, для настройки консоли для отображения шрифта Unicode).
Шаг 3: проверьте кодировку utf8 с помощью файла
Так как в Win console это плохо получается, лучше всего записать кодировку, которую вы создали, прямо в файл и открыть этот файл с помощью текстового редактора (lke Notepad++), который может показать вам кодировку.
Примечание: все это было сделано с использованием только стандартной библиотеки (кроме посредниковMessageBoxW()
) и его локаль.
Дальнейшие шаги
Если вы хотите определить кодировку, в первую очередь нужно посмотреть, есть ли спецификация в самом начале вашего файла (открыт для двоичного ввода, языковой стандарт по умолчанию "C"):
char bom_utf8[]{0xEF, 0xBB, 0xBF};
char bom_utf16be[] { 0xFE, 0xFF};
char bom_utf16le[] { 0xFf, 0xFe};
char bom_utf32be[] { 0, 0, 0xFf, 0xFe};
char bom_uff32le[] { 0xFf, 0xFe, 0, 0};
Просто загрузите первые несколько байтов и сравните с этими данными.
Если вы нашли один, все в порядке. Если нет, вам придется перебирать файл.
Быстрое приближение, если вы ожидаете западных языков, следующее: Если вы найдете много нулевых байтов (>25% <50%), это, вероятно, utf16. Если вы найдете более 50% нулей, это, вероятно, utf32.
Но более точный подход может иметь смысл. Например, чтобы проверить, является ли файл UTF16, вам просто нужно реализовать небольшой конечный автомат, который проверяет, что в любом случае первое слово имеет старший байт между 0xD8 и 0xDB, а следующее слово имеет старший байт между 0xDC и 0xDF. То, что высоко, а что низко, зависит, конечно, от нуля или до старшего.
Для UTF8 это похожая практика, но конечный автомат немного сложнее, потому что битовый шаблон первого символа определяет, сколько символов должно следовать, и каждый из следующих должен иметь битовый шаблон. (c & 0xC0) == 0x80
,