libmxml是一个开源、小巧的C语言xml库。这里简单分析一下它是用什么样的数据结构来保存分析过的xml文档。
mxml关键的结构体mxml_node_t是这样的实现的:
struct mxml_node_s /**** An XML node. @private@ ****/ { mxml_type_t type; /* Node type */ struct mxml_node_s *next; /* Next node under same parent */ struct mxml_node_s *prev; /* Previous node under same parent */ struct mxml_node_s *parent; /* Parent node */ struct mxml_node_s *child; /* First child node */ struct mxml_node_s *last_child; /* Last child node */ mxml_value_t value; /* Node value */ int ref_count; /* Use count */ void *user_data; /* User data */ }; typedef struct mxml_node_s mxml_node_t; /**** An XML node. ****/
它使用左孩子右兄弟的树形结构来描述xml报文:即下层节点登记在child链表,兄弟节点登记在next链表。 如果某个节点下面有N个子节点,则child指向第一个子节点,该子节点的next指向下一个同父节点的子节点。 比较特殊的是,mxml把xml节点值也认为是一个子节点。例如<group>value</group>, 其中value(type是MXML_OPAQUE)是一个独立的子节点,挂载在group节点(type是MXML_ELEMENT)下面。 另外,空白符(空格,回车换行,制表符)和注释,虽然对xml报文无实质意义,但mxml还是把它们做为一个节点存储起来。
由于mxml只是使用简单的链表存储xml元素,所以元素节点个数比较多时,mxml查找元素效率是比较低的。所以libmxml提供了一个索引查找的函数,它需要先遍历xml元素树,生成一个排序过的数组,加快查找速度。
为了方便大家理解,我写了一个函数打印xml结构体。
void printNode(mxml_node_t *node, int nNodeSn, int level) { static int currNodeSn = 0; if (node == NULL) { return; } ++currNodeSn; //每遇到一个新节点 则将节点序号递增,做为本节点序号 printf("[%- 3d -> %- 3d] ", currNodeSn, nNodeSn); switch (node->type) { case MXML_ELEMENT: { int i; printf("level %d MXML_ELEMENT [%s]", level, node->value.element.name); for (i = 0; i < node->value.element.num_attrs; ++i) { printf(" %s=%s", node->value.element.attrs[i].name, node->value.element.attrs[i].value); } printf("\n"); } break; case MXML_INTEGER: printf("level %d MXML_INTEGER %d\n", level, node->value.integer); break; case MXML_OPAQUE: printf("level %d MXML_OPAQUE [%s]\n", level, node->value.opaque); break; case MXML_REAL: printf("level %d MXML_REAL %lf\n", level, node->value.real); break; case MXML_TEXT: printf("level %d MXML_TEXT [%s]\n", level, node->value.text.string); break; case MXML_CUSTOM: printf("level %d MXML_CUSTOM\n", level); break; default: printf("unknown node type %d\n", node->type); } //深度优先遍历 if (node->child) { //访问子节点时把本节点序号做为父节点序号 层级加1 printNode(node->child, currNodeSn, level + 1); } if (node->next) { //访问兄弟节点,直接传父节点序号即可 层级也不用加1 printNode(node->next, nNodeSn, level); } }
运行示例如下:
xml源如下:
<?xml version="1.0" encoding="GBK" ?> <group> <option>122334 我们 <string>我们</string>45677 <keyword type="opaque">InputSlot</keyword> <default type="opaque">Auto</default> <text>Media Source</text> <order type="real">10.000000</order> <choice> <keyword type="opaque">Auto</keyword> <text>Auto Tray Selection</text> <code type="opaque" /> </choice> <choice> <keyword type="opaque">Upper</keyword> <text>Tray 1</text> <code type=&q