简介:

Mini-XML是一个小型的XML库。您可以使用它在应用程序中读写XML以及和类XML数据文件,而不需要其他大型非标准库。Mini-XML只需要与一个ANSI C兼容的编译器(gcc以及大部分编译器)和一个make程序即可工作。

Mini-XML提供以下功能:

  1. 读取UTF-8 UTF-16以及写UTF-8的XML文件以及字符串
  2. 数据是存储在链表树结构中,保留了XML数据层次结构
  3. 占用最小内存空间使用SAX(流)读取XML文件和字符串
  4. 只要内存够用,支持任意元素名称,属性和属性值,没有限制.
  5. 在叶子节点中支持整数,实数,以及cdata,和文本数据类型
  6. 提供创建和管理数据树的函数
  7. Find和walk功能可方便进行数据定位以及数据树导航。

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数据定义了几个标准回调函数:

  • MXML_INTEGER_CALLBACK:所有数据节点都包含空格分隔的整数
  • MXML_OPAQUE_CALLBACK:所有数据节点都包含以空格分隔不透明字符串
  • MXML_REAL_CALLBACK:所有数据节点都包含以空格分隔的浮点数
  • MXML_TEXT_CALLBACK: 所有数据节点都包含以空格分隔字符串

另外你可以为更为复杂的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);elsereturn (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文件

<?xml version="1.0" encoding="utf-8"?>
<data><node>val1</node><node>val2</node><node>val3</node><group><node>val4</node><node>val5</node><node>val6</node></group><node>val7</node><node>val8</node>
</data>

在内存中的文件节点树如下所示:

mxmlGetType函数返回节点的数据类型:

mxml_type_t mxmlGetType(mxml_node_t *node);
 
  • MXML_CUSTOM:应用程序自定义的值
  • MXML_ELEMENT:XML元素,CDATA,注释或者处理指令
  • MXML_INTEGER:以空格分隔的整数
  • MXML_OPAQUE:以空格分隔的不透明字符串
  • MXML_REAL:以空格分隔的浮点数
  • MXML_TEXT:以空格分隔的字符串

需要注意的是: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;    /* <?xml ... ?> */
mxml_node_t *data;   /* <data> */
mxml_node_t *node;   /* <node> */
mxml_node_t *group;  /* <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>...</node>是使用mxmlNewElement和mxmlNewText函数创建的。mxmlNewText函数的第一个参数指定父节点,在上述例子中第二个参数指定空格出现在文本-0之前或者不出现。最后一个参数指定套添加的实际文本。

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 <li>'s, <dd>'s, and <dt>'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参数可以是下面三个常量之一:

  • MXML_NO_DESCEND:忽略元素层次结构中的子节点,而使用后同级的兄弟节点或者父节点,直到根节点。
  • MXML_DESCEND_FIRST:从几点的第一个自己点开始检索,然后搜索所有同级兄弟节点。在遍历父节点的直接子节点时,通常使用此方法。在之前例子中的在”?xml“下的所有的node以及group
  • MXML_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 ...
}

上面遍历的显示结果是:

<?xml version="1.0" encoding="utf-8"?>
<data>
<node>
val1
<node>
val2
<node>
val3
<group>
<node>
val4
<node>
val5
<node>
val6
<node>
val7
<node>
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支持如下事件:

  • MXML_SAX_CDATA:刚读取到了CDATA数据
  • MXML_SAX_COMMET:刚读取到了注释数据
  • MXML_SAX_DATA:刚读取到了数据(自定义数据,整形,opaque,实数,或者文本数据)
  • MXML_SAX_DIRECTIVE:刚刚读取了一条处理指令
  • MXML_SAX_ELEMENT_CLOSE:刚刚读取到了一个关闭元素</element>
  • MXML_SAX_ELEMENT_OPEN:刚刚读取到了一个Open元素</element>

元素在处理关闭元素后就被释放了,所有其他节点都在处理后释放。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信息。另外还保留了像<html>,<head>,<body>,<?xml...?>,<!DOCTYPE>等元素。

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

Onvif再学习---MiniXml-介绍相关推荐

  1. Onvif协议学习:4、门外汉理解Onvif协议

    Onvif协议学习:4.门外汉理解Onvif协议 文章目录 Onvif协议学习:4.门外汉理解Onvif协议 1.ONVIF背景 2.ONVIF技术框架 3.ONVIF技术规格 4.ONVIF接口规范 ...

  2. Onvif协议学习:13、遮挡报警

    Onvif协议学习:13.遮挡报警 文章目录 Onvif协议学习:13.遮挡报警 1.原理简介 (1).Basic Notification (2).Pull-Point Notification 2 ...

  3. oracle入门学习(3) 所用的学习环境介绍与设置

    oracle入门学习(3) 原文见我的QQ空间:http://user.qzone.qq.com/284648964?ptlang=2052 由于原文是写在我的QQ空间,文章转过来的过程中造图片丢失, ...

  4. C语言再学习--关键字

    如需转载请注明出处:https://blog.csdn.net/qq_29350001/article/details/53021879 C语言一共有32个关键字,如下表所示: 关键字 说明 auto ...

  5. LIVE555再学习 -- testOnDemandRTSPServer 源码分析

    一.简介 先看一下官网上的介绍: testOnDemandRTSPServercreates a RTSP server that can stream, via RTP unicast, from ...

  6. LIVE555再学习 -- testH264VideoStreamer 源码分析

    上一篇文章我们已经讲了一部分: testH264VideoStreamer 重复从 H.264 基本流视频文件(名为"test.264")中读取,并使用 RTP 多播进行流式传输. ...

  7. LIVE555再学习 -- testRTSPClient 实例

    上一篇文章简单看了一遍 testRTSPClient  的源码,接下来举几个应用实例加深一下. 首先什么都不做修改,先执行一遍,看一下. 一.执行 testRTSPClient 特么,上面的东西我没看 ...

  8. LIVE555再学习 -- DM368/Hi3516A 交叉编译

    接着上篇文章来讲,参看:LIVE555再学习 -- Linux 下编译 下载源码.文件介绍部分不再介绍.主要看配置编译部分. 三.配置编译 DM368 的交叉编译器为 arm-none-linux-g ...

  9. FFmpeg再学习 -- SDL 环境搭建和视频显示

    继续看雷霄骅的 课程资料 - 基于FFmpeg+SDL的视频播放器的制作 一.SDL 简介 参看:WIKI -- Simple DirectMedia Layer 参看:最简单的视音频播放示例9:SD ...

最新文章

  1. ImportError: No module named _ssl解决方法
  2. jdk动态代理实例和cglib动态代理实例_CGLib 动态代理 原理解析
  3. linux gcc-9.2.0 源码编译
  4. 访问外部扩展C语言编程,单片机C语言编程(系统扩展IC)8.ppt
  5. Vue.js 条件渲染
  6. 虚拟机dhcp服务器怎么检验,实验十二虚拟机上DHCP服务器的配置和验证.doc
  7. python sanic orm_基于sanic的微服务框架 - 架构分析
  8. PHP在线加密系统-陌屿云PLUS开源版V8.01
  9. swing point 怎么让x 不变_Swing舞出我人生 Vol.02 / 我的人生分为跳舞之前和跳舞之后...
  10. this.$set 更新整个数组_学点算法(二)——有序数组二分搜索算法
  11. Latex表格中内容过长换行方法
  12. java jquery提交表单数据_[Java教程]jquery实现ajax提交表单信息
  13. 破解网站发布系统 ASP生成静态页面方法
  14. Linux内核网络编程
  15. JDK和CGLIB动态代理的区别
  16. mysql填写数据库_学习MYSQL过程中自己写的数据库操作
  17. 基因和疾病:缺陷、脱轨以及妥协
  18. HTTP 和 DNS 原理概念了解 ;
  19. 有一个程序员男友是一种怎么样的体验?
  20. xctf攻防世界 MISC高手进阶区 saleae

热门文章

  1. android用对话框捕捉异常,Android - 在捕获异常时在doinbackground期间显示警报对话框...
  2. matlab 车身阻尼比曲线,汽车阻尼比及振动响应的分析
  3. Pentaho Data Integration(Kettle) 6.0
  4. linux 怎样安装 nginx
  5. 初探 Ettercap: ARP投毒 DNS欺骗
  6. 【Machine Learning 学习笔记】Stochastic Dual Coordinate Ascent for SVM 代码实现
  7. 分位数Quantiles
  8. 【线性代数】机器学习·算法必备 线性代数总结
  9. 新考纲 PMP 备考精讲
  10. 《机器学习实战》学习第一章