Xerces C++ 解析XML文件
2013-12-27 11:24
435 查看
1、Xerces-C++是什么? Xerces-C++ 的前身是 IBM 的 XML4C 项目。XML4C 和 XML4J 是两个并列的项目,而 XML4J 是 Xerces-J——Java 实现——的前身。IBM 将这两个项目的源代码让与 Apache 软件基金会(Apache Software Foundation),他们将其分别改名为 Xerces-C++ 和 Xerces-J。这两个项目是 Apache XML 组的核心项目(如果看到的是“Xerces-C”而不是“Xerces-C++”,也是同一个东西,因为这个项目一开始就是用 C(译者注:原文为C++)语言编写的)。 2、Xerces-C++: 功能介绍 Xerces-C++是一个非常健壮的 XML 解析器,其提供的两种解析XML文档的方法,DOM和SAX (我是采用DOM方法)。 SAX是一个面向事件的编程API.一个解析引擎消耗XML序列数据,并在发现进来的XML数据的结构时回调应用程序,这些回调称为事件句柄. 与SAX不同,它允许对XML文档进行编辑并保存为一个文件或者流,还允许以编程的方式构建一个XML文档.DOM提供了一个内存中的模型,你可以遍历文档树,删除节点或者嫁接新节点.与解析的SAX事件不同,DOM事件反映出用户与文档的互动以及使用文档的改变. 总的来说,SAX是按行遍历XML文档的,而DOM是先把XML文档生成树,然后遍历DOM树,来解析每个节点. Xerces-C++:学习的过程 2.1、平台选择: 在学习Xerces-C++之前你必须选择一种应用平台,可以是windows、linux、cygwin,以及solaris等系统平台。在这里,我选用的是Redhat Enterprise Linux AS3,选用的Xerces-C++ 是xerces-c-src_2_7_0.tar.gz,可以从官方网站:http://www.apache.org/ 直接下载。 2.2、编译源码 由于我下载下来的是源码,所以需要对其进行编译,否则我们无法加载库文件。 首先进入你的工作目录:cd /home/olcom/laubo(这是我当前工作目录) 然后解压你的源码包: tar zxvf xerces-c-src_2_7_0.tar.gz 设置包含源代码的环境变量: export XERCESCROOT=/home/olcom/laubo/xerces-c-src_2_7_0 进入目录:cd xerces-c-src_2_7_0/src/xercesc 运行脚本生成makefile文件: ./runConfigure -plinux -cgcc -xg++ -C--prefix=/opt/ApacheXML 选项: -p 为操作系统平台 -c C 编译器 -x C++编译器 -c 库的配置路径 编译源码:make make install (编译可能要花费你好一会儿,在我的机器上花费大约7分钟的时间,所以要耐心等候) 2.3、学习类库 因为类库很大,所以刚开始,我并没有选择去分析与阅读类库,我是先在网上了一个比较完整的例子,然后对其进行编译和调试,然后从例子下手去分析类库所提供的接口。这里,我把自己的程序简化了一下,希望可以作为大家学习的例子。 首先,我们需要定义一种 XML文档的样式。在这里,我们简单的定义一种样式(含有中文),如下: //sample.xml <?xml version="1.0" encoding="utf-8" standalone="no"?> <国家调查> <Node1> <subNode> <subNode1> <subNode11>china 111-> 江苏 </subNode11> <subNode11>china 112-> 天津 </subNode11> <subNode11>china 113-> 北京 </subNode11> <subNode11>china 114-> 上海 </subNode11> <subNode11>china 115-> 广州 </subNode11> </subNode1> </subNode> <subNode1>Asia 12-> 韩国 </subNode1> <subNode2>Asia 13-> 日本 </subNode2> <subNode3>Asia 14-> 越南 </subNode3> <subNode4>Asia 15-> 柬埔寨 </subNode4> <subNode5>Asia 16-> 老挝 </subNode5> </Node1> <Node2> <subNode>America 21-> 巴西 </subNode> <subNode>America 22-> 阿根廷 </subNode> <subNode>America 23-> 智利 </subNode> <subNode>America 24-> 墨西哥 </subNode> <subNode>America 25-> 巴拉圭 </subNode> <subNode>America 26-> 美国 </subNode> <subNode>America 27-> 加拿大 </subNode> </Node2> <Node3> <subNode>Europe 31-> 英国</subNode> <subNode>Europe 32-> 意大利 </subNode> <subNode>Europe 33-> 法国</subNode> <subNode>Europe 34-> 德国 </subNode> <subNode>Europe 35-> 西班牙</subNode> <subNode>Europe 36-> 匈牙利 </subNode> </Node3> <Node5>THE END </Node5> </国家调查> 定义好格式后,我们来看看程序是如何实现对其解析的,程序如下:CODE: [Copy to clipboard]//CXML.h #ifndef XML_PARSER_HPP #define XML_PARSER_HPP #include <xercesc/util/TransService.hpp> #include <xercesc/dom/DOM.hpp> #include <xercesc/dom/DOMDocument.hpp> #include <xercesc/dom/DOMDocumentType.hpp> #include <xercesc/dom/DOMElement.hpp> #include <xercesc/dom/DOMImplementation.hpp> #include <xercesc/dom/DOMImplementationLS.hpp> #include <xercesc/dom/DOMNodeIterator.hpp> #include <xercesc/dom/DOMNodeList.hpp> #include <xercesc/dom/DOMText.hpp> #include <xercesc/dom/DOMAttr.hpp> #include <xercesc/parsers/XercesDOMParser.hpp> #include <xercesc/util/XMLUni.hpp> #include <xercesc/framework/XMLFormatter.hpp> #include <xercesc/util/XMLString.hpp> #include <stdlib.h> #include <string> #include <vector> #include <stdexcept> using namespace std; using namespace xercesc; class XMLStringTranslate; class CXML { public: CXML(); ~CXML(); XMLTransService::Codes tranServiceCode; void xmlParser(string&) throw(std::runtime_error); private: XMLStringTranslate *XMLTan; xercesc::XercesDOMParser *m_DOMXmlParser; //定义解析对象 }; class XMLStringTranslate : public XMLFormatTarget { public: XMLStringTranslate(const char * const encoding); bool TranslatorUTF8ToChinese(string &strTranslatorMsg); bool UTF8_2_GB2312(char *in, int inLen, char *out, int outLen); string translate(const XMLCh* const value); const XMLCh * const translate(const char * const value); virtual ~XMLStringTranslate();protected: XMLFormatter * fFormatter; XMLCh * fEncodingUsed; XMLCh * toFill; char * m_value; protected: enum Constants { kTmpBufSize = 16 * 1024, kCharBufSize = 16 * 1024 }; void clearbuffer(); virtual void writeChars(const XMLByte* const toWrite , const unsigned int count , XMLFormatter* const formatter); }; #endif//CXML.cpp #include <string> #include <iostream> #include <sstream> #include <stdexcept> #include <list> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <unistd.h> #include <iconv.h> #include "CXML.h" bool XMLStringTranslate::UTF8_2_GB2312(char *in, int inLen, char *out, int outLen) //码型转换 { iconv_t cd = iconv_open( "gbk", "UTF-8" ); // check cd if( (int)cd == -1 ) { cout << "iconv is ERROR" << endl; return false; } char *pin = in, *pout = out; int inLen_ = inLen + 1; int outLen_ = outLen;iconv( cd, &pin, (size_t*)&inLen_, &pout, (size_t*)&outLen_ ); iconv_close(cd); return true; } bool XMLStringTranslate::TranslatorUTF8ToChinese(string &strTranslatorMsg) { char* pstrSource = const_cast<char*>(strTranslatorMsg.c_str()); char pstrDestination[strTranslatorMsg.length()*2+1]; //如此处编译出错,可改为 //char *pstrDestination = new char[strTranslatorMsg.length()*2+1], 但要记住释放 memset(pstrDestination, '\0', strTranslatorMsg.length()*2+1); if(!UTF8_2_GB2312(pstrSource, strTranslatorMsg.length(), pstrDestination, strTranslatorMsg.length())) return false;strTranslatorMsg = pstrDestination; return true; } CXML::CXML() { try { // Initialize Xerces-C++ library XMLPlatformUtils::Initialize(); } catch(xercesc::XMLException & excp) { char* msg = XMLString::transcode(excp.getMessage()); printf("XML toolkit initialization error: %s\n", msg); XMLString::release(&msg); } XMLTan = new XMLStringTranslate("utf-8"); //创建 XercesDOMParser 对象,用于解析文档 m_DOMXmlParser = new XercesDOMParser; } CXML::~CXML() { try { delete XMLTan; XMLPlatformUtils::Terminate(); } catch(XMLException& excp) { char* msg = XMLString::transcode(excp.getMessage()); printf("XML toolkit terminate error: %s\n", msg); XMLString::release(&msg); } } void CXML::xmlParser(string & xmlFile) throw( std::runtime_error ) { //获取文件信息状态 struct stat fileStatus; int iretStat = stat(xmlFile.c_str(), &fileStatus); if( iretStat == ENOENT ) throw ( std::runtime_error("file_name does not exist, or path is an empty string.") ); else if( iretStat == ENOTDIR ) throw ( std::runtime_error("A component of the path is not a directory.")); else if( iretStat == ELOOP ) throw ( std::runtime_error("Too many symbolic links encountered while traversing the path.")); else if( iretStat == EACCES ) throw ( std::runtime_error("ermission denied.")); else if( iretStat == ENAMETOOLONG ) throw ( std::runtime_error("File can not be read\n")); //配置DOMParser m_DOMXmlParser->setValidationScheme( XercesDOMParser::Val_Auto ); m_DOMXmlParser->setDoNamespaces( false ); m_DOMXmlParser->setDoSchema( false ); m_DOMXmlParser->setLoadExternalDTD( false ); try { //调用 Xerces C++ 类库提供的解析接口 m_DOMXmlParser->parse(xmlFile.c_str()) ; //获得DOM树 DOMDocument* xmlDoc = m_DOMXmlParser->getDocument(); DOMElement *pRoot = xmlDoc->getDocumentElement(); if (!pRoot ) { throw(std::runtime_error( "empty XML document" )); } // create a walker to visit all text nodes. /********************************************** DOMTreeWalker *walker = xmlDoc->createTreeWalker(pRoot, DOMNodeFilter::SHOW_TEXT, NULL, true); // use the tree walker to print out the text nodes. std::cout<< "TreeWalker:\n"; for (DOMNode *current = walker->nextNode(); current != 0; current = walker->nextNode() ) { char *strValue = XMLString::transcode( current->getNodeValue() ); std::cout <<strValue; XMLString::release(&strValue); } std::cout << std::endl; *************************************************/ // create an iterator to visit all text nodes. DOMNodeIterator* iterator = xmlDoc->createNodeIterator(pRoot, DOMNodeFilter::SHOW_TEXT, NULL, true); // use the tree walker to print out the text nodes. std::cout<< "iterator:\n"; for ( DOMNode * current = iterator->nextNode(); current != 0; current = iterator->nextNode() ) { string strValue = XMLTan->translate(current->getNodeValue() ); XMLTan->TranslatorUTF8ToChinese(strValue); std::cout <<strValue<<endl; } std::cout<< std::endl; } catch( xercesc::XMLException& excp ) { char* msg = xercesc::XMLString::transcode( excp.getMessage() ); ostringstream errBuf; errBuf << "Error parsing file: " << msg << flush; XMLString::release( &msg ); } } XMLStringTranslate::XMLStringTranslate(const char * const encoding):fFormatter(0), m_value(0),fEncodingUsed(0),toFill(0) { XMLFormatTarget * myFormTarget = this; fEncodingUsed=XMLString::transcode(encoding); fFormatter = new XMLFormatter(fEncodingUsed ,myFormTarget ,XMLFormatter::NoEscapes ,XMLFormatter::UnRep_CharRef); toFill=new XMLCh[kTmpBufSize]; clearbuffer(); } XMLStringTranslate::~XMLStringTranslate() { if(fFormatter) delete fFormatter; if(fEncodingUsed) delete [] fEncodingUsed; if(m_value) free(m_value); if(toFill) free(toFill);fFormatter=0; fEncodingUsed=0; m_value=0; toFill=0; } void XMLStringTranslate::writeChars(const XMLByte* const toWrite , const unsigned int count , XMLFormatter* const formatter) { if(m_value) free(m_value); m_value=0; m_value=new char[count+1]; memset(m_value,0,count+1); memcpy(m_value,(char *)toWrite,count+1); } void XMLStringTranslate::clearbuffer() { if(!toFill) return; for(int i=0;i<kTmpBufSize;i++) toFill=0; } string XMLStringTranslate::translate(const XMLCh* const value) //实现从 XMLCh* 到 string类型的转换 { *fFormatter<<value; string strValue=string(m_value); return strValue; } const XMLCh * const XMLStringTranslate::translate(const char * const value) { clearbuffer(); const unsigned int srcCount=XMLString::stringLen(value); unsigned char fCharSizeBuf[kCharBufSize]; XMLTranscoder * pTranscoder=(XMLTranscoder *)fFormatter->getTranscoder(); unsigned int bytesEaten; unsigned int size=pTranscoder->transcodeFrom( (XMLByte *)value, srcCount, toFill, kTmpBufSize, bytesEaten, fCharSizeBuf ); toFill[size]=0; string t1=string(value); string t2=translate(toFill); assert(t1==t2); return toFill; } #ifdef MAIN_TEST int main() { string xmlFile = "sample.xml"; CXML cxml; cxml.xmlParser(xmlFile); return 0; } #endif //Makefile #tHIS IS MAKEFILE FOR XERCES-C++ APPLIACTION MAIN = xml CC = g++ CFLAGS = -c -g -Wall $(MAIN):CXML.o [TAB]$(CC) CXML.o -o xml -L/opt/ApacheXML/lib -lxerces-c CXML.o:CXML.cpp [TAB]$(CC) $(CFLAGS) -pedantic -I/opt/ApacheXML/include CXML.cpp -DMAIN_TEST .PHONY:clean clean: [TAB]rm CXML.o $(MAIN) 下面简要分析一下源程序: 首先,要想利用Xerces C++类库来解析XML文档,必须要对类库进行初始化,所以在类XML的构造函数中,我们对类库进行了初始化:XMLPlatformUtils::Initialize(); 接下来,我们定义的解析对象,并在构造函数中对其进行了初始化操作,然后,在xmlParser函数中我们调用类库的解析函数接口,传人xml文件名(m_DOMXmlParser->parse(xmlFile.c_str()) ;)。因为在这里我们选用的是DOM方法,所以接下来我们需要创建DOM树:DOMDocument* xmlDoc = m_DOMXmlParser->getDocument();,并获取DOM树的根节点 DOMElement *pRoot = xmlDoc->getDocumentElement()。 再接下来是什么呢?根据上面所说的,我们需要遍历这棵DOM树,因此我们需要一种遍历方法,在程序中我给出了两种遍历的方法,一种是创建遍历树 DOMTreeWalker *walker = xmlDoc->createTreeWalker(pRoot, DOMNodeFilter::SHOW_TEXT, NULL, true),还有一种是通过迭代器来遍历整棵DOM树 DOMNodeIterator* iterator = xmlDoc->createNodeIterator(pRoot, DOMNodeFilter::SHOW_TEXT, NULL, true)。两种方法都可以达到同样的效果。程序中注释掉的代码是创建遍历树方法。 遍历完,并打印出节点值以后,我们需要终止对类库的调用,所以在析构函数中:XMLPlatformUtils::Terminate()。 解析简单xml文档的基本步骤就是如此简单,至于复杂的XML文档,解析的步骤,尤其是创建DOM树的方法有点不同,在这里便不作介绍。接下来,来讲一下困扰我多天的中文解析问题。我们知道,Xerces C++默认只支持节点名中文,至于节点值,属性值则不支持,即使解析出来的也是乱码,所以需要自己解决。在这里,我们选用UTF-8编码格式的XML文档。先来看一下乱码的原因,由于XML解析器解析的字符串都是 XMLCh*(typedef unsigned int XMLCh)格式的,一个字符占用一个字节,而汉字字符确要占用两个字节。故若不做适当的转换,汉字的输出结果就变成乱码了。在http://www.vckbase.com/document/viewdoc/?id=738 提供了一种解决的方法,但是那个解决方案只有在locale环境为UTF-8的情况下你才可以看见正常的中文输出,在locale为GB18030等环境下,你的中文是乱码。但是在一种环境下可以正常显示,说明已经可以正常解析出来了,只是在不同环境的机器上需要进行码型转换,因此,我在他提供的类中又添加了两种方法,来进行码型转换: bool TranslatorUTF8ToChinese(string &strTranslatorMsg); //实现从UTF-8到GBK、GB2312等码型的转换 bool UTF8_2_GB2312(char *in, int inLen, char *out, int outLen); 这样,你就可以在把UTF-8编码的中文正常的解析打印出来了。 |
声明:欢迎任何人和组织转载本blog中文章,但必须标记文章原始链接和作者信息。本文链接:http://blog.csdn.net/li_007/archive/2009/03/31/4038989.aspx开拓进取的小乌龟------->CSDN点滴点点滴滴Blog前面简单介绍了TinyXML,顺便写了一个简单的例子来测试利用TinyXML生成xml文件,其实代码实现很简单。如下:void WrittingXML(TiXmlString & xmlFile)
{
TiXmlDeclaration * xmlDec = new TiXmlDeclaration("1.0", "UTF-8", "yes");
TiXmlDocument * xmlDocs = new TiXmlDocument();
xmlDocs->LinkEndChild(xmlDec);
TiXmlElement * element = new TiXmlElement("Document");
xmlDocs->LinkEndChild(element);
TiXmlComment * comment = new TiXmlComment(" This is a list of new books ");
element->LinkEndChild(comment);
TiXmlElement * book = new TiXmlElement("Book");
book->SetAttribute("Name", "How to use TinyXML");
element->LinkEndChild(book);
TiXmlElement * author = new TiXmlElement("Author");
TiXmlText * Authortext = new TiXmlText("Leezhm");
author->LinkEndChild(Authortext);
book->LinkEndChild(author);
TiXmlElement * date = new TiXmlElement("Date");
TiXmlText * Datetext = new TiXmlText("2009-3-30");
date->LinkEndChild(Datetext);
book->LinkEndChild(date);
xmlDocs->SaveFile(xmlFile.c_str());
delete xmlDocs;
}运行后利用IE 8.0打开截图如下:
![](http://pic.diybl.com/pic/20090403/163659/0.jpg)
好了,看后就知道很简单的,这也是我选择它的一个原因,但这里做很简单的测试,没有对xml进行验证代码实现,但是TinyXML自己带的test Application中有对xml进行验证的函数,大家有兴趣可以去看。代码很简单不啰嗦,说点别的,仔细看看代码,其中使用了很多new来建立了对象指针,但是只看到一个delete,也许你会说着会引起内存泄漏,其实在上面的函数中,后面的delete也可以不要,并且还不会造成内存泄漏。原因看看TinyXML实现的源码中TiXmlNode的析构函数的实现代码如下:当然顺便注意下所有的类的虚拟构造函数。TiXmlNode::~TiXmlNode()
{
TiXmlNode* node = firstChild;
TiXmlNode* temp = 0;
while ( node )
{
temp = node;
node = node->next;
delete temp;
}
}
在结合一下前面文章介绍TinyXML的继承图表就清楚了为什么这儿不需要我们手动释放堆栈了,特别是不需要一个一个地释放,只需要最后释放最高节点就可以,比如TiXmlDocument节点。。。。。。在我实现的函数中其实也可以不需要delete xmlDocs这句的,因为他们都是局部变量,当变量的作用域结束的时候,会自动释放。当然使用delete会触发相应的class的析构函数的调用,这些都是C++的实现机制,在次不多说。BTW:正是由于上面的原理,我们添加到TinyXML中的节点。。。都必须是动态用new来创建的。如果创建的不是指针,在内存释放的时候会出现错误的,这个可以自己去测试。我想TinyXML这样实现是因为TInyXML实现机制的要求,因为它是基于DOM,所有的节点都是在内存中。
C++的XML编程经验――LIBXML2库使用指南
2010-02-08 15:57499人阅读评论(0)收藏举报写这篇文章的原因有如下几点:1)C++标准库中没有操作XML的方法,用C++操作XML文件必须熟悉一种函数库,LIBXML2是其中一种很优秀的XML库,而且它同时支持多种编程语言;2)LIBXML2库的Tutorial写得不太好,尤其是编码转换的部分,不适用于中文编码的转换;3)网上的大多数关于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/,完全版的库是开源的,并且带有例子程序和说明文档。最好将这个库先下载下来,因为这样可以查看其中的文档和例子。
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目录下)。显然我选择了第二种,那么编译链接一个名为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表明关闭编译优化;/D _DEBUG表明定义一个名为_DEBUG的宏
CPP_FLAGS=/c /MTd /Zi /Od /D _DEBUG
#链接选项,/DEBUG表明创建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的写法,但后续例子程序的编译链接依葫芦画瓢都没有问题,执行编译链接的命令如下:
nmake TARGET_NAME=CreateXmlFile
执行清理的命令如下:
nmake TARGET_NAME=CreateXmlFile clean
2. Libxml2中的数据类型和函数
一个函数库中可能有几百种数据类型以及几千个函数,但是记住大师的话,90%的功能都是由30%的内容提供的。对于libxml2,我认为搞懂以下的数据类型和函数就足够了。
2.1 内部字符类型xmlChar
xmlChar是Libxml2中的字符类型,库中所有字符、字符串都是基于这个数据类型。事实上它的定义是:xmlstring.h
typedef unsigned char xmlChar;
使用unsigned char作为内部字符格式是考虑到它能很好适应UTF-8编码,而UTF-8编码正是libxml2的内部编码,其它格式的编码要转换为这个编码才能在libxml2中使用。
还经常可以看到使用xmlChar*作为字符串类型,很多函数会返回一个动态分配内存的xmlChar*变量,使用这样的函数时记得要手动删除内存。
2.2 xmlChar相关函数
如同标准c中的char类型一样,xmlChar也有动态内存分配、字符串操作等相关函数。例如xmlMalloc是动态分配内存的函数;xmlFree是配套的释放内存函数;xmlStrcmp是字符串比较函数等等。
基本上xmlChar字符串相关函数都在xmlstring.h中定义;而动态内存分配函数在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的相关信息,例如文件名、文档类型、子节点等等;xmlDocPtr等于xmlDoc*,它搞成这个样子总让人以为是智能指针,其实不是,要手动删除的。
xmlNewDoc函数创建一个新的文档指针。
xmlParseFile函数以默认方式读入一个UTF-8格式的文档,并返回文档指针。
xmlReadFile函数读入一个带有某种编码的xml文档,并返回文档指针;细节见libxml2参考手册。
xmlFreeDoc释放文档指针。特别注意,当你调用xmlFreeDoc时,该文档所有包含的节点内存都被释放,所以一般来说不需要手动调用xmlFreeNode或者xmlFreeNodeList来释放动态分配的节点内存,除非你把该节点从文档中移除了。一般来说,一个文档中所有节点都应该动态分配,然后加入文档,最后调用xmlFreeDoc一次释放所有节点申请的动态内存,这也是为什么我们很少看见xmlNodeFree的原因。
xmlSaveFile将文档以默认方式存入一个文件。
xmlSaveFormatFileEnc可将文档以某种编码/格式存入一个文件中。
2.5 节点类型xmlNode、指针xmlNodePtr
节点应该是xml中最重要的元素了,xmlNode代表了xml文档中的一个节点,实现为一个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 */
};
可以看到,节点之间是以链表和树两种方式同时组织起来的,next和prev指针可以组成链表,而parent和children可以组织为树。同时还有以下重要元素:
l 节点中的文字内容:content;
l 节点所属文档:doc;
l 节点名字:name;
l 节点的namespace:ns;
l 节点属性列表:properties;
Xml文档的操作其根本原理就是在节点之间移动、查询节点的各项信息,并进行增加、删除、修改的操作。
xmlDocSetRootElement函数可以将一个节点设置为某个文档的根节点,这是将文档与节点连接起来的重要手段,当有了根结点以后,所有子节点就可以依次连接上根节点,从而组织成为一个xml树。
2.6 节点集合类型xmlNodeSet、指针xmlNodeSetPtr
节点集合代表一个由节点组成的变量,节点集合只作为Xpath的查询结果而出现(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 @@ */
};
可以看出,节点集合有三个成员,分别是节点集合的节点数、最大可容纳的节点数,以及节点数组头指针。对节点集合中各个节点的访问方式很简单,如下:
xmlNodeSetPtr nodeset = XPATH查询结果;
for (int i = 0; i < nodeset->nodeNr; i++)
{
nodeset->nodeTab[i];
}
注意,libxml2是一个c函数库,因此其函数和数据类型都使用c语言的方式来处理。如果是c++,我想我宁愿用STL中的vector来表示一个节点集合更好,而且没有内存泄漏或者溢出的担忧。
3. 简单xml操作例子
了解以上基本知识之后,就可以进行一些简单的xml操作了。当然,还没有涉及到内码转换(使得xml中可以处理中文)、xpath等较复杂的操作。
3.1 创建xml文档
有了上面的基础,创建一个xml文档显得非常简单,其流程如下:
l 用xmlNewDoc函数创建一个文档指针doc;
l 用xmlNewNode函数创建一个节点指针root_node;
l 用xmlDocSetRootElement将root_node设置为doc的根结点;
l 给root_node添加一系列的子节点,并设置子节点的内容和属性;
l 用xmlSaveFile将xml文档存入文件;
l 用xmlFreeDoc函数关闭文档指针,并清除本文档中所有节点动态申请的内存。
注意,有多种方式可以添加子节点:第一是用xmlNewTextChild直接添加一个文本子节点;第二是先创建新节点,然后用xmlAddChild将新节点加入上层节点。
源代码文件是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: 创建一个xml文件
*********************************************************************/
#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文档,从中取出想要的信息,例如节点中包含的文字,或者某个节点的属性,其流程如下:
l 用xmlReadFile函数读出一个文档指针doc;
l 用xmlDocGetRootElement函数得到根节点curNode;
l curNode->xmlChildrenNode就是根节点的子节点集合;
l 轮询子节点集合,找到所需的节点,用xmlNodeGetContent取出其内容;
l 用xmlHasProp查找含有某个属性的节点;
l 取出该节点的属性集合,用xmlGetProp取出其属性值;
l 用xmlFreeDoc函数关闭文档指针,并清除本文档中所有节点动态申请的内存。
注意:节点列表的指针依然是xmlNodePtr,属性列表的指针也是xmlAttrPtr,并没有xmlNodeList或者xmlAttrList这样的类型。看作列表的时候使用它们的next和prev链表指针来进行轮询。只有在Xpath中有xmlNodeSet这种类型,其使用方法前面已经介绍了。
相关文章推荐
- 转载:Xerces C++解析XML文档
- xerces C/C++ 读取XML文件的一个小例子
- C++中Xerces解析xml文档
- 毕业论文中使用的XML读取和制造工具!!!C++ 使用TinyXML解析XML文件
- 调用Xerces C++ XML解析库引起的内存泄漏
- XML解析简介及Xerces-C++简单使用举例
- xerces C++ 解析Utf8 xml中的非UTF 8字符。修改xerces源码XMLUTF8Transcoder.cpp
- 初次使用C++解析XML文件
- 几个C/C++的XML解析库(包括纯C的读写查找XML文件的库
- C++ 使用TinyXML解析XML文件
- 几个C/C++的 XML解析库(包括纯C的读写查找XML文件的库,都是开源的)
- 几个C/C++的 XML解析库(包括纯C的读写查找XML文件的库,都是开源的)
- Python3.5环境下使用generateDS解析xml文件生成C++头文件的方法
- 【C++】使用libxml解析XML文件
- C++生成和解析XML文件
- XML文件的解析、读取和生成(C/C++、win7、VS2010)
- XML解析简介及Xerces-C++简单使用举例
- C++ 使用TinyXML解析XML文件
- XML解析简介及Xerces-C++简单使用举例
- libxml2.7.8 c++ 解析xml文件