前言:
最近项目中需要将用户的配置信息进行保存,考虑到后期出厂这个配置文件可以清晰地动态更改,所以选择使用xml的方式。
xml相关的库有很多,例如libxml2、Tinyxml、RapidXml、MinXml等。
本文使用的是minxml,至于其他库的使用后续会进行补充和比较,敬请期待!
下载:
https://github.com/michaelrsweet/mxml/releases
解压后请仔细阅读ReadMe,例如:
Mini-XML comes with an autoconf-based configure script; just type the following command to get things going: ./configure The default install prefix is /usr/local, which can be overridden using the --prefix option: ./configure --prefix=/foo Other configure options can be found using the --help option: ./configure --help Once you have configured the software, type "make" to do the build and run the test program to verify that things are working, as follows: make If you are using Mini-XML under Microsoft Windows with Visual C++, use the included project files in the "vcnet" subdirectory to build the library instead. Note: The static library on Windows is NOT thread-safe.
使用:
阅读完ReadMe基本上就有了mxml的概念了,例如:
- 通过configure脚本产生Makefile,进而可以使用make命令;
- 如果使用mxml,需要包含mxml.h;
- 整个mxml的核心是一个链表,数据类型为mxml_node_t;
mxml提供的接口很多,下面挑一些经常使用的接口。
1、创建xml
最开始xml肯定是不存在的,创建的接口为 mxmlNewXML,函数原型:
mxml_node_t *mxmlNewXML (const char *version);
参数是一个代表xml 版本信息的字符串,返回的是链表的头指针。
来看下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. ****/
其中:
- type为节点的类型,包括MXML_ELEMENT、MXML_INTEGER、MXML_OPAQUE、MXML_REAL、MXML_TEXT、MXML_CUSTOM、MXML_IGNORE等;
- value为节点的属性值;
- user_data是用户设定的特殊数据;
这里的type会经常使用,根据不同的type需要进行get、set等操作,type的数据类型为:
typedef enum mxml_type_e /**** The XML node type. ****/ { MXML_IGNORE = -1, /* Ignore/throw away node @since Mini-XML 2.3@ */ MXML_ELEMENT, /* XML element with attributes */ MXML_INTEGER, /* Integer value */ MXML_OPAQUE, /* Opaque string */ MXML_REAL, /* Real value */ MXML_TEXT, /* Text fragment */ MXML_CUSTOM /* Custom data @since Mini-XML 2.1@ */ } mxml_type_t;
2、添加xml节点
创建后会得到链表的头指针,后续根据这个头指针进行操作就可以了。
当然,如果是一个已经存在的xml文件,肯定是不能通过创建的xml的形式获取链表头,而是通过load的方式,下面会解析load的接口使用。
mxml 提供了很多添加节点的方法,例如:
- mxmlNewCDATA
- mxmlNewCustom
- mxmlNewElement
- mxmlNewInteger
- mxmlNewOpaque
- mxmlNewReal
- mxmlNewText
- mxmlNewTextf
- mxmlAdd
其中New*的接口最终调用的是mxmlAdd,区别是New是知道parent,而mxmlAdd之前并不一定需要知道parent,知道调用mxmlAdd的时候才需指定parent。
建议使用New*的方式进行xml节点的添加。
另外,选择不同的New*接口最终的节点type是不同的,详细看第 1 节。
考虑到字符串中间可能有空格的形式,而mxmlNewText只是针对第一个单词,所以建议使用mxmlNewOpaque接口进行统一管理。
建议也只是建议,个人可以根据实际情况进行接口选择。
3、删除xml节点
mxmlDelete不仅会删除当前节点,并且会进行free。当然会将该节点下的子节点一并delete掉;
mxmlRelease只有在节点的ref_count为1时,才会进行释放,释放时调用mxmlDelete;
mxmlRemove并不会做free,只是将节点从parent中remove掉,使用时注意内存情况及时回收;
4、修改xml节点
对于element的节点有:
其他type 的节点有:
- mxmlSetCDATA
- mxmlSetCustom
- mxmlSetCustomHandlers
- mxmlSetInteger
- mxmlSetOpaque
- mxmlSetReal
- mxmlSetText
- mxmlSetTextf
- mxmlSetUserData
5、查询xml 节点
对于element的节点有:
其他type 的节点有:
- mxmlGetCDATA
- mxmlGetCustom
- mxmlGetInteger
- mxmlGetNextSibling
- mxmlGetOpaque
- mxmlGetPrevSibling
- mxmlGetReal
- mxmlGetRefCount
- mxmlGetText
- mxmlGetType
- mxmlGetUserData
还有几个特别的接口:
其中mxmlFindElement 会经常在查询的时候使用,一般是先通过该接口找到element,才能进一步的确认value、attribute等信息。来看下函数原型:
mxml_node_t *mxmlFindElement ( mxml_node_t *node, mxml_node_t *top, const char *name, const char *attr, const char *value, int descend );
- 第一个参数应该为父节点;
- 第二个参数可以是父节点,也可以是根节点;
- 第三个参数为要查找的element的名称;
- 第四个参数为element的attr 名称;
- 第五个参数为attr对应的属性值;
另外,可以通过mxmlGetFirstChild 接口切换到子节点的,然后通过
查找同一级的其他节点。
6、保存xml
上面的一系列操作知识针对链表进行,最终需要将链表的信息保存到xml文件中或保存到string中。使用接口为:
这里主要解析mxmlSaveFile,来看下函数原型:
int mxmlSaveFile ( mxml_node_t *node, FILE *fp, mxml_save_cb_t cb );
- 第一个参数一般为根节点;
- 第二个参数为xml文件的句柄;
- 第三个参数为保存过程中的回调,一共是4次,分别为MXML_WS_BEFORE_OPEN、MXML_WS_AFTER_OPEN、MXML_WS_BEFORE_CLOSE、MXML_WS_AFTER_CLOSE;
其他两个参数很好理解,主要是第三个参数,通过实际例子来理解,例如有个这样的节点:
<string name="string1">hehe1</string>
那么,
在<string 之前会触发MXML_WS_BEFORE_OPEN的callback; 在"string1"> 之后会触发MXML_WS_AFTER_OPEN 的callback; 在</string> 之前会触发MXML_WS_BEFORE_CLOSE的callback; 在</string> 之后会触发MXML_WS_AFTER_CLOSE 的callback;
注意:
mxml中如果没有设定callback,那么xml文件中的是没有格式的,所以,用户需要通过callback处理这个格式。而在coding过程中需要考虑刚打开的xml文件中添加新节点(需要关注load的方式)、连续添加节点、修改已有节点等情况,因为在save的时候每个节点都会触发callback。
来看下我之前写的一个例子中的callback,其中节点有可能是integer、string、string-array、item等。
1 const char *whitespace_cb(mxml_node_t *node, int where) 2 { 3 // printf("whitespace_cb, node name: %s, node type: %d, where: %d\n", mxmlGetElement(node), node->type, where); 4 const char *name; 5 6 name = mxmlGetElement(node); 7 8 if (name == NULL) { // it isn't an element 9 return NULL; 10 } 11 12 if (!need_update_whitespace(node)) { 13 return NULL; 14 } 15 16 if (!strcmp(XML_NODE_ROOT_NAME, name)) { // config 17 if (where == MXML_WS_BEFORE_OPEN) { 18 return XML_SEPERATE_ROOT; 19 } 20 } else if (!strcmp(XML_NODE_INTEGER_NAME, name) //integer 21 || !strcmp(XML_NODE_STRING_NAME, name)) { //string 22 if (where == MXML_WS_AFTER_CLOSE) { 23 return XML_SEPERATE_ROOT; 24 } else if (where == MXML_WS_BEFORE_OPEN) { 25 return XML_SEPERATE_COMMON; 26 } 27 } else if (!strcmp(XML_NODE_STRING_ARRAY_NAME, name)) { //string-array 28 if (where == MXML_WS_AFTER_CLOSE) { 29 return XML_SEPERATE_ROOT; 30 } else if (where == MXML_WS_BEFORE_OPEN || where == MXML_WS_BEFORE_CLOSE) { 31 return XML_SEPERATE_COMMON; 32 } 33 } else if (!strcmp(XML_NODE_ARRAY_ITEM_NAME, name)) { // item 34 if (where == MXML_WS_BEFORE_OPEN) { 35 return XML_SEPERATE_ITEM; 36 } 37 } 38 39 return NULL; 40 }
其他的都ok的,返回的是空格、回车、制表符等,但在这之前有个函数need_update_whitespace 来确定该节点格式是否需要更新。
7、加载xml
从创建xml一直到保存xml 的流程都梳理完了,只差 load 已有的xml了。
同样的,这里只分析mxmlLoadFile,来看下函数原型:
mxml_node_t *mxmlLoadFile ( mxml_node_t *top, FILE *fp, mxml_load_cb_t cb );
同save,load的时候也有callback,这个load_cb主要是在load xml 到链表时需要确定element 下子节点的type。
还是save 中同样的节点:
<string name="string1">hehe1</string>
load xml的时候可以通过element 的name 为string,确定子节点hehe1 的类型为MXML_TEXT或是MXML_OPAQUE。这里的type 最好是跟add 的时候统一,再次建议使用OPAQUE 的类型进行操作。
所以,如果使用OPAQUE 的类型进行load xml,那么mxml 中提供了这样的callback:MXML_OPAQUE_CALLBACK
mxml 也提供了其他类型的callback,MXML_INTEGER_CALLBACK、MXML_REAL_CALLBACK、MXML_TEXT_CALLBACK、MXML_IGNORE_CALLBACK、MXML_NO_CALLBACK
注意:
load 的时候如果用OPAQUE格式,会将之前save 时候的空格、tab(哪怕这些是在element之前)认为也 是OPAQUE格式的节点。
8、特殊接口
mxml 中会给字符串指定wrap 的最大值,如果到了mxml会自动换行。
设定为0 则关闭wrap 属性。