UTF8 zu / von Wide Char-Konvertierung in STL

75

Ist es möglich, einen UTF8-String in einem std :: string in einen std :: wstring und umgekehrt plattformunabhängig zu konvertieren? In einer Windows-Anwendung würde ich MultiByteToWideChar und WideCharToMultiByte verwenden. Der Code ist jedoch für mehrere Betriebssysteme kompiliert und ich bin auf die Standard-C ++ - Bibliothek beschränkt.

Vladimir Grigorov
quelle
3
Die Standard-C ++ - Bibliothek heißt übrigens nicht STL; Die STL ist nur ein kleiner Unterabschnitt der Standard-C ++ - Bibliothek. In diesem Fall fragen Sie vermutlich nach Funktionen in der Standard-C ++ - Bibliothek, und ich habe entsprechend geantwortet.
Chris Jester-Young
6
Sie haben nicht angegeben, mit welcher Codierung Sie enden möchten. wstring gibt keine bestimmte Codierung an. Natürlich wäre es natürlich, auf Plattformen, auf denen wchar_t 4 Bytes breit ist, und utf16, wenn wchar_t 2 Bytes groß ist, in utf32 zu konvertieren. Ist es das was du willst?
Jalf
1
@jalf Dein Kommentar ist irreführend. std::wstringist std::basic_string<wchar_t>. wchar_tist ein undurchsichtiger Datentyp, der ein Unicode-Zeichen darstellt (die Tatsache, dass es unter Windows 16 Bit lang ist, bedeutet nur, dass Windows nicht dem Standard folgt). Es gibt keine "Codierung" für abstrakte Unicode-Zeichen, sie sind nur Zeichen.
Kirelagin

Antworten:

53

Ich habe diese Frage vor 5 Jahren gestellt. Dieser Thread war damals sehr hilfreich für mich, ich kam zu einem Schluss, dann ging ich mit meinem Projekt weiter. Es ist lustig, dass ich in letzter Zeit etwas Ähnliches brauchte, völlig unabhängig von diesem Projekt aus der Vergangenheit. Als ich nach möglichen Lösungen suchte, stieß ich auf meine eigene Frage :)

Die Lösung, die ich jetzt gewählt habe, basiert auf C ++ 11. Die Boost-Bibliotheken, die Constantin in seiner Antwort erwähnt, sind jetzt Teil des Standards. Wenn wir std :: wstring durch den neuen String-Typ std :: u16string ersetzen, sehen die Konvertierungen folgendermaßen aus:

UTF-8 bis 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 bis UTF-8

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

Wie aus den anderen Antworten hervorgeht, gibt es mehrere Ansätze für das Problem. Deshalb verzichte ich darauf, eine akzeptierte Antwort zu wählen.

Vladimir Grigorov
quelle
wstring impliziert 2 oder 4 Bytes anstelle von Einzelbytezeichen. Wo ist die Frage, um von der utf8-Codierung zu wechseln?
Chawathe Vipul S
1
Ich habe einige seltsam schlechte Leistung mit Codecvt, suchen Sie hier für Details: stackoverflow.com/questions/26196686/…
Xtra Coder
2
Ist das UTF-16 mit LE oder BE?
Thomthom
7
std :: wstring_convert veraltet in C ++ 17
HojjatJafary
1
@HojjatJafary, was ist der Ersatz?
Jakar
23

Sie können utf8_codecvt_facetaus der Boost-Serialisierungsbibliothek extrahieren .

Ihr Anwendungsbeispiel:

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

Suchen Sie nach utf8_codecvt_facet.hppund utf8_codecvt_facet.cppDateien in Boost-Quellen.

Constantin
quelle
Ich dachte, Sie mussten den Stream durchdringen, bevor er geöffnet wird, sonst wird der Durchfluss ignoriert!
Martin York
Martin, es scheint mit Visual Studio 2005 zu funktionieren: 0x41a wurde erfolgreich in die UTF-8-Sequenz {0xd0, 0x9a} konvertiert.
Constantin
21

Die Problemdefinition besagt ausdrücklich, dass die 8-Bit-Zeichencodierung UTF-8 ist. Das macht dies zu einem trivialen Problem. Für die Konvertierung von einer UTF-Spezifikation in eine andere ist lediglich ein wenig Aufwand erforderlich.

Schauen Sie sich einfach die Codierungen auf diesen Wikipedia-Seiten für UTF-8 , UTF-16 und UTF-32 an .

Das Prinzip ist einfach: Gehen Sie die Eingabe durch und setzen Sie einen 32-Bit-Unicode-Codepunkt gemäß einer UTF-Spezifikation zusammen. Geben Sie dann den Codepunkt gemäß der anderen Spezifikation aus. Die einzelnen Codepunkte benötigen keine Übersetzung, wie dies bei jeder anderen Zeichenkodierung erforderlich wäre. Das macht dies zu einem einfachen Problem.

Hier ist eine schnelle Implementierung der wchar_tUTF-8-Konvertierung und umgekehrt. Es wird davon ausgegangen, dass die Eingabe bereits ordnungsgemäß codiert ist - hier gilt das alte Sprichwort "Müll rein, Müll raus". Ich glaube, dass die Überprüfung der Codierung am besten als separater Schritt erfolgt.

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

Der obige Code funktioniert sowohl für UTF-16- als auch für UTF-32-Eingaben, einfach weil der Bereich d800durch dfffungültige Codepunkte ist. Sie zeigen an, dass Sie UTF-16 dekodieren. Wenn Sie wissen, dass dies wchar_t32 Bit sind, können Sie Code entfernen, um die Funktion zu optimieren.

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

Wenn Sie wissen, dass dies wchar_t32 Bit sind, können Sie Code aus dieser Funktion entfernen, aber in diesem Fall sollte dies keinen Unterschied machen. Der Ausdruck sizeof(wchar_t) > 2ist zur Kompilierungszeit bekannt, sodass jeder anständige Compiler toten Code erkennt und entfernt.

Mark Ransom
quelle
Ich sehe in der ursprünglichen Frage nichts über std :: string mit UTF-8-codierten Strings: "Ist es möglich, std :: string plattformunabhängig in std :: wstring und umgekehrt zu konvertieren?"
Nemanja Trifunovic
1
UTF-8 ist im Titel des Beitrags angegeben. Sie haben Recht, dass es im Textkörper fehlt.
Mark Ransom
6
Aber '' widechar '' bedeutet nicht unbedingt
UTF16
6
Was Sie haben, kann ein guter "Proof of Concept" sein. Es ist eine Sache, gültige Codierungen erfolgreich zu konvertieren. Es ist ein weiterer Aufwand, die Konvertierung ungültiger Codierungsdaten (z. B. ungepaarte Surrogate in UTF-16) gemäß den Spezifikationen korrekt zu handhaben. Dafür benötigen Sie wirklich gründlich gestalteten und getesteten Code.
Craig McQueen
2
@Craig McQueen, du hast absolut recht. Ich ging davon aus, dass die Codierung bereits korrekt war und es sich nur um eine mechanische Konvertierung handelte. Ich bin sicher, dass es Situationen gibt, in denen dies der Fall ist, und dieser Code wäre angemessen - aber die Einschränkungen sollten explizit angegeben werden. Aus der ursprünglichen Frage geht nicht hervor, ob dies ein Problem sein sollte oder nicht.
Mark Ransom
13

Es gibt verschiedene Möglichkeiten, dies zu tun, aber die Ergebnisse hängen davon ab, welche Zeichencodierungen in den Variablen stringund enthalten wstringsind.

Wenn Sie wissen, dass stringes sich um ASCII handelt, können Sie einfach wstringden Iterator-Konstruktor verwenden:

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

Wenn Sie stringjedoch eine andere Codierung haben, erhalten Sie sehr schlechte Ergebnisse. Wenn die Codierung Unicode ist, können Sie sich das ICU-Projekt ansehen , das einen plattformübergreifenden Satz von Bibliotheken bereitstellt, die in und aus allen Arten von Unicode-Codierungen konvertieren.

Wenn Ihr stringZeichen in einer Codepage enthält, kann $ DEITY Ihrer Seele gnädig sein.

Ben Straub
quelle
4
ICU konvertiert auch / von jeder Zeichenkodierung, die mir jemals begegnet ist. Es ist riesig.
Martin York
2

Sie können die codecvtGebietsschemafacette verwenden . Es ist eine spezielle Spezialisierung definiert, codecvt<wchar_t, char, mbstate_t>die für Sie von Nutzen sein kann. Das Verhalten ist jedoch systemspezifisch und garantiert in keiner Weise die Konvertierung auf UTF-8.

Chris Jester-Young
quelle
2
Das Codieren / Decodieren nach Gebietsschema ist eine schlechte Idee. So wie du gesagt hast: "garantiert nicht".
Tyler Long
@ TylerLong sollte man natürlich die std :: locale-Instanz speziell für die erforderliche Konvertierung konfigurieren.
Basilevs
@ Basilevs Ich denke immer noch, dass die Verwendung des Gebietsschemas zum Codieren / Decodieren falsch ist. Der richtige Weg ist, encodingstatt zu konfigurieren locale. Soweit ich das beurteilen kann, gibt es kein solches Gebietsschema, das jedes einzelne Unicode-Zeichen darstellen kann. Angenommen, ich möchte eine Zeichenfolge codieren, die alle Unicode-Zeichen enthält. Welches Gebietsschema schlagen Sie mir zur Konfiguration vor? Korrigiere mich, wenn ich falsch liege.
Tyler Long
@TylerLong Locale in C ++ ist ein sehr abstraktes Konzept, das weit mehr als nur regionale Einstellungen und Codierungen abdeckt. Grundsätzlich kann man damit alles machen. Während codecvt_facet in der Tat mehr als nur einfaches Rekodieren handhabt, hindert absolut nichts daran, einfache Unicode-Transformationen durchzuführen.
Basilevs
1

UTFConverter - Überprüfen Sie diese Bibliothek. Es führt eine solche Konvertierung durch, aber Sie benötigen auch die ConvertUTF-Klasse - ich habe sie hier gefunden

Trisch
quelle
0

Ich habe meine eigene Bibliothek für die Konvertierung von utf-8 in utf-16 / utf-32 erstellt - aber beschlossen, zu diesem Zweck einen Teil des vorhandenen Projekts zu erstellen.

https://github.com/tapika/cutf

(Entstanden von https://github.com/noct/cutf )

Die API funktioniert sowohl mit C als auch mit C ++.

Funktionsprototypen sehen folgendermaßen aus: (Eine vollständige Liste finden Sie unter 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

Beispielnutzung / einfache Testanwendung für utf-Konvertierungstests:

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

Und wenn diese Bibliothek Ihren Anforderungen nicht entspricht, können Sie den folgenden Link öffnen:

http://utf8everywhere.org/

Scrollen Sie am Ende der Seite nach unten und wählen Sie eine beliebige schwerere Bibliothek aus.

TarmoPikaro
quelle
-1

Ich glaube nicht, dass es einen tragbaren Weg gibt, dies zu tun. C ++ kennt die Codierung seiner Multibyte-Zeichen nicht.

Wie Chris vorgeschlagen hat, ist es am besten, mit Codecvt zu spielen.

Martin Cote
quelle
Die Frage lautet "UTF8", daher ist "die Codierung seiner Multibyte-Zeichen" bekannt.
Tyler Long