libxml -- 解析 XML 文档
參考
XML
介紹:XML 和 DOM
XML是eXtensible Markup Language的縮寫,它是一種可擴展性標識語言, 能夠讓 你自己創造標識,標識你所表示的內容。DOM全稱是Document Object Model(文檔 對象模型),定義了一組與平臺和語言無關的接口,以便程序和腳本能夠動態訪問 和修改XML文檔內容、結構及樣式。XML創建了標識,而 DOM的作用就是告訴程序 如何操作和顯示這些標識。
XML將數據組織成為一棵樹,DOM通過解析XML文檔,為XML文檔在邏輯上建立一個 樹模型,樹的節點是一個個的對象。這樣通過操作這棵樹和這些對象就可以完成 對XML文檔的操作,為處理文檔的所有方面提供了一個完美的概念性框架。
XML 中共有12種節點類型,其中最常見的節點類型有5種:
元素libxml
介紹
本文所介紹的 libxml 是針對 C 語言的一套 API 接口。其他如 ruby,python 亦有對應的基于 libxml 開發的綁定庫接口。
數據類型 — xmlChar
在 libXml 中用 xmlChar 替代 char , XML 使用 UTF-8 編碼的一字節字符串。如 果你的數據使用其它編碼,它必須被轉換到 UTF-8 才能使用libxml的函數。
如同標準 C 中的 char 類型一樣, xmlChar 也有動態內存分配、字符串操作等 相關函數。例如 xmlMalloc 是動態分配內存的函數; xmlFree 是配套的釋放內 存函數; xmlStrcmp 是字符串比較函數等等。基本上 xmlChar 字符串相關函數 都在xmlstring.h 中定義;而動態內存分配函數在 xmlmemory.h 中定義。另外要 注意,因為總是要在 xmlChar* 和 char* 之間進行類型轉換,所以定義了一個宏 BAD_CAST ,其定義如下: xmlstring.h
#define BAD_CAST (xmlChar *)原則上來說, unsigned char 和 char 之間進行強制類型轉換是沒有問題的。
數據結構
xmlDoc可以看到,節點之間是以鏈表和樹兩種方式同時組織起來的,next和prev指針可 以組成鏈表,而parent和children可以組織為樹。所有節點都是文檔 xmlDoc 節 點的直接或間接子節點。同時還有以下重要元素:
- 節點中的文字內容: content ;
- 節點所屬文檔: doc ;
- 節點名字: name ;
- 節點的 namespace: ns ;
- 節點屬性列表: properties ;
xml 文檔的操作其根本原理就是在節點之間移動、查詢節點的各項信息,并進行 增加、刪除、修改的操作。 xmlDocSetRootElement 函數可以將一個節點設置為 某個文檔的根節點,這是將文檔與節點連接起來的重要手段,當有了根結點以 后,所有子節點就可以依次連接上根節點,從而組織成為一個 xml 樹。
創建 XML 文檔
創建一個 XML 文檔流程如下:
示例
下面用一個例子說明一些函數的使用,和創建一個 XML 文檔的大致步驟:
#include <stdio.h> #include <stdlib.h> #include <libxml/parser.h> #include <libxml/tree.h>int main (int argc, char **argv) {xmlDocPtr pdoc = NULL;xmlNodePtr proot_node = NULL,pnode = NULL,pnode1 = NULL;// 創建一個新文檔并設置 root 節點 // 一個 XML 文件只有一個 root 節點 pdoc = xmlNewDoc (BAD_CAST "1.0");proot_node = xmlNewNode (NULL, BAD_CAST "根節點");xmlNewProp (proot_node, BAD_CAST "版本", BAD_CAST "1.0");xmlDocSetRootElement (pdoc, proot_node);pnode = xmlNewNode (NULL, BAD_CAST "子節點1");// 創建上面 pnode 的子節點 xmlNewChild (pnode, NULL, BAD_CAST "子子節點1", BAD_CAST "信息");// 添加子節點到 root 節點 xmlAddChild (proot_node, pnode);pnode1 = xmlNewNode (NULL, BAD_CAST "子子節點1");xmlAddChild (pnode, pnode1);xmlAddChild (pnode1,xmlNewText (BAD_CAST "這是更低的節點,子子子節點1"));// 還可以這樣直接創建一個子節點到 root 節點上 xmlNewTextChild (proot_node, NULL, BAD_CAST "子節點2", BAD_CAST "子節點2的內容");xmlNewTextChild (proot_node, NULL, BAD_CAST "子節點3", BAD_CAST "子節點3的內容");// 保存 xml 為文件,如果沒有給出文件名參數,就輸出到標準輸出 xmlSaveFormatFileEnc (argc > 1 ? argv[1]:"-", pdoc, "UTF-8", 1);// 釋放資源 xmlFreeDoc (pdoc);xmlCleanupParser ();xmlMemoryDump ();return 0; }編譯這個例子,先看看系統里面的 libxml2 庫的 pkgconfig 信息:
root@jianlee:~/lab/xml# cat /usr/lib/pkgconfig/libxml-2.0.pc prefix=/usr exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include modules=1Name: libXML Version: 2.6.32 Description: libXML library version2. Requires: Libs: -L${libdir} -lxml2 Libs.private: -lz -lm Cflags: -I${includedir}/libxml2root@jianlee:~/lab/xml# pkg-config libxml-2.0 --cflags --libs -I/usr/include/libxml2 -lxml2編譯:
root@jianlee:~/lab/xml# gcc -Wall `pkg-config libxml-2.0 --cflags --libs` create_xml.c如果沒有修改源程序,輸出應該是這樣:
root@jianlee:~/lab/xml# ./a.out <?xml version="1.0" encoding="UTF-8"?> <根節點 版本="1.0"><子節點1><子子節點1>信息</子子節點1><子子節點1>這是更低的節點,子子子節點1</子子節點1></子節點1><子節點2>子節點2的內容</子節點2><子節點3>子節點3的內容</子節點3> </根節點>示例補充說明
輸出的各節點不要在一行
上面使用下面方式保存 xml 文檔,輸出的文件各子節點間自動加入回車:
// 保存 xml 為文件,如果沒有給出文件名參數,就輸出到標準輸出xmlSaveFormatFileEnc (argc > 1 ? argv[1]:"-", pdoc, "UTF-8", 1);如果把上面的 1 換成 0 ,輸出格式是放在一行。
用到的函數說明
上面涉及幾個函數和類型定義,不過意思很明了,下面解釋一個(重要的是自己 動手寫程序,反復實驗,所謂熟能生巧)。
xmlDocPtr最后顯示是這個樣子:
<根節點 版本="1.0"> xmlDocSetRootElement會出現下面的結果:
<子子節點1>這是更低的節點,子子子節點1</子子節點1> xmlNewTextChild解析 XML 文檔
解析一個xml文檔,從中取出想要的信息,例如節點中包含的文字,或者某個節點 的屬性,其流程如下:
- 用 xmlReadFile 函數讀出一個文檔指針 doc ;
- 用 xmlDocGetRootElement 函數得到根節點 curNode ;
- curNode->xmlChildrenNode 就是根節點的子節點集合 ;
- 輪詢子節點集合,找到所需的節點,用 xmlNodeGetContent 取出其內容 ;
- 用 xmlHasProp 查找含有某個屬性的節點 ;
- 取出該節點的屬性集合,用 xmlGetProp 取出其屬性值 ;
- 用 xmlFreeDoc 函數關閉文檔指針,并清除本文檔中所有節點動態申請的內存。
注意: 節點列表的指針依然是 xmlNodePtr ,屬性列表的指針也是 xmlAttrPtr ,并沒有 xmlNodeList 或者 xmlAttrList 這樣的類型 。看作列表的時候使用它 們的 next 和 prev 鏈表指針來進行輪詢 。只有在 Xpath 中有 xmlNodeSet 這 種類型。
示例
#include <stdio.h> #include <stdlib.h> #include <libxml/parser.h> #include <libxml/tree.h>int main (int argc , char **argv) {xmlDocPtr pdoc = NULL;xmlNodePtr proot = NULL, curNode = NULL;char *psfilename;if (argc < 1){printf ("用法: %s xml文件名\n", argv[0]);exit (1);}psfilename = argv[1];// 打開 xml 文檔 //xmlKeepBlanksDefault(0); pdoc = xmlReadFile (psfilename, "UTF-8", XML_PARSE_RECOVER);if (pdoc == NULL){printf ("打開文件 %s 出錯!\n", psfilename);exit (1);}// 獲取 xml 文檔對象的根節對象 proot = xmlDocGetRootElement (pdoc);if (proot == NULL){printf("錯: %s 是空文檔(沒有root節點)!\n", psfilename);exit (1);}/* 我使用上面程序創建的 xml 文檔,它的根節點是“根節點”,這里比較是否正確。*/if (xmlStrcmp (proot->name, BAD_CAST "根節點") != 0){printf ("錯誤文檔" );exit (1);}/* 如果打開的 xml 對象有 version 屬性,那么就輸出它的值。 */if (xmlHasProp (proot, BAD_CAST "版本")){xmlChar *szAttr = xmlGetProp (proot, BAD_CAST "版本");printf ("版本: %s \n根節點:%s\n" , szAttr, proot->name);}else{printf (" xml 文檔沒有版本信息\n");}curNode = proot->xmlChildrenNode;char n=0;while (curNode != NULL){if (curNode->name != BAD_CAST "text"){printf ("子節點%d: %s\n", n++,curNode->name);}curNode = curNode->next;}/* 關閉和清理 */xmlFreeDoc (pdoc);xmlCleanupParser ();return 0; }編譯運行(使用上例創建的 my.xml 文件):
root@jianlee:~/lab/xml# cat my.xml <?xml version="1.0" encoding="UTF-8"?> <根節點 版本="1.0"><子節點1><子子節點1>信息</子子節點1><子子節點1>這是更低的節點,子子子節點1</子子節點1></子節點1><子節點2>子節點2的內容</子節點2><子節點3>子節點3的內容</子節點3> </根節點> root@jianlee:~/lab/xml# gcc -Wall `pkg-config libxml-2.0 --cflags --libs` read_xml.c root@jianlee:~/lab/xml# ./a.out my.xml 版本: 1.0 根節點:根節點 子節點0: text 子節點1: 子節點1 子節點2: text 子節點3: 子節點2 子節點4: text 子節點5: 子節點3 子節點6: text為什么 my.xml 文件中顯示只有 ”子節點1“、 ”子節點2“和 “子節點3”三個子節 點,而程序顯示有 7 個子節點呢?!而且 0、2、4、6 都是 text 名字?
這是因為其他四個分別是元素前后的空白文本符號,而 XML 把它們也當做一個 Node !元素是 Node 的一種類型。XML 文檔對象模型 (DOM) 定義了幾種不同的 Nodes 類型,包括 Elements(如 files 或者 age)、Attributes(如 units) 和 Text(如 root 或者 10)。元素可以具有子節點。
在打開 xml 文檔之前加上一句(取消上面程序中的此句注釋就可以):
xmlKeepBlanksDefault(0);或者使用下面參數讀取 xml 文檔:
//讀取xml文件時忽略空格 doc = xmlReadFile(docname, NULL, XML_PARSE_NOBLANKS);這樣就可以按我們所想的運行了:
root@jianlee:~/lab/xml# gcc -Wall `pkg-config libxml-2.0 --cflags --libs` read_xml.c root@jianlee:~/lab/xml# ./a.out my.xml 版本: 1.0 根節點:根節點 子節點0: 子節點1 子節點1: 子節點2 子節點2: 子節點3還有一點注意: my.xml 文件中的子節點名字一次是 “子節點1”、“子節點2”、 “子節點3”。程序中的 n 值確是從 0 開始計算。從 0 還是 1 是個人喜好。我有 時候喜好從 0 開始,有時候喜好從 1 開始。
xmlFreeDoc修改 xml 文檔
首先打開一個已經存在的xml文檔,順著根結點找到需要添加、刪除、修改的地 方,調用相應的xml函數對節點進行增、刪、改操作。
刪除節點
刪除節點使用下面方法:
if (!xmlStrcmp(curNode->name, BAD_CAST "newNode1")){xmlNodePtr tempNode;tempNode = curNode->next;xmlUnlinkNode(curNode);xmlFreeNode(curNode);curNode = tempNode;continue;}即將當前節點從文檔中斷鏈(unlink),這樣本文檔就不會再包含這個子節點。 這樣做需要使用一個臨時變量來存儲斷鏈節點的后續節點,并記得要手動刪除斷 鏈節點的內存。
示例
#include <stdio.h> #include <stdlib.h> #include <libxml/parser.h>int main(int argc, char* argv[]) {xmlDocPtr doc; //定義解析文檔指針 xmlNodePtr curNode; //定義結點指針(你需要它為了在各個結點間移動) char *szDocName;if (argc <= 1){printf("Usage: %s docname\n", argv[0]);return(0);}szDocName = argv[1];xmlKeepBlanksDefault(0);doc = xmlReadFile(szDocName,"UTF-8",XML_PARSE_RECOVER); //解析文件 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;}curNode = curNode->children;while (NULL != curNode){//刪除 "子節點1" if (!xmlStrcmp(curNode->name, BAD_CAST "子節點1")){xmlNodePtr tempNode;tempNode = curNode->next;xmlUnlinkNode(curNode);xmlFreeNode(curNode);curNode = tempNode;continue;}//修改 "子節點2" 的屬性值 if (!xmlStrcmp(curNode->name, BAD_CAST "子節點2")){xmlSetProp(curNode,BAD_CAST "屬性1", BAD_CAST "設置");}//修改 “子節點2” 的內容 if (!xmlStrcmp(curNode->name, BAD_CAST "子節點2")){xmlNodeSetContent(curNode, BAD_CAST "內容變了");}//增加一個屬性 if (!xmlStrcmp(curNode->name, BAD_CAST "子節點3")){xmlNewProp(curNode, BAD_CAST "新屬性", BAD_CAST "有");}//增加 "子節點4" if (!xmlStrcmp(curNode->name, BAD_CAST "子節點3")){xmlNewTextChild(curNode, NULL, BAD_CAST "新子子節點1", BAD_CAST "新內容");}curNode = curNode->next;}// 保存文件 xmlSaveFormatFileEnc (szDocName, doc,"UTF-8",1);xmlFreeDoc (doc);xmlCleanupParser ();xmlMemoryDump ();return 0; }編譯運行:
root@jianlee:~/lab/xml# cat my.xml <?xml version="1.0" encoding="UTF-8"?> <根節點 版本="1.0"><子節點1><子子節點1>信息</子子節點1><子子節點1>這是更低的節點,子子子節點1</子子節點1></子節點1><子節點2>子節點2的內容</子節點2><子節點3>子節點3的內容</子節點3> </根節點> root@jianlee:~/lab/xml# gcc -Wall `pkg-config libxml-2.0 --cflags --libs` modify_xml.c root@jianlee:~/lab/xml# ./a.out my.xml root@jianlee:~/lab/xml# cat my.xml <?xml version="1.0" encoding="UTF-8"?> <根節點 版本="1.0"><子節點2 屬性1="設置">內容變了</子節點2><子節點3 新屬性="有">子節點3的內容<新子子節點1>新內容</新子子節點1></子節點3> </根節點> root@jianlee:~/lab/xml# ./a.out my.xml # 看看再運行一次的結果! root@jianlee:~/lab/xml# cat my.xml <?xml version="1.0" encoding="UTF-8"?> <根節點 版本="1.0"><子節點2 屬性1="設置">內容變了</子節點2><子節點3 新屬性="有" 新屬性="有">子節點3的內容<新子子節點1>新內容</新子子節點1><新子子節點1>新內容</新子子節點1></子節點3> </根節點>Xpath — 處理大型 XML 文檔
libxml2 庫函數
要注意的函數
xmlKeepBlanksDefault
int xmlKeepBlanksDefault (int val)設置是否忽略空白節點,比如空格,在分析前必須調用,默認值是0,最好設置成1.
xmlKeepBlanksDefault(0) 除了在讀入xml文件時忽略空白之外,還會在寫出xml 文件時在每行前面放置縮進(indent)。如果使用xmlKeepBlanksDefault(1) 則 你會發現每行前面的縮進就沒有了,但不會影響回車換行。
xmlSaveFormatFile
// 保存 xml 為文件,如果沒有給出文件名參數,就輸出到標準輸出xmlSaveFormatFileEnc (argc > 1 ? argv[1]:"-", pdoc, "UTF-8", 1);xmlSaveFormatFile 的 format 參數設置成 0,保存后的 xml 文檔里是會把所有 的結點都放到一行里顯示。設置為 1,就可以自動添加回車。
讀取 xml 文件
xmlParseFile
xmlDocPtr xmlParseFile (const char * filename)以默認方式讀入一個 UTF-8 格式的 xml 文檔, 并返回一個文檔對象指針 <libxml/tree.h>
xmlReadFile
指定編碼讀取一個 xml 文檔,返回指針。
xml 操作基本結構及其指針類型
xmlDoc, xmlDocPtr
文檔對象的結構體及其指針
xmlNode, xmlNodePtr
節點對象的結構體及其指針
xmlAttr, xmlAttrPtr
節點屬性的結構體及其指針
xmlNs, xmlNsPtr
節點命名空間的結構及其指針
根節點相關函數
xmlDocGetRootElement
xmlNodePtr xmlDocGetRootElement (xmlDocPtr doc) 獲取文檔根節點xmlDocSetRootElement
xmlNodePtr xmlDocSetRootElement (xmlDocPtr doc, xmlNodePtr root) 設置文檔根節點創建子節點相關函數
xmlNewNode
xmlNodePtr xmlNewNode (xmlNsPtr ns, const xmlChar * name) 創建新節點xmlNewChild
xmlNodePtr xmlNewChild (xmlNodePtr parent, xmlNsPtr ns, const xmlChar * name, const xmlChar * content) 創建新的子節點xmlCopyNode
xmlNodePtr xmlCopyNode (const xmlNodePtr node, int extended) 復制當前節點添加子節點相關函數
xmlAddChild
xmlNodePtr xmlAddChild (xmlNodePtr parent, xmlNodePtr cur) 給指定節點添加子節點xmlAddNextSibling
xmlNodePtr xmlAddNextSibling (xmlNodePtr cur, xmlNodePtr elem) 添加后一個兄弟節點xmlAddPrevSibling
xmlNodePtr xmlAddPrevSibling (xmlNodePtr cur, xmlNodePtr elem) 添加前一個兄弟節點xmlAddSibling
xmlNodePtr xmlAddSibling (xmlNodePtr cur, xmlNodePtr elem) 添加兄弟節點屬性相關函數
xmlNewProp
xmlAttrPtr xmlNewProp (xmlNodePtr node, const xmlChar * name, const xmlChar * value) 創建新節點屬性xmlGetProp
xmlChar * xmlGetProp (xmlNodePtr node, const xmlChar * name) 讀取節點屬性xmlSetProp
xmlAttrPtr xmlSetProp (xmlNodePtr node, const xmlChar * name, const xmlChar * value) 設置節點屬性總結
以上是生活随笔為你收集整理的libxml -- 解析 XML 文档的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vim 命令(全)
- 下一篇: mysql profiling 应用