转载请注明出处
作者:小马
从题目你应该获取到以下的信息:
1 本文不讲xml,只讲msxml
2 msxml又包括很多方面,比如DOM,SOM,XSLT等,本文只讲DOM
好了,现在开始...
一 关于msxml DOM
什么是msxml
MSXML 是一款微软的 xml 语言解析器, 如果你不了解COM,知道这引起就可以了, 否则的话,你应该知道, msxml实际上一种com组件,所谓com组件,可以理解成一个独立的功能模块, 客户程序员只需获取到组件对象, 然后调用里面的接口进行操作. 对于组件内部如何实现, 不需要关心. com组件有很多实现形式, msxml以dll的形式实现(比如msxml4.dll), 事实上这种实现形式也比较通用. 对于com,不是本文的重点,不方便说太多(事实上,我也说不了多少,嘿嘿), 如果感兴趣,可以看看潘总的那本<<com原理与应用>>
什么是DOM
中文意为XML 文档对象模型, 它定义访问和操作XML文档的标准方法和属性. DOM 将 XML 文档作为一个树形结构,而树叶被定义为节点.它本身是W3C定义的一个标准. 举个例子,比如标准定义了如下的节点类型:Document,DocumentFragment,ProcessingInstruction等.
什么是msxml DOM
msxml DOM可以说是微软按照DOM的标准规范,实现的一组API, 它是msxml中一部分, 下面这幅图来自msdn,很形象的表达了这种关系:
图1
二搭建环境
本文所有分析, 源码测试都是基于以下环境: windows XP, visual studio2005, msxml4.0.语言是c/c++
对于搭建msxml4.0, 可以下载安装包安装, 也可以手动安装. 安装包的实际上是执行下列操作, 把msxml4.dll msxml4a.dll msxml4r.dll三个文件copy
到system32目录下,并用regsvr32注册. 所以你也可以手动执行这些操作.
配置项目环境
vs2005下有两种方式引用msxml4, 静态链接和动态链接.
静态链接
第一步, #include <msxml2.h>
第二步, 在"属性-链接-输入"中加入msxml2.lib.
有一点要说明, 在vc8的安装目录下,MsXml2.Lib, msxml2.h都是存在的, 所以直接以<>的形式包含就可以了.
动态链接
只要把下面两行加到代码里
#import <msxml4.dll> raw_interfaces_only using namespace MSXML2;
关于raw_interfaces_only,简单说明, 它表示使用原始接口,而不是智能指针封装的接口. 因为缺省时import会自动生成符合automation的接口.如果你不想这样,就加上这一句.
三 msxml DOM基本操作
本文只写一些基本的操作,旨在有助于理解和入门, 更多详细的操作和属性的介绍还得查阅msdn.
msxml DOM的有两种操作方式,一种是用原始的接口,一种是用智能指针的方式, 后者用智能指针技术对前者做了一个封装, 可以自动的处理COM对象引用计数以及动态内存管理等方面的内容. 原理上是一致的. 这部分的所有示例代码都是只给出原始接口的操作,并且采动态加载的方式
1 CoInitialize和CoUninitialize
这两个分别是初始化com库和关闭com库的API,所以基于com的应用,都要在操作开始前调用CoInitialize,并在
结束后调用CoUninitialize, MSXML当然也不例外, 如下:
CoInitialize(NULL); .... ....//msxml相关的操作在这里 CoUninitialize();
2 创建对象
MSXML2::IXMLDOMDocument *pxmldoc = NULL; HRESULT hr; hr = CoCreateInstance(__uuidof(DOMDocument40), NULL, CLSCTX_INPROC_SERVER, __uuidof(MSXML2::IXMLDOMDocument), (void**)&pxmldoc); if (FAILED(hr)) { printf("Failed to CoCreate an instance of an XML DOM\n"); }
说明
你可能觉得奇怪, 既然前已经声明了using namespace MSXML2, 定义pxmldoc变量的时候为什么还要加上MSXML2::,
如果不加MSXML2::,你会发现编译的时候会出现类似"IXMLDOMDocument ambiguous symbol"错误, 这是因为vc8本身已经包含了msxml的定义, 在头文件Msxml.h已经有这些类型的定义了, 这就是定 义冲突. 要解决这个问题,显示的把命名空间的前缀加上去就可以了.
关于IXMLDOMDocument, 是一个文档对象,它指向整个xml文档,想了解详细的内容可以查阅msdn.
CoCreateInstance是com里的东东,你只需知道,通过它可以取得文档对象的接口指针就可以了. 有了这个接口指针,才能调用里的成员函数进行各种操作.
HRESULT是COM里用的比较多的数据类型, 一般用做函数的返回值, 对于这种类型最好不要简单的判断hr == 或hr !=, 而是用SUCCEEDED
判断成功,用FAILED判断失败.
3 加载xml
VARIANT var; VARIANT_BOOL status; VariantInit(&var); V_BSTR(&var) = SysAllocString(L"test.xml"); V_VT(&var) = VT_BSTR; pXMLDom->load(var, &status); if (status!=VARIANT_TRUE) { printf("Failed to load xml\n"); if (&var) VariantClear(&var); if (pXMLDom) pXMLDom->Release(); }
说明
load函数的定义如下:
HRESULT load(
VARIANT xmlSource,
VARIANT_BOOL *isSuccessful);
VARIANT,VARIANT_BOOL都COM里的数据类型, 这是一种为了跨平台操作而定义的类型, 你要知道VARIANT变量在用之前最好先
VariantInit,用完了VariantClear释放.你可能奇怪为什么要释放,哪里有动态内存分配吗? 这里:
V_BSTR(&var) = SysAllocString(L"test.xml");
如果load出错, 同时要记得释放pXMLDom,因为前面CoCreateInstance内部实际上已经执行了AddRef操作,你要负责释放(有点晕了吧?)
4 读xml
假设xml文档的内容如下:
<?xml version="1.0" encoding="utf-8"?> <book> <name>Fly</name> <price discount = "80%">23.5</price> </book>
代码:
IXMLDOMNode *pNode=NULL; BSTR bstr = NULL; IXMLDOMElement *pIXMLDOMElement = NULL; if (bstr) SysFreeString(bstr); bstr = SysAllocString(L"book"); BSTR bstrAttributeName = SysAllocString(L"discount"); pXMLDom->selectSingleNode(bstr, &pNode); if (!pNode) { printf("Failed to selectSingleNode\n"); if (bstr) SysFreeString(bstr); if (pXMLDom) pXMLDom->Release(); return; } /////////////////////////////////////////////////////////// pNode->get_xml(&bstr); printf("book.xml:\n%s\n", _com_util::ConvertBSTRToString(bstr)); ////////////////////////////////////////////////////////// if (bstr) SysFreeString(bstr); if (pNode != NULL) { pNode->Release(); pNode = NULL; } bstr = SysAllocString(L"book/price"); pxmldoc->selectSingleNode(bstr, &pNode); if (bstr) SysFreeString(bstr); bstr = SysAllocString(L"discount"); pNode->QueryInterface(__uuidof(MSXML2::IXMLDOMElement), (void **)&pElement); pElement->getAttribute(bstr, &var);//如果成功,var="80%"
说明
上面的代码展示了如下的操作,
1 selectSingleNode获取根结点"book",并调用get_xml输了该结点所有的内容.
2 selectSingleNode获取根结点"book/price"结点, 并调用getAttribute获取该结点中属性"discount"的值.
要注意getAttribute是IXMLDOMElement的方法, IXMLDOMNode无法直接调用, IXMLDOMElement是IXMLDOMNode的子类, IXMLDOMElement的指针可以转化成IXMLDOMNode,但是IXMLDOMNode转化成IXMLDOMElement使用就不推荐了. 一般用下面的方法通 IXMLDOMNode获取IXMLDOMElement.
pNode->QueryInterface(__uuidof(MSXML2::IXMLDOMElement), (void **)&pElement);
5 写xml
VariantClear(&var); V_BSTR(&var) = SysAllocString(L"75%"); V_VT(&var) = VT_BSTR; hr = pElement->setAttribute(bstr, var); VariantClear(&var); V_BSTR(&var) = SysAllocString(L"book.xml"); V_VT(&var) = VT_BSTR; hr = pxmldoc->save(var);
说明
把"discount"的值改为"75%", 主要是save函数, 如果不调用save,你对xml所做的修改,仅限于内存,并没有保存到文件里.
四 附言
最后,对于在网上经常看到的两个问题, 说一下自己的意见.
Q:
msxml DOM的实际应用中,是原始接口好,还是用智能指针接口好?
ans:
建议用智能指针的接口.
一方面, 避免了频繁的处理AddRef, Release, 而且处理不好容易造成内存泄露. 另一方面,从上面的例子也明显感觉到,
智能指针接口的在一些基本的操作上简单很多,比如属性的直接访问.
Q:使用msxml DOM, 有没有必要熟练掌握xml?
ans:
我的经验是, 要看你是做哪一块的应用. 比如你是一个c/c++的工程师, 你只需要
用msxml对xml文档进行基本的读写操作, 那么你只需对xml有个大概的了解就可以了,比如你只要知道你要操作的那些东东在xml中是
什么就可以了. 没有必要通读w3c中xml的规范. 事实上,有个简单的办法可以让你清楚你该了解多少, msdn里在讲到DOM之前,有关于xml
本身的一些基本知识的描述,你把这里面的东东理解就行了.