• 以C之名,解析Xml


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

    Xml文件介绍

    解析XML文件

    libxml2的安装

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

    指令安装

    Linux是Debian或Ubuntu:

    sudo apt-get install libxml2
    sudo apt-get install libxml2-dev
    
    • 1
    • 2

    编译安装

    可参考libxml2的官方网址: Home · Wiki · GNOME / libxml2 · GitLab
    下载最新的libxml2库: Releases · GNOME / libxml2 · GitLab
    安装参考: c语言读取xml配置文件-CSDN博客

    image.png
    具体安装步骤:

    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. 执行配置命令

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

    需要安装python,执行命令:

    sudo apt-get install python-dev
    
    • 1

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

    1. 执行make install执行安装

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

    XML文件类型

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    内部字符类型xmlChar

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

    #incldue<xmlstring.h>
    
    typedef unsigned char xmlChar;
    
    • 1
    • 2
    • 3

    使用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

    #include
    
    #define BAD_CAST (xmlChar *)
    
    • 1
    • 2
    • 3

    原则上来说,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

    #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 */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    能看到,节点之间是以链表和树两种方式同时组织起来的,next和prev指针能组成链表,而parent和children能组织为树。同时更有以下重要元素:

    l 节点中的文字内容:content;
    l 节点所属文件:doc;
    l 节点名字:name;
    l 节点的namespace:ns;
    l 节点属性列表:properties;

    Xml文件的操作其根本原理就是在节点之间移动、查询节点的各项信息,并进行增加、删除、修改的操作

    xmlDocSetRootElement函数能将一个节点设置为某个文件的根节点,这是将文件和节点连接起来的重要手段,当有了根结点以后,所有子节点就能依次连接上根节点,从而组织成为一个xml树。

    节点集合类型xmlNodeSet、指针xmlNodeSetPtr

    节点集合代表一个由节点组成的变量,节点集合只作为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 @@ */
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    节点集合有三个成员,分别是节点集合的节点数nodeNr最大可容纳的节点数nodeMax,及节点数组头指针nodeTab

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

    xmlNodeSetPtr nodeset = XPATH查询结果;
    for (int i = 0; i < nodeNr; i++) 
    {
    	nodeset->nodeTab;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    xml文档结构

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    实操练习

    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将新节点加入上层节点

    源码分析:

    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    参考学习: “libxml/parser.h: 没有那个文件或目录”解决方案_iamlate的博客-CSDN博客

    1. make编译,你应该会报错, “找不到parser.h等头文件”
    2. 然后你去查找一下你会发现, 头文件竟然在src里找到了

    c51637ef8fff5f084728e1c4b85f96c.png

    1. 进入/usr/include目录, 你会发现你要找的头文件都是在该路径下,**/usr/include/libxml2/libxml/…h. **
    2. 因为安装好的库,头文件默认是在libxml2的目录下, 所以我们需要做一个软链接.
    	sudo ln -s /usr/include/libxml2/libxml  /usr/include/libxml
    
    • 1
    1. 然后重新make编译, 你会发现又报错. 哈哈

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

    1. 加上动态库,重新编译
    gcc xml.c -o xml -I/usr/include/libxml -lxml2
    
    • 1
    1. 运行成功,

    image.png

    1. 此时会生成一个CreateXml.xml文件

    image.png

    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    附上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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    解析xml文档

    在上面,我们学会了如何创建xml文件. 这节学习如何解析已存在的xml文件
    解析一个xml文件,从中读取信息, 例如节点中的文字, 或某个节点的属性…

    解析流程如下:

    1. xmlReadFile函数读出一个文件指针doc;
    2. xmlDocGetRootElement函数得到根节点curNode;
    3. curNode->xmlChildrenNode就是根节点的子节点集合;
    4. 轮询子节点集合, 找到所需要的节点, 用xmlNodeGetContent取出内容;
    5. xmlHasProp查找含有某个属性的节点;
    6. 取出该节点的属性集合, 用xmlGetProp取出其属性值;
    7. xmlFreeDoc函数关闭文件指针, 并清除本文中的所有节点申请的动态内存;
    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    源码解析:

    #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;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    输出结果
    image.png

  • 相关阅读:
    【C++】list 容器最全详解(什么是list? list容器的常用接口有那些?)
    AI+边缘计算,让城市治理难题“看得见”又“管得了”
    2022-11-27 ARM- 用C语言实现stm32的三盏灯的点亮
    干货 | 移动端App自动化之App控件定位
    快速解决“msvcp110.dll丢失”问题,msvcp110.dll丢失修复分享
    linux中mkdir -p用法
    Zabbix安装与配置
    行业领先的三个企业正在利用聊天机器人变得更强
    选择排序--java(详解)
    [Unity 3d] 使用 Unity 开发无边框、可拖拽、缩放、置顶、最小化的应用
  • 原文地址:https://blog.csdn.net/m0_51451952/article/details/132856811