|
|
|
[XML初学进阶]XML(45)合并(接口) 网上资源
|
|
8.1.1 接口的由来
到目前为止,我们都是在孤立地讲XML、讨论和XML相关的协议和工具,教会大家如何直接写一个XML文件,如何利用浏览器分析它浏览它。但实际上,同HTML一样,XML有时是动态生成的,需要我们编写一段代码一个脚本,作为一个“局外人”间接地去创建、访问和操作一个XML文件。还有些时候,我们所开发的应用程序需要能够读懂别人写的XML文件,从中提取我们所需要的信息。在以上这些情况下,我们都需要一个XML接口,这个接口是善意友好的,我们以它为媒,将我们的应用程序与XML文档结合在一起。在这一章里,我们就来介绍由W3C和XML_DEV邮件列表成员分别提出的两个标准应用程序接口:DOM和SAX。
实际上,XML文档就是一个文本文件,因此在我们需要访问文档中的内容时,必须首先书写一个能够识别XML文档信息的文本文件阅读器,也就是我们通常所说的XML语法分析器,由它来帮助我们解释XML文档并提取其中的内容。这就要求每个应用XML的人都要自己去处理XML的语法细节,显然是一项非常耗时的工作。更糟糕的是,如果需要在不同的应用程序或开发环境中访问XML文档中的数据,这样的分析器代码就要被重写多次。
或许您会觉得这难不倒您:把XML分析器做成一个DLL不就可以解决代码重写的问题了吗?回答当然是肯定的。不过,每一个XML分析器的DLL必然有自己的接口,我们正是通过这个接口来访问并处理XML文档中的数据的。但如果各种分析器的接口都不相同,那么我们的开发就必须是针对某一个XML分析器的,要是想换用另外一个分析器,那么非常抱歉,程序不得不重新改写。因此,有了DLL有了接口还远远不够,我们还需要一个善意友好的接口,也就是一个统一的接口。要真正实现代码的重用,就必须解决DLL的接口标准问题。
我们知道,数据库有标准的ODBC/JDBC这样的接口规范。在它的帮助下,我们编写数据库应用程序的时候只要针对于接口即可,可以不管后台的数据库系统究竟是ORACLE还是SYBASE,是DB2还是SQL Server,这给应用程序的开发带来了很大的便利。同样的道理,在我们做XML的应用开发时,一个统一的XML数据接口也是必需的。
W3C意识到了上述问题的存在,于是制定了一套书写XML分析器的标准接口规范--DOM。除此之外,XML_DEV邮件列表中的成员根据应用的需求也自发地定义了一套对XML文档进行操作的接口规范--SAX。这两种接口规范各有侧重,互有长短,应用都比较广泛。
下面,我们给出DOM和SAX在应用程序开发过程中所处地位的示意图。从图中可以看出,应用程序不是直接对XML文档进行操作的,而是首先由XML分析器对XML文档进行分析,然后,应用程序通过XML分析器所提供的DOM接口或SAX接口对分析结果进行操作,从而间接地实现了对XML文档的访问。
500)this.width=500'>
8.1.2 DOM与SAX并存
如前所述,之所以要制定一个接口标准,是为了给XML应用程序的开发带来方便,使得应用程序可以根据需要随时选择、更换合适的分析器,同时又无须对程序本身再做大的改动。那么读者可能会问,既然接口需要统一,为什么现在还有两个接口标准共存呢?这两个标准之间又存在什么关系呢?
事实上,DOM和SAX两个接口标准之所以能够并存,就是因为它们所要实现的目标不同。DOM和SAX分析器在接口实现过程中分别侧重于不同的方面,二者各有长短,分别满足了不同的应用需求。在进行横向比较之前,我们先来分别简要介绍一下这两个标准。
DOM
DOM的全称是Document Object Model,也即文档对象模型。在应用程序中,基于DOM的XML分析器将一个XML文档转换成一个对象模型的集合(通常称DOM树),应用程序正是通过对这个对象模型的操作,来实现对XML文档数据的操作。通过DOM接口,应用程序可以在任何时候访问XML文档中的任何一部分数据,因此,这种利用DOM接口的机制也被称作随机访问机制。
DOM接口提供了一种通过分层对象模型来访问XML文档信息的方式,这些分层对象模型依据XML的文档结构形成了一棵节点树。无论XML文档中所描述的是什么类型的信息,即便是制表数据、项目列表或一个文档,利用DOM所生成的模型都是节点树的形式。也就是说,DOM强制使用树模型来访问XML文档中的信息。由于XML本质上就是一种分层结构,所以这种描述方法是相当有效的。
SAX
SAX的全称是Simple APIs for XML,也即XML简单应用程序接口。与DOM不同,SAX提供的访问模式是一种顺序模式,这是一种快速读写XML数据的方式。当使用SAX分析器对XML文档进行分析时,会触发一系列事件,并激活相应的事件处理函数,应用程序通过这些事件处理函数实现对XML文档的访问,因而SAX接口也被称作事件驱动接口。
DOM树所提供的随机访问方式给应用程序的开发带来了很大的灵活性,它可以任意地控制整个XML文档中的内容。然而,由于DOM分析器把整个XML文档转化成DOM树放在了内存中,因此,当文档比较大或者结构比较复杂时,对内存的需求就比较高。而且,对于结构复杂的树的遍历也是一项耗时的操作。所以,DOM分析器对机器性能的要求比较高,实现效率不十分理想。不过,由于DOM分析器所采用的树结构的思想与XML文档的结构相吻合,同时鉴于随机访问所带来的方便,因此,DOM分析器还是有很广泛的使用价值的。
SAX分析器在对XML文档进行分析时,触发了一系列的事件,由于事件触发本身是有时序性的,因此,SAX提供的是一种顺序访问机制,对于已经分析过的部分,不能再倒回去重新处理。SAX之所以被叫做"简单"应用程序接口,是因为SAX分析器只做了一些简单的工作,大部分工作还要由应用程序自己去做。也就是说,SAX分析器在实现时,它只是顺序地检查XML文档中的字节流,判断当前字节是XML语法中的哪一部分、是否符合XML语法,然后再触发相应的事件,而事件处理函数本身则要由应用程序自己来实现。同DOM分析器相比,SAX分析器缺乏灵活性。然而,由于SAX分析器实现简单,对内存要求比较低,因此实现效率比较高,对于那些只需要访问XML文档中的数据而不对文档进行更改的应用程序来说,SAX分析器更为合适。
综上所述,无论是DOM接口还是SAX接口,都各自有其优缺点。也正是如此,它们将长期并存下去,在不同的应用中发挥不同的作用。
8.2.1 DOM的组成
对于XML应用开发来说,DOM就是一个对象化的XML数据接口,一个与语言无关、与平台无关的标准接口规范。它定义了HTML文档和XML文档的逻辑结构,给出了一种访问和处理HTML文档和XML文档的方法。利用DOM,程序开发人员可以动态地创建文档,遍历文档结构,添加、修改、删除文档内容,改变文档的显示方式等等。可以这样说,文档代表的是数据,而DOM则代表了如何去处理这些数据。无论是在浏览器里还是在浏览器外,无论是在服务器上还是在客户端,只要有用到XML的地方,就会碰到对DOM的应用。
作为W3C的标准接口规范,目前,DOM由三部分组成,包括:核心(core)、HTML和XML。核心部分是结构化文档比较底层对象的集合,这一部分所定义的对象已经完全可以表达出任何HTML和XML文档中的数据了。HTML接口和XML接口两部分则是专为操作具体的HTML文档和XML文档所提供的高级接口,使对这两类文件的操作更加方便。
目前,DOM有两个版本,一个是由W3C于1998年8月18日通过的DOM Level 1,另一个则是正在制定过程中的DOM Level 2,W3C已于2000年3月7日通过了DOM Level 2的候选推荐版本。
8.2.2 DOM树
前面我们讲过,DOM为我们提供的访问XML文档信息的媒介是一种分层对象模型,而这个层次的结构,则是一棵根据XML文档生成的节点树。
一个XML分析器,在对XML文档进行分析之后,不管这个文档有多简单或者多复杂,其中的信息都会被转化成一棵对象节点树。在这棵节点树中,有一个根节点--Document节点,所有其他的节点都是根节点的后代节点。节点树生成之后,就可以通过DOM接口访问、修改、添加、删除、创建树中的节点和内容。
以我们非常熟悉的客户联系信息的例子来说明,下面给出一个XML文档片段:
<?xml version="1.0" encoding="gb_2312" ?><addressbook><person sex = "male"> <name>张三</name> <email>zhs@xml.net.cn</email></person><person sex = "male"> <name>李四</name> <email>ls@xml.net.cn</email></person></addressbook>
用DOM来表示这段文档,如图所示:
500)this.width=500'>
在这棵文档对象树中,文档中所有的内容都是用节点来表示的。一个节点又可以包含其他节点,节点本身还可能包含一些信息,例如节点的名字、节点值、节点类型等。文档中的根实际上也是一个元素,之所以要把它单独列出来,是因为在XML文档中,所有其他元素都是根元素的后代元素,而且根元素是唯一的,具有其他元素所不具有的某些特征。
这个例子比较简单,事实上,DOM中还包含注释、处理指令、文档类型、实体、实体引用、命名空间、事件、样式单等多种对象模型。
文档对象模型利用对象来把文档模型化,这些模型不仅描述了文档的结构,还定义了模型中对象的行为。换句话说,在上面给出的例子里,图中的节点不是数据结构,而是对象,对象中包含方法和属性。在DOM中,对象模型要实现:
用来表示、操作文档的接口
接口的行为和属性
接口之间的关系以及互操作
8.2.3 DOM的四个基本接口
在DOM接口规范中,有四个基本的接口:Document,Node,NodeList以及NamedNodeMap。在这四个基本接口中,Document接口是对文档进行操作的入口,它是从Node接口继承过来的。Node接口是其他大多数接口的父类,象Documet,Element,Attribute,Text,Comment等接口都是从Node接口继承过来的。NodeList接口是一个节点的集合,它包含了某个节点中的所有子节点。NamedNodeMap接口也是一个节点的集合,通过该接口,可以建立节点名和节点之间的一一映射关系,从而利用节点名可以直接访问特定的节点。下面将对这四个接口分别做一些简单的介绍。
Document接口
Document接口代表了整个XML/HTML文档,因此,它是整棵文档树的根,提供了对文档中的数据进行访问和操作的入口。
由于元素、文本节点、注释、处理指令等都不能脱离文档的上下文关系而独立存在,所以在Document接口提供了创建其他节点对象的方法,通过该方法创建的节点对象都有一个ownerDocument属性,用来表明当前节点是由谁所创建的以及节点同Document之间的联系。
在DOM树中,Document接口同其他接口之间的关系如下图所示:
500)this.width=500'>
由图可以看出,Document节点是DOM树中的根节点,也即对XML文档进行操作的入口节点。通过Docuemt节点,可以访问到文档中的其他节点,如处理指令、注释、文档类型以及XML文档的根元素节点等等。另外,从上图我们还可以看出,在一棵DOM树中,Document节点可以包含多个处理指令、多个注释作为其子节点,而文档类型节点和XML文档根元素节点都是唯一的。
关于Document接口的IDL(Interface Definition Language接口定义语言)定义和其中一些比较常用的属性和方法的详细介绍在许多参考书都可以找到,我们将在后面结合实际例子给予介绍。
Node接口
Node接口在整个DOM树中具有举足轻重的地位,DOM接口中有很大一部分接口是从Node接口继承过来的,例如,Element、Attr、CDATASection等接口,都是从Node继承过来的。在DOM树中,Node接口代表了树中的一个节点。一个典型的Node接口如下图所示:
500)this.width=500'>
如图所示,Node接口提供了访问DOM树中元素内容与信息的途径,并给出了对DOM树中的元素进行遍历的支持。
同样,我们将在后面结合实际例子详细说明Node接口的具体使用方法。
NodeList接口
NodeList接口提供了对节点集合的抽象定义,它并不包含如何实现这个节点集的定义。NodeList用于表示有顺序关系的一组节点,比如某个节点的子节点序列。另外,它还出现在一些方法的返回值中,例如GetNodeByName。
在DOM中,NodeList的对象是"live"的,换句话说,对文档的改变,会直接反映到相关的NodeList对象中。例如,如果通过DOM获得一个NodeList对象,该对象中包含了某个Element节点的所有子节点的集合,那么,当再通过DOM对Element节点进行操作(添加、删除、改动节点中的子节点)时,这些改变将会自动地反映到NodeList对象中,而不需DOM应用程序再做其他额外的操作。
NodeList中的每个item都可以通过一个索引来访问,该索引值从0开始。
NamedNodeMap接口
实现了NamedNodeMap接口的对象中包含了可以通过名字来访问的一组节点的集合。不过注意,NamedNodeMap并不是从NodeList继承过来的,它所包含的节点集中的节点是无序的。尽管这些节点也可以通过索引来进行访问,但这只是提供了枚举NamedNodeMap中所包含节点的一种简单方法,并不表明在DOM规范中为NamedNodeMap中的节点规定了一种排列顺序。
NamedNodeMap表示的是一组节点和其唯一名字的一一对应关系,这个接口主要用在属性节点的表示上。与NodeList相同,在DOM中,NamedNodeMap对象也是"live"的。
8.2.4.1 创建Document对象
通过上面的介绍,我们已经对DOM接口有了部分了解,那么,这些接口以及接口中的属性和方法又应该如何使用呢?这正是接下来所要讲述的内容。
如前所述,利用DOM,程序开发人员可以动态地创建文档,遍历文档结构,添加、修改、删除文档内容等等。在本节中,我们将通过微软的XML分析器msxml,对DOM接口的这些应用做一详细的介绍。首先,我们来讲一下一切操作的基础——创建Document对象。通过创建Document对象,应用程序或者脚本就得到了对XML文档进行操作的入口。下面给出了使用不同的编程语言创建Document对象的范例。
JScript:var doc = new ActiveXObject("Microsoft.XMLDOM")
VB Script:Dim docSet doc = CreateObject("Microsoft.XMLDOM")
VB:Dim doc As ObjectSet doc = CreateObject("Microsoft.XMLDOM")或者Dim doc As DOMDocumentSet doc = New DOMDocument
VC:HRESULT hr = CoCreateInstance(CLSID_DOMDocument,NULL,CLSCTX_INPROC_SERVER,IID_IXMLDocument(LPVOID*),&m_pXMLDocument);
为了描述简单起见,在后面的例子中将只采用VBScript语言作为范例书写语言。
8.2.4.2 加载XML文档
在Document对象创建之后,我们就得到了对文档进行操作的入口,那么,创建的这个文档对象是如何同实际的XML文档关联在一起的呢?说来可笑,在W3C的DOM接口规范中,没有任何一个地方定义了DOM中的接口对象同实际文档相关联的方法。因此,不同的XML分析器所提供的加载XML文档的方法也不尽相同。在微软的msxml中,提供了一个load方法来加载XML文档,建立DOM树同XML文档之间的关联。
依旧以联系人列表信息的XML文档.xml为例,可通过下述方式来加载文档:
Dim myDocumentSet myDocument = CreateObject("microsoft.xmldom")myDocument.async = FalsemyDocument.load("client.xml")
XML文档被加载后,就在内存中形成了一棵DOM树,如图9-6所示:
500)this.width=500'>
8.2.4.3 遍历XML文档
现在,我们已经可以通过DOM来创建XML文档对象,并加载XML文档了。对于已经加载的文档,我们要从文档中获取所需要的内容,这就要求能够通过DOM树来访问树中的任何一个节点,也就是对DOM树的遍历。下面我们依旧以client.xml为例,通过几个实例来说明如何遍历DOM树中的节点。
首先,我们要获取XML文档的根元素节点,用VBScript语言描述这个操作如下:
root = myDocument.documentElement
该语句的实际含义如下图黄色箭头所示。
500)this.width=500'>
在得到了文档的根元素节点之后,我们又将如何访问其他元素呢?以文档中的第二个person元素为例,对该元素节点以及其子节点的访问可以通过下面的方式来实现:
personNode = root.childNodes.item(1)nameNode = personNode.childNodes.item(0)textNode = nameNode.childNodes.item(0)theName = textNode.nodeValue
上述访问语句执行后,theName的值是"李四"。下图黄色箭头给出了这一访问过程的示意:
500)this.width=500'>
在上面的代码中,root是文档的根元素节点addressbook节点,personNode和nameNode都是元素类型的节点,textNode是TEXT类型的节点,theName是一个字符串。
childNodes是NodeList类型的属性,item是NodeList接口中Node类型的属性,通过item可以访问NodeList节点集合中的任意节点(这儿有一点需要注意,当我们要访问根元素节点addressbook的第二个person子节点personNode时,我们用的索引参数是"1",这是因为item中的索引参数是从0开始的,如果我们要访问节点集合中的第一个节点,则应该用item(0)来表示)。
在DOM规范中,要访问元素节点的文本内容,需要先得到元素节点的TEXT子节点,再通过TEXT节点的属性获取文本内容。微软在实现DOM接口时对DOM进行了部分扩展,可以通过元素类型节点的text属性直接获得元素中的文本内容。具体实用说明可以参考微软msdn中的帮助。
上面的例子给出了如何访问DOM树中的元素节点,对于DOM树中的属性节点,访问方法略有不同,可以通过下面的语句来实现:
attr = node.attributes.getNamedItem("sex")attrContent = attr.nodeValue
上述访问语句执行后,attrContent的值是"male"。下图用黄色箭头标出了这一访问的过程。
500)this.width=500'>
在上面的代码中,attr是属性类型的节点,attributes是NamedNodeMap类型的属性,getNamedItem是NamedNodeMap接口中的方法。属性的内容可以通过属性节点的nodeValue来获得。
8.2.4.4 添加元素
目前,我们已经能够通过DOM获取XML文档中的信息了。如前所述,通过DOM还可以动态地更改XML文档中的内容。下面我们仍旧使用上面的client.xml文档,通过实例来说明如何更改XML文档中的内容。
假如我们希望在addressbook.xml中,给第一个person元素增添一个字符串为"北大方正"的company元素,实现这一添加元素操作的语句如下:
node = root.childNodes.item(0)newNode = myDocument.createElement("company")node.insertBefore(newNode,node.lastNode)textNode = myDocument.creatTextNode("北大方正")node.childNodes.item(1).appendChild(textNode)
下面给出了添加元素的操作示意图:
500)this.width=500'>
8.2.4.5 删除元素
现在,我们再把前一小节添加的元素删除,可通过下面的代码实现这一目的:
node = root.childNodes.item(0)oldNode = node.removeChild(node.childNodes.item(1))
其中,oldNode中存放的是已被删除的节点。在删除某个节点时,以该节点为根的子树将整个被删除,因此得到的结果DOM树恢复原状。
8.2.4.6 修改元素
元素内容的改变包括元素名称、元素属性、元素所包含的文本内容等项目的改变。下面仅就元素所包含的文本内容的改变给出一个范例,其他内容的改变实现方法与此类似。
假如想把张三的电子邮件地址更改为zhs@pku.edu.cn,通过下列语句就可以实现:
node = root.childNodes.item(0)emailNode = node.childNodes.item(0)emailNode.childNodes.item(0).nodeValue = zhs@pku.edu.cn
下面给出了修改后的效果:
500)this.width=500'>
至此,我们已经把对DOM树的常用操作做了简单的介绍。一般说来,支持DOM的XML分析器通常会对DOM做一些扩展,这些扩展不属于DOM规范中的标准,但却给DOM树的操作带来了方便,不同的分析器所做的扩展也不尽相同,可以通过查询相关技术支持资料或者帮助来获取更多的信息。
8.3.1 SAX分析器接口简介
由前面介绍可知,SAX是一种事件驱动的接口,它的基本原理是由接口的用户提供符合定义的处理器,XML分析时遇到特定的事件,就去调用处理器中特定事件的处理函数。一般SAX接口都是用JAVA实现的,但事实上C++也可以用于实现SAX接口,只是C++的分析器比较少。之所以叫做"简单"应用程序接口,是因为这个接口确实非常简单,绝大多数事情分析器都没有做,需要应用程序自己去实现,因而开发者的任务也相应重一些。
SAX分析器的大体构成框架如图所示:
500)this.width=500'>
图中最上方的SAXParserFactory用来生成一个分析器实例。XML文档是从左侧箭头所示处读入,当分析器对文档进行分析时,就会触发在DocumentHandler,ErrorHandler,DTDHandler以及EntityResolver接口中定义的回调方法。
下面我们就对SAX分析器中的几个主要API接口作一简单的介绍。
SAXParserFactory
SAXParserFactory对象用来按照系统属性中的定义创建一个分析器的实例,接口是Javax.xml.parser. SAXParserFactory。
Parser
org.xml.sax.Parser接口定义了类似setDocumentHandler的方法来创建事件处理函数。另外,该接口中还定义了parser(URL)方法来对XML文档进行实际的分析工作。
DocumentHandler
当分析器遇到XML文档中的标记时,就会激活该接口中的startDocument,endDocument,startElement以及endElement等方法。另外,characters方法以及processingInstruction方法也是在DocumentHandler接口中实现的。当分析器遇到元素内部的文本内容时就会激活characters方法,当分析器遇到处理指令时就会激活processingInstruction方法。
ErrorHandler
当分析器在分析过程中遇到不同的错误时,ErrorHandler接口中的error、fatalError或者warning方法就会被激活。
DTDHandler
当处理DTD中的定义时,就会调用该接口中的方法。
EntityResolver
当分析器要识别由URI定义的数据时,就会调用该接口中的resolveEntity方法。一个典型的SAX应用程序至少要提供一个DocumentHandler接口。一个健壮的SAX应用程序还应该提供ErrorHandler接口。
8.3.2.1 生成应用程序框架
下面几节中我们利用SAX分析器读入一个XML文档,然后原样输出其内容,通过这个例子来分析一下SAX接口的应用。实际应用中我们往往要对XML数据做一些特定的处理来完成某种需求,一般不会把照原样输出读入的XML文档。不过,现在我们只是想通过一个具体的应用程序来解释如何应用SAX接口,这不失为一种简单而有效的方法。在示例分析过程中,我们采用的SAX分析器是SUN的JAXP,采用的编程语言是JAVA。
首先,我们创建一个名为Echo.java的文件,文件的基本框架为:
public class Echo extends HandlerBase{public static void main (String argv[]){ if (argv.length != 1) { System.err.println ("Usage: cmd filename"); System.exit (1); } try {// Set up output streamout = new OutputStreamWriter (System.out, "GB2312"); }catch (Throwable t) { t.printStackTrace (); } System.exit (0);}static private Writer out;}
在这段代码中,Echo类是从HandleBase继承过来的。在HandleBase中,实现了JAXP中的所有接口,这样,通过HandleBase的继承类,就可以重载所有我们所关心的方法,而对不关心的方法则采用缺省处理。
由于我们想把这个示例做成一个单独的完整的程序,因此需要一个main方法以及一个命令行参数(用来告诉应用程序对哪个XML文档进行操作)。代码中的黑体部分首先对命令行参数进行处理,获得XML文档的文件名,然后创建了一个文档输出流。
8.3.2.2 引入需要的类
应用程序中将用到下面几个类包,需要在程序开始对其进行引入:
import java.io.*;import org.xml.sax.*;import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.ParserConfigurationException;import javax.xml.parsers.SAXParser;public class Echo extends HandlerBase{ ...
在这几个类包中,java.io中的类是用于输出的;org.xml.sax包中定义了SAX分析器中用到的所有接口;SAXParserFactory类用来创建所需要的分析器实例;如果SAXParserFactory不能按照配置选项创建合适的分析器,该类就会产生一个ParserConfigurationException异常;最后SAXParser是类工厂创建的分析器实例。
8.3.2.3 创建分析器
引入上述类包之后,就可以创建分析器实例了,下面代码中的黑体部分就是分析器实例的创建过程:
public static void main (String argv []) {if (argv.length != 1) { System.err.println ("Usage: cmd filename"); System.exit (1);} // Use the default (non-validating) parserSAXParserFactory factory = SAXParserFactory.newInstance();try { // Set up output stream out = new OutputStreamWriter (System.out, "UTF8"); // Parse the input SAXParser saxParser = factory.newSAXParser(); saxParser.parse( new File(argv [0]), new Echo() );} catch (Throwable t) { t.printStackTrace ();}System.exit (0);}
在上面黑体部分所标注的代码中,首先创建了一个SAXParserFactory的实例factory,然后由这个类工厂创建了一个分析器实例,并通过该分析器实例处理分析事件,得到要处理的文档。
8.3.2.4 实现DocumentHandler接口
DocumentHandler接口是我们要用到的一个最重要的JAXP接口,在该接口中,需要一系列的方法,这些方法是SAX分析器在对XML文档进行分析时遇到不同的分析事件所激发的相应处理。目前,主要用到五个方法:startDocument,endDocument,startElement,endElement,以及characters,下面黑体部分所标注的代码就是用来响应这五种事件处理的:
...static private Writer out;public void startDocument ()throws SAXException { }public void endDocument ()throws SAXException { }public void startElement (String name, AttributeList attrs)throws SAXException { }public void endElement (String name)throws SAXException { }public void characters (char buf [], int offset, int len)throws SAXException { }...
当分析器遇到一个起始标记或者一个结束标记时,标记名就作为一个字符串变量传递给方法startElement和endElement。另外,当遇到起始标记时,起始标记所代表的元素的所有的属性都以AttributeList的形式传递给startElement方法。在一个元素内的字符以一个字符数组的形式传递给characters方法,在characters方法中还有另外两个参数,其中一个代表字符数组的长度,也即字符的个数,另一个参数代表元素内的第一个字符在字符数组中的偏移。
对于下面所示的XML文档:
<?xml version = "1.0"?><addressbook><person> <lastname>Idris</lastname> <firstname>Nazmul</firstname> <company>The Bean Factory, LLC.</company> <email>xml@beanfactory.com</email></person></addressbook>
当对它利用SAX分析器中的DocumentHandler接口中的方法进行分析时,方法的调用时序如图所示:
500)this.width=500'>
8.3.2.5 输出异常
每个DocumentHandler接口的方法都会产生SAXExceptions异常,但不会产生写文件时可能发生的IOExceptions异常。可是,由于SAXException可以封装其他异常,因此可以通过一个自定义的方法来处理异常。下面代码的黑体部分就是用来处理异常的函数:
public void characters (char buf [], int offset, int Len)throws SAXException{ }private void emit (String s)throws SAXException{try { out.write (s); out.flush ();} catch (IOException e) { throw new SAXException ("I/O error", e);}}...
当emit方法被调用时,所有输入/输出错误都被封装在了SAXException中,并由SAX分析器来处理SAXException异常。
8.3.2.6 简单格式化输出
在对XML文档进行实际的处理之前,我们还需要做最后一件事情——写一个简单的换行函数nl,使输出文档不至于都输出在同一行上,以增加输出文档的可读性,如下面代码中的黑体所示:
private void emit (String s) {... } private void nl () throws SAXException {String lineEnd = System.getProperty("line.separator");try { out.write (lineEnd);} catch (IOException e) { throw new SAXException ("I/O error", e);}}
8.3.2.7 处理文档事件
在"实现DocumentHandler接口"一节中,我们定义了五个文档事件处理方法的框架,现在,我们来实现框架中的实际内容,如下所示:
public void startDocument ()throws SAXException {emit ("<?xml version= 1.0 encoding= gb2312 ?>");nl();}public void endDocument ()throws SAXException {try { nl(); out.flush ();} catch (IOException e) { throw new SAXException ("I/O error", e); } }
在上面代码中,当分析器遇到文档的起始部分时,就输出XML文档声明语句。当分析器分析到文档的结束时,简单地输出一个换行,并把输出流中的内容全部输出。就文档开始与结束而言,不需要再做其他的工作。而对于元素起始与结束,所做的工作如下面代码中的黑体部分所示:
public void startElement (String name, AttributeList attrs)throws SAXException {emit ("<"+name);if (attrs != null) { for (int i = 0; i < attrs.getLength (); i++) { emit (" "); emit (attrs.getName(i)+"=\""+attrs.getValue (i)+"\""); }}emit (">");}public void endElement (String name)throws SAXException {emit ("</"+name+">");}
通过上面的代码,可以输出所遇到的元素名称以及元素的所有属性。现在,只剩下XML文档中的文本内容没有输出了。下面代码则是characters方法的实现:
public void characters (char buf [], int offset, int len)throws SAXException {String s = new String(buf, offset, len);emit (s);}
到目前为止,我们已经写完了利用SAX分析器的一个应用程序Echo.java,接下来就是编译并执行该应用程序了。
8.3.3 应用程序的编译与执行
最后,我们来说说所编写的程序的编译和执行方法。
编译应用程序
在不同的系统中,编译指令也有所不同。
Windows:javac -classpath %XML_HOME%\jaxp.jar;%XML_HOME%\parser.jar Echo.java Unix:javac -classpath ${XML_HOME}/jaxp.jar:${XML_HOME}/parser.jar Echo.java
其中
XML_HOME是安装JAXP和Project X库的路径。
jaxp.jar中包含了JAXP中所定义的APIs。
parser.jar中则包含了实现SAX和DOM APIs的接口和类。
执行应用程序
如果要执行应用程序,在不同的系统中,指令也有所不同。
Windows:java -classpath .;%XML_HOME%\jaxp.jar;%XML_HOME%\parser.jar Echo slideSample.xml
经过了编译和执行后,我们的echo.java程序就可以如愿以偿地发挥作用了。
| |
|
|
|