c++ - UTF8 hacia/desde la conversión de ancho de char en STL
unicode utf-8 (9)
¿Es posible convertir cadenas UTF8 en std :: string a std :: wstring y viceversa de una manera independiente de la plataforma? En una aplicación de Windows usaría MultiByteToWideChar y WideCharToMultiByte. Sin embargo, el código está compilado para múltiples sistemas operativos y estoy limitado a la biblioteca estándar de C ++.
Hay varias formas de hacerlo, pero los resultados dependen de las codificaciones de los caracteres en las variables string
y wstring
.
Si sabe que la string
es ASCII, simplemente puede usar el constructor del iterador de wstring
:
string s = "This is surely ASCII.";
wstring w(s.begin(), s.end());
Sin embargo, si su string
tiene alguna otra codificación, obtendrá muy malos resultados. Si la codificación es Unicode, podría echarle un vistazo al proyecto ICU , que proporciona un conjunto de bibliotecas multiplataforma que se convierte a todo tipo de codificaciones Unicode.
Si su string
contiene caracteres en una página de códigos, entonces puede $ DEITY tener piedad de su alma.
He hecho esta pregunta hace 5 años. Este hilo fue muy útil para mí en ese momento, llegué a una conclusión y luego continué con mi proyecto. Es gracioso que necesité algo similar recientemente, totalmente ajeno a ese proyecto del pasado. Mientras buscaba posibles soluciones, tropecé con mi propia pregunta :)
La solución que elegí ahora está basada en C ++ 11. Las bibliotecas de impulso que Constantin menciona en su respuesta son ahora parte del estándar. Si reemplazamos std :: wstring con la nueva cadena escriba std :: u16string, las conversiones se verán así:
UTF-8 a 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 a UTF-8
std::u16string source;
...
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
std::string dest = convert.to_bytes(source);
Como se ve en las otras respuestas, hay múltiples enfoques para el problema. Es por eso que me abstengo de elegir una respuesta aceptada.
La definición del problema establece explícitamente que la codificación de caracteres de 8 bits es UTF-8. Eso hace que esto sea un problema trivial; todo lo que requiere es un poco de tiempo para convertir una especificación UTF a otra.
Basta con mirar las codificaciones en estas páginas de Wikipedia para UTF-8 , UTF-16 y UTF-32 .
El principio es simple: revise la entrada y ensamble un punto de código Unicode de 32 bits de acuerdo con una especificación UTF, luego emita el punto de código de acuerdo con la otra especificación. Los puntos de código individuales no necesitan traducción, como se requeriría con cualquier otra codificación de caracteres; eso es lo que hace que esto sea un problema simple.
Aquí hay una implementación rápida de la wchar_t
de wchar_t
a UTF-8 y viceversa. Asume que la entrada ya está codificada correctamente; aquí se aplica el viejo dicho "Basura adentro, basura afuera". Creo que verificar la codificación se realiza mejor como un paso separado.
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;
}
El código anterior funciona para las entradas UTF-16 y UTF-32, simplemente porque el rango d800
a dfff
son puntos de código no válidos; indican que está decodificando UTF-16. Si sabes que wchar_t
tiene 32 bits, entonces puedes eliminar algún código para optimizar la función.
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;
}
De nuevo, si sabe que wchar_t
tiene 32 bits, podría eliminar algún código de esta función, pero en este caso no debería hacer ninguna diferencia. La expresión sizeof(wchar_t) > 2
es conocida en tiempo de compilación, por lo que cualquier compilador decente reconocerá el código muerto y lo eliminará.
No creo que haya una manera portátil de hacer esto. C ++ no conoce la codificación de sus caracteres multibyte.
Como sugirió Chris, tu mejor opción es jugar con codecvt.
Puede extraer utf8_codecvt_facet
de la biblioteca de serialización de Boost .
Su ejemplo de uso:
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);
}
Busque los archivos utf8_codecvt_facet.hpp
y utf8_codecvt_facet.cpp
en las fuentes de impulso.
Puede usar la faceta de configuración regional codecvt
. Hay una especialización específica definida, codecvt<wchar_t, char, mbstate_t>
que puede ser útil para usted, aunque el comportamiento de eso es específico del sistema y no garantiza la conversión a UTF-8 de ninguna manera.
Crédito a bames53 por proporcionar versiones actualizadas
UTFConverter : UTFConverter esta biblioteca. Hace tal conversión, pero también necesita la clase ConvertUTF - La he encontrado here