我在使用C标准库格式化函数(如sprintf())处理包含非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()等,但由于现有的巨大应用程序,这绝对是不可能的。我可以想象编写一个在内部使用这些函数的包装器,但这会非常棘手且效率很低。
因此,最好的解决方案是替换标准C库的格式化功能的UTF-8。
目前我正在开发QNX 6.4,但回复了其他操作系统。例如Linux也非常受欢迎。
答案 0 :(得分:9)
好吧,一旦你要求printf
对Unicode字符进行智能填充,就会遇到重大问题。正如他们所说,
w͢͢͝h͡o͢͢͡͡k̵͟n̴͘ǫw̸̛s͘w̧̕a҉̡͢ţ̕ho̵r͏̵rors̡̡lį̶e͟͟͟͟in͢͢t̕h̷̡͟e͟͟d̛a͜r̕͡k̢̨̢̨h̴e͏a̷̢̡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
版本就是在寻找各种你不想要的麻烦。
答案 1 :(得分:0)
以下C99代码片段定义了函数u8printf,其中格式说明符(例如%10s)产生10个utf-8代码点,即字符而不是字节。在调用此例程之前,不要忘记在setlocale(LC_ALL,&#34;&#34;)之前设置区域设置。这是有效的,因为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);
}