XML DOM (文档对象模型)对象提供了一个标准的方法来操作存储在XML文档中的信息,这就是DOM应用编程接口(API)函数。它是应用程序和XML文档之间的桥梁。DOM包含两个关键的抽象概念:一个是树状的层次结构,另一个是用来表示文档内容和结构的节点集合。树状层次包括了所有节点,节点本身也可以包含其他的节点。这样的好处是可以通过这个层次结构来找到并修改某一特定节点的信息。 |
微软的MSXML解析器读取一个XML文档,然后把它的内容解析到一个抽象的信息容器中,该信息容器被称为节点(NODES)。这些节点代表文档的结构和内容,并允许应用程序来操作文档中的信息而不需要知道XML的语义。一个文档被解析后,它的节点能够在任何时候被浏览而不需要保持一定的顺序。 |
对开发人员来说,最重要的编程对象是DOMDocument。 DOMDocument对象通过暴露的属性和方法来允许浏览、查询和修改XML文档的内容和结构。 |
本文主要介绍DOM的结构和应用,同时用VC编程语言给出了通过MSXML进行XML解析的实例。 |
DOMDocument对象的结构和应用 |
文档对象的创建 |
HRESULT hr; |
IXMLDomDocument* pXMLDoc; |
IXMLDOMNode* pXDN; |
//COM的初始化 |
Hr=CoInitialize(NULL); |
/*得到关于IXMLDOMDocument |
接口的指针pXMLDOC*/ |
hr=CoCreateInstance(CLSID_DOMDocument,NULL, |
CLSCTX_INPPROC_SERVER,IID_IXMLDOMDocument, |
(void**)&pXMLDoc); |
//得到关于IXMLDOMNode接口的指针pXDN |
hr=pXMLDoc->QueryInterface(IID_IXMLDOM |
Node,(void**)&pXDN); |
在MSXML解析器的使用过程中,我们可以使用文档中的CreateElement方法创建一个节点来装载和保存XML文件,也可以通过Load或者是 LoadXML方法从一个指定的URL来装载一个XML文档。Load(LoadXML)方法带有两个参数:第一个参数xmlSource表示需要被解析的文档,第二个参数isSuccessful表示文档装载是否成功。 |
文档对象的保存 |
Save方法是用来把文档保存到一个指定的位置。Save方法中参数destination用来表示需要被保存的对象的类型,对象可以是一个文件、一个ASP Response方法、一个XML文档对象,或者是一个能够支持持久保存(persistence)的客户对象。下面是使用Save方法的一个例子程序的部分代码: |
BOOL DOMDocSaveLocation() |
{ |
BOOL bResult = FALSE; |
IXMLDOMDocument *pIXMLDOMDocument = NULL; |
HRESULT hr; |
try |
{ |
_variant_t varString = _T(“D://sample.xml"); |
/* 这里省略了创建一个DOMDocument |
对象和装载XML文档的代码*/ |
//将文档保存到D://sample.xml中去 |
hr=pIXMLDOMDocument->save(varString); |
if(SUCCEEDED(hr)) |
bResult = TRUE; |
} |
catch(...) |
{ |
DisplayErrorToUser(); |
/*这里省略了释放对IXMLDOMDocument |
接口的引用的代码*/ |
} |
return bResult; |
} |
设置解析标志 |
在解析过程中,我们需要得到和设置解析标志。利用不同的解析标志,我们可以用不同的方法来解析一个XML文档。XML标准允许解析器验证或者不验证文档,允许不验证文档的解析过程跳过对外部资源的提取,还可以设置标志来表明是否要从文档中移去多余的空格。DOMDocument对象暴露了如下几个属性,允许用户在运行的时候利用它们改变解析器的行为。 |
1.Async属性方法:get_Async和put_Async。 |
2.ValidateOnParse属性方法:get_ValidateOnParse和 put_ValidateOnParse。 |
3.ResolveExternals属性方法:get_ ResolveExternals和put_ ResolveExternals。 |
4.PreserveWhiteSpace属性方法:get_ PreserveWhiteSpace和put_ PreserveWhiteSpace。 |
每一个属性可以接受或者返回一个Boolean值。缺省情况下,Async、ValidateOnParse、ResolveExternals的值为TRUE,PreserveWhiteSpace的值跟 XML文档的设置有关,如果XML文档中设置了xml:space属性的话,该值为FALSE。 |
在文档解析过程中可以收集到以下的信息: |
1.doctype(文档类型):是用来定义文档格式的DTD文件。如果XML文档没有相关的 DTD文档的话,它就返回NULL。 |
2.implementation(实现):表示该文档的实现,用来指出当前文档所支持的XML的版本。 |
3.parseError(解析错误):指出在解析过程中最后所发生的错误。 |
4.readyState(状态信息):表示XML文档的状态信息。readyState对于异步使用微软的XML 解析器来说重要的作用是提高了性能。当异步装载XML文档的时候,程序可能需要检查解析的状态,MSXML提供了4个状态,分别为正在状态、已经状态、正在解析和解析完成。 |
5.url(统一资源定位):表示正在被装载和解析的XML文档的URL的情况。如果该文档是在内存中建立的话,这个属性返回NULL值。 |
节点的操作 |
在得到文档树结构以后,我们可以操作树中的每一个节点,一般通过两个方法得到树中的节点,分别为nodeFromID和getElementsByTagName。 |
nodeFromID包括两个参数,第一个参数idString用来表示ID值,第二个参数node返回指向和该ID相匹配的节点的接口指针。根据XML的技术规定,每一个XML文档中的ID值必须是唯一的,而且一个元素(element)只能和一个ID 相关联。 |
getElementsByTagName方法有两个参数,第一个参数 tagName表示需要查找的元素(Element)名称,如果tagName为“*”则返回文档中所有的元素。第二个参数为resultList,它实际是指向接口IXMLDOMNodeList的指针,用来返回和 tagName(标签名字)相关的所有节点的集合。 |
下面是相关例子程序的部分代码: |
IXMLDOMDocument *pIXMLDOMDocument = NULL; |
wstring strFindText (_T(“author")); |
IXMLDOMNodeList *pIDOMNodeList = NULL; |
IXMLDOMNode *pIDOMNode = NULL; |
long value; |
BSTR bstrItemText; |
HRESULT hr; |
try |
{ |
/*此处省略创建一个DOMDocument |
文档对象并装载具体文档的代码*/ |
/*下面的代码用来得到一个和标签名称 |
author相关的所有节点的集合*/ |
//是否正确地得到了指向IDOMNodeList的指针 |
hr=pIXMLDOMDocument->getElementsByTagName |
((TCHAR*)strFindText.data(), &pIDOMNodeList); |
SUCCEEDED(hr) ? 0 : throw hr; |
//得到所包含的节点的个数 |
hr = pIDOMNodeList->get_length(&value); |
if(SUCCEEDED(hr)) |
{ |
pIDOMNodeList->reset(); |
for(int ii = 0; ii < value; ii++) |
{ |
//得到具体的一个节点 |
pIDOMNodeList->get_item(ii,&pIDOM |
Node); |
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(); |
} |
可以通过方法CreateNode来创建一个新的节点。 CreateNode包括4个参数,第一个参数type表示要创建的节点的类型,第二个参数name表示新节点的nodeName的值,第三个参数namespaceURI表示和该节点相关的名字空间,第四个参数node表示新创建的节点。可以通过使用已经提供的类型(type)、名称(name)和名字空间(nodeName)来创建一个新节点。 |
当一个节点被创建的时候,它实际上是在一个名字空间范围(如果已经提供了名字空间的话)内被创建的。如果没有提供名字空间的话,它实际上是在文档的名字空间范围内被创建的。 |
解析XML |
为了说明如何在VC中使用XML DOM模型,这里我们介绍一个简单的Console Application实例程序。下面是主要的程序代码,用来在一个XML 文档中定位一个特殊的节点,并插入一个新的子节点。 |
#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”的节点,然后它再去寻找一个名为“xmltext”的节点,然后提取包含在该节点中的文本并显示它,最后它把新的改变过的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的方法create |
Node方法创建一个新节点。*/ |
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(); |
} |
因为XML文档有比HTML更加严格的语法要求,所以使用和编写一个XML解析器比编写一个HTML的解析器要容易。同时因为XML文档不仅标记文档的显示属性,更重要的是它标记了文档的结构和包含信息的特征,所以我们可以方便地通过XML解析器来获取特定节点的信息并加以显示或修改。 |