web-dev-qa-db-fra.com

Conversion UTF8 vers / à partir de caractères larges en STL

Est-il possible de convertir une chaîne UTF8 dans une chaîne std :: en std :: wstring et vice versa d'une manière indépendante de la plateforme? Dans une application Windows, j'utiliserais MultiByteToWideChar et WideCharToMultiByte. Cependant, le code est compilé pour plusieurs systèmes d'exploitation et je suis limité à la bibliothèque C++ standard.

68
Vladimir Grigorov

J'ai posé cette question il y a 5 ans. Ce fil m'a été très utile à l'époque, je suis arrivé à une conclusion, puis j'ai poursuivi mon projet. C'est drôle que j'avais besoin de quelque chose de similaire récemment, sans aucun rapport avec ce projet du passé. Alors que je cherchais des solutions possibles, je suis tombé sur ma propre question :)

La solution que j'ai choisie maintenant est basée sur C++ 11. Les bibliothèques de boost que Constantin mentionne dans sa réponse font maintenant partie de la norme. Si nous remplaçons std :: wstring par le nouveau type de chaîne std :: u16string, les conversions ressembleront à ceci:

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);    

Comme le montrent les autres réponses, il existe plusieurs approches au problème. C'est pourquoi je m'abstiens de choisir une réponse acceptée.

43
Vladimir Grigorov
25
Assaf Lavie

Vous pouvez extraire utf8_codecvt_facet de Boost bibliothèque de sérialisation .

Leur exemple d'utilisation:

  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);
  }

Chercher utf8_codecvt_facet.hpp et utf8_codecvt_facet.cpp fichiers dans les sources boost.

23
Constantin

La définition du problème indique explicitement que le codage de caractères à 8 bits est UTF-8. Cela en fait un problème trivial; tout ce qu'il faut, c'est un peu de twiddling pour convertir d'une spécification UTF en une autre.

Regardez simplement les encodages sur ces pages Wikipedia pour TF-8 , TF-16 , et TF-32 .

Le principe est simple - passez par l'entrée et assemblez un point de code Unicode 32 bits selon une spécification UTF, puis émettez le point de code selon l'autre spécification. Les points de code individuels n'ont pas besoin de traduction, comme cela serait requis avec tout autre codage de caractères; c'est ce qui en fait un problème simple.

Voici une implémentation rapide de la conversion de wchar_t En UTF-8 et vice versa. Il suppose que l'entrée est déjà correctement encodée - le vieil adage "Garbage in, garbage out" s'applique ici. Je crois que la vérification de l'encodage est préférable de faire une étape distincte.

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;
}

Le code ci-dessus fonctionne pour les entrées UTF-16 et UTF-32, simplement parce que la plage d800 À dfff sont des points de code non valides; ils indiquent que vous décodez UTF-16. Si vous savez que wchar_t Est de 32 bits, vous pouvez supprimer du code pour optimiser la fonction.

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;
}

Encore une fois, si vous savez que wchar_t Est de 32 bits, vous pouvez supprimer du code de cette fonction, mais dans ce cas, cela ne devrait faire aucune différence. L'expression sizeof(wchar_t) > 2 est connue au moment de la compilation, donc tout compilateur décent reconnaîtra le code mort et le supprimera.

16
Mark Ransom

Il existe plusieurs façons de procéder, mais les résultats dépendent de ce que sont les encodages de caractères dans les variables string et wstring.

Si vous savez que string est ASCII, vous pouvez simplement utiliser le constructeur d'itérateur de wstring:

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

Cependant, si votre string a un autre encodage, vous obtiendrez de très mauvais résultats. Si l'encodage est Unicode, vous pouvez jeter un œil au projet IC , qui fournit un ensemble multiplateforme de bibliothèques qui convertissent vers et depuis toutes sortes d'encodages Unicode.

Si votre string contient des caractères dans une page de codes, alors $ DEITY peut avoir pitié de votre âme.

13
Ben Straub

ConvertUTF.hConvertUTF.c

Nous remercions bames5 pour avoir fourni des versions mises à jour

4
vharron

Vous pouvez utiliser la facette codecvt locale . Une spécialisation spécifique est définie, codecvt<wchar_t, char, mbstate_t> qui peut vous être utile, bien que son comportement soit spécifique au système et ne garantisse aucune conversion en UTF-8.

2
Chris Jester-Young

TFConverter - consultez cette bibliothèque. Il fait une telle conversion, mais vous avez également besoin de la classe ConvertUTF - je l'ai trouvé ici

1
Trisch

J'ai créé ma propre bibliothèque pour la conversion d'utf-8 en utf-16/utf-32 - mais j'ai décidé de créer un fork du projet existant à cet effet.

https://github.com/tapika/cutf

(Originaire de https://github.com/noct/cutf )

L'API fonctionne aussi bien en C simple qu'en C++.

Les prototypes de fonctions ressemblent à ceci: (Pour la liste complète, voir 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

Exemple d'utilisation/application de test simple pour les tests de conversion 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;
}

Et si cette bibliothèque ne répond pas à vos besoins, n'hésitez pas à ouvrir le lien suivant:

http://utf8everywhere.org/

et faites défiler vers le bas à la fin de la page et choisissez n'importe quelle bibliothèque plus lourde que vous aimez.

0
TarmoPikaro