Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5832993
  • 博文数量: 671
  • 博客积分: 10010
  • 博客等级: 上将
  • 技术积分: 7310
  • 用 户 组: 普通用户
  • 注册时间: 2006-07-14 09:56
文章分类

全部博文(671)

文章存档

2011年(1)

2010年(2)

2009年(24)

2008年(271)

2007年(319)

2006年(54)

我的朋友

分类: C/C++

2009-02-19 17:45:20

1.引言:

  XML (eXtensible Markup Language)语言是SGML语言的子集,它保留了SGML主要的使用功能,同时大大缩减了SGML的复杂性。XML语言系统建立的目的就是使它不仅能够表示文档的内容,而且可以表示文档的结构,这样在同时能够被人类理解的同时,也能够被机器所理解。微软提供了两种进行XML文档解析的接口,一种是基于文档对象模型(DOM)的,另外一种就是SAX(Simple API for XML)。如果你安装了IE5.0以上版本的话,你的系统中实际上就存在了这两种XML语法解析器。可以从微软站点()下载最新的MSXML的SDK和Parser文件。它是一个叫做MSXML.DLL的动态链接库,最新版本为msxml3,实际上它是一个COM对象库,里面封装了所有进行XML解析所需要的所有必要的对象。因为COM是一种以二进制格式出现的和语言无关的可重用对象。所以你可以用任何语言(比如VB,VC,DELPHI,C++ Builder甚至是剧本语言等等)对它进行调用,在你的应用中实现对XML文档的解析。下面的关于XML文档对象模型的介绍是基于微软最新的msxml3为基础进行的。

  2.SAX介绍

  SAX(Simple API For XML)是一个公共的基于事件的XML文档解析标准,这里我们简单介绍一下MSXML是如何通过COM接口实现SAX功能的。首先需要注意的使SAX是一个接口集合,它允许开发人员在自己的应用中通过这个接口集合来读取和分析XML文档中的数据。SAX2是当前微软最新的API。

  SAX最初是为Java编程语言设计的,并使用了Java的接口定义,因为Java语言接口并不是语言中立的,所以各个厂商就需要建立自己的工具把SAX接口映射到它们自己的特殊的语言上去。微软的SAX2给VB和VC提供了接口,当然它们都有自己的接口集合映射到各自的语言和类型定义上去(比如ISAXContentHandler是VC的接口,而IVBSAXContentHandler是VB的接口)。 能够通过一个简单的,快速的方法来对XML文档进行处理,并且和DOM相比的话,它所占用的系统资源更少。我们知道,当你通过DOM来操作一个XML文件的时候,DOM读取该文件,然后把它分割成单个的对象(比如元素,属性和注释等等),然后在内存中创建一个关于该文档的树结构。使用DOM的好处是你可以引用和操作每一个对象。但是为一个文档创建一个树结构,尤其当文档尺寸很大的时候,需要大量的内存空间。

  和DOM不同的是,SAX2是基于事件的,这意味着当它在一个XML文档中发现特殊的符号的时候,它会产生相关的事件。SAX2的优点是当它读到XML文档中每一部分内容的时候,就会产生一个事件,我们的应用程序就可以在这个事件中写入具体的处理代码,然后解析器就移动到文档的下一段。因为SAX2以序列的形式处理文档,它和DOM相比,对内存的需求很少。而且当SAX2找到需要的信息的时候,它能够停止对当前文档的解析。因为SAX不需要在内存中建立整个文档的树结构,SAX和DOM相比,可以被认为是一个轻量级的接口集合。

  我们可以从另一个角度来理解SAX和DOM的区别,如果把它们认为是数据库的游标的话,SAX就类似是只读(read-only)和前向(forward-only)游标,而DOM更象是一个静态(static)的游标允许对记录进行遍历和更新。在开发数据库的时候,我们都知道这两种游标都有自己的适用范围而且都是非常有用的,所以SAX和DOM的作用也可以类似的进行这样的理解。SAX对XML进行处理的流程图如下所示:



  2.1 SAX2和DOM相比的优点

  当需要处理大的文件的时候,SAX对内存的需求很小,因为它并不会因为XML文档尺寸的增加而增加对内存的需求。SAX允许你在任何时候终止解析,这样的一个好处是如果实际上你只需要对文档的一部分信息进行处理的时候,你可以在得到该部分信息以后,就终止对文档的解析。同时,当你想要提取文档中一小部分内容的时候(对许多基于XML的应用来说,实际上没有必要读完整个XML文档),比如,你想要通过扫描数据找到文档中关于某一个特定股票的相关信息的时候,就不需要把不必要的数据放到内存里面,用SAX,你的应用能够扫描数据发现和该股票相关的信息,然后创建一个仅和该部分相关的一个文档结构,这样不仅节省了系统资源,还节省了处理时间。另外,当你想要创建一个新的文档结构时,在一些情况下,你可能想要使用SAX来创建一个高层对象的数据结构,比如股票代码和价格信息,然后和其他的XML文档的数据进行结合,而不是建立一个有关低层的元素、属性、处理指令相关的DOM结构的时候,你可以通过使用SAX更加有效的建立文档结构。特别当系统资源有限的时候,对大规模的文档来说,SAX提供了一个更加有效的方法来解析XML文档。因为SAX可以只处理文档中某一部分的信息,而DOM实际上是根据整个文档建立树状结构并放到内存中,所以如果XML文件很大的话,DOM模型对内存的需要量就很大。

  2.2 SAX的缺陷

  SAX模型也存在一些缺陷,因为整个文档并没有放到内存中,所以它不能随机的到达文档的某一部分,同时也因为整个文档不在内存中,开发人员必须在处理过程中按顺序处理信息,所以SAX在处理包含很多内部交叉引用的文档时就会有一些困难。不能实现复杂的搜索,同时你在处理文档部分信息的时候,必须自己考虑清楚是否保存相关的上下文信息。而且在当前的IE浏览器中还不支持SAX的实现。同时需要注意的是,DOM是已经被W3C承认的标准,而SAX还仅仅是微软自己建立的一个规范。
 3. SAX分析器应用介绍

  3.1 寻找相关的信息

  一些Web站点提供了新闻标题作为他们服务的一部分,有一些站点甚至能够根据用户的喜好进行信息的过滤,使用SAX2,我们就可以创建一个应用来扫描新闻并且只向客户浏览器传递用户感兴趣部分的信息。比如,假设你有一个XML新闻文件,你想为某一个客户从该文件中提取一小部分信息,使用SAX应用,你可以指定要搜索内容的特征,然后搜索并提取那些和这个特征相匹配的信息。

  3.2 得到XML文档的一个子集

  通过SAX2,我们可以从一个大的文件中提取一小部分信息,然后中断对XML文档的解析。比如,假设你要建立一个应用来检验某一个特殊商品的库存,数据文档所包含的商品不止一种,而且每一种的尺寸都很大。使用SAX2,你可以创建一个应用来读这个数据文档,发现该指定商品,并返回该商品的库存数量,然后可以中断对该数据文档的继续分析。这里用SAX2有两个优点,首先,解析器只要读到包含该商品的信息以后就不需要继续解析该数据文档。其次,应用只需要从记录中提取需要的商品数量这个项(item),其他的项(item)可以忽略。

  3.3 基于事件解析的基础

  SAX2解析器读XML文档,然后产生基于特殊符号的事件。SAX2解析器实际上并不为该文档在内存中创建一棵树结构,它序列的处理一个文档的内容并产生相关的事件。

  比如,当你进行基于事件的编程的时候,你可以创建函数来响应用户定义的事件(比如OnClick事件)。在利用SAX进行编程的时候,需要注意的是,是解析器而不是用户产生事件。

  比如考虑下面一个简单的文档。

  

  

  TurboWidget

  


  当SAX2在处理这个文档的时候,它产生如下的一系列的事件:

  StartDocument( )

  StartElement( "parts" )

  StartElement( "part" )

  Characters( "TurboWidget" )

  EndElement( "part" )

  EndElement( "parts" )

  EndDocument( )

可以把SAX2看成是一个有拉特点(PUSH)的解析器,SAX2产生事件,然后你可以自己去处理些事件。实际上,当SAX2在解析一个文档的时候,SAXXMLReader读该文档并产生一系列的事件,你可以选择一些事件进行处理。
4. 创建一个应用SAX的应用程序框架

  SAX2产生的事件包括如下的种类:

  ¨ 和XML文档内容相关的事件(ISAXContentHandler)

  ¨ 和DTD相关的事件(ISAXDTDHandler)

  ¨ 出现错误时发生的事件(ISAXErrorHandler)

  为了处理这些事件,你需要实现一个相关的处理类,该处理类需要包含一些方法来处理相关的事件。你必须对你想要处理的事件实现相关的处理。如果你不想处理某一个事件的话,只需要简单的忽略它就可以。在实际应用中,我们首先要继承这些接口,用C++我们可以创建一个类,在这个类的方法中,我们可以告诉应用程序在接收到一个事件的时候如何进行处理。下面是建立一个基于SAX的应用的基本步骤:

  1. 创建头文件当使用SAX2的时候,我们需要用到动态连接库MSXML.DLL,为了使用MSXML中包含的SAX2接口,你必须在程序的头文件(一般在stdafx.h中)中包含下列的代码:

  #import raw_interfaces_only

  using namespace MSXML2;

  2. 建立具体的操作(handler)类,SAX2主要定义了三个基本的操作类,它们分别是ISAXContentHandler,ISAXDTDHandler和ISAXErrorHandler。

  ISAXContentHandler是用来处理SAX2解析器对文档内容进行解析时所产生的消息的,ISAXXMLReader通过方法putContentHandler来注册这个实例。而ISAXDTDHandler是用来处理和DTD相关的基本的消息的,ISAXXMLReader通过方法putDTDHandler来注册这个实例。ISAXErrorHandler提供了对在解析过程中遇到错误时产生的错误事件的处理,ISAXXMLReader通过方法putErrorHandler来注册这个实例

  因为这三个类都是用来对事件进行处理的,并且需要在接口ISAXXMLReader中进行注册。但是它们的基本使用方法类似,所以我们这里只详细描述对接口ISAXContentHandler 的操作。
ISAXContentHandler接口接收关于文档的内容变化的事件,这是实现SAX应用所需要的最重要的接口,如果应用在遇到基本的解析事件的时候需要被通知的话,ISAXXMLReader通过方法putContentHandler来注册这个实例,然后ISAXXMLReader就使用这个实例来报告基于文档的事件,比如元素的开始,元素的结束和相关的字符串数据等等。ISAXContentHandler 包括了很多的方法:比如startDocument,endDocument,startElement,endElement等等。实际上它包含了好接个startXXX和endXXX对来建立不同的信息集合的抽象。比如startDocument方法在文档信息开始的时候被调用,而在startDocument以后被调用的方法就被认为是文档信息项(item)的子项。在文档信息内容结束的时候endDocument就被调用,表示文档信息的结束。 实际上是SAX2在解析文档的时候,当处于文档某一位置的时候,会激发相应的方法,比如当一个文档开始的时候,就会激发startDocument方法,在实际实现的时候,我们可以在我们继承ISAXContentHandler类的实现类中,重载该方法,实现我们自己想要的处理。我们可以把这些方法看成是ISAXContentHandler接口提供给我们的。需要注意的是事件被处理的顺序和信息在文档中的位置是一致的。

  同时需要注意的是,如果我们需要在我们的应用中对这些消息进行处理的话,我们就要继承处理这些消息的类,比如我们只需要对文档内容进行处理,而忽略对DTD和解析过程中错误(Error)的处理,那么我们只需要创建一个新的类,该类继承ISAXContentHandler接口,因为ISAXContentHandler中定义了很多的事件处理方法,而事实上我们只需要对我们所关心事件的处理方法进行重载,对我们不关心的事件可以简单的忽略它。

   比如我们只关心startElement和endElement事件,而且我们假设我们建立的类的名称为CXMLContentDeal,我们的类就可以如下面所示:

  class CXMLContentDeal : public ISAXContentHandler

  {

   public:

   CXMLContentDeal();

   virtual CXMLContentDeal ();

   virtual HRESULT STDMETHODCALLTYPE startElement(

           /* [in] */ wchar_t __RPC_FAR *pwchNamespaceUri,

           /* [in] */ int cchNamespaceUri,

           /* [in] */ wchar_t __RPC_FAR *pwchLocalName,

           /* [in] */ int cchLocalName,

           /* [in] */ wchar_t __RPC_FAR *pwchRawName,

           /* [in] */ int cchRawName,

           /* [in] */ ISAXAttributes __RPC_FAR *pAttributes);

   irtual HRESULT STDMETHODCALLTYPE endElement(

           /* [in] */ wchar_t __RPC_FAR *pwchNamespaceUri,

           /* [in] */ int cchNamespaceUri,

           /* [in] */ wchar_t __RPC_FAR *pwchLocalName,

           /* [in] */ int cchLocalName,

           /* [in] */ wchar_t __RPC_FAR *pwchRawName,

           /* [in] */ int cchRawName);

        }

然后我们可以重载方法startElement和endElement来进行和应用相关的特殊的处理。
3. 通过接口ISAXXMLReader创建一个解析器。XMLReader是SAX应用实现的主要的接口,XMLReader的作用是这样的。首先,XML的开发人员使用这个接口来注册他们对其他SAX接口的实现(比如ContentHandler,DTDHandler,ErrorHandler等等),另外,XMLREADER通过setFeature和setProperty两个方法来配置SAX解析器的行为,最后,XMLReader封装了解析的功能。示例代码如下:

   ISAXXMLReader* pRdr = NULL;

   HRESULT hr = CoCreateInstance(_uuidof(SAXXMLReader), NULL, CLSCTX_ALL, __uuidof(ISAXXMLReader), (void **)&pRdr);

  4. 创建相应的事件(handler)处理类,这里不妨假设我们只处理和文档内容相关的事件。示例代码如下:

   CXMLContentDeal * pMc = new CXMLContentDeal();

   注意这里CXMLContentDeal是继承接口ISAXContentHandler的类。

  5.在解析器中注册事件处理类,示例代码如下:

   hr = pRdr->putContentHandler(pMc);

  6.开始进行文档的解析,示例代码如下

   hr = pRdr->parseURL(URL); file://这里的URL是指一个具体XML文档的位置

  7.释放解析器对象

   pRdr->Release();

以上就是基于SAX的应用程序的框架结构,我们可以看到,实际的事件处理是在我们的继承类CXMLContentDeal中实现的,在我们这个示例代码中,每当文档中一个新的元素开始的时候,都会激活方法startElement,每当一个元素结束的时候,都会激活方法endElement。我们可以在startElement和endElement中写入和应用相关的特定的代码。

  5. 小结

  SAX提供了一个解析XML文档的轻量级实现,它和DOM的对XML文档的解析在某种意义上可以和用ATL建立COM对象和用MFC建立COM对象的意义相比较,它能够搜索一个巨大的XML文档并从中提取一小部分有用的信息。它能够随时终止对XML文档的解析,而且它对系统的要求很低,不会随XML文档尺寸的增加而增加对系统的开销。

  本文简单介绍了如何用微软提供的SAX解析器进行XML文档解析,并给出了基于SAX的应用的框架结构,利用SAX,我们可以在自己的应用中嵌入对XML文档的分析和处理,并且不会大大增加应用对系统资源的需求。
阅读(2142) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~