UTF8 в / из широкого преобразования символов в STL

Можно ли преобразовать строку UTF8 в std:: string в std:: wstring и наоборот независимо от платформы? В приложении Windows я бы использовал MultiByteToWideChar и WideCharToMultiByte. Тем не менее, код скомпилирован для нескольких ОС, и я ограничен стандартной библиотекой C++.

9 ответов

Я задавал этот вопрос 5 лет назад. Эта тема была очень полезна для меня тогда, я пришел к выводу, а затем я продолжил свой проект. Забавно, что недавно мне понадобилось нечто подобное, совершенно не связанное с этим проектом из прошлого. Пока я искал возможные решения, я наткнулся на свой вопрос:)

Решение, которое я выбрал сейчас, основано на C++11. Библиотеки поддержки, которые Константин упоминает в своем ответе, теперь являются частью стандарта. Если мы заменим std::wstring новым типом строки std::u16string, то преобразования будут выглядеть так:

UTF-8 до UTF-16

std::string source;
...
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
std::u16string dest = convert.from_bytes(source);    

UTF-16 до UTF-8

std::u16string source;
...
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
std::string dest = convert.to_bytes(source);    

Как видно из других ответов, существует несколько подходов к проблеме. Вот почему я воздерживаюсь от выбора принятого ответа.

В определении проблемы прямо указано, что 8-битная кодировка символов - UTF-8. Это делает это тривиальной проблемой; все, что для этого требуется, - это немного переворачивать одну спецификацию UTF в другую.

Просто посмотрите на кодировки на этих страницах Википедии для UTF-8, UTF-16 и UTF-32.

Принцип прост - пройдите ввод и соберите 32-битную кодовую точку Unicode в соответствии с одной спецификацией UTF, а затем создайте кодовую точку в соответствии с другой спецификацией. Отдельные кодовые точки не нуждаются в переводе, как это требуется для любой другой кодировки символов; вот что делает эту проблему простой.

Вот быстрая реализация wchar_t преобразование в UTF-8 и наоборот. Предполагается, что входные данные уже правильно закодированы - здесь применяется старая поговорка "Мусор в мусор". Я считаю, что проверка кодировки лучше всего выполнять как отдельный шаг.

std::string wchar_to_UTF8(const wchar_t * in)
{
    std::string out;
    unsigned int codepoint = 0;
    for (in;  *in != 0;  ++in)
    {
        if (*in >= 0xd800 && *in <= 0xdbff)
            codepoint = ((*in - 0xd800) << 10) + 0x10000;
        else
        {
            if (*in >= 0xdc00 && *in <= 0xdfff)
                codepoint |= *in - 0xdc00;
            else
                codepoint = *in;

            if (codepoint <= 0x7f)
                out.append(1, static_cast<char>(codepoint));
            else if (codepoint <= 0x7ff)
            {
                out.append(1, static_cast<char>(0xc0 | ((codepoint >> 6) & 0x1f)));
                out.append(1, static_cast<char>(0x80 | (codepoint & 0x3f)));
            }
            else if (codepoint <= 0xffff)
            {
                out.append(1, static_cast<char>(0xe0 | ((codepoint >> 12) & 0x0f)));
                out.append(1, static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f)));
                out.append(1, static_cast<char>(0x80 | (codepoint & 0x3f)));
            }
            else
            {
                out.append(1, static_cast<char>(0xf0 | ((codepoint >> 18) & 0x07)));
                out.append(1, static_cast<char>(0x80 | ((codepoint >> 12) & 0x3f)));
                out.append(1, static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f)));
                out.append(1, static_cast<char>(0x80 | (codepoint & 0x3f)));
            }
            codepoint = 0;
        }
    }
    return out;
}

Приведенный выше код работает как для входа UTF-16, так и для входа UTF-32, просто потому, что диапазон d800 через dfff недействительные кодовые точки; они указывают, что вы декодируете UTF-16. Если вы знаете, что wchar_t 32 бита, то вы могли бы удалить некоторый код для оптимизации функции.

std::wstring UTF8_to_wchar(const char * in)
{
    std::wstring out;
    unsigned int codepoint;
    while (*in != 0)
    {
        unsigned char ch = static_cast<unsigned char>(*in);
        if (ch <= 0x7f)
            codepoint = ch;
        else if (ch <= 0xbf)
            codepoint = (codepoint << 6) | (ch & 0x3f);
        else if (ch <= 0xdf)
            codepoint = ch & 0x1f;
        else if (ch <= 0xef)
            codepoint = ch & 0x0f;
        else
            codepoint = ch & 0x07;
        ++in;
        if (((*in & 0xc0) != 0x80) && (codepoint <= 0x10ffff))
        {
            if (sizeof(wchar_t) > 2)
                out.append(1, static_cast<wchar_t>(codepoint));
            else if (codepoint > 0xffff)
            {
                out.append(1, static_cast<wchar_t>(0xd800 + (codepoint >> 10)));
                out.append(1, static_cast<wchar_t>(0xdc00 + (codepoint & 0x03ff)));
            }
            else if (codepoint < 0xd800 || codepoint >= 0xe000)
                out.append(1, static_cast<wchar_t>(codepoint));
        }
    }
    return out;
}

Опять же, если вы это знаете wchar_t 32 бита, вы можете удалить некоторый код из этой функции, но в этом случае это не должно иметь никакого значения. Выражение sizeof(wchar_t) > 2 известен во время компиляции, поэтому любой приличный компилятор распознает мертвый код и удаляет его.

Вы можете извлечь utf8_codecvt_facet из библиотеки повышения сериализации.

Пример их использования:

  typedef wchar_t ucs4_t;

  std::locale old_locale;
  std::locale utf8_locale(old_locale,new utf8_codecvt_facet<ucs4_t>);

  // Set a New global locale
  std::locale::global(utf8_locale);

  // Send the UCS-4 data out, converting to UTF-8
  {
    std::wofstream ofs("data.ucd");
    ofs.imbue(utf8_locale);
    std::copy(ucs4_data.begin(),ucs4_data.end(),
          std::ostream_iterator<ucs4_t,ucs4_t>(ofs));
  }

  // Read the UTF-8 data back in, converting to UCS-4 on the way in
  std::vector<ucs4_t> from_file;
  {
    std::wifstream ifs("data.ucd");
    ifs.imbue(utf8_locale);
    ucs4_t item = 0;
    while (ifs >> item) from_file.push_back(item);
  }

Ищу utf8_codecvt_facet.hpp а также utf8_codecvt_facet.cpp файлы в форсированных источниках.

Есть несколько способов сделать это, но результаты зависят от того, какие кодировки символов находятся в string а также wstring переменные.

Если вы знаете string это ASCII, вы можете просто использовать wstringИтератор конструктора:

string s = "This is surely ASCII.";
wstring w(s.begin(), s.end());

Если твой string имеет другую кодировку, однако вы получите очень плохие результаты. Если кодировка Unicode, вы можете взглянуть на проект ICU, который предоставляет кроссплатформенный набор библиотек, которые преобразуются во все виды кодировок Unicode.

Если твой string содержит символы в кодовой странице, тогда пусть $DEITY помилует вашу душу.

ConvertUTF.h ConvertUTF.c

bames53 за предоставление обновленных версий

Вы можете использовать codecvt языковой аспект. Там определена конкретная специализация, codecvt<wchar_t, char, mbstate_t> это может быть полезно для вас, однако, поведение этого зависит от системы, и не гарантирует преобразование в UTF-8 в любом случае.

UTFConverter - проверить эту библиотеку. Это делает такое преобразование, но вам нужен также класс ConvertUTF - я нашел его здесь

Создал свою собственную библиотеку для преобразования utf-8 в utf-16/utf-32, но решил сделать для этой цели вилку существующего проекта.

https://github.com/tapika/cutf

(Источник https://github.com/noct/cutf)

API работает как с простым C, так и с C++.

Прототипы функций выглядят так: (Полный список см. https://github.com/tapika/cutf/blob/master/cutf.h)

//
//  Converts utf-8 string to wide version.
//
//  returns target string length.
//
size_t utf8towchar(const char* s, size_t inSize, wchar_t* out, size_t bufSize);

//
//  Converts wide string to utf-8 string.
//
//  returns filled buffer length (not string length)
//
size_t wchartoutf8(const wchar_t* s, size_t inSize, char* out, size_t outsize);

#ifdef __cplusplus

std::wstring utf8towide(const char* s);
std::wstring utf8towide(const std::string& s);
std::string  widetoutf8(const wchar_t* ws);
std::string  widetoutf8(const std::wstring& ws);

#endif

Пример использования / простого тестового приложения для тестирования преобразования utf:

#include "cutf.h"

#define ok(statement)                                       \
    if( !(statement) )                                      \
    {                                                       \
        printf("Failed statement: %s\n", #statement);       \
        r = 1;                                              \
    }

int simpleStringTest()
{
    const wchar_t* chineseText = L"主体";
    auto s = widetoutf8(chineseText);
    size_t r = 0;

    printf("simple string test:  ");

    ok( s.length() == 6 );
    uint8_t utf8_array[] = { 0xE4, 0xB8, 0xBB, 0xE4, 0xBD, 0x93 };

    for(int i = 0; i < 6; i++)
        ok(((uint8_t)s[i]) == utf8_array[i]);

    auto ws = utf8towide(s);
    ok(ws.length() == 2);
    ok(ws == chineseText);

    if( r == 0 )
        printf("ok.\n");

    return (int)r;
}

И если эта библиотека вам не подходит - смело открывайте следующую ссылку:

http://utf8everywhere.org/

и прокрутите вниз в конце страницы и выберите любую более тяжелую библиотеку, которая вам нравится.

Я не думаю, что есть портативный способ сделать это. C++ не знает кодировку своих многобайтовых символов.

Как предположил Крис, лучше всего играть с codecvt.

Другие вопросы по тегам