解析xml之--SAX

SAXDOM一样也是一个访问XML文档的接口。SAXSimple API for XML的缩写。它不像DOM那样是W 3C 的推荐标准。它是由XML-DEV邮件列表的成员开发维护,由David Megginson领导(david@megginson.com)的一个Public Domain软件。SAX是一个彻底的自由软件,它的作者放弃了对它的所有权利,并且它也被许可用于任何目的(在文章最后附录了它的版权声明)。

到现在为止SAX的版本已经发展到2.0。在这个最新版本中增加了对名称空间(Namespaces)的支持,而且可以通过对features以及properties的设置来对解析器做全面的配置,这其中包括设置解析器是否对文档进行有效性验证,以及怎样来处理带有名称空间的元素名称等。SAX1中的接口已经不再使用了,这里只会讨论有关SAX2的开发。在本文中提到SAX只是指SAX 2。另外,本文的所有例子都是用java编写,SAX解析器也使用的是JAVA版本。 

实现了SAX的解析器有很多,比如ApacheXercesOracleXML Parser等等。在本文中的例子程序使用的都是Xerces解析器,你可以从 http://xml.apache.org 得到它。让我们下载得到xerces.jar文件然后将其加入到classpath中去,这样我们就已经建立好环境(在xerces.jar中已经包含了SAX接口,所以不必特意再去寻找SAX类库)。

SAX API中有两个包,org.xml.saxorg.xml.sax.helper。其中org.xml.sax中主要定义了SAX的一些基础接口,如XMLReaderContentHandlerErrorHandlerDTDHandlerEntityResolver等。而在org.xml.sax.helper中则是一些方便开发人员使用的帮助类,如缺省实现所有处理器接口的帮助类DefaultHandler、方便开发人员创建XMLReaderXMLReaderFactory类等等。在这两个包中还有一些应用于SAX1的接口,同时还有几个类它们只是为了便于将在SAX1上开发的应用移植到SAX2上,在这篇文章中就不涉及了。下面是我们要关注的接口和类:

Package org.xml.sax

介绍

Interfaces

接口

Attributes

定义了一个属性列表接口,供访问元素的属性列表而用。

ContentHandler

处理解析文档内容时产生的事件。

DTDHandler

处理解析DTD时的相应事件。

EntityResolver

处理外部实体。

ErrorHandler

处理解析过程中所遇到的文档错误事件。

Locator

为了定位解析中产生的内容事件在文档中的位置而准备的一个定位器接口。

XMLFilter

提供了一个方便应用开发的过滤器接口。

XMLReader

任何兼容SAX2的解析器都要实现这个接口,这个接口让应用程序可以设置或查找featuresproperties,注册各种事件处理器,以及开始解析文档。

Classes

InputSource

XML实体准备的输入源。

Exceptions

SAXException

包装了一般的SAX错误和警告。

SAXNotRecognizedException

为识别不出某些标识而抛出的异常。

SAXNotSupportedException

为不支持某个操作而抛出的异常。

SAXParseException

包装了一个关于XML解析的错误或者警告。

 

Package org.xml.sax.helpers

帮助类所在的包

Classes

AttributesImpl

Attributes接口的缺省实现

NamespaceSupport

提供名称空间支持。

DefaultHandler

缺省实现了四个处理器接口,方便用户开发,在开发过程中会经常用到。

LocatorImpl

提供了一个对Locator接口的实现

XMLFilterImpl

对过滤器接口的实现,使用过滤器进行应用程序开发时,继承这个类很方便。

XMLReaderFactory

为方便创建不同的XMLReader而提供。也会经常用到。


SAX
的设计实现与DOM是完全不同的!DOM处理XML文档是基于将XML文档解析成树状模型,放入内存进行处理。而SAX则是采用基于事件驱动的处理模式,它将XML文档转化成一系列的事件,由单独的事件处理器来决定如何处理。为了了解如何使用SAX API处理XML文档,这里先介绍一下SAX所使用的基于事件驱动的处理模式。

这种基于事件的处理模式是一种通用的程序设计模式,被广泛应用于GUI设计。在JAVAAWTSWING以及JAVA BEANS中就有它的身影。而SAX的基于事件驱动的处理模式就与上面三者中的非常相像。

基于事件的处理模式主要是围绕着事件源以及事件处理器(或者叫监听器)来工作的。一个可以产生事件的对象被称为事件源,而可以针对事件产生响应的对象就被叫做事件处理器。事件源和事件处理器是通过在事件源中的事件处理器注册方法连接的。这样当事件源产生事件后,调用事件处理器相应的处理方法,一个事件就获得了处理。当然在事件源调用事件处理器中特定方法的时候,会传递给事件处理器相应事件的状态信息,这样事件处理器才能够根据事件信息来决定自己的行为。

SAX接口中,事件源是org.xml.sax包中的XMLReader,它通过parse()方法来开始解析XML文档并根据文档内容产生事件。而事件处理器则是org.xml.sax包中的ContentHandler,DTDHandler,ErrorHandler,以及EntityResolver这四个接口。它们分别处理事件源在解析过程中产生的不同种类的事件(其中DTDHandler是为解析文档DTD时而用)。而事件源XMLReader和这四个事件处理器的连接是通过在XMLReader中的相应的事件处理器注册方法set***()来完成的。详细介绍请见下表:

处理器名称

所处理事件

注册方法

org.xml.sax.ContentHandler

跟文档内容有关的所有事件:

1.       文档的开始和结束

2.       XML元素的开始和结束

3.       可忽略的实体

4.       名称空间前缀映射开始和结束

5.       处理指令

6.       字符数据和可忽略的空格

XMLReader中的setContentHandler(ContentHandler handler)方法

org.xml.sax.ErrorHandler

处理XML文档解析时产生的错误。如果一个应用程序没有注册一个错误处理器类,会发生不可预料的解析器行为。

setErrorHandler(ErrorHandler handler)

org.xml.sax.DTDHandler

处理对文档DTD进行解析时产生的相应事件

setDTDHandler(DTDHandler handler)

org.xml.sax.EntityResolver

处理外部实体

setEntityResolver(EntityResolver resolver)

在这四个处理器接口中,对我们最重要的是ContentHandler接口。下面让我们看一下对其中方法的说明:

方法名称

方法说明

public void setDocumentLocator(Locator locator)

设置一个可以定位文档内容事件发生位置的定位器对象

public void startDocument() throws SAXException

用于处理文档解析开始事件

public void endDocument() throws SAXException

用于处理文档解析结束事件

public void startPrefixMapping(java.lang.String prefix, java.lang.String uri) throws SAXException

用于处理前缀映射开始事件,从参数中可以得到前缀名称以及所指向的uri

public void endPrefixMapping(java.lang.String prefix) throws SAXException

用于处理前缀映射结束事件,从参数中可以得到前缀名称

public void startElement(java.lang.String namespaceURI,java.lang.String localName,java.lang.String qName,Attributes atts) throws SAXException

处理元素开始事件,从参数中可以获得元素所在名称空间的uri,元素名称,属性列表等信息

public void endElement(java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName) throws SAXException

处理元素结束事件,从参数中可以获得元素所在名称空间的uri,元素名称等信息

public void characters(char[] ch, int start, int length) throws SAXException

处理元素的字符内容,从参数中可以获得内容

public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException

处理元素的可忽略空格

public void processingInstruction(java.lang.String target, java.lang.String data) throws SAXException

处理解析中产生的处理指令事件

这里再介绍一下org.xml.sax.XMLReader中的方法,然后让我们看一个具体的例子。XMLReader是所有兼容SAX2的解析器都要实现的接口,由它的方法开始解析文档,并且调用它的注册方法来注册各种事件处理器。请看下表:

方法名称

方法介绍

public Boolean getFeature(java.lang.String name)throws SAXNotRecognizedException,SAXNotSupportedException

得到某个feature的值

public void setFeature(java.lang.String name,boolean value) throws SAXNotRecognizedException,SAXNotSupportedException

设置某个feature的值,例如,如果需要解析器支持对文档进行验证那么就这么调用本方法。myReader.setFeature(http://xml.org/sax/features/validation,true);其中myReaderXMLReader的实例。

public java.lang.Object getProperty(java.lang.String name)throws SAXNotRecognizedException,SAXNotSupportedException

返回一个property的值

public void setProperty(java.lang.String name,java.lang.Object value)throws SAXNotRecognizedException,SAXNotSupportedException

设置一个property的值

public void setEntityResolver(EntityResolver resolver)

注册处理外部实体的EntityResolver

public EntityResolver getEntityResolver()

得到系统中注册的EntityResolver

public void setDTDHandler(DTDHandler handler)

注册处理DTD解析事件的DTDHandler

public DTDHandler getDTDHandler()

得到系统中注册的DTDHandler

public void setContentHandler(ContentHandler handler)

注册处理XML文档内容解析事件的ContentHandler

public ContentHandler getContentHandler()

得到系统中注册的ContentHandler

public void setErrorHandler(ErrorHandler handler)

注册处理文档解析错误事件的ErrorHandler

public ErrorHandler getErrorHandler()

得到系统中注册的ErrorHandler

public void parse(InputSource input)throws java.io.IOException,SAXException

开始解析一个XML文档。

public void parse(java.lang.String systemId)throws java.io.IOException,SAXException

开始解析一个使用系统标识符标识的XML文档。这个方法只是上面方法的一个快捷方式它等同于:parse(new InputSource(systemId));


让我们通过例子来看一下使用SAX解析XML文档的应用程序是如何建立的。下面是在应用程序中被处理的XML文档。为了说明SAX对名称空间的支持,我在这里特意加了一个有名称空间的元素,在这里会产生相应的前缀映射开始和结束事件。

<?xml version="1.0" encoding="GB2312"?>

<我的书架 >

   <技术书籍>

       <图书>       

   <书名>JAVA 2编程详解</书名>        

   <价格 货币单位="人民币">150</价格>          

  <购买日期>2000,1,24</购买日期>      

 </图书>      

   </技术书籍>   

<book:文学书籍 xmlns:book="http://javausr.com"/>  

 <历史书籍/>

</我的书架>

这里的例子程序只是简单地将遇到的事件信息打印出来。我们首先实现ContentHandler接口来处理在XML文档解析过程中产生的和文档内容相关的事件,代码如下所示MyContentHandler.java
package com.javausr.saxexample;

import org.xml.sax.Attributes;import org.xml.sax.ContentHandler;

import org.xml.sax.Locator;import org.xml.sax.SAXException;

public class MyContentHandler implements ContentHandler {   

private StringBuffer buf;   

public void setDocumentLocator( Locator locator ) {    } 

public void startDocument() throws SAXException {        buf=new StringBuffer();      

System.out.println("*******开始解析文档*******");    }   

public void endDocument() throws SAXException {        System.out.println("*******解析文档结束*******");    }    public void processingInstruction( String target, String instruction )        throws SAXException {    }  

 public void startPrefixMapping( String prefix, String uri ) {          System.out.println("/n前缀映射: " + prefix +" 开始!"+ "  它的URI:" + uri);    }   

public void endPrefixMapping( String prefix ) {          System.out.println("/n前缀映射: "+prefix+" 结束!");    }    public void startElement( String namespaceURI, String localName,                                  String fullName, Attributes attributes )                          throws SAXException {        System.out.println("/n 元素: " + "["+fullName+"]" +" 开始解析!");        // 打印出属性信息        for ( int i = 0; i < attributes.getLength(); i++ ) {            System.out.println("/t属性名称:" + attributes.getLocalName(i)                + " 属性值:" + attributes.getValue(i));        }    }    public void endElement( String namespaceURI, String localName,                                                      String fullName )                          throws SAXException {        //打印出非空的元素内容并将StringBuffer清空                        String nullStr="";        if (!buf.toString().trim().equals(nullStr)){           System.out.println("/t内容是: " + buf.toString().trim());        }        buf.setLength(0);        //打印元素解析结束信息        System.out.println("元素: "+"["+fullName+"]"+" 解析结束!");                  }  

 public void characters( char[] chars, int start, int length )                                throws SAXException {          //将元素内容累加到StringBuffer                          buf.append(chars,start,length);    }  

 public void ignorableWhitespace( char[] chars, int start, int length )                                  throws SAXException {    }    public void skippedEntity( String name ) throws SAXException {    }}

下面让我们创建一个调入了xerces解析器来实现XMLReader接口、并使用刚才创建的MyContentHandler来处理相应解析事件的MySAXApp.java类:
package com.javausr.saxexample;

import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;import org.xml.sax.ContentHandler;import org.xml.sax.SAXException;import java.io.IOException;public class MySAXApp {  public static void main( String[] args ) {        if ( args.length != 1 ) {      System.out.println("输入: java MySAXApp ");      System.exit(0);    }    try {        // 初始化reader        XMLReader reader = XMLReaderFactory.createXMLReader                          ("org.apache.xerces.parsers.SAXParser") ;        // 创建ContentHandler的实例        ContentHandler contentHandler = new MyContentHandler();        // reader中注册实例化的ContentHandler        reader.setContentHandler( contentHandler );        // 开始解析文档        reader.parse(args[0]);    } catch ( IOException e ) {        System.out.println("读入文档时错: " + e.getMessage());    } catch ( SAXException e ) {        System.out.println("解析文档时错: " + e.getMessage());    }  }}

下面让我们来看一下执行结果:

D:/sax/classes>java com.javausr.saxexample.MySAXApp d:/book.xml*******开始解析文档*******元素: [我的书架] 开始解析!元素: [技术书籍] 开始解析!元素: [图书] 开始解析!元素: [书名] 开始解析!        内容是: JAVA 2编程详解元素: [书名] 解析结束!元素: [价格] 开始解析!        属性名称:货币单位 属性值:人民币        内容是: 150元素: [价格] 解析结束!元素: [购买日期] 开始解析!        内容是: 2000,1,24元素: [购买日期] 解析结束!元素: [图书] 解析结束!元素: [技术书籍] 解析结束!前缀映射: book 开始!  它的URI:http://javausr.com元素: [book:文学书籍] 开始解析!元素: [book:文学书籍] 解析结束!前缀映射: book 结束!元素: [历史书籍] 开始解析!元素: [历史书籍] 解析结束!元素: [我的书架] 解析结束!*******解析文档结束*******

上面就是使用SAX解析一个XML文档的基本过程,但是MyContentHandler只是处理了解析过程中和文档内容相关的事件,如果在解析过程中出现了错误那我们需要实现ErrorHandler接口来处理。如果不注册一个错误处理器来处理的话,那么错误事件将不会被报告,而且解析器会出现不可预知的行为。在解析过程中产生的错误被分成了3类,它们分别是warningerror,以及fatalerror,也就是说在ErrorHandler中有这么三个相应的方法来处理这些错误事件。下面是对这三个错误处理方法的介绍:

方法名称

方法介绍

warning()

SAX解析器将用这个方法来报告在XML1.0规范中定义的非错误(error)或者致命错误(fatal error)的错误状态。对这个错误缺省的行为是什么也不做。SAX解析器必须在调用这个方法后继续提供正常的解析事件:应用程序应该能继续处理完文档。

error()

这个方法对应在W 3C XML 1.0规范的1.2部分中定义的"error"概念。例如,一个带有有效性验证的解析器会使用这个方法来报告违反有效性验证的情况。一个带有有效性验证的解析器会使用这个方法来报告违背有些性约束的情况。缺省的行为是什么也不做。SAX解析器必须在调用这个方法后继续提供正常的解析事件:应用程序应该能继续处理完文档。如果应用程序做不到这样,则解析器即使在XML1.0规范没有要求的情况下也要报告一个致命错误。

fatalError()

这个方法对应在W 3C XML1.0规范的1.2部分定义的"fatal error"概念。例如,一个解析器会使用这个方法来报告违反格式良好约束的情况。在解析器调用这个方法后应用程序必须表明这个文档是不可使用的,而且应该只是为了收集错误信息而继续进行处理(如果需要的话):实际上,一旦在这个方法被调用后SAX解析器可以停止报告任何事件。

下面是实现了ErrorHandler接口的MyErrorHandler.java类:
package com.javausr.saxexample;

import org.xml.sax.ErrorHandler;import org.xml.sax.SAXParseException;import org.xml.sax.SAXException;public class MyErrorHandler implements ErrorHandler {    public void warning( SAXParseException exception ) {        System.out.println("*******WARNING******");        System.out.println("/t:/t" + exception.getLineNumber());        System.out.println("/t:/t" + exception.getColumnNumber());        System.out.println("/t错误信息:/t" + exception.getMessage());        System.out.println("********************");    }    public void error( SAXParseException exception ) throws SAXException{        System.out.println("******* ERROR ******");        System.out.println("/t:/t" + exception.getLineNumber());        System.out.println("/t:/t" + exception.getColumnNumber());        System.out.println("/t错误信息:/t" + exception.getMessage());        System.out.println("********************");    }    public void fatalError( SAXParseException exception ) throws SAXException {        System.out.println("******** FATAL ERROR ********");        System.out.println("/t:/t" + exception.getLineNumber());        System.out.println("/t:/t" + exception.getColumnNumber());        System.out.println("/t错误信息:/t" + exception.getMessage());        System.out.println("*****************************");    }}

我们也要对MySAXApp.java类做一些修改(在源代码中蓝色标出的部分)使它使用MyErrorHandler.java
package com.javausr.saxexample;

import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;import org.xml.sax.ContentHandler;//引入ErrorHandlerimport org.xml.sax.ErrorHandler;import org.xml.sax.SAXException;import java.io.IOException;public class MySAXApp {            public static void main( String[] args ) {                  if ( args.length != 1 ) {            System.out.println("输入: java MySAXApp ");            System.exit(0);        }        try {            // 初始化reader            XMLReader reader = XMLReaderFactory.createXMLReader                               ("org.apache.xerces.parsers.SAXParser") ;            // 创建ContentHandler的实例            ContentHandler contentHandler = new MyContentHandler();            // reader中注册实例化的ContentHandler            reader.setContentHandler( contentHandler );            // 创建ErrorHandler的实例            ErrorHandler errorHandler = new MyErrorHandler();            // reader中注册实例化的ErrorHandler            reader.setErrorHandler( errorHandler );            // 开始解析文档            reader.parse(args[0]);    } catch ( IOException e ) {        System.out.println("读入文档时错: " + e.getMessage());    } catch ( SAXException e ) {        System.out.println("解析文档时错: " + e.getMessage());    }  }

让我们人为制造些错误来检查一下我们的错误处理器工作情况。删除元素<购买日期>的闭合标记,这样会产生一个fatal error,下面是执行结果:
D:/sax/classes>java com.javausr.saxexample.MySAXApp d:/book.xml

*******开始解析文档*******元素: [我的书架] 开始解析!元素: [技术书籍] 开始解析!元素: [图书] 开始解析!元素: [书名] 开始解析!元素: [书名] 开始解析!        内容是: JAVA 2编程详解元素: [书名] 解析结束!元素: [价格] 开始解析!        属性名称:货币单位 属性值:人民币        内容是: 150元素: [价格] 解析结束!元素: [购买日期] 开始解析!******** FATAL ERROR ********        :     8        :     7        错误信息:       The element type "购买日期" must be terminated by the matching end-tag "</购买日期>".*****************************解析文档时错: Stopping after fatal error: The element type "购买日期" must be terminated by the matching end-tag "</购买日期>".

现在总结一下如何书写基于SAX的应用程序。一般步骤如下:

1.       实现一个或多个处理器接口(ContentHandler, ErrorHandler, DTDHandler ,or EntityResover)

2.       创建一个XMLReader类的实例。

3.       在新的XMLReader实例中通过大量的set*****() 方法注册一个事件处理器的实例

4.       调用XMLReaderparse()方法来处理文档。

 
现在的程序是比较完整了,但还有许多可以改进的地方。首先在我们实现的MyContentHandler.java中,你会发现有很多方法实际上什么也没有做,但为了实现ContentHandler接口,不得不把它们写出来,这样很是麻烦。SAX API已经考虑到这个问题,在它的org.xml.sax.helper包中为我们提供了一个方便实现各种处理器接口的帮助类DefaultHandler。这个类缺省实现了上面提到的4个处理器接口。这样我们只需继承这个类,然后覆盖我们想要实现的事件处理方法即可。下面我们来新建一个继承了DefaultHandlerMyDefaultHandler.java类,然后把在MyContentHandler.javaMyErrorHandler.java中实现的事件处理方法照搬到MyDefaultHandler.java类中,那些没有使用的方法就不必重复了。这里是MyDefaultHandler.java
package com.javausr.saxexample;

import org.xml.sax.*;import org.xml.sax.helpers.*;import java.io.*;public class MyDefaultHandler extends DefaultHandler {    private StringBuffer buf;    public void startDocument() throws SAXException {        buf=new StringBuffer();        System.out.println("*******开始解析文档*******");    }    public void endDocument() throws SAXException {        System.out.println("*******解析文档结束*******");    }    public void startPrefixMapping( String prefix, String uri ) {System.out.println("/n前缀映射: " + prefix +" 开始!"+ "  它的URI:"+uri);    }    public void endPrefixMapping( String prefix ) {       System.out.println("/n前缀映射: "+prefix+" 结束!");    }    public void startElement( String namespaceURI, String localName,                                  String fullName, Attributes attributes )                          throws SAXException {        System.out.println("/n元素: " + "["+fullName+"]" +" 开始解析!");        // 打印出属性信息        for ( int i = 0; i < attributes.getLength(); i++ ) {            System.out.println("/t属性名称:" + attributes.getLocalName(i)                + " 属性值:" + attributes.getValue(i));        }    }    public void endElement( String namespaceURI, String localName,                                                      String fullName )                          throws SAXException {       //打印出非空的元素内容并将StringBuffer清空       String nullStr="";       if (!buf.toString().trim().equals(nullStr)){          System.out.println("/t内容是: " + buf.toString().trim());       }       buf.setLength(0);       //打印元素解析结束信息        System.out.println("元素: "+"["+fullName+"]"+" 解析结束!");    }    public void characters( char[] chars, int start, int length )                                throws SAXException {       //将元素内容累加到StringBuffer       buf.append(chars,start,length);    }    public void warning( SAXParseException exception ) {        System.out.println("*******WARNING******");        System.out.println("/t:/t" + exception.getLineNumber());        System.out.println("/t:/t" + exception.getColumnNumber());        System.out.println("/t错误信息:/t" + exception.getMessage());        System.out.println("********************");    }    public void error( SAXParseException exception ) throws SAXException{        System.out.println("******* ERROR ******");        System.out.println("/t:/t" + exception.getLineNumber());        System.out.println("/t:/t" + exception.getColumnNumber());        System.out.println("/t错误信息:/t" + exception.getMessage());        System.out.println("********************");    }    public void fatalError( SAXParseException exception ) throws SAXException {        System.out.println("******** FATAL ERROR ********");        System.out.println("/t:/t" + exception.getLineNumber());        System.out.println("/t:/t" + exception.getColumnNumber());        System.out.println("/t错误信息:/t" + exception.getMessage());        System.out.println("*****************************");    }}

我们也要对MySAXApp.java做相应的修改,修改已在源代码中标出:
package com.javausr.saxexample;

import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;//引入DefaultHandlerimport org.xml.sax.helpers.DefaultHandler;import org.xml.sax.SAXException;import java.io.IOException;public class MySAXApp {  public static void main( String[] args ) {              if ( args.length != 1 ) {        System.out.println("输入: java MySAXApp ");        System.exit(0);      }    try {        // 初始化reader        XMLReader reader = XMLReaderFactory.createXMLReader                         ("org.apache.xerces.parsers.SAXParser") ;        // 创建DefaultHandler的实例        DefaultHandler defaultHandler=new MyDefaultHandler();        //reader中将defaultHandler注册为ContentHandler        reader.setContentHandler(defaultHandler);        //reader中将defaultHandler注册为ErrorHandler        reader.setErrorHandler(defaultHandler);        // 开始解析文档        reader.parse(args[0]);    } catch ( IOException e ) {        System.out.println("读入文档时错: " + e.getMessage());    } catch ( SAXException e ) {        System.out.println("解析文档时错: " + e.getMessage());    }  }}


SAX API中还提供了一个过滤器接口org.xml.sax.XMLFilter,以及对它的缺省实现org.xml.sax.helper.XMLFilterImpl。使用它们可以很容易的开发出复杂的SAX应用。这里要先介绍一下过滤器设计模式。这个设计模式很好理解,就像一个净化水的过程。自然界中的水流过一个个的过滤器得到最后的饮用水。这些过滤器,有的是清除水中的泥沙,有的是杀灭水中的细菌,总之不同的过滤器完成不同的任务。在应用开发中,我们让被改造的对象(这里是事件流)通过这些过滤器对象从而得到改造后符合要求的对象。这样,在过滤器的帮助之下,我们可以非常方便的在每个过滤器中实现一个特定功能,从而创建结构复杂的应用程序。在应用程序中你可以构造任意多个过滤器,将它们串接起来完成任务。

SAX APIorg.xml.sax.XMLFilter接口继承了org.xml.sax.XMLReader接口。它与XMLReader不同的是它不像XMLReader那样通过解析文档来获取事件,而是从其他XMLReader中获取事件,当然这也包括从其他的XMLFilter中获取事件。在org.xml.sax.XMLFilter中有两个方法:

方法名称

方法描述

Public void setParent(XMLReader parent)

设置父XMLReader。这个方法让应用程序将这个过滤器连接到它的父XMLReader (也可能是另一个过滤器)

Public XMLReader getParent()

获取父XMLReader。这个方法让应用程序可以查询父XMLReader(也可能是另一个过滤器)。最好不要在父XMLReader中直接进行任何操作:让所有的事件通过这个过滤器来处理。

我们不需要自己实现org.xml.sax.XMLFilter接口,在SAX API 中提供了一个org.xml.sax.helper.XMLFilterImpl类,它不仅实现了org.xml.sax.XMLFilter接口而且还实现了其他四个核心处理器接口,我们只需要继承它即可完成我们的过滤器。刚开始使用XMLFilterImpl比较容易让人迷惑,你只需要记住:

1.       在你继承的XMLFilterImpl类中用set****()方法这册的事件处理器是给过滤后的事件流而用的。

2.       在你继承的XMLFilterImpl类中实现的那些事件处理方法,比如startDocument()startElement()characters()等才是这个过滤器实现它自身功能的地方。而通过继承XMLFilterImpl而实现的这个类会被造型成各种处理器(它本身实现了四个处理器接口)用在它的父XMLReader中。这个步骤会在你调用自己创建的过滤器的parse()方法开始解析文档时被自动执行(请参见SAX源代码)。

3.       如果不是使用带参数的构造器创建XMLFilter对象,务必使用setParent(XMLReader parent)方法连接它的父XMLReader

4.       如果使用多个过滤器的话,执行顺序是从父亲到最后的过滤器。但是开始解析却要调用最后一个过滤器的parse()方法。

 

下面让我们结合已有的例子来演示过滤器org.xml.sax.XMLFilter的作用。我们在这个过滤器中要过滤掉<技术书籍>这个元素,最后得到的事件流还是由上边实现的MyDefaultHandler来处理。源代码如下MyFilter.java
package com.javausr.saxexample;

import org.xml.sax.*;import org.xml.sax.helpers.*;import java.io.*;public class MyFilter extends XMLFilterImpl {   private String currentElement;   public MyFilter( XMLReader parent ) {      super(parent);   }   /**    * 过滤掉元素<技术书籍>的开始事件    **/   public void startElement( String namespaceURI, String localName,                             String fullName, Attributes attributes )      throws SAXException {         currentElement = localName;         if ( !localName.equals("技术书籍") ) {           super.startElement(namespaceURI, localName, fullName, attributes);         }      }   /**    * 过滤掉元素<技术书籍>的结束事件    **/   public void endElement(String namespaceURI, String localName, String                          fullName)      throws SAXException {         if ( !localName.equals("技术书籍") ) {            super.endElement(namespaceURI, localName, fullName);         }    }   /**    * 过滤掉元素<技术书籍>中的内容    **/    public void characters(char[] buffer, int start, int length) throws SAXException {        if ( !currentElement.equals("技术书籍") ) {          super.characters( buffer,start,length );        }    }}

同样我们还要修改MySAXApp.java,修改后的代码如下所示:
package com.javausr.saxexample;

import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;import org.xml.sax.helpers.DefaultHandler;//引入XMLFilterimport org.xml.sax.XMLFilter;import org.xml.sax.SAXException;import java.io.IOException;public class MySAXApp {  public static void main( String[] args ) {        if ( args.length != 1 ) {      System.out.println("输入: java MySAXApp ");      System.exit(0);    }    try {            // 初始化reader        XMLReader reader = XMLReaderFactory.createXMLReader                           ("org.apache.xerces.parsers.SAXParser") ;        //初始化过滤器        XMLFilter myFilter=new MyFilter(reader);        // 创建DefaultHandler的实例        DefaultHandler defaultHandler=new MyDefaultHandler();        //为过滤后的事件流设置ContentHandler        myFilter.setContentHandler(defaultHandler);        //为过滤后的事件流设置ErrorHandler        myFilter.setErrorHandler(defaultHandler);            // 开始解析文档,注意是使用myFilter中的解析方法        myFilter.parse(args[0]);      } catch ( IOException e ) {            System.out.println("读入文档时错: " + e.getMessage());      } catch ( SAXException e ) {            System.out.println("解析文档时错: " + e.getMessage());    }  }}

这里是最后的执行结果,我们可以发现有关<技术书籍>的全部事件已经被过滤掉了。认真看一下结果,你一定觉得奇怪,为什么<技术书籍>元素的孩子元素仍然存在。请记住SAX是把XML文档解析成事件流,所有没有被过滤的事件都会保留下来。这就是SAXDOM的最大不同。在DOM中文档被解析成了树状模型,如果你删除一个元素,那么这个元素以及它的孩子元素就都会被删除,这符合树状模型的特点。

D:/sax/classes>java com.javausr.saxexample.MySAXApp d:/book.xml

*******开始解析文档*******元素: [我的书架] 开始解析!元素: [图书] 开始解析!元素: [书名] 开始解析!        内容是: JAVA 2编程详解元素: [书名] 解析结束!元素: [价格] 开始解析!        属性名称:货币单位 属性值:人民币        内容是: 150元素: [价格] 解析结束!元素: [购买日期] 开始解析!        内容是: 2000,1,24元素: [购买日期] 解析结束!元素: [图书] 解析结束!前缀映射: book 开始!  它的URI:http://javausr.com元素: [book:文学书籍] 开始解析!元素: [book:文学书籍] 解析结束!前缀映射: book 结束!元素: [历史书籍] 开始解析!元素: [历史书籍] 解析结束!元素: [我的书架] 解析结束!*******解析文档结束*******


首先是有关元素内容的问题,在SAX API定义中元素内容可以在一次事件(由characters()方法处理)中返回,也可以在多次事件中返回,这样我们就应该考虑不能一次得到所有内容数据的情况。一般的解决办法是定义一个StringBuffer由它来保存内容数据,在元素结束或者新元素开始的时候清空这个StringBuffer从而可以保存新的内容数据。请参考上面的相应的源代码。

还有在SAX API中特意提到从 characters(char[] ch,int start,int length)方法中提取数据时一定不要从返回的字符数组范围之外读取,这一点我们也要切记。

另一个值得注意的问题是,在 startElement()方法中返回的Attributes属性列表中的属性顺序并没有被特意规定,在不同的SAX实现中也各不相同。所以我们在编写程序时不要把属性顺序想成一定的。


通过上面的介绍我想大家对SAX已经有了一个基本的了解。每一个进行XML开发的编程人员都知道DOM,那为什么在有了DOM这个功能强大的文档对象模型之后,我们还需要SAX?这就要从它们根本不同的实现方法上来分析。DOM解析器是通过将XML文档解析成树状模型并将其放入内存来完成解析工作的,而后对文档的操作都是在这个树状模型上完成的。这个在内存中的文档树将是文档实际大小的几倍。这样做的好处是结构清除、操作方便,而带来的麻烦就是极其耗费系统资源。而SAX正好克服了DOM的缺点。SAX解析器的处理过程是通读整个文档,根据文档内容产生事件,而把对这些事件的处理交由事件处理器处理。SAX不需要在内存中保存整个文档,它对系统资源的节省是显而易见的。这样在一些需要处理大型XML文档和性能要求比较高的场合就要用SAX了。

下面的表格列出了SAXDOM在一些方面的对照:

SAX

DOM

顺序读入文档并产生相应事件,可以处理任何大小的XML文档

在内存中创建文档树,不适于处理大型XML文档。

只能对文档按顺序解析一遍,不支持对文档的随意访问。

可以随意访问文档树的任何部分,没有次数限制。

只能读取XML文档内容,而不能修改

可以随意修改文档树,从而修改XML文档。

开发上比较复杂,需要自己来实现事件处理器。

易于理解,易于开发。

对开发人员而言更灵活,可以用SAX创建自己的XML对象模型。

已经在DOM基础之上创建好了文档树。

通过对SAXDOM的分析,它们各有自己的不同应用领域:

SAX适于处理下面的问题:

1.       对大型文档进行处理。

2.       只需要文档的部分内容,或者只需要从文档中得到特定信息。

3.       想创建自己的对象模型的时候。

DOM适于处理下面的问题:

1.       需要对文档进行修改

2.       需要随机对文档进行访问,例如XSLT解析器。

 


 

三种构造解析器方法:

 方法1

import java.io.*;
import javax.xml.parsers.*;
import org.xml.sax.*;

        SAXParserFactory spf = SAXParserFactory.newInstance();
        SAXParser sp = 
null;
        
try {
            sp = spf.newSAXParser();
            File file = 
new File("src/c03/students.xml");
            sp.parse(file, 
new SAXPrinter());
        }
        
catch (ParserConfigurationException e) {
            e.printStackTrace();
        }
        
catch (SAXException e) {
            e.printStackTrace();
        }
        
catch (IOException e) {
            e.printStackTrace();
        }

方法2

import org.apache.xerces.parsers.SAXParser;
import org.xml.sax.InputStream;
import java.io.FileInputStream;

SAXParser saxParser = 
new SAXParser();
saxParser.setContentHandler(
new EventHandler());
saxParser.parse(
new InputSource(new FileInputStream("game.xml")));

方法3

import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXExceptio  import java.io.IOException;

       
// 初始化reader
        XMLReader reader = XMLReaderFactory.createXMLReader
                          ("org.apache.xerces.parsers.SAXParser") ;

        
// 创建ContentHandler的实例
        ContentHandler contentHandler = new MyContentHandler();

        
// reader中注册实例化的ContentHandler
        reader.setContentHandler( contentHandler );

        
// 开始解析文档
        reader.parse(args[0]);

SAX DOM 一样也是一个访问 XML 文档的接口。 SAX Simple API for XML 的缩写。它不像 DOM 那样是 W3C 的推荐标准。它是由 XML-DEV 邮件列表的成员开发维护,由 David Megginson 领导( david@megginson.com )的一个 Public Domain 软件。 SAX 是一个彻底的自由软件,它的作者放弃了对它的所有权利,并且它也被许可用于任何目的(在文章最后附录了它的版权声明)。
到现在为止 SAX 的版本已经发展到 2.0 。在这个最新版本中增加了对名称空间( Namespaces )的支持,而且可以通过对 features 以及 properties 的设置来对解析器做全面的配置,这其中包括设置解析器是否对文档进行有效性验证,以及怎样来处理带有名称空间的元素名称等。 SAX1 中的接口已经不再使用了,这里只会讨论有关 SAX2 的开发。在本文中提到 SAX 只是指 SAX 2 。另外,本文的所有例子都是用 java 编写, SAX 解析器也使用的是 JAVA 版本。 
实现了 SAX 的解析器有很多,比如 Apache Xerces Oracle XML Parser 等等。在本文中的例子程序使用的都是 Xerces 解析器 , 你可以从 http://xml.apache.org 得到它。让我们下载得到 xerces.jar 文件然后将其加入到 classpath 中去,这样我们就已经建立好环境(在 xerces.jar 中已经包含了 SAX 接口,所以不必特意再去寻找 SAX 类库)。
SAX API 中有两个包, org.xml.sax org.xml.sax.helper 。其中 org.xml.sax 中主要定义了 SAX 的一些基础接口,如 XMLReader ContentHandler ErrorHandler DTDHandler EntityResolver 等。而在 org.xml.sax.helper 中则是一些方便开发人员使用的帮助类,如缺省实现所有处理器接口的帮助类 DefaultHandler 、方便开发人员创建 XMLReader XMLReaderFactory 类等等。在这两个包中还有一些应用于 SAX1 的接口,同时还有几个类它们只是为了便于将在 SAX1 上开发的应用移植到 SAX2 上,在这篇文章中就不涉及了。下面是我们要关注的接口和类:

Package org.xml.sax
介绍
Interfaces
接口
Attributes
定义了一个属性列表接口,供访问元素的属性列表而用。
ContentHandler
处理解析文档内容时产生的事件。
DTDHandler
处理解析 DTD 时的相应事件。
EntityResolver
处理外部实体。
ErrorHandler
处理解析过程中所遇到的文档错误事件。
Locator
为了定位解析中产生的内容事件在文档中的位置而准备的一个定位器接口。
XMLFilter
提供了一个方便应用开发的过滤器接口。
XMLReader
任何兼容 SAX2 的解析器都要实现这个接口,这个接口让应用程序可以设置或查找 features properties ,注册各种事件处理器,以及开始解析文档。
Classes
InputSource
XML 实体准备的输入源。
Exceptions
SAXException
包装了一般的 SAX 错误和警告。
SAXNotRecognizedException
为识别不出某些标识而抛出的异常。
SAXNotSupportedException
为不支持某个操作而抛出的异常。
SAXParseException
包装了一个关于 XML 解析的错误或者警告。

 

Package org.xml.sax.helpers
帮助类所在的包
Classes
AttributesImpl
Attributes 接口的缺省实现
NamespaceSupport
提供名称空间支持。
DefaultHandler
缺省实现了四个处理器接口,方便用户开发,在开发过程中会经常用到。
LocatorImpl
提供了一个对 Locator 接口的实现
XMLFilterImpl
对过滤器接口的实现,使用过滤器进行应用程序开发时,继承这个类很方便。
XMLReaderFactory
为方便创建不同的 XMLReader 而提供。也会经常用到。


SAX
的设计实现与 DOM 是完全不同的! DOM 处理 XML 文档是基于将 XML 文档解析成树状模型,放入内存进行处理。而 SAX 则是采用基于事件驱动的处理模式,它将 XML 文档转化成一系列的事件,由单独的事件处理器来决定如何处理。为了了解如何使用 SAX API 处理 XML 文档,这里先介绍一下 SAX 所使用的基于事件驱动的处理模式。
这种基于事件的处理模式是一种通用的程序设计模式,被广泛应用于 GUI 设计。在 JAVA AWT SWING 以及 JAVA BEANS 中就有它的身影。而 SAX 的基于事件驱动的处理模式就与上面三者中的非常相像。
基于事件的处理模式主要是围绕着事件源以及事件处理器(或者叫监听器)来工作的。一个可以产生事件的对象被称为事件源,而可以针对事件产生响应的对象就被叫做事件处理器。事件源和事件处理器是通过在事件源中的事件处理器注册方法连接的。这样当事件源产生事件后,调用事件处理器相应的处理方法,一个事件就获得了处理。当然在事件源调用事件处理器中特定方法的时候,会传递给事件处理器相应事件的状态信息,这样事件处理器才能够根据事件信息来决定自己的行为。
SAX 接口中,事件源是 org.xml.sax 包中的 XMLReader ,它通过 parse() 方法来开始解析 XML 文档并根据文档内容产生事件。而事件处理器则是 org.xml.sax 包中的 ContentHandler,DTDHandler,ErrorHandler, 以及 EntityResolver 这四个接口。它们分别处理事件源在解析过程中产生的不同种类的事件(其中 DTDHandler 是为解析文档 DTD 时而用)。而事件源 XMLReader 和这四个事件处理器的连接是通过在 XMLReader 中的相应的事件处理器注册方法 set***() 来完成的。详细介绍请见下表:

处理器名称
所处理事件
注册方法
org.xml.sax.ContentHandler
跟文档内容有关的所有事件:
1.       文档的开始和结束
2.       XML 元素的开始和结束
3.       可忽略的实体
4.       名称空间前缀映射开始和结束
5.       处理指令
6.       字符数据和可忽略的空格
XMLReader 中的 setContentHandler(ContentHandler handler) 方法
org.xml.sax.ErrorHandler
处理 XML 文档解析时产生的错误。如果一个应用程序没有注册一个错误处理器类,会发生不可预料的解析器行为。
setErrorHandler(ErrorHandler handler)
org.xml.sax.DTDHandler
处理对文档 DTD 进行解析时产生的相应事件
setDTDHandler(DTDHandler handler)
org.xml.sax.EntityResolver
处理外部实体
setEntityResolver(EntityResolver resolver)

在这四个处理器接口中,对我们最重要的是 ContentHandler 接口。下面让我们看一下对其中方法的说明:

方法名称
方法说明
public void setDocumentLocator (Locator locator)
设置一个可以定位文档内容事件发生位置的定位器对象
public void startDocument () throws SAXException
用于处理文档解析开始事件
public void endDocument () throws SAXException
用于处理文档解析结束事件
public void startPrefixMapping (java.lang.String prefix, java.lang.String uri) throws SAXException
用于处理前缀映射开始事件,从参数中可以得到前缀名称以及所指向的 uri
public void endPrefixMapping (java.lang.String prefix) throws SAXException
用于处理前缀映射结束事件,从参数中可以得到前缀名称
public void startElement (java.lang.String namespaceURI,java.lang.String localName,java.lang.String qName,Attributes atts) throws SAXException
处理元素开始事件,从参数中可以获得元素所在名称空间的 uri ,元素名称,属性列表等信息
public void endElement (java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName) throws SAXException
处理元素结束事件,从参数中可以获得元素所在名称空间的 uri ,元素名称等信息
public void characters (char[] ch, int start, int length) throws SAXException
处理元素的字符内容,从参数中可以获得内容
public void ignorableWhitespace (char[] ch, int start, int length) throws SAXException
处理元素的可忽略空格
public void processingInstruction (java.lang.String target, java.lang.String data) throws SAXException
处理解析中产生的处理指令事件

这里再介绍一下 org.xml.sax.XMLReader 中的方法,然后让我们看一个具体的例子。 XMLReader 是所有兼容 SAX2 的解析器都要实现的接口,由它的方法开始解析文档,并且调用它的注册方法来注册各种事件处理器。请看下表:

方法名称
方法介绍
public Boolean getFeature (java.lang.String name)throws SAXNotRecognizedException,SAXNotSupportedException
得到某个 feature 的值
public void setFeature (java.lang.String name,boolean value) throws SAXNotRecognizedException,SAXNotSupportedException
设置某个 feature 的值,例如,如果需要解析器支持对文档进行验证那么就这么调用本方法。 myReader.setFeature(http://xml.org/sax/features/validation,true); 其中 myReader XMLReader 的实例。
public java.lang.Object getProperty (java.lang.String name)throws SAXNotRecognizedException,SAXNotSupportedException
返回一个 property 的值
public void setProperty (java.lang.String name,java.lang.Object value)throws SAXNotRecognizedException,SAXNotSupportedException
设置一个 property 的值
public void setEntityResolver (EntityResolver resolver)
注册处理外部实体的 EntityResolver
public EntityResolver getEntityResolver ()
得到系统中注册的 EntityResolver
public void setDTDHandler (DTDHandler handler)
注册处理 DTD 解析事件的 DTDHandler
public DTDHandler getDTDHandler ()
得到系统中注册的 DTDHandler
public void setContentHandler (ContentHandler handler)
注册处理 XML 文档内容解析事件的 ContentHandler
public ContentHandler getContentHandler ()
得到系统中注册的 ContentHandler
public void setErrorHandler (ErrorHandler handler)
注册处理文档解析错误事件的 ErrorHandler
public ErrorHandler getErrorHandler ()
得到系统中注册的 ErrorHandler
public void parse (InputSource input)throws java.io.IOException,SAXException
开始解析一个 XML 文档。
public void parse (java.lang.String systemId)throws java.io.IOException,SAXException
开始解析一个使用系统标识符标识的 XML 文档。这个方法只是上面方法的一个快捷方式它等同于: parse(new InputSource(systemId));


让我们通过例子来看一下使用 SAX 解析 XML 文档的应用程序是如何建立的。下面是在应用程序中被处理的 XML 文档。为了说明 SAX 对名称空间的支持,我在这里特意加了一个有名称空间的元素,在这里会产生相应的前缀映射开始和结束事件。

<?xml version="1.0" encoding="GB2312"?>
< 我的书架 >
   < 技术书籍>
       < 图书>       
   < 书名>JAVA 2编程详解</书名>        
   < 价格 货币单位="人民币">150</价格>          
 < 购买日期>2000,1,24</购买日期>      
 </ 图书>      
   </ 技术书籍>   
<book: 文学书籍 xmlns:book="http://javausr.com"/>  
 < 历史书籍/>
</ 我的书架>

这里的例子程序只是简单地将遇到的事件信息打印出来。我们首先实现 ContentHandler 接口来处理在 XML 文档解析过程中产生的和文档内容相关的事件,代码如下所示 MyContentHandler.java
package com.javausr.saxexample;

import org.xml.sax.Attributes;import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;import org.xml.sax.SAXException;
public class MyContentHandler implements ContentHandler {   
private StringBuffer buf;   
public void setDocumentLocator( Locator locator ) {    } 
public void startDocument() throws SAXException {        buf=new StringBuffer();      
System.out.println("******* 开始解析文档*******");    }   
public void endDocument() throws SAXException {        System.out.println("******* 解析文档结束*******");    }    public void processingInstruction( String target, String instruction )        throws SAXException {    }  
 public void startPrefixMapping( String prefix, String uri ) {          System.out.println("/n 前缀映射: " + prefix +" 开始!"+ " 它的URI是:" + uri);    }   
public void endPrefixMapping( String prefix ) {          System.out.println("/n 前缀映射: "+prefix+" 结束!");    }    public void startElement( String namespaceURI, String localName,                                  String fullName, Attributes attributes )                          throws SAXException {        System.out.println("/n 元素: " + "["+fullName+"]" +" 开始解析!");        // 打印出属性信息        for ( int i = 0; i < attributes.getLength(); i++ ) {            System.out.println("/t属性名称:" + attributes.getLocalName(i)                + " 属性值:" + attributes.getValue(i));        }    }    public void endElement( String namespaceURI, String localName,                                                      String fullName )                          throws SAXException {        //打印出非空的元素内容并将StringBuffer清空                        String nullStr="";        if (!buf.toString().trim().equals(nullStr)){           System.out.println("/t内容是: " + buf.toString().trim());        }        buf.setLength(0);        //打印元素解析结束信息        System.out.println("元素: "+"["+fullName+"]"+" 解析结束!");                  }  
 public void characters( char[] chars, int start, int length )                                throws SAXException {          // 将元素内容累加到StringBuffer中                          buf.append(chars,start,length);    }  
 public void ignorableWhitespace( char[] chars, int start, int length )                                  throws SAXException {    }    public void skippedEntity( String name ) throws SAXException {    }}

下面让我们创建一个调入了 xerces 解析器来实现 XMLReader 接口、并使用刚才创建的 MyContentHandler 来处理相应解析事件的 MySAXApp.java 类:
package com.javausr.saxexample;

import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;import org.xml.sax.ContentHandler;import org.xml.sax.SAXException;import java.io.IOException;public class MySAXApp { public static void main( String[] args ) {        if ( args.length != 1 ) {      System.out.println(" 输入: java MySAXApp ");      System.exit(0);    }    try {        // 初始化reader        XMLReader reader = XMLReaderFactory.createXMLReader                          ("org.apache.xerces.parsers.SAXParser") ;        // 创建ContentHandler的实例        ContentHandler contentHandler = new MyContentHandler();        // 在reader中注册实例化的ContentHandler        reader.setContentHandler( contentHandler );        // 开始解析文档        reader.parse(args[0]);    } catch ( IOException e ) {        System.out.println("读入文档时错: " + e.getMessage());    } catch ( SAXException e ) {        System.out.println("解析文档时错: " + e.getMessage());    } }}

下面让我们来看一下执行结果 :

D:/sax/classes>java com.javausr.saxexample.MySAXApp d:/book.xml******* 开始解析文档*******元素: [我的书架] 开始解析!元素: [技术书籍] 开始解析!元素: [图书] 开始解析!元素: [书名] 开始解析!        内容是: JAVA 2编程详解元素: [书名] 解析结束!元素: [价格] 开始解析!        属性名称:货币单位 属性值:人民币        内容是: 150元素: [价格] 解析结束!元素: [购买日期] 开始解析!        内容是: 2000,1,24元素: [购买日期] 解析结束!元素: [图书] 解析结束!元素: [技术书籍] 解析结束!前缀映射: book 开始! 它的URI是:http://javausr.com元素: [book:文学书籍] 开始解析!元素: [book:文学书籍] 解析结束!前缀映射: book 结束!元素: [历史书籍] 开始解析!元素: [历史书籍] 解析结束!元素: [我的书架] 解析结束!*******解析文档结束*******

上面就是使用 SAX 解析一个 XML 文档的基本过程,但是 MyContentHandler 只是处理了解析过程中和文档内容相关的事件,如果在解析过程中出现了错误那我们需要实现 ErrorHandler 接口来处理。如果不注册一个错误处理器来处理的话,那么错误事件将不会被报告,而且解析器会出现不可预知的行为。在解析过程中产生的错误被分成了 3 类,它们分别是 warning error ,以及 fatalerror ,也就是说在 ErrorHandler 中有这么三个相应的方法来处理这些错误事件。下面是对这三个错误处理方法的介绍:

方法名称
方法介绍
warning()
SAX 解析器将用这个方法来报告在 XML1.0 规范中定义的非错误( error )或者致命错误 (fatal error) 的错误状态。对这个错误缺省的行为是什么也不做。 SAX 解析器必须在调用这个方法后继续提供正常的解析事件:应用程序应该能继续处理完文档。
error()
这个方法对应在 W3C XML 1.0 规范的 1.2 部分中定义的 "error" 概念。例如,一个带有有效性验证的解析器会使用这个方法来报告违反有效性验证的情况。一个带有有效性验证的解析器会使用这个方法来报告违背有些性约束的情况。缺省的行为是什么也不做。 SAX 解析器必须在调用这个方法后继续提供正常的解析事件:应用程序应该能继续处理完文档。如果应用程序做不到这样,则解析器即使在 XML1.0 规范没有要求的情况下也要报告一个致命错误。
fatalError()
这个方法对应在 W3C XML1.0 规范的 1.2 部分定义的 "fatal error" 概念。例如,一个解析器会使用这个方法来报告违反格式良好约束的情况。在解析器调用这个方法后应用程序必须表明这个文档是不可使用的,而且应该只是为了收集错误信息而继续进行处理(如果需要的话):实际上,一旦在这个方法被调用后 SAX 解析器可以停止报告任何事件。

下面是实现了 ErrorHandler 接口的 MyErrorHandler.java 类:
package com.javausr.saxexample;

import org.xml.sax.ErrorHandler;import org.xml.sax.SAXParseException;import org.xml.sax.SAXException;public class MyErrorHandler implements ErrorHandler {    public void warning( SAXParseException exception ) {        System.out.println("*******WARNING******");        System.out.println("/t 行:/t" + exception.getLineNumber());        System.out.println("/t列:/t" + exception.getColumnNumber());        System.out.println("/t错误信息:/t" + exception.getMessage());        System.out.println("********************");    }    public void error( SAXParseException exception ) throws SAXException{        System.out.println("******* ERROR ******");        System.out.println("/t行:/t" + exception.getLineNumber());        System.out.println("/t列:/t" + exception.getColumnNumber());        System.out.println("/t错误信息:/t" + exception.getMessage());        System.out.println("********************");    }    public void fatalError( SAXParseException exception ) throws SAXException {        System.out.println("******** FATAL ERROR ********");        System.out.println("/t行:/t" + exception.getLineNumber());        System.out.println("/t列:/t" + exception.getColumnNumber());        System.out.println("/t错误信息:/t" + exception.getMessage());        System.out.println("*****************************");    }}

我们也要对 MySAXApp.java 类做一些修改 ( 在源代码中蓝色标出的部分 ) 使它使用 MyErrorHandler.java
package com.javausr.saxexample;

import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;import org.xml.sax.ContentHandler;// 引入ErrorHandlerimport org.xml.sax.ErrorHandler;import org.xml.sax.SAXException;import java.io.IOException;public class MySAXApp {            public static void main( String[] args ) {                  if ( args.length != 1 ) {            System.out.println("输入: java MySAXApp ");            System.exit(0);        }        try {            // 初始化reader            XMLReader reader = XMLReaderFactory.createXMLReader                               ("org.apache.xerces.parsers.SAXParser") ;            // 创建ContentHandler的实例            ContentHandler contentHandler = new MyContentHandler();            // 在reader中注册实例化的ContentHandler            reader.setContentHandler( contentHandler );            // 创建ErrorHandler的实例            ErrorHandler errorHandler = new MyErrorHandler();            // 在reader中注册实例化的ErrorHandler            reader.setErrorHandler( errorHandler );            // 开始解析文档            reader.parse(args[0]);    } catch ( IOException e ) {        System.out.println("读入文档时错: " + e.getMessage());    } catch ( SAXException e ) {        System.out.println("解析文档时错: " + e.getMessage());    } }

让我们人为制造些错误来检查一下我们的错误处理器工作情况。删除元素 < 购买日期 > 的闭合标记,这样会产生一个 fatal error ,下面是执行结果:
D:/sax/classes>java com.javausr.saxexample.MySAXApp d:/book.xml

******* 开始解析文档*******元素: [我的书架] 开始解析!元素: [技术书籍] 开始解析!元素: [图书] 开始解析!元素: [书名] 开始解析!元素: [书名] 开始解析!        内容是: JAVA 2编程详解元素: [书名] 解析结束!元素: [价格] 开始解析!        属性名称:货币单位 属性值:人民币        内容是: 150元素: [价格] 解析结束!元素: [购买日期] 开始解析!******** FATAL ERROR ********        行:     8        列:     7        错误信息:       The element type "购买日期" must be terminated by the matching end-tag "</购买日期>".*****************************解析文档时错: Stopping after fatal error: The element type "购买日期" must be terminated by the matching end-tag "</购买日期>".

现在总结一下如何书写基于 SAX 的应用程序。一般步骤如下:
1.       实现一个或多个处理器接口 (ContentHandler, ErrorHandler, DTDHandler ,or EntityResover)
2.       创建一个 XMLReader 类的实例。
3.       在新的 XMLReader 实例中通过大量的 set*****() 方法注册一个事件处理器的实例
4.       调用 XMLReader parse() 方法来处理文档。
 
现在的程序是比较完整了,但还有许多可以改进的地方。首先在我们实现的 MyContentHandler.java 中,你会发现有很多方法实际上什么也没有做,但为了实现 ContentHandler 接口,不得不把它们写出来,这样很是麻烦。 SAX API 已经考虑到这个问题,在它的 org.xml.sax.helper 包中为我们提供了一个方便实现各种处理器接口的帮助类 DefaultHandler 。这个类缺省实现了上面提到的 4 个处理器接口。这样我们只需继承这个类,然后覆盖我们想要实现的事件处理方法即可。下面我们来新建一个继承了 DefaultHandler MyDefaultHandler.java 类,然后把在 MyContentHandler.java MyErrorHandler.java 中实现的事件处理方法照搬到 MyDefaultHandler.java 类中,那些没有使用的方法就不必重复了。这里是 MyDefaultHandler.java
package com.javausr.saxexample;

import org.xml.sax.*;import org.xml.sax.helpers.*;import java.io.*;public class MyDefaultHandler extends DefaultHandler {    private StringBuffer buf;    public void startDocument() throws SAXException {        buf=new StringBuffer();        System.out.println("******* 开始解析文档*******");    }    public void endDocument() throws SAXException {        System.out.println("*******解析文档结束*******");    }    public void startPrefixMapping( String prefix, String uri ) {System.out.println("/n前缀映射: " + prefix +" 开始!"+ " 它的URI是:"+uri);    }    public void endPrefixMapping( String prefix ) {       System.out.println("/n前缀映射: "+prefix+" 结束!");    }    public void startElement( String namespaceURI, String localName,                                  String fullName, Attributes attributes )                          throws SAXException {        System.out.println("/n元素: " + "["+fullName+"]" +" 开始解析!");        // 打印出属性信息        for ( int i = 0; i < attributes.getLength(); i++ ) {            System.out.println("/t属性名称:" + attributes.getLocalName(i)                + " 属性值:" + attributes.getValue(i));        }    }    public void endElement( String namespaceURI, String localName,                                                      String fullName )                          throws SAXException {       //打印出非空的元素内容并将StringBuffer清空       String nullStr="";       if (!buf.toString().trim().equals(nullStr)){          System.out.println("/t内容是: " + buf.toString().trim());       }       buf.setLength(0);       //打印元素解析结束信息        System.out.println("元素: "+"["+fullName+"]"+" 解析结束!");    }    public void characters( char[] chars, int start, int length )                                throws SAXException {       //将元素内容累加到StringBuffer中       buf.append(chars,start,length);    }    public void warning( SAXParseException exception ) {        System.out.println("*******WARNING******");        System.out.println("/t行:/t" + exception.getLineNumber());        System.out.println("/t列:/t" + exception.getColumnNumber());        System.out.println("/t错误信息:/t" + exception.getMessage());        System.out.println("********************");    }    public void error( SAXParseException exception ) throws SAXException{        System.out.println("******* ERROR ******");        System.out.println("/t行:/t" + exception.getLineNumber());        System.out.println("/t列:/t" + exception.getColumnNumber());        System.out.println("/t错误信息:/t" + exception.getMessage());        System.out.println("********************");    }    public void fatalError( SAXParseException exception ) throws SAXException {        System.out.println("******** FATAL ERROR ********");        System.out.println("/t行:/t" + exception.getLineNumber());        System.out.println("/t列:/t" + exception.getColumnNumber());        System.out.println("/t错误信息:/t" + exception.getMessage());        System.out.println("*****************************");    }}

我们也要对 MySAXApp.java 做相应的修改,修改已在源代码中标出:
package com.javausr.saxexample;

import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;// 引入DefaultHandlerimport org.xml.sax.helpers.DefaultHandler;import org.xml.sax.SAXException;import java.io.IOException;public class MySAXApp { public static void main( String[] args ) {              if ( args.length != 1 ) {        System.out.println("输入: java MySAXApp ");        System.exit(0);      }    try {        // 初始化reader        XMLReader reader = XMLReaderFactory.createXMLReader                         ("org.apache.xerces.parsers.SAXParser") ;        // 创建DefaultHandler的实例        DefaultHandler defaultHandler=new MyDefaultHandler();        //在reader中将defaultHandler注册为ContentHandler        reader.setContentHandler(defaultHandler);        //在reader中将defaultHandler注册为ErrorHandler        reader.setErrorHandler(defaultHandler);        // 开始解析文档        reader.parse(args[0]);    } catch ( IOException e ) {        System.out.println("读入文档时错: " + e.getMessage());    } catch ( SAXException e ) {        System.out.println("解析文档时错: " + e.getMessage());    } }}


SAX API 中还提供了一个过滤器接口 org.xml.sax.XMLFilter ,以及对它的缺省实现 org.xml.sax.helper.XMLFilterImpl 。使用它们可以很容易的开发出复杂的 SAX 应用。这里要先介绍一下过滤器设计模式。这个设计模式很好理解,就像一个净化水的过程。自然界中的水流过一个个的过滤器得到最后的饮用水。这些过滤器,有的是清除水中的泥沙,有的是杀灭水中的细菌,总之不同的过滤器完成不同的任务。在应用开发中,我们让被改造的对象(这里是事件流)通过这些过滤器对象从而得到改造后符合要求的对象。这样,在过滤器的帮助之下,我们可以非常方便的在每个过滤器中实现一个特定功能,从而创建结构复杂的应用程序。在应用程序中你可以构造任意多个过滤器,将它们串接起来完成任务。
SAX API org.xml.sax.XMLFilter 接口继承了 org.xml.sax.XMLReader 接口。它与 XMLReader 不同的是它不像 XMLReader 那样通过解析文档来获取事件,而是从其他 XMLReader 中获取事件,当然这也包括从其他的 XMLFilter 中获取事件。在 org.xml.sax.XMLFilter 中有两个方法:

方法名称
方法描述
Public void setParent(XMLReader parent)
设置父 XMLReader 。这个方法让应用程序将这个过滤器连接到它的父 XMLReader ( 也可能是另一个过滤器 )
Public XMLReader getParent()
获取父 XMLReader 。这个方法让应用程序可以查询父 XMLReader (也可能是另一个过滤器)。最好不要在父 XMLReader 中直接进行任何操作:让所有的事件通过这个过滤器来处理。

我们不需要自己实现 org.xml.sax.XMLFilter 接口,在 SAX API 中提供了一个 org.xml.sax.helper.XMLFilterImpl 类,它不仅实现了 org.xml.sax.XMLFilter 接口而且还实现了其他四个核心处理器接口,我们只需要继承它即可完成我们的过滤器。刚开始使用 XMLFilterImpl 比较容易让人迷惑,你只需要记住:
1.       在你继承的 XMLFilterImpl 类中用 set****() 方法这册的事件处理器是给过滤后的事件流而用的。
2.       在你继承的 XMLFilterImpl 类中实现的那些事件处理方法,比如 startDocument() startElement() characters() 等才是这个过滤器实现它自身功能的地方。而通过继承 XMLFilterImpl 而实现的这个类会被造型成各种处理器(它本身实现了四个处理器接口)用在它的父 XMLReader 中。这个步骤会在你调用自己创建的过滤器的 parse() 方法开始解析文档时被自动执行(请参见 SAX 源代码)。
3.       如果不是使用带参数的构造器创建 XMLFilter 对象,务必使用 setParent(XMLReader parent) 方法连接它的父 XMLReader
4.       如果使用多个过滤器的话,执行顺序是从父亲到最后的过滤器。但是开始解析却要调用最后一个过滤器的 parse() 方法。
 
下面让我们结合已有的例子来演示过滤器 org.xml.sax.XMLFilter 的作用。我们在这个过滤器中要过滤掉 < 技术书籍 > 这个元素,最后得到的事件流还是由上边实现的 MyDefaultHandler 来处理。源代码如下 MyFilter.java
package com.javausr.saxexample;

import org.xml.sax.*;import org.xml.sax.helpers.*;import java.io.*;public class MyFilter extends XMLFilterImpl {   private String currentElement;   public MyFilter( XMLReader parent ) {      super(parent);   }   /**    * 过滤掉元素<技术书籍>的开始事件    **/   public void startElement( String namespaceURI, String localName,                             String fullName, Attributes attributes )      throws SAXException {         currentElement = localName;         if ( !localName.equals("技术书籍") ) {           super.startElement(namespaceURI, localName, fullName, attributes);         }      }   /**    * 过滤掉元素<技术书籍>的结束事件    **/   public void endElement(String namespaceURI, String localName, String                          fullName)      throws SAXException {         if ( !localName.equals("技术书籍") ) {            super.endElement(namespaceURI, localName, fullName);         }    }   /**    * 过滤掉元素<技术书籍>中的内容    **/    public void characters(char[] buffer, int start, int length) throws SAXException {        if ( !currentElement.equals("技术书籍") ) {          super.characters( buffer,start,length );        }    }}

同样我们还要修改 MySAXApp.java ,修改后的代码如下所示:
package com.javausr.saxexample;

import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;import org.xml.sax.helpers.DefaultHandler;// 引入XMLFilterimport org.xml.sax.XMLFilter;import org.xml.sax.SAXException;import java.io.IOException;public class MySAXApp { public static void main( String[] args ) {        if ( args.length != 1 ) {      System.out.println("输入: java MySAXApp ");      System.exit(0);    }    try {            // 初始化reader        XMLReader reader = XMLReaderFactory.createXMLReader                           ("org.apache.xerces.parsers.SAXParser") ;        //初始化过滤器        XMLFilter myFilter=new MyFilter(reader);        // 创建DefaultHandler的实例        DefaultHandler defaultHandler=new MyDefaultHandler();        //为过滤后的事件流设置ContentHandler        myFilter.setContentHandler(defaultHandler);        //为过滤后的事件流设置ErrorHandler        myFilter.setErrorHandler(defaultHandler);            // 开始解析文档,注意是使用myFilter中的解析方法        myFilter.parse(args[0]);      } catch ( IOException e ) {            System.out.println("读入文档时错: " + e.getMessage());      } catch ( SAXException e ) {            System.out.println("解析文档时错: " + e.getMessage());    } }}

这里是最后的执行结果,我们可以发现有关 < 技术书籍 > 的全部事件已经被过滤掉了。认真看一下结果,你一定觉得奇怪,为什么 < 技术书籍 > 元素的孩子元素仍然存在。请记住 SAX 是把 XML 文档解析成事件流,所有没有被过滤的事件都会保留下来。这就是 SAX DOM 的最大不同。在 DOM 中文档被解析成了树状模型,如果你删除一个元素,那么这个元素以及它的孩子元素就都会被删除,这符合树状模型的特点。
D:/sax/classes>java com.javausr.saxexample.MySAXApp d:/book.xml

******* 开始解析文档*******元素: [我的书架] 开始解析!元素: [图书] 开始解析!元素: [书名] 开始解析!        内容是: JAVA 2编程详解元素: [书名] 解析结束!元素: [价格] 开始解析!        属性名称:货币单位 属性值:人民币        内容是: 150元素: [价格] 解析结束!元素: [购买日期] 开始解析!        内容是: 2000,1,24元素: [购买日期] 解析结束!元素: [图书] 解析结束!前缀映射: book 开始! 它的URI是:http://javausr.com元素: [book:文学书籍] 开始解析!元素: [book:文学书籍] 解析结束!前缀映射: book 结束!元素: [历史书籍] 开始解析!元素: [历史书籍] 解析结束!元素: [我的书架] 解析结束!*******解析文档结束*******


首先是有关元素内容的问题,在 SAX API 定义中元素内容可以在一次事件(由 characters() 方法处理)中返回,也可以在多次事件中返回,这样我们就应该考虑不能一次得到所有内容数据的情况。一般的解决办法是定义一个 StringBuffer 由它来保存内容数据,在元素结束或者新元素开始的时候清空这个 StringBuffer 从而可以保存新的内容数据。请参考上面的相应的源代码。
还有在 SAX API 中特意提到从 characters (char[] ch,int start,int length) 方法中提取数据时一定不要从返回的字符数组范围之外读取,这一点我们也要切记。
另一个值得注意的问题是,在 startElement () 方法中返回的 Attributes 属性列表中的属性顺序并没有被特意规定,在不同的 SAX 实现中也各不相同。所以我们在编写程序时不要把属性顺序想成一定的。

通过上面的介绍我想大家对 SAX 已经有了一个基本的了解。每一个进行 XML 开发的编程人员都知道 DOM ,那为什么在有了 DOM 这个功能强大的文档对象模型之后,我们还需要 SAX ?这就要从它们根本不同的实现方法上来分析。 DOM 解析器是通过将 XML 文档解析成树状模型并将其放入内存来完成解析工作的,而后对文档的操作都是在这个树状模型上完成的。这个在内存中的文档树将是文档实际大小的几倍。这样做的好处是结构清除、操作方便,而带来的麻烦就是极其耗费系统资源。而 SAX 正好克服了 DOM 的缺点。 SAX 解析器的处理过程是通读整个文档,根据文档内容产生事件,而把对这些事件的处理交由事件处理器处理。 SAX 不需要在内存中保存整个文档,它对系统资源的节省是显而易见的。这样在一些需要处理大型 XML 文档和性能要求比较高的场合就要用 SAX 了。
下面的表格列出了 SAX DOM 在一些方面的对照:

SAX
DOM
顺序读入文档并产生相应事件,可以处理任何大小的 XML 文档
在内存中创建文档树,不适于处理大型 XML 文档。
只能对文档按顺序解析一遍,不支持对文档的随意访问。
可以随意访问文档树的任何部分,没有次数限制。
只能读取 XML 文档内容,而不能修改
可以随意修改文档树,从而修改 XML 文档。
开发上比较复杂,需要自己来实现事件处理器。
易于理解,易于开发。
对开发人员而言更灵活,可以用 SAX 创建自己的 XML 对象模型。
已经在 DOM 基础之上创建好了文档树。

通过对 SAX DOM 的分析,它们各有自己的不同应用领域:
SAX 适于处理下面的问题:
1.       对大型文档进行处理。
2.       只需要文档的部分内容,或者只需要从文档中得到特定信息。
3.       想创建自己的对象模型的时候。
DOM 适于处理下面的问题:
1.       需要对文档进行修改
2.       需要随机对文档进行访问,例如 XSLT 解析器。
 

 
三种构造解析器方法:
  方法 1
import  java.io.*;
import  javax.xml.parsers.*;
import  org.xml.sax.*;

        SAXParserFactory spf = SAXParserFactory.newInstance();
        SAXParser sp = 
null ;
        
try  {
            sp = spf.newSAXParser();
            File file = 
new  File("src/c03/students.xml");
            sp.parse(file, 
new  SAXPrinter());
        }
        
catch  (ParserConfigurationException e) {
            e.printStackTrace();
        }
        
catch  (SAXException e) {
            e.printStackTrace();
        }
        
catch  (IOException e) {
            e.printStackTrace();
        }
方法 2
import  org.apache.xerces.parsers.SAXParser;
import  org.xml.sax.InputStream;
import  java.io.FileInputStream;

SAXParser saxParser = 
new  SAXParser();
saxParser.setContentHandler(
new  EventHandler());
saxParser.parse(
new  InputSource( new  FileInputStream("game.xml")));
方法 3
import  org.xml.sax.XMLReader;
import  org.xml.sax.helpers.XMLReaderFactory;
import  org.xml.sax.ContentHandler;
import  org.xml.sax.SAXException;
import  java.io.IOException;

       
//  初始化 reader
        XMLReader reader = XMLReaderFactory.createXMLReader
                          ("org.apache.xerces.parsers.SAXParser") ;

        
//  创建 ContentHandler 的实例
        ContentHandler contentHandler =  new  MyContentHandler();

        
//  reader 中注册实例化的 ContentHandler
        reader.setContentHandler( contentHandler );

        
//  开始解析文档
        reader.parse(args[0]);
 
 
 

n;

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值