参考文章:
https://blog.csdn.net/huangkaiyue1/article/details/54142305
https://blog.csdn.net/shenwansangz/article/details/47358785 (minixml中文手册)
https://blog.csdn.net/weixin_42325191/article/details/85058198
yuan@ubuntu:~/smb/mxml-2.12$ ./testmxml test.xml 报错的话
./testmxml: error while loading shared libraries: libmxml.so.1: cannot open shared object file: No such file or directory
这个问题是没有配置环境变量LD_LIBRARY_PATH=/usr/local/lib
使用export LD_LIBRARY_PATH=/usr/local/lib
-
1.源码获取路径:http://www.minixml.org/
http://michaelrsweet.github.io/mxml/
可以直接下载.tar.gz包解压安装,或者直接通过github fork到自己的仓库,git到自己本地安装,关于github和git源码在下篇中再讲,现在讲解如何通过.tar.gz包安装。
现假定你已获取源码包并已成功放入到虚拟机Ubuntu目录下。
关于如何把文件从window传到虚拟机安装的系统中,可自行百度,或者有必要的话,下次再开篇博文。
2.首先解压mxml-2.10.tar.gz文件:
tar -xzvf mxml-2.10.tar.gz
3.编译安装
进入到解压后的文件目录:
- 设置环境./configure
- 编译make
- 安装 make install 注意root权限才可以安装,否则会提示权限不够 sudo make install
以上步骤顺利完成后,mini_xml库就已经安装到你的Linux系统中了。
以后就可以直接调用头文件 #include "mxml.h"使用了。
4.编译自带测试程序
下面用源码中自带的测试程序,先了解下大致功能
源码中有一个testmxml.c的文件就是测试程序,现编译成可执行文件
gcc -o testmxml testmxml.c
提示错误:
testmxml.c:(.text+0x1655):对‘mxmlSAXLoadFile’未定义的引用
/tmp/cc9KVjbn.o:在函数‘type_cb’中:
testmxml.c:(.text+0x1827):对‘mxmlElementGetAttr’未定义的引用
collect2: error: ld returned 1 exit status
动态链接库未在编译时链接进去,修改
gcc -o testmxml testmxml.c -lmxml
再编译:
//usr/local/lib/libmxml.so:对‘pthread_getspecific’未定义的引用
//usr/local/lib/libmxml.so:对‘pthread_key_create’未定义的引用
//usr/local/lib/libmxml.so:对‘pthread_once’未定义的引用
//usr/local/lib/libmxml.so:对‘pthread_setspecific’未定义的引用
//usr/local/lib/libmxml.so:对‘pthread_key_delete’未定义的引用
collect2: error: ld returned 1 exit status
还是有错误,看提示信息是pthread线程相关动态库未链接进来,继续修改
gcc -o testmxml testmxml.c -lmxml -lpthread
这次便编译成功了
5.运行
./testmxml
提示信息:
Usage: testmxml filename.xml [string-output.xml]
说明输入参数需要有一个.xml文件,或者一个可选的输出.xml文件。
第一个文件必须要在testmxml同一目录下存在
如在源程序中已经存在一份test.xml文件
重新运行:
./testmxml test.xml
就会把test.xml文件中的内容显示出来了。
如果运行:
./testmxml test.xml testnew.xml
就会把test.xml文件中的内容复制到testnew.xml文件中,同时会显示在界面上。
今天就先讲解到此,下一次就详细讲解如何实现一个最简单的xml文件解析程序。
二.相关参数描述
1、mxml 提取xml内容回调参数描述
mxmlLoadFile()和mxmlLoadString().这些函数的最后一个参数是一个回调函数,决定了在一个XML文档中每个数据节点的值的类型。
mxmlLoadFile(NULL, fp, MXML_OPAQUE_CALLBACK);
Mini-XML为简单XML数据文件定义了几个标准的回调函数:
MXML_INTEGER_CALLBACK-所有的数据节点包含以空格分割的整数。
MXML_OPAQUE_CALLBACK-所有的数据节点包含"不透明"字符串(CDATA)。
MXML_REAL_CALLBACK-所有的数据节点包含以空格分割的浮点数。
MXML_TEXT_CALLBACK-所有的数据节点包含以空格分割的文本字符串。
2、通过mxmlFindElement 提取节点内容时
mxml_node_t *val = mxmlFindElement(node, tree, "标签值",NULL, NULL,MXML_DESCEND);
MXML_DESCEND含义是一直向下直到树的根部
MXML_DESCEND_FIRST含义是向下搜索到一个节点的第一个匹配子节点,但不再继续向下搜索
MXML_NO_DESCEND含义是不查看任何的子节点在XML元素层次中,仅查看同级的伙伴节点或者父节点直到到达顶级节点或者给出的树的顶级节点.
三.编写xml解析测试例子
1、解析xml数据
5000
FE-D0-18-00
FE-D0-18-01
2、解析程序如下:
#include
#include
#include
#include
int main(int argc,char **argv)
{
if(argc<2){
printf("input xml file\n");
return -1;
}
FILE *fp;
mxml_node_t *tree,*node;
fp = fopen(argv[1], "r");
tree = mxmlLoadFile(NULL, fp,MXML_TEXT_CALLBACK);
fclose(fp);
mxml_node_t *val,*id;
node = mxmlGetFirstChild(tree);
val = mxmlFindElement(node, tree, "password1",NULL, NULL,MXML_DESCEND);
if(val){
printf(": %s \n",val->child->value.text.string);
}
node = mxmlFindElement(tree, tree, "note",NULL, NULL,MXML_DESCEND);
printf(" year:%s \n",mxmlElementGetAttr(node,"year"));
printf(" date:%s \n",mxmlElementGetAttr(node,"date"));
printf(" month:%s \n",mxmlElementGetAttr(node,"month"));
id = mxmlFindElement(node, tree, "id",NULL, NULL,MXML_DESCEND);
printf("[%s}\n",id->child->value.text.string);
mxmlDelete(tree);
return 0;
}
3、minixml空格无法解析问题
上面的代码,MXML_TEXT_CALLBACK 采用回调时,以空格进行分割
出现如下标签:
FE-D0 18-00
会出现解析出来的password 为: FE-D0 后面的18-00 没办法提取出来
带空格xml内容如下:
5000
FE-D0 18-00
FE-D0 18-01
4、解析带空格的代码
#include
#include
#include
#include
int main(int argc,char **argv)
{
if(argc<2){
printf("input xml file\n");
return -1;
}
FILE *fp;
mxml_node_t *tree,*node;
fp = fopen(argv[1], "r");
tree = mxmlLoadFile(NULL, fp,MXML_OPAQUE_CALLBACK);
fclose(fp);
mxml_node_t *val,*id;
node = mxmlGetFirstChild(tree);
val = mxmlFindElement(node, tree, "password1",NULL, NULL,MXML_DESCEND);
if(val){
printf(": %s \n",val->child->value.opaque);
}
node = mxmlFindElement(tree, tree, "note",NULL, NULL,MXML_DESCEND);
printf(" year:%s \n",mxmlElementGetAttr(node,"year"));
printf(" date:%s \n",mxmlElementGetAttr(node,"date"));
printf(" month:%s \n",mxmlElementGetAttr(node,"month"));
id = mxmlFindElement(node, tree, "id",NULL, NULL,MXML_DESCEND);
printf("[%s}\n",val->child->value.opaque);
mxmlDelete(tree);
return 0;
}
四.编写xml组装测试例子
参考 https://blog.csdn.net/bluesonic/article/details/3887143 (minixml中文手册)
节点
每一块XML文件中的信息片断(元素、文本、数字)是一个存储在内存中的"节点(nodes)" .节点使用mxml_node_t 结构进行定义. 它的type 成员变量定义了节点类型(element, integer, opaque, real, or text) 决定了需要从联合(union)类型的成员变量value 中获取的值.
表 2-1: Mini-XML 节点值的成员变量
值
类型
节点成员
用户定义
void *
node->value.custom.data
XML元素
char *
node->value.element.name
整数
int
node->value.integer
不透明字符串
char *
node->value.opaque
浮点数
double
node->value.real
文本
char *
node->value.text.string
译者:节点类型定义枚举参见:mxml_type_e。Mini-XML中的节点类型定义和其他有些解析器有些不同,其中整数、浮点、和文本节点是指在一个XML元素中一系列的使用空格作为分割的值,每个元素可以拥有多个以上节点,并可以选择使用空格分开,如:aa bb cc ,Mini-MXML在使用参数:MXML_TEXT_CALLBACK进行载入时,将在abc元素下面生成3个text类型的子节点。在创建时也可以使用同样的方式创建节点。整数和浮点也是同样方式,但如果转换失败则MiniXML报错。而不透明字符串类型(OPAQUE)则不进行字符串分割,在载入时需要使用MXML_OPAQUE_CALLBACK参数,将所有字符串形成一个子节点。详情见:使用加载回调函数。 Z.F
每一个节点总是有一个成员变量:user_data 可以允许你为每一个节点关联你需要的应用数据.
新的节点可以使用以下函数进行创建 mxmlNewElement, mxmlNewInteger, mxmlNewOpaque, mxmlNewReal, mxmlNewText mxmlNewTextf mxmlNewXML . 只有 elements 可以拥有子节点,顶级节点必须是一个 element , 通常是 使用mxmlNewXML()函数创建的节点.
每个节点都有一些关联节点的指针,上(parent), 下(child), 左(prev), and 右(next) 相对应于当前节点. 如果你有一个XML文件如下所示:
val1
val2
val3
val4
val5
val6
val7
val8
那么在内存中的文件节点树看上去如下所示:
?xml
|
data
|
node - node - node - group - node - node
| | | | | |
val1 val2 val3 | val7 val8
|
node - node - node
| | |
val4 val5 val6
这里"-"指向下一个节点,"|"指向第一个子节点。
当你使用完毕这些XML数据后,使用函数mxmlDelete 来释放指定节点或者整个XML树节点和它下面所有子节点的内存:
mxmlDelete(tree);
创建 XML 文档
你可以在内存中创建和更新XML文档,使用mxmlNew 一系列函数. 下面的代码将创建上一章描述的XML文档:
mxml_node_t *xml; /* */
mxml_node_t *data; /* */
mxml_node_t *node; /* */
mxml_node_t *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) ,第二个参数是元素名 (data):
data = mxmlNewElement(xml, "data");
每个在本文件中... 之间的部分使用函数mxmlNewElement 和 mxmlNewText来创建. mxmlNewText 的第一个参数指定了父节点 (node).第二个参数指定了是否在文本之前添加空白字符,在本例中使用0或者false.最后一个参数指定了需要添加的实际文本:
node = mxmlNewElement(data, "node");
mxmlNewText(node, 0, "val1");
使用 mxmlElementSetAttr函数可以在标签头中增加附加属性,使用方法如下:
mxmlElementSetAttr(mxml_node_t *node, /* I - Element node */
const char *name, /* I - Name of attribute */
const char *value) /* I - Attribute value */
mxmlElementSetAttr(group, "test1", "11");
mxmlElementSetAttr(group, "test2", "22");
加载XML
你可以加载一个XML文件使用函数 mxmlLoadFile :
FILE *fp;
mxml_node_t *tree;
fp = fopen("filename.xml", "r");
tree = mxmlLoadFile(NULL, fp,
MXML_TEXT_CALLBACK);
fclose(fp);
第一个参数如果有的话则指定了一个存在的XML父节点.一般你将这个参数等于NULL,除非你想要连接多个XML源. 如果此参数等于NULL,那么指定的XML文件必须是一个完整的XML文档,文档头部要包含?xml元素.
第二个参数指定了一个标准的文件流,使用 fopen() 或者 popen()进行打开. 你也可以使用stdin,如果你想要实现一个XML过滤器程序.
第三个参数指定了一个回调函数用于一个新的XML元素节点直接返回的子节点的值类型: MXML_CUSTOM, MXML_IGNORE, MXML_INTEGER, MXML_OPAQUE, MXML_REAL, or MXML_TEXT. 加载回调函数的细节在第三章做了详细描述. 示例代码使用 MXML_TEXT_CALLBACK 常量指定文档中所有的数据节点都包含使用以空格字符分割的文本的值. 其他标准的回调还有 MXML_IGNORE_CALLBACK, MXML_INTEGER_CALLBACK, MXML_OPAQUE_CALLBACK, 和MXML_REAL_CALLBACK.
函数mxmlLoadString 可以从一个字符串中载入XML节点树:
char buffer[8192];
mxml_node_t *tree;
...
tree = mxmlLoadString(NULL, buffer,
MXML_TEXT_CALLBACK);
第一个和第三个参数和 mxmlLoadFile()用法一样. 第二个参数指定了指定了字符串或者字符缓冲区用于加载XML,当父节点参数为NULL时内容必须为完整的XML文档,包括XML头?xml元素.
保存 XML
你可以保存XML文件使用 mxmlSaveFile 函数:
FILE *fp;
mxml_node_t *tree;
fp = fopen("filename.xml", "w");
mxmlSaveFile(tree, fp, MXML_NO_CALLBACK);
fclose(fp);
第一个参数为想要保存的XML节点树,一般应该是一个指向你的XML文档顶级节点?xml的节点指针.
第二个单数是一个标准文件流,使用 fopen() 或者 popen()来打开. 你也可以使用stdout 如果你想要实现一个XML过滤器程序.
第三个参数是一个空白回调函数用来控制保存文件时插入的"空白"字符."空白回调"的详细信息参见 第三章. 以上的示例代码使用了MXML_NO_CALLBACK常量来指定不需要特别的空白处理.
函数mxmlSaveAllocString, 和mxmlSaveString 保存XML节点树到一个字符串中:
char buffer[8192];
char *ptr;
mxml_node_t *tree;
...
mxmlSaveString(tree, buffer, sizeof(buffer),
MXML_NO_CALLBACK);
...
ptr = mxmlSaveAllocString(tree, MXML_NO_CALLBACK);
第一个和最后一个参数的用法和函数 mxmlSaveFile()一样. 函数mxmlSaveString 给出了一个指针和长度的参数来保存XML文档到一个固定大小的缓冲区中。, mxmlSaveAllocString() 返回了使用malloc分配的一个字符串缓冲区(注意ptr使用完需要手动释放内存)
自动折行控制
当我们保存XML文档时, Mini-XML一般在第75列进行折行,因为这样在终端下最易读. 函数 mxmlSetWrapMargin 可以覆盖缺省的折行界限:
/* 设置自动折行到 132 列*/
mxmlSetWrapMargin(132);
/* 取消自动折行*/
mxmlSetWrapMargin(0);
搜索和遍历节点
函数mxmlWalkPrev and mxmlWalkNext可以被用来遍历XML节点树:
mxml_node_t *node;
node = mxmlWalkPrev(current, tree,
MXML_DESCEND);
node = mxmlWalkNext(current, tree,
MXML_DESCEND);
另外,你可以搜索一个命名的XML元素/节点,使用函数 mxmlFindElement:
mxml_node_t *node;
node = mxmlFindElement(tree, tree, "name",
"attr", "value",
MXML_DESCEND);
参数name, attr, 和value 可以被设置为NULL作为全部匹配, e.g.:
/* 搜索第一个 "a" 元素*/
node = mxmlFindElement(tree, tree, "a",
NULL, NULL,
MXML_DESCEND);
/* 搜索第一个"a" 元素并包含"href"属性*/
node = mxmlFindElement(tree, tree, "a",
"href", NULL,
MXML_DESCEND);
/* 搜索第一个"a" 元素并且包含"href"属性等于给出的URL */
node = mxmlFindElement(tree, tree, "a",
"href",
"http://www.easysw.com/",
MXML_DESCEND);
/* 搜索第一个包含"src"属性的XML元素*/
node = mxmlFindElement(tree, tree, NULL,
"src", NULL,
MXML_DESCEND);
/* 搜索第一个包含"src"= "foo.jpg"属性的XML元素 */
node = mxmlFindElement(tree, tree, NULL,
"src", "foo.jpg",
MXML_DESCEND);
你也可以使用同样的功能进行遍历:
mxml_node_t *node;
for (node = mxmlFindElement(tree, tree,
"name",
NULL, NULL,
MXML_DESCEND);
node != NULL;
node = mxmlFindElement(node, tree,
"name",
NULL, NULL,
MXML_DESCEND))
{
... do something ...
}
参数MXML_DESCEND可以是下面三个常量之一:
- MXML_NO_DESCEND 含义是不查看任何的子节点在XML元素层次中,仅查看同级的伙伴节点或者父节点直到到达顶级节点或者给出的树的顶级节点.
"group"节点的上一个节点时它左面的"node"子节点,下一个节点是"group"右面的"node"子节点..
- MXML_DESCEND_FIRST含义是向下搜索到一个节点的第一个匹配子节点,但不再继续向下搜索。你一般使用于遍历一个父节点的直接的子节点。 ,如:在上面的例子中的所有在"?xml"父节点下的所有的"node"和"group"子节点。.
这个模式仅适用于搜索(search)功能,遍历功能(walk)对待它和 MXML_DESCEND 一样,因为每次调用都是首次调用。
- MXML_DESCEND含义是一直向下直到树的根部. "group"节点的上一个节点将是"val3"节点,下一个节点将是"group"的下面的第一个子节点。
如果你要使用函数mxmlWalkNext()从根结点"?xml" 遍历到整个树的结束, 那么这个顺序将如下所示:
?xml data node val1 node val2 node val3 group node val4 node val5 node val6 node val7 node val8
如果你从"val8"开始并使用函数mxmlWalkPrev()进行遍历, 这个顺序将是反的,结束于"?xml"节点.