前言:

  最近项目中需要将用户的配置信息进行保存,考虑到后期出厂这个配置文件可以清晰地动态更改,所以选择使用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 提供了很多添加节点的方法,例如:

其中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 的节点有:

 

5、查询xml 节点

对于element的节点有:

其他type 的节点有:

还有几个特别的接口:

其中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 属性。