发现VB6中SAX的乐趣[转]

发现VB6中SAX的乐趣
http://www.techng.com/content.aspx?titleid=5748

微软公司的XML核服务(Core Services),也就是广为人知的MSXML2,为VB和COM开发者提供了一种很有用的XML工具箱。在前面的几篇文章中,我已经介绍了MSXML2的DOM解析器,并演示了如何在一个图书目录管理应用程序中使用它的例子。现在,我将带你看看XML解析器coin的SAX部分。

SAX是什么?

受篇幅所限,我只能在本文简单的讨论一下SAX是如何工作的,如果你对SAX感兴趣,我建议你看看我们以前的一篇文章《XML基础教程:掌握SAX 》一文。简单的说,SAX(XML的简单API,即Simple API for XML)是一种连续的push解析器,SAX解析器把XML文档中的元素按照先后顺序依次推到它的主应用程序中。SAX最初是用来作为Java的解析器的,随后用于各种各样的其它语言,包括微软公司的COM实现。最为一种解析器,SAX相对于DOM的一个优势就是处理大文档以及查询文档中某段特定信息。当然,SAX要比DOM复杂,它要求你跟踪SAX正在处理的内容信息以便知道解析器处于文档中的哪个部分。

微软的SAX实现
在MSMAX2中实际上有两种SAX实现,一种实现是为VB程序员提供的,而另一种适合于C++开发者。从VB的角度来看,你需要管理几个类并用SAX运行它们。

SAXXMLReader:解析器本身
MSXML为VB提供的SAX解析器由IVBSAXXMLReader界面定义。SAXXMLReader类是该界面的一个与版本无关(version-independent)的实现,你可以在应用程序中把它做为读取器(reader)来使用,这样可以保证应用程序与MSXML的新版本兼容。你可以通过调用parse或者parseURL方法来设置解析器处理某个文档。就它本身而言,SAXXMLReader只能解析文档,它不能通知你它正在分析的内容。你将需要实现一种有效的界面来实际利用解析器。

内容处理器
IVBSAXContentHandler界面包含了一系列方法, SAX解析器通过调用这些方法就可以向应用程序通知文档中的内容。我在表A中列出了若干个比较重要的方法:
表A

startDocument 当解析器开始解析文档时调用。
startElement 解析器每遇到一个新的元素时(解析器读到元素的开始标记)就调用它。输入参数代表元素的位置以及元素的full-qualified名称。注意SAX使用的是一种深度优先的遍历方法——先解析子元素然后分析同级元素(兄弟节点)。
characters 当数据元素调用startElement方法后调用。该数据作为输入参数传递到本方法。由于SAX解析器的VB实现是非验证性的(non-validating),所以本方法也可以接收白空格。
endElement 调用startElement和characters之后,当解析器读到元素的结束标志时调用。
processingInstruction 当解析器遇到处理指令元素时调用。指令的内容通过一个输入参数传递到这个方法中。
endDocument 当解析器解析文档完毕时调用。也就是说,现在解析器可以解析另一个文档了。

重要的文档处理方法


你至少需要完成界面函数中的startElement和characters方法,并把实现类的一个实例通过它的contentHandler属性传递给SAXXMLReader。

实现内容函数的一个好笑的方面就是SAX是无态的(stateless),也就是说你的实现类必须跟踪当前正在解析的元素(保存你从startElement方法得到的元素名),这样你才可以知道如怎样characters方法来处理接收到的元素内容。还有,目前SAX的VB实现是非验证性的,这就产生了一个有趣的效果:文档中的白空格(white space)实际上是被characters方法处理,而不是像你可能猜测的那样传递给方法ignorableWhitespace。

对错误的处理
SAX解析器使用另一种特殊的界面,即IVBSAXErrorHandler,来向应用程序通知解析错误。尽管在文档中可以引用error和fatalError方法,但当前SAX的VB实现只能调用fatalError。有趣的是解析器同样也向应用程序发送它遇到的错误,这就使得错误处理器(handler)对象有些多余。如果你选择使用其中的某一个,那么你至少要实现fatalError方法并通过使用errorHandler属性来向SAXXMLReader传递一个实现类的实例。

重看“书籍目录”应用程序

现在,让我们把这些部件组装到一个例子中。我已经用SAX取代DOM重写了我们的老朋友——“书籍目录”应用程序。该应用程序得到了相当的简化(我以及删除了添加新书籍以及编辑书籍的功能),这样你就可以集中注意力看看SAX如何工作的。你可以在这儿下载本项目的源代码,它包括了catalog.xml的一份拷贝,该应用程序解析并显示出一个树型视图控件。图A是该它运行时的情形。

图A



运行中的SAX例子程序
创建SAX客户端应用程序的第一步就是实现它的内容处理器界面,即IVBSAXContentHandler。在清单A中,我给出了cSaxReader的代码,它同时实现了内容处理器界面和错误处理器界面。

先让我们谈谈内容处理器界面。你可以看到我是如何实现 startDocument、startElement、characters、和 endElement方法的。StartDocument方法的任务很简单,就是在SAX开始解析一个新的文档时分配集合(collection)类。实际工作是在 startElement和characters方法中完成的。前者保存当前元素的名字,即strLocalName,到一个模块层次上的变量,这样稍后当characters处理元素中的数据时,它可以把数据赋值给适当的book类属性中。当startElement处理一个名字为“book”的元素时,它可以通过oAttributes(一个IVBSAXAttributes实例)参数访问到的id属性来查询该书的id号。最后,当调用endElement时,它抛弃当前元素的名字,并设置一个布尔型的标志变量来表示是否可以结束解析该书。这就避免了characters没有必要的处理两个元素之前的白空格。

正如我在前面所提到的那样,由于严重的(fatal)错误可以作为可捕获的(trapable)错误返回到客户应用程序,因此实现错误处理对象显得有些多余。在实现错误处理器时你可以做的一件事就是判断错误发生在文档中的位置。你可以通过检测IVBSAXLocator实例的lineNumber和columnNumber属性来实现上述功能,IVBSAXLocator实例是错误处理器中fatalError方法的一个参数。

当建立内容处理器和错误处理器后,你接下来只需要通过调用SAXXMLReader的parse或者parseURL方法来设置解析器到工作文档上。

Listing A – cSaxReader.cls


Option Explicit

'implement the content handler and error handler interfaces here
Implements MSXML2.IVBSAXContentHandler
Implements MSXML2.IVBSAXErrorHandler

Private m_book As cBook 'New book
Private m_colBooks As cBooksCollection 'books collection
Private m_oSaxReader As MSXML2.SAXXMLReader 'SAX reader
Private m_currentElement As String 'last element name handed off from reader
Private m_blParsingABook As Boolean 'true if we're inside a book element

Public Sub LoadBooks(strFile As String)
On Error GoTo err_LoadBooks
'SAXXMLReader throws errors for parse errors regardless of whether or not
'we're handling errors in an IVBSAXErrorHandler implementation

'parse a file from a URL
m_oSaxReader.parseURL strFile
Exit Sub

err_LoadBooks:
MsgBox "Unable to load from file, book list may be incomplete."
Exit Sub

End Sub

Public Function GetBookList() As cBooksCollection
'retrieve the finished book list
Set GetBookList = m_colBooks
End Function

Private Sub Class_Initialize()
'set up the SAX reader
Set m_oSaxReader = New MSXML2.SAXXMLReader
'pass a reference to this object to use as the content handler...
Set m_oSaxReader.contentHandler = Me
'and error handler objects
Set m_oSaxReader.errorHandler = Me

End Sub

Private Sub Class_Terminate()
Set m_oSaxReader = Nothing
Set m_colBooks = Nothing
Set m_book = Nothing

End Sub

Private Sub IVBSAXContentHandler_characters(strChars As String)
'skip the element if we aren't parsing a book (inside a book element)
'easy way to skip whitespace
If m_blParsingABook Then
'check the last element name sent to startElement to determine
'what to do with the data we just received
Select Case m_currentElement
Case "title"
m_book.Title = strChars
Case "author"
m_book.Author = strChars
Case "price"
m_book.Price = CCur(strChars)
Case "publish_date"
m_book.Pub_Date = CDate(strChars)
Case "genre"
m_book.Genre = strChars
Case "description"
m_book.Description = strChars
End Select
End If

End Sub

Private Property Set IVBSAXContentHandler_documentLocator(ByVal RHS As MSXML2.IVBSAXLocator)

End Property

Private Sub IVBSAXContentHandler_endDocument()

End Sub

Private Sub IVBSAXContentHandler_endElement(strNamespaceURI As String, strLocalName As String, strQName As String)
'if we just moved out of a book element, then create a new Book
'object and reset the parsing flag.
If strLocalName = "book" Then
m_colBooks.AddBook m_book
m_blParsingABook = False
End If
'discard the current element name
m_currentElement = ""

End Sub

Private Sub IVBSAXContentHandler_endPrefixMapping(strPrefix As String)

End Sub

Private Sub IVBSAXContentHandler_ignorableWhitespace(strChars As String)

End Sub

Private Sub IVBSAXContentHandler_processingInstruction(strTarget As String, strData As String)

End Sub

Private Sub IVBSAXContentHandler_skippedEntity(strName As String)

End Sub

Private Sub IVBSAXContentHandler_startDocument()
'create a new collection to hold the contents of a new document
Set m_colBooks = New cBooksCollection

End Sub

Private Sub IVBSAXContentHandler_startElement(strNamespaceURI As String, strLocalName As String, strQName As String, ByVal oAttributes As MSXML2.IVBSAXAttributes)
'preserve the name of the element we just entered
m_currentElement = strLocalName

'if this element is a book element then create a new book object
'and set it's ID property, then set the parsing flag so that the
'characters method will handle the child nodes of this book
If strLocalName = "book" Then
Set m_book = New cBook
m_book.ID = oAttributes.getValueFromName("", "id")
m_blParsingABook = True
End If

End Sub

Private Sub IVBSAXContentHandler_startPrefixMapping(strPrefix As String, strURI As String)

End Sub

Private Sub IVBSAXErrorHandler_error(ByVal oLocator As MSXML2.IVBSAXLocator, strErrorMessage As String, ByVal nErrorCode As Long)

End Sub

Private Sub IVBSAXErrorHandler_fatalError(ByVal oLocator As MSXML2.IVBSAXLocator, strErrorMessage As String, ByVal nErrorCode As Long)
'a fatal error occurred, note that the reader will also raise an error after this
'routine exits
Dim strMsg As String
'The oLocator object contains context information
'such as line and column numbers for the error
strMsg = strErrorMessage & vbCrLf & "Line: " & oLocator.lineNumber & " Column: " & oLocator.columnNumber
MsgBox "Parse failed: " & strMsg
End Sub

Private Sub IVBSAXErrorHandler_ignorableWarning(ByVal oLocator As MSXML2.IVBSAXLocator, strErrorMessage As String, ByVal nErrorCode As Long)

End Sub


有关资料:下载本项目的源代码
posted @ 2005-05-23 01:39  vboy  阅读(756)  评论(0编辑  收藏  举报