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