就目前来说,有三种方式可以解析XML文件:DOM、SAX、StAX。DOM将整个XML文件加载到内存中,并构建出节点树;应用程序可以通过遍历节点树的方式来解析XML文件中的各个节点、属性等信息;这种方式便于对XML节点的添加修改等,而且解析也很方便,然后它比较耗费内存,解析速度也不快。SAX则是基于事件的解析,解析器在一次读取XML文件中根据读取的数据产生相应的事件,由应用程序实现相应的事件处理逻辑,即它是一种“推”的解析方式;这种解析方法速度快、占用内存少,但是它需要应用程序自己处理解析器的状态,实现起来会比较麻烦,而且它只支持对XML文件的读取,不支持写入。不同于SAX的“推”的解析方式,StAX是基于“拉”的解析方式,即应用程序根据自己的需要控制解析器的读取;这种方式继承了SAX解析速度快、占用内存少等优点,同时它好保持了接口简单、编程容易等特点;不过它也应该不支持写XML文件的,木有仔细看过这个框架,先猜测一下~~。貌似DOM底层采用了SAX的实现,因而本文首先介绍基于SAX方式的XML文件解析。
SAX的解析框架相对比较简单,以下是它核心类关系图:
InputSource类
InputSource是SAX中对要被解析的XML资源文件的抽象,它封装了以下信息:
private Reader characterStream;
private InputStream byteStream;
private String systemId;
private String publicId;
private String encoding;
应用程序可以显示的设置characterStream、byteStream来指定实际的XML资源文件,或者使用systemId和publicId的方式来定位XML资源文件。这里systemId、publicId借用了导入DTD文件是定义的SYSTEM、PUBLIC概念。在DTD中,SYSTEM和PUBLIC都表示外部资源文件,所不同的是SYSTEM指定的是具体的资源文件,它可以是相对路径也可以是绝对路径,而PUBLIC则是使用定义的名称查找资源文件。如以下是Spring使用DTD时的写法:
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" " http://www.springframework.org/dtd/spring-beans.dtd ">
这里的publicId是:“-//SPRING//DTD BEAN//EN”
systemId是:“http://www.springframework.org/dtd/spring-beans.dtd”
很多框架都实现了自己的EntityResolver,以实现自定义的Entity查找逻辑,就像Spring,它首先在当前ClassPath下查找对应的DTD文件(Spring.jar文件中)。
在实现中,很少去使用publicId的,以Spring源码中没有使用publicId,甚至我在xerces源码中都没有看到对publicId的处理,不过看它的文档,貌似是提供了publicId的实现,不知道是Java的实现版本没有提供还是我没有找对地方,而且按测试的结果,它不能只存在publicId也就是说如果存在publicId的话,systemId也必须存在。按着文档对publicId做一个简单的解释,在以上的定义中“-//SPRINT/DTD BEAN//EN”只是一个名字,在XML解析引擎中使用这个名字去查找资源文件真正的位置,这个名字和资源文件实际路径的映射文件一般定义在一个catalog文件中(有些引擎支持配置),其定义格式一般为:
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<public publicId="-//OASIS//DTD XML DocBook V4.1.2//EN"
uri="docbook/xml/docbookx.dtd"/>
<system systemId="urn:x-oasis:docbook-xml-v4.1.2"
uri="docbook/xml/docbookx.dtd"/>
<delegatePublic publicIdStartString="-//Example//"
catalog="http://www.example.com/catalog"/>
<public publidId="-//SPRING//DTD BEAN//EN"
uri="http://www.springframework.org/dtd/spring-beans.dtd" />
</catalog>
publicId以目前来看感觉可以忽略。有兴趣对这个做深入研究的童鞋可以参考一下文档:
http://xerces.apache.org/xml-commons/components/resolver/resolver-article.html
http://supportweb.cs.bham.ac.uk/documentation/tutorials/docsystem/build/tutorials/docbooksys/segmentedhtml/ch08s02.html
在使用InputSource时,还需要注意资源文件的查找顺序问题,即InputSource中的characterStream、byteStream、systemId都可以表示一个资源文件,因而需要定义它们的查找顺序:即先查看characterStream字段,如果有值则使用(此时encoding字段无效);然后查看byteStream字段,如果有值,则使用;最后尝试使用URI解析systemId字段(可以是相对路径),如果能找到对应的资源文件,则使用;否则,抛出异常。对后两种情况,可以指定encoding字段表示资源文件的编码方式。
XMLReaderFactory类
XMLReaderFactory从这个类的名字中已经能知道它是用于创建XMLReader实例的工场类,它提供两个静态方法以创建XMLReader实例:
public static XMLReader createXMLReader();
public static XMLReader createXMLReader(String className);
对不带参数的createXMLReader()方法,它实现了一种动态查找XMLReader具体实现类的方式:
1. 首先查看系统属性中是否存在“org.xml.sax.driver”属性的定义,如果存在,则使用该属性定义的XMLReader实现类。
2. 其次查看ClassPath下是否存在“META-INF/services/org.xml.sax.driver”文件的定义,如果存在,则使用该文件中定义XMLReader的实现类。
3. 否则,默认使用“com.sun.org.apache.xerces.internal.parsers.SAXParser”类作为XMLReader的实现类。
而对带参数的createXMLReader()工场方法来说,它只是实例化传入的XMLReader的实现类。
XMLReader接口和实现
XMLReader是实现真正解析XML资源文件的接口,之所以不使用Parser是因为这个名称已经在SAX1中被使用,而在SAX2中将实现解析的接口名称重命名成XMLReader。在使用SAX解析XML资源文件时,默认使用SAXParser实现类,它继承自AbstractSAXParser(参考以上类关系图)。XMLReader接口提供以下方法:
public interface XMLReader {
public boolean getFeature (String name) throws SAXNotRecognizedException, SAXNotSupportedException;
public void setFeature (String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException;
public Object getProperty (String name) throws SAXNotRecognizedException, SAXNotSupportedException;
public void setProperty (String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException;
public void setEntityResolver (EntityResolver resolver);
public EntityResolver getEntityResolver ();
public void setDTDHandler (DTDHandler handler);
public DTDHandler getDTDHandler ();
public void setContentHandler (ContentHandler handler);
public ContentHandler getContentHandler ();
public void setErrorHandler (ErrorHandler handler);
public ErrorHandler getErrorHandler ();
public void parse (InputSource input) throws IOException, SAXException;
public void parse (String systemId) throws IOException, SAXException;
}
这个接口定义了一些操作XML解析器的属性和方法:
1. Feature
Feature值一般是一个URI的全称,用于定义当前解析器支持的特性,比如按注释,所有解析器都要识别的特性有:
http://xml.org/sax/features/namespaces
http://xml.org/sax/features/namespace-prefixes
还有其他一些常见的Feature有(默认AbstractSAXParser支持的Feature):
http://xml.org/sax/features/string-interning
http://xml.org/sax/features/is-standalone
http://xml.org/sax/features/xml-1.1
http://xml.org/sax/features/lexical-handler/parameter-entities
http://xml.org/sax/features/resolve-dtd-uris
http://xml.org/sax/features/xmlns-uris
http://xml.org/sax/features/unicode-normalization-checking
http://xml.org/sax/features/use-entity-resolver2
http://xml.org/sax/features/use-attributes2
http://xml.org/sax/features/use-locator2
http://xml.org/sax/features/internal/parser-settings
http://xml.org/sax/features/internal/xinclude
一些用户自定义的解析器可以指定自己支持的Feature。XMLReader提供接口查询、设置指定当前XMLReader是否支持某种Feature。
2. Property
XMLReader还支持通过URI方式获取解析器相关的一些属性值,一些扩展的事件Handler也可以通过该方式定义。如常见的属性定义有:
http://xml.org/sax/properties/document-xml-version
http://xml.org/sax/properties/lexical-handler
http://xml.org/sax/properties/declaration-handler
http://xml.org/sax/properties/dom-node
具体可以参考xerces中的介绍:
http://xerces.apache.org/xerces2-j/properties.html
3. 事件处理器(Event Handlers)注册方法
应用程序通过注册相应的事件处理器来和XMLReader解析器教务,解析器在解析过程中产生的事件都会通过调用相应事件处理器中的相应的方法来将信息传给应用程序。默认支持的时间处理器有:
EntityResolver:实现用户自定义外部实体解析处理逻辑。
DTDHandler:实现在NOTATION定义以及存在没有解析的Entity定义时,用户可以加入自定义的处理逻辑。
ContentHandler:处理所有对XML内容解析时产生的所有事件。
ErrorHandler:解析器在解析过程中遇到错误时被调用。
通过设置属性值的方式,AbstractSAXParser还支持以下两种Handler:
LexicalHandler:SAX2扩展接口,提供更多的XML资源文件相关的信息,如注释、CDATA等。
DeclHandler:SAX2扩展接口,在DTD定义事件中提供回调方法。
4. 解析方法(parse)
解析器实现解析XML资源文件的方法。应用程序可以传入InputSource实例,也可以传入systemId的值,即一个URI字符串或相对路径指定的文件名。解析方法(parse)是线程同步的,对一个XMLReader实例在解析时,另一个线程会等待该线程结束后才开始解析新的文件,因而一般情况下,在多个线程中都会创建各自的XMLReader实例。然而当一个XMLReader实例解析完成后,我们可以重用该XMLReader实例解析新的XML文件,此时之前的Feature、Property以及Handler的注册等信息都保持不变,我们可以手动调用相应的方法改变之。
EntityResolver接口
EntityResolver接口提供应用程序自定义实体解析的扩展点,即应用程序可以注册自己的实体解析类以实现自己特定的逻辑,如在本地、数据库、网络中查找外部实体。比如Spring就实现了自己的BeansDtdResolver,对InputSource小节中定义的spring-beans.dtd,它会先查找BeanDtdResolver类所在的目录(一般为jar包内)中存在的spring-beans.dtd,如果存在该文件,则会加载该文件作为DTD文件,否则,使用默认的EntityResolver(即返回null,则XML引擎自己提供的解析器)。
然而什么是实体(Entity)呢?如果使用DTD作为XML文件的定义模板,那么在引入DTD文件时,即使引入一个外部实体,其PUBLIC、SYSTEM定义分别对应publicId和systemId。实体的另一个使用地方是在DTD文件定义时可以定义命名实体,而该命名实体可以在XML文件中使用(貌似这个用途不怎么多~~)。比如我们可以定义如下DTD文件:
<!ELEMENT website (name,copyright)>
<!ELEMENT name (#PCDATA)>
<!-- Parameter Entity-->
<!ENTITY % copyrightElement "<!ELEMENT copyright (#PCDATA)>">
%copyrightElement;
<!--Normal Entity-->
<!ENTITY name "cnblog">
<!--External Entity-->
<!ENTITY copyright SYSTEM "copyright.desc">
该DTD可以在以下XML文件中使用:
<?xml version="1.1" encoding="UTF-8"?>
<!DOCTYPE website SYSTEM "../dtds/entitiesDtD.dtd">
<website>
<name>&name;</name>
<copyright>©right;</copyright>
</website>
此时,在解析XML文件时,&name;值为cnblog,而©right;的值为copyright.desc文件中的内容。EntityResolver接口也只有在需要解析外部实体是才会被调用,比如在解析&name;实体时就不会被调用。
EntityResolver的接口定义如下:
public interface EntityResolver {
public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException;
}
resolveEntity()方法返回用户自定义的InputSource实例,如果该方法返回null,则XML解析引擎默认为外部实体为URL,并使用该URL创建InputSource。
一般情况下使用XML解析引擎内部默认的实现即可,但是像Spring这种从本地读取DTD文件时,则需要实现自己的EntityResolver,其实现核心代码如下:
public InputSource resolveEntity(String publicId, String systemId) throws IOException {
if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
......
Resource resource = new ClassPathResource(dtdFile, getClass());
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
return source;
......
}
// Use the default behavior -> download from website or wherever.
return null;
}
如果使用XSD作为XML的定义模板,我们可以定义schemaLocation将XSD文件引入,比如Spring配置文件中的用法:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="myCustomer" class="levin.spring.test.Customer">
<property name="name" value="customer"></property>
<property name="address" value="shanghai"></property>
<property name="user" ref="user"></property>
</bean>
<context:property-holder id="propertyHolder" class="org.springframework.context.PropertyHolder" />
</beans>
以上例子,我们将所有XML的命名空间定义在beans标签中,并在标签中使用xsi:schemaLocation定义了所有xsd文件的位置,在XML解析中,每当遇到一个新的命名空间,解析器就会先调用resolveEntity方法,并将xsi:schemaLocation中定义的位置传入resolveEntity()方法(publicId为null、systemId为schema的位置,如http://www.springframework.org/schema/context/spring-context.xsd, 默认解析器会从这个位置上查找xsd文件,然后开始解析相应的标签,比如对默认命名空间所使用的xsd文件,SAXParser会先解析该文件,然后真正解析beans标签。如DTD作为外部实体的导入,应用程序可以定义自己的EntityResolver以实现自定义的外部实体查找逻辑,如Spring定义自己的EntityResolver(PluggableSchemaResolver),以从本地查找xsd文件:
public InputSource resolveEntity(String publicId, String systemId) throws IOException {
String resourceLocation = getSchemaMapping(systemId);
if (resourceLocation != null) {
Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
return source;
}
// Use the default behavior -> download from website or wherever.
return null;
}
其实以上配置文件还可以写成:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myCustomer" class="levin.spring.test.Customer">
<property name="name" value="customer"></property>
<property name="address" value="shanghai"></property>
<property name="user" ref="user"></property>
</bean>
<context:property-holder xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
id="propertyHolder" class="org.springframework.context.PropertyHolder" />
</beans>
最后,默认SAXParser默认实现不支持XSD文件的解析,我们需要手动的设置以下属性以打开这一属性:
XMLReader reader = XMLReaderFactory.createXMLReader(); reader.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema"); reader.setFeature("http://apache.org/xml/features/validation/schema", true);
DTDHandler接口
提供用户在NOTATION定义以及存在没有解析的Entity定义时注册用户自定义的处理逻辑。这里需要解释两个定义:什么是NOTATION以及什么是没有解析的Entity定义。
首先来解释一下什么是NOTATION:在XML文件中可以包含一些非XML的数据,NOTATION即定义了这种非XML数据的格式,从而应用程序可以识别这些数据。然而什么是非XML数据呢?举个例子,以下DTD定义了一个image标签,它包含source属性,而这个source属性指向某个gif图片:
<!NOTATION gif SYSTEM "image/gif">
<!ENTITY JENN SYSTEM "http://images.about.com/sites/guidepics/html.gif" NDATA gif>
<!ELEMENT image EMPTY>
<!ATTLIST image source ENTITY #REQUIRED>
对应的XML数据段:
<image source="JENN" />
我们知道在XML文件文本数据,中一般只能包含文本数据,那么如何将图片文件嵌入到XML文件中呢?XML提供了三种方法实现在XML文件中包含二进制数据:一种是使用以上NOTATION的方式;一种是使用mine扩展;还有一种是将二进制数据保存到<![CDATA[ …]]>中(具体可以参考:http://www.ibm.com/developerworks/cn/xml/x-binary/index.html)。
其次,什么是没有解析的Entity定义呢?没有解析并不是在DTD文件中所有没有被使用到的Entity,而是指所有那些NDATA类型的实体定义,因为这些实体需要用户自己去解析,因而XML解析引擎不会默认对他们做解析。比如以上的&name;实体是被解析过的,因而它会转换成“cnblog”值显示出来;而以上JENN的这个实体则没有被XML解析引擎解析,因而它会触发DTDHandler接口的调用。
那么我们再来看一下DTDHandler接口提供的方法吧:
public interface DTDHandler {
public abstract void notationDecl (String name, String publicId,
String systemId) throws SAXException;
public abstract void unparsedEntityDecl (String name, String publicId,
String systemId, String notationName) throws SAXException;
}
这两个方法正好提供了DTDHandler两种特性的回调方法:
1. 当XML解析引擎在解析DTD文件中NOTATION的定义时,它会调用notationDecl()方法,此时用户注册的DTDHandler则可以根据传入的publicId和systemId解析当前NOTATION相应的数据。
2. 当XML解析引擎在解析DTD文件中NDATA类型的Entity定义时,它会调用unparsedEntityDecl()方法。用户可以根据传入的publicId、systemId、notationName等信息解析当前Entity,比如以上则是从指定的URL中读取html.gif文件,读到的数据可以name作为key保存起来,之后,当用户在解析source属性是,可以根据source属性中的值得到相应的数据保存起来。
一般来说DTD文件的解析要在所有XML文件中的Element解析之前,因而DTDHandler中的回调函数一般在startDocument()事件之后,而在第一个startElement()事件之前。
最后,感觉应该很少有用户会去使用NOTATION的概念吧,除非用户想在XML文件中包含二进制数据,其实我个人感觉这种二进制数据的实现方式也不好,所以个人感觉很少有人需要去实现DTDHandler的接口。
ContentHandler接口
ContentHandler接口处理所有对XML内容解析时产生的所有事件。它的接口定义如下:
public interface ContentHandler {
public void setDocumentLocator(Locator locator);
public void startDocument() throws SAXException;
public void endDocument() throws SAXException;
public void startPrefixMapping (String prefix, String uri)
throws SAXException;
public void endPrefixMapping (String prefix) throws SAXException;
public void startElement (String uri, String localName, String qName,
Attributes atts) throws SAXException;
public void endElement (String uri, String localName, String qName)
throws SAXException;
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 (String target, String data)
throws SAXException;
public void skippedEntity (String name) throws SAXException;
}
a. setDocumentLocator方法
解析器解析XML文件内容时,它会首先调用setDocumentLocator设置一个Locator实例,应用程序可以从Locator实例中获取解析器当前在解析的位置:
public interface Locator {
public abstract String getPublicId();
public abstract String getSystemId();
public abstract int getLineNumber();
public abstract int getColumnNumber();
}
其中publicId、systemId用于定位正在解析的文件,它可以是正在解析的XML文件,也可以是在XML中引入的外部实体文件。lineNumber、columnNumber则用于定位当前事件发生时所在解析文件的行号和列号,行号和列号只是用于近似的诊断信息,不保证完全正确。
b. startDocument、endDocument方法
这两个方法提供在XML文件解析开始和结束的位置插入应用程序自定义的逻辑,只是两个常见的扩展点。
c. startPrefixMapping、endPrefixMapping方法
这两个方法是在引入一个命名空间和结束该命名空间的作用域时调用的,比如在EntityResolver接口中定义了两种方式的Spring XML配置文件,对第一个定义文件中,context命名空间在beans标签中定义,因而在解析beans标签时,它会首先调用startPrefixMapping方法,其参数中prefix为context,uri为http://www.springframework.org/schema/context,然后在调用beans标签的startElement方法;此时context命名空间的作用域范围在beans标签内,因而当beans结束后(在调用beans标签相关的endElement方法),会调用endPrefixMapping方法。而在第二个定义文件中,context命名空间在context:property-holder标签中定义,此时startPrefixMapping方法会在解析该标签时调用,但是在调用该标签相关的startElement之前;并且此时context的命名空间的作用域也只是在context:property-holder中。
d. startElement、endElement方法
在解析一个标签开始和结束时分别会调用这两个方法。在调用startElement之前,xsd外部实体的引入、解析、验证都已经完成了(resolveEntity方法),命名空间解析也已经完成(startPrefixMapping方法)。在startElement方法的参数中,uri为该标签命名空间所的uri的值,如http://www.springframework.org/schema/context,如果没有定义则为空;localName指本地名字,如property-holder;qName为包含命名空间的名字,如context:property-holder;atts为该标签中定义的属性值,它封装在Attributes接口中:
public interface Attributes {
public abstract int getLength ();
public abstract String getURI (int index);
public abstract String getLocalName (int index);
public abstract String getQName (int index);
public abstract String getType (int index);
public abstract String getValue (int index);
public int getIndex (String uri, String localName);
public int getIndex (String qName);
public abstract String getType (String uri, String localName);
public abstract String getType (String qName);
public abstract String getValue (String uri, String localName);
public abstract String getValue (String qName);
}
Attributes类提供了两种类型的查找方式:索引和命名属性。其中索引的顺序并没有定义,因而在不同版本中的顺序可能是不同的,这里没用定义遍历所有属性的方法,因而我们可以使用getLength方法获取属性个数,然后使用索引方式获取相应的值,而在其他情况下,个人不能推荐使用索引方式。对命名属性,可以使用两种方式:一种是使用包含命名空间的全名(qName),另一种是使用uri加localName的方式定义。
关于getType,这里的type指的是DTD、XSD中定义的属性的类型:"CDATA", "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES", or "NOTATION",默认为CDATA。而在Java读取到的所有属性的值都是字符串。
e. characters方法
SAXParser在没读到一些非标签内中的字符串时,都会调用该方法,这些字符串可以是某个标签的值,也可以是标签之前的空格和换行符。如一下XML数据:
会在标记的7个位置分别调用characters方法,其中ch数组是文件读取的字符串数组,其大小默认为2k,可以使用http://apache.org/xml/properties/input-buffer-size属性值设置其大小,但是如果新设置的大小不能一次读取不可分割的数据内容时,该大小或增加;start指定当前字符串在ch数组中的起始位置;length则是指当前字符串的长度。
如果我们需要读取该部分的字符串内容,可以使用以下方式:
new String(ch, start, length)
f. ignorableWhitespace方法
当解析器遇到可忽略的空格时被调用。什么是可忽略的空格呢?可以参考:http://www.w3.org/TR/2006/REC-xml-20060816/#sec-white-space,我一直没有读懂文档中的解释.....而且据xerces中解释,ignorableWhitespace只用于DTD中,可参考:http://xerces.apache.org/xerces2-j/faq-sax.html中相关内容。不过貌似我们很少使用这个方式,我就不深究了。
g. processingInstruction方法
在XML文件中还可以定义一下指令,以供一些特定的处理工具读取并处理。SAXPaser在遇到这些指令时,就会调用这个方法。这些指令如:
<?xml-stylesheet href="show.css" type="text/css" ?>
此时target为:xml-stylesheet,data为:href=”show.css” type=”text/css”
h. skippedEntity方法
当SAXParser跳过一个实体时,该方法会被调用。那么什么情况下它会跳过一个实体的解析呢?这个我貌似也一直木有读懂….幸好这个方法用的也不多。
ErrorHandler接口
ErrorHandler在解析器在解析过程中遇到错误时被调用。ErrorHandler分成三个级别:warn、error、fatalerror,解析器会根据当前错误的级别调用相应的方法。
public interface ErrorHandler {
public abstract void warning (SAXParseException exception)
throws SAXException;
public abstract void error (SAXParseException exception)
throws SAXException;
public abstract void fatalError (SAXParseException exception)
throws SAXException;
}
DefaultHandler类
DefaultHandler类实现了所有以上接口EntityResolver、DTDHandler、ContentHandler、ErrorHandler,并提供了所有方法的空实现。我们在使用SAX时,一般都是继承自DefaultHandler,然后实现需要的方法,而保持其他方法默认实现。
LexicalHandler接口
该接口是SAX2提供的扩展接口,它可以处理XML资源文件中更多的词汇(Lexical)信息,如注释、CDATA等。可以使用以下方法注册该接口:
xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler", handler);
LexicalHandler接口定义:
public interface LexicalHandler {
//在一个DTD定义开始时调用
public abstract void startDTD (String name, String publicId, String systemId)
throws SAXException;
//在一个DTD定义结束时调用
public abstract void endDTD ()
throws SAXException;
//在一个实体解析开始时调用
public abstract void startEntity (String name)
throws SAXException;
//在一个实体解析结束时调用
public abstract void endEntity (String name)
throws SAXException;
//CDATA数据区开始时调用
public abstract void startCDATA ()
throws SAXException;
//在一个CDATA数据区结束后调用
public abstract void endCDATA ()
throws SAXException;
//所有的注释信息,包括外部实体(如DTD)中的注释信息
public abstract void comment (char ch[], int start, int length)
throws SAXException;
}
DeclHandler接口
SAX2扩展接口,它提供了DTD定义事件的回调方法。可以使用一下方法注册该接口:
xmlReader.setProperty("http://xml.org/sax/properties/declaration-handler", handler);
DeclHandler接口定义:
public interface DeclHandler {
//定义一个Element标签时的回调,name为Element Name,model为”EMPTY”, “ANY”,
//或者所有其他复杂Element定义的值
public abstract void elementDecl (String name, String model)
throws SAXException;
//定义一个属性标签时的回调。
public abstract void attributeDecl (String eName, String aName,
String type, String mode, String value)
throws SAXException;
//定义一个内部实体时的回调
public abstract void internalEntityDecl (String name, String value)
throws SAXException;
//定义一个外部实体时的回调
public abstract void externalEntityDecl (String name, String publicId,
String systemId)
throws SAXException;
}
一个具体SAX解析实例
写了那么多,花了我好几天的时间,不过还是收获不少,至少对SAX解析XML的方式有了一个比较深入的了解了。最后使用一个简单的例子结束这篇文章吧。
首先简单的定义XML文件:
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="12">
<name>thinking in java</name>
<price>85.5</price>
</book>
<book id="15">
<name>Spring in Action</name>
<price>39.0</price>
</book>
</books>
对应Book类:
class Book {
private String id;
private String name;
private double price;
}
一个简单的解析器:
public class BookXmlParser extends DefaultHandler {
private Locator locator;
private List<Book> books;
private Book currentBook;
private String preTag;
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
@Override
public void startDocument() throws SAXException {
books = new ArrayList<Book>();
currentBook = null;
preTag = null;
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
if("book".equals(qName)) {
currentBook = new Book();
currentBook.setId(attributes.getValue("id"));
}
preTag = qName;
}
public void endElement(String uri, String localName, String qName)
throws SAXException {
if("book".equals(qName)) {
books.add(currentBook);
currentBook = null;
}
preTag = null;
}
public void characters(char ch[], int start, int length)
throws SAXException {
if(preTag != null && currentBook != null) {
String value = new String(ch, start, length);
if("name".equals(preTag)) {
currentBook.setName(value);
} else if("price".equals(preTag)) {
currentBook.setPrice(Double.parseDouble(value));
}
}
}
public void warning(SAXParseException e) throws SAXException {
System.out.println("Warning occurred: \n");
System.out.println("Location Info: " + locatorInfo());
e.printStackTrace(System.out);
}
public void error(SAXParseException e) throws SAXException {
System.out.println("Error occurred: \n");
System.out.println("Location Info: " + locatorInfo());
e.printStackTrace(System.out);
}
public void fatalError(SAXParseException e) throws SAXException {
System.out.println("Fatal Error occurred: \n");
System.out.println("Location Info: " + locatorInfo());
e.printStackTrace(System.out);
throw e;
}
private String locatorInfo() {
return "resource: " + locator.getSystemId() + ", Locator Info: [" +
locator.getLineNumber() + ", " + locator.getColumnNumber() + "]";
}
public static void main(String[] args) throws Exception {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
BookXmlParser handler = new BookXmlParser();
xmlReader.setContentHandler(handler);
xmlReader.setEntityResolver(handler);
xmlReader.setErrorHandler(handler);
xmlReader.setDTDHandler(handler);
xmlReader.parse("resources/xmlfiles/book.xml");
System.out.println("Book List:");
System.out.println(handler.books);
}
}