使用 MSXML 分析器处理 XML 文档
2007-03-30 21:31:59 来源:WEB开发网在 Kenn Scribner 近期有关 XML 和 MSXML DOM 分析器的文章中,仅介绍了该分析器的部分功能。这些文章将 XML 作为一种技术进行了说明,但是并没有介绍 XML 分析器本身。现在,Kenn 将回过头来介绍 MSXML 分析器,并讲解处理 XML 文档和节点所需的基本知识:搜索特定的节点、插入节点和检索节点值。
MSXML 分析器基于 XML 文档对象模型,对于查看表 1 中所示的各种文档对象来说,它非常重要。这些对象直接出自 XML 规范本身。MSXML 还可以进一步将 XML DOM 对象合并到 COM 中。因此,弄清楚哪个 XML DOM 对象对应于哪个 MSXML COM 接口非常容易。例如,IXMLDOMNode 代表称为 Node 的 DOM 对象。
表 1. XML DOM 对象及其用途 | |
DOM 对象 | 用途 |
DOMImplementation | 一个查询对象,用于确定 DOM 支持的级别 |
DocumentFragment | 表示树的一部分(可进行剪切/粘贴操作) |
Document | 表示树中的顶级节点 |
NodeList | 用于访问 XML 节点的 Iterator 对象 |
Node | 用于扩展带核心 XML 标记的元素 |
NamedNodeMap | 命名空间支持和迭代通过属性节点集合 |
CharacterData | 文本操作对象 |
Attr | 表示元素的属性 |
Element | 表示 XML 元素的节点(可用于访问属性) |
Text | 表示给定元素或属性对象的文本内容 |
CDATASection | 用于屏蔽 XML 部分,使其不受分析和验证 |
Notation | 包含基于 DTD 或架构内的表示法 |
Entity | 表示已分析或未分析的实体 |
EntityReference | 表示实体引用节点 |
ProcessingInstruction | 表示处理指令 |
虽然有时比较容易混淆,但是 XML 文档对象可以是(并且通常是)多态的。即,“节点”同时也是一个“元素”。当您试图确定需要何种 DOM 对象来执行何种操作时,这有时会造成混淆。可以使用“文档”对象来创建 DOM“节点”,但是,如果要向新创建的节点添加属性,就必须通过其作为“元素”的一面来访问它。如果说存在一种将对象和操作关联在一起的神奇模式,那么我还没能从自己的日常工作中将它提炼出来。我发现自己仍需要不断参考 MSDN 文档来查看哪个 COM 接口提供了所需的方法以执行我试图完成的任务。各种对象方法看上去的确是按逻辑分组的,这也正是我对 DOM 当初的开发模式的推断(通过分组逻辑操作)。
因此,其中的诀窍就在于从 MSXML 分析器检索适当的 DOM 对象,这一操作的具体实现就是 COM 对象。操作的基本模式将是:首先实例化 MSXML COM 对象本身的一个副本,然后从该副本请求或以其他方式获取指向附加 XML DOM 对象(本身也是 COM 对象)的指针。
MSXML DOM 试验应用程序创建一个漂亮的应用程序,演示众多的 MSXML 功能,这很简单,但实际上,附加的代码只会画蛇添足。相反,我选择了开发一个简单的基于控制台的应用程序,该应用程序执行四种基本操作:
• | 从磁盘加载一个 XML 文件。 |
• | 搜索特定的节点,并向该节点插入一个子节点。 |
• | 搜索另一个节点,并显示该节点内包含的(文本)值。 |
• | 将修改后的 XML 文档保存回磁盘中。 |
为了进一步简化,我硬编码了 XML 文档文件的名称和 XML 节点本身。当然,如果这是一个真实的应用程序,您可能很少(或者永远不会)采用这样的方法。但是在本例中,进行这些权衡,是为了简化围绕在 MSXML 功能两边的代码。
像平常一样,在示例应用程序中,我选择了使用 ATL 来包装许多与 COM 有关的活动。您肯定看到我使用了 CComPtr 和 CComQIPtr 对象,但是我还额外加入了几个 CComBSTR 和 CComVariant 对象。如果您不熟悉它们,只需要记住它们是用于处理一些细节的模板,这些细节对于本文的主旨来说并非至关重要,但是从更广的角度讲,还是比较重要的。真正重要的是看到如何搜索 XML 节点,添加新的(具有属性的)节点,以及显示节点内包含的文本。
我的基于控制台的应用程序可以在附带的 下载文件中找到,它将加载一个名为 xmldata.xml 的 XML 文档文件(假定其与可执行文件位于同一个目录中),并假定该文档包含以下 XML 数据:
<?xml version="1.0"?>
<xmldata>
<xmlnode />
<xmltext>Hello, World!</xmltext>
</xmldata>
我们将首先搜索 xmlnode 节点,如果找到了该节点,我们将插入一个新的(带有属性的)节点作为其子级。生成的 XML 文档将为:
<?xml version="1.0"?>
<xmldata>
<xmlnode>
<xmlchildnode xml="fun" />
</xmlnode>
<xmltext>Hello, World!</xmltext>
</xmldata>
打印 节点内包含的信息 ("Hello, World!") 之后,我们将把该新 XML 文档保存到名为 updatedxml.xml 的文件中。然后,就可以使用文本编辑器或 Internet Explorer 5.x 来查看结果。现在让我们转到代码。
应用程序首先初始化了 COM 运行库,然后创建了 MSXML 分析器的一个实例:
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";
如果创建分析器实例成功,接下来,我们将把 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";
搜索节点与文档对象有关,因此,我们将使用 IXMLDOMDocument::selectSingleNode() 来根据其名称查找特定的 XML 节点。其他的技巧很多,但是如果准确地知道要查找的节点的名称,这是最直接的方法:
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";
一些您应当了解的其他方法包括 IXMLDOMDocument::nodeFromID() 和 IXMLDOMElement::getElementsByTagName(),使用它们可以获得文档中的节点的列表。您还可以将文档作为树来进行访问,并依次通过它(获取子节点,获取同辈节点等)。
任一种情况下,搜索的结果都是一个 MSXML 节点对象 IXMLDOMNode。文档中必须存在该节点,否则搜索将失败。我的应用程序使用该节点作为一个全新 XML 节点的父级,该新节点是由 XML 文档对象创建的:
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";
如果分析器可以创建该节点,下一步就是将它放到 XML 树中。IXMLDOMNode::appendChild() 正是完成这一任务的方法:
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";
如果父节点的确将新创建的节点插入为其子级,将返回另一个 IXMLDOMNode 实例,该实例表示新的子节点。实际上,该新子节点和传递给 appendChild() 的节点是同一个 XML 节点。由于在存在问题时附加的子节点的指针将为 Null,因此,检查该指针很有用。
到目前为止,我找到了一个特定的节点,并为它创建了一个新的子节点,下面,让我们看看如何处理属性。假定您要将该属性添加到新的子节点:
xml="fun"
这并不难,但是您必须从 IXMLDOMNode 切换到 IXMLDOMElement,以便访问该子节点的元素特征。在实践中,这意味着您必须查询 IXMLDOMNode 接口的相关 IXMLDOMElement 接口,查明后,再调用 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";
此时,已经修改了 XML 树,并创建了所需的树。应用程序可以在这个时候将文档保存到磁盘,或者执行其他任务。现在,让我们来搜索另一个节点并显示该节点所包含的值(文本)。您已经了解了如何搜索节点,因此,我们将直接讲解数据提取。
提取节点数据的关键在于使用 IXMLDOMNode::get_nodeTypedValue()。可以使用 Microsoft 数据类型架构来标识节点所包含的数据,因此可以方便地存储浮点值、整数、字符串或该架构所支持的任何数据类型。可以使用 dt:type 属性来指定数据类型,如下所示:
<model dt:type="string">SL-2</model>
<year dt:type="int">1992</year>
如果特定的节点具有指定的数据类型,就可以使用 get_nodeTypedValue() 以该格式提取数据。如果未指定数据类型,将假定数据为文本,分析器将返回具有 BSTR 数据的 VARIANT。在本例中,这没有任何问题,因为我们要搜索的节点是一个实际上包含一个字符串的文本节点。在需要时,始终可以使用 atoi() 等方法将字符串转换为其他形式。本例中,我们只是提取该字符串数据并显示它:
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";
}
如果能够检索与节点关联的值,并且该值为 BSTR(预期的数据类型),我们将在屏幕上显示该文本。如果不能,将显示一条错误消息,不过,根据情况而定,可以方便地采取其他操作。
最后一项与 XML 有关的操作是将已更新的 XML 树保存到磁盘,这一任务是使用 IXMLDOMDocument::save() 完成的:
hr = spXMLDOM->save(CComVariant("updatedxml.xml"));
if ( FAILED(hr) )
throw "Unable to save updated XML document";
完成保存后,向屏幕写一条简短说明,并退出。
这个示例应用程序无论如何都算不上漂亮。您可以让自己的应用程序执行很多其他功能,但我希望您通过这个简短的示例了解到了如何从 C++ 程序使用 MSXML 分析器。该分析器本身是一个复杂的软件,无论怎样强调使用 MSDN Library 作为参考,都不能算是过份。该分析器公开了许多接口,这些接口通常会公开许多方法。即便如此,我在自己的项目中仍频繁地使用该分析器,在亲自编写了一些代码并进行试验后,我发现这个软件制作很精良 并且便于使用。我希望您也同样会发现该分析器和一般意义上的 XML 具有广泛的用途。
更多精彩
赞助商链接