Linux下使用libxml庫解析xml檔案

2020-08-12 21:44:58

目錄

 

libxml簡介

libxml庫安裝

libxml2中的數據型別和函數

xml文件解析範例

執行結果


  • libxml簡介

 libxml是一個用於解析xml檔案的庫,在各個平臺下都能使用,也支援多種語言,如c,Python等。

  • libxml庫安裝

  1. 直接使用shell命令安裝
    sudo apt-get install libxml2 
    
    sudo apt-get install libxml2-dev

     

  2. 使用原始碼安裝

先在官網下載原始碼,然後執行原始碼安裝三部曲

GitHub原始碼鏈接

1)$./configure
              

2)$make

過程可能稍微有點漫長。耐心等待一下。


3)$sudo make install

  • libxml2中的數據型別和函數

一個函數庫中可能有幾百種數據型別及幾千個函數,不過記住大師的話,90%的功能都是由30%的內容提供的。對於libxml2搞懂以下的數據型別和函數就足以了。

1.1   內部字元型別xmlChar

xmlChar是Libxml2中的字元型別,庫中所有字元、字串都是基於這個數據型別。事實上他的定義是:xmlstring.h

typedef unsigned char xmlChar

使用unsigned char作爲內部字元格式是考慮到他能非常好適應UTF-8編碼,而UTF-8編碼正是libxml2的內部編碼,其他格式的編碼要轉換爲這個編碼才能 纔能在libxml2中使用。還經常能看到使用xmlChar*作爲字串型別,非常多函數會返回一個動態分配記憶體的xmlChar*變數,使用這樣的函數時記得要手動刪除記憶體。

1.2   xmlChar相關函數

如同標準c中的char型別相同,xmlChar也有動態記憶體分配、字串操作等相關函數。例如xmlMalloc是動態分配記憶體的函數;xmlFree是配套的釋放記憶體函數;xmlStrcmp是字串比較函數等等。

基本上xmlChar字串相關函數都在xmlstring.h中定義;而動態記憶體分配函數在xmlmemory.h中定義。

1.3   xmlChar*和其他型別之間的轉換

另外要注意,因爲總是要在xmlChar*和char*之間進行型別轉換,所以定義了一個宏BAD_CAST,其定義如下:xmlstring.h

#define BAD_CAST (xmlChar *)

原則上來說,unsigned char和char之間進行強制型別轉換是沒有問題的。

1.4   檔案型別xmlDoc、指針xmlDocPtr

xmlDoc是個struct,儲存了一個xml的相關資訊,例如檔名、檔案型別、子節點等等;xmlDocPtr等於xmlDoc*,他搞成這個樣子總讓人以爲是智慧指針,其實不是,要手動刪除的。

xmlNewDoc()函數建立一個新的檔案指針。

xmlParseFile()函數以預設方式讀入一個UTF-8格式的檔案,並返迴檔案指針。

xmlReadFile()函數讀入一個帶有某種編碼的xml檔案,並返迴檔案指針;細節見libxml2參考手冊。

xmlFreeDoc()釋放檔案指針。特別注意,當你呼叫xmlFreeDoc時,該檔案所有包含的節點記憶體都被釋放,所以一般來說不必手動

xmlFreeNode或xmlFreeNodeList來釋放動態分配的節點記憶體,除非你把該節點從檔案中移除了。一般來說,一個檔案中所有節點都應該動態分配,然後加入檔案,最後呼叫xmlFreeDoc一次釋放所有節點申請的動態記憶體,這也是爲什麼我們非常少看見xmlNodeFree的原因。

xmlSaveFile()將檔案以預設方式存入一個檔案。

xmlSaveFormatFileEnc()可將檔案以某種編碼/格式存入一個檔案中。

1.5   節點型別xmlNode、指針xmlNodePtr

節點應該是xml中最重要的元素了,xmlNode代表了xml檔案中的一個節點,實現爲一個struct,內容非常豐富: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能組織爲樹。同時更有以下重要元素:

節點中的文字內容:content;

節點所屬檔案:doc;       

節點名字:name;       

節點的namespace:ns;     

節點屬性列表:properties;

Xml檔案的操作其根本原理就是在節點之間移動、查詢節點的各項資訊,並進行增加、刪除、修改的操作。

xmlDocSetRootElement函數能將一個節點設定爲某個檔案的根節點,這是將檔案和節點連線起來的重要手段,當有了根結點以後,所有子節點就能依次連線上根節點,從而組織成爲一個xml樹。

1.6   節點集合型別xmlNodeSet、指針xmlNodeSetPtr

節點集合代表一個由節點組成的變數,節點集合只作爲Xpath的查詢結果而出現(XPATH的介紹見後面),因此被定義在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 @@ */
};

能看出,節點集合有三個成員,分別是節點集合的節點數、最大可容納的節點數,及節點陣列頭指針。對節點集合中各個節點的存取方式非常簡單,如下:

xmlNodeSetPtr nodeset = XPATH  //查詢結果;
for (int i = 0; i nodeNr; i++) 
{
nodeset->nodeTab;
}

注意:

libxml2是個c函數庫,因此其函數和數據型別都使用c語言的方式來處理。如果是c++,我想我寧願用STL中的vector來表示一個節點集合更好,而且沒有記憶體漏失或溢位的擔憂。

  • xml文件解析範例

/*********************************************************************************
 *      Copyright:  ysn
 *
 *       Filename:  query_apn.c
 *    Description:  This file is used query APN by MCC/MNC in etc/apns-full-conf.xml,
 *                  which is download from google Android:
 *      https://android.googlesource.com/device/sample/+/master/etc/apns-full-conf.xml  
 *                 
 *        Version:  1.0.0(2020年07月25日)
 *         Author:  Tian Jincheng <[email protected]>
 *      ChangeLog:  1, Release initial version on "2020年08月12日 20時04分38秒"
 *                 
 ********************************************************************************/

#include <libxml2/libxml/xmlmemory.h>
#include <libxml2/libxml/parser.h>
#include <libxml/xmlversion.h>
#include <string.h>
#include "query_apn.h"

#define   FILE_NAME  "/home/pi/program/etc/apns-full-conf.xml"
int parse_apn(const char *file_name, apn_info_t *apn_info);
int main (int argc, char **argv)
{
    apn_info_t apn_info;
    parse_apn(FILE_NAME, &apn_info);
    return 0;
} /* ----- End of main() ----- */

int parse_apn(const char *file_name, apn_info_t *apn_info)
{
    xmlDocPtr doc;        /* xml document tree */
    xmlNodePtr cur;       /* xml node */
    xmlChar *mcc;         /* Operator MCC */
    xmlChar *mnc;         /* Operator MNC */
    xmlChar *apn;         /* pppd APN */
    xmlChar *uid;         /* APN user ID */
    xmlChar *pwd;         /* APN password */
    xmlChar *version;     /* APN list version */
    char    *qmcc = "460";
    char    *qmnc = "03";
    int      found = 0;

    if( !file_name || !apn_info )
    {
        printf("Invalid input arguments\n");
    }

    doc = xmlReadFile(file_name, "UTF_8", XML_PARSE_RECOVER);  //先讀入需要解析的xml檔案
    if (doc == NULL)
    {
        fprintf(stderr, "Failed to parse xml file:%s\n", file_name);
        goto FAILED;
    }
    
    cur = xmlDocGetRootElement(doc);  //獲取根節點
    if (cur == NULL)
    {
        fprintf(stderr, "Root is empty.\n");
        goto FAILED;
    }

    if ( xmlStrcmp(cur->name, (const xmlChar *)"apns") )
    {
        fprintf(stderr, "The root is not apns.\n");
        goto FAILED;
    }

    if (xmlHasProp(cur, "version"))
    {
        version = xmlGetProp(cur, "version");
        printf("version: %s\n", version);

    }
    cur = cur->xmlChildrenNode;
    while (cur != NULL)
    {
        if ( !xmlStrcmp(cur->name, (const xmlChar *)"apn"))
        {
            mcc = xmlGetProp(cur, "mcc");
            mnc = xmlGetProp(cur, "mnc");

            if( !xmlStrcmp(mcc, (const xmlChar *)qmcc) && !xmlStrcmp(mnc, (const xmlChar *)qmnc))
            {
                apn = xmlGetProp(cur, "apn");
                uid = xmlGetProp(cur, "user");
                pwd = xmlGetProp(cur, "password");
                printf("mcc:%s mnc:%s apn:%s uid:%s pwd:%s\n",mcc, mnc, apn, uid, pwd);
                if( !xmlStrstr(apn, "wap") )
                {
                    found = 1;
                }
            }
        }
        cur = cur->next;
    }
FAILED:
    if (doc)
    {
        xmlFreeDoc(doc);
    }

    return found;
}


  • 執行結果