(由於包含太多格式符號,新浪提示篇幅過長,因此分爲上、下兩篇)
本篇是接前文“MSXML應用總結 概念篇”寫的,主要總結一下MSXML DOM接口的應用。DOM(Document Object Model)是微軟提供的處理XML文檔的一個API標準庫,我們可以將其理解爲一組抽象了XML文檔結構的接口。
MSXML的DOM模型是符合W3C DOM標準的,而DOM API在Windows中以COM接口的形式提供,關於COM請大家查閱相關資料。簡單來說,COM提供了一個環境和一套規則,使接口的設計實現到對象的創建、使用和釋放都標準化,從而使COM支持跨平臺和跨語言;更重要的是,遵守COM規範使我們代碼的接口與實現分離,將程序框架的穩定與擴展統一起來,對於使用COM接口的人則更加簡單直觀。COM中一個很重要的概念是refcount,即接口對象的訪問計數,通過AddRef和Release兩個接口函數來控制。要想用好refcount還是件較困難的事情,因此我推薦大家使用智能指針。使用智能指針就像使用一個簡單指針一樣,我們完全不用去關心指針指向內存空間的釋放。
本篇總結采用API版本是MSXML2.0。
首先我們看一下常用的接口:
IXMLDOMDocument:XML文檔接口,DOM樹結構的根結點,是對文檔訪問和操作的入口;
IXMLDOMNode:節點接口,該接口是普遍意義上的節點接口,很多類型節點接口都從它派生,包括IXMLDOMDocument;
IXMLDOMNodeList:節點列表接口,表示一組關聯的節點集合;該列表中的node元素通過index(從0開始)訪問,另外該接口中的元素還是動態的,會隨着XML文檔的改變而更新;
IXMLDOMNamedNodeMap:節點集合接口,也表示一組關聯節點的集合;不過與list不同的是,該集合是無序的,該接口常用於表示節點的屬性集,並且該接口也是動態的;
IXMLDOMElement:元素接口,一般用來表示一個節點及其屬性;
IXMLDOMAttribute:節點屬性接口,對節點屬性進行訪問和操作;
IXMLDOMText:節點中文本控制接口;
IXMLDOMComment:XML文檔中的註釋接口;
IXMLDOMParseError:出錯處理接口,包括了錯誤的詳細信息。
以上都是最常用的DOM接口,還有一些接口沒有在此列出。對於接口來說,都有相應的智能指針接口,一般爲接口名加上Ptr,比如IXMLDOMDocument的智能指針接口爲IXMLDOMDocumentPtr。這裏有一個接口繼承關係示意圖:
在VS2005環境下進行DOM應用開發,首先要設置DOM接口應用環境,在stdafx.h文件中加入語句:
#import <msxml3.dll> raw_interfaces_only
如果你的系統文件夾下有msxml6.dll文件,#import語句將成生MSXML庫類型信息,一般會在你的工程編譯文件夾下生成msxml6.tlh和msxml6.tli兩個文件,打開看一下可知這兩個文件包含了一些COM接口類型及函數的聲明以及一些庫信息。實際上,#import指令使dll庫中的類型信息導出爲描述的COM接口的c++類頭文件。而“raw_interfaces_only”屬性使得生成文件只有msxml6.tlh一個,而且接口函數只有HRESULT返回類型一種形式,且省去了raw_前綴;如果去掉該屬性,則除了在msxml6.tlh文件中聲明帶raw_前綴的返回HRESULT類型的接口函數外,還會在msxml6.tlh中生成不帶raw_前綴的wrapper接口函數,並在msxml6.tli文件中生成返回接口指針類型的wrapper接口函數。因此我們在應用DOM接口的時候,發現有兩套完成相同功能的接口函數,分別返回HRESULT類型和接口指針類型,就是因爲上述原因,這應該是Windows環境下COM接口描述的規則,比較深入的介紹請參考這篇文章:http://www.cnblogs.com/xiaotaoliang/archive/2005/07/20/196257.html。爲了應用方便,我們下面的示例代碼不一定用的都是加了“raw_interfaces_only”屬性後的接口函數,建議大家可以去掉該屬性,此處只是加以說明。
另外一種加載DOM接口的方法是直接在工程環境中添加msxml庫的路徑,並鏈接msxml6.lib文件,這裏不再詳述。
設置DOM環境後,還要初始化COM應用環境,在應用線程初始化函數中調用CoInitialize,並在線程退出時調用CoUninitialize。
現在我們可以用DOM接口來對xml文件進行操作了,我將按操作分類進行總結。
一、xml文件的加載和保存
由於DOM模型面向的是整個xml文件,因此我們需要自己創建的接口只有IXMLDOMDocument一個,其他接口都是從它直接或間接得到的,xml文件的加載和保存函數也在IXMLDOMDocument接口中實現。創建IXMLDOMDocument接口的代碼如下:
MSXML2::IXMLDOMDocumentPtr pXmlDoc;
HRESULT hr = pXmlDoc.CreateInstance( __uuidof(MSXML2::DOMDocument60), NULL, CLSCTX_INPROC_SERVER);
if( FAILED(hr))
printf("Failed to create DOM document interface pointer.\n");
加載xml文件代碼爲:
try
{
pXmlDoc->async = VARIANT_FALSE;
pXmlDoc->validateOnParse = VARIANT_FALSE;
pXmlDoc->resolveExternals = VARIANT_FALSE;
if( pXmlDoc->load("test.xml") != VARIANT_TRUE)
{
printf("Fail reason: %s\n", (LPCSTR)pXmlDoc->parseError->Getreason());
}
else
{
// success
}
}
catch(_com_error errorObject)
{
printf("Exception, HRESULT = 0x%08x", errorObject.Error());
}
上面代碼中,開始3句是設置IXMLDOMDocument接口的3個屬性值。
async表示調用的阻塞模式,爲true時爲異步,此時load函數調用立即返回,而不管文件加載是否完成;爲false時爲同步模式,即在加載完之後函數返回。在異步模式中,可以通過查詢readyState屬性值來判斷是否加載完畢,也可以設置onreadystatechange handler或者onreadystatechange event進行處理。async的默認值爲true。
validateOnParse表示當xml文件結構有錯誤時是否繼續進行分析,默認值爲true。
resolveExternals表示在分析xml時,外部定義或document type definition(DTD)等是否被處理,MSXML6.0中的默認值爲false。
另外要解釋一下VARIANT類型,一般在COM中用的比較多。VARIANT類型被用來表示多種數據類型,在接口中應用還是很方便的。其實它的定義是一個結構體,其中有一個變量指示了數據的真正類型,還有一個union變量,由各種類型的數據成員構成。這樣,VARIANT就能支持各種類型的數據了。值得一提的是,VARIANT中字符串類型是用BSTR表示的,BSTR也是COM編程中通用的字符串類型,爲Unicode字符串。BSTR字符串的內存分配都由系統統一管理,通過SysAllocString和SysFreeString控制。Windows提供了專門的類來處理VARIANT和BSTR,具體可以參考這篇文章:http://www.vckbase.com/document/viewdoc/?id=1096。
load函數既可以加載本地文件,也可以加載URL形式的遠程文件(沒有測試)。另外還有一個對應的loadXML函數可以直接加載字符串形式的xml,但只支持UTF-16和UCS-2兩種編碼。
保存xml文件的代碼爲:
try
{
if( FAILED( pXmlDoc->save(L"myData.xml")))
{
printf("Fail reason: %s\n", (LPCSTR)pXmlDoc->parseError->Getreason());
}
else
{
// success
}
}
catch(_com_error errorObject)
{
printf("Exception, HRESULT = 0x%08x", errorObject.Error());
}
二、獲取root節點指針
有了IXMLDOMDocument接口指針,就能很方便的得到root節點接口指針。對於加載xml來說,有3種方式,代碼如下:
MSXML2::IXMLDOMElementPtr pRootNode = pXmlDoc->documentElement;
或
MSXML2::IXMLDOMElementPtr pRootNode;
pXmlDoc->get_documentElement(&pRootNode);
或
MSXML2::IXMLDOMNodePtr pRootNode, pNode;
pXmlDoc->get_firstChild(&pRootNode);
while( pRootNode)
{
MSXML2::DOMNodeType type;
pRootNode->get_nodeType(&type);
if(type==NODE_ELEMENT)
break;
pNode = pRootNode;
pNode->get_nextSibling(&pRootNode);
}
最常用的又簡單的方法就是第一種。寫出後兩種方法是想說明兩個問題,後面的操作方法將只介紹最常用的方法。
可以看到第二種方法並不是直接訪問的IXMLDOMDocument接口的屬性值,而是通過函數得到。對於DOM接口的屬性,一般都有對應的get或put函數來對屬性進行讀寫。
第三種方法是爲了讓大家再次理解各種類型的node之間的聯繫與區別,我們可以看到IXMLDOMDocument和IXMLDOMElement均爲一個IXMLDOMNode,我們可以通過遍歷IXMLDOMDocument的子節點得到root節點。只不過要注意的是,IXMLDOMDocument的get_firstChild返回的節點並不一定就是root,可能是一些註釋或空格行之類,我們需要判斷節點類型。節點類型的種類及說明如下表:
種類 |
值 |
意義 |
子節點類型 |
父節點類型 |
NODE_ELEMENT |
1 |
表示一個元素 |
ProcessingInstruction, Text, Comment, CDATASection, EntityReference, Element |
Document, DocumentFragment, EntityReference, Element |
NODE_ATTRIBUTE |
2 |
表示元素的屬性 |
Text , EntityReference |
— |
NODE_TEXT |
3 |
表示一個標籤的文本 |
— |
Attribute, DocumentFragment, Element, EntityReference |
NODE_CDATA_SECTION |
4 |
表示一個CDATA section |
— |
DocumentFragment, EntityReference, Element |
NODE_ENTITY_REFERENCE |
5 |
表示實體引用 |
Element, Text, ProcessingInstruction, Comment, CDATASection, EntityReference |
Attribute, DocumentFragment, Element, EntityReference |
NODE_ENTITY |
6 |
表示擴展實體 |
可表示該實體的節點類型 |
DocumentType |
NODE_PROCESSING_INSTRUCTION |
7 |
表示一個操作指示 |
— |
Document, DocumentFragment, Element, EntityReference |
NODE_COMMENT |
8 |
表示註釋 |
— |
Document, DocumentFragment, Element, EntityReference |
NODE_DOCUMENT |
9 |
表示xml文檔 |
Element, ProcessingInstruction, Comment, DocumentType |
— |
NODE_DOCUMENT_TYPE |
10 |
表示文檔類型聲明,出現在<!DOCTYPE>標籤中 |
Notation, Entity |
Document |
NODE_DOCUMENT_FRAGMENT |
11 |
表示文檔片段或與文檔 |
Element, ProcessingInstruction, Comment, Text, CDATASection, EntityReference |
— |
NODE_NOTATION |
12 |
表示DTD中聲明的表示法 |
— |
Document |
而對於新建的一個xml來說,我們創建IXMLDOMDocument接口後,調用createElement_x函數創建的第一個節點即爲root節點。