LIBXML2庫使用指南2

3. 簡單xml操做例子html

http://blog.sina.com.cn/s/blog_4673bfa50100b0xj.htmljava

瞭解以上基本知識以後,就能夠進行一些簡單的xml操做了。固然,尚未涉及到內碼轉換(使得xml中能夠處理中文)、xpath等較複雜的操做。node

3.1 建立xml文檔ios

有了上面的基礎,建立一個xml文檔顯得很是簡單,其流程以下:c++

用xmlNewDoc函數建立一個文檔指針doc;數據庫

用xmlNewNode函數建立一個節點指針root_node;編程

用xmlDocSetRootElement將root_node設置爲doc的根結點;windows

給root_node添加一系列的子節點,並設置子節點的內容和屬性;網絡

用xmlSaveFile將xml文檔存入文件;函數

用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文檔,從中取出想要的信息,例如節點中包含的文字,或者某個節點的屬性,其流程以下:

用xmlReadFile函數讀出一個文檔指針doc;

用xmlDocGetRootElement函數獲得根節點curNode;

curNode->xmlChildrenNode就是根節點的子節點集合;

輪詢子節點集合,找到所需的節點,用xmlNodeGetContent取出其內容;

用xmlHasProp查找含有某個屬性的節點;

取出該節點的屬性集合,用xmlGetProp取出其屬性值;

用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語法簡單易學,而且有一個很好的官方教程,見http://www.zvon.org/xxl/XPathTutorial/Output_chi/introduction.html。這個站點的XML各類教程齊全,而且有包括中文在內的各國語言版本,真是讓我喜歡到很是!

使用XPATH以前,必須首先熟悉幾個數據類型和函數,它們是使用XPATH的前提。在libxml2中使用Xpath是很是簡單的,其流程以下:

定義一個XPATH上下文指針xmlXPathContextPtr context,而且使用xmlXPathNewContext函數來初始化這個指針;

定義一個XPATH對象指針xmlXPathObjectPtr result,而且使用xmlXPathEvalExpression函數來計算Xpath表達式,獲得查詢結果,將結果存入對象指針中;

使用result->nodesetval獲得節點集合指針,其中包含了全部符合Xpath查詢結果的節點;

使用xmlXPathFreeContext釋放上下文指針;

使用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文件的方法,不然每尋找一個節點都要從根節點找起,會把人累死。

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的一個典型使用流程以下:

獲得一個UTF-8的字符串szSrc;

定義一個char*的字符指針szDes,並不須要給他動態審判內存;

szDes = u2g(szSrc),這樣就能夠獲得轉換後的GB2312編碼的字符串;

使用完這個字符串後使用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先定義一個XML Schema,這樣的數據首先能夠作一個良構校驗,還能夠來一個Schema校驗,如此的話出錯率會比沒有格式的數據小得多。目前XML已經普遍做爲網絡之間的數據格式了;

第三,能夠用來做爲你自定義的數據存儲格式,例如對象持久化之類的功能;

最後,能夠用來顯示你的技術很高深,原本你要存儲一個1,結果你這樣存儲了:

<?xml version="1.0" encoding="GB2312"?>

<root>

 <My_Program_Code content="1"></My_Program_Code>

</root>

而後再用libxml2取出來,最好還來幾回編碼轉換,是否是讓人以爲你很牛呢,哈哈!說笑了,千萬不要這麼作。

6. 小結

本文是實用編程技術的第四篇,有興趣的能夠看看個人前三篇:

DLL編寫教程

紙黃金均價管理小軟件—黃金祕書

Socket編程指南及示例程序

另外,關於XML也能夠看看我寫的這幾篇博客:

XML的本質討論

XSL:轉換從哪裏開始?

數據庫和XML數據讀取性能比較

相關文章
相關標籤/搜索