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版本。
像DOM一样,SAX并不是一个实际可以使用的XML文档解析器,而是其他兼容SAX的解析器要实现的接口和帮助类的集合。如果你想使用SAX的话,你必须满足下面的要求:
实现了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 | 跟文档内容有关的所有事件:
| 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对名称空间的支持,我在这里特意加了一个有名称空间的元素,在这里会产生相应的前缀映射开始和结束事件。
<我的书架 >
<技术书籍>
<图书>
<书名>JAVA 2编程详解书名>
<价格 货币单位="人民币">150价格>
<购买日期>2000,1,24购买日期>
图书>
技术书籍>
|
这里的例子程序只是简单地将遇到的事件信息打印出来。我们首先实现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:saxclasses>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("*****************************");
}
} |
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/443058/viewspace-1053476/ ,如需转载,请注明出处,否则将追究法律责任。