82 votes

Conversion UTF8 vers / depuis caractères larges en LIST

Est-il possible de convertir une chaîne UTF8 dans un std :: string en std :: wstring et vice versa de manière indépendante de la plate-forme? 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 standard C ++.

64voto

Vladimir Grigorov Points 3306

J'ai posé cette question il y a 5 ans. Ce fil a été très utile pour moi, j'en suis venu à une conclusion, puis j'ai avancé sur mon projet. C'est drôle que j'ai besoin de quelque chose de similaire récemment, totalement étrangers à ce projet dans le passé. Comme je faisais des recherches pour trouver des solutions possibles, je suis tombé sur ma propre question :)

La solution que j'ai choisi est maintenant basé sur le C++11. Les bibliothèques boost que Constantin mentionne dans sa réponse sont désormais partie de la norme. Si on remplace les std::wstring avec la nouvelle chaîne de type std::u16string, puis les conversions ressemblera à 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 pour de l'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 on le voit à partir d'autres réponses, il existe de nombreuses approches à ce problème. C'est pourquoi je m'abstiens de sélection d'une accepté de répondre.

29voto

Mark Ransom Points 132545

Allez, les gens - la définition du problème, qui stipule explicitement que les 8 bits de codage de caractères UTF-8. C'est un banal problème; si vous l'avez écrit vous-même, le code ne serait probablement pas plus d'une douzaine de lignes.

Il suffit de regarder les codages sur ces pages de Wikipédia pour de l'UTF-8 et UTF-16.

Edit: j'ai peut-être été coupable d'un peu d'exagération. Certes, le code est bien plus qu'une dizaine de lignes; j'ai peu-tourner toute ma vie, alors peut-être que cela semble plus simple pour moi que ça l'est vraiment.

Le principe est simple: passer par l'entrée et à assembler un 32-bit de point de code Unicode selon un UTF spec, puis émet le point de code, selon les autres spec. L'individu points de code ont pas besoin de traduction, qu'il serait nécessaire avec un autre codage de caractères; c'est ce qui rend ce un problème simple.

Voici une mise en œuvre rapide de l'UTF-16 de conversion en UTF-8 et vice versa. Il suppose que l'entrée est déjà codé correctement - le vieux dicton: "Garbage in, Garbage out" s'applique ici. Je crois que la vérification de l'encodage est mieux de le faire comme une étape distincte. Notez ce code n'est que très peu testé.

std::string UTF16to8(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;
}

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

Edit 2: Voici une version améliorée de l' UTF8to16. Il ne gère pas mal formés d'entrée très bien, mais il n'émettent que des bêtises au lieu de l'invalidité de l'UTF-16 séquences. C'est beaucoup plus simple que l'original, et peut-être plus rapide aussi.

std::wstring UTF8to16(const char * in)
{
    std::wstring out;
    if (in == NULL)
        return 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 (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;
}

24voto

Assaf Lavie Points 20181

22voto

Constantin Points 12185

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

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

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

13voto

Ben Straub Points 3224

Il y a plusieurs façons de le faire, mais les résultats dépendent de ce que les encodages de caractères sont dans l' string et wstring variables.

Si vous connaissez l' string est en ASCII, vous pouvez simplement utiliser wstrings'itérateur constructeur:

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

Si votre string a un autre encodage, cependant, vous aurez de très mauvais résultats. Si le codage Unicode, vous pouvez prendre un coup d'oeil à l' ICU projet, qui prévoit une croix-plate-forme ensemble de bibliothèques convertir vers et à partir de toutes sortes de codages Unicode.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X