UTF8 до / з широкого перетворення символів у STL


76

Чи можна перетворити рядок UTF8 у std :: string у std :: wstring і навпаки незалежно від платформи? У програмі Windows я б використовував MultiByteToWideChar та WideCharToMultiByte. Однак код складений для декількох ОС, і я обмежуюсь стандартною бібліотекою C ++.


3
До речі, стандартна бібліотека C ++ не називається STL; STL - це лише невеликий підрозділ стандартної бібліотеки C ++. У цьому випадку я вважаю, що ви просите про функціональність у стандартній бібліотеці C ++, і я відповів відповідним чином.
Chris Jester-Young

6
Ви не вказали, з яким кодуванням ви хочете закінчити. wstring не вказує жодного конкретного кодування. Звичайно, було б природно перетворити на utf32 на платформах, де wchar_t має 4 байти в ширину, і utf16, якщо wchar_t - 2 байти. Це те, що ти хочеш?
jalf

1
@jalf Ваш коментар вводить в оману. std::wstringє std::basic_string<wchar_t>. wchar_tє непрозорим типом даних, який представляє символ Unicode (той факт, що в Windows він має 16 біт, означає лише те, що Windows не відповідає стандарту). Для абстрактних символів Unicode не існує "кодування", це лише символи.
kirelagin

Відповіді:


54

Я поставив це питання 5 років тому. Тоді ця тема мені дуже допомогла, я дійшов висновку, а потім перейшов до свого проекту. Смішно, що нещодавно мені потрібно було щось подібне, абсолютно не пов’язане з тим проектом минулого. Досліджуючи можливі рішення, я натрапив на власне запитання :)

Рішення, яке я обрав зараз, базується на C ++ 11. Бібліотеки, які Константин згадує у своїй відповіді , тепер є частиною стандарту. Якщо ми замінимо std :: wstring на новий тип рядка std :: u16string, то перетворення виглядатимуть так:

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

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

Як видно з інших відповідей, існує безліч підходів до проблеми. Тому я утримуюся від прийняття прийнятої відповіді.


wstring передбачає 2 або 4 байти замість однобайтових символів. Де питання, щоб перейти від кодування utf8?
Chawathe Vipul S

1
У мене є якась дивна погана продуктивність з codecvt, детальніше дивіться тут: stackoverflow.com/questions/26196686/…
Xtra Coder

2
Це UTF-16 з LE або BE?
thomthom

7
std :: wstring_convert застарілий у C ++ 17
HojjatJafary

1
@HojjatJafary, що таке заміна?
якар


23

Ви можете витягти utf8_codecvt_facetз бібліотеки Boost серіалізації .

Приклад їх використання:

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

Шукайте utf8_codecvt_facet.hppта utf8_codecvt_facet.cppфайли у джерелах підвищення.


Я хоч вам довелося просочити потік до того, як його відкрити, інакше проникнення ігнорується!
Мартін Йорк,

Мартіне, схоже, це працює з Visual Studio 2005: 0x41a успішно перетворено на послідовність UTF-8 {0xd0, 0x9a}.
Константин

22

У визначенні проблеми чітко зазначено, що 8-бітове кодування символів - UTF-8. Це робить це тривіальною проблемою; все, що йому потрібно, - це трохи обертання для перетворення з однієї специфікації UTF на іншу.

Просто подивіться на кодування на цих сторінках Вікіпедії для UTF-8 , UTF-16 та UTF-32 .

Принцип простий - пройдіться через вхід і зіберіть 32-бітну кодову точку Unicode відповідно до однієї специфікації UTF, а потім випустіть кодову точку відповідно до іншої специфікації. Окремі кодові точки не потребують перекладу, як це потрібно для будь-якого іншого кодування символів; ось що робить цю просту проблему.

Ось швидка реалізація перетворення wchar_tв UTF-8 і навпаки. Він передбачає, що вхідні дані вже правильно закодовані - тут застосовується стара приказка "Сміття всередину, сміття винесене". Я вважаю, що перевірку кодування найкраще робити окремим кроком.

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

Вище код працює як UTF-16 і UTF-32 введення, просто тому , що діапазон d800через dfffнеприпустимі кодові точки; вони вказують на те, що ви декодуєте UTF-16. Якщо ви знаєте, що wchar_tце 32 біти, ви можете видалити деякий код, щоб оптимізувати функцію.

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

Знову ж таки, якщо ви знаєте, що wchar_tце 32 біти, ви можете видалити деякий код із цієї функції, але в цьому випадку це не повинно мати жодної різниці. Вираз sizeof(wchar_t) > 2відомий під час компіляції, тому будь-який гідний компілятор розпізнає мертвий код і видалить його.


Я не бачу, щоб він щось говорив про std :: string, що містить кодовані рядки UTF-8, в оригінальному запитанні: "Чи можна перетворити std :: string в std :: wstring і навпаки незалежно від платформи?"
Неманья Трифунович

1
UTF-8 вказано в назві допису. Ви праві, що його немає в тексті.
Марк Ренсом

6
Але `` widechar '' не обов'язково означає UTF16
moogs

6
Те, що ви отримали, може бути хорошим "доказом концепції". Одна справа успішно перетворити дійсне кодування. Це ще один рівень зусиль, щоб правильно обробити перетворення недійсних даних кодування (наприклад, неспарені сурогати в UTF-16) відповідно до специфікацій. Для цього вам дійсно потрібен більш ретельно розроблений і перевірений код.
Крейг МакКвін

2
@Craig McQueen, ти абсолютно прав. Я зробив припущення, що кодування вже було правильним, і це було просто механічне перетворення. Я впевнений, що бувають ситуації, коли це так, і цей код був би адекватним - але обмеження повинні бути вказані чітко. З оригінального запитання не зрозуміло, чи має це викликати занепокоєння чи ні.
Марк Ренсом,

13

Існує кілька способів зробити це, але результати залежать від того, яке кодування символів є у змінних stringand wstring.

Якщо ви знаєте, що stringтаке ASCII, ви можете просто використати wstringконструктор ітераторів ':

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

stringОднак якщо у вас є якесь інше кодування, ви отримаєте дуже погані результати. Якщо кодування є Unicode, ви можете поглянути на проект ICU , який забезпечує міжплатформенний набір бібліотек, які перетворюють на та з усіх видів кодування Unicode.

Якщо ваш stringвміст містить символи на кодовій сторінці, тоді $ DEITY може помилувати вашу душу.


4
ICU також перетворює / з кожного кодування символів, яке я коли-небудь стикався. Це величезне.
Мартін Йорк,


2

Ви можете використовувати codecvtфасет локалі . Визначено конкретну спеціалізацію, codecvt<wchar_t, char, mbstate_t>яка може бути вам корисною, хоча поведінка цієї системи є специфічною і жодним чином не гарантує перетворення на UTF-8.


2
Кодування / декодування відповідно до мови є поганою ідеєю. Так само, як ви сказали: "не гарантує".
Тайлер Лонг,

@TylerLong, очевидно, слід налаштувати екземпляр std :: locale спеціально для необхідного перетворення.
Басилевс

@Basilevs Я все ще вважаю, що використання локалі для кодування / декодування є неправильним. Правильний спосіб - це налаштування encodingзамість locale. Наскільки я можу зрозуміти, не існує такої локалі, яка могла б представляти кожен окремий символ Unicode. Скажімо, я хочу кодувати рядок, що містить усі символи Unicode, яку локаль ви пропонуєте мені налаштувати? Побачте мене, якщо я помиляюся.
Тайлер Лонг

@TylerLong Locale в C ++ - це дуже абстрактне поняття, яке охоплює набагато більше речей, ніж просто регіональні налаштування та кодування. В основному можна. Робити все з цим. Хоча codecvt_facet справді обробляє більше, ніж просто перекодування, абсолютно ніщо не заважає йому робити прості перетворення Unicode.
Basilevs

1

UTFConverter - перевірте цю бібліотеку. Він робить таку конвертацію, але вам потрібен також клас ConvertUTF - я знайшов його тут


0

Створив власну бібліотеку для перетворення utf-8 у utf-16 / utf-32 - але вирішив зробити для цього форк існуючого проекту.

https://github.com/tapika/cutf

(Виник з https://github.com/noct/cutf )

API працює як із звичайним C, так і з C ++.

Прототипи функцій виглядають так: (Повний список див. 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

Зразок використання / простий тестовий додаток для тестування перетворення utf:

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

І якщо ця бібліотека не відповідає вашим потребам, сміливо відкривайте таке посилання:

http://utf8everywhere.org/

і прокрутіть униз у кінці сторінки та виберіть будь-яку важчу бібліотеку, яка вам подобається.


-1

Я не думаю, що існує портативний спосіб зробити це. C ++ не знає кодування своїх багатобайтових символів.

Як запропонував Кріс, найкращим варіантом є грати з codecvt.


Питання говорить "UTF8", тому "кодування його багатобайтових символів" відомо.
Тайлер Лонг,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.