在 Kenn Scribner 近期有關 XML和MSXML DOM 分析器的文章中,僅介紹了該分析器的部 分功能。這些文章將XML 作為一種技術進行了說明,但是並沒有介紹 XML 分析器本身。現在 ,Kenn 將回過頭來介紹 MSXML 分析器,並講解處理 XML 文檔和節點所需的基本知識:搜索 特定的節點、插入節點和檢索節點值。
MSXML 分析器基於 XML 文檔對象模型,對於查看表 1 中所示的各種文檔對象來說,它非 常重要。這些對象直接出自 XML 規范本身。MSXML 還可以進一步將XML DOM 對象合並到 COM 中。因此,弄清楚哪個 XML DOM 對象對應於哪個 MSXML COM 接口非常容易。例如, IXMLDOMNode 代表稱為 Node 的DOM 對象。
表 1. XML DOM 對象及其用途 DOM 對象 用途 DOMImplementation 一個查詢對象,用於確定 DOM 支持的級別 DocumentFragment 表示樹的一部分(可進行剪切/粘貼操作) Document 表示樹中的頂級節點 NodeList 用於訪問 XML 節點的Iterator 對象 Node 用於擴展帶核心 XML 標記的元素 NamedNodeMap 命名空間支持和迭代通過屬性節點集合 CharacterData 文本操作對象 Attr 表示元素的屬性 Element 表示 XML 元素的節點(可用於訪問屬性) Text 表示給定元素或屬性對象的文本內容 CDATASection 用於屏蔽 XML 部分,使其不受分析和驗證 Notation 包含基於 DTD 或架構內的表示法 Entity 表示已分析或未分析的實體 EntityReference 表示實體引用節點 ProcessingInstruction 表示處理指令雖然有時比較容易混淆,但是 XML 文檔對象可以是(並且通常是)多態的。即,“ 節點”同時也是一個“元素”。當您試圖確定需要何種 DOM 對象來執行何 種操作時,這有時會造成混淆。可以使用“文檔”對象來創建 DOM“節點 ”,但是,如果要向新創建的節點添加屬性,就必須通過其作為“元素”的 一面來訪問它。如果說存在一種將對象和操作關聯在一起的神奇模式,那麼我還沒能從自己 的日常工作中將它提煉出來。我發現自己仍需要不斷參考 MSDN 文檔來查看哪個 COM 接口提 供了所需的方法以執行我試圖完成的任務。各種對象方法看上去的確是按邏輯分組的,這也 正是我對 DOM 當初的開發模式的推斷(通過分組邏輯操作)。
因此,其中的訣竅就在於從 MSXML 分析器檢索適當的DOM 對象,這一操作的具體實現就 是 COM 對象。操作的基本模式將是:首先實例化 MSXML COM 對象本身的一個副本,然後從 該副本請求或以其他方式獲取指向附加 XML DOM 對象(本身也是 COM 對象)的指針。
MSXML DOM 試驗應用程序
創建一個漂亮的應用程序,演示眾多的MSXML 功能,這很簡單,但實際上,附加的代碼只 會畫蛇添足。相反,我選擇了開發一個簡單的基於控制台的應用程序,該應用程序執行四種 基本操作:
• 從磁盤加載一個 XML 文件。 • 搜索特定的節點,並向該節點插入一個子節點。 • 搜索另一個節點,並顯示該節點內包含的(文本)值。 • 將修改後的XML 文檔保存回磁盤中。
為了進一步簡化,我硬編碼了 XML 文檔文件的名稱和 XML 節點本身。當然,如果這是一 個真實的應用程序,您可能很少(或者永遠不會)采用這樣的方法。但是在本例中,進行這 些權衡,是為了簡化圍繞在 MSXML 功能兩邊的代碼。
像平常一樣,在示例應用程序中,我選擇了使用 ATL 來包裝許多與 COM 有關的活動。您 肯定看到我使用了 CComPtr和CComQIPtr 對象,但是我還額外加入了幾個 CComBSTR和 CComVariant 對象。如果您不熟悉它們,只需要記住它們是用於處理一些細節的模板,這些 細節對於本文的主旨來說並非至關重要,但是從更廣的角度講,還是比較重要的。真正重要 的是看到如何搜索 XML 節點,添加新的(具有屬性的)節點,以及顯示節點內包含的文本。
我的基於控制台的應用程序可以在附帶的下載文件中找到,它將加載一個名為 xmldata.xml 的XML 文檔文件(假定其與可執行文件位於同一個目錄中),並假定該文檔包 含以下 XML 數據:
<?xml version="1.0"?>
<xmldata>
<xmlnode />
<xmltext>Hello, World!</xmltext>
</xmldata>
我們將首先搜索 xmlnode 節點,如果找到了該節點,我們將插入一個新的(帶有屬性的 )節點作為其子級。生成的XML 文檔將為:
<?xml version="1.0"?>
<xmldata>
<xmlnode>
<xmlchildnode xml="fun" />
</xmlnode>
<xmltext>Hello, World!</xmltext>
</xmldata>
打印 節點內包含的信息 ("Hello, World!") 之後,我們將把該新 XML 文檔 保存到名為 updatedxml.xml 的文件中。然後,就可以使用文本編輯器或 Internet Explorer 5.x 來查看結果。現在讓我們轉到代碼。
應用程序首先初始化了 COM 運行庫,然後創建了 MSXML 分析器的一個實例:
CComPtr<IXMLDOMDocument> spXMLDOM;
HRESULT hr = spXMLDOM.CoCreateInstance(
__uuidof(DOMDocument));
if ( FAILED(hr) )
throw "Unable to create XML parser object";
if ( spXMLDOM.p == NULL )
throw "Unable to create XML parser object";
如果創建分析器實例成功,接下來,我們將把 XML 文檔加載到分析器中:
VARIANT_BOOL bSuccess = false;
hr = spXMLDOM->load(CComVariant(L"xmldata.xml"),
&bSuccess);
if ( FAILED(hr) )
throw "Unable to load XML document into the parser";
if ( !bSuccess )
throw "Unable to load XML document into the parser";
搜索節點與文檔對象有關,因此,我們將使用 IXMLDOMDocument::selectSingleNode() 來根據其名稱查找特定的XML 節點。其他的技巧很多,但是如果准確地知道要查找的節點的 名稱,這是最直接的方法:
CComBSTR bstrSS(L"xmldata/xmlnode");
CComPtr<IXMLDOMNode> spXMLNode;
hr = spXMLDOM->selectSingleNode(bstrSS,&spXMLNode);
if ( FAILED(hr) )
throw "Unable to locate 'xmlnode' XML node";
if ( spXMLNode.p == NULL )
throw "Unable to locate 'xmlnode' XML node";
一些您應當了解的其他方法包括 IXMLDOMDocument::nodeFromID()和 IXMLDOMElement::getElementsByTagName(),使用它們可以獲得文檔中的節點的列表。您還 可以將文檔作為樹來進行訪問,並依次通過它(獲取子節點,獲取同輩節點等)。
任一種情況下,搜索的結果都是一個 MSXML 節點對象 IXMLDOMNode。文檔中必須存在該 節點,否則搜索將失敗。我的應用程序使用該節點作為一個全新 XML 節點的父級,該新節點 是由 XML 文檔對象創建的:
CComPtr<IXMLDOMNode> spXMLChildNode;
hr = spXMLDOM->createNode(CComVariant(NODE_ELEMENT),
CComBSTR("xmlchildnode"),
NULL,
&spXMLChildNode);
if ( FAILED(hr) )
throw "Unable to create 'xmlchildnode' XML node";
if ( spXMLChildNode.p == NULL )
throw "Unable to create 'xmlchildnode' XML node";
如果分析器可以創建該節點,下一步就是將它放到 XML 樹中。 IXMLDOMNode::appendChild() 正是完成這一任務的方法:
CComPtr<IXMLDOMNode> spInsertedNode;
hr = spXMLNode->appendChild(spXMLChildNode,
&spInsertedNode);
if ( FAILED(hr) )
throw "Unable to move 'xmlchildnode' XML node";
if ( spInsertedNode.p == NULL )
throw "Unable to move 'xmlchildnode' XML node";
如果父節點的確將新創建的節點插入為其子級,將返回另一個 IXMLDOMNode 實例,該實 例表示新的子節點。實際上,該新子節點和傳遞給 appendChild() 的節點是同一個 XML 節 點。由於在存在問題時附加的子節點的指針將為 Null,因此,檢查該指針很有用。
到目前為止,我找到了一個特定的節點,並為它創建了一個新的子節點,下面,讓我們看 看如何處理屬性。假定您要將該屬性添加到新的子節點:
xml="fun"
這並不難,但是您必須從 IXMLDOMNode 切換到 IXMLDOMElement,以便訪問該子節點的元 素特征。在實踐中,這意味著您必須查詢 IXMLDOMNode 接口的相關 IXMLDOMElement 接口, 查明後,再調用 IXMLDOMElement::setAttribute():
CComQIPtr<IXMLDOMElement> spXMLChildElement;
spXMLChildElement = spInsertedNode;
if ( spXMLChildElement.p == NULL )
throw "Unable to query for 'xmlchildnode' XML _
element interface";
hr = spXMLChildElement->setAttribute(CComBSTR(L"xml"),
CComVariant(L"fun"));
if ( FAILED(hr) )
throw "Unable to insert new attribute";
此時,已經修改了 XML 樹,並創建了所需的樹。應用程序可以在這個時候將文檔保存到 磁盤,或者執行其他任務。現在,讓我們來搜索另一個節點並顯示該節點所包含的值(文本 )。您已經了解了如何搜索節點,因此,我們將直接講解數據提取。
提取節點數據的關鍵在於使用 IXMLDOMNode::get_nodeTypedValue()。可以使用 Microsoft 數據類型架構來標識節點所包含的數據,因此可以方便地存儲浮點值、整數、字 符串或該架構所支持的任何數據類型。可以使用 dt:type 屬性來指定數據類型,如下所示:
<model dt:type="string">SL-2</model>
<year dt:type="int">1992</year>
如果特定的節點具有指定的數據類型,就可以使用 get_nodeTypedValue() 以該格式提取 數據。如果未指定數據類型,將假定數據為文本,分析器將返回具有 BSTR 數據的VARIANT。 在本例中,這沒有任何問題,因為我們要搜索的節點是一個實際上包含一個字符串的文本節 點。在需要時,始終可以使用 atoi() 等方法將字符串轉換為其他形式。本例中,我們只是 提取該字符串數據並顯示它:
CComVariant varValue(VT_EMPTY);
hr = spXMLNode->get_nodeTypedValue(&varValue);
if ( FAILED(hr) )
throw "Unable to retrieve 'xmltext' text";
if ( varValue.vt == VT_BSTR ) {
// Display the results...since we're not using the
// wide version of the STL, we need to convert the
// BSTR to ANSI text for display...
USES_CONVERSION;
LPTSTR lpstrMsg = W2T(varValue.bstrVal);
std::cout << lpstrMsg << std::endl;
}
else {
// Some error
throw "Unable to retrieve 'xmltext' text";
}
如果能夠檢索與節點關聯的值,並且該值為 BSTR(預期的數據類型),我們將在屏幕上 顯示該文本。如果不能,將顯示一條錯誤消息,不過,根據情況而定,可以方便地采取其他 操作。
最後一項與 XML 有關的操作是將已更新的XML 樹保存到磁盤,這一任務是使用 IXMLDOMDocument::save() 完成的:
hr = spXMLDOM->save(CComVariant("updatedxml.xml"));
if ( FAILED(hr) )
throw "Unable to save updated XML document";
完成保存後,向屏幕寫一條簡短說明,並退出。
這個示例應用程序無論如何都算不上漂亮。您可以讓自己的應用程序執行很多其他功能, 但我希望您通過這個簡短的示例了解到了如何從 C++ 程序使用 MSXML 分析器。該分析器本 身是一個復雜的軟件,無論怎樣強調使用 MSDN Library 作為參考,都不能算是過份。該分 析器公開了許多接口,這些接口通常會公開許多方法。即便如此,我在自己的項目中仍頻繁 地使用該分析器,在親自編寫了一些代碼並進行試驗後,我發現這個軟件制作很精良 並且便 於使用。我希望您也同樣會發現該分析器和一般意義上的XML 具有廣泛的用途。