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

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.       下载与安装LIBXML2和ICONV

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

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,XMLCONFIG FILE

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

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

 <?xmlversion="1.0" encoding="GB2312" ?>

 <main>20080526</main>

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

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

{

   xmlDocPtr doc;   //定义解析文档指针

   xmlNodePtr curNodePtr; //定义结点指针

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

   if (doc == NULL )

   {

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

      xmlFreeDoc(doc);

      exit(1);

   }  

   curNodePtr = xmlDocGetRootElement(doc); //确定文档根元素

   /*检查确认当前文档中包含内容*/

   if (curNodePtr == NULL)

   {

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

      xmlFreeDoc(doc);

      exit(1);

   }

   //读取xml文档中的内容并赋值给对象属性

   xszRel = xmlNodeGetContent(curNodePtr);

   xmlFreeDoc(doc);

   return xszRel;

}

intmain(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目录,同时将编译和链接的目的文件夹都设置为项目文件夹上层的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。

然后,主程序便简化为:

intmain(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;

}

运行结果为:

IP = 127.0.0.1 Port = 80

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

4.       XML的配置文件类CXMLConfig

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

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

classCXMLConfig 

{

public:

   CXMLConfig(const char* szXmlFilename);

   ~CXMLConfig();

   //根据XPATH路径读取字符串

   string getXmlString(const char *szXpath);

   int getXmlInt(const char* szXpath);   

private:

   //代码转换:从一种编码转为另一种编码  

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

   //UNICODE码转为GB2312码  

   //成功则返回一个动态分配的char*变量,需要在使用完毕后手动free,失败返回NULL

   char* u2g(char *inbuf);

   //GB2312码转为UNICODE码   

   //成功则返回一个动态分配的char*变量,需要在使用完毕后手动free,失败返回NULL

   char* g2u(char *inbuf);

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

   xmlXPathObjectPtr get_nodeset(const xmlChar *xpath);

private:

   string m_strFilename;

   xmlDocPtr m_doc;

};

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

intmain(int argc, char* argv[])

{

   CXMLConfigxmlConfig("..""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;

}

运行结果为:

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,将原有配置文件保存为备份文件。修改后的类如下:

classCXMLConfig 

{

public:

   CXMLConfig(const char* szXmlFilename);

   ~CXMLConfig();

   //根据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:

   //代码转换:从一种编码转为另一种编码  

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

                  int inlen, char* outbuf, int outlen);

   //UNICODE码转为GB2312码  

   char* u2g(char *inbuf);

   //GB2312码转为UNICODE码  

   char* g2u(char *inbuf);

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

   xmlXPathObjectPtr get_nodeset(const xmlChar *xpath);

   // 禁止拷贝构造函数和"="操作

   CXMLConfig(const CXMLConfig&);

   CXMLConfig& operator=(const CXMLConfig&);

private:

   string m_strFilename;

   xmlDocPtr m_doc;

};

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

intmain(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;

}

运行完以后会发现两个结果,第一个是配置文件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配置文件结构,并放在正确的地方;

第三,使用CXMLConfigxmlConfig("..""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++的入门文章,恰到好处!

 

 

http://www.blogjava.net/wxb_nudt/archive/2009/01/03/203317.html

你可能感兴趣的:(C++中的XML配置文件编程经验)