一、引言
當前Web上流行的劇本語言是以HTML爲主的語言結構,HTML是一種標記語言,而不是一種編程語言,主要的標記是針對顯示,而不是針對文檔內容本身結構的描述的。也就是說,機器本身是不能夠解析它的內容的,所以就出現了XML語言。XML (eXtensible Markup Language)語言是SGML語言的子集,它保留了SGML主要的使用功能,同時大大縮減了SGML的複雜性。XML語言系統建立的目的就是使它不僅能夠表示文檔的內容,而且可以表示文檔的結構,這樣在同時能夠被人類理解的同時,也能夠被機器所理解。XML要求遵循一定的嚴格的標準。XML分析程序比HTML瀏覽器更加要挑剔語法和結構,XML要求正在創建的網頁正確的使用語法和結構,而不是象HTML一樣,通過瀏覽器推測文檔中應該是什麼東西來實現HTML的顯示,XML使得分析程序不論在性能還是穩定性方面都更容易實現。XML文檔每次的分析結果都是一致的,不象HTML,不同的瀏覽器可能對同一個HTML作出不同的分析和顯示。
同時因爲分析程序不需要花時間重建不完整的文檔,所以它們能比同類HTML能更有效地執行其任務。它們能全力以赴地根據已經包含在文檔中的那個樹結構建造出相應的樹來,而不用在信息流中的混合結構的基礎上進行顯示。XML標準是對數據的處理應用,而不是隻針對Web網頁的。任何類型的應用都可以在分析程序的上面進行建造,瀏覽器只是XML的一個小的組成部分。當然,瀏覽仍舊極其重要,因爲它爲XML工作人員提供用於閱讀信息的友好工具。但對更大的項目來說它就不過是一個顯示窗口。因爲XML具有嚴格的語法結構,所以我們甚至可以用XML來定義一個應用層的通訊協議,比如互聯網開放貿易協議(Internet Open Trading Protocol)就是用XML來定義的。從某種意義上說,以前我們用BNF範式定義的一些協議和格式從原則上說都可以用XML來定義。實際上,如果我們有足夠的耐心,我們完全可以用XML來定義一個C++語言的規範。
當然,XML允許大量HTML樣式的形式自由的開發,但是它對規則的要求更加嚴格。XML主要有三個要素:DTD(Document Type Declaration——文檔類型聲明)或XML Schema(XML大綱)、XSL(eXtensible Stylesheet Language——可擴展樣式語言)和XLink(eXtensible Link Language——可擴展鏈接語言)。DTD和XML大綱規定了XML文件的邏輯結構,定義了XML文件中的元素、元素的屬性以及元素和元素的屬性之間的關係;Namespace(名域)實現統一的XML文檔數據表示以及數據的相互集成;XSL是用於規定XML文檔呈現樣式的語言,它使得數據與其表現形式相互獨立,比如XSL能使Web瀏覽器改變文檔的表示法,例如數據的顯示順序的變化,不需要再與服務器進行通訊。通過改變樣式表,同一個文檔可以顯示得更大,或者經過摺疊只顯示外面得一層,或者可以變爲打印得格式。而XLink將進一步擴展目前Web上已有的簡單鏈接。
二、實現XML解析的說明
當然,從理論上說,根據XML的格式定義,我們可以自己編寫一個XML的語法分析器,但是實際上微軟已經給我們提供了一個XML語法解析器,如果你安裝了IE5.0以上版本的話,實際上你就已經安裝了XML語法解析器。可以從微軟站點(www.microsoft.com)下載最新的MSXML的SDK和Parser文件。它是一個叫做MSXML.DLL的動態鏈接庫,最新版本爲msxml3,實際上它是一個COM對象庫,裏面封裝了所有進行XML解析所需要的所有必要的對象。因爲COM是一種以二進制格式出現的和語言無關的可重用對象。所以你可以用任何語言(比如VB,VC,DELPHI,C++ Builder甚至是劇本語言等等)對它進行調用,在你的應用中實現對XML文檔的解析。下面的關於XML文檔對象模型的介紹是基於微軟最新的msxml3爲基礎進行的。
三、XML文檔對象(XML DOM)模型分析
XML DOM對象提供了一個標準的方法來操作存儲在XML文檔中的信息,DOM應用編程接口(API)用來作爲應用程序和XML文檔之間的橋樑。
DOM可以認爲是一個標準的結構體系用來連接文檔和應用程序(也可以是劇本語言)。MSXML解析器允許你裝載和創建一個文檔,收集文檔的錯誤信息,得到和操作文檔中的所有的信息和結構,並把文檔保存在一個XML文件中。DOM提供給用戶一個接口來裝載、到達和操作並序列化XML文檔。DOM提供了對存儲在內存中的XML文檔的一個完全的表示,提供了可以隨機訪問整個文檔的方法。DOM允許應用程序根據MSXML解析器提供的邏輯結構來操作XML文檔中的信息。利用MSXML所提供的接口來操作XML。
實際上MSXML解析器根據XML文檔生成一個DOM樹結構,它能夠讀XML文檔並根據XML文檔內容創建一個節點的邏輯結構,文檔本身被認爲是一個包含了所有其他節點的節點。
DOM使用戶能夠把文檔看成是一個有結構的信息樹,而不是簡單的文本流。這樣應用程序或者是劇本即使不知道XML的語義細節也能夠方便的操作該結構。DOM包含兩個關鍵的抽象:一個樹狀的層次、另一個是用來表示文檔內容和結構的節點集合。樹狀層次包括了所有這些節點,節點本身也可以包含其他的節點。這樣的好處是對於開發人員來說,他可以通過這個層次結構來找到並修改相應的某一個節點的信息。DOM把節點看成是一個通常的對象,這樣就有可能創建一個劇本來裝載一個文檔,然後遍歷所有的節點,顯示感興趣的節點的信息。注意節點可以有很多中具體的類型,比如元素、屬性和文本都可以認爲是一個節點。
微軟的MSXML解析器讀一個XML文檔,然後把它的內容解析到一個抽象的信息容器中稱爲節點(NODES)。這些節點代表文檔的結構和內容,並允許應用程序來讀和操作文檔中的信息而不需要顯示的知道XML的語義。在一個文檔被解析以後,它的節點能夠在任何時候被瀏覽而不需要保持一定的順序。
對開發人員來說,最重要的編程對象是DOMDocument。DOMDocument對象通過暴露屬性和方法來允許你瀏覽,查詢和修改XML文檔的內容和結構,每一個接下來的對象暴露自己的屬性和方法,這樣你就能夠收集關於對象實例的信息,操作對象的值和結構,並導航到樹的其他對象上去。
MSXML.DLL所包括的主要的COM接口有:
(1)DOMDocument
DOMDocument對象是XML DOM的基礎,你可以利用它所暴露的屬性和方法來允許你瀏覽、查詢和修改XML文檔的內容和結構。DOMDocument表示了樹的頂層節點。它實現了DOM文檔的所有的基本的方法並且提供了額外的成員函數來支持XSL和XSLT。它創建了一個文檔對象,所有其他的對象都可以從這個文檔對象中得到和創建。
(2)IXMLDOMNode
IXMLDOMNode是文檔對象模型(DOM)中的基本的對象,元素,屬性,註釋,過程指令和其他的文檔組件都可以認爲是IXMLDOMNode,事實上,DOMDocument對象本身也是一個IXMLDOMNode對象。
(3)IXMLDOMNodeList
IXMLDOMNodeList實際上是一個節點(Node)對象的集合,節點的增加、刪除和變化都可以在集合中立刻反映出來,可以通過“for...next”結構來遍歷所有的節點。
(4)IXMLDOMParseError
IXMLDOMParseError接口用來返回在解析過程中所出現的詳細的信息,包括錯誤號,行號,字符位置和文本描述。
下面主要描述一個DOMDocument對象的創建過程,這裏我們用VC描述創建一個文檔對象的過程。
HRESULT hr;
IXMLDomDocument* pXMLDoc;
IXMLDOMNode* pXDN;
Hr=CoInitialize(NULL); //COM的初始化
//得到關於IXMLDOMDocument接口的指針pXMLDOC。
hr=CoCreateInstance(CLSID_DOMDocument,NULL,CLSCTX_INPPROC_SERVER,
IID_IXMLDOMDocument,(void**)&pXMLDoc);
//得到關於IXMLDOMNode接口的指針pXDN。
hr=pXMLDoc->QueryInterface(IID_IXMLDOMNode,(void**)&pXDN);
在MSXML解析器使用過程中,我們可以使用文檔中的createElement方法來創建一個節點裝載和保存XML文件。通過load或者是loadXML方法可以從一個指定的URL來裝載一個XML文檔。Load(LoadXML)方法帶有兩個參數:第一個參數xmlSource表示需要被解析的文檔,第二個參數isSuccessful表示文檔裝載是否成功。Save方法是用來把文檔保存到一個指定的位置。Save方法有一個參數destination用來表示需要保存的對象的類型,對象可以是一個文件,一個ASP Response方法,一個XML文檔對象,或者是一個能夠支持持久保存(persistence)的客戶對象。下面是save方法使用的一個簡單的例子(具體程序請參見http://www.swm.com.cn/swm/200101/利用MSXML解析XML文本)。
同時,在解析過程中,我們需要得到和設置解析標誌。利用不同的解析標誌,我們可能以不同的方法來解析一個XML文檔。XML標準允許解析器驗證或者不驗證文檔,允許不驗證文檔的解析過程跳過對外部資源的提取。另外,你可能設置標誌來表明你是否要從文檔中移去多餘的空格。
爲了達到這個目的,DOMDocument對象暴露了下面幾個屬性,允許用戶在運行的時候改變解析器的行爲:
(1)Async(相對於C++是兩個方法,分別爲get_async和put_async)
(2)ValidateOnparse (相對於C++是兩個方法,分別爲get_validateOnParse和 put_validateOnParse)
(3)ResolveExternals(相對於C++是兩個方法,分別爲get_ ResolveExternals和put_ ResolveExternals)
(4)PersercveWhiteSpace(相對於C++是兩個方法,分別爲get_ PersercveWhiteSpace和put_ PersercveWhiteSpace)
每一個屬性可以接受或者返回一個Boolean值。缺省的,anync,validateOnParse,resolveExternals的值爲TRUE,perserveWhiteSpace的值跟XML文檔的設置有關,如果XML文檔中設置了xml:space屬性的話,該值爲FALSE。
同時在文檔解析過程中可以收集一些和文檔信息的信息,實際上在文檔解析過程中可以得到以下的信息:
(1)doctype(文檔類型):實際上是和用來定義文檔格式的DTD文件。如果XML文檔沒有相關的DTD文檔的話,它就返回NULL。
(2)implementation(實現):表示該文檔的實現,實際上就是用來指出當前文檔所支持的XML的版本。
(3)parseError(解析錯誤):在解析過程中最後所發生的錯誤。
(4)readyState(狀態信息):表示XML文檔的狀態信息,readyState對於異步使用微軟的XML解析器來說的重要作用是提高了性能,當異步裝載XML文檔的時候,你的程序可能需要檢查解析的狀態,MSXML提供了四個狀態,分別爲正在狀態,已經狀態,正在解析和解析完成。
(5)url(統一資源定位):關於正在被裝載和解析的XML文檔的URL的情況。注意如果該文檔是在內存中建立的話,這個屬性返回NULL值。
在得到文檔樹結構以後,我們可以操作樹中的每一個節點,可以通過兩個方法得到樹中的節點,分別爲nodeFromID和getElementsByTagName。
nodeFromID包括兩個參數,第一個參數idString用來表示ID值,第二個參數node返回指向和該ID相匹配的NODE節點的接口指針。注意根據XML的技術規定,每一個XML文檔中的ID值必須是唯一的而且一個元素(element)僅且只能和一個ID相關聯。
getElementsByTagName方法有兩個參數,第一個參數tagName表示需要查找的元素(Element)的名稱,如果tagName爲“*”的話返回文檔中所有的元素(Element)。第二個參數爲resultList,它實際是指向接口IXMLDOMNodeList的指針,用來返回和tagName(標籤名字)相關的所有的Node的集合。
下面是一個簡單的例子
下面是save方法使用的一個簡單的例子:
BOOL DOMDocSaveLocation()
{
BOOL bResult = FALSE;
IXMLDOMDocument *pIXMLDOMDocument = NULL;
HRESULT hr;
try
{
_variant_t varString = _T("D://sample.xml");
// 這裏需要創建一個DOMDocument對象和裝載XML文檔,代碼省略.
hr = pIXMLDOMDocument->save(varString); //保存文檔到D://sample.xml中去。
if(SUCCEEDED(hr))
bResult = TRUE;
}
catch(...)
{
DisplayErrorToUser();
// 這裏需要釋放對IXMLDOMDocument接口的引用,代碼省略。
}
return bResult;
}
例子2
IXMLDOMDocument *pIXMLDOMDocument = NULL;
wstring strFindText (_T("author"));
IXMLDOMNodeList *pIDOMNodeList = NULL;
IXMLDOMNode *pIDOMNode = NULL;
long value;
BSTR bstrItemText;
HRESULT hr;
try
{
// 創建一個DOMDocument文檔對象,並裝載具體文檔,相關代碼省略。
//下面的代碼用來得到一個和標籤名稱author相關的所有的節點集合
hr = pIXMLDOMDocument->getElementsByTagName(
(TCHAR*)strFindText.data(), &pIDOMNodeList);
SUCCEEDED(hr) ? 0 : throw hr;
//是否正確的得到了指向IDOMNodeList的指針。
hr = pIDOMNodeList->get_length(&value); //得到所包含的NODE節點的個數
if(SUCCEEDED(hr))
{
pIDOMNodeList->reset();
for(int ii = 0; ii < value; ii++)
{
//得到具體的一個NODE節點
pIDOMNodeList->get_item(ii, &pIDOMNode);
if(pIDOMNode )
{
pIDOMNode->get_text(&bstrItemText); //得到該節點相關的文本信息
::MessageBox(NULL, bstrItemText,strFindText.data(), MB_OK);
pIDOMNode->Release();
pIDOMNode = NULL;
}
}
}
pIDOMNodeList->Release();
pIDOMNodeList = NULL;
}
catch(...)
{
if(pIDOMNodeList)
pIDOMNodeList->Release();
if(pIDOMNode)
pIDOMNode->Release();
DisplayErrorToUser();
}
簡單的實例程序
#include < atlbase.h>
//下面的.h文件是在安裝了最新的XML Parser以後所包含的.h文件。
#include "C:/Program Files/Microsoft XML Parser SDK/inc/msxml2.h"
#include < iostream>
void main()
{
// 初始化COM接口
CoInitialize(NULL);
//在程序中,我們假定我們裝載的XML文件名稱爲xmldata.xml,它缺省的和可執行文
//件在同一個目錄中。該文件的內容如下:
// < ?xml version="1.0"?>
// < xmldata>
// < xmlnode />
// < xmltext>Hello, World!< / xmltext>
// < /xmldata>
//
//程序將尋找名爲“xmlnode”的節點,然後插入一個新的名稱爲“xmlchildnode”的
//節點,然後它去尋找一個名爲“xmltest”的節點,然後提取包含在節點中的文本並顯
//示它。最後它把新的改變過的XML文檔保存在名稱爲“updatexml.xml”的文檔中。
try {
// 通過智能指針創建一個解析器的實例。
CComPtr< IXMLDOMDocument> spXMLDOM;
HRESULT hr = spXMLDOM.CoCreateInstance(__uuidof(DOMDocument));
if ( FAILED(hr) ) throw "不能創建XML Parser對象";
if ( spXMLDOM.p == NULL ) throw "不能創建XML Parser對象";
// 如果對象創建成功的話,就開始裝載XML文檔
VARIANT_BOOL bSuccess = false;
hr = spXMLDOM->load(CComVariant(L"xmldata.xml"),&bSuccess);
if ( FAILED(hr) ) throw "不能夠在解析器中裝載XML文檔";
if ( !bSuccess ) throw "不能夠在解析器中裝載XML文檔";
// 檢查並搜索"xmldata/xmlnode"
CComBSTR bstrSS(L"xmldata/xmlnode");
CComPtr< IXMLDOMNode> spXMLNode;
//用接口IXMLDOMDocument的方法selectSingleNode方法定位該節點
hr = spXMLDOM->selectSingleNode(bstrSS,&spXMLNode);
if ( FAILED(hr) ) throw "不能在XML節點中定位''xmlnode'' ";
if ( spXMLNode.p == NULL ) throw "不能在XML節點中定位''xmlnode'' ";
//DOM對象“spXMLNode”現在包含了XML節點< xmlnode>,所以我們可以在
//它下面創建一個子節點並把找到的該節點作爲它的父節點。
CComPtr< IXMLDOMNode> spXMLChildNode;
//用接口IXMLDOMDocument的方法createNode方法創建一個新節點。
hr = spXMLDOM->createNode(
CComVariant(NODE_ELEMENT),
CComBSTR("xmlchildnode"),
NULL,&spXMLChildNode);
if ( FAILED(hr) ) throw "不能創建''xmlchildnode'' 節點";
if ( spXMLChildNode.p == NULL )
throw "不能創建''xmlchildnode'' 節點";
//添加新節點到spXMLNode節點下去。
CComPtr< IXMLDOMNode> spInsertedNode;
hr = spXMLNode->appendChild(spXMLChildNode,&spInsertedNode);
if ( FAILED(hr) ) throw "不能創建''xmlchildnode'' 節點";
if ( spInsertedNode.p == NULL ) throw "不能移動''xmlchildnode'' 節點";
//對新節點添加屬性。
CComQIPtr< IXMLDOMElement> spXMLChildElement;
spXMLChildElement = spInsertedNode;
if ( spXMLChildElement.p == NULL )
throw "不能在XML元素接口中查詢到''xmlchildnode'' ";
//設置新節點的屬性
hr = spXMLChildElement->setAttribute(CComBSTR(L"xml"),CComVariant(L"fun"));
if ( FAILED(hr) ) throw "不能插入新的屬性";
//下面的程序段用來尋找一個節點並顯示該節點的相關信息
// 查找"xmldata/xmltext"節點
spXMLNode = NULL; // 釋放先前的節點
bstrSS = L"xmldata/xmltext";
hr = spXMLDOM->selectSingleNode(bstrSS,&spXMLNode);
if ( FAILED(hr) ) throw "不能定位''xmltext''節點";
if ( spXMLNode.p == NULL ) throw "不能定位''xmltext''節點";
// 得到該節點包含的文本並顯示它
CComVariant varValue(VT_EMPTY);
hr = spXMLNode->get_nodeTypedValue(&varValue);
if ( FAILED(hr) ) throw "不能提取''xmltext''文本";
if ( varValue.vt == VT_BSTR ) {
// 顯示結果,注意這裏要把字符串從形式BSTR轉化爲ANSI
USES_CONVERSION;
LPTSTR lpstrMsg = W2T(varValue.bstrVal);
std::cout < < lpstrMsg < < std::endl;
} // if
else {
// 如果出現錯誤
throw "不能提取''xmltext''文本";
} // else
//保存修改過的XML文檔到指定的文檔名
hr = spXMLDOM->save(CComVariant("updatedxml.xml"));
if ( FAILED(hr) ) throw "不能保存修改過的XML文檔";
std::cout < < "處理完成..." < < std::endl < < std::endl;
} // try
catch(char* lpstrErr) {
// 出現錯誤
std::cout < < lpstrErr < < std::endl < < std::endl;
} // catch
catch(...) {
// 未知錯誤
std::cout < < "未知錯誤..." < < std::endl < < std::endl;
} // catch
// 結束對COM的使用
CoUninitialize();
}
最後我們討論一下如何來創建新的節點,實際上可以通過方法createNode來創建一個新的節點。CreateNode包括四個參數,第一個參數Type表示要創建的節點的類型,第二個參數name表示新節點的nodeName的值,第三個參數namespaceURI表示該節點相關的名字空間,第四個參數node表示新創建的節點。注意可以通過使用已經提供的類型(Type),名稱(name)和名字空間(nodeName)來創建一個節點。
當一個節點被創建的時候,它實際上是在一個名字空間範圍(如果已經提供了名字空間的話)內創建的。如果沒有提供名字空間的話,它實際上是在文檔的名字空間範圍內創建的。
四、利用MSXML進行XML文檔分析的簡單實例
爲了說明如何在VC中使用XML DOM模型,這裏我們顯示了一個簡單的實例程序(具體程序請參見www.swm.com.cn/swm/200101/利用MSXML解析XML文本),是一個Console Application。下面是主要的程序代碼,本代碼用來在一個XML文檔中定位一個特殊的Node節點,並插入一個新的子節點。
五、總結
XML文檔因爲有着比HTML嚴格的多的語法要求,所以使用和編寫一個XML解析器要比編寫一個HTML的解析器要容易的多。同時因爲XML文檔不僅可以標記文檔的顯示屬性,更重要的是它標記了文檔的結構和包含信息的特徵,所以我們可以方便的通過XML解析器來獲取特定節點的信息並加以顯示或修改,方便了用戶對XML文檔的操作和維護。同時我們需要注意的是XML是一種開放的結構體系並不依賴於任何一家公司,所以開發基於XML的應用必然會得到絕大多數軟件開發平臺的支持。另外,我們也可以看到,象微軟這樣的軟件開發主流企業也把目光定位在基於XML+COM的體系上,無論是微軟的Office系列、Web服務器和瀏覽器還是數據庫產品(SQL Server)都已經開始支持基於XML的應用。通過XML來定製應用程序的前端,COM來實現具體的業務對象和數據庫對象,使系統具有更加靈活的擴展性和維護性。
文章出處:飛諾網(www.firnow.com):http://dev.firnow.com/course/3_program/vc/vc_js/2008311/104123_2.html