工作中用到了EXPAT,为了以后查询方便,把网上搜索到的内容综合整理如下。
win32 plat下的c/c++下使用expat。
expat是基于sax来进行xml解析而不是dom解析。因此,在expat中设置了很多的回调来处理。
在win32下使用,可以http://sourceforge.net/projects/expat/下载,里面有win32版本,下载下来的是一个安装包,直接安装,
安装之后,在安装目录例如C:\Expat-2.0.0下有Docslibs source 等几个目录,其中libs目录下放了4个文件,分别是libexpat
的Ansi和Unicode的dll和对应lib。在source下,有example tests lib等目录,其中lib目录下是expat的源代码(头文件和实现文件)
以及dsp文件。里面有编译生成动态链接库的工程文件expat.dsp以及编译成为静态库的expat_static.dsp。expat.dsp编译出来的动态区
名称为libexpat.dll(libexpat.lib);静态库工程expat_static.dsp编译出来的静态库名称为:libexpatMT.lib。
在source\example目录下有参考代码。
其中看看elements 这个example就ok了。
对于一个简单的xml文件,我们
main(int argc,char *argv[])
{
XML_Parser p = XML_ParserCreate(NULL);
XML_SetCharacterDataHandler(p,charhandler);
XML_SetElementHandler(p,start,end);
for (;;) {
int done;
int len;
len = fread(Buff,1,BUFFSIZE,stdin);
if (ferror(stdin)) {
fprintf(stderr,"Read error\n");
exit(-1);
}
done = feof(stdin);
if (XML_Parse(p,Buff,len,done) ==XML_STATUS_ERROR) {
fprintf(stderr,"Parse error at line%" XML_FMT_INT_MOD "u:\n%s\n",
XML_GetCurrentLineNumber(p),
XML_ErrorString(XML_GetErrorCode(p)));
exit(-1);
}
printf( " depth = %d \n",Depth);
if (done)
break;
}
return 0;
}
其中XML_SetElementHandler设置回调,处理element节点;XML_SetCharacterDataHandler设置回调用于处理text节点
一般来说有了这两个我们就可以处理了,例如下面的一个xml文件
<?xml version="1.0"?>
<xmlRoot price="">
<YEAR Now="2005">
<QUARTER1>2005</QUARTER1>
</YEAR>
</xmlRoot>
处理到xmlRoot节点的时候,会调用XML_SetElementHandler设置的回调函数,我们可以从回调函数中获取节点名称,节点的
属性列表,包括各个属性名称和对应的属性值。这里就可以获取到一个属性price,值为空。
继续下面的处理,当处理到QIARTER1时,会调用XML_SetCharacterDataHandler设置的回调函数获取text节点值。
char buf[100]={0};
static void XMLCALL charhandler(void *userData,const XML_Char *s,int len)
{
if(len!=0)
{
memcpy(buf,s,len);
buf[len] = '\0';
rintf("%s ",buf);
}
}
注意,这里的s不是以\0结束的。
本文介绍expat 解析xml的基本方法,如果你希望用最轻量的解析器,请选择TinyXML,它更简单。
使用expat的原因很多,主要还是因为expat更灵活。习惯了TinyXML,一开始不太习惯expat,分析一下,其实很容易上手的。
1.回调函数
以下案例解析xml文件中的elment,attribute和text。expat使用回调方式返回xml数据,解析器解析到一个element及其内部属性后,将调用事先设置好的函数,同样,当element结束和text结束后,也会分别调用对应的函数。
2.如何处理数据之间的包含关系
典型的方式是定义三个函数分别处理elment开始(含属性)、element结束和文本内容。回调函数的第一个参数是自定义的,通常用于存储XML文档的上下文信息,用XML_SetUserData可以设置这个参数,下例中传递一个整数指针,以便在每次回调时能知道该元素是第几层元素。
该参数也可以是一个栈对象的地址,开始一个元素时,将新元素对应的数据压入堆栈,处理下一级元素时,新元素是栈顶元素在子元素,然后处理完了继续把该元素压入堆栈,继续下一级新的子元素。当元素结束后,需要出栈,以便解析下个兄弟元素程时能取到父节点。
好啦,基本应用还是很简单的,实际上Expat的API函数不多。
3.如何处理属性
属性通过ElementHandler回调函数传入,这里有一个char** atts就是属性,这是一个字符指针数组,如果有N个属性,数组大小就是2*N+1,最后一个素组元素为空指针,奇数指针对应属性名称,偶数指针对应属性值(字符串格式)。可以在一个循环中处理多个属性,当遇到空指针时,表示没有更多属性了。
好啦,先看sample吧:
#include <stdio.h>
#include "expat.h"
#pragmawarning(disable:4996)
#defineXML_FMT_INT_MOD "l"
staticvoidXMLCALL startElement(void *userData,const char *name,const char **atts)
{
int i;
int *depthPtr = (int *)userData;
for (i = 0; i < *depthPtr; i++)
printf(" ");
printf(name);
*depthPtr += 1;
for(i=0;atts[i]!=0;i+=2)
{
printf(" %s=%s",atts[i],atts[i+1]);
}
printf("\n");
}
staticvoidXMLCALL endElement(void *userData,const char *name)
{
int *depthPtr = (int *)userData;
*depthPtr -= 1;
}
intmain(intargc, char *argv[])
{
char buf[BUFSIZ]; XML_Parserparser = XML_ParserCreate(NULL);
int done; int depth = 0;
XML_SetUserData(parser,&depth);
XML_SetElementHandler(parser,startElement,endElement);
FILE* pFile= argc<2 ?stdin : fopen(argv[1],"rb");
do
{int len = (int)fread(buf,1,sizeof(buf),pFile);
done = len < sizeof(buf);
if (XML_Parse(parser,buf,done) == XML_STATUS_ERROR)
{
fprintf(stderr,"%s at line %"XML_FMT_INT_MOD "u\n",
XML_ErrorString(XML_GetErrorCode(parser)),
XML_GetCurrentLineNumber(parser));
return 1;
}
}
while (!done);
XML_ParserFree(parser);
fclose(pFile);
return 0;
}
4.其他ElementHanlder
expat还可以设置CData,Comment的handler,另外一些函数本人还没使用过,涉及到更多的xml标准的知识,如果需要,可以参考官方的手册。
参考:
http://www.xml.com/pub/a/1999/09/expat/index.html
要了解如何使用expat XML解析器之前,先来仔细地分析一下怎么样使用expat库的小例子,看看具体调用了那些接口函数,是否会很复杂的呢?‘它的例子程序如下:
#001
#013
#015 #include<stdio.h>
#016 #include"xmlparse.h"
#017
#018 #defineBUFFSIZE 8192
#019
#020 charBuff[BUFFSIZE];
#021
#023
#024 void
#025 start(void*data,const char *el,const char **attr) {
#026 int i;
#027
#029 printf("");
#030
#032
#034 printf("%s='%s'",attr[i],attr[i + 1]);
#035 }
#036
#038 Depth++;
#039 }
#040
#041 void
#042 end(void*data,const char *el) {
#043 Depth--;
#044 }
#045
#046 void
#047 main(int argc,char **argv) {
#048 XML_Parser p =XML_ParserCreate(NULL);
#049 if (! p) {
#050fprintf(stderr,"Couldn't allocate memory for parser\n");
#051 exit(-1);
#052 }
#053
#054XML_SetElementHandler(p,end);
#055
#056 for (;;) {
#057 int done;
#058 int len;
#059
#060 len =fread(Buff,stdin);
#061 if(ferror(stdin)) {
#062fprintf(stderr,"Read error\n");
#063 exit(-1);
#064 }
#065 done =feof(stdin);
#066
#067 if (!XML_Parse(p,done)) {
#068fprintf(stderr,"Parse error at line %d:\n%s\n",
#069XML_GetCurrentLineNumber(p),
#070XML_ErrorString(XML_GetErrorCode(p)));
#071 exit(-1);
#072 }
#073
#074 if (done)
#075 break;
#076 }
#077 }
#078
----------------------------------------------------------------------------------------------------------------
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_EndElementHandlerend)
第一个参数是那个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_Parserparser,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_CharacterDataHandlerhandler)
这个函数是设置处理一个<>和</>之间的字段的回调。
回调原型如下:
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,intisFinal)
第二个参数是用户指定的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()会返回失败。