参考文章:
libxml2的安装及使用_阿卡基YUAN的博客-CSDN博客
参考安装:Linux如何安装并配置libxml2库?解决“libxml2 not found“问题_Mintimate的博客-CSDN博客
Linux是Debian或Ubuntu:
sudo apt-get install libxml2
sudo apt-get install libxml2-dev
可参考libxml2的官方网址: Home · Wiki · GNOME / libxml2 · GitLab
下载最新的libxml2库: Releases · GNOME / libxml2 · GitLab
安装参考: c语言读取xml配置文件-CSDN博客
具体安装步骤:
解压:$tar zxvf libxml2-2.9.1.tar.gz
进入解压后的安装目录:$cd libxml2-2.9.1
配置libxml2库
执行配置命令
./configure
libxml.c:14:20: fatal error: Python.h: No such file or directory
需要安装python,执行命令:
sudo apt-get install python-dev
安装完python-dev之后,再次编译成功。
安装完成之后,查看output路径下,增加了相关的文件。
xml配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<bmp_para>
<para id="1">
<width>1920</width>
<height>1080</height>
<bit>3</bit>
<blue>0</blue>
<green>0</green>
<red>255</red>
</para>
</bmp_para>
xmlChar是Libxml2中的字符类型,库中所有字符、字符串都是基于这个数据类型。事实上他的定义是:xmlstring.h
#incldue<xmlstring.h>
typedef unsigned char xmlChar;
使用unsigned char作为内部字符格式是考虑到他能非常好适应UTF-8编码,而UTF-8编码正是libxml2的内部编码,其他格式的编码要转换为这个编码才能在libxml2中使用。
如同标准c中的char类型相同,xmlChar也有动态内存分配、字符串操作等相关函数
基本上xmlChar字符串相关函数都在xmlstring.h中定义;而动态内存分配函数在xmlmemory.h中定义
另外要注意,因为总是要在xmlChar和char之间进行类型转换,所以定义了一个宏BAD_CAST,其定义如下:xmlstring.h
#include
#define BAD_CAST (xmlChar *)
原则上来说,unsigned char和char之间进行强制类型转换是没有问题的。
xmlDoc是个struct,保存了一个xml的相关信息,例如文件名、文件类型、子节点等等;xmlDocPtr等于xmlDoc*,他搞成这个样子总让人以为是智能指针,其实不是,要手动删除的。
节点应该是xml中最重要的元素了,xmlNode代表了xml文件中的一个节点,实现为一个struct,内容非常丰富:tree.h
#include
typedef struct _xmlNode xmlNode;
typedef xmlNode *xmlNodePtr;
struct _xmlNode {
void *_private;/* application data */
xmlElementType type; /* type number, must be second ! */
const xmlChar *name; /* the name of the node, or the entity */
struct _xmlNode *children; /* parent->childs link */
struct _xmlNode *last; /* last child link */
struct _xmlNode *parent;/* child->parent link */
struct _xmlNode *next; /* next sibling link */
struct _xmlNode *prev; /* previous sibling link */
struct _xmlDoc *doc;/* the containing document */
/* End of common part */
xmlNs *ns; /* pointer to the associated namespace */
xmlChar *content; /* the content */
struct _xmlAttr *properties;/* properties list */
xmlNs *nsDef; /* namespace definitions on this node */
void *psvi;/* for type/PSVI informations */
unsigned short line; /* line number */
unsigned short extra; /* extra data for XPath/XSLT */
};
能看到,节点之间是以链表和树两种方式同时组织起来的,next和prev指针能组成链表,而parent和children能组织为树。同时更有以下重要元素:
l 节点中的文字内容:content;
l 节点所属文件:doc;
l 节点名字:name;
l 节点的namespace:ns;
l 节点属性列表:properties;
Xml文件的操作其根本原理就是在节点之间移动、查询节点的各项信息,并进行增加、删除、修改的操作
xmlDocSetRootElement函数能将一个节点设置为某个文件的根节点,这是将文件和节点连接起来的重要手段,当有了根结点以后,所有子节点就能依次连接上根节点,从而组织成为一个xml树。
节点集合代表一个由节点组成的变量,节点集合只作为Xpath的查询结果而出现(XPATH的介绍见后面),因此被定义在xpath.h中,其定义如下:
#include
/*
* A node-set (an unordered collection of nodes without duplicates).
*/
typedef struct _xmlNodeSet xmlNodeSet;
typedef xmlNodeSet *xmlNodeSetPtr;
struct _xmlNodeSet {
int nodeNr; /* number of nodes in the set */
int nodeMax; /* size of the array as allocated */
xmlNodePtr *nodeTab; /* array of nodes in no particular order */
/* @@ with_ns to check wether namespace nodes should be looked at @@ */
};
节点集合有三个成员,分别是节点集合的节点数nodeNr、最大可容纳的节点数nodeMax,及节点数组头指针nodeTab。
对节点集合中各个节点的访问方式非常简单,如下:
xmlNodeSetPtr nodeset = XPATH查询结果;
for (int i = 0; i < nodeNr; i++)
{
nodeset->nodeTab;
}
注意:libxml2是个c函数库,因此其函数和数据类型都使用c语言的方式来处理。
xml按照树形结构进行存储,节点分为元素和文本,必须有根节点。如下的xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<phone_books>
<phone id="1">
<name>Anker</name>
<tel>18923873456</tel>
<address>Shenzheng</address>
</phone>
<phone id="2">
<name>Jermey</name>
<tel>18623873456</tel>
<address>Beijing</address>
</phone>
<phone id="3">
<name>Lili</name>
<tel>13223873456</tel>
<address>Shanghai</address>
</phone>
</phone_books>
libxml2常用的接口如下:
内部字符类型:xmlChar,用无符号型的char方便表示utf-8编码。libxml2提供了一个宏进行转换,#define BAD_CAST (xmlChar *)。
文档类型xmlDoc,指针类型xmlDocPtr。
xmlDoc是个struct,保存了一个xml的相关信息,例如文件名、文件类型、子节点等等;xmlDocPtr等于xmlDoc*。
xmlNewDoc函数创建一个新的文件指针。
xmlParseFile函数以默认方式读入一个UTF-8格式的文件,并返回文件指针。
xmlReadFile函数读入一个带有某种编码的xml文件,并返回文件指针
xmlFreeDoc释放文件指针。特别注意,当你调用xmlFreeDoc时,该文件所有包含的节点内存都被释放
xmlFreeNodeList来释放动态分配的节点内存,除非你把该节点从文件中移除了。
xmlSaveFile将文件以默认方式存入一个文件。
xmlSaveFormatFileEnc可将文件以某种编码/格式存入一个文件中。
节点类型xmlNode、指针xmlNodePtr
xmlDocSetRootElement函数能将一个节点设置为某个文件的根节点
创建流程:
可以有多种方式添加子节点:
源码分析:
#include
#include
#include
#include
#include
int main(int argc, char *argv[]){
xmlDocPtr doc = xmlNewDoc(BAD_CAST"1.0");
xmlNodePtr root_node = xmlNewNode(NULL, BAD_CAST"root");
xmlDocSetRootElement(doc, root_node); //设置根节点
//在根节点下直接创建子节点
xmlNewTextChild(root_node, NULL, BAD_CAST"newNode1", BAD_CAST"newNode1 content");
xmlNewTextChild(root_node, NULL, BAD_CAST"newNode2", BAD_CAST"newNode2 content");
xmlNewTextChild(root_node, NULL, BAD_CAST"newNode3", BAD_CAST"newNode3 content");
xmlNodePtr node = xmlNewNode(NULL, BAD_CAST"node2"); //创建新节点,并设置内容和属性,并添加到根节点
xmlNodePtr content = xmlNewText(BAD_CAST"NODE CONTENT");
xmlAddChild(root_node, node);
xmlAddChild(root_node, content);
xmlNewProp(node, BAD_CAST"attribute", BAD_CAST"yes");
//创建一个儿子和孙子节点
node = xmlNewNode(NULL, BAD_CAST"son");
xmlAddChild(root_node, node);
xmlNodePtr grandson = xmlNewNode(NULL, BAD_CAST"grandson");
xmlAddChild(node, grandson);
xmlAddChild(grandson, xmlNewText(BAD_CAST"This is grandson Node"));
//存储xml文件
int nRet = xmlSaveFile("CreatedXml.xml",doc);
if(nRet != -1){
printf("xml creat success\n");
xmlFreeDoc(doc);
}
return 0;
}
参考学习: “libxml/parser.h: 没有那个文件或目录”解决方案_iamlate的博客-CSDN博客
sudo ln -s /usr/include/libxml2/libxml /usr/include/libxml
这里是报错的原因是你一些文件没有链接成功, 加上libxml2.so的动态库就行了.
gcc xml.c -o xml -I/usr/include/libxml -lxml2
<root>
<newNode1>
newNode1 content
newNode1>
<newNode2>
newNode2 content
newNode2>
<newNode3>
newNode3 content
newNode3>
<node2 attribute="yes"/>NODE CONTENT
<son>
<grandson>This is grandson Nodegrandson>
son>
root>
附上Makefile文件, 供大家查看, 也正好锻炼一下Makefile
CC := gcc
SRC := $(wildcard *.c)
OBJS := $(patsubst %.c,%.o, ${SRC})
TARGET := $(basename ${OBJS})
INCLUDE_DIR := /usr/include/libxml
CFLAGS+= -I$(INCLUDE_DIR)/
LIBS+= -lxml2
add: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $^ -o $@ $(CFLAGS) $(LIBS)
%.o: %.c
$(CC) -c $< -o $@
clean:
-rm -rf $(OBJS)
-rm -rf $(TARGET)
在上面,我们学会了如何创建xml文件. 这节学习如何解析已存在的xml文件
解析一个xml文件,从中读取信息, 例如节点中的文字, 或某个节点的属性…
解析流程如下:
<root>
<newNode1>
newNode1 content
newNode1>
<newNode2>
newNode2 content
newNode2>
<newNode3>
newNode3 content
newNode3>
<node2 attribute="yes"/>NODE CONTENT
<son>
<grandson>This is grandson Nodegrandson>
son>
root>
源码解析:
#include
#include
#include
#include
#include
#include
int main(int argc, char* argv[]){
xmlDocPtr doc; //定义解析文件指针
xmlNodePtr curNode; //定义根节点指针
xmlChar *szKey; //临时字符串变量
char *szDocName;
if(argc < 2){
printf("Usage: %s \n", argv[0]);
exit(EXIT_FAILURE);
}
szDocName = argv[1]; //保存xml文件名
doc = xmlReadFile(szDocName, "GB2312", XML_PARSE_RECOVER); //解析文件
//检查是否解析成功
if(doc == NULL){
fprintf(stderr, "Document parse failure\n");
exit(EXIT_FAILURE);
}
curNode = xmlDocGetRootElement(doc); //确定根节点
//先检查当前xml文件里是否是空
if(curNode == NULL){
fprintf(stderr, "empty xml file\n");
exit(EXIT_FAILURE);
}
if(xmlStrcmp(curNode->name, "root") != 0){ //判断是否是根节点
fprintf(stderr, "Type error\n");
exit(EXIT_FAILURE);
}
curNode = curNode->xmlChildrenNode; //遍历子节点 next
xmlNodePtr propNodeptr = curNode;
while(curNode != NULL){
if((xmlStrcmp(curNode->name, (const xmlChar *)"newNode2")) == 0){ //取出子节点的名字
szKey = xmlNodeGetContent(curNode); //保存子节点的名字
printf("newNode1: %s\n", szKey);
xmlFree(szKey); //清空xmlChar类型
}
if(xmlHasProp(curNode, BAD_CAST"attribute") != 0){ //查看属性是attribute的子节点
propNodeptr = curNode; //如果没有就下一个字节点
}
curNode = curNode->next;
}
//查找属性
xmlAttrPtr attPtr = propNodeptr->properties;
while(attPtr != NULL){
if(!xmlStrcmp(attPtr->name, BAD_CAST"attribute")){
xmlChar *szAttr = xmlGetProp(propNodeptr, BAD_CAST"attribute");
printf("szAttr: %s\n", szAttr);
xmlFree(szAttr);
}
attPtr = attPtr->next;
}
xmlFreeDoc(doc);
return 0;
}
输出结果