C++中的XML配置文件编程经验

来源:http://www.blogjava.net/wxb_nudt/archive/2008/05/27/203317.html

 

    C++中并没有操作XML文件的标准库,因此大家需要使用各自熟悉的XML库来解决XML文件的读取与写入。XML的一个重要用途是作为程序的配置文件,存储程序运行相关的各种数据。本文总结了使用libxml2库来对XML配置文件进行编程的一些经验。最后提供了一个封装好的类CXMLConfig,并详细说明了该类的功能、使用方法和注意事项。

阅读本文所需的技术背景:

l         C/C++简单语法;

l         XML技术,XPATH技术;

l         C++编译器知识;

本文的内容包括:

l         下载与安装LIBXML2和ICONV;

l         第一个例子程序的编写、编译链接和运行;

l         使用XPATH读出多个配置项的值;

l         XML的配置文件类CXMLConfig;

l         将配置项写入XML文件;

l         CXMLConfig类使用小结;

阅读本文之前最好先读我的上一篇博客C++的XML编程经验――LIBXML2库使用指南,那一篇专门介绍libxml2库的使用方法。本文将不会再详细介绍libxml2的使用,而是集中精力介绍如何存取XML中的数据。

本文的源代码是一个VC6的工程,里面包含三个子工程。地址在http://www.blogjava.net/Files/wxb_nudt/XMLConfigFile.rar

1.       下载与安装LIBXML2ICONV

为了方便读者,这一段原文照抄上一篇博客。

Libxml2是一个C语言的XML程序库,可以简单方便的提供对XML文档的各种操作,并且支持XPATH查询,以及部分的支持XSLT转换等功能。Libxml2的下载地址是http://xmlsoft.org/,完全版的库是开源的,并且带有例子程序和说明文档。最好将这个库先下载下来,因为这样可以查看其中的文档和例子。

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是不需要安装的,它又没有使用任何需要注册的组件或者数据库,只需要告诉编译器和链接器这些资源的位置就可以了。

注意:要在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目录下)。

2.       HELLO,XML CONFIG FILE

本节的源代码位于项目HelloXml中,使用的xml文件是Helloxml.xml。

在安装配置好libxml2和iconv库之后,就可以写一个简单的程序来读取XML中的数据了。该XML内容如下:

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

 <main>20080526</main>

使用libxml2库读取main节点包含的内容,代码如下:

 

  

 

  1. xmlChar* LoadConfigFile(const char* szConfigFilename, xmlChar* xszRel)
  2. {
  3.     xmlDocPtr doc;   //定义解析文档指针
  4.     xmlNodePtr curNodePtr; //定义结点指针
  5.     doc = xmlReadFile(szConfigFilename,"GB2312",XML_PARSE_RECOVER); //解析文件
  6.     if (doc == NULL ) 
  7.     {
  8.        fprintf(stderr,"Document not parsed successfully. "n");     
  9.        xmlFreeDoc(doc); 
  10.        exit(1);
  11.     }  
  12.     curNodePtr = xmlDocGetRootElement(doc); //确定文档根元素
  13.     /*检查确认当前文档中包含内容*/ 
  14.     if (curNodePtr == NULL)
  15.     {
  16.        fprintf(stderr,"empty document"n"); 
  17.        xmlFreeDoc(doc); 
  18.        exit(1);
  19.     }
  20.     //读取xml文档中的内容并赋值给对象属性
  21.     xszRel = xmlNodeGetContent(curNodePtr);
  22.     xmlFreeDoc(doc); 
  23.     return xszRel;
  24. }
  25. int main(int argc, char* argv[])
  26. {
  27.     xmlChar* xszContent = NULL;
  28.     xszContent = LoadConfigFile("..""Debug""HelloXml.xml",xszContent);
  29.     if (xszContent != NULL)
  30.     {
  31.        cout<<"HELLO, XML CONFIG FILE. content = "<<xszContent<<endl;
  32.        xmlFree(xszContent);
  33.     }
  34.     return 0;
  35. }

编译代码之前要注意:xml文档存放的地点不是本项目文件夹,而是项目文件夹上层的Debug目录,同时将编译和链接的目的文件夹都设置为项目文件夹上层的Debug目录。第二点,在link选项中加入了libxml2.lib和iconv.lib。第三点,在系统的Path变量中指明了libxml2.dll、iconv.dll和zlib1.dll的路径(为了方便读者,我将这三个dll都拷贝到了Debug目录下面)。

编译链接完毕后运行程序,得到如下结果:

HELLO, XML CONFIG FILE. content = 20080526

3.       使用XPATH读出多个配置项的值

本节的源代码位于项目XPathConfig中,使用的xml文件是XPathConfig.xml。

上面的例子中,为了理解的便利仅在根节点中存储了一个值,而实际的配置文件往往是同时存放多个配置项的值。举例如下:

<main>

    <IP>127.0.0.1</IP>

    <Port>80</Port>

</main>

Xml中存储了一个IP地址和一个端口值。其XPATH地址分别是/main/IP/和/main/Port/。当然,更加复杂的XPATH值也可同样处理。

为了方便的操作xml文档,我写了一组xml函数,位于Code_Conv.h和Code_Conv.cpp中,其功能如下:

l         openXmlFile,打开Xml文档,返回文档指针;

l         closeXmlFile,关闭Xml文档;

l         getXmlString,根据XPATH路径读取字符串;

l         getXmlInt,根据XPATH路径读取整型值;

为了处理中文以及查询Xpath节点,我还写了四个被上述函数调用的函数:

l         code_convert,从一种编码转为另一种编码;

l         u2g,从UTF-8转换为GB2312编码;

l         g2u,从GB2312转换为UTF-8编码;

l         get_nodeset,调用xpath查询节点集合,成功则返回xpath的对象指针,失败返回NULL。

然后,主程序便简化为:

 

 

 

  1. int main(int argc, char* argv[])
  2. {
  3.     xmlDocPtr doc = openXmlFile("..""Debug""XPathConfig.xml");
  4.     string strIP = getXmlString(doc,"/main/IP");
  5.     int iPort = getXmlInt(doc,"/main/Port");
  6.     cout<<"IP = "<<strIP.c_str()<<" Port = "<<iPort<<endl;
  7.     closeXmlFile(doc);
  8.     return 0;
  9. }

运行结果为:

IP = 127.0.0.1 Port = 80

观察上面的代码可以发现,整个主程序几乎与libxml2库无关了,除了一个xmlDocPtr变量。再次观察可以发现,这个变量几乎出现在每个自定义函数中,它代表的是一种状态,或者可以称为属性。而那些自定义函数可以称之为功能。因此,按照许多C++专著的说法,属性+功能=对象。《C++沉思录》中说道,C和C++最大的不同在于,C++拥有一个最合适的存储程序状态的位置,即对象的属性;而C则必须在许多函数中留出一个位置来保存这个状态。这句话,简直正确得可怕

4.       XML的配置文件类CXMLConfig

本节的源代码位于项目UseClass中,使用的xml文件还是XPathConfig.xml。

于是有了下面的CXMLConfig类定义:

 

  1. class CXMLConfig 
  2. {
  3. public:
  4.     CXMLConfig(const char* szXmlFilename);
  5.     ~CXMLConfig();
  6.     //根据XPATH路径读取字符串
  7.     string getXmlString(const char *szXpath);
  8.     int getXmlInt(const char* szXpath);    
  9. private:
  10.     //代码转换:从一种编码转为另一种编码   
  11.     int code_convert(char* from_charset, char* to_charset, char* inbuf,int inlen, char* outbuf, int outlen);
  12.     //UNICODE码转为GB2312码   
  13.     //成功则返回一个动态分配的char*变量,需要在使用完毕后手动free,失败返回NULL
  14.     char* u2g(char *inbuf);
  15.     //GB2312码转为UNICODE码   
  16.     //成功则返回一个动态分配的char*变量,需要在使用完毕后手动free,失败返回NULL
  17.     char* g2u(char *inbuf);
  18.     //调用xpath查询节点集合,成功则返回xpath的对象指针,失败返回NULL
  19.     xmlXPathObjectPtr get_nodeset(const xmlChar *xpath);
  20. private:
  21.     string m_strFilename;
  22.     xmlDocPtr m_doc;
  23. };

使用这个类来改写主程序,可以让使用者完全脱离libxml2的库环境,并且省略了打开和关闭xml文件的步骤,因为这些工作在构造和析构函数中完成了。

  1. int main(int argc, char* argv[])
  2. {
  3.     CXMLConfig xmlConfig("..""Debug""XPathConfig.xml");
  4.     string strIP = xmlConfig.getXmlString("/main/IP");
  5.     int iPort = xmlConfig.getXmlInt("/main/Port");
  6.     cout<<"IP = "<<strIP.c_str()<<" Port = "<<iPort<<endl;
  7.     return 0;
  8. }

运行结果为:

IP = 127.0.0.1 Port = 80

5.       将配置项写入XML文件

本节的源代码位于项目UseClass中,使用的xml文件依然是XPathConfig.xml。

目前CXMLConfig类已经有了打开xml文件,读取数据以及关闭xml文件的功能。还缺少写入数据的功能。写入数据功能的算法也很简单:先将xml文件读入内存,然后通过xpath找到相应节点,并修改节点内容,最后将内存中的xml文件一次性写入硬盘。这里有一点要注意,如果在写入过程中硬盘断电或者出现其他故障,则会造成无法恢复的错误,数据会全部丢失。为了防止这种情况,还应该在写入前进行数据备份的工作。通盘考虑后,在CXMLConfig类中加入如下函数:

writeXmlString:将字符串写入xml文档相应节点;

writeXmlInt:将整型写入xml文档相应节点;

saveConfigFile:将内存中的xml文档写入硬盘;

saveBakConfigFile:保存当前的xml文档到bak文件(即xml文档名加_BAK.XML)中;

loadBakConfigFile:将bak文件读入内存;

注意,在调用saveConfigFile时会自动调用saveBakConfigFile,将原有配置文件保存为备份文件。修改后的类如下:

 

  1. class CXMLConfig 
  2. {
  3. public:
  4.     CXMLConfig(const char* szXmlFilename);
  5.     ~CXMLConfig();
  6.     //根据XPATH路径读取字符串
  7.     string getXmlString(const char *szXpath);
  8.     int getXmlInt(const char* szXpath);    
  9.     bool writeXmlString(const string strValue, const char* szXpath);
  10.     bool writeXmlInt(const int iValue, const char* szXpath);
  11.     bool saveConfigFile();
  12.     bool saveBakConfigFile();
  13.     bool loadBakConfigFile();
  14. private:
  15.     //代码转换:从一种编码转为另一种编码   
  16.     int code_convert(char* from_charset, char* to_charset, char* inbuf,
  17.                    int inlen, char* outbuf, int outlen);
  18.     //UNICODE码转为GB2312码   
  19.     char* u2g(char *inbuf);
  20.     //GB2312码转为UNICODE码   
  21.     char* g2u(char *inbuf);
  22.     //调用xpath查询节点集合,成功则返回xpath的对象指针,失败返回NULL
  23.     xmlXPathObjectPtr get_nodeset(const xmlChar *xpath);
  24.     // 禁止拷贝构造函数和"="操作
  25.     CXMLConfig(const CXMLConfig&);
  26.     CXMLConfig& operator=(const CXMLConfig&);
  27. private:
  28.     string m_strFilename;
  29.     xmlDocPtr m_doc;
  30. };

然后我们修改了主程序,其功能为读出数据后修改了数据,然后存入了配置文件,主程序如下:

 

 

 

 

  1. int main(int argc, char* argv[])
  2. {
  3.     CXMLConfig xmlConfig("..""Debug""XPathConfig.xml");
  4.     string strIP = xmlConfig.getXmlString("/main/IP");
  5.     int iPort = xmlConfig.getXmlInt("/main/Port");
  6.     cout<<"IP = "<<strIP.c_str()<<" Port = "<<iPort<<endl;
  7.     strIP = "127.1.1.1";
  8.     iPort = 81;
  9.     xmlConfig.writeXmlString(strIP,"/main/IP");
  10.     xmlConfig.writeXmlInt(iPort,"/main/Port");
  11.     if(xmlConfig.saveConfigFile())
  12.     {
  13.        cout<<"Save Config file success!"<<endl;
  14.     }
  15.     return 0;
  16. }

运行完以后会发现两个结果,第一个是配置文件XPathConfig.xml中的内容已经被修改,第二个是原配置文件内容备份在XPathConfig_bak.xml中。

6.       CXMLConfig类使用小结

目前为止,CXMLConfig类提供了较为便利的读取和保存XML配置文件的功能。那么使用CXMLConfig需要哪些步骤呢?

第一,正确安装了libxml2和iconv库,包括头文件、lib文件和dll文件。注意头文件主要是libxml2和iconv的头文件,lib文件就是两个libxml2.lib和iconv.lib,而dll有三个,即libxml2.dll、iconv.dll和zlib1.dll。注意:如果你没有正确安装,那么无法正确编译我的例子程序,但是可以运行,因为我已经将dll都包含到运行目录下

第二,确信你弄懂了你的xml配置文件结构,并放在正确的地方;

第三,使用CXMLConfig xmlConfig("..""Debug""XPathConfig.xml")语句正确构造一个CXMLConfig对象,并调用相应的方法来操作xml文件。

CXMLConfig类使用的注意事项:

第一,注意xml文件必须使用节点来存储数据,而不是属性。若使用属性来保存数据,CXMLConfig类不会正确读出其数据,当然更不能正确写入。若有兴趣,可以扩展CXMLConfig类来实现对属性数据的存取,事实上那非常简单。

第二,若有两个节点的XPATH路径相同,例如

<main>

    <IP>127.0.0.1</IP>

<IP>127.0.0.2</IP>

    <Port>80</Port>

</main>

那么使用getXmlString将只会得到第一个节点的内容。同理,写入时也只会写入第一个节点。

CXMLConfig类的使用环境:

第一,   使用节点来存储数据;

第二,   节点的XPATH路径各不相同;

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

总之,若有更复杂的要求,请还是仔细研究libxml2或者任意一个开源或商用XML库。

 

7.       文末的话

事实上,按照原计划这篇博客才刚刚开头,后面才是最精彩的部分。其内容是介绍如何将XML文件当作一个小型的数据库,把多个XPATH路径相同的键和值读入一个std::map<std::string,std::string>中,然后在程序中方便的使用这个map来查找,存取某一类数据。但是由于前面的部分写作时考虑得太详细,而且CXMLConfig类也介绍逐渐趋于完善,因此为了防止喧宾夺主,本文就到这里结束为好。作为一篇libxml2和C++的入门文章,恰到好处!

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值