Отображение больших строк в кодировке UTF-8 для стандартного вывода прилично, несмотря на ошибки Windows или MinGW - PullRequest
0 голосов
/ 24 сентября 2019

2-е обновление: Я нашел очень простое решение , что на самом деле это не такая сложная проблема, всего один день после запроса.Но люди кажутся недалекими, так что уже есть три близких голоса:

  1. Дубликат "Как использовать символы Юникода в командной строке Windows?"(1x) :

    Очевидно, нет, что было разъяснено в комментариях.Речь идет не об инструменте командной строки Windows, которым я не пользуюсь.

  2. Непонятно, о чем вы спрашиваете (1x) :

    Тогда вы должны страдать от функционального аналфавитизма .Я не могу быть более конкретным, когда спрашиваю, например: « Есть ли простой способ определить, является ли символ в std :: string неконечной частью символа UTF-8? » (помечены жирным шрифтом для лучшей видимости, действительно) и утверждают, что этого будет достаточно, чтобы ответить на вопрос (и даже объяснить, почему).Серьезно, есть даже картинки, чтобы показать проблему.Кроме того, мой собственный существующий ответ должен прояснить это еще больше.Ваши собственные недостатки недостаточны, чтобы объявить что-то слишком сложным для понимания.

  3. Слишком широкий (1x) ("Пожалуйста, отредактируйте вопрос, чтобы ограничить его конкретнымпроблема с достаточным количеством деталей для определения адекватного ответа [...] "):

    Это должно быть еще одна проблема с функциональным аналфавитизмом.Я четко заявил, что достаточно одного способа решения проблемы (который я уже нашел) достаточно.Вы можете определить адекватный ответ следующим образом: Взгляните на принятый ответ моего собственного.В качестве альтернативы, используйте свой мозг, чтобы интерпретировать мои четко определенные слова, если вы в состоянии, что, к сожалению, кажется, несколько человек на этой платформе.

Однако есть реальная причина закрытьэтот вопрос: он уже решен.Но такой возможности для закрытого голосования нет.Итак, ясно, Stack Exchange поддерживает, что могут быть найдены альтернативные решения.Поскольку я любопытный человек, меня также интересуют альтернативные способы решения этой проблемы.Если ваш недостаток интеллекта не справляется с пониманием, в чем заключается проблема и что она весьма актуальна в определенных средах (например, таких, которые используют Windows, C ++ в Eclipse CDT, UTF-8, но no Visual Studioи no Windows Console), тогда вы можете просто уйти, не стоя на пути других людей, чтобы удовлетворить их любопытство.Спасибо!

1-е обновление: Я использовал app.exe > out.txt 2>&1, который генерирует файл без этих проблем форматирования.Таким образом, проблема в том, что обычно std :: cout выполняет это разбиение, но базовый элемент управления (который получает последовательность символов) должен обрабатывать правильную повторную сборку?(К сожалению, в Windows, похоже, ничего не происходит, кроме потоков файлов. Поэтому мне все еще нужно обойти это. Желательно без предварительной записи файлов и отображения их содержимого - что, конечно, работает.)

Вкл.В системе, которую я использую (Windows 7; MinGW-w64 (GCC 8.1 для Windows)), есть ошибка с std::cout, так что строки в кодировке UTF-8 выводятся на печать до их повторной сборки, даже если они были разобраны внутриstd::cout, передав большую строку.Следующий код объясняет, как ошибка ведет себя.Обратите внимание, что, однако, ошибочные отображения выглядят случайными, т. Е. Способ, которым std::cout разделяет (равно) std::string объекты, не эквивалентен для каждого выполнения программы.Но проблемы постоянно появляются при индексах, кратных 1024, и вот как я сделал вывод о таком поведении.

#include <iostream>
#include <sstream>

void myFaultyOutput();
void simulatedFaultyBehavior();

int main()
{
    myFaultyOutput();
    //simulatedFaultyBehavior();
}

void myFaultyOutput() {
    std::stringstream ss; // Note that ss is built correctly (which could be shown by saving ss.str() to a file).
    ss << "...";
    for (int i = 0; i < 20; i++) {
        for (int j = 0; j < 341; j++)
            ss << u8"\u301A";
        ss << "\n..";
    }
    std::cout << ss.str() << std::endl; // Problem occurs here, with cout.
    // Note that converting ss.str() to UTF-16 std::wstring and using std::wcout results in std::wcout not
    // displaying anything, not even ASCII characters in the future (until restarting the application).
}

// To display the problem on well-behaved systems ; just imagine the output would not contain newlines, while the faulty formatted characters remain.
void simulatedFaultyBehavior() {
    std::stringstream ss;
    int amount = 2000;
    for (int j = 0; j < amount; j++)
        ss << u8"\u301A";
    std::string s = ss.str();
    std::cout << "s.length(): " << s.length() << std::endl; // amount * 3
    while (s.length() > 1024) {
        std::cout << s.substr(0, 1024) << std::endl;
        s = s.substr(1024);
    }
    std::cout << s << std::endl << std::flush;
}

Чтобы обойти это поведение, я бы хотел разделить большие строки (которые я получаю как таковые из API) вручную на части длиной менее 1024 символов (и затем вызывать std :: cout отдельно для каждой из них).Но я не знаю, какие символы на самом деле являются просто бесконечной частью символа UTF-8, и встроенные преобразователи Unicode также кажутся ненадежными (возможно, также зависящими от системы?). Существует ли простой способ определить, является ли символ в std::string неконечной частью символа UTF-8? Следующая цитата объясняет, почему было бы достаточно ответа на этот вопрос.

Символ UTF-8 может, например, состоять из трех символов.Так что если кто-то разбивает строку на две части, он должен держать эти три символа вместе.В противном случае необходимо делать то, что существующие элементы управления графическим интерфейсом явно не в состоянии сделать последовательно.Например, повторная сборка символов UTF-8, которые были разбиты на части.

Лучшие идеи для обхода проблемы (кроме «Не используйте Windows» / «Не используйте UTF-8»)"/" Не используйте cout ", конечно) также приветствуются.

Обратите внимание, что этот вопрос не относится к консоли Windows (я не использую его - все отображается в Eclise и, возможно, в wxWidgetsЭлементы пользовательского интерфейса, которые правильно отображают UTF-8).Это также не связано с MSVC (как я уже упоминал, я использую MinGW-компилятор).В коде также упоминается, что использование std :: wcout с UTF-16 вообще не работает (из-за другой MinGW ошибка Eclipse). Ошибка является результатом того, что элементы управления пользовательского интерфейса не могут обрабатывать действия std::cout (которые могут быть преднамеренными или нет). Кроме того, все обычно работает нормально, за исключением этих символов UTF-8.которые были разделены на разные символы (например, \ u301A на \ u0003 + \ u001A) с индексами, кратными 1024 (и только случайным образом).Такое поведение уже подразумевает, что большинство предположений комментаторов являются ложными.Пожалуйста, внимательно рассмотрите код, особенно его комментарии, а не спешите с выводами.

Чтобы прояснить проблему с отображением при вызове myFaultyOutput():

in Eclipse CDT

in Scintilla (implemented in wxWidgets as wxStyledTextCtrl)

1 Ответ

0 голосов
/ 25 сентября 2019

Я разработал довольно простой обходной путь, экспериментируя, и я удивлен, что никто не знал (я ничего такого не нашел в Интернете).

Попытка ответа Нм дала хороший намек с упоминанием функции, специфичной для платформы _setmode.«По замыслу» (согласно этому ответу и этой статье ) он устанавливает режим преобразования файлов, то есть как входные и выходные потоки в соответствии с процессомобрабатываются.Но в то же время он аннулирует использование std::ostream / std::istream, но требует использования std::wostream / std::wistream для прилично отформатированных входных и выходных потоков.

Например, использование _setmode(_fileno(stdin), _O_U8TEXT) отведенийstd::wcout теперь хорошо работает с выводом std::wstring как UTF-8, но std::cout выводит символы мусора, даже в аргументах ASCII.Но я хочу иметь возможность в основном использовать std::string, особенно std::cout для вывода.Как я уже упоминал, это редкий случай, когда форматирование для std::cout завершается неудачно, поэтому только в тех случаях, когда я распечатываю строки, которые могут привести к этой проблеме (потенциальные символы, закодированные несколькими символами, с индексами не менее 1024)Я хочу использовать специальную функцию вывода, скажем coutUtf8String(string s).

Режим по умолчанию (непереведенный) _setmode - _O_BINARY.Мы можем временно переключать режимы.Так почему бы просто не переключиться на _O_U8TEXT, преобразовать объект std::string в кодировке UTF-8 в std::wstring, использовать std::wcout на нем, а затем переключиться обратно на _O_BINARY?Чтобы оставаться независимым от платформы, можно просто определить обычный вызов std::cout, когда он не в Windows.Вот код:

#if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__)
#include <fcntl.h> // Also includes the non-standard file <io.h>
                   // (POSIX compatibility layer) to use _setmode on Windows NT.
#endif

void coutUtf8String(string s) {
#if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__)
    if (s.length() > 1024) {
        // Set translation mode of wcout to UTF-8, renders cout unusable "by design"
        // (see https://developercommunity.visualstudio.com/solutions/411680/view.html).
        if (_setmode(_fileno(stdout), _O_U8TEXT) != -1) {
            wcout << utf8toWide(s) << flush; // We must flush before resetting the mode.
             // Set translation mode of wcout to untranslated, renders cout usable again.
            _setmode(_fileno(stdout), _O_BINARY);
        } else
            // Let's use wcout anyway. Since no sink (such as Eclipse's console
            // window) is attached when _setmode fails, and such sinks seem to be
            // the cause for wcout to fail in default mode. The UI console view
            // is filled properly like this, regardless of translation modes.
            wcout << utf8toWide(s) << flush;
    } else
        cout << s << flush;
#else
    cout << s << flush;
#endif
}

wstring utf8toWide(const char* in) {
    wstring out;
    if (in == nullptr)
        return out;
    uint32_t codepoint = 0;
    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 (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;
}

Это решение особенно удобно, поскольку фактически не поддерживает UTF-8, std::string или std::cout, которые в основном используются по уважительным причинам ,но просто использует std::string и поддерживает независимость от платформы.Я скорее согласен с этим ответом , который добавляет wchar_t (и весь избыточный мусор, который идет с ним, такой как std::wstring, std::wstringstream, std::wostream, std::wistream, std::wstreambuf)до C ++ была ошибка.Только потому, что Microsoft принимает плохие дизайнерские решения, не следует принимать их ошибки, а обходить их.

Визуальное подтверждение: enter image description here

...