Поставлен в тупик с Unicode, Boost, C++, codecvts

В C++ я хочу использовать Unicode, чтобы делать вещи. Так что, упав в кроличью нору Unicode, мне удалось оказаться в железнодорожной катастрофе путаницы, головных болей и мест.

Но в Boost у меня возникла неудачная проблема: пытаться использовать пути к файлам Unicode и пытаться использовать библиотеку опций программы Boost с вводом Unicode. Я прочитал все, что я мог найти на предметах локалей, codecvts, кодировок Unicode и Boost.

Моя текущая попытка заставить вещи работать - иметь codecvt, который принимает строку UTF-8 и преобразует ее в кодировку платформы (UTF-8 в POSIX, UTF-16 в Windows), я пытался избежать wchar_t,

Самое близкое, что я на самом деле получил, - это попытаться сделать это с Boost.Locale, чтобы преобразовать строку UTF-8 в строку UTF-32 на выходе.

#include <string>
#include <boost/locale.hpp>
#include <locale>

int main(void)
{
  std::string data("Testing, 㤹");

  std::locale fromLoc = boost::locale::generator().generate("en_US.UTF-8");
  std::locale toLoc   = boost::locale::generator().generate("en_US.UTF-32");

  typedef std::codecvt<wchar_t, char, mbstate_t> cvtType;
  cvtType const* toCvt = &std::use_facet<cvtType>(toLoc);

  std::locale convLoc = std::locale(fromLoc, toCvt);

  std::cout.imbue(convLoc);
  std::cout << data << std::endl;

  // Output is unconverted -- what?

  return 0;
}

Я думаю, что у меня был какой-то другой вид конверсии, работающий с использованием широких символов, но я действительно не знаю, что я вообще делаю. Я не знаю, какой инструмент лучше всего подходит для этой работы. Помогите?

3 ответа

Решение

Хорошо, после долгих нескольких месяцев я понял это, и я хотел бы помочь людям в будущем.

Прежде всего, Codecvt был неправильным способом сделать это. Boost.Locale предоставляет простой способ преобразования между наборами символов в его пространстве имен boost::locale::conv. Вот один пример (есть другие, не основанные на локалях).

#include <boost/locale.hpp>
namespace loc = boost::locale;

int main(void)
{
  loc::generator gen;
  std::locale blah = gen.generate("en_US.utf-32");

  std::string UTF8String = "Tésting!";
  // from_utf will also work with wide strings as it uses the character size
  // to detect the encoding.
  std::string converted = loc::conv::from_utf(UTF8String, blah);

  // Outputs a UTF-32 string.
  std::cout << converted << std::endl;

  return 0;
}

Как вы можете видеть, если вы замените "en_US.utf-32" на "", он будет выводиться в локали пользователя.

Я до сих пор не знаю, как заставить std::cout делать это все время, но функция translate() Boost.Locale выводит в локали пользователя.

Что касается файловой системы, использующей кроссплатформенность строк UTF-8, кажется, что это возможно, вот ссылка на то, как это сделать.

Классы замены iostream файловой системы Boost отлично работают с UTF-16 при использовании с Visual C++.

Однако они не работают (в смысле поддержки произвольных имен файлов) при использовании с g++ в Windows - по крайней мере, в версии Boost 1.47. Есть комментарий к коду, объясняющий это; по сути, стандартная библиотека Visual C++ предоставляет нестандартные wchar_t основанные конструкторы, которые используют классы файловой системы Boost, но g++ не поддерживает эти расширения.

Обходной путь - использовать 8.3 коротких имен файлов, но это решение немного хрупко, поскольку в старых версиях Windows пользователь может отключить автоматическую генерацию коротких имен файлов.


Пример кода для использования файловой системы Boost в Windows:

#include "CmdLineArgs.h"        // CmdLineArgs
#include "throwx.h"             // throwX, hopefully
#include "string_conversions.h" // ansiOrFillerFrom( wstring )

#include <boost/filesystem/fstream.hpp>     // boost::filesystem::ifstream
#include <iostream>             // std::cout, std::cerr, std::endl
#include <stdexcept>            // std::runtime_error, std::exception
#include <string>               // std::string
#include <stdlib.h>             // EXIT_SUCCESS, EXIT_FAILURE
using namespace std;
namespace bfs = boost::filesystem;

inline string ansi( wstring const& ws ) { return ansiWithFillersFrom( ws ); }

int main()
{
    try
    {
        CmdLineArgs const   args;
        wstring const       programPath     = args.at( 0 );

        hopefully( args.nArgs() == 2 )
            || throwX( "Usage: " + ansi( programPath ) + " FILENAME" );

        wstring const       filePath        = args.at( 1 );
        bfs::ifstream       stream( filePath );     // Nice Boost ifstream subclass.
        hopefully( !stream.fail() )
            || throwX( "Failed to open file '" + ansi( filePath ) + "'" );

        string line;
        while( getline( stream, line ) )
        {
            cout << line << endl;
        }
        hopefully( stream.eof() )
            || throwX( "Failed to list contents of file '" + ansi( filePath ) + "'" );

        return EXIT_SUCCESS;
    }
    catch( exception const& x )
    {
        cerr << "!" << x.what() << endl;
    }
    return EXIT_FAILURE;
}
  std::cout.imbue(convLoc);
  std::cout << data << std::endl;

Это не конвертация, так как он использует codecvt<char, char, mbstate_t>который не работает. Единственными стандартными потоками, которые используют codecvt, являются файловые потоки. std::cout не требуется для выполнения какого-либо преобразования вообще.

Чтобы заставить Boost.Filesystem интерпретировать узкие строки как UTF-8 в Windows, используйтеboost::filesystem::imbue с локалью с кодовым аспектом UTF-8 ↔ UTF-16. Boost.Locale имеет реализацию последнего.

Другие вопросы по тегам