Поиск функций форматирования с поддержкой UTF8, таких как printf() и т. д.

Я обнаружил интересную проблему при обработке строк UTF-8, содержащих символы, отличные от ASCII, с помощью функций форматирования стандартной библиотеки C, таких как sprintf():

Функции семейства printf() не знают о utf-8 и обрабатывают все на основе количества байтов, а не символов. Поэтому форматирование неверное.

Простой пример:

#include <stdio.h>

int main(int argc, char *argv[])
{
    const char* testMsg = "Tääääßt";
    char buf[1024];
    int len;

    sprintf(buf, "|%7.7s|", testMsg);
    len = strlen(buf);
    printf("Result=\"%s\", len=%d", buf, len);

    return 0;
}

Результат:

 Result="|Täää|", len=7

Скорее всего, некоторые из вас порекомендуют преобразовать приложение из char в wchar_t и использовать fwprintf() и т. д., но это абсолютно невозможно из-за огромного количества существующих приложений. Я мог бы представить себе написание оболочки, которая использует эти функции внутри, но это было бы сложно и очень неэффективно.

Таким образом, лучшим решением будет замена функций форматирования стандартной библиотеки C с поддержкой UTF-8.

В настоящее время я работаю над QNX 6.4, но отвечаю за другие операционные системы. например Linux, также очень приветствуются.


person mh.    schedule 17.02.2012    source источник
comment
В выводе вашего примера отсутствует начальный символ «|» персонаж, который вряд ли отражает то, что произошло на самом деле.   -  person unwind    schedule 17.02.2012
comment
@unwind Ты был прав, спасибо. Фиксированный.   -  person mh.    schedule 17.02.2012
comment
Не могли бы вы использовать библиотеку Unicode (например, flexiguided.de/publications.utf8proc.en.html) и передать printf количество байтов для строки Unicode?   -  person trojanfoe    schedule 17.02.2012
comment
Просто предупреждение: подсчет символов в данных Unicode — довольно сложное дело. Помимо того факта, что каждая кодовая точка в UTF-8 состоит из нескольких байтов, каждый глиф (или графема) может состоять из нескольких кодовых точек, и по этой причине fwprintf в любом случае не подходит для усечения данных Unicode — например, вы можете вырезать снять ударение, не обрезая символ, к которому он относится. Поэтому, что бы вы ни использовали, убедитесь, что значение длины, которую вы указываете, вам понятно.   -  person Steve Jessop    schedule 17.02.2012
comment
возможный дубликат Какая лучшая библиотека Unicode для C?   -  person    schedule 17.02.2012
comment
Такие функции, как len(), однозначно возвращают количество байтов (или, ну, элементов). Тот факт, что они отображаются как разное количество символов в вашей локали, в основном не контролируется C. Если вам нужна ширина отображения, не используйте функцию для подсчета байтов.   -  person tripleee    schedule 17.02.2012
comment
@Steve Jassop В QNX есть функция utf8strlen(), которая подсчитывает количество символов в строке UTF-8. Теперь он будет работать для меня, хотя я еще не проверял, будет ли он работать правильно для всех особых случаев ;-) .   -  person mh.    schedule 17.02.2012
comment
@mh.: В документации это не совсем ясно (там указаны символы UTF-8), но я считаю, что utf8strlen измеряет количество кодовых точек. Итак, как отмечает Дитрих, если вы обрежете строку, которая выглядит как "Tä", до 2 символов, вы получите Ta, если исходная строка была U+0054 U+0061 U+0308.   -  person Steve Jessop    schedule 17.02.2012
comment
@tripleee Вам не следует иметь дело с байтами или локалями, если вы работаете с Unicode. Вы должны иметь дело с абстрактными кодовыми точками в универсальном наборе символов, и не должно быть никаких эффектов локали, связанных с печатью.   -  person tchrist    schedule 18.02.2012
comment
@SteveJessop Это действительно не должно быть сложно. Правильная библиотека делает эти вещи тривиальными. Вы должны быть в состоянии считать и проходить по любой кодовой точке или по графеме без какой-либо суеты. Тем не менее, C и C++ все еще несколько отстают в этом отношении. Интернет теперь на 80 % состоит из Unicode, что означает взрывной рост на 600 % за последние 5 лет. Многие другие языки делают это намного проще, чем C или C++.   -  person tchrist    schedule 18.02.2012
comment
@tchist: Правильная библиотека делает эти вещи тривиальными. Верно во многих вещах - найти хирурга легко, но я бы все же назвал хирургию сложной ;-p В этом случае написание библиотеки было бы трудным (или, по крайней мере, потребовало бы внимательного отношения к довольно большому стандарту). Интеграция одного из них не всегда проста, если существующий код имеет неподходящее понятие длины, которое необходимо разделить на несколько разных понятий (по крайней мере: длина буфера, количество кодовых точек, количество графем). Вот почему важно, чтобы значение длины было ясным в любом конкретном контексте.   -  person Steve Jessop    schedule 19.02.2012


Ответы (2)


Ну, как только вы попросите printf выполнить интеллектуальное заполнение символов Unicode, вы столкнетесь с серьезными проблемами. Как они сказали,

w͢͢͝h͡o͢͡ ̸͢k̵͟n̴͘ǫw̸̛s͘ w͘͢ḩ̵a҉̡͢t ̧̕ho̵r͏̵rors̡ ̶͡͠lį̶e͟͟ ̶̶͝͝in͢ ͏t̕h̷̡͟e ͟͟d̛a͜r̕͡k̢̨ ͡h̴e͏a̷̢̡rt͏ ̴̷̴̷̵̶͠͠f̸ u̧͘ní̛͜c͢͏o̷͏d̸͢e̡͝? ͞

  • Сколько символов Юникода в Tääääßt? Ну, это может быть от 7 до 11, в зависимости от того, как оно закодировано. Каждое ä может быть записано как U+00E4, что является одним символом, или может быть записано как U+0061 U+0308, что является двумя символами. Итак, ваша следующая надежда — подсчитать кластеры графем. (Нет, нормализация не решит проблему.)

  • Но насколько широк кластер графемы? Очевидно, что a имеет ширину в один столбец. U + 200B должен иметь нулевую ширину столбцов, это пространство «нулевой ширины». Должна ли каждая ひらがな быть шириной в две колонки? Обычно они есть в эмуляторах терминала. Что происходит, когда вы форматируете ひらがな как 7 столбцов, вы получаете "ひらが ", что добавляет пробел, или вы получаете "ひらが", что составляет всего 6 столбцов?

  • Если вы вырезаете что-то, что смешивает текст RTL и LTR, следует ли после этого сбросить направление текста? Чем ты планируешь заняться? (Некоторые эмуляторы терминалов, такие как Apple, поддерживают смешанное написание текста слева направо и справа налево.)

  • Какова ваша цель, усекая текст? Вы пытаетесь показать пользователю строку в ограниченном пространстве или пытаетесь написать формат, который использует поля фиксированной ширины?

По сути, если вы хотите разрезать текст Unicode на куски, вам не следует делать это с чем-то таким простым, как printf (или wprintf, что, возможно, еще хуже). Используйте LibICU (веб-сайт) для повторения нужных разрывов. Написание версии printf с поддержкой UTF-8 вызывает всевозможные проблемы, которые вам не нужны.

person Dietrich Epp    schedule 17.02.2012
comment
Я думаю, что понимаю проблемы, о которых вы упомянули, и знаю, что некоторые из них не могут быть удовлетворительно решены в ASCII. Однако на данный момент я был бы доволен простой заменой printf(), которая будет работать с европейскими и азиатскими символами и не требует учета экзотических функций, таких как изменения направления текста. Моя цель относительно усечения в формате - это поля фиксированной ширины. Я знаю, что это не будет хорошо работать с азиатскими символами, которые могут быть шире даже в Courier, но на данный момент это будет работать для меня, пока я не найду время для переделки печати приложения на основе ASCII. - person mh.; 17.02.2012

Следующий фрагмент кода C99 определяет функцию u8printf, где спецификаторы формата, такие как %10s, дают 10 кодовых точек utf-8, то есть символов, а не байтов. Не забудьте установить локаль с помощью setlocale(LC_ALL,"") где-нибудь перед вызовом этой подпрограммы. Это работает, потому что wprintf использует wchar_t внутри. Вы можете определить u8fprintf и u8sprintf аналогичным образом. Если вы хотите написать это без массивов переменной длины C99, то также возможна подходящая комбинация malloc/free.

int u8printf(char *fmt,...){
    va_list ap;
    va_start(ap,fmt);
        int n=mbstowcs(0,fmt,0);
        if(n==-1) return -1;
        wchar_t wfmt[n+1];
        mbstowcs(wfmt,fmt,n+1);
        for(int m=128;m<=32768;m*=2){
            wchar_t wbuf[m];
            int r=vswprintf(wbuf,m,wfmt,ap);
            if(r!=-1) {
                char buf[m*4];
                wcstombs(buf,wbuf,m*4);
                fputs(buf,stdout);
                return r;
            }
        }
        return -1;
    va_end(ap);
}
person ejolson    schedule 06.05.2014