XML的建立、解析-C語言

  前言:今天在作一個小項目時,客戶要求的xml,跟如今有系統要求的不同,因此要本身從新寫函數支持返回,進行簡單總結,但願對你們有所幫助。node

  首先,使用xml函數須要鏈上動態庫libxml2,須要在電腦上安裝libxml的開發包,安裝方法以下:shell

      Ubuntu系統: sudo apt-get install libxml2-dev
      CentOS系統:yum install libxml2-devel  

1.   建立XML文檔

  (1)相關函數有許多,網上也有特別多的解釋,你們能夠百度一下,這裏只是簡單介紹一部分;建立一個XML文檔很是簡單,其流程以下:函數

        ①    用xmlNewDoc函數建立一個文檔指針doc。優化

        ②    用xmlNewNode函數建立一個節點指針root_node。編碼

        ③    用xmlDocSetRootElement將root_node設置爲doc的根結點。spa

        ④    給root_node添加一系列的子節點,並設置子節點的內容和屬性。命令行

        ⑤    用xmlSaveFile將XML文檔存入文件(用xmlDocDumpFormatMemoryEnc將XML存入內存)。指針

        ⑥    用xmlFreeDoc關閉文檔指針,並清除本文檔中全部節點動態申請的內存。code

     有多種方式能夠添加子節點,如能夠用xmlNewTextChild直接添加一個文本子節點。也能夠先建立新節點,而後用xmlAddChild將新節點加入到上層節點中。orm

  注:xmlSaveFile存入文件方便單獨執行程序查看結果,通常項目用用xmlDocDumpFormatMemoryEnc將XML存入內存!

   (2)建立xml文件舉例 

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

int main()
{    
    xmlChar *result = NULL;
    int size = 0;
    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(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 a grandson node"));
    xmlNodePtr congson = xmlNewNode(NULL, BAD_CAST "congson");
    xmlAddChild(grandson,congson);
    
    //存儲xml文檔
    //xmlKeepBlanksDefault(0);
    //xmlDocDumpFormatMemoryEnc(doc, &result, &size, "UTF-8", 1);

    int nRel = xmlSaveFile("CreateXml.xml",doc);
    if (nRel != -1)
    {
        printf("一個xml文檔被建立,寫入%d個字節\n", nRel);
    }
    //釋放文檔內節點動態申請的內存
    xmlFreeDoc(doc);
    return 1;
}

  CentOS系統下面執行:gcc CreateXmlFile.c -o CreateXmlFile -I /usr/include/libxml2 -lxml2

    執行./CreateXmlFile,會生成一個XML文件CreatedXml.xml。

 2.   解析XML文檔

 (1)XML解析流程

    解析一個XML文檔,從中取出想要的信息,例如節點中包含的文字,或者某個節點的屬性。其流程以下:

        ①    用xmlReadFile函數讀入一個文件,並返回一個文檔指針doc。

        ②    用xmlDocGetRootElement函數獲得根節點curNode。

        ③    此時curNode->xmlChildrenNode就是根節點的首個兒子節點,該兒子節點的兄弟節點可用next指針進行輪詢。

        ④    輪詢全部子節點,找到所需的節點,用xmlNodeGetContent取出其內容。

        ⑤    用xmlHasProp查找含有某個屬性的節點,屬性列表指針xmlAttrPtr將指向該節點的屬性列表。

        ⑥    取出該節點的屬性,用xmlGetProp取出其屬性值。

  ⑦    xmlFreeDoc函數關閉文檔指針,並清除本文檔中全部節點動態申請的內存。

    (2). 解析XML文件並獲取屬性示例 

#include <stdio.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
 
int main(int argc, char* argv[])
{
    xmlDocPtr doc;           //定義解析文件指針
    xmlNodePtr curNode;      //定義結點指針
    xmlChar *szKey;          //臨時字符串變量
    char *szDocName;
    if (argc <= 1) {
        printf("Usage: %s docname", argv[0]);
        return(0);
    }
    szDocName = argv[1];
    doc = xmlReadFile(szDocName,"GB2312",XML_PARSE_RECOVER);
    //解析文件
    //檢查解析文檔是否成功,若是不成功,libxml將報錯並中止解析。
    //一個常見錯誤是不適當的編碼,XML標準文檔除了用UTF-8或UTF-16外還可用其它編碼保存
    if (NULL == doc) {
        fprintf(stderr,"Document not parsed successfully.");
        return -1;
    }
    //獲取根節點
    curNode = xmlDocGetRootElement(doc);
    if (NULL == curNode) {
        fprintf(stderr,"empty document");
        xmlFreeDoc(doc);
        return -1;
    }
    //確認根元素名字是否符合
    if (xmlStrcmp(curNode->name, BAD_CAST "root")) {
        fprintf(stderr,"document of the wrong type, root node != root");
        xmlFreeDoc(doc);
        return -1;
    }
    curNode = curNode->xmlChildrenNode;
    xmlNodePtr propNodePtr = curNode;
    while(curNode != NULL) {
        //取出節點中的內容
        if ((!xmlStrcmp(curNode->name, (const xmlChar *) "newNode1"))) {
            szKey = xmlNodeGetContent(curNode);
            printf("newNode1: %s\n", szKey);
            xmlFree(szKey);
        }
        //查找帶有屬性attribute的節點
        if (xmlHasProp(curNode,BAD_CAST "attribute")) {
            propNodePtr = curNode;
        }
        curNode = curNode->next;
    }
 
    //查找屬性
    xmlAttrPtr attrPtr = propNodePtr->properties;
    while (attrPtr != NULL) {
        if (!xmlStrcmp(attrPtr->name, BAD_CAST "attribute")) {
            xmlChar* szAttr = xmlGetProp(propNodePtr,BAD_CAST "attribute");
            printf("get attribute=%s\n", szAttr) ;
            xmlFree(szAttr);
        }
        attrPtr = attrPtr->next;
    }
    xmlFreeDoc(doc);
    return 0;
}

   編譯:gcc ParseXmlFile.c -o ParseXmlFile -I /usr/include/libxml2  -lxml2

   執行:./ParseXmlFile  xxx.xml 

三、用iconv解決XML中字符集問題

    libxml2中默認的內碼是UTF-8,全部使用libxml2進行處理的xml文件,必須首先顯式或者默認轉換爲UTF-8編碼才能被處理。

要在XML中使用中文,就必須可以在UTF-8和GB2312之間進行轉換。libxml2提供了默認的內碼轉換機制,而且在libxml2的Tutorial中有一個例子,事實證實這個例子並不很適合用來轉換中文。

    有些場合須要使用iconv來進行編碼轉換,libxml2自己也是使用iconv進行編碼轉換的。iconv是一個專門用來進行編碼轉換的庫,基本上支持目前全部經常使用的編碼,它是glibc庫的一個部分。

     本節其實和libxml沒有太大關係,能夠把它簡單看做是一個編碼轉換方面的專題。下文提供了一個通用轉碼函數,並在此基礎上實現了兩個轉碼封裝函數,即從UTF-8轉換到GB2312的函數u2g,以及反向轉換的函數g2u。其代碼以下:    

  

#include <iconv.h>
#include <string.h>
 
//代碼轉換,從一種編碼轉爲另外一種編碼  
int code_convert(char* from_charset, char* to_charset, char* inbuf,
               int inlen, char* outbuf, int outlen)
{
    iconv_t cd;
    char **pin = &inbuf;   
    char **pout = &outbuf;
    cd = iconv_open(to_charset,from_charset);  
    if(cd == 0) { return -1; }
    memset(outbuf,0,outlen);  
    if(iconv(cd,(const char **)pin, (unsigned int *)&inlen, pout, (unsigned int*)&outlen) == -1) {
        return -1;      
    }
       
    iconv_close(cd);
    return 0;  
}
 
//UNICODE碼轉爲GB2312碼  
//成功則返回一個動態分配的char*變量,須要在使用完畢後手動free,失敗返回NULL
char* u2g(char *inbuf)  
{
    int nOutLen = 2 * strlen(inbuf) - 1;
    char *szOut = (char*)malloc(nOutLen);
    if (-1 == code_convert("utf-8", "gb2312", inbuf, strlen(inbuf), szOut, nOutLen)) {
       free(szOut);
       szOut = NULL;
    }
    return szOut;
}  
 
//GB2312碼轉爲UNICODE碼  
//成功則返回一個動態分配的char*變量,須要在使用完畢後手動free,失敗返回NULL
char* g2u(char *inbuf)  
{
    int nOutLen = 2 * strlen(inbuf) - 1;
    char *szOut = (char *)malloc(nOutLen);
    if (-1 == code_convert("gb2312", "utf-8", inbuf, strlen(inbuf), szOut, nOutLen)) {
       free(szOut);
       szOut = NULL;
    }
    return szOut;
}

 下面以UTF-8到GB2312轉碼流程說明上文中轉碼函數的使用,使用流程以下:

    ①    獲得一個UTF-8的字符串szSrc。

    ②    定義一個char *的字符指針szDes,並不須要給它動態申請內存。

    ③    調用szDes = u2g(szSrc),這樣szDes就指向轉換後GB2312編碼的字符串。

    ④    使用完這個字符串後使用free(szDes)來釋放內存。

    Linux系統下有個iconv命令,能夠在shell命令行輸入iconv  --help 來查看用法,在程序中能夠採用system系統調用來進行文件轉碼。下文中f表示from,t表示to,其轉碼方法以下:

    system("iconv –f 源格式 –t 目標格式 源文件 >目標文件")

    system("iconv –f  GB18030 –t UTF-8  test_gb.txt > test_utf.txt")

 

    shell命令行使用方法: iconv -f gb2312 -t utf-8 readme.txt -o readme.txt

  總結:會發現建立xml整個流程都很長,不適合放在函數中!公司去華爲的一個大神,先用雙向鏈表建立再返回,封裝一個函數,這樣就很方便了,我也在繼續優化中...

相關文章
相關標籤/搜索