UTF8 to/from wide char conversion in STL

后端 未结 10 719
面向向阳花
面向向阳花 2020-11-22 06:48

Is it possible to convert UTF8 string in a std::string to std::wstring and vice versa in a platform independent manner? In a Windows application I would use MultiByteToWideC

相关标签:
10条回答
  • 2020-11-22 07:03

    I don't think there's a portable way of doing this. C++ doesn't know the encoding of its multibyte characters.

    As Chris suggested, your best bet is to play with codecvt.

    0 讨论(0)
  • 2020-11-22 07:05

    Created my own library for utf-8 to utf-16/utf-32 conversion - but decided to make a fork of existing project for that purpose.

    https://github.com/tapika/cutf

    (Originated from https://github.com/noct/cutf )

    API works with plain C as well as with C++.

    Function prototypes looks like this: (For full list see 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
    

    Sample usage / simple test application for utf conversion testing:

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

    And if this library does not satisfy your needs - feel free to open following link:

    http://utf8everywhere.org/

    and scroll down at the end of page and pick up any heavier library which you like.

    0 讨论(0)
  • 2020-11-22 07:07

    The problem definition explicitly states that the 8-bit character encoding is UTF-8. That makes this a trivial problem; all it requires is a little bit-twiddling to convert from one UTF spec to another.

    Just look at the encodings on these Wikipedia pages for UTF-8, UTF-16, and UTF-32.

    The principle is simple - go through the input and assemble a 32-bit Unicode code point according to one UTF spec, then emit the code point according to the other spec. The individual code points need no translation, as would be required with any other character encoding; that's what makes this a simple problem.

    Here's a quick implementation of wchar_t to UTF-8 conversion and vice versa. It assumes that the input is already properly encoded - the old saying "Garbage in, garbage out" applies here. I believe that verifying the encoding is best done as a separate step.

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

    The above code works for both UTF-16 and UTF-32 input, simply because the range d800 through dfff are invalid code points; they indicate that you're decoding UTF-16. If you know that wchar_t is 32 bits then you could remove some code to optimize the function.

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

    Again if you know that wchar_t is 32 bits you could remove some code from this function, but in this case it shouldn't make any difference. The expression sizeof(wchar_t) > 2 is known at compile time, so any decent compiler will recognize dead code and remove it.

    0 讨论(0)
  • 2020-11-22 07:08

    UTFConverter - check out this library. It does such a convertion, but you need also ConvertUTF class - I've found it here

    0 讨论(0)
  • 2020-11-22 07:12

    UTF8-CPP: UTF-8 with C++ in a Portable Way

    0 讨论(0)
  • 2020-11-22 07:12

    You can extract utf8_codecvt_facet from Boost serialization library.

    Their usage example:

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

    Look for utf8_codecvt_facet.hpp and utf8_codecvt_facet.cpp files in boost sources.

    0 讨论(0)
提交回复
热议问题