Почему преобразование кодировки символов должно быть таким сложным в c ++? - PullRequest
3 голосов
/ 17 октября 2019

В качестве предисловия я не сомневаюсь, что мои знания Unicode и кодировки символов неполны. Извините за любые искажения.

Недавно я разрабатывал приложение, которое будет обрабатывать входящие данные в различных кодировках. Это, например, UTF-8, ASCII, SHIFT-JIS и т. Д.

Теперь я понимаю, что для данного кодирования существует «отображение» между ним и данным значением Unicode. Возьмем, к примеру, символ «あ».

Значение Юникода будет U + 3042, значение UTF-8 будет \ xe3 \ x81 \ x82, и, наконец, значение SHIFT-JIS будет \ x82 \ xa0.

Все они могут быть непосредственно представлены в виде потока байтов с прямым отображением, однако преобразование между ними представляется сложной задачей.

С таким языком, как Python, ниже может бытьИспользуется:

utf8_val = "あ"
unicode_val = utf8_val.decode("utf-8")
sjis_val = unicode_val.encode("shift-jis", "ignore")

Я понимаю, что ICU или Boost.Locale могут достичь аналогичных результатов, используя функции to_utf и from_utf, однако, почему не существует какого-либо базового способа добиться этого с помощью стандартногобиблиотека?

Похоже, что преобразования преобразования существуют в Microsoft STL (https://github.com/microsoft/STL/tree/master/stl/inc/cvt), однако без реальной документации о них неясно, как их использовать.

НаконецПохоже, что этого можно достичь с помощью библиотеки <locale>, однако в документации нет четких ответов о том, как это сделать. Мое лучшее предположение - сделать с codecvt :: in и codecvt. :: out однако даже здесь неясно, как правильно подходить.

Я просто что-то пропускаю? Или преобразование действительно так сложно?

edit: я попытался выполнить базовое преобразование char-> unicode, используя документацию из MS (здесь) :

std::string conv(std::string str, std::string enc)
{
    std::locale loc(enc.c_str());
    std::string dest;
    dest.resize(str.length() * 2);

    char* pnewNext;
    const char* porigNext;
    std::mbstate_t mbstate;

    auto res = std::use_facet<std::codecvt<char, char, std::mbstate_t>>
               (loc).out(mbstate,
                         &str[0], &str[str.size()-1], porigNext,
                         &dest[0], &dest[dest.length()-1], pnewNext);

    std::cout << "errcode : " << res << std::endl;
    for(const auto& c : str)
    {
        std::cout << std::hex << std::setw(2) << std::setfill('0') << (uint)(unsigned char)c << std::endl;
    }
    for(const auto& c : dest)
    {
        std::cout << std::hex << std::setw(2) << std::setfill('0') << (uint)(unsigned char)c << std::endl;
    }
    return dest;
}

К сожалению, этот подход все еще кажется неправильным:

errcode : 3 // this is std::codecvt_base::noconv

33 //these are the input bytes
44

00 //these are the output bytes
00
00
00

Для тех, кто может столкнуться с этим, я написал (плохой) алгоритм преобразования UTF-8 в Unicode. Это определенно можно улучшить (как по производительности, так и по размеру кода). Несмотря на это, он успешно конвертируется (без каких-либо реальных проверок - будьте предупреждены).

std::size_t ucsz(char c)
{
    std::size_t count = 0;
    for (std::size_t i = 7; i > 0; --i)
    {
        if (((c >> i) & 1) == 0)
        {
            return std::max(static_cast<int>(count), 1);
        }
        ++count;
    }
    return count;
}

std::string utf8_to_unicode(std::string utf8)
{
    std::string unicode;
    for (std::size_t i = 0; i < utf8.length();)
    {
        auto length = ucsz(utf8[i]);
        switch (length)
        {
        case 4:
        {
            std::uint32_t val = 0;
            for (std::size_t j = 0; j < 21; ++j)
            {
                auto idx = std::max(0, (int)(i + (3 - (j / 6))));
                val |= ((utf8[idx] >> (j % 6)) & 1U) ? (1UL << j) : 0;
            }
            unicode.push_back(val >> 16);
            unicode.push_back(val >> 8);
            unicode.push_back(val >> 0);
            ++i;
            ++i;
            ++i;
            break;
        }
        case 3:
        {
            std::uint32_t val = 0;
            for (std::size_t j = 0; j < 16; ++j)
            {
                auto idx = std::max(0, (int)(i + (2 - (j / 6))));
                val |= ((utf8[idx] >> (j % 6)) & 1U) ? (1UL << j) : 0;
            }
            unicode.push_back(val >> 8);
            unicode.push_back(val >> 0);
            ++i;
            ++i;
            break;
        }
        case 2:
        {
            std::uint16_t val = 0;
            for (std::size_t j = 0; j < 11; ++j)
            {
                auto idx = std::max(0, (int)(i + (1 - (j / 6))));
                val |= ((utf8[idx] >> (j % 6)) & 1U) ? (1UL << j) : 0;
            }
            unicode.push_back(val >> 8);
            unicode.push_back(val >> 0);
            ++i;
            break;
        }
        case 1:
        {
            unicode.push_back(utf8[i]);
            break;
        }
        default:
        {
            break;
        }
        }
        ++i;
    }
    return unicode;
}
...