Как превратить std::string, который содержит закодированный текст utf-16, в utf-16 wstring?

Итак, мы получаем строку типа Новая папка, которая представляет собой utf-8 представление строки в кодировке utf-16 (Новая папка в utf-16), мы хотим превратить эту строку в wstring, не меняя кодировку.. что означает буквально перенести все данные из строки в wstring без любое преобразование. Таким образом, мы получим wstring с содержимым Новая папка. Как сделать такое?

Обновление: Я хотел сказать, что у нас есть все данные для правильной строки utf-16 внутри строки. Все, что нам нужно, это поместить эти данные в wstring... это означает, что если wstring содержит wchar, который может быть 0000, нам нужно будет поместить 2 строковых символа 00 и 00 вместе, чтобы получить их. Это то, что я не знаю, как это сделать.

Update2 Как я сюда попал: библиотека C++, которую я обязан использовать на своем сервере, представляет собой анализатор в стиле C. и он возвращает мне адрес запроса пользователя как std::string. пока я заставляю своих клиентов присылать мне запросы в таком формате.

url_encode(UTF16toUTF8(wstring)) //pseudocode.

где

string UTF16toUTF8(const wstring & in)
{
    string out;
    unsigned int codepoint;
    bool completecode = false;
    for (wstring::const_iterator p = in.begin();  p != in.end();  ++p)
    {
        if (*p >= 0xd800 && *p <= 0xdbff)
        {
            codepoint = ((*p - 0xd800) << 10) + 0x10000;
            completecode = false;
        }
        else if (!completecode && *p >= 0xdc00 && *p <= 0xdfff)
        {
            codepoint |= *p - 0xdc00;
            completecode = true;
        }
        else
        {
            codepoint = *p;
            completecode = true;
        }
        if (completecode)
        {
            if (codepoint <= 0x7f)
                out.push_back(codepoint);
            else if (codepoint <= 0x7ff)
            {
                out.push_back(0xc0 | ((codepoint >> 6) & 0x1f));
                out.push_back(0x80 | (codepoint & 0x3f));
            }
            else if (codepoint <= 0xffff)
            {
                out.push_back(0xe0 | ((codepoint >> 12) & 0x0f));
                out.push_back(0x80 | ((codepoint >> 6) & 0x3f));
                out.push_back(0x80 | (codepoint & 0x3f));
            }
            else
            {
                out.push_back(0xf0 | ((codepoint >> 18) & 0x07));
                out.push_back(0x80 | ((codepoint >> 12) & 0x3f));
                out.push_back(0x80 | ((codepoint >> 6) & 0x3f));
                out.push_back(0x80 | (codepoint & 0x3f));
            }
        }
    }
    return out;
}

std::string url_encode( std::string sSrc )
{
    const char SAFE[256] =
    {
        /*      0 1 2 3  4 5 6 7  8 9 A B  C D E F */
        /* 0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* 1 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* 2 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* 3 */ 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,

        /* 4 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
        /* 5 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
        /* 6 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
        /* 7 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,

        /* 8 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* 9 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* A */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* B */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,

        /* C */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* D */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* E */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* F */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
    };
    const char DEC2HEX[16 + 1] = "0123456789ABCDEF";
    const unsigned char * pSrc = (const unsigned char *)sSrc.c_str();
    const int SRC_LEN = sSrc.length();
    unsigned char * const pStart = new unsigned char[SRC_LEN * 3];
    unsigned char * pEnd = pStart;
    const unsigned char * const SRC_END = pSrc + SRC_LEN;

    for (; pSrc < SRC_END; ++pSrc)
    {
        if (SAFE[*pSrc]) 
            *pEnd++ = *pSrc;
        else
        {
            // escape this char
            *pEnd++ = '%';
            *pEnd++ = DEC2HEX[*pSrc >> 4];
            *pEnd++ = DEC2HEX[*pSrc & 0x0F];
        }
    }

    std::string sResult((char *)pStart, (char *)pEnd);
    delete [] pStart;
    return sResult;
}

std::string url_decode( std::string sSrc )
{
    // Note from RFC1630:  "Sequences which start with a percent sign
    // but are not followed by two hexadecimal characters (0-9, A-F) are reserved
    // for future extension"

    const char HEX2DEC[256] = 
    {
        /*       0  1  2  3   4  5  6  7   8  9  A  B   C  D  E  F */
        /* 0 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 1 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 2 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 3 */  0, 1, 2, 3,  4, 5, 6, 7,  8, 9,-1,-1, -1,-1,-1,-1,

        /* 4 */ -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 5 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 6 */ -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 7 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,

        /* 8 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 9 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* A */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* B */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,

        /* C */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* D */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* E */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* F */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
    };

    const unsigned char * pSrc = (const unsigned char *)sSrc.c_str();
    const int SRC_LEN = sSrc.length();
    const unsigned char * const SRC_END = pSrc + SRC_LEN;
    const unsigned char * const SRC_LAST_DEC = SRC_END - 2;   // last decodable '%' 

    char * const pStart = new char[SRC_LEN];
    char * pEnd = pStart;

    while (pSrc < SRC_LAST_DEC)
    {
        if (*pSrc == '%')
        {
            char dec1, dec2;
            if (-1 != (dec1 = HEX2DEC[*(pSrc + 1)])
                && -1 != (dec2 = HEX2DEC[*(pSrc + 2)]))
            {
                *pEnd++ = (dec1 << 4) + dec2;
                pSrc += 3;
                continue;
            }
        }

        *pEnd++ = *pSrc++;
    }

    // the last 2- chars
    while (pSrc < SRC_END)
        *pEnd++ = *pSrc++;

    std::string sResult(pStart, pEnd);
    delete [] pStart;
    return sResult;
}

Конечно, я вызываю url_decode, но получаю строку..( так что я надеюсь, что теперь моя проблема более ясна.


person Rella    schedule 26.08.2011    source источник
comment
возможный дубликат как преобразовать UTF-8 std:: строка в UTF-16 std::wstring   -  person Nicol Bolas    schedule 27.08.2011
comment
@Kabumbus: старший байт или младший байт стоит первым в вашей строке? Если у вас есть 12 34 в вашей строке, вы ожидаете получить 1234 или 3412 в своей wstring?   -  person john    schedule 27.08.2011
comment
@Kalumbus: теперь я полностью запутался. Я снова передумал и думаю, что вы действительно хотите преобразовать UTF-8 в UTF-16, а не просто выполнять сдвиг байтов. Но кто знает. Если вам нужно UTF-8 для UTF-16, ссылка, которую разместил Никол Болас выше, будет работать.   -  person john    schedule 27.08.2011
comment
-1 действительно непонятно, что спросили.   -  person Cheers and hth. - Alf    schedule 27.08.2011
comment
@Kalumbus, можете ли вы опубликовать пример того, что вы хотите, используя значения байтов и слов вместо использования иностранных символов, которые просто очень сбивают с толку.   -  person john    schedule 27.08.2011
comment
Не могли бы вы выложить бинарный дамп вашего файла (или хотя бы небольшой его части)? Вы можете легко создать такой дамп с помощью xxd. Нам было бы легче понять, сколько раз было выполнено неправильное преобразование.   -  person Sylvain Defresne    schedule 27.08.2011


Ответы (2)


Вот что я возился с решением вашей проблемы:

std::string wrong("Новая папка");
std::wstring correct( (wchar_t*)wrong.data() );

Согласно http://www.cplusplus.com/reference/string/string/data/ функция-член data() должна предоставить нам необработанный char*, и простое приведение к (wchar_t*) должно заставить его склеить 00 и 00 вместе, чтобы получить 0000, как вы описываете в своем примере.

Мне лично не нравится такой кастинг, но пока это все, что я придумал.

Изменить. Какую библиотеку вы используете? Есть ли у него какая-то другая функция, чтобы обратить вспять то, что он сделал?

Если это популярно, наверняка у кого-то еще была эта проблема раньше. Как Они это решили?

Редактировать 2. Вот отвратительный способ, использующий malloc, некоторые предположения о том, что в исходной строке не будет половинных кодовых точек, и еще одно ужасное приведение. :(

std::string wrong("Новая папка");
wchar_t *lesswrong = (wchar_t*) malloc (wrong.size()/sizeof(wchar_t) + sizeof(wchar_t));
lesswrong = (wchar_t*)wrong.data();
lesswrong[wrong.size()] = '\0';
std::wstring correct( lesswrong );

Это никак не может быть правильным. Даже если это работает, это так уродливо.

Редактировать 3. Как и Керрик Сади, это лучший способ сделать это.

std::string wrong("Новая папка");
std::wstring correct( (wchar_t*)wrong.data(), wrong.size()/2 );
person Sqeaky    schedule 26.08.2011
comment
Возможно, вам также придется передать здесь аргумент размера, потому что data() не дает строку (wchar-) с нулевым завершением. - person Kerrek SB; 27.08.2011
comment
Это не сработает, если sizeof(wchar_t) не равно 2. В Mac OS X sizeof(wchar_t) равно 4 для 64-битных программ. - person Sylvain Defresne; 27.08.2011
comment
Будет работать только в том случае, если порядок байтов правильный, плюс у вас отсутствует нулевой терминатор, о котором упоминает Керрек. - person john; 27.08.2011
comment
У вас все в порядке, я полностью проигнорировал отсутствие нулевого терминатора :( У меня есть другая, но все же отвратительная идея - person Sqeaky; 27.08.2011
comment
Это предполагает, что данные были закодированы на этой машине, поэтому порядок байтов должен совпадать, но вы правы, на машине unix это заполнило бы 2 кодовые точки utf16 в одну кодовую точку utf32. Я сделал еще одно предположение, предполагая, что библиотека, которую он использует, будет использовать тот же размер для wchar_t, что и этот код (похоже, я допускаю натяжку). - person Sqeaky; 27.08.2011
comment
@Sqeaky: std::wstring correct( (wchar_t*)wrong.data(), wrong.size() ); определенно неверно. Конструктору требуется количество символов, но wrong.size() дает количество байтов. - person ildjarn; 27.08.2011

Если я вас правильно понял, у вас есть объект std::string, содержащий строку в кодировке UTF-16, и вы хотите преобразовать его в std::wstring без изменения кодировки. Если я прав, вам не нужно преобразовывать ни кодировку, ни представление, а только хранилище.

Вы также думаете, что строка может быть неправильно закодирована в UTF-8. Однако UTF-8 — это кодировка переменной длины, но длина ваших неправильно интерпретируемых данных (длина 22 символа) ровно в два раза превышает длину ваших исходных данных ( Новая папка состоит из 11 символов). Вот почему я подозреваю, что это может быть просто случай неправильного хранения, а не неправильного кодирования.

Следующий код делает это:

std::wstring convert_utf16_string_to_wstring(const std::string& input) {
    assert((input.size() & 1) == 0);
    size_t len = input.size() / 2;
    std::wstring output;
    output.resize(len);

    for (size_t i = 0; i < len; ++i) {
        unsigned char chr1 = (unsigned char)input[2 * i];
        unsigned char chr2 = (unsigned char)input[2 * i + 1];

        // Note: this line suppose that you use `UTF-16-BE` both for
        // the std::string and the std::wstring. You'll have to swap
        // chr1 & chr2 if this is not the case.
        unsigned short val = (chr2 << 8)|(chr1);
        output[i] = (wchar_t)(val);
    }

    return output;
}

Если вы знаете, что на всех платформах вы ориентируетесь на sizeof(wchar_t), равное 2 (это не относится к Mac OS для 64-битных программ, например, где sizeof(wchar_t) равно 4), то вы можете использовать простое приведение:

std::wstring convert_utf16_string_to_wstring(const std::string& input) {
    assert(sizeof(wchar_t) == 2); // A static assert would be better here
    assert((input.size() & 1) == 0);
    return input.empty()
        ? std::wstring()
        : std::wstring((wchar_t*)input[0], input.size() / 2);
}
person Sylvain Defresne    schedule 26.08.2011
comment
Это не преобразование из UTF-8 в UTF-16, а просто преобразование из байтового представления UTF-16 в представление слова. - person bdonlan; 27.08.2011
comment
ну, я получаю ⿯뾍껯뾢ꃯ뾿⃯뾯ꃯ뾯ꫯ뾠, что больше похоже на настоящие буквы, но что-то не так..( - person Rella; 27.08.2011
comment
@bdonlan: Мне совсем не ясно, что OP действительно хочет преобразования UTF-8 в UTF-16. Я дважды передумал по этому поводу. - person john; 27.08.2011
comment
@bdonlan: этот OP не требует преобразования из UTF-16 в UTF-8. У него есть std::string, содержащий данные, закодированные в UTF-16, и он хочет, чтобы эти данные хранились в std::wstring. - person Sylvain Defresne; 27.08.2011
comment
@Kabumbus: Извините, я перевернул chr1 и chr2 в одной из строк. Я отредактировал свой пост, и я ожидаю, что это правильная версия. Кстати, если sizeof(wchar_t) == 2, то вы, вероятно, можете просто сделать memcpy(&output[0], &input[0], input.size()) вместо цикла. - person Sylvain Defresne; 27.08.2011
comment
ОП говорит: «Итак, мы получаем строку типа rќРѕРІР°СЏ їР°РїРєР°, которая представляет собой utf-8 представление [...] - person bdonlan; 27.08.2011
comment
@bdonlan: ну, если я возьму исходную строку Новая папка, закодированную в utf-16, интерпретирую ее как кодировку iso-8859-5 (кажется русская) и закодирую в utf- 8, я получаю нечто совершенно отличное от Новая папка. Более того, в строке Новая папка 22 символа, а в исходной строке Новая папка всего 11, что наводит на мысль, что преобразования в utf-8 не было. (который является кодировкой переменной длины). - person Sylvain Defresne; 27.08.2011