开源xml解析器
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Expat
Expat 是什么?
Expat 是一个用C语言开发的、用来解析XML文档的开发库,它最初是开源的、Mozilla 项目下的一个XML解析器。
关于作者
这个库的开发者是James Clark,还开发了很多我们所熟知的工具包:groff、Jade、XP (a Java XML parser package)、 and XT (a Java XSL engine)。
Expat XML Parser 概述
Expat是一个面向流的解析器。
您注册的解析器回调(或handler)功能,然后开始搜索它的文档。
当解析器识别该文件的指定的位置,它会调用该部分相应的处理程序(如果您已经注册的一个)。
该文件被输送到解析器,会被分割成多个片断,并分段装到内存中。
因此expat可以解析那些巨大的文件。
如何使用它们
Expat XML Parser支持设置多种不同的处理器。
但是要使用它们,你只需要学习四个功能,即可满足80%的需要。
它们是:
XML_ParserCreate Create a new parser object.
XML_SetElementHandler Set handlers for start and end tags.
XML_SetCharacterDataHandler Set handler for text.
XML_Parse Pass a buffer full of document to the parser
开源的XML Parser expat
文章分类:C++编程
expat是使用C所写的XML解释器,采用流的方式来解析XML文件,并且基于事件通知型来调用分析到的数据,并不需要把所有XML文件全部加载到内存里,这样可以分析非常大的XML文件。
由于 expat库是由XML的主要负责人James Clark来实现的,因此它是符合W3C的XML标准的。
正因为源码全部是纯C所写,因此,非常容易移植,尤其是适用于嵌入式平台,我在往联芯的手机平台上移植时,几乎没改任何东西。
不过,优点也带来了缺点,因为是采用流的方式解析XML,所以不会像TinyXML那样在
一块内存中生成基于DOM的树。
虽然这样解析起来略显麻烦,但是基于回调的机制,在我看来还是蛮方便的。
下面就说使用方法:
首先是用XML_ParserCreate(const XML_Char *encodingName),参数一般为NULL,函数返回一个XML_Parser类型指针,我们就当他是一个句柄吧,类似于Windows里的内核对象,一般需要保存在一个全局的指针里。
然后调用XML_SetElementHandler(XML_Parser parser,
XML_StartElementHandler start,
XML_EndElementHandler end)
第一个参数是那个Parser句柄,第二个和第三个参数则是整个Parser的核心,类型为CallBack的函数,不了解CallBack函数的,我在这里简单说下,函数调用一般分为两种,一种是主调,即编写代码者自己调用的函数,还一种称为Callback函数,编码者写好,但他自己却不主动调用,而是在某些条件下(编码者并不清楚具体时间和流程),由其他函数调用,比如设备驱动,操作系统提供了一组某个设备的函数指针,比如LCD屏驱动,由一组画点,画线,画块等函数组成,当更换LCD时,只需要把操作系统开放的函数指针,指向你提供的接口即可,操作系统再需要时,会自动调用你的驱动函数,这就是回调函数一个典型的例子。
这二个回调分别是对应于解析<>和</>,下面分别详细介绍这个2个回调函数。
typedef void (XMLCALL *XML_StartElementHandler) (
void *userData,
const XML_Char *name,
const XML_Char **atts);
其中第一个参数userData, 可以由函数
XML_SetUserData(XML_Parser parser, void *p)
设置,参数就不用说了吧?后面两个参数,我用个具体的列子说明下,这样更好理解:比如有个标准XML,某个标签属性如下:
<feed version="2.0" ctxt-id="9212" template-id="default" feed-type="ftti"> 那么StartElementHandler回调返回的name就是标签"feed", **atts是一个指针数组,分别指向标签的一组属性,atts[0]就是"version", atts[1]就是"2.0", 以此类推。
应该很清楚了吧?呵呵。
这时候必然有个对应的</feed>:
typedef void (XMLCALL *XML_EndElementHandler) (
void *userData,
const XML_Char *name);
就是处理标签结束的,name就是"feed”了,这个回调一般是用户设置自己的状态机的。
最后一个函数就是
XML_SetCharacterDataHandler(
XML_Parser parser,
XML_CharacterDataHandler handler)
这个函数是设置处理一个<>和</>之间的字段的回调。
回调原型如下:
typedef void (XMLCALL *XML_CharacterDataHandler) (
void *userData,
const XML_Char *s,
int len);
其中第二个参数是一块Buffer的指针,如果你单步DEBUG后,你会发现expat用的就是你传入的那块Buffer(这块Buffer下面讲解),比如:
<title>天气</title>
<summary>28日08时至29日08时,陕西中南部、山西西南部、河南中南部、
湖北北部、四川中东部、重庆西部和北部、贵州西部等地的部分地区有大雨或
暴雨,河南南部、湖北北部等地局部有大暴雨。
【点击“更多”查询其他城市
天气】</summary>
假设目前解析到天气这个charData, 如果你看那个指针的所有内容的话,实际上是这样的:天气</title>
<summary>28日08时至29日08时,陕西中南部、山西西南部、河南中南部、湖北北部、四川中东部、重庆西部和北部、贵州西部等地的部分地区有大雨或暴雨,河南南部、湖北北部等地局部有大暴雨。
【点击“更多”查询其他城市天气】</summary> 所以要根据第三个参数len来确定正确的数据。
但这里有个非常隐晦的问题,如果不知道的话,会带来很大麻烦,下面说。
最后就是parse,调用
XML_Parse(XML_Parser parser, const char *s, int len, int isFinal)
第二个参数是用户指定的Buffer指针,第三个是这块Buffer中实际内容的字节数,最后参数代表是否这块Buffer已经结束。
比如要解析的XML文件太大,但内存比较吃紧,Buffer 比较小,则可以循环读取文件,然后丢给Parser,在文件读取结束前,isFinal参数为FALSE,反之为TRUE。
这里的Buffer如果太小则会造成上面提到那个隐晦的问题,
XML_CharacterDataHandler一次返回的可能并不是完整的CharData,比如这个charData的Len大于你的 Buffer大小,那这是会连续调用2次XML_CharacterDataHandler,我们需要将2次结果拼接起来,以得到正确结果,因此我们的状态机一定要考虑到这点。
顺便说下
XML_ParserReset(XML_Parser parser, const XML_Char *encodingName)函数,在某些时候,如果你不确定前后2次XML是否一样的情况下,比如网络上投递的XML,在一次解析后最好调用一次本函数,否则会出现意料之外的结果。
比如前后两次XML完全一样,可这你并不知情,那么XML_Parse()会返回失败。
XML解析-libxml库函数解释
libxml(一)
绪论
Libxml是一个实现读、创建及操纵XML数据功能的C语言库。
这个指南提供例子代码并给出它基本功能的解释。
在这个项目的主页上有Libxml及更多关于它可用的资料。
包含
有完整的API文档。
这个指南并不能替代这些完整的文档,但是阐明功能需要使用库来完成基本操作。
这个指南基于一个简单的XML应用,它使用我写的一篇文章生成,它包含有元数据和文章的主体。
本指南中的例子代码示范如何做到:
•解析文档
•取得指定元素的文本
•添加一个元素及它的内容
•添加一个属性
•取得一个属性的值
例子的完整代码包含在附录中
数据类型
Libxml定义了许多数据类型,我们将反复碰到它们,它隐藏了杂乱的来源以致你不必处理它除非你有特定的需要。
xmlChar替代char,使用UTF-8编码的一字节字符串。
如果你的数据使用其它编码,它必须被转换到UTF-8才能使用libxml的函数。
在libxml编码支持WEB页面有更多关于编码的有用信息。
XmlDoc包含由解析文档建立的树结构,xmlDocPtr是指向这个结构的指针。
xmlNodePtr and xmlNode包含单一结点的结构xmlNodePtr是指向这个结构的指针,它被用于遍历文档树。
解析文档
解析文档时仅仅需要文件名并只调用一个函数,并有错误检查。
完整代码:附录C, Keyword例程代码
①xmlDocPtr doc;
②xmlNodePtr cur;
③doc = xmlParseFile(docname);
④if (doc == NULL ) {
fprintf(stderr,"Document not parsed successfully. \n");
return;
}
⑤cur = xmlDocGetRootElement(doc);
⑥if (cur == NULL) {
fprintf(stderr,"empty document\n");
xmlFreeDoc(doc);
return;
}
⑦if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
fprintf(stderr,"document of the wrong type, root node != story");
xmlFreeDoc(doc);
return;
}
①定义解析文档指针。
②定义结点指针(你需要它为了在各个结点间移动)。
④检查解析文档是否成功,如果不成功,libxml将指一个注册的错误并停止。
注释
一个常见错误是不适当的编码。
XML标准文档除了用UTF-8或UTF-16外还可用其它编码保存。
如果文档是这样,libxml将自动地为你转换到UTF-8。
更多关于XML编码信息包含在XML标准中。
⑤取得文档根元素
⑥检查确认当前文档中包含内容。
⑦在这个例子中,我们需要确认文档是正确的类型。
“Story”是在这个指南中使用文档的根类型。
取得元素内容
你找到在文档树中你要查找的元素后可以取得它的内容。
在这个例子中我们查找“story”元素。
进程将在冗长的树中查找我们感兴趣的元素。
我们假定你已经有了一个名为doc的xmlDocPtr和一个名为cur的xmlNodPtr。
①cur = cur->xmlChildrenNode;
②while (cur != NULL) {
if ((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))){
parseStory (doc, cur);
}
cur = cur->next;
}
①取得cur的第一个子结点,cur指向文档的根,即“story”元素。
②这个循环迭代通过“story”的子元素查找“storyinfo”。
这是一个包含有我们将查找的“keywords”的元素。
它使用了libxml字符串比较函数xmlStrcmp。
如果相符,它调用函数parseStory。
void parseStory (xmlDocPtr doc, xmlNodePtr cur) {
xmlChar *key;
① cur = cur->xmlChildrenNode;
② while (cur != NULL) {
if ((!xmlStrcmp(cur->name, (const xmlChar *)"keyword"))) {
③ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1);
printf("keyword: %s\n", key);
xmlFree(key);
}
cur = cur->next;
}
return;
}
①再次取得第一个子结点。
②像上面那个循环一样,我们能够迭代,查找我们感兴趣的叫做“keyword”的元素。
③当我们找到元素“keyword”时,我们需要打印它包含在XML中的记录的内容,文本被包含于元素的子结点中,因此我们借助了cur->xmlChildrenNode,为了取得文本,我们使用函数xmlNodeListGetString,它有一个文档指针参数,在这个例子中,我们仅仅打印它。
注释
因为xmlNodeListGetString为它返回的字符串分配内存,你必须使用xmlFree释放它。
使用XPath取得元素内容
除了一步步遍历文档树查找元素外,Libxml2包含支持使用Xpath表达式取得指定结点集。
完整的Xpath API文档在这里。
Xpath允许通过路径文档搜索匹配指定条件的结点。
在下面的例子中,我们搜索文档中所有的“keyword”元素。
注释
下面是Xpath完整的讨论。
它详细的使用资料,请查阅Xpath规范。
这个例子完整的代码参见附录D,XPath例程代码。
Using XPath requires setting up an xmlXPathContext and then supplying the XPath expression and the context to the xmlXPathEvalExpression
function.
The function returns an xmlXPathObjectPtr, which includes the set of nodes satisfying the XPath expression.
使用XPath需要安装xmlXPathContext才支持XPath表达式及xmlXPathEvalExpression函数,这个函数返回一个xmlXPathObjectPtr,它包含有
XPath表达式的结点集。
xmlXPathObjectPtr
getnodeset (xmlDocPtr doc, xmlChar *xpath){
①xmlXPathContextPtr context;
xmlXPathObjectPtr result;
②context = xmlXPathNewContext(doc);
③result = xmlXPathEvalExpression(xpath, context);
④if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
printf("No result\n");
return NULL;
}
xmlXPathFreeContext(context);
return result;
}
①首先定义变量
②初始化变量context
③应用XPath表达式
④检查结果
由函数返回的xmlPathObjectPtr包含一个结点集和其它需要被迭代及操作的信息。
在这个例子中我们的函数返回xmlXPathObjectPtr,我们使用它打印我们文档中keyword结点的内容。
这个结点集对象包含在集合(nodeNr)中的元素数目及一个结点(nodeTab)数组。
①for (i=0; i < nodeset->nodeNr; i++) {
②keyword = xmlNodeListGetString(doc,
nodeset->nodeTab[i]->xmlChildrenNode, printf("keyword: %s\n", keyword); xmlFree(keyword);
}
①变量nodeset->Nr持有结点集中元素的数量。
我们使用它遍历数组。
②打印每个结点包含的内容。
注释
Note that we are printing the child node of the node that is returned, because the contents of the keyword element are a child text node.注意我们打印的是结点的子结点的返回值,因为keyword元素的内容是一个子文本结点。
写(插入)元素
写元素内容使用上面许多一样的步骤—解析文档并遍历树。
我们先解析文档然后遍历树查找我们想插入元素的位置。
在这个例子中,我们再一次查找“storyinfo”元素并插入一个keyword。
然后我们装文件写入磁盘。
完整代码:附录E,添加keyword例程
本例中主要的不同在于parseStory
void
parseStory (xmlDocPtr doc, xmlNodePtr cur, char *keyword) {
①xmlNewTextChild (cur, NULL, "keyword", keyword);
return;
}
①XmlNewTextChild函数添加一个当前结点的新的子元素到树中
一旦结点被添加,我们应当写文档到文件中。
你是否想给元素指定一个命名空间?你能添加它,在我们的例子中,命名空间是NULL。
xmlSaveFormatFile (docname, doc, 1);
第一个参数是写入文件的名,你注意到和我们刚刚读入的文件名是一样的。
在这个例子中,我们仅仅覆盖原来的文件。
第二个参数是一个xmlDoc结构指针,第三个参数设定为1,保证在输出上写入。
libxml(二)
写属性
写属性类似于给一个新元素写文本。
在这个例子中,我们将添加一个reference结点URI属性到我们的文档中。
完整代码:附录F,添加属性例程代码。
reference是story元素的一个子结点,所以找到并插入新元素及其属性是简单的。
一旦我们在parseDoc进行了错误检查,我们将在正确的位置加放我们的新元素。
但进行之前我们需要定义一个此前我们不见过的数据类型。
xmlAttrPtr newattr;
我们也需要xmlNodePtr:
xmlNodePtr newnode;
剩下的parseDoc则和前面一样,检查根结点是否为story。
如果是的,那我们知道我们将在指定的位置添加我们的元素。
① newnode = xmlNewTextChild (cur, NULL, "reference", NULL);
②newattr = xmlNewProp (newnode, "uri", uri);
①使用xmlNewTextChild函数添国一个新结点到当前结点位置。
一旦结点被添加,文件应像前面的例子将我们添加的元素及文本内容写入磁盘。
取得属性
取得属性值类似于前面我们取得一个结点的文本内容。
在这个例子中,我们将取出我们在前一部分添加的URI的值。
完整代码:附录G,取得属性值例程代码。
这个例子的初始步骤和前面是类似的:解析文档,查找你感兴趣的元素,然后进入一个函数完成指定的请求任务。
在这个例子中,我们调用getReference。
void getReference (xmlDocPtr doc, xmlNodePtr cur) {
xmlChar *uri;
cur = cur->xmlChildrenNode;
while (cur != NULL) {
if ((!xmlStrcmp(cur->name, (const xmlChar *)"reference"))) {
① uri = xmlGetProp(cur, "uri");
printf("uri: %s\n", uri);
xmlFree(uri);
}
cur = cur->next;
}
return;
}
①关键函数是xmlGetProp,它返回一个包含属性值的xmlChar。
在本例中,我们仅仅打印它。
注释
如果你使用DTD定义属性的固定值或缺省值,这个函数也将取得它。
编码转换
数据编码兼容问题是程序员新建普通的XML或特定XML时最常见的困难。
稍后的讨论来思考设计你的应用程序将帮助你避免这个困难。
实际上,libxml能以UTF-8格式保存和操纵多种数据
你的程序使用其它的数据格式,比如常见的ISO-8859-1编码,必须使用libxml函数转换到UTF-8。
如果你想你的程序以除UTF-8外的其它编码方式输出也必须做转换。
如果能有效地转换数据Libxml将使用转换器。
无转换器时,仅仅UTF-8、UTF-16和ISO-8859-1能够被作为外部格式使用。
有转换器时,它能将从其它格式与UTF-8互换的任何格式均可使用。
当前转换器支持大约150种不同的编码格式之间的相互转换。
实际支持的格式数量正在被实现。
每一个实现在的转换器尽可能的支持每一种格式。
警告:一个常见错误是在内部数据不同的部分使用不同的编码格式。
最常见的情况是一个用以ISO-8859-1作为内部数据格式,结合libxml部分使用UTF-8格式。
结果是一个应用程序要面对不同地内部数据格式。
一部分代码执行后,它或其它部分代码将使用曲解的数据。
这个例子构造一个简单的文档,然后添加在命令行提供的内容到根元素并使用适当的编码将结果输出到标准输出设备上。
在这个例子中,我们使用ISO-8859-1编码。
在命令输入的内容将被从ISO-8859-1转换到UTF-8。
完整代码:附件H,编码转换例程代码。
包含在例子中的转换函数使用libxml的xmlFindCharEncodingHandler函数。
①xmlCharEncodingHandlerPtr handler;
②size = (int)strlen(in)+1;
out_size = size*2-1;
out = malloc((size_t)out_size);
…
③handler = xmlFindCharEncodingHandler(encoding);
…
④handler->input(out, &out_size, in, &temp);
…
⑤xmlSaveFormatFileEnc("-", doc, encoding, 1);
①定义一个xmlCharEncodingHandler函数指针。
②XmlCharEncodingHandler函数需要给出输入和输出字符串的大小,这里计算输入输出字符串。
③XmlFindCharEncodingHandler使用数据初始编码作为参数搜索libxml已经完成的转换器句柄并将找到的函数指针返回,如果没有找到则返回NULL。
④The conversion function identified by handler requires as its arguments pointers to the input and output strings, along with the length of each. The lengths must be determined separately by the application.
由句柄指定的转换函数请求输入、输出字符中及它们的长度作为参数。
这个长度必须由应用程序分别指定。
⑤用指定编码而不是UTF-8输出,我们使用xmlSaveFormatFileEnc指不定期编码方式。
libxml2在linux下的使用
官网地址:
1.下载和安装LIBXML2
Libxml2是个C语言的XML程式库,能够简单方便的提供对XML文档的各种操作,并且支持XPATH查询,连同部分的支持XSLT转换等功能。
Libxml2的下载地址是
/,完全版的库是开源的,并且带有例子程式和说明文档。
最好将这个库先下载下来,因为这样能够查看其中的文档和例子。
由于我是在linux下用C语言进行研发的,所以我下载的是libxml2-2.6.20.tar.gz
版本的源码包。
具体安装步骤:
1、解压:$tar zxvf libxml2-2.6.20.tar.gz
2、进入解压后的安装目录:$cd libxml2-2.6.20
3、安装三部曲:1)$./configure
2)$make
3)$make install(安装完毕)
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能够组织为树。
同时更有以下重要元素:
●节点中的文字内容:content;
●节点所属文档:doc;
●节点名字:name;
●节点的namespace:ns;
●节点属性列表: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 nodeNr; i++)
{
nodeset->nodeTab;
}
注意,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
#include
#include
#include
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文档被创建,写入"个字节" }
//释放文档内节点动态申请的内存
xmlFreeDoc(doc);
return 1;
}
编译链接命令如下:
nmake TARGET_NAME=CreateXmlFile
然后执行可执行文档CreateXmlFile.exe,会生成一个xml文档CreatedXml.xml,打开后如下所示:
root>
newNode1>newNode1 contentnewNode1>
newNode2>newNode2 contentnewNode2>
newNode3>newNode3 contentnewNode3>
node2 attribute="yes">NODE CONTENTnode2>
son>
grandson>This is a grandson nodegrandson>
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这种类型,其使用方法前面已介绍了。
源代码如下: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
#include
int main(int argc, char* argv[])
{
xmlDocPtr doc; //定义解析文档指针
xmlNodePtr curNode; //定义结点指针(您需要他为了在各个结点间移动)
xmlChar *szKey; //临时字符串变量
char *szDocName;
if (argc {
printf("Usage: %s docname"n", argv[0]);
return(0);
}
szDocName = argv[1];
doc = xmlReadFile(szDocName,"GB2312",XML_PARSE_RECOVER); //解析文档
//检查解析文档是否成功,假如不成功,libxml将指一个注册的错误并停止。
//一个常见错误是不适当的编码。
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)
{
//取出节点中的内容
if ((!xmlStrcmp(curNode->name, (const xmlChar *)"newNode1")))
{
szKey = xmlNodeGetContent(curNode);
printf("newNode1: %s"n", szKey);
xmlFree(szKey);
}
//查找带有属性attribute的节点
if (xmlHasProp(curNode,BAD_CAST "attribute"))。