• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            興海北路

            ---男兒仗劍自橫行
            <2011年4月>
            272829303112
            3456789
            10111213141516
            17181920212223
            24252627282930
            1234567

            統(tǒng)計

            • 隨筆 - 85
            • 文章 - 0
            • 評論 - 17
            • 引用 - 0

            常用鏈接

            留言簿(6)

            隨筆分類

            隨筆檔案

            收藏夾

            全是知識啊

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            C++的XML編程經(jīng)驗――LIBXML2庫使用指南

            寫這篇文章的原因有如下幾點:1)C++標準庫中沒有操作XML的方法,用C++操作XML文件必須熟悉一種函數(shù)庫,LIBXML2是其中一種很優(yōu)秀的XML庫,而且它同時支持多種編程語言;2)LIBXML2庫的Tutorial寫得不太好,尤其是編碼轉換的部分,不適用于中文編碼的轉換;3)網(wǎng)上的大多數(shù)關于Libxml2的介紹僅僅是翻譯了自帶的資料,沒有詳細介紹如何在windows平臺下進行編程,更很少提到如何解決中文問題。

            基于以上幾點原因,決定寫一個在Windows平臺下,使用C/C++語言,應用LibXml2庫來進行xml文檔操作,同時使用ICONV庫進行中文編碼轉換的文檔。其中還涉及了Makefile、XPATH等相關內容。本文中所有的源代碼在http://www.blogjava.net/Files/wxb_nudt/xml_src.rar。

            1.       下載與安裝LIBXML2和ICONV

            Libxml2是一個C語言的XML程序庫,可以簡單方便的提供對XML文檔的各種操作,并且支持XPATH查詢,以及部分的支持XSLT轉換等功能。Libxml2的下載地址是http://xmlsoft.org/,完全版的庫是開源的,并且?guī)в欣映绦蚝驼f明文檔。最好將這個庫先下載下來,因為這樣可以查看其中的文檔和例子。

            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。

            在編程的時候,我們使用windows版本的libxml2、zlib和iconv,將其解壓縮到指定文件夾,例如D:"libxml2-2.6.30.win32,D:"zlib-1.2.3.win32以及D:"iconv-1.9.2.win32。事實上,我們知道在windows下面使用頭文件、庫文件和dll是不需要安裝的,它又沒有使用任何需要注冊的組件或者數(shù)據(jù)庫,只需要告訴編譯器和鏈接器這些資源的位置就可以了。

            注意:要在path變量中加上D:"iconv-1.9.2.win32"bin;D:"zlib-1.2.3.win32"bin;D:"libxml2-2.6.30.win32"bin這三個地址,否則在執(zhí)行的時候就找不到。或者使用更簡單的方法,把其中的三個dll到拷貝到system32目錄中。

            有兩種方法來編譯鏈接基于libxml2的程序,第一種是在VC環(huán)境中設置lib和include路徑,并在link設置中添加libxml2.lib和iconv.lib;第二種是用編譯器選項告訴編譯器cl.exe頭文件的位置,并用鏈接器選項告訴鏈接器link.exe庫文件的位置,同時在windows環(huán)境變量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表明關閉編譯優(yōu)化;/D _DEBUG表明定義一個名為_DEBUG的宏

            CPP_FLAGS=/c /MTd /Zi /Od /D _DEBUG

            #鏈接選項,/DEBUG表明創(chuàng)建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的寫法,但后續(xù)例子程序的編譯鏈接依葫蘆畫瓢都沒有問題,執(zhí)行編譯鏈接的命令如下:

            nmake TARGET_NAME=CreateXmlFile

            執(zhí)行清理的命令如下:

            nmake TARGET_NAME=CreateXmlFile clean

            2.       Libxml2中的數(shù)據(jù)類型和函數(shù)

            一個函數(shù)庫中可能有幾百種數(shù)據(jù)類型以及幾千個函數(shù),但是記住大師的話,90%的功能都是由30%的內容提供的。對于libxml2,我認為搞懂以下的數(shù)據(jù)類型和函數(shù)就足夠了。

            2.1   內部字符類型xmlChar

            xmlChar是Libxml2中的字符類型,庫中所有字符、字符串都是基于這個數(shù)據(jù)類型。事實上它的定義是:xmlstring.h

            typedef unsigned char xmlChar;

            使用unsigned char作為內部字符格式是考慮到它能很好適應UTF-8編碼,而UTF-8編碼正是libxml2的內部編碼,其它格式的編碼要轉換為這個編碼才能在libxml2中使用。

            還經(jīng)??梢钥吹绞褂脁mlChar*作為字符串類型,很多函數(shù)會返回一個動態(tài)分配內存的xmlChar*變量,使用這樣的函數(shù)時記得要手動刪除內存。

            2.2   xmlChar相關函數(shù)

            如同標準c中的char類型一樣,xmlChar也有動態(tài)內存分配、字符串操作等相關函數(shù)。例如xmlMalloc是動態(tài)分配內存的函數(shù);xmlFree是配套的釋放內存函數(shù);xmlStrcmp是字符串比較函數(shù)等等。

            基本上xmlChar字符串相關函數(shù)都在xmlstring.h中定義;而動態(tài)內存分配函數(shù)在xmlmemory.h中定義。

            2.3   xmlChar*與其它類型之間的轉換

            另外要注意,因為總是要在xmlChar*和char*之間進行類型轉換,所以定義了一個宏BAD_CAST,其定義如下:xmlstring.h

            #define BAD_CAST (xmlChar *)

            原則上來說,unsigned char和char之間進行強制類型轉換是沒有問題的。

            2.4   文檔類型xmlDoc、指針xmlDocPtr

            xmlDoc是一個struct,保存了一個xml的相關信息,例如文件名、文檔類型、子節(jié)點等等;xmlDocPtr等于xmlDoc*,它搞成這個樣子總讓人以為是智能指針,其實不是,要手動刪除的。

            xmlNewDoc函數(shù)創(chuàng)建一個新的文檔指針。

            xmlParseFile函數(shù)以默認方式讀入一個UTF-8格式的文檔,并返回文檔指針。

            xmlReadFile函數(shù)讀入一個帶有某種編碼的xml文檔,并返回文檔指針;細節(jié)見libxml2參考手冊。

            xmlFreeDoc釋放文檔指針。特別注意,當你調用xmlFreeDoc時,該文檔所有包含的節(jié)點內存都被釋放,所以一般來說不需要手動調用xmlFreeNode或者xmlFreeNodeList來釋放動態(tài)分配的節(jié)點內存,除非你把該節(jié)點從文檔中移除了。一般來說,一個文檔中所有節(jié)點都應該動態(tài)分配,然后加入文檔,最后調用xmlFreeDoc一次釋放所有節(jié)點申請的動態(tài)內存,這也是為什么我們很少看見xmlNodeFree的原因。

            xmlSaveFile將文檔以默認方式存入一個文件。

            xmlSaveFormatFileEnc可將文檔以某種編碼/格式存入一個文件中。

            2.5   節(jié)點類型xmlNode、指針xmlNodePtr

            節(jié)點應該是xml中最重要的元素了,xmlNode代表了xml文檔中的一個節(jié)點,實現(xiàn)為一個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 */

            };

            可以看到,節(jié)點之間是以鏈表和樹兩種方式同時組織起來的,next和prev指針可以組成鏈表,而parent和children可以組織為樹。同時還有以下重要元素:

            l         節(jié)點中的文字內容:content;

            l         節(jié)點所屬文檔:doc;

            l         節(jié)點名字:name;

            l         節(jié)點的namespace:ns;

            l         節(jié)點屬性列表:properties;

            Xml文檔的操作其根本原理就是在節(jié)點之間移動、查詢節(jié)點的各項信息,并進行增加、刪除、修改的操作。

            xmlDocSetRootElement函數(shù)可以將一個節(jié)點設置為某個文檔的根節(jié)點,這是將文檔與節(jié)點連接起來的重要手段,當有了根結點以后,所有子節(jié)點就可以依次連接上根節(jié)點,從而組織成為一個xml樹。

            2.6   節(jié)點集合類型xmlNodeSet、指針xmlNodeSetPtr

            節(jié)點集合代表一個由節(jié)點組成的變量,節(jié)點集合只作為Xpath的查詢結果而出現(xiàn)(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 @@ */

            };

            可以看出,節(jié)點集合有三個成員,分別是節(jié)點集合的節(jié)點數(shù)、最大可容納的節(jié)點數(shù),以及節(jié)點數(shù)組頭指針。對節(jié)點集合中各個節(jié)點的訪問方式很簡單,如下:

            xmlNodeSetPtr nodeset = XPATH查詢結果;

            for (int i = 0; i < nodeset->nodeNr; i++)

            {

             nodeset->nodeTab[i];

            }

            注意,libxml2是一個c函數(shù)庫,因此其函數(shù)和數(shù)據(jù)類型都使用c語言的方式來處理。如果是c++,我想我寧愿用STL中的vector來表示一個節(jié)點集合更好,而且沒有內存泄漏或者溢出的擔憂。

            3.       簡單xml操作例子

            了解以上基本知識之后,就可以進行一些簡單的xml操作了。當然,還沒有涉及到內碼轉換(使得xml中可以處理中文)、xpath等較復雜的操作。

            3.1   創(chuàng)建xml文檔

            有了上面的基礎,創(chuàng)建一個xml文檔顯得非常簡單,其流程如下:

            l         用xmlNewDoc函數(shù)創(chuàng)建一個文檔指針doc;

            l         用xmlNewNode函數(shù)創(chuàng)建一個節(jié)點指針root_node;

            l         用xmlDocSetRootElement將root_node設置為doc的根結點;

            l         給root_node添加一系列的子節(jié)點,并設置子節(jié)點的內容和屬性;

            l         用xmlSaveFile將xml文檔存入文件;

            l         用xmlFreeDoc函數(shù)關閉文檔指針,并清除本文檔中所有節(jié)點動態(tài)申請的內存。

            注意,有多種方式可以添加子節(jié)點:第一是用xmlNewTextChild直接添加一個文本子節(jié)點;第二是先創(chuàng)建新節(jié)點,然后用xmlAddChild將新節(jié)點加入上層節(jié)點。

            源代碼文件是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:   創(chuàng)建一個xml文件

            *********************************************************************/

            #include <stdio.h>

            #include <libxml/parser.h>

            #include <libxml/tree.h>

            #include <iostream.h>

            int main()

            {

                //定義文檔和節(jié)點指針

                xmlDocPtr doc = xmlNewDoc(BAD_CAST"1.0");

                xmlNodePtr root_node = xmlNewNode(NULL,BAD_CAST"root");

                //設置根節(jié)點

                xmlDocSetRootElement(doc,root_node);

                //在根節(jié)點中直接創(chuàng)建節(jié)點

                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");

                //創(chuàng)建一個節(jié)點,設置其內容和屬性,然后加入根結點

                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");

                //創(chuàng)建一個兒子和孫子節(jié)點

                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文檔被創(chuàng)建,寫入"<<nRel<<"個字節(jié)"<<endl;

                }

                //釋放文檔內節(jié)點動態(tài)申請的內存

                xmlFreeDoc(doc);

                return 1;

            }

            編譯鏈接命令如下:

            nmake TARGET_NAME=CreateXmlFile

            然后執(zhí)行可執(zhí)行文件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文檔,從中取出想要的信息,例如節(jié)點中包含的文字,或者某個節(jié)點的屬性,其流程如下:

            l         用xmlReadFile函數(shù)讀出一個文檔指針doc;

            l         用xmlDocGetRootElement函數(shù)得到根節(jié)點curNode;

            l         curNode->xmlChildrenNode就是根節(jié)點的子節(jié)點集合;

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

            l         用xmlHasProp查找含有某個屬性的節(jié)點;

            l         取出該節(jié)點的屬性集合,用xmlGetProp取出其屬性值;

            l         用xmlFreeDoc函數(shù)關閉文檔指針,并清除本文檔中所有節(jié)點動態(tài)申請的內存。

            注意:節(jié)點列表的指針依然是xmlNodePtr,屬性列表的指針也是xmlAttrPtr,并沒有xmlNodeList或者xmlAttrList這樣的類型??醋髁斜淼臅r候使用它們的next和prev鏈表指針來進行輪詢。只有在Xpath中有xmlNodeSet這種類型,其使用方法前面已經(jīng)介紹了。

            源代碼如下: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將指一個注冊的錯誤并停止。

                //一個常見錯誤是不適當?shù)木幋a。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)

                {

                   //取出節(jié)點中的內容

                   if ((!xmlStrcmp(curNode->name, (const xmlChar *)"newNode1")))

                   {

                       szKey = xmlNodeGetContent(curNode);

                       printf("newNode1: %s"n", szKey);

                       xmlFree(szKey);

                   }

                   //查找?guī)в袑傩詀ttribute的節(jié)點

                   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

            執(zhí)行命令如下,使用第一次創(chuàng)建的xml文件作為輸入:

            ParseXmlFile.exe CreatedXml.xml

            觀察源代碼可發(fā)現(xiàn),所有以查詢方式得到的xmlChar*字符串都必須使用xmlFree函數(shù)手動釋放。否則會造成內存泄漏。

            3.3   修改xml文檔

            有了上面的基礎,修改xml文檔的內容就很簡單了。首先打開一個已經(jīng)存在的xml文檔,順著根結點找到需要添加、刪除、修改的地方,調用相應的xml函數(shù)對節(jié)點進行增、刪、改操作。源代碼見ChangeXmlFile,編譯鏈接方法如上。執(zhí)行下面的命令:

            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函數(shù),我們刪除節(jié)點使用的是以下一段代碼:

                   if (!xmlStrcmp(curNode->name, BAD_CAST "newNode1"))

                   {

                       xmlNodePtr tempNode;

                       tempNode = curNode->next;

                       xmlUnlinkNode(curNode);

                       xmlFreeNode(curNode);

                       curNode = tempNode;

                       continue;

                   }

            即將當前節(jié)點從文檔中斷鏈(unlink),這樣本文檔就不會再包含這個子節(jié)點。這樣做需要使用一個臨時變量來存儲斷鏈節(jié)點的后續(xù)節(jié)點,并記得要手動刪除斷鏈節(jié)點的內存。

            3.4   使用XPATH查找xml文檔

            簡而言之,XPATH之于xml,好比SQL之于關系數(shù)據(jù)庫。要在一個復雜的xml文檔中查找所需的信息,XPATH簡直是必不可少的工具。XPATH語法簡單易學,并且有一個很好的官方教程,見http://www.zvon.org/xxl/XPathTutorial/Output_chi/introduction.html。這個站點的XML各種教程齊全,并且有包括中文在內的各國語言版本,真是讓我喜歡到非常!

            使用XPATH之前,必須首先熟悉幾個數(shù)據(jù)類型和函數(shù),它們是使用XPATH的前提。在libxml2中使用Xpath是非常簡單的,其流程如下:

            l         定義一個XPATH上下文指針xmlXPathContextPtr context,并且使用xmlXPathNewContext函數(shù)來初始化這個指針;

            l         定義一個XPATH對象指針xmlXPathObjectPtr result,并且使用xmlXPathEvalExpression函數(shù)來計算Xpath表達式,得到查詢結果,將結果存入對象指針中;

            l         使用result->nodesetval得到節(jié)點集合指針,其中包含了所有符合Xpath查詢結果的節(jié)點;

            l         使用xmlXPathFreeContext釋放上下文指針;

            l         使用xmlXPathFreeObject釋放Xpath對象指針;

            具體的使用方法可以看XpathForXmlFile.cpp的這一段代碼,其功能是查找符合某個Xpath語句的對象指針:

            xmlXPathObjectPtr getNodeSet(xmlDocPtr doc, const xmlChar *szXpath)

            {

                xmlXPathContextPtr context;    //XPATH上下文指針

                xmlXPathObjectPtr result;       //XPATH對象指針,用來存儲查詢結果

                context = xmlXPathNewContext(doc);     //創(chuàng)建一個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']"語句的結果,并且將找到的節(jié)點的屬性和內容打印出來。編譯鏈接命令如下:

            nmake TARGET_NAME=XpathForXmlFile

            執(zhí)行方式如下:

            XpathForXmlFile.exe CreatedXml.xml

            觀察結果可以看出找到了一個節(jié)點,即root下面node2節(jié)點,它的attribute屬性值正好等于yes。更多關于Xpath的內容可以參考XPATH官方手冊。只有掌握了XPATH,才掌握了使用大型XML文件的方法,否則每尋找一個節(jié)點都要從根節(jié)點找起,會把人累死。

            4.       用ICONV解決XML中的中文問題

            Libxml2中默認的內碼是UTF-8,所有使用libxml2進行處理的xml文件,必須首先顯式或者默認的轉換為UTF-8編碼才能被處理。

            要在xml中使用中文,就必須能夠在UTF-8和GB2312內碼(較常用的一種簡體中文編碼)之間進行轉換。Libxml2提供了默認的內碼轉換機制,并且在libxml2的Tutorial中有一個例子,事實證明這個例子并不適合用來轉換中文。

            所以需要我們顯式的使用ICONV來進行內碼轉換,libxml2本身也是使用ICONV進行轉換的。ICONV是一個專門用來進行編碼轉換的庫,基本上支持目前所有常用的編碼。它是glibc庫的一個部分,常常被用于UNIX系統(tǒng)中。當然,在windows下面使用也沒有任何問題。前面已經(jīng)提到了ICONV的安裝和使用方法,這里主要講一下編程相關問題。

            本節(jié)其實和xml以及l(fā)ibxml2沒有太大關系,你可以把它簡單看作是一個編碼轉換方面的專題。我們僅僅需要學會使用兩個函數(shù)就可以了,即從UTF-8轉換到GB2312的函數(shù)u2g,以及反向轉換的函數(shù)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碼  

            //成功則返回一個動態(tài)分配的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碼  

            //成功則返回一個動態(tài)分配的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語言的年代我們常常這么干,唯一的害處的編譯鏈接出來的可執(zhí)行程序體積變大了。當然這時因為我們這段代碼很小的原因,再大一點我就要用dll了。

            從UTF-8到GB2312的一個典型使用流程如下:

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

            l         定義一個char*的字符指針szDes,并不需要給他動態(tài)審判內存;

            l         szDes = u2g(szSrc),這樣就可以得到轉換后的GB2312編碼的字符串;

            l         使用完這個字符串后使用free(szDes)來釋放內存。

            本文并不準備講述iconv中的函數(shù)細節(jié),因為那幾個函數(shù)以及數(shù)據(jù)類型都非常簡單,我們還是重點看一下如何在libxml2中使用編碼轉換來處理帶有中文的xml文件。下面是使用以上方法來創(chuàng)建一個帶有中文的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:   創(chuàng)建一個xml文件,其中包含中文

            *********************************************************************/

            #include <stdio.h>

            #include <libxml/parser.h>

            #include <libxml/tree.h>

            #include <iostream.h>

            #include "wxb_codeConv.c" //自己寫的編碼轉換函數(shù)

            int main(int argc, char **argv)

            {

                //定義文檔和節(jié)點指針

                xmlDocPtr doc = xmlNewDoc(BAD_CAST"1.0");

                xmlNodePtr root_node = xmlNewNode(NULL,BAD_CAST"root");

                //設置根節(jié)點

                xmlDocSetRootElement(doc,root_node);

                //一個中文字符串轉換為UTF-8字符串,然后寫入

                char* szOut = g2u("節(jié)點1的內容");

                //在根節(jié)點中直接創(chuàng)建節(jié)點

                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);

                //創(chuàng)建一個節(jié)點,設置其內容和屬性,然后加入根結點

                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);

                //創(chuàng)建一個中文節(jié)點

                szOut = g2u("中文節(jié)點");

                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文檔被創(chuàng)建,寫入"<<nRel<<"個字節(jié)"<<endl;

                }

                xmlFreeDoc(doc);

                return 1;

            }

            編譯鏈接命令如下:

            nmake TARGET_NAME=CreateXmlFile_cn

            完成后執(zhí)行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>節(jié)點1的內容</node1>

                <node2 attribute="屬性值">NODE CONTENT</node2>

                <中文節(jié)點>content of chinese node</中文節(jié)點>

            </root>

            觀察可知,節(jié)點的名稱、內容、屬性都可以使用中文了。在解析、修改和查找XML文檔時都可以使用上面的方法,只要記住,進入xml文檔之前將中文編碼轉換為UTF-8編碼;從XML中取出數(shù)據(jù)時,不管三七二十一都可以轉換為GB2312再用,否則你很有可能見到傳說中的亂碼!

            5.       用XML來做點什么

            有了以上的基礎,相信已經(jīng)可以順利的在c/c++程序中使用XML文檔了。那么,我們到底要用XML來做什么呢?我隨便說一說自己的想法:

            第一,可以用來作為配置文件。例如很多組件就是用XML來做配置文件;當然,我們知道用INI做配置文件更簡單,只要熟悉兩個函數(shù)就可以了;不過,復雜一點的配置文件我還是建議采用XML;

            第二,可以用來作為在程序之間傳送數(shù)據(jù)的格式,這樣的話最好給你的xml先定義一個XML Schema,這樣的數(shù)據(jù)首先可以做一個良構校驗,還可以來一個Schema校驗,如此的話出錯率會比沒有格式的數(shù)據(jù)小得多。目前XML已經(jīng)廣泛作為網(wǎng)絡之間的數(shù)據(jù)格式了;

            第三,可以用來作為你自定義的數(shù)據(jù)存儲格式,例如對象持久化之類的功能;

            最后,可以用來顯示你的技術很高深,本來你要存儲一個1,結果你這樣存儲了:

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

            <root>

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

            </root>

            然后再用libxml2取出來,最好還來幾次編碼轉換,是不是讓人覺得你很牛呢,哈哈!說笑了,千萬不要這么做。


            posted on 2008-04-01 17:01 隨意門 閱讀(1712) 評論(0)  編輯 收藏 引用

            久久国产精品成人影院| 久久久久久午夜成人影院| 国产精品无码久久综合| 久久久久久亚洲精品影院| 国产激情久久久久影院老熟女免费| 男女久久久国产一区二区三区| 亚洲欧美另类日本久久国产真实乱对白 | 欧美与黑人午夜性猛交久久久 | 精品国产乱码久久久久久郑州公司| 久久久久亚洲av综合波多野结衣 | 2020久久精品亚洲热综合一本| 久久久久99精品成人片牛牛影视 | 亚洲精品成人网久久久久久| 国内精品伊人久久久久网站| 亚洲成色999久久网站| 精品午夜久久福利大片| 99精品国产在热久久无毒不卡 | 欧美激情精品久久久久久久九九九| 99久久国产综合精品五月天喷水| 91精品国产综合久久四虎久久无码一级 | 久久精品免费网站网| 久久婷婷五月综合成人D啪| 四虎影视久久久免费| 久久SE精品一区二区| 伊人久久综合精品无码AV专区| 久久精品国产亚洲av麻豆图片| 无码日韩人妻精品久久蜜桃| 久久久婷婷五月亚洲97号色 | 国产亚洲美女精品久久久| 久久九九全国免费| 久久成人国产精品一区二区| 人人狠狠综合久久亚洲高清| 国产精品一区二区久久精品涩爱 | 久久精品国产精品青草app| 亚洲国产精久久久久久久| 无码人妻久久一区二区三区蜜桃 | 久久国产精品99国产精| 91久久精品无码一区二区毛片| 人妻丰满?V无码久久不卡| 久久婷婷激情综合色综合俺也去| 国内精品人妻无码久久久影院 |