expat就是用来解析XML格式的文件的库
XML格式如下
apple
tree
hello kitty
第一个
同理
所以,键值对中可以包含别的键值对。
在了解了XML格式的数据后,我们就可以来解析它了。
直接先给大家看一下主函数
int main()
{
XML_Parser parser = XML_ParserCreate(NULL); // 第一步
XML_SetUserData(parser, elempt); // 第二步
XML_SetElementHandler(parser,StartElementHandler,EndElementHandler); // 第三步
XML_SetCharacterDataHandler(parser,CharacterDataHandler); // 第四步
XML_Parse(parser, buf.c_str(), buf.length(), 1); //第五步
XML_ParserFree(parser); //第六步
return 0;
}
主函数非常简单,只要前5步就可以解析xml格式的数据,从中得到我们想要的数据,最后释放句柄。
1.第一步。通过 XML_ParserCreate(NULL) ,函数建立一个XML_Parser格式的对象"parser",参数一般为NULL。后面这段话是自己编的(这个对象的概念本人不是非常理解。只能讲讲自己的理解。相当于建立"parser"这一个变量,而这个变量不是我们普通的变量,是XML_Parser类型的。就比如说,int和float类型,虽然占用的字节数一样,但是解析方式完全不一样,他们有各自的解析方式。而XML_Parser类型的也有自己的解析方式,我们必须以XML_Parser能识别的方式来存储数据。XML_ParserCreate函数就是能建立一个XML_Parser类型的数据。这个XML_Parser类型的数据格式是整个解析XML格式的基础。)
2.第二步。XML_SetUserData(parser, char *elempt),parser就是第一步中建立的对象。elempt是一个char *类型的字符串。这个字符串会在expat内部传递给其他函数。这个参数的作用会在下面的实际例子中展示。
3.第三步。XML_SetElementHandler(parser,StartElementHandler,EndElementHandler),parser就是第一步中建立的对象。StartElementHandler是一个回调函数。回调函数的具体概念不讲,可以自行百度。这个函数的作用就是在解析一个XML格式的文件时,每当遇到一个起始标记
4.第四步。XML_SetCharacterDataHandler(parser,CharacterDataHandler),parser就是第一步中建立的对象。CharacterDataHandler也是一个回调函数。每当遇到一对键值对中存在内容时,就会执行一次函数。结合第三步,我给大家讲一下整个过程。以本篇最开始的XML格式的数据为例。
4.1解析开始,遇到
4.2遇到
4.3然后遇到"apple",执行一次CharacterDataHandler
4.4遇到,执行EndElementHandler
4.5后面同理,最后,执行EndElementHandler,,执行EndElementHandler。解析完成
5.XML_Parse(parser, buf.c_str(), buf.length(), done),parser就是第一步中建立的对象,buf就是将XML格式的数据转换成string格式的数据。buf.c_str()得到char *类型的字符串(这个应该是c++的基础,不懂的话,百度一下,应该能明白)。buf.length(),表示buf的长度。最后一个done表示XML是否解析完成,未完成表示0,完成表示1。
6.上面这5步就是解析XML的步骤了。下面来将上面提到的三个回调函数完成一下
6.1 StartElementHandler。userData就是第二步中的char *elempt传过来的数据。name就是标签名,如果遇到
void StartElementHandler(void *userData, const XML_Char *name, const XML_Char **atts)
{
if(!strcmp(name,"name"))
{
cout << name << "\tstart_of_name" << endl;
}
if(!strcmp(name,"red"))
{
cout << name << "\tstart_of_red" << endl;;
cout << atts[0] << "\t" << atts[1]<< "\t" << atts[2]<< "\t" << atts[3]<< endl;
}
if(!strcmp(name,"blue"))
{
cout << name << "\tstart_of_blue" << endl;
}
if(!strcmp(name,"green"))
{
cout << name << "\tstart_of_green" << endl;
}
if(!strcmp(name,"pink"))
{
cout << name << "\tstart_of_pink" << endl;
}
}
6.2EndElementHandler。userData就是第二步中的的char *elempt。name就是标签的名字。
void EndElementHandler(void *userData, const XML_Char *name)
{
if(!strcmp(name,"name"))
{
cout << name << "\tend_of_name" << endl;
}
if(!strcmp(name,"red"))
{
cout << name << "\tend_of_red" << endl;
}
if(!strcmp(name,"blue"))
{
cout << name << "\tend_of_blue" << endl;
}
if(!strcmp(name,"green"))
{
cout << name << "\tend_of_green" << endl;
}
if(!strcmp(name,"pink"))
{
cout << name << "\tend_of_pink" << endl;
}
}
6.3 CharacterDataHandler。userData就是第二步中的char *elempt,这里我们就用到了。s代表了一个起始标签后的所有内容,比如读到
void CharacterDataHandler(void *userData, const XML_Char *s, int len)
{
string str2 = s;
string str1;
cout << str2 << endl;
for(int i=0;i
7.上面6点完成了expat函数的设置。接下来就可以具体的解析XML了
附上完整的程序
#include
#include "expat.h"
#include
#include
#include
#include
using namespace std;
char * elempt = "apple"; //userData
string buf = "" //XML格式的数据
"apple<\/red>"
"<\/blue>"
"tree<\/green>"
"hello kitty<\/pink>"
"<\/name>";
//如果是windows环境,<\/name>是对的
//如果是unix环境,则为
typedef struct{
string red;
string blue;
string green;
string pink;
}fruit;
fruit table_temp;
vector mytable;
int flag = 0;
void StartElementHandler(void *userData, const XML_Char *name, const XML_Char **atts) //遇到起始标记的回调函数
{
if(!strcmp(name,"name"))
{
//cout << name << "\tstart_of_name" << endl;
}
if(!strcmp(name,"red")) //当遇到时,将flag置1
{
//cout << name << "\tstart_of_red" << endl;;
//cout << atts[0] << "\t" << atts[1]<< "\t" << atts[2]<< "\t" << atts[3]<< endl;
flag = 1;
}
if(!strcmp(name,"blue")) //当遇到时,将flag置2
{
//cout << name << "\tstart_of_blue" << endl;
flag = 2;
}
if(!strcmp(name,"green")) //当遇到时,将flag置3
{
//cout << name << "\tstart_of_green" << endl;
flag = 3;
}
if(!strcmp(name,"pink")) //当遇到时,将flag置4
{
//cout << name << "\tstart_of_pink" << endl;
flag = 4;
}
}
void EndElementHandler(void *userData, const XML_Char *name)
{
if(!strcmp(name,"pink")) //当遇到pink结束标签时,表示4个数据都得到了,将结构体放入vecotr中
{
//cout << name << "\tend_of_name" << endl;
mytable.push_back(table_temp);
}
if(!strcmp(name,"red"))
{
//cout << name << "\tend_of_red" << endl;
}
if(!strcmp(name,"blue"))
{
//cout << name << "\tend_of_blue" << endl;
}
if(!strcmp(name,"green"))
{
//cout << name << "\tend_of_green" << endl;
}
if(!strcmp(name,"pink"))
{
//cout << name << "\tend_of_pink" << endl;
}
}
void CharacterDataHandler(void *userData, const XML_Char *s, int len)
{
string str2 = s;
string str1;
//cout << str2 << endl;
for(int i=0;i
8.已经把所有打印都注释了。大家可以打开注释看看结果。其中还有许多细节方面的东西。将字符串换成下面两种情况试一下。
string buf = ""
"apple<\/red>"
"<\/blue>"
"tree<\/green>"
"hello kitty<\/pink>"
"apple<\/red>"
"<\/blue>"
"tree<\/green>"
"hello kitty<\/pink>"
"<\/name>";
string buf = ""
"apple<\/red>"
"<\/blue>"
"tree<\/green>"
"hello kitty<\/pink>"
"<\/name>"
"apple<\/red>"
"<\/blue>"
"tree<\/green>"
"hello kitty<\/pink>";
当遇到的结束标签为第一个起始标签时,name整个解析过程也就结束了。不管后面有没有内容,都会结束。换句话说,只有解析到和第一个起始标签相同的结束标签(或者为空)时,解析才会停止,不管中间有多少内容,都不会结束。
键值对不需要一对一对的出现,可以只出现起始标记,也可以只出现结束标记。但是这在解析时是可以的。但是在XML中是非法的。所以必须有起始标签和结束标签。
---------------------------------------------------------------更新-------------------------------------------------------------------------------------------------------------
最近看了一下官方的文档,发现程序还待改进。官网中提供了两个解析的例子
主要是在XML_Parse()时有区别,提供了容错机制。
直接拷贝一下官网的XML_Parse
do {
size_t len = fread(buf, 1, sizeof(buf), stdin);
done = len < sizeof(buf);
if (XML_Parse(parser, buf, (int)len, done) == XML_STATUS_ERROR) {
fprintf(stderr,
"%" XML_FMT_STR " at line %" XML_FMT_INT_MOD "u\n",
XML_ErrorString(XML_GetErrorCode(parser)),
XML_GetCurrentLineNumber(parser));
return 1;
}
} while (!done);
主要是用了XML_ErrorString(XML_GetErrorCode(parser)),XML_GetCurrentLineNumber(parser)这两个函数。