以C之名,解析Xml

参考文章:
libxml2的安装及使用_阿卡基YUAN的博客-CSDN博客

Xml文件介绍

解析XML文件

libxml2的安装

参考安装:Linux如何安装并配置libxml2库?解决"libxml2 not found"问题_Mintimate的博客-CSDN博客

指令安装

Linux是Debian或Ubuntu:

bash 复制代码
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博客

具体安装步骤:

  1. 解压:$tar zxvf libxml2-2.9.1.tar.gz

  2. 进入解压后的安装目录:$cd libxml2-2.9.1

  3. 配置libxml2库

    1. ./configure
    2. make
    3. make install
  4. 执行配置命令

c 复制代码
./configure
  1. 编译过程中出现出错
c 复制代码
libxml.c:14:20: fatal error: Python.h: No such file or directory

需要安装python,执行命令:

bash 复制代码
sudo apt-get install python-dev

安装完python-dev之后,再次编译成功。

  1. 执行make install执行安装

安装完成之后,查看output路径下,增加了相关的文件。

XML文件类型

xml配置文件如下:

bash 复制代码
<?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

xmlChar是Libxml2中的字符类型,库中所有字符、字符串都是基于这个数据类型。事实上他的定义是:xmlstring.h

c 复制代码
#incldue<xmlstring.h>

typedef unsigned char xmlChar;

使用unsigned char作为内部字符格式是考虑到他能非常好适应UTF-8编码,而UTF-8编码正是libxml2的内部编码,其他格式的编码要转换为这个编码才能在libxml2中使用。

xmlChar相关函数

如同标准c中的char类型相同,xmlChar也有动态内存分配、字符串操作等相关函数

  1. xmlMalloc是动态分配内存的函数;
  2. xmlFree是配套的释放内存函数;
  3. xmlStrcmp是字符串比较函数等等。

基本上xmlChar字符串相关函数都在xmlstring.h 中定义;而动态内存分配函数在xmlmemory.h中定义

xmlChar*和其他类型之间的转换

另外要注意,因为总是要在xmlChar和char 之间进行类型转换,所以定义了一个宏BAD_CAST ,其定义如下:xmlstring.h

c 复制代码
#include<xmlstring.h>

#define BAD_CAST (xmlChar *)

原则上来说,unsigned char和char之间进行强制类型转换是没有问题的。

文件类型xmlDoc、指针xmlDocPtr

xmlDoc是个struct,保存了一个xml的相关信息,例如文件名、文件类型、子节点等等;xmlDocPtr等于xmlDoc*,他搞成这个样子总让人以为是智能指针,其实不是,要手动删除的。

  • **xmlNewDoc: **创建一个新的文件指针。
  • **xmlParseFile: **以默认方式读入一个UTF-8格式的文件,并返回文件指针
  • **xmlReadFile: **读入一个带有某种编码的xml文件,并返回文件指针;
  • **xmlFreeDoc: **释放文件指针。特别注意,当你调用xmlFreeDoc时,该文件所有包含的节点内存都被释放,所以一般来说不必手动调用xmlFreeNode或xmlFreeNodeList来释放动态分配的节点内存,除非你把该节点从文件中移除了。一般来说,一个文件中所有节点都应该动态分配,然后加入文件,最后调用xmlFreeDoc一次释放所有节点申请的动态内存,这也是为什么我们非常少看见xmlNodeFree的原因。
  • **xmlSaveFile: **将文件以默认方式存入一个文件。
  • **xmlSaveFormatFileEnc: **可将文件以某种编码/格式存入一个文件中。

节点类型xmlNode、指针xmlNodePtr

节点应该是xml中最重要的元素了,xmlNode代表了xml文件中的一个节点,实现为一个struct,内容非常丰富:tree.h

c 复制代码
#include<tree.h>

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树。

节点集合类型xmlNodeSet、指针xmlNodeSetPtr

节点集合代表一个由节点组成的变量,节点集合只作为Xpath的查询结果而出现(XPATH的介绍见后面),因此被定义在xpath.h中,其定义如下:

c 复制代码
#include<xpath.h>

/*
* 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

对节点集合中各个节点的访问方式非常简单,如下:

c 复制代码
xmlNodeSetPtr nodeset = XPATH查询结果;
for (int i = 0; i < nodeNr; i++) 
{
	nodeset->nodeTab;
}

注意:libxml2是个c函数库,因此其函数和数据类型都使用c语言的方式来处理。

xml文档结构

xml按照树形结构进行存储,节点分为元素和文本,必须有根节点。如下的xml文件:

c 复制代码
<?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函数能将一个节点设置为某个文件的根节点

创建xml文件

创建流程:

  1. 用xmlNewDoc函数创建一个文件指针doc;
  2. 用xmlNewNode函数创建一个节点指针root_node;
  3. 用xmlDocSetRootElement函数讲root_node设置为doc的根节点;
  4. 给root_node添加一系列的子节点, 并设置子节点的内容和属性;
  5. 用xmlSaveFile函数讲xml文件存入文件
  6. 用xmlFreeDoc函数关闭文件指针, 并清除本文件中所有节点动态申请的内存

可以有多种方式添加子节点:

  1. 用xmlNewTextChild函数直接添加一个文本子节点
  2. 先创建新节点, 然后用xmlAddChild将新节点加入上层节点

源码分析:

c 复制代码
#include<stdio.h>
#include<stdlib.h>

#include<libxml/parser.h>
#include<libxml/tree.h>
#include<libxml/xmlmemory.h>

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博客

  1. make编译,你应该会报错, "找不到parser.h等头文件"
  2. 然后你去查找一下你会发现, 头文件竟然在src里找到了
  1. 进入/usr/include目录, 你会发现你要找的头文件都是在该路径下,**/usr/include/libxml2/libxml/...h. **
  2. 因为安装好的库,头文件默认是在libxml2的目录下, 所以我们需要做一个软链接.
c 复制代码
	sudo ln -s /usr/include/libxml2/libxml  /usr/include/libxml
  1. 然后重新make编译, 你会发现又报错. 哈哈

这里是报错的原因是你一些文件没有链接成功, 加上libxml2.so的动态库就行了.

  1. 加上动态库,重新编译
c 复制代码
gcc xml.c -o xml -I/usr/include/libxml -lxml2
  1. 运行成功,
  1. 此时会生成一个CreateXml.xml文件
xml 复制代码
<?xml version="1.0"?>
<root>
  <newNode1>
    newNode1 content
  </newNode1>
  <newNode2>
    newNode2 content
  </newNode2>
  <newNode3>
    newNode3 content
  </newNode3>
  <node2 attribute="yes"/>NODE CONTENT
  <son>
    <grandson>This is grandson Node</grandson>
  </son>
</root>

附上Makefile文件, 供大家查看, 也正好锻炼一下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文件

解析一个xml文件,从中读取信息, 例如节点中的文字, 或某个节点的属性...

解析流程如下:

  1. xmlReadFile函数读出一个文件指针doc;
  2. xmlDocGetRootElement函数得到根节点curNode;
  3. curNode->xmlChildrenNode就是根节点的子节点集合;
  4. 轮询子节点集合, 找到所需要的节点, 用xmlNodeGetContent取出内容;
  5. xmlHasProp查找含有某个属性的节点;
  6. 取出该节点的属性集合, 用xmlGetProp取出其属性值;
  7. xmlFreeDoc函数关闭文件指针, 并清除本文中的所有节点申请的动态内存;
xml 复制代码
<?xml version="1.0"?>
<root>
  <newNode1>
    newNode1 content
  </newNode1>
  <newNode2>
    newNode2 content
  </newNode2>
  <newNode3>
    newNode3 content
  </newNode3>
  <node2 attribute="yes"/>NODE CONTENT
  <son>
    <grandson>This is grandson Node</grandson>
  </son>
</root>

源码解析:

makefile 复制代码
#include<stdio.h>
#include<stdlib.h>

#include<libxml/parser.h>
#include<libxml/tree.h>
#include<libxml/xmlmemory.h>
#include<libxml/xmlstring.h>

int main(int argc, char* argv[]){
    xmlDocPtr doc;                      //定义解析文件指针
    xmlNodePtr curNode;                 //定义根节点指针
    xmlChar *szKey;                     //临时字符串变量
    char *szDocName;

    if(argc < 2){
        printf("Usage: %s <xml>\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;

}

输出结果

相关推荐
奋斗的小花生1 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功1 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨1 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程1 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
萧鼎4 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步
学地理的小胖砸4 小时前
【一些关于Python的信息和帮助】
开发语言·python