MDA之路

          MDA,UML,XML,Eclipse及Java相關(guān)的Blog
          posts - 53, comments - 494, trackbacks - 0, articles - 2
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          C++中的XML配置文件編程經(jīng)驗

          Posted on 2008-05-27 19:40 wxb_nudt 閱讀(22925) 評論(8)  編輯  收藏 所屬分類: 實用編程技術(shù)
           

          C++中并沒有操作XML文件的標(biāo)準(zhǔn)庫,因此大家需要使用各自熟悉的XML庫來解決XML文件的讀取與寫入。XML的一個重要用途是作為程序的配置文件,存儲程序運行相關(guān)的各種數(shù)據(jù)。本文總結(jié)了使用libxml2庫來對XML配置文件進行編程的一些經(jīng)驗。最后提供了一個封裝好的類CXMLConfig,并詳細(xì)說明了該類的功能、使用方法和注意事項。

          閱讀本文所需的技術(shù)背景:

          l         C/C++簡單語法;

          l         XML技術(shù),XPATH技術(shù);

          l         C++編譯器知識;

          本文的內(nèi)容包括:

          l         下載與安裝LIBXML2ICONV

          l         第一個例子程序的編寫、編譯鏈接和運行;

          l         使用XPATH讀出多個配置項的值;

          l         XML的配置文件類CXMLConfig

          l         將配置項寫入XML文件;

          l         CXMLConfig類使用小結(jié);

          閱讀本文之前最好先讀我的上一篇博客C++XML編程經(jīng)驗――LIBXML2庫使用指南,那一篇專門介紹libxml2庫的使用方法。本文將不會再詳細(xì)介紹libxml2的使用,而是集中精力介紹如何存取XML中的數(shù)據(jù)。

          本文的源代碼是一個VC6的工程,里面包含三個子工程。地址在http://www.aygfsteel.com/Files/wxb_nudt/XMLConfigFile.rar

          1.       下載與安裝LIBXML2ICONV

          為了方便讀者,這一段原文照抄上一篇博客。

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

          windows版本的的下載地址是http://www.zlatkovic.com/libxml.en.html;這個版本只提供了頭文件、庫文件和dll,不包含源代碼、例子程序和文檔。在文本中,只需要下載libxml2庫、iconv庫和zlib庫就行了(注意,libxml2庫依賴iconvzlib庫,本文中重點關(guān)注libxml2iconvzlib不介紹),我使用的版本是libxml2-2.6.30.win32.zipzlib-1.2.3.win32.zipiconv-1.9.2.win32.zip

          在編程的時候,我們使用windows版本的libxml2zlibiconv,將其解壓縮到指定文件夾,例如D:"libxml2-2.6.30.win32D:"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)境中設(shè)置libinclude路徑,并在link設(shè)置中添加libxml2.libiconv.lib;第二種是用編譯器選項告訴編譯器cl.exe頭文件的位置,并用鏈接器選項告訴鏈接器link.exe庫文件的位置,同時在windows環(huán)境變量path中添加libxml2bin文件夾的位置,以便于程序運行時可以找到dll(也可以將dll拷貝到system32目錄下)。

          2.       HELLO,XML CONFIG FILE

          本節(jié)的源代碼位于項目HelloXml中,使用的xml文件是Helloxml.xml

          在安裝配置好libxml2iconv庫之后,就可以寫一個簡單的程序來讀取XML中的數(shù)據(jù)了。該XML內(nèi)容如下:

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

           <main>20080526</main>

          使用libxml2庫讀取main節(jié)點包含的內(nèi)容,代碼如下:

          xmlChar* LoadConfigFile(const char* szConfigFilename, xmlChar* xszRel)

          {

              xmlDocPtr doc;   //定義解析文檔指針

              xmlNodePtr curNodePtr; //定義結(jié)點指針

              doc = xmlReadFile(szConfigFilename,"GB2312",XML_PARSE_RECOVER); //解析文件

              if (doc == NULL )

              {

                 fprintf(stderr,"Document not parsed successfully. "n");    

                 xmlFreeDoc(doc);

                 exit(1);

              }  

              curNodePtr = xmlDocGetRootElement(doc); //確定文檔根元素

              /*檢查確認(rèn)當(dāng)前文檔中包含內(nèi)容*/

              if (curNodePtr == NULL)

              {

                 fprintf(stderr,"empty document"n");

                 xmlFreeDoc(doc);

                 exit(1);

              }

              //讀取xml文檔中的內(nèi)容并賦值給對象屬性

              xszRel = xmlNodeGetContent(curNodePtr);

              xmlFreeDoc(doc);

              return xszRel;

          }

          int main(int argc, char* argv[])

          {

              xmlChar* xszContent = NULL;

              xszContent = LoadConfigFile("..""Debug""HelloXml.xml",xszContent);

              if (xszContent != NULL)

              {

                 cout<<"HELLO, XML CONFIG FILE. content = "<<xszContent<<endl;

                 xmlFree(xszContent);

              }

              return 0;

          }

          編譯代碼之前要注意:xml文檔存放的地點不是本項目文件夾,而是項目文件夾上層的Debug目錄,同時將編譯和鏈接的目的文件夾都設(shè)置為項目文件夾上層的Debug目錄。第二點,在link選項中加入了libxml2.libiconv.lib。第三點,在系統(tǒng)的Path變量中指明了libxml2.dlliconv.dllzlib1.dll的路徑(為了方便讀者,我將這三個dll都拷貝到了Debug目錄下面)。

          編譯鏈接完畢后運行程序,得到如下結(jié)果:

          HELLO, XML CONFIG FILE. content = 20080526

          3.       使用XPATH讀出多個配置項的值

          本節(jié)的源代碼位于項目XPathConfig中,使用的xml文件是XPathConfig.xml

          上面的例子中,為了理解的便利僅在根節(jié)點中存儲了一個值,而實際的配置文件往往是同時存放多個配置項的值。舉例如下:

          <main>

              <IP>127.0.0.1</IP>

              <Port>80</Port>

          </main>

          Xml中存儲了一個IP地址和一個端口值。其XPATH地址分別是/main/IP//main/Port/。當(dāng)然,更加復(fù)雜的XPATH值也可同樣處理。

          為了方便的操作xml文檔,我寫了一組xml函數(shù),位于Code_Conv.hCode_Conv.cpp中,其功能如下:

          l         openXmlFile,打開Xml文檔,返回文檔指針;

          l         closeXmlFile,關(guān)閉Xml文檔;

          l         getXmlString,根據(jù)XPATH路徑讀取字符串;

          l         getXmlInt,根據(jù)XPATH路徑讀取整型值;

          為了處理中文以及查詢Xpath節(jié)點,我還寫了四個被上述函數(shù)調(diào)用的函數(shù):

          l         code_convert,從一種編碼轉(zhuǎn)為另一種編碼;

          l         u2g,從UTF-8轉(zhuǎn)換為GB2312編碼;

          l         g2u,從GB2312轉(zhuǎn)換為UTF-8編碼;

          l         get_nodeset,調(diào)用xpath查詢節(jié)點集合,成功則返回xpath的對象指針,失敗返回NULL

          然后,主程序便簡化為:

          int main(int argc, char* argv[])

          {

              xmlDocPtr doc = openXmlFile("..""Debug""XPathConfig.xml");

              string strIP = getXmlString(doc,"/main/IP");

              int iPort = getXmlInt(doc,"/main/Port");

              cout<<"IP = "<<strIP.c_str()<<" Port = "<<iPort<<endl;

              closeXmlFile(doc);

              return 0;

          }

          運行結(jié)果為:

          IP = 127.0.0.1 Port = 80

          觀察上面的代碼可以發(fā)現(xiàn),整個主程序幾乎與libxml2庫無關(guān)了,除了一個xmlDocPtr變量。再次觀察可以發(fā)現(xiàn),這個變量幾乎出現(xiàn)在每個自定義函數(shù)中,它代表的是一種狀態(tài),或者可以稱為屬性。而那些自定義函數(shù)可以稱之為功能。因此,按照許多C++專著的說法,屬性+功能=對象。《C++沉思錄》中說道,CC++最大的不同在于,C++擁有一個最合適的存儲程序狀態(tài)的位置,即對象的屬性;而C則必須在許多函數(shù)中留出一個位置來保存這個狀態(tài)。這句話,簡直正確得可怕

          4.       XML的配置文件類CXMLConfig

          本節(jié)的源代碼位于項目UseClass中,使用的xml文件還是XPathConfig.xml

          于是有了下面的CXMLConfig類定義:

          class CXMLConfig 

          {

          public:

              CXMLConfig(const char* szXmlFilename);

              ~CXMLConfig();

              //根據(jù)XPATH路徑讀取字符串

              string getXmlString(const char *szXpath);

              int getXmlInt(const char* szXpath);   

          private:

              //代碼轉(zhuǎn)換:從一種編碼轉(zhuǎn)為另一種編碼  

              int code_convert(char* from_charset, char* to_charset, char* inbuf,int inlen, char* outbuf, int outlen);

              //UNICODE碼轉(zhuǎn)為GB2312碼  

              //成功則返回一個動態(tài)分配的char*變量,需要在使用完畢后手動free,失敗返回NULL

              char* u2g(char *inbuf);

              //GB2312轉(zhuǎn)為UNICODE   

              //成功則返回一個動態(tài)分配的char*變量,需要在使用完畢后手動free,失敗返回NULL

              char* g2u(char *inbuf);

              //調(diào)用xpath查詢節(jié)點集合,成功則返回xpath的對象指針,失敗返回NULL

              xmlXPathObjectPtr get_nodeset(const xmlChar *xpath);

          private:

              string m_strFilename;

              xmlDocPtr m_doc;

          };

          使用這個類來改寫主程序,可以讓使用者完全脫離libxml2的庫環(huán)境,并且省略了打開和關(guān)閉xml文件的步驟,因為這些工作在構(gòu)造和析構(gòu)函數(shù)中完成了。

          int main(int argc, char* argv[])

          {

              CXMLConfig xmlConfig("..""Debug""XPathConfig.xml");

              string strIP = xmlConfig.getXmlString("/main/IP");

              int iPort = xmlConfig.getXmlInt("/main/Port");

              cout<<"IP = "<<strIP.c_str()<<" Port = "<<iPort<<endl;

              return 0;

          }

          運行結(jié)果為:

          IP = 127.0.0.1 Port = 80

          5.       將配置項寫入XML文件

          本節(jié)的源代碼位于項目UseClass中,使用的xml文件依然是XPathConfig.xml

          目前CXMLConfig類已經(jīng)有了打開xml文件,讀取數(shù)據(jù)以及關(guān)閉xml文件的功能。還缺少寫入數(shù)據(jù)的功能。寫入數(shù)據(jù)功能的算法也很簡單:先將xml文件讀入內(nèi)存,然后通過xpath找到相應(yīng)節(jié)點,并修改節(jié)點內(nèi)容,最后將內(nèi)存中的xml文件一次性寫入硬盤。這里有一點要注意,如果在寫入過程中硬盤斷電或者出現(xiàn)其他故障,則會造成無法恢復(fù)的錯誤,數(shù)據(jù)會全部丟失。為了防止這種情況,還應(yīng)該在寫入前進行數(shù)據(jù)備份的工作。通盤考慮后,在CXMLConfig類中加入如下函數(shù):

          writeXmlString:將字符串寫入xml文檔相應(yīng)節(jié)點;

          writeXmlInt:將整型寫入xml文檔相應(yīng)節(jié)點;

          saveConfigFile:將內(nèi)存中的xml文檔寫入硬盤;

          saveBakConfigFile:保存當(dāng)前的xml文檔到bak文件(即xml文檔名加_BAK.XML)中;

          loadBakConfigFile:將bak文件讀入內(nèi)存;

          注意,在調(diào)用saveConfigFile時會自動調(diào)用saveBakConfigFile,將原有配置文件保存為備份文件。修改后的類如下:

          class CXMLConfig 

          {

          public:

              CXMLConfig(const char* szXmlFilename);

              ~CXMLConfig();

              //根據(jù)XPATH路徑讀取字符串

              string getXmlString(const char *szXpath);

              int getXmlInt(const char* szXpath);   

              bool writeXmlString(const string strValue, const char* szXpath);

              bool writeXmlInt(const int iValue, const char* szXpath);

              bool saveConfigFile();

              bool saveBakConfigFile();

              bool loadBakConfigFile();

          private:

              //代碼轉(zhuǎn)換:從一種編碼轉(zhuǎn)為另一種編碼  

              int code_convert(char* from_charset, char* to_charset, char* inbuf,

                             int inlen, char* outbuf, int outlen);

              //UNICODE碼轉(zhuǎn)為GB2312碼  

              char* u2g(char *inbuf);

              //GB2312轉(zhuǎn)為UNICODE  

              char* g2u(char *inbuf);

              //調(diào)用xpath查詢節(jié)點集合,成功則返回xpath的對象指針,失敗返回NULL

              xmlXPathObjectPtr get_nodeset(const xmlChar *xpath);

              // 禁止拷貝構(gòu)造函數(shù)和"="操作

              CXMLConfig(const CXMLConfig&);

              CXMLConfig& operator=(const CXMLConfig&);

          private:

              string m_strFilename;

              xmlDocPtr m_doc;

          };

          然后我們修改了主程序,其功能為讀出數(shù)據(jù)后修改了數(shù)據(jù),然后存入了配置文件,主程序如下:

          int main(int argc, char* argv[])

          {

              CXMLConfig xmlConfig("..""Debug""XPathConfig.xml");

              string strIP = xmlConfig.getXmlString("/main/IP");

              int iPort = xmlConfig.getXmlInt("/main/Port");

              cout<<"IP = "<<strIP.c_str()<<" Port = "<<iPort<<endl;

              strIP = "127.1.1.1";

              iPort = 81;

              xmlConfig.writeXmlString(strIP,"/main/IP");

              xmlConfig.writeXmlInt(iPort,"/main/Port");

              if(xmlConfig.saveConfigFile())

              {

                 cout<<"Save Config file success!"<<endl;

              }

              return 0;

          }

          運行完以后會發(fā)現(xiàn)兩個結(jié)果,第一個是配置文件XPathConfig.xml中的內(nèi)容已經(jīng)被修改,第二個是原配置文件內(nèi)容備份在XPathConfig_bak.xml中。

          6.       CXMLConfig類使用小結(jié)

          目前為止,CXMLConfig類提供了較為便利的讀取和保存XML配置文件的功能。那么使用CXMLConfig需要哪些步驟呢?

          第一,正確安裝了libxml2iconv庫,包括頭文件、lib文件和dll文件。注意頭文件主要是libxml2iconv的頭文件,lib文件就是兩個libxml2.libiconv.lib,而dll有三個,即libxml2.dlliconv.dllzlib1.dll注意:如果你沒有正確安裝,那么無法正確編譯我的例子程序,但是可以運行,因為我已經(jīng)將dll都包含到運行目錄下

          第二,確信你弄懂了你的xml配置文件結(jié)構(gòu),并放在正確的地方;

          第三,使用CXMLConfig xmlConfig("..""Debug""XPathConfig.xml")語句正確構(gòu)造一個CXMLConfig對象,并調(diào)用相應(yīng)的方法來操作xml文件。

          CXMLConfig類使用的注意事項:

          第一,注意xml文件必須使用節(jié)點來存儲數(shù)據(jù),而不是屬性。若使用屬性來保存數(shù)據(jù),CXMLConfig類不會正確讀出其數(shù)據(jù),當(dāng)然更不能正確寫入。若有興趣,可以擴展CXMLConfig類來實現(xiàn)對屬性數(shù)據(jù)的存取,事實上那非常簡單。

          第二,若有兩個節(jié)點的XPATH路徑相同,例如

          <main>

              <IP>127.0.0.1</IP>

          <IP>127.0.0.2</IP>

              <Port>80</Port>

          </main>

          那么使用getXmlString將只會得到第一個節(jié)點的內(nèi)容。同理,寫入時也只會寫入第一個節(jié)點。

          CXMLConfig類的使用環(huán)境:

          第一,   使用節(jié)點來存儲數(shù)據(jù);

          第二,   節(jié)點的XPATH路徑各不相同;

          第三,   XML文件最好不大于100M

          總之,若有更復(fù)雜的要求,請還是仔細(xì)研究libxml2或者任意一個開源或商用XML庫。

          7.       文末的話

          事實上,按照原計劃這篇博客才剛剛開頭,后面才是最精彩的部分。其內(nèi)容是介紹如何將XML文件當(dāng)作一個小型的數(shù)據(jù)庫,把多個XPATH路徑相同的鍵和值讀入一個std::map<std::string,std::string>中,然后在程序中方便的使用這個map來查找,存取某一類數(shù)據(jù)。但是由于前面的部分寫作時考慮得太詳細(xì),而且CXMLConfig類也介紹逐漸趨于完善,因此為了防止喧賓奪主,本文就到這里結(jié)束為好。作為一篇libxml2C++的入門文章,恰到好處!


          評論

          # re: C++中的XML配置文件編程經(jīng)驗[未登錄]  回復(fù)  更多評論   

          2008-05-30 09:13 by diego
          師兄近來可好?

          # re: C++中的XML配置文件編程經(jīng)驗[未登錄]  回復(fù)  更多評論   

          2008-10-16 10:27 by walter
          好文章!!!
          受益良多 謝謝

          # re: C++中的XML配置文件編程經(jīng)驗  回復(fù)  更多評論   

          2008-11-19 11:47 by bluebaby
          為什么我在編譯HelloXml時出現(xiàn)這樣的錯誤提示,"error C2146: syntax error : missing ';' before identifier 'UNALIGNED'","fatal error C1004: unexpected end of file found";這個頭文件我也沒修改過,而且我看了一下出錯位置,也沒發(fā)現(xiàn)那有什么錯誤,請問是怎么回事?

          # re: C++中的XML配置文件編程經(jīng)驗  回復(fù)  更多評論   

          2009-01-01 19:01 by redhat126
          受益匪淺!
          請問可以將路徑當(dāng)作變量寫入嗎?
          我發(fā)現(xiàn)在寫入'<'時,出現(xiàn)的是&lt;,有什么辦法呢?

          # re: C++中的XML配置文件編程經(jīng)驗[未登錄]  回復(fù)  更多評論   

          2009-01-03 13:16 by wxb_nudt
          @redhat126
          對于很多XML文檔來說, '<'的內(nèi)部編碼就是&lt;
          所以你要注意編碼的情況。具體的情況很多,要靠自己掌握。@redhat126

          # re: C++中的XML配置文件編程經(jīng)驗  回復(fù)  更多評論   

          2010-02-10 18:09 by neige
          不知前輩后面的部分寫出來了沒有?非常期待,將會對我非常有用。

          # re: C++中的XML配置文件編程經(jīng)驗  回復(fù)  更多評論   

          2011-10-20 08:49 by fff
          d:\win2003\program files\microsoft visual studio\vc98\include\bhtypes.h(19) : error C2146: syntax error : missing ';' before identifier 'UNALIGNED'

          # re: C++中的XML配置文件編程經(jīng)驗  回復(fù)  更多評論   

          2012-03-06 12:02 by gsh
          在用xmllib2 中xpath的節(jié)點路徑如果有漢字時就有問題,比如"/root/節(jié)點1[position=1]"就查不到應(yīng)該返回的值,變成沒值了。
          主站蜘蛛池模板: 麻栗坡县| 彰化市| 灵石县| 布拖县| 涪陵区| 南漳县| 和顺县| 永修县| 普格县| 昭苏县| 原平市| 长海县| 双辽市| 延安市| 元氏县| 晋州市| 崇礼县| 南汇区| 灌云县| 公主岭市| 鄂托克旗| 莱芜市| 呼伦贝尔市| 彝良县| 资兴市| 屏南县| 蛟河市| 额尔古纳市| 博湖县| 新田县| 汽车| 孝感市| 沙雅县| 休宁县| 宁明县| 石柱| 喀喇沁旗| 乌苏市| 南宫市| 辽阳市| 高邮市|