【问题标题】:Looking for UTF8-aware formatting functions like printf(), etc寻找支持 UTF8 的格式化函数,如 printf() 等
【发布时间】:2012-03-08 16:33:12
【问题描述】:

在使用 sprintf() 等 C 标准库格式化函数处理包含非 ASCII 字符的 UTF-8 字符串时,我发现了一个有趣的问题:

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() 等,但由于现有应用程序庞大,这绝对是不可能的。我可以想象编写一个在内部使用这些函数的包装器,但这会很棘手而且效率很低。

因此,最好的解决方案是使用 UTF-8 感知替代标准 C 库的格式化函数。

目前我正在开发 QNX 6.4,但回复其他操作系统。例如Linux,也很受欢迎。

【问题讨论】:

  • 您的示例输出省略了前导 '|'性格,这似乎不太可能反映真实发生的事情。
  • @unwind 你是对的,谢谢。固定。
  • 您能否使用 Unicode 库(如 flexiguided.de/publications.utf8proc.en.html)并提供 printf Unicode 字符串的字节数?
  • 只是一个警告,计算 Unicode 数据中的“字符”是一项相当复杂的工作。除了 UTF-8 中的每个代码点由几个字节组成的事实之外,每个字形(或“字形”)可以由多个代码点组成,因此fwprintf 无论如何都不足以截断 Unicode 数据——因为例如,您可以切断重音而不切断它适用的字符。所以无论你最终使用什么,确保你指定的长度的含义对你来说是清楚的。

标签: c utf-8 libc qnx


【解决方案1】:

好吧,一旦您要求printf 对 Unicode 字符进行智能填充,您就会遇到重大问题。正如他们所说,

w͢͢͝h͡o̸̸͢͢͢͢͡͡҉̴̡͘͢ǫ̸̛r͏̵rorsȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱrtȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱȱ

  • Tääääßt 中有多少个 Unicode 字符?嗯,它可能是从 7 到 11 的任何地方,这取决于它的编码方式。每个ä可以写成U+00E4,一个字符,也可以写成U+0061 U+0308,两个字符。所以你的下一个希望是计算字素簇。 (不,标准化不会让问题消失。)

  • 但是,字素簇有多宽?显然,a 是一列宽。 U+200B 应该是零列宽,它是一个“零宽度”空间。每个ひらがな应该是两列宽吗?它们通常在终端仿真器中。将ひらがな 格式化为 7 列时会发生什么情况,你会得到 "ひらが ",它添加了一个空格,还是你得到 "ひらが",它只有 6 列?

  • 如果您剪切了混合 RTL 和 LTR 文本的内容,您是否应该在之后重置文本方向?你会怎样做? (一些终端仿真器,例如 Apple 的,支持从左到右和从右到左的混合文本。)

  • 截断文本的目的是什么?您是要在有限的空间内向用户显示字符串,还是要编写使用固定宽度字段的格式?

基本上,如果您想将 Unicode 文本切割成块,您不应该使用像 printf(或 wprintf,这很可能更糟)这样简单的东西。使用 LibICU (website) 迭代您想要的休息时间。编写一个支持 UTF-8 的 printf 版本是在自找各种你不想要的麻烦。

【讨论】:

  • 我想我理解你提到的问题,并且知道其中一些问题在 ASCII 中无法令人满意地解决。但是,现在我很乐意直接替换 printf() ,它适用于欧洲和亚洲字符,并且不需要考虑文本方向变化等特殊功能。我关于格式截断的目标是固定宽度的字段。我知道这不适用于即使在“Courier”中也可以更宽的亚洲字符,但现在这对我有用,直到我有时间重新设计应用程序的基于 ASCII 的打印。
【解决方案2】:

以下 C99 代码 sn-p 定义了函数 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);
}

【讨论】:

    猜你喜欢
    • 2019-10-18
    • 1970-01-01
    • 1970-01-01
    • 2019-07-01
    • 2021-05-17
    • 2010-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多