Правильный способ печати широких строк на терминалах с разными кодировками

Ниже я постараюсь напечатать строку (латинский "ex", кириллический "ya" и финикийский "teth") к терминалам с различными кодировками, а именно utf8, cp1251 и C (POSIX). Я ожидаю увидеть в терминале utf8, XЯ? в терминале cp1251 и X?? в терминале C (POSIX). Вопросительные знаки объясняются тем, что выходная библиотека C++ заменяет символы, которые она не может представить ?, Это правильное и ожидаемое поведение.

(1) Моя первая наивная попытка была просто напечатать строку широких символов в wcout:

wchar_t str[] = L"\U00000058\U0000042f\U00010908";
std::wcout << str << std::endl;
// utf8 terminal output: X??
// cp1251: X??
// C: X??

Во всех терминалах он правильно печатал только первый символ ascii7. Другие символы были заменены на "?" Метки. Оказалось, что это произошло потому, что во время запуска программы LC_ALL установлен на C.

(2) Вторая попытка была вызвать вручную std::setlocale() с кодировкой utf8:

wchar_t str[] = L"\U00000058\U0000042f\U00010908";
std::setlocale(LC_ALL, "en_US.UTF-8");
std::wcout << str << std::endl;
// utf8: XЯ
// cp1251: XЯ𐤈
// C: XЯð¤

Очевидно, это работало правильно в терминале utf8, но приводило к мусору в двух других терминалах.

(3) Третья попытка была разобрать $LANG Переменная окружения для фактического кодирования, используемого терминалом (и надеюсь, что все части терминала используют одинаковое кодирование):

const char* lang = std::getenv("LANG");
if (!lang) {
  std::cerr << "Couldn't get LANG" << std::endl;
  exit(1);
}

wchar_t str[] = L"\U00000058\U0000042f\U00010908";
std::setlocale(LC_ALL, lang);
std::wcout << str << std::endl;
// utf8: XЯ
// cp1251: XЯ?
// C: X??

Теперь выход во всех трех терминалах был, как я ожидал. Тем не менее, смешивание std::cout а также std::wcout плохая идея, и std::cout определенно используется некоторыми сторонними библиотеками, используемыми в моей программе. Это делает std::wcout непригодным для использования.

(4) Итак, четвертая попытка (или, собственно, идея) состояла в том, чтобы обнаружить кодирование терминала из $LANGиспользовать codevct() преобразовать wchar_t[] введите в кодировку терминала и распечатайте его обычным способом std::cout.write(), К сожалению, я не смог найти способ явно установить целевую кодировку для codevct(),

(5) Пятая и пока самая лучшая попытка заключалась в использовании iconv() вручную:

// get $LANG env var
const char* lang = std::getenv("LANG");
if (!lang) {
  std::cerr << "Couldn't get $LANG" << std::endl;
  exit(1);
}

// find out encoding from $LANG, e.g. "utf8", "cp1251", etc
std::string enc(lang);
size_t pos = enc.rfind('.');
if (pos != std::string::npos) {
  enc = enc.substr(pos + 1);
}
if (enc == "C" || enc == "POSIX") {
  enc = "iso8859-1";
}

// convert wchar_t[] string into terminal encoding
wchar_t str[] = L"\U00000058\U0000042f\U00010908";
iconv_t handler = iconv_open(enc.c_str(), "UTF32LE");
if (handler == (iconv_t)-1) {
  std::cerr << "Couldn't create iconv handler: " << strerror(errno) << std::endl;
  exit(1);
}

char buf[1024];

char* inbuf = (char*)str;
size_t inbytes = sizeof(str);
char* outbuf = buf;
size_t outbytes = sizeof(buf);

while (true) {
  size_t res = iconv(handler, &inbuf, &inbytes, &outbuf, &outbytes);
  if (res != (size_t)-1) {
    break;
  }
  if (errno == EILSEQ) {
    // replace non-convertable code point with question mark and retry iconv()
    inbuf[0] = '\x3f';
    inbuf[1] = '\x00';
    inbuf[2] = '\x00';
    inbuf[3] = '\x00';
  } else {
    std::cerr << "iconv() failed: %s" << strerror(errno) << std::endl;
    exit(1);
  }
}
iconv_close(handler);

// write converted string to std::cout
std::cout.write(buf, sizeof(buf) - outbytes);
std::cout << std::endl;
// utf8: XЯ
// cp1251: XЯ?
// C: X??

Это работало правильно во всех трех терминалах. И теперь я тоже не боюсь, что std::cout используется в других частях программы. Однако я нахожу это решение не C++- способом.

Итак, вопрос в том, как правильно печатать широкие строки в C++? Я был бы в порядке с платформно-ориентированным решением (Linux + glibc + GCC).

0 ответов

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