在Linux下使用libxml2----從安裝到使用


文章來自:http://blog.csdn.net/shanzhizi/article/details/7726679

一、下載和安裝LIBXML2
【方法一】

Libxml2是個C語言的XML程式庫,能簡單方便的提供對XML文件的各種操作,並且支持XPATH查詢,及部分的支持XSLT轉換等功能。Libxml2的下載地址是
http://xmlsoft.org/
完全版的庫是開源的,並且帶有例子程式和說明文件。由於我是在linux下用C語言進行研發的,所以我下載的是libxml2-2.6.20.tar.gz版本的源碼包。

 

具體安裝步驟:
1
、解壓:$tar zxvf libxml2-2.6.20.tar.gz
2、進入解壓后的安裝目錄:$cd libxml2-2.6.20
3、安裝三部曲:1)$./configure
              2)$make
              3)$make install
安裝完畢。

libxml2安裝中出現的錯誤:

cannot remove `libtoolT': No such file or directory

解決方法:

修改configure文件

$ vim configure

刪除這一行: $RM "$cfgfile" 

重新再運行 $ ./configure

 

你在安裝libxml2的時候,./configure  --prefix=/usr/local 
你在安裝好后,需要重新定義: 
export  LD_LIBRARY_PATH=/usr/local/lib 
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig 
具體的位置根據prefix指定的路徑定。

 

【方法二】

#sudo apt-get install libxml2

#sudo apt-get install libxml2-dev

 

【方法三】

libxml2 安裝使用 收藏

  在項目中需要對數據的保存與交換,為了方便期間使用普通文本保存。但對文本解析時,考慮到程序的健壯性就需要處理多種意外情況,比較麻煩,因此對xml產生了點興趣。xml本身網站上有大把的資料,可以隨時參考。

 

  Gnome項目提供了一個xml的C解析器:Libxml2。libxml2最新提供了xmlreader接口,使用很方便,不過版本在2.6以上才支持。今天倒騰了一了下,做了下簡單測試,在編譯自己的小程序上被拌了一下,寫文紀念。

 

  現在的最新版本是2.6.30,可以到http://ftp.gnome.org/pub/GNOME/sources/libxml2/2.6/ 下載。

 

 安裝很簡單,三部走。

 

view plaincopy to clipboardprint?

01.// install  

02.#./configure  

03.#make  

04.#make install  

05.//test install  

06.#make tests  

07.//uninstall  

08.#make uninstall 

// install

#./configure

#make

#make install

//test install

#make tests

//uninstall

#make uninstall

 

  如果不需要特別的定制,在configure階段可以直接默認。 libxml2將默認把頭文件與庫安裝在/usr/local/include/libxml2/libxml目錄下。因此可能會讓你在第一次編譯自己的程序時遇到頭文件“no such file”的錯誤(比如我自己就這么笨╮(╯▽╰)╭)。

 

  libxml2提供了解決方法,它很體貼地在/usr/local/bin目錄下為您提供了xml2-config、xmlcatalog、xmllint三個便利的工具(嘀咕下:其實一點都不便利,提前給人說下多好啊,還得害人去查資料)。其中xml2-config在編譯時用得到。

 

  xml2-config可以。。。自己看下面吧

 

view plaincopy to clipboardprint?

01.[root@Amanda ~]# xml2-config   

02.Usage: xml2-config [OPTION]  

03.Known values for OPTION are:  

04.  --prefix=DIR          change libxml prefix [default /usr/local]  

05.  --exec-prefix=DIR     change libxml exec prefix [default /usr/local]  

06.  --libs                print library linking information  

07.  --cflags              print pre-processor and compiler flags  

08.  --modules             module support enabled  

09.  --help                display this help and exit  

10.  --version             output version information 

[root@Amanda ~]# xml2-config

Usage: xml2-config [OPTION]

Known values for OPTION are:

  --prefix=DIR          change libxml prefix [default /usr/local]

  --exec-prefix=DIR     change libxml exec prefix [default /usr/local]

  --libs                print library linking information

  --cflags              print pre-processor and compiler flags

  --modules             module support enabled

  --help                display this help and exit

  --version             output version information

 

 

這里說到編譯時用到的參數: --cflags和--libs,幫助上說明這個為工程編譯時提供輔助。它們提供的信息如下:

 

view plaincopy to clipboardprint?

01.[root@Amanda ~]# xml2-config --cflags  

02.-I/usr/local/include/libxml2  

03.[root@Amanda ~]# xml2-config --libs  

04.-L/usr/local/lib -lxml2 -lz -lm 

[root@Amanda ~]# xml2-config --cflags

-I/usr/local/include/libxml2

[root@Amanda ~]# xml2-config --libs

-L/usr/local/lib -lxml2 -lz -lm

 

所以在編譯自己的測試程序時,可以直接使用 #gcc -I /usr/local/include/libxml2 -L/usr/local/lib -lxml2 -lz -lm  -o test test.c 

 

或者加入到makefile中,例如:

CFLAGS=`xml2-config --cflags`

LIBS=`xml2-config --libs`

 

測試程序:

#include <stdio.h>

#include <libxml/parser.h>

#include <libxml/tree.h>

int main(int argc, char **argv)

{

  xmlDocPtr doc = NULL;

  xmlNodePtr root_node = NULL, node = NULL, node1 = NULL;

  doc = xmlNewDoc(BAD_CAST "1.0");

  root_node = xmlNewNode(NULL, BAD_CAST "root");

  xmlDocSetRootElement(doc, root_node);

  xmlNewChild(root_node, NULL, BAD_CAST "node1",BAD_CAST "content of node1");

  node=xmlNewChild(root_node, NULL, BAD_CAST "node3",BAD_CAST"node has attributes");

  xmlNewProp(node, BAD_CAST "attribute", BAD_CAST "yes");

  node = xmlNewNode(NULL, BAD_CAST "node4");

  node1 = xmlNewText(BAD_CAST"other way to create content");

  xmlAddChild(node, node1);

  xmlAddChild(root_node, node);

  xmlSaveFormatFileEnc(argc > 1 ? argv[1] : "-", doc, "UTF-8", 1);

  xmlFreeDoc(doc);

  xmlCleanupParser();

  xmlMemoryDump();   

  return(0);

}

 

 

用下面的命令編譯通過。

 

gcc -I /usr/local/include/libxml2  -L /usr/local/lib -lxml2 test.c -o test

 

二、Libxml2中的數據類型和函數

一個函數庫中可能有幾百種數據類型及幾千個函數,不過記住大師的話,90%的功能都是由30%的內容提供的。對於libxml2(http://blog.csdn.net/shanzhizi),我認為搞懂以下的數據類型和函數就足夠了。
2.1   內部字符類型xmlChar
xmlChar是Libxml2中的字符類型,庫中所有字符、字符串都是基於這個數據類型。事實上他的定義是:xmlstring.h
typedef unsigned char xmlChar;
使用unsigned char作為內部字符格式是考慮到他能非常好適應UTF-8編碼,而UTF-8編碼正是libxml2的內部編碼,其他格式的編碼要轉換為這個編碼才能在libxml2中使用。
還經常能看到使用xmlChar*作為字符串類型,非常多函數會返回一個動態分配內存的xmlChar*變量,使用這樣的函數時記得要手動刪除內存。
2.2   xmlChar相關函數
如同標准c中的char類型相同,xmlChar也有動態內存分配、字符串操作等相關函數。例如xmlMalloc是動態分配內存的函數;xmlFree是配套的釋放內存函數;xmlStrcmp是字符串比較函數等等。
基本上xmlChar字符串相關函數都在xmlstring.h中定義;而動態內存分配函數在xmlmemory.h中定義。
2.3   xmlChar*和其他類型之間的轉換
另外要注意,因為總是要在xmlChar*和char*之間進行類型轉換,所以定義了一個宏BAD_CAST,其定義如下:xmlstring.h
#define BAD_CAST (xmlChar *)
原則上來說,unsigned char和char之間進行強制類型轉換是沒有問題的。
2.4   文件類型xmlDoc、指針xmlDocPtr
xmlDoc是個struct,保存了一個xml的相關信息,例如文件名、文件類型、子節點等等;xmlDocPtr等於xmlDoc*,他搞成這個樣子總讓人以為是智能指針,其實不是,要手動刪除的。
xmlNewDoc函數創建一個新的文件指針。
xmlParseFile函數以默認方式讀入一個UTF-8格式的文件,並返回文件指針。
xmlReadFile函數讀入一個帶有某種編碼的xml文件,並返回文件指針;細節見libxml2參考手冊。
xmlFreeDoc釋放文件指針。特別注意,當你調用xmlFreeDoc時,該文件所有包含的節點內存都被釋放,所以一般來說不必手動調用xmlFreeNode或xmlFreeNodeList來釋放動態分配的節點內存,除非你把該節點從文件中移除了。一般來說,一個文件中所有節點都應該動態分配,然后加入文件,最后調用xmlFreeDoc一次釋放所有節點申請的動態內存,這也是為什么我們非常少看見xmlNodeFree的原因。
xmlSaveFile將文件以默認方式存入一個文件。
xmlSaveFormatFileEnc可將文件以某種編碼/格式存入一個文件中。
2.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能組織為樹。同時更有以下重要元素:
l         節點中的文字內容:content;
l         節點所屬文件:doc;
l         節點名字:name;
l         節點的namespace:ns;
l         節點屬性列表:properties;
Xml文件的操作其根本原理就是在節點之間移動、查詢節點的各項信息,並進行增加、刪除、修改的操作。
xmlDocSetRootElement函數能將一個節點設置為某個文件的根節點,這是將文件和節點連接起來的重要手段,當有了根結點以后,所有子節點就能依次連接上根節點,從而組織成為一個xml樹。
2.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來表示一個節點集合更好,而且沒有內存泄漏或溢出的擔憂。
3.       簡單xml操作例子
了解以上基本知識之后,就能進行一些簡單的xml操作了。當然,還沒有涉及到內碼轉換(使得xml中能處理中文)、xpath等較復雜的操作。
3.1   創建xml文件
有了上面的基礎,創建一個xml文件顯得非常簡單,其流程如下:
l         用xmlNewDoc函數創建一個文件指針doc;
l         用xmlNewNode函數創建一個節點指針root_node;
l         用xmlDocSetRootElement將root_node設置為doc的根結點;
l         給root_node添加一系列的子節點,並設置子節點的內容和屬性;
l         用xmlSaveFile將xml文件存入文件;
l         用xmlFreeDoc函數關閉文件指針,並清除本文件中所有節點動態申請的內存。
注意,有多種方式能添加子節點:第一是用xmlNewTextChild直接添加一個文本子節點;第二是先創建新節點,然后用xmlAddChild將新節點加入上層節點。
原始碼文件是CreateXmlFile.cpp,如下:
/********************************************************************
    created:   2007/11/09
    created:   9:11:2007   15:34
    filename: CreateXmlFile.cpp
    author:       Wang xuebin 
    depend:       libxml2.lib 
    build:     nmake TARGET_NAME=CreateXmlFile
    purpose:   創建一個xml文件
*********************************************************************/
#include 
#include 
#include 
#include 
int main()
{
    //定義文件和節點指針
    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"));
    //存儲xml文件
    int nRel = xmlSaveFile("CreatedXml.xml",doc);
    if (nRel != -1)
    {
       cout一個xml文件被創建,寫入"個字節"    }
    //釋放文件內節點動態申請的內存
    xmlFreeDoc(doc);
    return 1;
}
編譯鏈接命令如下:
nmake TARGET_NAME=CreateXmlFile
然后執行可執行文件CreateXmlFile.exe,會生成一個xml文件CreatedXml.xml,打開后如下所示:
root>
    newNode1>newNode1 contentnewNode1>
    newNode2>newNode2 contentnewNode2>
    newNode3>newNode3 contentnewNode3>
    node2 attribute="yes">NODE CONTENTnode2>
    son>
       grandson>This is a grandson nodegrandson>
    son>
root>
最佳使用類似XMLSPY這樣的工具打開,因為這些工具能自動整理xml文件的柵格,否則非常有可能是沒有所有換行的一個xml文件,可讀性較差。
3.2   解析xml文件
解析一個xml文件,從中取出想要的信息,例如節點中包含的文字,或某個節點的屬性,其流程如下:
l         用xmlReadFile函數讀出一個文件指針doc;
l         用xmlDocGetRootElement函數得到根節點curNode;
l         curNode->xmlChildrenNode就是根節點的子節點集合;
l         輪詢子節點集合,找到所需的節點,用xmlNodeGetContent取出其內容;
l         用xmlHasProp查找含有某個屬性的節點;
l         取出該節點的屬性集合,用xmlGetProp取出其屬性值;
l         用xmlFreeDoc函數關閉文件指針,並清除本文件中所有節點動態申請的內存。
注意:節點列表的指針依然是xmlNodePtr,屬性列表的指針也是xmlAttrPtr,並沒有xmlNodeList或xmlAttrList這樣的類型。看作列表的時候使用他們的next和prev鏈表指針來進行輪詢。只有在Xpath中有xmlNodeSet這種類型,其使用方法前面已介紹了。
原始碼如下:ParseXmlFile.cpp
/********************************************************************
    created:   2007/11/15
    created:   15:11:2007   11:47
    filename: ParseXmlFile.cpp
    author:       Wang xuebin 
    depend:       libxml2.lib
    build:     nmake TARGET_NAME=ParseXmlFile
    purpose:   解析xml文件
*********************************************************************/
#include 
#include 
int main(int argc, char* argv[])
{
    xmlDocPtr doc;           //定義解析文件指針
    xmlNodePtr curNode;      //定義結點指針(你需要他為了在各個結點間移動) 
    xmlChar *szKey;          //臨時字符串變量
    char *szDocName;
    if (argc     {
       printf("Usage: %s docname"n", argv[0]);
       return(0);
    }
    szDocName = argv[1];
    doc = xmlReadFile(szDocName,"GB2312",XML_PARSE_RECOVER); //解析文件
    //檢查解析文件是否成功,如果不成功,libxml將指一個注冊的錯誤並停止。
    //一個常見錯誤是不適當的編碼。XML標准文件除了用UTF-8或UTF-16外還可用其他編碼保存。
    //如果文件是這樣,libxml將自動地為你轉換到UTF-8。更多關於XML編碼信息包含在XML標准中.
    if (NULL == doc) 
    {  
       fprintf(stderr,"Document not parsed successfully. "n");     
       return -1; 
    } 
    curNode = xmlDocGetRootElement(doc); //確定文件根元素
    /*檢查確認當前文件中包含內容*/ 
    if (NULL == curNode)
    { 
       fprintf(stderr,"empty document"n"); 
       xmlFreeDoc(doc); 
       return -1; 
    } 
    /*在這個例子中,我們需要確認文件是正確的類型。“root”是在這個示例中使用文件的根類型。*/
    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");
           cout           xmlFree(szAttr);
       }
       attrPtr = attrPtr->next;
    }
    xmlFreeDoc(doc);
    return 0;
}
編譯鏈接命令如下:
nmake TARGET_NAME=ParseXmlFile
執行命令如下,使用第一次創建的xml文件作為輸入:
ParseXmlFile.exe CreatedXml.xml
觀察原始碼可發現,所有以查詢方式得到的xmlChar*字符串都必須使用xmlFree函數手動釋放。否則會造成內存泄漏。
3.3   修改xml文件
有了上面的基礎,修改xml文件的內容就非常簡單了。首先打開一個已存在的xml文件,順着根結點找到需要添加、刪除、修改的地方,調用相應的xml函數對節點進行增、刪、改操作。原始碼見ChangeXmlFile,編譯鏈接方法如上。執行下面的命令:
ChangeXmlFile.exe CreatedXml.xml
能得到一個修改后的xml文件ChangedXml.xml,如下:
root>
    newNode2>content changednewNode2>
    newNode3 newAttr="YES">newNode3 contentnewNode3>
    node2 attribute="no">NODE CONTENTnode2>
    son>
       grandson>This is a grandson nodegrandson>
       newGrandSon>new contentnewGrandSon>
    son>
root>
需要注意的是,並沒有xmlDelNode或xmlRemoveNode函數,我們刪除節點使用的是以下一段代碼:
       if (!xmlStrcmp(curNode->name, BAD_CAST "newNode1"))
       {
           xmlNodePtr tempNode;
           tempNode = curNode->next;
           xmlUnlinkNode(curNode);
           xmlFreeNode(curNode);
           curNode = tempNode;
           continue;
       }
即將當前節點從文件中斷鏈(unlink),這樣本文件就不會再包含這個子節點。這樣做需要使用一個臨時變量來存儲斷鏈節點的后續節點,並記得要手動刪除斷鏈節點的內存。
3.4   使用XPATH查找xml文件
簡而言之,XPATH之於xml,好比SQL之於關系數據庫。要在一個復雜的xml文件中查找所需的信息,XPATH簡直是必不可少的工具。XPATH語法簡單易學,並且有一個非常好的官方教程,見
http://www.zvon.org/xxl/XPathTutorial/Output_chi/introduction.html
。這個站點的XML各種教程齊全,並且有包括中文在內的各國語言版本,真是讓我喜歡到非常!
使用XPATH之前,必須首先熟悉幾個數據類型和函數,他們是使用XPATH的前提。在libxml2中使用Xpath是非常簡單的,其流程如下:
l         定義一個XPATH上下文指針xmlXPathContextPtr context,並且使用xmlXPathNewContext函數來初始化這個指針;
l         定義一個XPATH對象指針xmlXPathObjectPtr result,並且使用xmlXPathEvalExpression函數來計算Xpath表達式,得到查詢結果,將結果存入對象指針中;
l         使用result->nodesetval得到節點集合指針,其中包含了所有符合Xpath查詢結果的節點;
l         使用xmlXPathFreeContext釋放上下文指針;
l         使用xmlXPathFreeObject釋放Xpath對象指針;
具體的使用方法能看XpathForXmlFile.cpp的這一段代碼,其功能是查找符合某個Xpath語句的對象指針:
xmlXPathObjectPtr getNodeSet(xmlDocPtr doc, const xmlChar *szXpath) 
{
    xmlXPathContextPtr context;    //XPATH上下文指針
    xmlXPathObjectPtr result;       //XPATH對象指針,用來存儲查詢結果
    context = xmlXPathNewContext(doc);     //創建一個XPath上下文指針
    if (context == NULL) 
    {   
       printf("context is NULL"n");
       return NULL; 
    }
    result = xmlXPathEvalExpression(szXpath, context); //查詢XPath表達式,得到一個查詢結果
    xmlXPathFreeContext(context);             //釋放上下文指針
    if (result == NULL) 
    {
       printf("xmlXPathEvalExpression return NULL"n"); 
       return NULL; 
    }
    if (xmlXPathNodeSetIsEmpty(result->nodesetval))   //檢查查詢結果是否為空
    {
       xmlXPathFreeObject(result);
       printf("nodeset is empty"n");
       return NULL;
    }
    return result;    
}
一個完整的使用Xpath的例子在代碼XpathForXmlFile.cpp中,他查找一個xml文件中符合"/root/node2[@attribute=’yes’]"語句的結果,並且將找到的節點的屬性和內容打印出來。編譯鏈接命令如下:
nmake TARGET_NAME=XpathForXmlFile
執行方式如下:
XpathForXmlFile.exe CreatedXml.xml
觀察結果能看出找到了一個節點,即root下面node2節點,他的attribute屬性值正好等於yes。更多關於Xpath的內容能參考XPATH官方手冊。只有掌控了XPATH,才掌控了使用大型XML文件的方法,否則每尋找一個節點都要從根節點找起,會把人累死。
4.       用ICONV解決XML中的中文問題
Libxml2中默認的內碼是UTF-8,所有使用libxml2進行處理的xml文件,必須首先顯式或默認的轉換為UTF-8編碼才能被處理。
要在xml中使用中文,就必須能夠在UTF-8和GB2312內碼(較常用的一種簡體中文編碼)之間進行轉換。Libxml2提供了默認的內碼轉換機制,並且在libxml2的Tutorial中有一個例子,事實證實這個例子並不適合用來轉換中文。
所以需要我們顯式的使用ICONV來進行內碼轉換,libxml2本身也是使用ICONV進行轉換的。ICONV是個專門用來進行編碼轉換的庫,基本上支持目前所有常用的編碼。他是glibc庫的一個部分,常常被用於UNIX系統中。當然,在windows下面使用也沒有所有問題。前面已提到了ICONV的安裝和使用方法,這里主要講一下編程相關問題。
本節其實和xml及libxml2沒有太大關系,你能把他簡單看作是個編碼轉換方面的專題。我們僅僅需要學會使用兩個函數就能了,即從UTF-8轉換到GB2312的函數u2g,及反向轉換的函數g2u,原始碼在wxb_codeConv.c中:
/********************************************************************
    created:   2007/11/15
    created:   15:11:2007   10:30
    filename: wxb_codeConv.c
    author:       Wang xuebin 
    depend:       iconv.lib
    build:     不必build,被包含到其他原始碼中
    purpose:   提供從UTF-8到GB2312的內碼轉換,及反向的轉換
*********************************************************************/
#include "iconv.h"
#include 
//代碼轉換:從一種編碼轉為另一種編碼   
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;
}   
使用的時候將這個c文件include到其他源文件中。include一個c文件並不奇怪,在c語言的年代我們常常這么干,唯一的害處的編譯鏈接出來的可執行程式體積變大了。當然這時因為我們這段代碼非常小的原因,再大一點我就要用dll了。
從UTF-8到GB2312的一個典型使用流程如下:
l         得到一個UTF-8的字符串szSrc;
l         定義一個char*的字符指針szDes,並不必給他動態審判內存;
l         szDes = u2g(szSrc),這樣就能得到轉換后的GB2312編碼的字符串;
l         使用完這個字符串后使用free(szDes)來釋放內存。
本文並不准備講述iconv中的函數細節,因為那幾個函數及數據類型都非常簡單,我們還是重點看一下怎么在libxml2中使用編碼轉換來處理帶有中文的xml文件。下面是使用以上方法來創建一個帶有中文的XML文件的例子程式CreateXmlFile_cn.cpp,原始碼如下:
/********************************************************************
    created:   2007/11/17
    created:   9:11:2007   15:34
    filename: CreateXmlFile.cpp
    author:       Wang xuebin 
    depend:       libxml2.lib iconv.lib
    build:     nmake TARGET_NAME=CreateXmlFile_cn
    purpose:   創建一個xml文件,其中包含中文
*********************************************************************/
#include 
#include 
#include 
#include 
#include "wxb_codeConv.c" //自己寫的編碼轉換函數
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);
    //一個中文字符串轉換為UTF-8字符串,然后寫入
    char* szOut = g2u("節點1的內容");
    //在根節點中直接創建節點
    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");
    xmlNewChild(root_node, NULL, BAD_CAST "node1",BAD_CAST szOut);
    free(szOut);
    //創建一個節點,設置其內容和屬性,然后加入根結點
    xmlNodePtr node = xmlNewNode(NULL,BAD_CAST"node2");
    xmlNodePtr content = xmlNewText(BAD_CAST"NODE CONTENT");
    xmlAddChild(root_node,node);
    xmlAddChild(node,content);
    szOut = g2u("屬性值");
    xmlNewProp(node,BAD_CAST"attribute",BAD_CAST szOut);
    free(szOut);
    //創建一個中文節點
    szOut = g2u("中文節點");
    xmlNewChild(root_node, NULL, BAD_CAST szOut,BAD_CAST "content of chinese node");
    free(szOut);
    //存儲xml文件
    int nRel = xmlSaveFormatFileEnc("CreatedXml_cn.xml",doc,"GB2312",1);
    if (nRel != -1)
    {
       cout一個xml文件被創建,寫入"個字節"    }
    xmlFreeDoc(doc);
    return 1;
}
編譯鏈接命令如下:
nmake TARGET_NAME=CreateXmlFile_cn
完成后執行CreateXmlFile_cn.exe能生成一個xml文件CreatedXml_cn.xml,其內容如下:
root>
    newNode1>newNode1 contentnewNode1>
    newNode2>newNode2 contentnewNode2>
    newNode3>newNode3 contentnewNode3>
    node1>節點1的內容node1>
    node2 attribute="屬性值">NODE CONTENTnode2>
    中文節點>content of chinese node中文節點>
root>
觀察可知,節點的名稱、內容、屬性都能使用中文了。在解析、修改和查找XML文件時都能使用上面的方法,只要記住,進入xml文件之前將中文編碼轉換為UTF-8編碼;從XML中取出數據時,不管三七二十一都能轉換為GB2312再用,否則你非常有可能見到傳說中的亂碼!
5.       用XML來做點什么
有了以上的基礎,相信已能順利的在c/c++程式中使用XML文件了。那么,我們到底要用XML來做什么呢?我隨便說一說自己的想法:
第一,能用來作為設置文件。例如非常多組件就是用XML來做設置文件;當然,我們知道用INI做設置文件更簡單,只要熟悉兩個函數就能了;不過,復雜一點的設置文件我還是建議采用XML;
第二,能用來作為在程式之間傳送數據的格式,這樣的話最佳給你的xml先定義一個XML Schema,這樣的數據首先能做一個良構校驗,還能來一個Schema校驗,如此的話出錯率會比沒有格式的數據小得多。目前XML已廣泛作為網絡之間的數據格式了;
第三,能用來作為你自定義的數據存儲格式,例如對象持久化之類的功能;
最后,能用來顯示你的技術非常高深,本來你要存儲一個1,結果你這樣存儲了:
root>
    My_Program_Code content="1">My_Program_Code>
root>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM