C语言自动识别文本编码

用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
全部UnicodeUTF-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

转载于:https://my.oschina.net/mickelfeng/blog/1036684

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值