在上篇中簡單介紹了libxml2讀寫XML檔案。
本篇將介紹如何在libxml2中讀寫中文資料。
libxml2中提供了轉碼器,通過建立自己的編碼介面由libxml根據當前編碼進行調用,實質上是使用iconv庫中的函數實現轉換。一般情況下XML使用UTF-8編碼,可以實現很好的跨平台性。
本文將網路上的一段代碼複製下來,並應用於系統中,並在32位與64位LINUX平台下測試通過。(源於網路的在64下有問題,我調試了2個多小時才找到問題所在)既然思想來源於網路,在這裡也將代碼公布在這裡。這樣就可以直接應用於代碼中。
#include <libxml/encoding.h><br />#include <libxml/xmlwriter.h><br />#include <libxml/xmlreader.h></p><p>#define ENCODING "gb2312"</p><p>xmlChar *ConvertInput(const char *in, const char *encoding);<br />char *ConvertFrom(xmlChar *in, const char *encoding);</p><p>iconv_t iconv_utf8_gbk;<br />iconv_t iconv_gbk_utf8;</p><p>int gbk_input(unsigned char *out, int *outlen, const unsigned char *in,<br />int *inlen)<br />{<br />char *outbuf = (char *) out;<br />char *inbuf = (char *) in;<br />size_t rslt;<br />size_t len1, len2;<br />// !!! 注意不能直接從int*到size_t*的轉換<br />// 在32位平台下是正常的,但到了64平台下size_t為64位,那(size_t*)inlen<br />// 將是一個未知的資料<br />len1 = *inlen;<br />len2 = *outlen;<br />rslt = iconv(iconv_utf8_gbk, (char **) &inbuf, &len1, &outbuf, &len2);<br />if (rslt < 0)<br />{<br />return rslt;<br />}<br />*outlen = ((unsigned char *) outbuf - out);<br />*inlen = ((unsigned char *) inbuf - in);<br />return *outlen;<br />}</p><p>int gbk_output(unsigned char *out, int *outlen, const unsigned char *in,<br />int *inlen)<br />{<br />char *outbuf = (char *) out;<br />char *inbuf = (char *) in;<br />size_t rslt;<br />// !!! 注意不能直接從int*到size_t*的轉換<br />// 在32位平台下是正常的,但到了64平台下size_t為64位,那(size_t*)inlen<br />// 將是一個未知的資料<br />size_t len1, len2;<br />len1 = *inlen;<br />len2 = *outlen;<br />rslt = iconv(iconv_gbk_utf8, (char **) &inbuf, &len1, &outbuf, &len2);<br />if (rslt < 0)<br />{<br />return rslt;<br />}<br />*outlen = ((unsigned char *) outbuf - out);<br />*inlen = ((unsigned char *) inbuf - in);<br />return *outlen;<br />}</p><p>/**<br /> * ConvertInput:<br /> * @in: string in a given encoding<br /> * @encoding: the encoding used<br /> *<br /> * Converts @in into UTF-8 for processing with libxml2 APIs<br /> *<br /> * Returns the converted UTF-8 string, or NULL in case of error.<br /> * xmlFree<br /> */<br />xmlChar *<br />ConvertInput(const char *in, const char *encoding)<br />{<br /> xmlChar *out;<br /> int ret;<br /> int size;<br /> int out_size;<br /> int temp;<br /> xmlCharEncodingHandlerPtr handler;<br /> if (in == 0)<br /> return 0;<br /> handler = xmlFindCharEncodingHandler(encoding);<br /> if (!handler) {<br /> printf("ConvertInput: no encoding handler found for '%s'/n",<br /> encoding ? encoding : "");<br /> return 0;<br /> }<br /> size = (int) strlen(in) + 1;<br /> out_size = size * 2 - 1;<br /> out = (unsigned char *) xmlMalloc((size_t) out_size);<br /> if (out != 0) {<br /> temp = size - 1;<br /> ret = handler->input(out, &out_size, (const xmlChar *) in, &temp);<br /> if ((ret < 0) || (temp - size + 1)) {<br /> if (ret < 0) {<br /> printf("ConvertInput: conversion wasn't successful./n");<br /> } else {<br /> printf<br /> ("ConvertInput: conversion wasn't successful. converted: %i octets./n",<br /> temp);<br /> }<br /> xmlFree(out);<br /> out = 0;<br /> } else {<br /> out = (unsigned char *) xmlRealloc(out, out_size + 1);<br /> out[out_size] = 0; /*null terminating out */<br /> }<br /> } else {<br /> printf("ConvertInput: no mem/n");<br /> }<br /> return out;<br />}</p><p>/**<br /> * ConvertFrom:<br /> * @in: string in a utf-8 encoding<br /> * @encoding: the encoding used<br /> *<br /> * Converts @in from UTF-8 for processing with libxml2 APIs<br /> *<br /> * Returns the converted UTF-8 string, or NULL in case of error.<br /> */<br />char *<br />ConvertFrom(xmlChar *in, const char *encoding)<br />{<br /> char *out;<br /> int ret;<br /> int size;<br /> int out_size;<br /> int temp;<br /> xmlCharEncodingHandlerPtr handler;</p><p> if (in == 0)<br /> return 0;</p><p> handler = xmlFindCharEncodingHandler(encoding);</p><p> if (!handler) {<br /> printf("ConvertFrom: no encoding handler found for '%s'/n",<br /> encoding ? encoding : "");<br /> return 0;<br /> }</p><p> size = (int) strlen((char*)in) + 1;<br /> out_size = size * 2 - 1;<br /> out = (char *) malloc((size_t) out_size);</p><p> if (out != 0) {<br /> temp = size - 1;<br /> ret = handler->output((xmlChar*)out, &out_size, (const xmlChar *) in, &temp);<br /> if ((ret < 0) || (temp - size + 1)) {<br /> if (ret < 0) {<br /> printf("ConvertFrom: conversion wasn't successful./n");<br /> } else {<br /> printf<br /> ("ConvertFrom: conversion wasn't successful. converted: %i octets./n",<br /> temp);<br /> }</p><p> free(out);<br /> out = 0;<br /> } else {<br /> out = (char *) realloc(out, out_size + 1);<br /> out[out_size] = 0; /*null terminating out */<br /> }<br /> } else {<br /> printf("ConvertFrom: no mem/n");<br /> }</p><p> return out;<br />}<br />
使用上述代碼的方式就是將編碼轉換初始化,並插入到XML庫的介面中。
iconv_utf8_gbk = iconv_open ("utf-8", "gbk");<br />iconv_gbk_utf8 = iconv_open ("gbk", "utf-8");</p><p>xmlNewCharEncodingHandler ("gb2312", gbk_input, gbk_output);//添加gb2312編碼支援<br />xmlNewCharEncodingHandler ("gbk", gbk_input, gbk_output);//添加gbk編碼支援</p><p>
就是使用iconv庫(LINUX中存在的,libxml也是使用該庫),建立utf-8編碼與gbk編碼的轉換介面,並將介面插入到libxml2庫中,這樣xml庫就支援對gb2312和gbk編碼的支援了。當然,這個轉換不會自動完成,我們需要使用從libxml庫中尋找特定編碼的介面,libxml支援一些基本的編碼介面,如:ISO-8859-1,ISO-8859-2等編碼,但不支援gbk,所以在上述代碼中,我們定義了gbk_input,與gbk_output兩個介面,這兩個介面是libxml庫的標準聲明。
Function type: xmlCharEncodingInputFunc
Function type: xmlCharEncodingInputFuncintxmlCharEncodingInputFunc(unsigned char * out,
int * outlen,
const unsigned char * in,
int * inlen)
Take a block of chars in the original encoding and try to convert it to an UTF-8 block of chars out.
| out: |
a pointer to an array of bytes to store the UTF-8 result |
| outlen: |
the length of @out |
| in: |
a pointer to an array of chars in the original encoding |
| inlen: |
the length of @in |
| Returns: |
the number of bytes written, -1 if lack of space, or -2 if the transcoding failed. The value of @inlen after return is the number of octets consumed if the return value is positive, else unpredictiable. The value of @outlen after return is the number of octets consumed. |
還有一個xmlCharEncodingOutputFunc函數,聲明與上面是一樣的。
在ConvertInput與ConvertFrom是在libxml庫中尋找指定編碼的介面,並完成編碼轉換。為了支援GBK,通過xmlNewCharEncodingHandler 來添加新的編碼到該編碼器中,ConvertXXXX從編碼庫中找到相應編碼的處理器,完成編碼與解碼工作。
在實際使用的時候就是使用ConvertXXXX來實現編碼與解碼。
如:
// 將一個編碼為GB2312的本地字串轉換成UTF-8編碼(建立XML文檔中的參數)<br />key = ConvertInput(p_k, ENCODING);<br />if (key == NULL){<br /> return -5;<br />}</p><p>// 將一個UTF-8編碼的XML字串轉換成一個GB2312的字串<br />p_k = ConvertFrom(element, ENCODING);<br />// 釋放由libxml讀函數返回的元素資料空間<br />xmlFree(element);
這兩個函數返回的是一個字串指標,記憶體由ConvertXXX函數分配,需要使用free函數釋放。
在使用完libxml庫之後,我們需要釋放由iconv_open開啟的轉換控制代碼,並且還要釋放libxml庫的轉換資源。
xmlCleanupCharEncodingHandlers();
用上述來釋放libxml庫中的資源。
後記:
使用網上的代碼,是非常快速的編碼方式,當然,我也直接使用了網上的代碼,但移植到64位平台時出現了問題,導致浪費了不少的調試時間(使用gdb在代碼中查看,使用printf列印指標值,資料....痛苦!)。在上面的代碼中,我加入了警示注釋,希望自己記住,下次看到這樣的代碼就知道有問題。
指標之間的轉換編碼器是不會警示,所以也沒有多注意,那個一整形指標轉換成size_t指標類型時,問題在不同平台就會產生問題。size_t定義為平台的資料長度,而int在依然是32位(不保證不變)。
int a = 10;
int *pa = &a;
size_t b;
size_t *pb;
pb = pa;
b = *pb;
printf("a:%d, b:%zd/n", a, b);
如上一段簡單的代碼,在32位平台上輸出都是10,64位平台之上輸出為a:10, b:-1306831365601230838
後面那個值是不定的,如果使用-Wall 編碼選項,gcc會報一個警告在pb = pa;如果你在32位平台使用pb = (size_t*)pb;那編碼器就不會警示,而網上的一份代碼就是這樣。這也不能責怪源作者,可以環境的不同,考慮自然也就不多了。
所以,當指標類型轉換的時候,一定要注意這個類型,你是否很瞭解。長度是不是一樣的?否則問題出現在平台問題上,是很常難以找到問題所在的。