前言:
最近项目中需要将用户的配置信息进行保存,考虑到后期出厂这个配置文件可以清晰地动态更改,所以选择使用xml的方式。
xml相关的库有很多,例如libxml2、Tinyxml、RapidXml、MinXml等。
本文使用的是minxml,至于其他库的使用后续会进行补充和比较,敬请期待!
下载:
Releases · michaelrsweet/mxml · GitHub
解压后请仔细阅读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等。
const char *whitespace_cb(mxml_node_t *node, int where)
{
// printf("whitespace_cb, node name: %s, node type: %d, where: %d\n", mxmlGetElement(node), node->type, where);
const char *name;
name = mxmlGetElement(node);
if (name == NULL) { // it isn't an element
return NULL;
}
if (!need_update_whitespace(node)) {
return NULL;
}
if (!strcmp(XML_NODE_ROOT_NAME, name)) { // config
if (where == MXML_WS_BEFORE_OPEN) {
return XML_SEPERATE_ROOT;
}
} else if (!strcmp(XML_NODE_INTEGER_NAME, name) //integer
|| !strcmp(XML_NODE_STRING_NAME, name)) { //string
if (where == MXML_WS_AFTER_CLOSE) {
return XML_SEPERATE_ROOT;
} else if (where == MXML_WS_BEFORE_OPEN) {
return XML_SEPERATE_COMMON;
}
} else if (!strcmp(XML_NODE_STRING_ARRAY_NAME, name)) { //string-array
if (where == MXML_WS_AFTER_CLOSE) {
return XML_SEPERATE_ROOT;
} else if (where == MXML_WS_BEFORE_OPEN || where == MXML_WS_BEFORE_CLOSE) {
return XML_SEPERATE_COMMON;
}
} else if (!strcmp(XML_NODE_ARRAY_ITEM_NAME, name)) { // item
if (where == MXML_WS_BEFORE_OPEN) {
return XML_SEPERATE_ITEM;
}
}
return NULL;
}
其他的都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 属性。