show toc
使用 MSXML 分析器處理 XML 文檔發(fā)布日期: 8/6/2004 | 更新日期: 8/6/2004
Kenn Scribner 在 Kenn Scribner 近期有關(guān) XML 和 MSXML DOM 分析器的文章中,僅介紹了該分析器的部分功能。這些文章將 XML 作為一種技術(shù)進(jìn)行了說明,但是并沒有介紹 XML 分析器本身?,F(xiàn)在,Kenn 將回過頭來介紹 MSXML 分析器,并講解處理 XML 文檔和節(jié)點(diǎn)所需的基本知識:搜索特定的節(jié)點(diǎn)、插入節(jié)點(diǎn)和檢索節(jié)點(diǎn)值。 MSXML 分析器基于 XML 文檔對象模型,對于查看表 1 中所示的各種文檔對象來說,它非常重要。這些對象直接出自 XML 規(guī)范本身。MSXML 還可以進(jìn)一步將 XML DOM 對象合并到 COM 中。因此,弄清楚哪個 XML DOM 對象對應(yīng)于哪個 MSXML COM 接口非常容易。例如,IXMLDOMNode 代表稱為 Node 的 DOM 對象。
雖然有時比較容易混淆,但是 XML 文檔對象可以是(并且通常是)多態(tài)的。即,“節(jié)點(diǎn)”同時也是一個“元素”。當(dāng)您試圖確定需要何種 DOM 對象來執(zhí)行何種操作時,這有時會造成混淆。可以使用“文檔”對象來創(chuàng)建 DOM“節(jié)點(diǎn)”,但是,如果要向新創(chuàng)建的節(jié)點(diǎn)添加屬性,就必須通過其作為“元素”的一面來訪問它。如果說存在一種將對象和操作關(guān)聯(lián)在一起的神奇模式,那么我還沒能從自己的日常工作中將它提煉出來。我發(fā)現(xiàn)自己仍需要不斷參考 MSDN 文檔來查看哪個 COM 接口提供了所需的方法以執(zhí)行我試圖完成的任務(wù)。各種對象方法看上去的確是按邏輯分組的,這也正是我對 DOM 當(dāng)初的開發(fā)模式的推斷(通過分組邏輯操作)。 因此,其中的訣竅就在于從 MSXML 分析器檢索適當(dāng)?shù)?DOM 對象,這一操作的具體實(shí)現(xiàn)就是 COM 對象。操作的基本模式將是:首先實(shí)例化 MSXML COM 對象本身的一個副本,然后從該副本請求或以其他方式獲取指向附加 XML DOM 對象(本身也是 COM 對象)的指針。 MSXML DOM 試驗(yàn)應(yīng)用程序創(chuàng)建一個漂亮的應(yīng)用程序,演示眾多的 MSXML 功能,這很簡單,但實(shí)際上,附加的代碼只會畫蛇添足。相反,我選擇了開發(fā)一個簡單的基于控制臺的應(yīng)用程序,該應(yīng)用程序執(zhí)行四種基本操作:
為了進(jìn)一步簡化,我硬編碼了 XML 文檔文件的名稱和 XML 節(jié)點(diǎn)本身。當(dāng)然,如果這是一個真實(shí)的應(yīng)用程序,您可能很少(或者永遠(yuǎn)不會)采用這樣的方法。但是在本例中,進(jìn)行這些權(quán)衡,是為了簡化圍繞在 MSXML 功能兩邊的代碼。 像平常一樣,在示例應(yīng)用程序中,我選擇了使用 ATL 來包裝許多與 COM 有關(guān)的活動。您肯定看到我使用了 CComPtr 和 CComQIPtr 對象,但是我還額外加入了幾個 CComBSTR 和 CComVariant 對象。如果您不熟悉它們,只需要記住它們是用于處理一些細(xì)節(jié)的模板,這些細(xì)節(jié)對于本文的主旨來說并非至關(guān)重要,但是從更廣的角度講,還是比較重要的。真正重要的是看到如何搜索 XML 節(jié)點(diǎn),添加新的(具有屬性的)節(jié)點(diǎn),以及顯示節(jié)點(diǎn)內(nèi)包含的文本。 我的基于控制臺的應(yīng)用程序可以在附帶的 下載文件中找到,它將加載一個名為 xmldata.xml 的 XML 文檔文件(假定其與可執(zhí)行文件位于同一個目錄中),并假定該文檔包含以下 XML 數(shù)據(jù): <?xml version="1.0"?> <xmldata> <xmlnode /> <xmltext>Hello, World!</xmltext> </xmldata> 我們將首先搜索 xmlnode 節(jié)點(diǎn),如果找到了該節(jié)點(diǎn),我們將插入一個新的(帶有屬性的)節(jié)點(diǎn)作為其子級。生成的 XML 文檔將為: <?xml version="1.0"?> <xmldata> <xmlnode> <xmlchildnode xml="fun" /> </xmlnode> <xmltext>Hello, World!</xmltext> </xmldata> 打印 節(jié)點(diǎn)內(nèi)包含的信息 ("Hello, World!") 之后,我們將把該新 XML 文檔保存到名為 updatedxml.xml 的文件中。然后,就可以使用文本編輯器或 Internet Explorer 5.x 來查看結(jié)果?,F(xiàn)在讓我們轉(zhuǎn)到代碼。 應(yīng)用程序首先初始化了 COM 運(yùn)行庫,然后創(chuàng)建了 MSXML 分析器的一個實(shí)例: 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"; 如果創(chuàng)建分析器實(shí)例成功,接下來,我們將把 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"; 搜索節(jié)點(diǎn)與文檔對象有關(guān),因此,我們將使用 IXMLDOMDocument::selectSingleNode() 來根據(jù)其名稱查找特定的 XML 節(jié)點(diǎn)。其他的技巧很多,但是如果準(zhǔn)確地知道要查找的節(jié)點(diǎn)的名稱,這是最直接的方法: 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"; 一些您應(yīng)當(dāng)了解的其他方法包括 IXMLDOMDocument::nodeFromID() 和 IXMLDOMElement::getElementsByTagName(),使用它們可以獲得文檔中的節(jié)點(diǎn)的列表。您還可以將文檔作為樹來進(jìn)行訪問,并依次通過它(獲取子節(jié)點(diǎn),獲取同輩節(jié)點(diǎn)等)。 任一種情況下,搜索的結(jié)果都是一個 MSXML 節(jié)點(diǎn)對象 IXMLDOMNode。文檔中必須存在該節(jié)點(diǎn),否則搜索將失敗。我的應(yīng)用程序使用該節(jié)點(diǎn)作為一個全新 XML 節(jié)點(diǎn)的父級,該新節(jié)點(diǎn)是由 XML 文檔對象創(chuàng)建的: 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"; 如果分析器可以創(chuàng)建該節(jié)點(diǎn),下一步就是將它放到 XML 樹中。IXMLDOMNode::appendChild() 正是完成這一任務(wù)的方法: 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"; 如果父節(jié)點(diǎn)的確將新創(chuàng)建的節(jié)點(diǎn)插入為其子級,將返回另一個 IXMLDOMNode 實(shí)例,該實(shí)例表示新的子節(jié)點(diǎn)。實(shí)際上,該新子節(jié)點(diǎn)和傳遞給 appendChild() 的節(jié)點(diǎn)是同一個 XML 節(jié)點(diǎn)。由于在存在問題時附加的子節(jié)點(diǎn)的指針將為 Null,因此,檢查該指針很有用。 到目前為止,我找到了一個特定的節(jié)點(diǎn),并為它創(chuàng)建了一個新的子節(jié)點(diǎn),下面,讓我們看看如何處理屬性。假定您要將該屬性添加到新的子節(jié)點(diǎn): xml="fun" 這并不難,但是您必須從 IXMLDOMNode 切換到 IXMLDOMElement,以便訪問該子節(jié)點(diǎn)的元素特征。在實(shí)踐中,這意味著您必須查詢 IXMLDOMNode 接口的相關(guān) IXMLDOMElement 接口,查明后,再調(diào)用 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"; 此時,已經(jīng)修改了 XML 樹,并創(chuàng)建了所需的樹。應(yīng)用程序可以在這個時候?qū)⑽臋n保存到磁盤,或者執(zhí)行其他任務(wù)。現(xiàn)在,讓我們來搜索另一個節(jié)點(diǎn)并顯示該節(jié)點(diǎn)所包含的值(文本)。您已經(jīng)了解了如何搜索節(jié)點(diǎn),因此,我們將直接講解數(shù)據(jù)提取。 提取節(jié)點(diǎn)數(shù)據(jù)的關(guān)鍵在于使用 IXMLDOMNode::get_nodeTypedValue()。可以使用 Microsoft 數(shù)據(jù)類型架構(gòu)來標(biāo)識節(jié)點(diǎn)所包含的數(shù)據(jù),因此可以方便地存儲浮點(diǎn)值、整數(shù)、字符串或該架構(gòu)所支持的任何數(shù)據(jù)類型??梢允褂?dt:type 屬性來指定數(shù)據(jù)類型,如下所示: <model dt:type="string">SL-2</model> <year dt:type="int">1992</year> 如果特定的節(jié)點(diǎn)具有指定的數(shù)據(jù)類型,就可以使用 get_nodeTypedValue() 以該格式提取數(shù)據(jù)。如果未指定數(shù)據(jù)類型,將假定數(shù)據(jù)為文本,分析器將返回具有 BSTR 數(shù)據(jù)的 VARIANT。在本例中,這沒有任何問題,因?yàn)槲覀円阉鞯墓?jié)點(diǎn)是一個實(shí)際上包含一個字符串的文本節(jié)點(diǎn)。在需要時,始終可以使用 atoi() 等方法將字符串轉(zhuǎn)換為其他形式。本例中,我們只是提取該字符串?dāng)?shù)據(jù)并顯示它: 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"; } 如果能夠檢索與節(jié)點(diǎn)關(guān)聯(lián)的值,并且該值為 BSTR(預(yù)期的數(shù)據(jù)類型),我們將在屏幕上顯示該文本。如果不能,將顯示一條錯誤消息,不過,根據(jù)情況而定,可以方便地采取其他操作。 最后一項(xiàng)與 XML 有關(guān)的操作是將已更新的 XML 樹保存到磁盤,這一任務(wù)是使用 IXMLDOMDocument::save() 完成的: hr = spXMLDOM->save(CComVariant("updatedxml.xml")); if ( FAILED(hr) ) throw "Unable to save updated XML document"; 完成保存后,向屏幕寫一條簡短說明,并退出。 這個示例應(yīng)用程序無論如何都算不上漂亮。您可以讓自己的應(yīng)用程序執(zhí)行很多其他功能,但我希望您通過這個簡短的示例了解到了如何從 C++ 程序使用 MSXML 分析器。該分析器本身是一個復(fù)雜的軟件,無論怎樣強(qiáng)調(diào)使用 MSDN Library 作為參考,都不能算是過份。該分析器公開了許多接口,這些接口通常會公開許多方法。即便如此,我在自己的項(xiàng)目中仍頻繁地使用該分析器,在親自編寫了一些代碼并進(jìn)行試驗(yàn)后,我發(fā)現(xiàn)這個軟件制作很精良 并且便于使用。我希望您也同樣會發(fā)現(xiàn)該分析器和一般意義上的 XML 具有廣泛的用途。 要了解有關(guān) Visual C++ Developer 和 Pinnacle Publishing 的更多信息,請?jiān)L問他們的 Web 站點(diǎn),網(wǎng)址為: http://www./ 注:這不是 Microsoft Corporation 的網(wǎng)站。Microsoft 對該網(wǎng)站內(nèi)容不承擔(dān)責(zé)任。 本文復(fù)制自 Visual C++ Developer 的 2000 年 11 月刊。版權(quán)所有 2000,Pinnacle Publishing, Inc.(除非另行說明)。保留所有權(quán)利。Visual C++ Developer 是 Pinnacle Publishing, Inc. 獨(dú)立發(fā)行的產(chǎn)品。未經(jīng) Pinnacle Publishing, Inc. 事先同意,不得以任何形式使用或復(fù)制本文的任何部分(評論文章中的簡短引用除外)。要聯(lián)系 Pinnacle Publishing, Inc.,請致電 1-800-788-1900。 |
|