寫這篇文章的緣由有以下幾點:1)C++標準庫中沒有操做XML的方法,用C++操做XML文件必須熟悉一種函數庫,LIBXML2是其中一種很優秀的XML庫,並且它同時支持多種編程語言;2)LIBXML2庫的Tutorial寫得不太好,尤爲是編碼轉換的部分,不適用於中文編碼的轉換;3)網上的大多數關於Libxml2的介紹僅僅是翻譯了自帶的資料,沒有詳細介紹如何在windows平臺下進行編程,更不多提到如何解決中文問題。 html
基於以上幾點緣由,決定寫一個在Windows平臺下,使用C/C++語言,應用LibXml2庫來進行xml文檔操做,同時使用ICONV庫進行中文編碼轉換的文檔。其中還涉及了Makefile、XPATH等相關內容。本文中全部的源代碼在http://www.blogjava.net/Files/wxb_nudt/xml_src.rar。 java
Libxml2是一個C語言的XML程序庫,能夠簡單方便的提供對XML文檔的各類操做,而且支持XPATH查詢,以及部分的支持XSLT轉換等功能。Libxml2的下載地址是http://xmlsoft.org/,徹底版的庫是開源的,而且帶有例子程序和說明文檔。最好將這個庫先下載下來,由於這樣能夠查看其中的文檔和例子。 node
windows版本的的下載地址是http://www.zlatkovic.com/libxml.en.html;這個版本只提供了頭文件、庫文件和dll,不包含源代碼、例子程序和文檔。在文本中,只須要下載libxml2庫、iconv庫和zlib庫就好了(注意,libxml2庫依賴iconv和zlib庫,本文中重點關注libxml2和iconv,zlib不介紹),我使用的版本是libxml2-2.6.30.win32.zip、zlib-1.2.3.win32.zip和iconv-1.9.2.win32.zip。 ios
在編程的時候,咱們使用windows版本的libxml2、zlib和iconv,將其解壓縮到指定文件夾,例如D:"libxml2-2.6.30.win32,D:"zlib-1.2.3.win32以及D:"iconv-1.9.2.win32。事實上,咱們知道在windows下面使用頭文件、庫文件和dll是不須要安裝的,它又沒有使用任何須要註冊的組件或者數據庫,只須要告訴編譯器和連接器這些資源的位置就能夠了。 c++
注意:要在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 windows
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
一個函數庫中可能有幾百種數據類型以及幾千個函數,可是記住大師的話,90%的功能都是由30%的內容提供的。對於libxml2,我認爲搞懂如下的數據類型和函數就足夠了。
xmlChar是Libxml2中的字符類型,庫中全部字符、字符串都是基於這個數據類型。事實上它的定義是:xmlstring.h
typedef unsigned char xmlChar;
使用unsigned char做爲內部字符格式是考慮到它能很好適應UTF-8編碼,而UTF-8編碼正是libxml2的內部編碼,其它格式的編碼要轉換爲這個編碼才能在libxml2中使用。
還常常能夠看到使用xmlChar*做爲字符串類型,不少函數會返回一個動態分配內存的xmlChar*變量,使用這樣的函數時記得要手動刪除內存。
如同標準c中的char類型同樣,xmlChar也有動態內存分配、字符串操做等相關函數。例如xmlMalloc是動態分配內存的函數;xmlFree是配套的釋放內存函數;xmlStrcmp是字符串比較函數等等。
基本上xmlChar字符串相關函數都在xmlstring.h中定義;而動態內存分配函數在xmlmemory.h中定義。
另外要注意,由於老是要在xmlChar*和char*之間進行類型轉換,因此定義了一個宏BAD_CAST,其定義以下:xmlstring.h
#define BAD_CAST (xmlChar *)
原則上來講,unsigned char和char之間進行強制類型轉換是沒有問題的。
xmlDoc是一個struct,保存了一個xml的相關信息,例如文件名、文檔類型、子節點等等;xmlDocPtr等於xmlDoc*,它搞成這個樣子總讓人覺得是智能指針,其實不是,要手動刪除的。
xmlNewDoc函數建立一個新的文檔指針。
xmlParseFile函數以默認方式讀入一個UTF-8格式的文檔,並返回文檔指針。
xmlReadFile函數讀入一個帶有某種編碼的xml文檔,並返回文檔指針;細節見libxml2參考手冊。
xmlFreeDoc釋放文檔指針。特別注意,當你調用xmlFreeDoc時,該文檔全部包含的節點內存都被釋放,因此通常來講不須要手動調用xmlFreeNode或者xmlFreeNodeList來釋放動態分配的節點內存,除非你把該節點從文檔中移除了。通常來講,一個文檔中全部節點都應該動態分配,而後加入文檔,最後調用xmlFreeDoc一次釋放全部節點申請的動態內存,這也是爲何咱們不多看見xmlNodeFree的緣由。
xmlSaveFile將文檔以默認方式存入一個文件。
xmlSaveFormatFileEnc可將文檔以某種編碼/格式存入一個文件中。
節點應該是xml中最重要的元素了,xmlNode表明了xml文檔中的一個節點,實現爲一個struct,內容很豐富:tree.h
typedef struct _xmlNode xmlNode;
typedef xmlNode *xmlNodePtr;
struct _xmlNode {
void *_private;/* application data */
xmlElementType type; /* type number, must be second ! */
const xmlChar *name; /* the name of the node, or the entity */
struct _xmlNode *children; /* parent->childs link */
struct _xmlNode *last; /* last child link */
struct _xmlNode *parent;/* child->parent link */
struct _xmlNode *next; /* next sibling link */
struct _xmlNode *prev; /* previous sibling link */
struct _xmlDoc *doc;/* the containing document */
/* End of common part */
xmlNs *ns; /* pointer to the associated namespace */
xmlChar *content; /* the content */
struct _xmlAttr *properties;/* properties list */
xmlNs *nsDef; /* namespace definitions on this node */
void *psvi;/* for type/PSVI informations */
unsigned short line; /* line number */
unsigned short extra; /* extra data for XPath/XSLT */
};
能夠看到,節點之間是以鏈表和樹兩種方式同時組織起來的,next和prev指針能夠組成鏈表,而parent和children能夠組織爲樹。同時還有如下重要元素:
l 節點中的文字內容:content;
l 節點所屬文檔:doc;
l 節點名字:name;
l 節點的namespace:ns;
l 節點屬性列表:properties;
Xml文檔的操做其根本原理就是在節點之間移動、查詢節點的各項信息,並進行增長、刪除、修改的操做。
xmlDocSetRootElement函數能夠將一個節點設置爲某個文檔的根節點,這是將文檔與節點鏈接起來的重要手段,當有了根結點之後,全部子節點就能夠依次鏈接上根節點,從而組織成爲一個xml樹。
節點集合表明一個由節點組成的變量,節點集合只做爲Xpath的查詢結果而出現(XPATH的介紹見後面),所以被定義在xpath.h中,其定義以下:
/*
* A node-set (an unordered collection of nodes without duplicates).
*/
typedef struct _xmlNodeSet xmlNodeSet;
typedef xmlNodeSet *xmlNodeSetPtr;
struct _xmlNodeSet {
int nodeNr; /* number of nodes in the set */
int nodeMax; /* size of the array as allocated */
xmlNodePtr *nodeTab;/* array of nodes in no particular order */
/* @@ with_ns to check wether namespace nodes should be looked at @@ */
};
能夠看出,節點集合有三個成員,分別是節點集合的節點數、最大可容納的節點數,以及節點數組頭指針。對節點集合中各個節點的訪問方式很簡單,以下:
xmlNodeSetPtr nodeset = XPATH查詢結果;
for (int i = 0; i < nodeset->nodeNr; i++)
{
nodeset->nodeTab[i];
}
注意,libxml2是一個c函數庫,所以其函數和數據類型都使用c語言的方式來處理。若是是c++,我想我寧願用STL中的vector來表示一個節點集合更好,並且沒有內存泄漏或者溢出的擔心。
瞭解以上基本知識以後,就能夠進行一些簡單的xml操做了。固然,尚未涉及到內碼轉換(使得xml中能夠處理中文)、xpath等較複雜的操做。
有了上面的基礎,建立一個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,以下:
/********************************************************************
created: 2007/11/09
created: 9:11:2007 15:34
filename: CreateXmlFile.cpp
author: Wang xuebin
depend: libxml2.lib
build: nmake TARGET_NAME=CreateXmlFile
purpose: 建立一個xml文件
*********************************************************************/
#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文件,可讀性較差。
解析一個xml文檔,從中取出想要的信息,例如節點中包含的文字,或者某個節點的屬性,其流程以下:
l 用xmlReadFile函數讀出一個文檔指針doc;
l 用xmlDocGetRootElement函數獲得根節點curNode;
l curNode->xmlChildrenNode就是根節點的子節點集合;
l 輪詢子節點集合,找到所需的節點,用xmlNodeGetContent取出其內容;
l 用xmlHasProp查找含有某個屬性的節點;
l 取出該節點的屬性集合,用xmlGetProp取出其屬性值;
l 用xmlFreeDoc函數關閉文檔指針,並清除本文檔中全部節點動態申請的內存。
注意:節點列表的指針依然是xmlNodePtr,屬性列表的指針也是xmlAttrPtr,並無xmlNodeList或者xmlAttrList這樣的類型。看做列表的時候使用它們的next和prev鏈表指針來進行輪詢。只有在Xpath中有xmlNodeSet這種類型,其使用方法前面已經介紹了。
源代碼以下:ParseXmlFile.cpp
/******************************************************************** created: 2007/11/15 created: 15:11:2007 11:47 filename: ParseXmlFile.cpp author: Wang xuebin depend: libxml2.lib build: nmake TARGET_NAME=ParseXmlFile purpose: 解析xml文件 *********************************************************************/ #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; } /*在這個例子中,咱們須要確認文檔是正確的類型。「root」是在這個示例中使用文檔的根類型。*/ 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函數手動釋放。不然會形成內存泄漏。
有了上面的基礎,修改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),這樣本文檔就不會再包含這個子節點。這樣作須要使用一個臨時變量來存儲斷鏈節點的後續節點,並記得要手動刪除斷鏈節點的內存。
簡而言之,XPATH之於xml,比如SQL之於關係數據庫。要在一個複雜的xml文檔中查找所需的信息,XPATH簡直是必不可少的工具。XPATH語法簡單易學,而且有一個很好的官方教程,見http://www.zvon.org/xxl/XPathTutorial/Output_chi/introduction.html。這個站點的XML各類教程齊全,而且有包括中文在內的各國語言版本,真是讓我喜歡到很是!
使用XPATH以前,必須首先熟悉幾個數據類型和函數,它們是使用XPATH的前提。在libxml2中使用Xpath是很是簡單的,其流程以下:
l 定義一個XPATH上下文指針xmlXPathContextPtr context,而且使用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(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文件的方法,不然每尋找一個節點都要從根節點找起,會把人累死。
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中:
/********************************************************************
created: 2007/11/15
created: 15:11:2007 10:30
filename: wxb_codeConv.c
author: Wang xuebin
depend: iconv.lib
build: 不須要build,被包含到其它源代碼中
purpose: 提供從UTF-8到GB2312的內碼轉換,以及反向的轉換
*********************************************************************/
#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,並不須要給他動態審判內存;
l szDes = u2g(szSrc),這樣就能夠獲得轉換後的GB2312編碼的字符串;
l 使用完這個字符串後使用free(szDes)來釋放內存。
本文並不許備講述iconv中的函數細節,由於那幾個函數以及數據類型都很是簡單,咱們仍是重點看一下如何在libxml2中使用編碼轉換來處理帶有中文的xml文件。下面是使用以上方法來建立一個帶有中文的XML文件的例子程序CreateXmlFile_cn.cpp,源代碼以下:
/********************************************************************
created: 2007/11/17
created: 9:11:2007 15:34
filename: CreateXmlFile.cpp
author: Wang xuebin
depend: libxml2.lib iconv.lib
build: nmake TARGET_NAME=CreateXmlFile_cn
purpose: 建立一個xml文件,其中包含中文
*********************************************************************/
#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再用,不然你頗有可能見到傳說中的亂碼!
有了以上的基礎,相信已經能夠順利的在c/c++程序中使用XML文檔了。那麼,咱們到底要用XML來作什麼呢?我隨便說一說本身的想法:
第一,能夠用來做爲配置文件。例如不少組件就是用XML來作配置文件;固然,咱們知道用INI作配置文件更簡單,只要熟悉兩個函數就能夠了;不過,複雜一點的配置文件我仍是建議採用XML;
第二,能夠用來做爲在程序之間傳送數據的格式,這樣的話最好給你的xml先定義一個XML Schema,這樣的數據首先能夠作一個良構校驗,還能夠來一個Schema校驗,如此的話出錯率會比沒有格式的數據小得多。目前XML已經普遍做爲網絡之間的數據格式了;
第三,能夠用來做爲你自定義的數據存儲格式,例如對象持久化之類的功能;
最後,能夠用來顯示你的技術很高深,原本你要存儲一個1,結果你這樣存儲了:
<?xml version="1.0" encoding="GB2312"?>
<root>
<My_Program_Code content="1"></My_Program_Code>
</root>
而後再用libxml2取出來,最好還來幾回編碼轉換,是否是讓人以爲你很牛呢,哈哈!說笑了,千萬不要這麼作。
本文是實用編程技術的第四篇,有興趣的能夠看看個人前三篇:
另外,關於XML也能夠看看我寫的這幾篇博客: