Проблема при преобразовании utf16 wide std :: wstring в utf8 wide std :: string для редких символов - PullRequest
1 голос
/ 06 ноября 2019

Почему некоторые широкие строки в кодировке utf16 при преобразовании в узкие строки в кодировке utf8 преобразуются в шестнадцатеричные значения, которые кажутся неправильными при преобразовании с помощью этой широко распространенной функции преобразования?

std::string convert_string(const std::wstring& str)
{
    std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
    return conv.to_bytes(str);
}

Привет. У меня есть приложение C ++ в Windows, которое принимает пользовательский ввод в командной строке. Я использую основную точку входа с широкими символами, чтобы получить ввод в виде строки utf16, которую я конвертирую в узкую строку utf8 с помощью вышеуказанной функции.

Эта функция может быть найдена во многих местах в Интернете и работаетпочти во всех случаях. Однако я нашел несколько примеров, где он не работает должным образом.

Например, если я ввожу символ emojii "?" в виде строкового литерала (в моем cpp-файле с кодировкой utf8) и напишу егона диск, файл (FILE-1) содержит следующие данные (которые являются правильными шестнадцатеричными значениями utf8, указанными здесь https://www.fileformat.info/info/unicode/char/1f922/index.htm):

    0xF0 0x9F 0xA4 0xA2

Однако, если я передам emojii моему приложению в командной строке ипреобразовать его в строку utf8 с помощью функции преобразования, описанной выше, а затем записать ее на диск, файл (FILE-2) содержит различные необработанные байты:

    0xED 0xA0 0xBE 0xED 0xB4 0xA2

В то время как второй файл, кажется, указывает на то, что преобразование произвелонеправильный вывод, если вы копируете и вставляете шестнадцатеричные значения (по крайней мере, в notepad ++), это приводит к правильному смайлику. Также WinMerge считает, что два файла идентичны.

, поэтому в заключение я хотел бы знать следующее:

  1. как некорректно выглядящие преобразованные шестнадцатеричные значения правильно отображаются на правильный символ utf8 в примере выше
  2. почему функция преобразования преобразует некоторые символы в эту форму, в то время как почти все другие символы выдают ожидаемые необработанные байты
  3. В качестве бонуса я хотел бы знать, возможно ли изменить функцию преобразования для остановкипри выводе этих редких символов в этой форме

Я должен отметить, что у меня уже есть обходная функция, которая использует вызовы WinAPI, однако использование только стандартных вызовов библиотеки - мечта:)

std::string convert_string(const std::wstring& wstr)
{
    if(wstr.empty())
        return std::string();

    int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
    std::string strTo(size_needed, 0);
    WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
    return strTo;
}

Ответы [ 2 ]

5 голосов
/ 06 ноября 2019

Проблема в том, что std::wstring_convert<std::codecvt_utf8<wchar_t>> конвертирует из UCS-2, не из UTF-16 . Символы внутри BMP (U + 0000..U + FFFF) имеют идентичные кодировки как в UCS-2, так и в UTF-16 и поэтому будут работать, но символы вне BMP (U + FFFF..U + 10FFFF), такие каккак ваш эмодзи, вообще не существует в UCS-2. Это означает, что преобразование не понимает символ и выдает неправильные байты UTF-8 (технически оно конвертирует каждую половину суррогатной пары UTF-16 в отдельный символ UTF-8).

Вам необходимо использоватьstd::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> вместо.

2 голосов
/ 06 ноября 2019

Здесь уже есть подтвержденный ответ. Но для записей, вот некоторая дополнительная информация.

Кодировка сморщенного лица emoji была введена в Unicode в 2016 году. Это 4 utf-8 байт (0xF0 0x9F 0xA4 0xA2) или 2 utf-16 слов (0xD83E 0xDD22).

Удивительная кодировка 0xED 0xA0 0xBE 0xED 0xB4 0xA2 фактически соответствует суррогатной паре UCS :

Итак, в основном, ваша первая кодировка - прямая utf8. Второе кодирование - это кодирование в utf8 кодирования UCS-2, которое соответствует кодированию utf-16 требуемого символа.

Как правильно сказал принятый ответ, виновником является std::codecvt_utf8<wchar_t>, поскольку речь идет о UCS-2, а не UTF-16.

Это довольно удивительно в наши дничтобы найти в стандартных библиотеках эту устаревшую кодировку, но я подозреваю, что это все еще напоминает лоббирование Microsoft в стандартном комитете, которое восходит к старой поддержке Windows для Unicode с UCS-2.

...