WEB开发网
开发学院软件开发VC 使用 MSXML 分析器处理 XML 文档 阅读

使用 MSXML 分析器处理 XML 文档

 2007-03-30 21:31:59 来源:WEB开发网   
核心提示:在 Kenn Scribner 近期有关 XML 和 MSXML DOM 分析器的文章中,仅介绍了该分析器的部分功能,使用 MSXML 分析器处理 XML 文档,这些文章将 XML 作为一种技术进行了说明,但是并没有介绍 XML 分析器本身,在亲自编写了一些代码并进行试验后,我发现这个软件制作很精良 并且便于使用,现在

在 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 具有广泛的用途。

Tags:使用 MSXML 分析器

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接