用C语言在编写一些文本处理程序时,文本编码就成为了很重要的部分,
但C语言本身,处理文本又十分困难,虽然有wchar_t这类标准库支持,
然后事实是各类编译器对其支持也不相同,而且缺乏一致的标准。
可移植的转码库libiconv
libiconv是GNU出品的一款非常方便的转码库,可以支持各类
常见编码类型。
语言 | 编码 |
---|---|
欧洲语系 | ASCII, ISO-8859-{1,2,3,4,5,7,9,10,13,14,15,16}, KOI8-R, KOI8-U, KOI8-RU, CP{1250,1251,1252,1253,1254,1257}, CP{850,866}, Mac{Roman,CentralEurope,Iceland,Croatian, Romania}, Mac{Cyrillic,Ukraine,Greek,Turkish}, Macintosh |
犹太语系 | ISO-8859-{6,8}, CP{1255,1256}, CP862, Mac{Hebrew,Arabic} |
日文 | EUC-JP, SHIFT_JIS, CP932, ISO-2022-JP, ISO-2022-JP-2, ISO-2022-JP-1 |
中文 | EUC-CN, HZ, GBK, GB18030, EUC-TW, BIG5, CP950, BIG5-HKSCS, ISO-2022-CN, ISO-2022-CN-EXT |
朝鲜文 | EUC-KR, CP949, ISO-2022-KR, JOHAB |
亚美尼亚语 | ARMSCII-8 |
格鲁尼亚语 | Georgian-Academy, Georgian-PS |
塔吉克语 | KOI8-T |
泰国语 | TIS-620, CP874, MacThai |
老挝语 | MuleLao-1, CP1133 |
越南语 | VISCII, TCVN, CP1258 |
特殊平台 | HP-ROMAN8, NEXTSTEP |
全部Unicode | UTF-8, UTF-7 UCS-2, UCS-2BE, UCS-2LE, UCS-4, UCS-4BE, UCS-4LE UTF-16, UTF-16BE, UTF-16LE, UTF-32, UTF-32BE, UTF-32LE C99, JAVA |
计算机内部处理,比较喜欢用Unicode这类覆盖字符集全的,而且一般选择固定长度字符的,像UTF-8这类变长字符集,会给字符处理算法带来困难。
于是一般的字符转码思路就是,将用户的输入字符集识别出来,转换成内部表示形式,一般个人喜欢选UTF-32,比较方便,而且覆盖字符集很全。
输出时,再将内部格式转换为用户需要的编码格式进行输出即可。像linux控制台,就可以直接输出UTF-8编码的字符串。Windows中文同样可以输出CP936编码的文本。
发现用户文件的编码
很多情况下,要读取的文件的编码可能不是很好确定,尤其是文件中并没有保存任何编码格式的相关信息时,这时我们只能靠尝试,用多种解码器进行解码,出错最少的,我们才认为成功。
自行编写该解码尝试器,难度和编码转换器类似,都要对编码格式深入了解才行,那么一般我们要使用该功能时,只需引入一个非常强大得到探测器库就可以了。
Mozilla Universal Character Set Detector,是一款强大的编码探测器库,具有操作简单,识别格式广的特点。其仓库地址:
https://github.com/batterseapower/libcharsetdetect
支持的探测格式更是有:
Big5
EUC-JP
EUC-KR
GB18030
gb18030
HZ-GB-2312
IBM855
IBM866
ISO-2022-CN
ISO-2022-JP
ISO-2022-KR
ISO-8859-2
ISO-8859-5
ISO-8859-7
ISO-8859-8
KOI8-R
Shift_JIS
TIS-620
UTF-8
UTF-16BE
UTF-16LE
UTF-32BE
UTF-32LE
windows-1250
windows-1251
windows-1252
windows-1253
windows-1255
x-euc-tw
X-ISO-10646-UCS-4-2143
X-ISO-10646-UCS-4-3412
x-mac-cyrillic
安装
这两款库的安装都非常简单,下载下来依次进行
./configure
make
这样就可以了
libiconv稍微麻烦,如果出现这个错误:
error: ‘gets’ undeclared here (not in a function)
是因为srclib/stdio.in.h这个文件695行有个bug,没检测GLIBC的版本所致:
--- srclib/stdio.in.h.orig 2011-08-07 16:42:06.000000000 +0300
+++ srclib/stdio.in.h 2013-01-10 15:53:03.000000000 +0200
@@ -695,7 +695,9 @@
/* It is very rare that the developer ever has full control of stdin,
so any use of gets warrants an unconditional warning. Assume it is
always declared, since it is required by C89. */
-_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
+#if defined(__GLIBC__) && !defined(__UCLIBC__) && !__GLIBC_PREREQ(2, 16)
+ _GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
+#endif
#endif
使用
libcharsetdetect的官网带有一个示例,很成功的运行起来,也没发现什么问题:
#include "charsetdetect.h"
#include "stdio.h"
#define BUFFER_SIZE 4096
int main(int argc, const char * argv[]) {
csd_t csd = csd_open(); // 首先开启一个探测器
if (csd == (csd_t)-1) {
printf("csd_open failed\n");
return 1;
}
int size;
char buf[BUFFER_SIZE] = {0}; // 这里建议用memset避免部分编译器报错
while ((size = fread(buf, 1, sizeof(buf), stdin)) != 0) {
int result = csd_consider(csd, buf, size);
if (result < 0) {
printf("csd_consider failed\n");
return 3;
} else if (result > 0) {
// Already have enough data
break;
}
}
const char *result = csd_close(csd);
if (result == NULL) {
printf("Unknown character set\n");
return 2;
} else {
printf("%s\n", result);
return 0;
}
}
编译运行即可:
gcc example.c -lcharsetdetect
./a.out < my_test_file.txt
libiconv比较反人类,build后,库和so文件都藏在各个目录里,没有install的化,就得自己找出来。
还好如果我们只是引用其中的转码功能,只需要lib/.libs/libiconv.so这个文件,注意,是这个软链接指向的目标。
头文件就拿include下的就好。
使用上来看,这个库的用法非常诡异,也是分三步:
iconv_t cd = iconv_open("to_code", "from_code"); // 确定要转换的编码
size_t nconv = iconv (cd, &inptr, &in_size, &outptr, &out_size); // 输入输出数据
iconv_close (cd); // 回收资源
iconv是其核心转换函数,其定义如下:
size_t iconv (iconv_t cd,
const char* * inbuf, size_t * inbytesleft,
char* * outbuf, size_t * outbytesleft);
首先解释一下各个参数的含义
cd是转换描述用的结构体,inbuf是指向输入数据的指针的指针,outbuf类似,
inbytesleft,outbytesleft也是两个指针。
这个函数工作时,所有的指针都在变化,*inbuf 会不断自增扫描, *outbuf会不断向后移动填写数据, 而 *inbytesleft 则表示输入数组还剩多少字节,注意单位都是字节。 *outbytesleft 同理,表示输出数组还剩的剩余空间。
当 *inbytesleft 为 0 时,表示输入完毕,而 *outbytesleft 为 0 后,会触发剩余空间不足的错误,方便你重新分配空间。
完整示例和封装
我将两个库结合在了一起,封装成了一个自动文本读取并转换的库,方便使用,代码仓库:
https://github.com/sunxfancy/ExIconv