简介:
Mini-XML是一个小型的XML库。您可以使用它在应用程序中读写XML以及和类XML数据文件,而不需要其他大型非标准库。Mini-XML只需要与一个ANSI C兼容的编译器(gcc以及大部分编译器)和一个make程序即可工作。
Mini-XML提供以下功能:
Mini-XML不基于其他模式文件或者其他定义信息源对数据进行验证或进行其他处理。
需要注意的是在3.0版本隐藏了mxml_node_t结构的定义,需要使用2.0版本中引入各种访问器函数
历史:
Mini-XML最初是从Gutenprint项目发展起来的,目的是用更小,更易使用的东西来替换掉libxml2库。历史不重要,只要记得Mini-XML比libxml2轻量级,更易使用即可。
Mini-XML资源
官网:https://www.msweet.org/mxml
Mini-XML中文手册:https://blog.csdn.net/bluesonic/article/details/3887143
使用Mini-XML:
在使用Mini-XML的时候需要在工程中包含一个头文件mxml.h
#include "mxml.h"
另外在应用程序链接的时候需要使用-lmxml选项
gcc -o myapp myapp.c -lxml -o myapp myapp.c -lxml
如果你安装了pkg-config软件,你可使用pkg-config软件来确定正确的编译器以及链接选项
gcc `pkg-config --cflags mxml` -o myprogram myprogram.c `pkg-config --libs mxml`
`pkg-config --cflags mxml` -o myprogram myprogram.c `pkg-config --libs mxml`
加载XML文件:
加载xml文件需要使用mxmlLoadFile函数.
mxml_node_t *mxmlLoadFile(mxml_node_t *top, FILE *fp,mxml_type_t (*cb)(mxml_node_t *));
*mxmlLoadFile(mxml_node_t *top, FILE *fp,mxml_type_t (*cb)(mxml_node_t *));
cb参数是有一个mxml_node_t指针参数且返回值是mxml_type_t类型的函数指针。cb所代表的回调函数可以是您提供的函数,也可以是由mxml提供的标准函数之一。例如要加载的xml文件的名字是filename.可以使用MXML_OPAQUE_CALLBACK函数
FILE *fp;
mxml_node_t *tree;
fp = fopen("filename.xml", "r");
tree = mxmlLoadFile(NULL, fp, MXML_OPAQUE_CALLBACK);
fclose(fp);
Mini-XML也提供从文件描述符或字符串中加载xml数据的函数:
mxml_node_t *mxmlLoadFd(mxml_node_t *top, int fd,mxml_type_t (*cb)(mxml_node_t *));
*mxmlLoadFd(mxml_node_t *top, int fd,mxml_type_t (*cb)(mxml_node_t *));
mxml_node_t *mxmlLoadString(mxml_node_t *top, const char *s,mxml_type_t (*cb)(mxml_node_t *));
*mxmlLoadString(mxml_node_t *top, const char *s,mxml_type_t (*cb)(mxml_node_t *));
加载回调:
mxmlLoad函数的最后一个参数是一个回调函数,此回调函数用于确定XML文档中每个数据节点的值的类型。Mini-XML为简单的XML数据定义了几个标准回调函数:
另外你可以为更为复杂的XML文档提供自己的回调函数。回调函数将接收指向当前元素节点的指针,并范围该元素节点的直接子节点的值的类型:MXML_CUSTOM,MXML_INTEGER,MXML_OPAQUE,MXML_REAL,或者MXML_TEXT..回调幻术在读取元素以及属性之后会被调用,因此您可以查看元素名称,属性以及属性值,以确定要放回的争取值类型。
下面的回调函数查找名为"type"的属性或元素,以确定子节点值类型。
mxml_type_t
type_cb(mxml_node_t *node)
{
const char *type;
/*
* You can lookup attributes and/or use the element name,
* hierarchy, etc...
*/
type = mxmlElementGetAttr(node, "type");
if (type == NULL)
type = mxmlGetElement(node);
if (!strcmp(type, "integer"))
return (MXML_INTEGER);
else if (!strcmp(type, "opaque"))
return (MXML_OPAQUE);
else if (!strcmp(type, "real"))
return (MXML_REAL);
else
return (MXML_TEXT);
}
要使用这个回调函数,只需要在调用任何load函数时使用此函数名称即可
FILE *fp;
mxml_node_t *tree;
fp = fopen("filename.xml", "r");
tree = mxmlLoadFile(NULL, fp, type_cb);
fclose(fp);
节点:
XML文件的每一条信息都存储在数据节点中。数据节点由mxml_node_t结构定义。每个节点都有一个类型化的值,可选的用户数据,父节点,前后兄弟节点,和可能存在的子节点。例如有如下XML文件
val1
val2
val3
val4
val5
val6
val7
val8
在内存中的文件节点树如下所示:
mxmlGetType函数返回节点的数据类型:
mxml_type_t mxmlGetType(mxml_node_t *node);
需要注意的是:CDATA,注释,和处理指令节点目前作为特殊元素存储在内存中,在后续的Mini-XML版本中会进行修改
使用mxmlGetParent,mxmlGetNextSibing,mxmlGetPreviousSibing函数,可以访问父节点以及同级节点数据,而使用mxmlGetFirstChild,mxmlGetLastChild函数访问元素节点的子节点;mxmlGetUserData函数获取用户(应用程序)与节点相关的数据:
mxml_node_t *mxmlGetFirstChild(mxml_node_t *node);
mxml_node_t *mxmlGetLastChild(mxml_node_t *node);
mxml_node_t *mxmlGetNextSibling(mxml_node_t *node);
mxml_node_t *mxmlGetParent(mxml_node_t *node);
mxml_node_t *mxmlGetPrevSibling(mxml_node_t *node);
void *mxmlGetUserData(mxml_node_t *node);
创建XML文档:
你可以使用各种的mxmlNew函数在内存中创建和更新XML文档。下面的代码将创建数据节点章节提到的XML文档。
mxml_node_t *xml; /* */
mxml_node_t *data; /* */
mxml_node_t *node; /* */
mxml_node_t *group; /* */
xml = mxmlNewXML("1.0");
data = mxmlNewElement(xml, "data");
node = mxmlNewElement(data, "node");
mxmlNewText(node, 0, "val1");
node = mxmlNewElement(data, "node");
mxmlNewText(node, 0, "val2");
node = mxmlNewElement(data, "node");
mxmlNewText(node, 0, "val3");
group = mxmlNewElement(data, "group");
node = mxmlNewElement(group, "node");
mxmlNewText(node, 0, "val4");
node = mxmlNewElement(group, "node");
mxmlNewText(node, 0, "val5");
node = mxmlNewElement(group, "node");
mxmlNewText(node, 0, "val6");
node = mxmlNewElement(data, "node");
mxmlNewText(node, 0, "val7");
node = mxmlNewElement(data, "node");
mxmlNewText(node, 0, "val8");
我们首先使用mxmlNewXML函数创建所有XML文件的公共声明节点:
xml = mxmlNewXML("1.0");
然后使用mxmlNewElement函数创建xml文档节点。第一个参数指定父节点xml,第二个参数指定元素名称(数据):
data = mxmlNewElement(xml, "data");
每个节点
node = mxmlNewElement(data, "node");
mxmlNewText(node, 0, "val1");
最终的结果是可以像从头磁盘或字符串加载XML文档一样保存处理内存中的XML文档。
保存XML文件
可以使用mxmlSaveFile函数去保存一个XML文件。
int mxmlSaveFile(mxml_node_t *node, FILE *fp,mxml_save_cb_t cb);
cb参数指定了一个函数,该函数返回一个元素节点前后插入的空格(如果空格存在)。MXML_NO_CALLBACK常量告诉Mini-XML不要包含任何额外的空格。例如将XML文件没有任何额外空格的情况下保存到filename文件中
FILE *fp;
fp = fopen("filename.xml", "w");
mxmlSaveFile(xml, fp, MXML_NO_CALLBACK);
fclose(fp);
Mini-XML还提供了保存到文件描述符或字符串的函数
char * mxmlSaveAllocString(mxml_node_t *node, mxml_save_cb_t cb);
int mxmlSaveFd(mxml_node_t *node, int fd, mxml_save_cb_t cb);
int mxmlSaveString(mxml_node_t *node, char *buffer, int bufsize,mxml_save_cb_t cb);
控制XML文档的列数:
在保存XML文档时,正常情况下Mini-XML输出的数据宽度是75列,以便在终端下可以完整的看到所有数据.mxmlSetWarpMargin函数或修改默认的换行列数。
void mxmlSetWrapMargin(int column);
例如
mxmlSetWrapMargin(132);//设置页边距为132列
mxmlSetWrapMargin(0);//设置页边距为0列,以禁用换行
保存回调函数:
mxmlSave函数的最后一个参数是一个回调函数,用于在XML文档中自动插入空格。对于每个元素几点,使用指向此节点的指针以及where值是 MXML_WS_BEFORE_OPEN,MXML_WS_AFTER_OPEN,MXML_WS_BEFORE_CLOSE,MXML_WS_AFTER_CLOSE是这些的时候调用回调函数的次数最多是4次。如果不应该添加空格或者不该插入字符(空格,制表符,回车,换行),则回调函数会返回NULL.
可以使用下面的空白回调函数将空白添加到XHTML的输出中,使其在标准文本编辑器中更具可读性。
const char *whitespace_cb(mxml_node_t *node, int where)
{
const char *element;
/*
* We can conditionally break to a new line before or after
* any element. These are just common HTML elements...
*/
element = mxmlGetElement(node);
if (!strcmp(element, "html") ||
!strcmp(element, "head") ||
!strcmp(element, "body") ||
!strcmp(element, "pre") ||
!strcmp(element, "p") ||
!strcmp(element, "h1") ||
!strcmp(element, "h2") ||
!strcmp(element, "h3") ||
!strcmp(element, "h4") ||
!strcmp(element, "h5") ||
!strcmp(element, "h6"))
{
/*
* Newlines before open and after close...
*/
if (where == MXML_WS_BEFORE_OPEN ||
where == MXML_WS_AFTER_CLOSE)
return ("\n");
}
else if (!strcmp(element, "dl") ||
!strcmp(element, "ol") ||
!strcmp(element, "ul"))
{
/*
* Put a newline before and after list elements...
*/
return ("\n");
}
else if (!strcmp(element, "dd") ||
!strcmp(element, "dt") ||
!strcmp(element, "li"))
{
/*
* Put a tab before 's, 's, and 's, and a
* newline after them...
*/
if (where == MXML_WS_BEFORE_OPEN)
return ("\t");
else if (where == MXML_WS_AFTER_CLOSE)
return ("\n");
}
/*
* Otherwise return NULL for no added whitespace...
*/
return (NULL);
}
要使用上述回调函数只需在调用保存的相关函数的时候使用函数名称即可:
FILE *fp;
mxml_node_t *tree;
fp = fopen("filename.xml", "w");
mxmlSaveFile(tree, fp, whitespace_cb);
fclose(fp);
内存管理:
处理完XML数据后,使用mxmlDelete函数去递归释放特定节点或者整个XML树的内存。
void mxmlDelete(mxml_node_t *tree);
你可以使用引用计数来管理内存使用。随着节点数量的增加或减少mxmlRetain和mxmlRelease函数或递增递减。当节点数变为0的时候mxmlRelease会自动调用mxmlDelete来释放节点树使用的内存。新节点是从1开始计数的。
关于节点更多信息:
元素节点:(保存在 <>中的内容)
元素节点(MXML_ELEMENT)是使用mxmlNewElement函数创建的。元素的属性可以使用mxmlElementSetAttr和mxmlElementSetAttrf函数设置,并使用mxmlElementDeleteAttr函数来清除属性
mxml_node_t *mxmlNewElement(mxml_node_t *parent, const char *name);
void mxmlElementSetAttr(mxml_node_t *node, const char *name, const char *value);
void mxmlElementSetAttrf(mxml_node_t *node, const char *name,const char *format, ...);
void mxmlElementDeleteAttr(mxml_node_t *node, const char *name);
可使用各种mxmlNew函数添加子节点,根节点必须是一个元素(Element),根节点通常由mxmlNewXML创建:
mxml_node_t *mxmlNewXML(const char *version);
mxmlGetElement函数依据元素名来检索元素,mxmlElementGetAttr函数检索与元素相关联的已命名的属性的值。mxmlElementGetAttrByIndex和mxmlElementGetAttrCount函数通过索引来检索属性
const char *mxmlGetElement(mxml_node_t *node);
const char *mxmlElementGetAttr(mxml_node_t *node, const char *name);
const char *mxmlElementGetAttrByIndex(mxml_node_t *node, int idx, const char **name);
int mxmlElementGetAttrCount(mxml_node_t *node);
CDATA节点:
使用mxmlNewCDATA函数创建CDATA(MXML_ELEMENT)节点。mxmlGetCDATA函数检索CDATA字符串指针。
mxml_node_t *mxmlNewCDATA(mxml_node_t *parent, const char *string);
const char *mxmlGetCDATA(mxml_node_t *node);
注释节点:
由于注释经常作为元素节点进行存储,所以使用mxmlNewElement函数通过 “!--元素名称-- ” 的形式创建注释节点(MXML_ELEMENT)。例如
mxml_node_t *node = mxmlNewElement("!-- This is a comment --");
类似的mxmlGetElement函数检索节点的注释字符串指针,其字符串指针所指向的数据就是 “!--元素名称-- ” 的形式。
const char *comment = mxmlGetElement(node);/* returns "!-- This is a comment --" */
处理指令节点:
由于处理指令节点经常作为元素节点进行存储,所以使用mxmlNewElement函数通过 “?元素名称? ” 的形式创建注释节点(MXML_ELEMENT)。例如
mxml_node_t *node = mxmlNewElement("?xml-stylesheet type=\"text/css\" href=\"style.css\"?");
类似的mxmlGetElement函数检索指令节点字符串,其字符串所包含的数据就是 “?元素名称?” 的形式。
const char *instr = mxmlGetElement(node);/* returned "?xml-stylesheet type=\"text/css\" href=\"style.css\"?" */
整数节点:(保存在 <>>中的内容)
整数节点是使用mxmlNewInteger函数创建的。mxmlGetInteger函数用于检索节点的整数值。
mxml_node_t *mxmlNewInteger(mxml_node_t *parent, int integer);
int mxmlGetInteger(mxml_node_t *node);
Opaque字符串(包含空格字符串)节点:(保存在 <>>中的内容)
Qpaque字符串节点是使用mxmlNewOpaque函数创建的。mxmlGetOpaque函数用于检索节点的Opaque值。
mxml_node_t *mxmlNewOpaque(mxml_node_t *parent, const char *opaque);
const char *mxmlGetOpaque(mxml_node_t *node);
文本(不包含空格)节点:(保存在 <>>中的内容)
使用mxmlNewText和mxmlNewTextf函数创建以空白分隔的文本字符串(MXML_TEXT)节点。每个文本节点都由一个文本字符串以及一个前导空白字符组成.mxmlGetText函数用于检索一个文本字符串节点的字符串指针和空白标志。
mxml_node_t *mxmlNewText(mxml_node_t *parent, int whitespace,const char *string);
mxml_node_t *mxmlNewTextf(mxml_node_t *parent, int whitespace,const char *format, ...);
const char *mxmlGetText(mxml_node_t *node, int *whitespace);
实数节点:(保存在 <>>中的内容)
使用mxmlNewReal函数创建实数(MXML_REAL)节点。mxmlGetReal函数用于检索节点的实数。
mxml_node_t *mxmlNewReal(mxml_node_t *parent, double real);
double mxmlGetReal(mxml_node_t *node);
XML文档中定位数据
Mini-XML提供了许多用于枚举,搜索,索引XML文档的函数。
查找节点:
mxmlFindPath函数使用path查找特定元素下的第一个节点。
mxml_node_t *mxmlFindPath(mxml_node_t *node, const char *path);
path字符串可以包含通配符,以匹配层次结构中的单元素节点。例如下面的代码将在group元素下找到第一个node元素,第一次使用显示路径,然后使用通配符的方式进行查找。
mxml_node_t *value = mxmlFindPath(xml, "data/group/node");
mxml_node_t *value = mxmlFindPath(xml, "data/*/node");
mxmlFindElement函数用于查找已命名元素,可选的有匹配属性,以及值。
mxml_node_t *mxmlFindElement(mxml_node_t *node, mxml_node_t *top,const char *element, const char *attr,const char *value, int descend);
在mxmlFindElement函数中element,attr,value参数可以传入NULL,来作为通配符使用
node = mxmlFindElement(tree, tree, "a", NULL, NULL,MXML_DESCEND);//查找第一个a元素
node = mxmlFindElement(tree, tree, "a", "href", NULL,MXML_DESCEND);//查找第一个拥有href属性的a元素
node = mxmlFindElement(tree, tree, "a", "href","http://michaelrsweet.github.io/",MXML_DESCEND);//查找第一个href属性值是"http://michaelrsweet.github.io/"的a元素
node = mxmlFindElement(tree, tree, NULL, "src", NULL,MXML_DESCEND);//查找第一个拥有src属性的元素
node = mxmlFindElement(tree, tree, NULL, "src", "foo.jpg",MXML_DESCEND);//查找第一个src="foo.jpg"的元素
你也可以使用相同的函数进行迭代查找。
mxml_node_t *node;
for (node = mxmlFindElement(tree, tree, "element", NULL,NULL, MXML_DESCEND);
node != NULL;
node = mxmlFindElement(node, tree, "element", NULL,NULL, MXML_DESCEND))
{
//... do something ...
}
上述例子中的descend参数可以是下面三个常量之一:
遍历节点:
mxmlFindNode和mxmlFindPath函数可以找到特定的元素节点。但是有时需要遍历所有的几点。mxmlWalkNext以及mxmlWalkPrev函数可以用于遍历XML节点树。
mxml_node_t *mxmlWalkNext(mxml_node_t *node, mxml_node_t *top,int descend);
mxml_node_t *mxmlWalkPrev(mxml_node_t *node, mxml_node_t *top,int descend);
根据descned参数,这些函数将自动遍历子节点、同级节点和父节点,直至根节点。例如下面的代码将遍历上一节示例文档中的所有节点;虽然mxmlFindNode和mxmlFindPath函数
mxml_node_t *node;
for (node = xml;node != NULL;node = mxmlWalkNext(node, xml, MXML_DESCEND))
{
//... do something ...
}
上面遍历的显示结果是:
val1
val2
val3
val4
val5
val6
val7
val8
索引:
mxmlIndexNew函数允许创建节点索引,以便更快的搜索和枚举。
mxml_index_t *mxmlIndexNew(mxml_node_t *node, const char *element,const char *attr);
element和attr参数控制索引中包含哪些元素,如果element不为空,则只向索引提那家指定名称的元素。类似的如果attr不为空,则只向索引中添加包含指定属性的元素。这些加入的节点在索引中是排过序的。例如,下面的代码创建了XML文档中的所有id值的索引。
mxml_index_t *ind = mxmlIndexNew(xml, NULL, "id");
一旦索引创建后,可以使用mxmlIndexFind函数去查找匹配的节点。
mxml_node_t *mxmlIndexFind(mxml_index_t *ind, const char *element,const char *value);
例如下面的代码。,会找到id为42的元素
mxml_node_t *node = mxmlIndexFind(ind, NULL, "42");
另外可以使用mxmlIndexReset和mxmlIndexEnum函数枚举索引中的节点。
mxml_node_t *mxmlIndexReset(mxml_index_t *ind);
mxml_node_t *mxmlIndexEnum(mxml_index_t *ind);
通常这些函数将在for循环中使用。
mxml_node_t *node;
for (node = mxmlIndexReset(ind);node != NULL;node = mxmlIndexEnum(ind))
{
//... do something ...
}
mxmlIndexCount函数返回索引中的节点数;mxmlIndexDelete函数释放所有与索引相关的内存。
int mxmlIndexGetCount(mxml_index_t *ind);
void mxmlIndexDelete(mxml_index_t *ind);
自定义数据类型
Mini-XML通过每个线程加载函数和保存回调函数支持自定义数据类型。对当前线程来说,在任何时候只有一组回调函数处于活跃状态,但是回调函数可以存储额外的信息,以便根据需要支持多种自定义数据类型。MXML_CUSTOM节点类型定义用户自定义数据节点。mxmlGetCustom函数检索自定义值指针。
const void *mxmlGetCustom(mxml_node_t *node);
自定义数据类型(MXML_CUSTOM)节点使用mxmlNewCustom函数创建,或者使用mxmlSetCustomHandlers函数指定加载回调函数创建。
typedef void (*mxml_custom_destroy_cb_t)(void *);
typedef int (*mxml_custom_load_cb_t)(mxml_node_t *, const char *);
typedef char *(*mxml_custom_save_cb_t)(mxml_node_t *);
mxml_node_t *mxmlNewCustom(mxml_node_t *parent, void *data,mxml_custom_destroy_cb_t destroy);
int mxmlSetCustom(mxml_node_t *node, void *data,mxml_custom_destroy_cb_t destroy);
void mxmlSetCustomHandlers(mxml_custom_load_cb_t load,mxml_custom_save_cb_t save);
加载回调函数接收指向当前数据节点的指针和来自XML源的opaque字符串(由字符串entities转换成UTF-8字符串)。例如,如果我们想支持一个自定义的日期/时间类型,它的值被编码"yyyy-mm-ddThh:mm:ssZ"(ISO格式),加载回调函数应该如下所示:
typedef struct
{
unsigned year, /* Year */
month, /* Month */
day, /* Day */
hour, /* Hour */
minute, /* Minute */
second; /* Second */
time_t unix; /* UNIX time */
} iso_date_time_t;
int load_custom(mxml_node_t *node, const char *data)
{
iso_date_time_t *dt;
struct tm tmdata;
dt = calloc(1, sizeof(iso_date_time_t));//Allocate data structure...
//Try reading 6 unsigned integers from the data string...
if (sscanf(data, "%u-%u-%uT%u:%u:%uZ", &(dt->year),&(dt->month), &(dt->day), &(dt->hour),&(dt->minute), &(dt->second)) != 6){
free(dt);
return (-1);
}
// Range check values...
if (dt->month < 1 || dt->month > 12 ||
dt->day < 1 || dt->day > 31 ||
dt->hour < 0 || dt->hour > 23 ||
dt->minute < 0 || dt->minute > 59 ||
dt->second < 0 || dt->second > 60)
{
//Date information is out of range...
free(dt);
return (-1);
}
//Convert ISO time to UNIX time in seconds...
tmdata.tm_year = dt->year - 1900;
tmdata.tm_mon = dt->month - 1;
tmdata.tm_day = dt->day;
tmdata.tm_hour = dt->hour;
tmdata.tm_min = dt->minute;
tmdata.tm_sec = dt->second;
dt->unix = gmtime(&tmdata);
mxmlSetCustom(node, (void *)dt, free);//Assign custom node data and destroy (free) function pointers...
return (0);
}
如果无法解析自定义数据,则函数会返回错误,而函数本身在成功时可以返回0或-1.自定义数据节点包含一个指向为节点分片自定义数据的void指针和一个指向析构函数的指针,析构函数将在删除节点时释放自定义数据。在本例中,我们使用标准的free函数,因为所有内容都包含在一个单独的calloc分配的内存中。
保存回调函数在接收节点指针,并返回已经分配空间的包含自定义数据的字符串。以下是保存回调函数用于处理 ISO date/time类型
char *save_custom(mxml_node_t *node)
{
char data[255];
iso_date_time_t *dt;
dt = (iso_date_time_t *)mxmlGetCustom(node);
snprintf(data, sizeof(data),
"%04u-%02u-%02uT%02u:%02u:%02uZ",
dt->year, dt->month, dt->day, dt->hour,
dt->minute, dt->second);
return (strdup(data));
}
使用mxmlSetCustomHandler函数注册回调函数。
mxmlSetCustomHandlers(load_custom, save_custom);
SAX(流)加载XML文档
Mini-XML支持XML简单API(SAX:Simple API of XML)的实现,SAX允许你以节点流的形式加载和处理XML文档。除了允许你处理任何大小的XML文档外,Mini-XML还允许你将一部分XML保存在内存中,以便后来处理。mxmlSAXLoadFd,mxmlSAXLoadFile,mxmlSAXLoadString函数提供SAX的加载函数API:
mxml_node_t *mxmlSAXLoadFd(mxml_node_t *top, int fd,mxml_type_t (*cb)(mxml_node_t *),mxml_sax_cb_t sax, void *sax_data);
mxml_node_t *mxmlSAXLoadFile(mxml_node_t *top, FILE *fp,mxml_type_t (*cb)(mxml_node_t *), mxml_sax_cb_t sax, void *sax_data);
mxml_node_t *mxmlSAXLoadString(mxml_node_t *top, const char *s,mxml_type_t (*cb)(mxml_node_t *),mxml_sax_cb_t sax, void *sax_data);
每一个函数的工作原理类似与相应的mxmlLoad函数,但是在读取每个节点时使用回调函数来处理。回调函数接收节点数据,事件代码,以及用户提供的数据指针。
void sax_cb(mxml_node_t *node, mxml_sax_event_t event,void *data)
{
//... do something ...
}
SAX支持如下事件:
元素在处理关闭元素后就被释放了,所有其他节点都在处理后释放。SAX回调函数可以使用mxmlRetain函数保留节点。例如下面的SAX回调函数将保留所有节点,可有效的模拟正常的内存负载。
void sax_cb(mxml_node_t *node, mxml_sax_event_t event,void *data)
{
if (event != MXML_SAX_ELEMENT_CLOSE)
mxmlRetain(node);
}
更典型的情况是,SAX回调只保留文档中用于后续处理的一小部分。例如下面的SAX回调函数将在XHTML文件中保留title和header信息。另外还保留了像,
,,,等元素。void sax_cb(mxml_node_t *node, mxml_sax_event_t event,void *data)
{
if (event == MXML_SAX_ELEMENT_OPEN)
{
const char *element = mxmlGetElement(node);
if (!strcmp(element, "html") ||
!strcmp(element, "head") ||
!strcmp(element, "title") ||
!strcmp(element, "body") ||
!strcmp(element, "h1") ||
!strcmp(element, "h2") ||
!strcmp(element, "h3") ||
!strcmp(element, "h4") ||
!strcmp(element, "h5") ||
!strcmp(element, "h6"))
mxmlRetain(node);
}
else if (event == MXML_SAX_DIRECTIVE)
mxmlRetain(node);
else if (event == MXML_SAX_DATA)
{
if (mxmlGetRefCount(mxmlGetParent(node)) > 1)
{
mxmlRetain(node);
}
}
}
然后可以像使用mxmlLoad函数加载框架文档树一样搜索生成的框架文档树。例如从stdin读取XHTML文档,然后显示文档制表符的title,header:
mxml_node_t *doc, *title, *body, *heading;
doc = mxmlSAXLoadFd(NULL, 0, MXML_TEXT_CALLBACK, sax_cb,NULL);
title = mxmlFindElement(doc, doc, "title", NULL, NULL,MXML_DESCEND);
if (title)
print_children(title);
body = mxmlFindElement(doc, doc, "body", NULL, NULL,MXML_DESCEND);
if (body)
{
for (heading = mxmlGetFirstChild(body);
heading;
heading = mxmlGetNextSibling(heading))
print_children(heading);
}
print_children函数如下:
void print_children(mxml_node_t *parent)
{
mxml_node_t *node;
const char *text;
int whitespace;
for (node = mxmlGetFirstChild(parent);node != NULL;node = mxmlGetNextSibling(node))
{
text = mxmlGetText(node, &whitespace);
if (whitespace)
putchar(' ');
fputs(text, stdout);
}
putchar('\n');
}
官网:https://www.msweet.org/mxml/mxml.html