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 помилует вашу душу.
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;
}
И если эта библиотека вам не подходит - смело открывайте следующую ссылку:
и прокрутите вниз в конце страницы и выберите любую более тяжелую библиотеку, которая вам нравится.
Я не думаю, что есть портативный способ сделать это. C++ не знает кодировку своих многобайтовых символов.
Как предположил Крис, лучше всего играть с codecvt.