libxml2.7.8 c++ 解析xml檔案 中文轉換
C++的XML程式設計經驗――LIBXML2庫使用指南
寫這篇文章的原因有如下幾點:1)C++標準庫中沒有操作XML的方法,用C++操作XML檔案必須熟悉一種函式庫,LIBXML2是其中一種很優秀的XML庫,而且它同時支援多種程式語言;2)LIBXML2庫的Tutorial寫得不太好,尤其是編碼轉換的部分,不適用於中文編碼的轉換;3)網上的大多數關於Libxml2的介紹僅僅是翻譯了自帶的資料,沒有詳細介紹如何在windows平臺下進行程式設計,更很少提到如何解決中文問題。
基於以上幾點原因,決定寫一個在Windows平臺下,使用C/C++語言,應用LibXml2庫來進行xml文件操作,同時使用ICONV庫進行中文編碼轉換的文件。其中還涉及了
下載與安裝LIBXML2和ICONV
Libxml2是一個C語言的XML程式庫,可以簡單方便的提供對XML文件的各種操作,並且支援XPATH查詢,以及部分的支援XSLT轉換等功能。Libxml2的下載地址是,完全版的庫是開源的,並且帶有例子程式和說明文件。最好將這個庫先下載下來,因為這樣可以檢視其中的文件和例子。
windows版本的的下載地址是;這個版本只提供了標頭檔案、庫檔案和dll,不包含原始碼、例子程式和文件。在文字中,只需要下載
在程式設計的時候,我們使用windows版本的libxml2、zlib和iconv,將其解壓縮到指定資料夾,例如D:"libxml2-2.6.30.win32,D:"zlib-1.2.3.win32以及D:"iconv-1.9.2.win32。事實上,我們知道在windows下面使用標頭檔案、庫檔案和
注意:要在path變數中加上D:"iconv-1.9.2.win32"bin;D:"zlib-1.2.3.win32"bin;D:"libxml2-2.6.30.win32"bin這三個地址,否則在執行的時候就找不到。或者使用更簡單的方法,把其中的三個dll到拷貝到system32目錄中。
有兩種方法來編譯連結基於libxml2的程式,第一種是在VC環境中設定lib和include路徑,並在link設定中新增libxml2.lib和iconv.lib;第二種是用編譯器選項告訴編譯器cl.exe標頭檔案的位置,並用連結器選項告訴連結器link.exe庫檔案的位置,同時在windows環境變數path中新增libxml2中bin資料夾的位置,以便於程式執行時可以找到dll(也可以將dll拷貝到system32目錄下)。顯然我選擇了第二種,那麼編譯連結一個名為CreateXmlFile.cpp原始檔的命令如下:
cl /c /I D:"iconv-1.9.2.win32"include /I D:"libxml2-2.6.30.win32"include CreateXmlFile.cpp
link /libpath:D:"iconv-1.9.2.win32"lib /libpath:D:"libxml2-2.6.30.win32"lib CreateXmlFile.obj iconv.lib libxml2.lib
顯然這樣很費時,那麼再不用makefile就顯得矯情了,於是,一個典型的使用nmake.exe(VC自帶的makefile工具)的檔案如下:MAKEFILE
#
# 本目錄下所有原始碼的makefile,使用方法是nmake TARGET_NAME=原始碼檔名字(不加字尾)
# 例如 nmake TARGET_NAME=CreateXmlFile
# Author: Wang Xuebin
#
# Flags - 編譯debug版本
#
#指定要使用的庫的路徑,需要使用者修改的變數一般放在makefile檔案的最上面
LIBXML2_HOME = D:"libxml2-2.6.30.win32
ICONV_HOME = D:"iconv-1.9.2.win32
#指定編譯器選項,/c表明cl命令只編譯不連結;/MTd表明使用多執行緒debug庫;/Zi表明產生完整的除錯資訊;
#/Od表明關閉編譯優化;/D _DEBUG表明定義一個名為_DEBUG的巨集
CPP_FLAGS=/c /MTd /Zi /Od /D _DEBUG
#連結選項,/DEBUG表明建立Debug資訊
EXE_LINK_FLAGS=/DEBUG
#指定連結的庫
LIBS=iconv.lib libxml2.lib
#指定編譯路徑選項,連結路徑選項
INCLUDE_FLAGS= /I $(LIBXML2_HOME)"include /I $(ICONV_HOME)"include
LIB_PATH_FLAGS = /libpath:$(ICONV_HOME)"lib /libpath:$(LIBXML2_HOME)"lib
#################################################
#
# Targets 目標
#
$(TARGET_NAME) : $(TARGET_NAME).exe
clean : $(TARGET_NAME).exe
$(TARGET_NAME).obj : $(TARGET_NAME).cpp
cl $(CPP_FLAGS) $(INCLUDE_FLAGS) $(TARGET_NAME).cpp
$(TARGET_NAME).exe : $(TARGET_NAME).obj
link $(EXE_LINK_FLAGS) $(LIB_PATH_FLAGS) $(TARGET_NAME).obj $(LIBS)
clean : $(TARGET_NAME).exe
del $(TARGET_NAME).exe
del $(TARGET_NAME).obj
del $(TARGET_NAME).ilk
del $(TARGET_NAME).pdb
本文不準備介紹makefile的寫法,但後續例子程式的編譯連結依葫蘆畫瓢都沒有問題,執行編譯連結的命令如下:
nmake TARGET_NAME=CreateXmlFile
執行清理的命令如下:
nmake TARGET_NAME=CreateXmlFile clean
Libxml2中的資料型別和函式
一個函式庫中可能有幾百種資料型別以及幾千個函式,但是記住大師的話,90%的功能都是由30%的內容提供的。對於libxml2,我認為搞懂以下的資料型別和函式就足夠了。
2.1內部字元型別xmlChar
xmlChar是Libxml2中的字元型別,庫中所有字元、字串都是基於這個資料型別。事實上它的定義是:xmlstring.h
typedef unsigned char xmlChar;
使用unsigned char作為內部字元格式是考慮到它能很好適應UTF-8編碼,而UTF-8編碼正是libxml2的內部編碼,其它格式的編碼要轉換為這個編碼才能在libxml2中使用。
還經常可以看到使用xmlChar*作為字串型別,很多函式會返回一個動態分配記憶體的xmlChar*變數,使用這樣的函式時記得要手動刪除記憶體。
2.2xmlChar相關函式
如同標準c中的char型別一樣,xmlChar也有動態記憶體分配、字串操作等相關函式。例如xmlMalloc是動態分配記憶體的函式;xmlFree是配套的釋放記憶體函式;xmlStrcmp是字串比較函式等等。
基本上xmlChar字串相關函式都在xmlstring.h中定義;而動態記憶體分配函式在xmlmemory.h中定義。
2.3xmlChar*與其它型別之間的轉換
另外要注意,因為總是要在xmlChar*和char*之間進行型別轉換,所以定義了一個巨集BAD_CAST,其定義如下:xmlstring.h
#define BAD_CAST (xmlChar *)
原則上來說,unsignedchar和char之間進行強制型別轉換是沒有問題的。
2.4文件型別xmlDoc、指標xmlDocPtr
xmlDoc是一個struct,儲存了一個xml的相關資訊,例如檔名、文件型別、子節點等等;xmlDocPtr等於xmlDoc*,它搞成這個樣子總讓人以為是智慧指標,其實不是,要手動刪除的。
xmlNewDoc函式建立一個新的文件指標。
xmlParseFile函式以預設方式讀入一個UTF-8格式的文件,並返回文件指標。
xmlReadFile函式讀入一個帶有某種編碼的xml文件,並返回文件指標;細節見libxml2參考手冊。
xmlFreeDoc釋放文件指標。特別注意,當你呼叫xmlFreeDoc時,該文件所有包含的節點記憶體都被釋放,所以一般來說不需要手動呼叫xmlFreeNode或者xmlFreeNodeList來釋放動態分配的節點記憶體,除非你把該節點從文件中移除了。一般來說,一個文件中所有節點都應該動態分配,然後加入文件,最後呼叫xmlFreeDoc一次釋放所有節點申請的動態記憶體,這也是為什麼我們很少看見xmlNodeFree的原因。
xmlSaveFile將文件以預設方式存入一個檔案。
xmlSaveFormatFileEnc可將文件以某種編碼/格式存入一個檔案中。
2.5節點型別xmlNode、指標xmlNodePtr
節點應該是xml中最重要的元素了,xmlNode代表了xml文件中的一個節點,實現為一個struct,內容很豐富:tree.h
typedef struct _xmlNode xmlNode;
typedef xmlNode *xmlNodePtr;
struct _xmlNode {
void *_private;
xmlElementType type;
const xmlChar *name;
struct _xmlNode *children;
struct _xmlNode *last;
struct _xmlNode *parent;
struct _xmlNode *next;
struct _xmlNode *prev;
struct _xmlDoc *doc;
xmlNs *ns;
xmlChar *content;
struct _xmlAttr *properties;
xmlNs *nsDef;
void *psvi;
unsigned short line;
unsigned short extra;
};
可以看到,節點之間是以連結串列和樹兩種方式同時組織起來的,next和prev指標可以組成連結串列,而parent和children可以組織為樹。同時還有以下重要元素:
l節點中的文字內容:content;
l節點所屬文件:doc;
l節點名字:name;
l節點的namespace:ns;
l節點屬性列表:properties;
Xml文件的操作其根本原理就是在節點之間移動、查詢節點的各項資訊,並進行增加、刪除、修改的操作。
xmlDocSetRootElement函式可以將一個節點設定為某個文件的根節點,這是將文件與節點連線起來的重要手段,當有了根結點以後,所有子節點就可以依次連線上根節點,從而組織成為一個xml樹。
2.6節點集合型別xmlNodeSet、指標xmlNodeSetPtr
節點集合代表一個由節點組成的變數,節點集合只作為Xpath的查詢結果而出現(XPATH的介紹見後面),因此被定義在xpath.h中,其定義如下:
typedef struct _xmlNodeSet xmlNodeSet;
typedef xmlNodeSet *xmlNodeSetPtr;
struct _xmlNodeSet {
int nodeNr;
int nodeMax;
xmlNodePtr *nodeTab;
};
可以看出,節點集合有三個成員,分別是節點集合的節點數、最大可容納的節點數,以及節點陣列頭指標。對節點集合中各個節點的訪問方式很簡單,如下:
xmlNodeSetPtr nodeset = XPATH查詢結果;
for (int i = 0; i < nodeset->nodeNr; i++)
{
nodeset->nodeTab[i];
}
注意,libxml2是一個c函式庫,因此其函式和資料型別都使用c語言的方式來處理。如果是c++,我想我寧願用STL中的vector來表示一個節點集合更好,而且沒有記憶體洩漏或者溢位的擔憂。
3.簡單xml操作例子
瞭解以上基本知識之後,就可以進行一些簡單的xml操作了。當然,還沒有涉及到內碼轉換(使得xml中可以處理中文)、xpath等較複雜的操作。
3.1建立xml文件
有了上面的基礎,建立一個xml文件顯得非常簡單,其流程如下:
l用xmlNewDoc函式建立一個文件指標doc;
l用xmlNewNode函式建立一個節點指標root_node;
l用xmlDocSetRootElement將root_node設定為doc的根結點;
l給root_node新增一系列的子節點,並設定子節點的內容和屬性;
l用xmlSaveFile將xml文件存入檔案;
l用xmlFreeDoc函式關閉文件指標,並清除本文件中所有節點動態申請的記憶體。
注意,有多種方式可以新增子節點:第一是用xmlNewTextChild直接新增一個文字子節點;第二是先建立新節點,然後用xmlAddChild將新節點加入上層節點。
原始碼檔案是CreateXmlFile.cpp,如下:
#include <stdio.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <iostream.h>
int main()
{
//定義文件和節點指標
xmlDocPtr doc = xmlNewDoc(BAD_CAST"1.0");
xmlNodePtr root_node = xmlNewNode(NULL,BAD_CAST"root");
//設定根節點
xmlDocSetRootElement(doc,root_node);
//在根節點中直接建立節點
xmlNewTextChild(root_node, NULL, BAD_CAST "newNode1", BAD_CAST "newNode1 content");
xmlNewTextChild(root_node, NULL, BAD_CAST "newNode2", BAD_CAST "newNode2 content");
xmlNewTextChild(root_node, NULL, BAD_CAST "newNode3", BAD_CAST "newNode3 content");
//建立一個節點,設定其內容和屬性,然後加入根結點
xmlNodePtr node = xmlNewNode(NULL,BAD_CAST"node2");
xmlNodePtr content = xmlNewText(BAD_CAST"NODE CONTENT");
xmlAddChild(root_node,node);
xmlAddChild(node,content);
xmlNewProp(node,BAD_CAST"attribute",BAD_CAST "yes");
//建立一個兒子和孫子節點
node = xmlNewNode(NULL, BAD_CAST "son");
xmlAddChild(root_node,node);
xmlNodePtr grandson = xmlNewNode(NULL, BAD_CAST "grandson");
xmlAddChild(node,grandson);
xmlAddChild(grandson, xmlNewText(BAD_CAST "This is a grandson node"));
//儲存xml文件
int nRel = xmlSaveFile("CreatedXml.xml",doc);
if (nRel != -1)
{
cout<<"一個xml文件被建立,寫入"<<nRel<<"個位元組"<<endl;
}
//釋放文件內節點動態申請的記憶體
xmlFreeDoc(doc);
return 1;
}
編譯連結命令如下:
nmake TARGET_NAME=CreateXmlFile
然後執行可執行檔案CreateXmlFile.exe,會生成一個xml檔案CreatedXml.xml,開啟後如下所示:
<?xml version="1.0"?>
<root>
<newNode1>newNode1 content</newNode1>
<newNode2>newNode2 content</newNode2>
<newNode3>newNode3 content</newNode3>
<node2 attribute="yes">NODE CONTENT</node2>
<son>
<grandson>This is a grandson node</grandson>
</son>
</root>
最好使用類似XMLSPY這樣的工具開啟,因為這些工具可以自動整理xml檔案的柵格,否則很有可能是沒有任何換行的一個xml檔案,可讀性較差。
3.2解析xml文件
解析一個xml文件,從中取出想要的資訊,例如節點中包含的文字,或者某個節點的屬性,其流程如下:
l用xmlReadFile函式讀出一個文件指標doc;
l用xmlDocGetRootElement函式得到根節點curNode;
lcurNode->xmlChildrenNode就是根節點的子節點集合;
l輪詢子節點集合,找到所需的節點,用xmlNodeGetContent取出其內容;
l用xmlHasProp查詢含有某個屬性的節點;
l取出該節點的屬性集合,用xmlGetProp取出其屬性值;
l用xmlFreeDoc函式關閉文件指標,並清除本文件中所有節點動態申請的記憶體。
注意:節點列表的指標依然是xmlNodePtr,屬性列表的指標也是xmlAttrPtr,並沒有xmlNodeList或者xmlAttrList這樣的型別。看作列表的時候使用它們的next和prev連結串列指標來進行輪詢。只有在Xpath中有xmlNodeSet這種型別,其使用方法前面已經介紹了。
原始碼如下:ParseXmlFile.cpp
#include <libxml/parser.h>
#include <iostream.h>
int main(int argc, char* argv[])
{
xmlDocPtr doc; //定義解析文件指標
xmlNodePtr curNode; //定義結點指標(你需要它為了在各個結點間移動)
xmlChar *szKey; //臨時字串變數
char *szDocName;
if (argc <= 1)
{
printf("Usage: %s docname"n", argv[0]);
return(0);
}
szDocName = argv[1];
doc = xmlReadFile(szDocName,"GB2312",XML_PARSE_RECOVER); //解析檔案
//檢查解析文件是否成功,如果不成功,libxml將指一個註冊的錯誤並停止。
//一個常見錯誤是不適當的編碼。XML標準文件除了用UTF-8或UTF-16外還可用其它編碼儲存。
//如果文件是這樣,libxml將自動地為你轉換到UTF-8。更多關於XML編碼資訊包含在XML標準中.
if (NULL == doc)
{
fprintf(stderr,"Document not parsed successfully. "n");
return -1;
}
curNode = xmlDocGetRootElement(doc); //確定文件根元素
if (NULL == curNode)
{
fprintf(stderr,"empty document"n");
xmlFreeDoc(doc);
return -1;
}
if (xmlStrcmp(curNode->name, BAD_CAST "root"))
{
fprintf(stderr,"document of the wrong type, root node != root");
xmlFreeDoc(doc);
return -1;
}
curNode = curNode->xmlChildrenNode;
xmlNodePtr propNodePtr = curNode;
while(curNode != NULL)
{
//取出節點中的內容
if ((!xmlStrcmp(curNode->name, (const xmlChar *)"newNode1")))
{
szKey = xmlNodeGetContent(curNode);
printf("newNode1: %s"n", szKey);
xmlFree(szKey);
}
//查詢帶有屬性attribute的節點
if (xmlHasProp(curNode,BAD_CAST "attribute"))
{
propNodePtr = curNode;
}
curNode = curNode->next;
}
//查詢屬性
xmlAttrPtr attrPtr = propNodePtr->properties;
while (attrPtr != NULL)
{
if (!xmlStrcmp(attrPtr->name, BAD_CAST "attribute"))
{
xmlChar* szAttr = xmlGetProp(propNodePtr,BAD_CAST "attribute");
cout<<"get attribute = "<<szAttr<<endl;
xmlFree(szAttr);
}
attrPtr = attrPtr->next;
}
xmlFreeDoc(doc);
return 0;
}
編譯連結命令如下:
nmake TARGET_NAME=ParseXmlFile
執行命令如下,使用第一次建立的xml檔案作為輸入:
ParseXmlFile.exe CreatedXml.xml
觀察原始碼可發現,所有以查詢方式得到的xmlChar*字串都必須使用xmlFree函式手動釋放。否則會造成記憶體洩漏。
3.3修改xml文件
有了上面的基礎,修改xml文件的內容就很簡單了。首先開啟一個已經存在的xml文件,順著根結點找到需要新增、刪除、修改的地方,呼叫相應的xml函式對節點進行增、刪、改操作。原始碼見ChangeXmlFile,編譯連結方法如上。執行下面的命令:
ChangeXmlFile.exe CreatedXml.xml
可以得到一個修改後的xml文件ChangedXml.xml,如下:
<?xml version="1.0"?>
<root>
<newNode2>content changed</newNode2>
<newNode3 newAttr="YES">newNode3 content</newNode3>
<node2 attribute="no">NODE CONTENT</node2>
<son>
<grandson>This is a grandson node</grandson>
<newGrandSon>new content</newGrandSon>
</son>
</root>
需要注意的是,並沒有xmlDelNode或者xmlRemoveNode函式,我們刪除節點使用的是以下一段程式碼:
if (!xmlStrcmp(curNode->name, BAD_CAST "newNode1"))
{
xmlNodePtr tempNode;
tempNode = curNode->next;
xmlUnlinkNode(curNode);
xmlFreeNode(curNode);
curNode = tempNode;
continue;
}
即將當前節點從文件中斷鏈(unlink),這樣本文件就不會再包含這個子節點。這樣做需要使用一個臨時變數來儲存斷鏈節點的後續節點,並記得要手動刪除斷鏈節點的記憶體。
3.4 使用XPATH查詢xml文件
簡而言之,XPATH之於xml,好比SQL之於關係資料庫。要在一個複雜的xml文件中查詢所需的資訊,XPATH簡直是必不可少的工具。XPATH語法簡單易學,並且有一個很好的官方教程,見。這個站點的XML各種教程齊全,並且有包括中文在內的各國語言版本,真是讓我喜歡到非常!
使用XPATH之前,必須首先熟悉幾個資料型別和函式,它們是使用XPATH的前提。在libxml2中使用Xpath是非常簡單的,其流程如下:
l 定義一個XPATH上下文指標xmlXPathContextPtrcontext,並且使用xmlXPathNewContext函式來初始化這個指標;
l 定義一個XPATH物件指標xmlXPathObjectPtr result,並且使用xmlXPathEvalExpression函式來計算Xpath表示式,得到查詢結果,將結果存入物件指標中;
l 使用result->nodesetval得到節點集合指標,其中包含了所有符合Xpath查詢結果的節點;
l 使用xmlXPathFreeContext釋放上下文指標;
l 使用xmlXPathFreeObject釋放Xpath物件指標;
具體的使用方法可以看XpathForXmlFile.cpp的這一段程式碼,其功能是查詢符合某個Xpath語句的物件指標:
xmlXPathObjectPtr getNodeSet(xmlDocPtr doc, const xmlChar *szXpath)
{
xmlXPathContextPtr context; //XPATH上下文指標
xmlXPathObjectPtr result; //XPATH物件指標,用來儲存查詢結果
context = xmlXPathNewContext(doc); //建立一個XPath上下文指標
if (context == NULL)
{
printf("context is NULL"n");
return NULL;
}
result = xmlXPathEvalexpression_r(szXpath, context); //查詢XPath表示式,得到一個查詢結果
xmlXPathFreeContext(context); //釋放上下文指標
if (result == NULL)
{
printf("xmlXPathEvalExpression return NULL"n");
return NULL;
}
if (xmlXPathNodeSetIsEmpty(result->nodesetval)) //檢查查詢結果是否為空
{
xmlXPathFreeObject(result);
printf("nodeset is empty"n");
return NULL;
}
return result;
}
一個完整的使用Xpath的例子在程式碼XpathForXmlFile.cpp中,它查詢一個xml檔案中符合"/root/node2[@attribute='yes']"語句的結果,並且將找到的節點的屬性和內容打印出來。編譯連結命令如下:
nmake TARGET_NAME=XpathForXmlFile
執行方式如下:
XpathForXmlFile.exe CreatedXml.xml
觀察結果可以看出找到了一個節點,即root下面node2節點,它的attribute屬性值正好等於yes。更多關於Xpath的內容可以參考XPATH官方手冊。只有掌握了XPATH,才掌握了使用大型XML檔案的方法,否則每尋找一個節點都要從根節點找起,會把人累死。
4.用ICONV解決XML中的中文問題
Libxml2中預設的內碼是UTF-8,所有使用libxml2進行處理的xml檔案,必須首先顯式或者預設的轉換為UTF-8編碼才能被處理。
要在xml中使用中文,就必須能夠在UTF-8和GB2312內碼(較常用的一種簡體中文編碼)之間進行轉換。Libxml2提供了預設的內碼轉換機制,並且在libxml2的Tutorial中有一個例子,事實證明這個例子並不適合用來轉換中文。
所以需要我們顯式的使用ICONV來進行內碼轉換,libxml2本身也是使用ICONV進行轉換的。ICONV是一個專門用來進行編碼轉換的庫,基本上支援目前所有常用的編碼。它是glibc庫的一個部分,常常被用於UNIX系統中。當然,在windows下面使用也沒有任何問題。前面已經提到了ICONV的安裝和使用方法,這裡主要講一下程式設計相關問題。
本節其實和xml以及libxml2沒有太大關係,你可以把它簡單看作是一個編碼轉換方面的專題。我們僅僅需要學會使用兩個函式就可以了,即從UTF-8轉換到GB2312的函式u2g,以及反向轉換的函式g2u,原始碼在wxb_codeConv.c中:
#include "iconv.h"
#include <string.h>
//程式碼轉換:從一種編碼轉為另一種編碼
int code_convert(char* from_charset, char* to_charset, char* inbuf,
int inlen, char* outbuf, int outlen)
{
iconv_t cd;
char** pin = &inbuf;
char** pout = &outbuf;
cd = iconv_open(to_charset,from_charset);
if(cd == 0)
return -1;
memset(outbuf,0,outlen);
if(iconv(cd,(const char**)pin,(unsigned int *)&inlen,pout,(unsigned int*)&outlen)
== -1)
return -1;
iconv_close(cd);
return 0;
}
//UNICODE碼轉為GB2312碼
//成功則返回一個動態分配的char*變數,需要在使用完畢後手動free,失敗返回NULL
char* u2g(char *inbuf)
{
int nOutLen = 2 * strlen(inbuf) - 1;
char* szOut = (char*)malloc(nOutLen);
if (-1 == code_convert("utf-8","gb2312",inbuf,strlen(inbuf),szOut,nOutLen))
{
free(szOut);
szOut = NULL;
}
return szOut;
}
//GB2312碼轉為UNICODE碼
//成功則返回一個動態分配的char*變數,需要在使用完畢後手動free,失敗返回NULL
char* g2u(char *inbuf)
{
int nOutLen = 2 * strlen(inbuf) - 1;
char* szOut = (char*)malloc(nOutLen);
if (-1 == code_convert("gb2312","utf-8",inbuf,strlen(inbuf),szOut,nOutLen))
{
free(szOut);
szOut = NULL;
}
return szOut;
}
使用的時候將這個c檔案include到其它原始檔中。include一個c檔案並不奇怪,在c語言的年代我們常常這麼幹,唯一的害處的編譯連結出來的可執行程式體積變大了。當然這時因為我們這段程式碼很小的原因,再大一點我就要用dll了。
從UTF-8到GB2312的一個典型使用流程如下:
l得到一個UTF-8的字串szSrc;
l定義一個char*的字元指標szDes,並不需要給他動態審判記憶體;
lszDes = u2g(szSrc),這樣就可以得到轉換後的GB2312編碼的字串;
l使用完這個字串後使用free(szDes)來釋放記憶體。
本文並不準備講述iconv中的函式細節,因為那幾個函式以及資料型別都非常簡單,我們還是重點看一下如何在libxml2中使用編碼轉換來處理帶有中文的xml檔案。下面是使用以上方法來建立一個帶有中文的XML檔案的例子程式CreateXmlFile_cn.cpp,原始碼如下:
#include <stdio.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <iostream.h>
#include "wxb_codeConv.c" //自己寫的編碼轉換函式
int main(int argc, char **argv)
{
//定義文件和節點指標
xmlDocPtr doc = xmlNewDoc(BAD_CAST"1.0");
xmlNodePtr root_node = xmlNewNode(NULL,BAD_CAST"root");
//設定根節點
xmlDocSetRootElement(doc,root_node);
//一箇中文字串轉換為UTF-8字串,然後寫入
char* szOut = g2u("節點1的內容");
//在根節點中直接建立節點
xmlNewTextChild(root_node, NULL, BAD_CAST "newNode1", BAD_CAST "newNode1 content");
xmlNewTextChild(root_node, NULL, BAD_CAST "newNode2", BAD_CAST "newNode2 content");
xmlNewTextChild(root_node, NULL, BAD_CAST "newNode3", BAD_CAST "newNode3 content");
xmlNewChild(root_node, NULL, BAD_CAST "node1",BAD_CAST szOut);
free(szOut);
//建立一個節點,設定其內容和屬性,然後加入根結點
xmlNodePtr node = xmlNewNode(NULL,BAD_CAST"node2");
xmlNodePtr content = xmlNewText(BAD_CAST"NODE CONTENT");
xmlAddChild(root_node,node);
xmlAddChild(node,content);
szOut = g2u("屬性值");
xmlNewProp(node,BAD_CAST"attribute",BAD_CAST szOut);
free(szOut);
//建立一箇中文節點
szOut = g2u("中文節點");
xmlNewChild(root_node, NULL, BAD_CAST szOut,BAD_CAST "content of chinese node");
free(szOut);
//儲存xml文件
int nRel = xmlSaveFormatFileEnc("CreatedXml_cn.xml",doc,"GB2312",1);
if (nRel != -1)
{
cout<<"一個xml文件被建立,寫入"<<nRel<<"個位元組"<<endl;
}
xmlFreeDoc(doc);
return 1;
}
編譯連結命令如下:
nmake TARGET_NAME=CreateXmlFile_cn
完成後執行CreateXmlFile_cn.exe可以生成一個xml檔案CreatedXml_cn.xml,其內容如下:
<?xml version="1.0" encoding="GB2312"?>
<root>
<newNode1>newNode1 content</newNode1>
<newNode2>newNode2 content</newNode2>
<newNode3>newNode3 content</newNode3>
<node1>節點1的內容</node1>
<node2 attribute="屬性值">NODE CONTENT</node2>
<中文節點>content of chinese node</中文節點>
</root>
觀察可知,節點的名稱、內容、屬性都可以使用中文了。在解析、修改和查詢XML文件時都可以使用上面的方法,只要記住,進入xml文件之前將中文編碼轉換為UTF-8編碼;從XML中取出資料時,不管三七二十一都可以轉換為GB2312再用,否則你很有可能見到傳說中的亂碼!
5.用XML來做點什麼
有了以上的基礎,相信已經可以順利的在c/c++程式中使用XML文件了。那麼,我們到底要用XML來做什麼呢?我隨便說一說自己的想法:
第一,可以用來作為配置檔案。例如很多元件就是用XML來做配置檔案;當然,我們知道用INI做配置檔案更簡單,只要熟悉兩個函式就可以了;不過,複雜一點的配置檔案我還是建議採用XML;
第二,可以用來作為在程式之間傳送資料的格式,這樣的話最好給你的xml先定義一個XMLSchema,這樣的資料首先可以做一個良構校驗,還可以來一個Schema校驗,如此的話出錯率會比沒有格式的資料小得多。目前XML已經廣泛作為網路之間的資料格式了;
第三,可以用來作為你自定義的資料儲存格式,例如物件持久化之類的功能;
最後,可以用來顯示你的技術很高深,本來你要儲存一個1,結果你這樣儲存了:
<?xml version="1.0" encoding="GB2312"?>
<root>
<My_Program_Code content="1"></My_Program_Code>
</root>
然後再用libxml2取出來,最好還來幾次編碼轉換,是不是讓人覺得你很牛呢,哈哈!說笑了,千萬不要這麼做。