现在的位置: 首页 > 综合 > 正文

SAX 解析 XML 文件详细解剖及案例

2013年08月12日 ⁄ 综合 ⁄ 共 8214字 ⁄ 字号 评论关闭

就目前来说,有三种方式可以解析XML文件:DOMSAXStAXDOM将整个XML文件加载到内存中,并构建出节点树;应用程序可以通过遍历节点树的方式来解析XML文件中的各个节点、属性等信息;这种方式便于对XML节点的添加修改等,而且解析也很方便,然后它比较耗费内存,解析速度也不快。SAX则是基于事件的解析,解析器在一次读取XML文件中根据读取的数据产生相应的事件,由应用程序实现相应的事件处理逻辑,即它是一种“推”的解析方式;这种解析方法速度快、占用内存少,但是它需要应用程序自己处理解析器的状态,实现起来会比较麻烦,而且它只支持对XML文件的读取,不支持写入。不同于SAX的“推”的解析方式,StAX是基于“拉”的解析方式,即应用程序根据自己的需要控制解析器的读取;这种方式继承了SAX解析速度快、占用内存少等优点,同时它好保持了接口简单、编程容易等特点;不过它也应该不支持写XML文件的,木有仔细看过这个框架,先猜测一下~~。貌似DOM底层采用了SAX的实现,因而本文首先介绍基于SAX方式的XML文件解析。

SAX的解析框架相对比较简单,以下是它核心类关系图:

InputSource

InputSourceSAX中对要被解析的XML资源文件的抽象,它封装了以下信息:

private Reader characterStream;

private InputStream byteStream;

private String systemId;

private String publicId;

private String encoding;

应用程序可以显示的设置characterStreambyteStream来指定实际的XML资源文件,或者使用systemIdpublicId的方式来定位XML资源文件。这里systemIdpublicId借用了导入DTD文件是定义的SYSTEMPUBLIC概念。在DTD中,SYSTEMPUBLIC都表示外部资源文件,所不同的是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中的characterStreambyteStreamsystemId都可以表示一个资源文件,因而需要定义它们的查找顺序:即先查看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

一些用户自定义的解析器可以指定自己支持的FeatureXMLReader提供接口查询、设置指定当前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

LexicalHandlerSAX2扩展接口,提供更多的XML资源文件相关的信息,如注释、CDATA等。

DeclHandlerSAX2扩展接口,在DTD定义事件中提供回调方法。

4.       解析方法(parse

解析器实现解析XML资源文件的方法。应用程序可以传入InputSource实例,也可以传入systemId的值,即一个URI字符串或相对路径指定的文件名。解析方法(parse)是线程同步的,对一个XMLReader实例在解析时,另一个线程会等待该线程结束后才开始解析新的文件,因而一般情况下,在多个线程中都会创建各自的XMLReader实例。然而当一个XMLReader实例解析完成后,我们可以重用该XMLReader实例解析新的XML文件,此时之前的FeatureProperty以及Handler的注册等信息都保持不变,我们可以手动调用相应的方法改变之。

 

EntityResolver接口

EntityResolver接口提供应用程序自定义实体解析的扩展点,即应用程序可以注册自己的实体解析类以实现自己特定的逻辑,如在本地、数据库、网络中查找外部实体。比如Spring就实现了自己的BeansDtdResolver,对InputSource小节中定义的spring-beans.dtd,它会先查找BeanDtdResolver类所在的目录(一般为jar包内)中存在的spring-beans.dtd,如果存在该文件,则会加载该文件作为DTD文件,否则,使用默认的EntityResolver(即返回null,则XML引擎自己提供的解析器)。

然而什么是实体(Entity)呢?如果使用DTD作为XML文件的定义模板,那么在引入DTD文件时,即使引入一个外部实体,其PUBLICSYSTEM定义分别对应publicIdsystemId。实体的另一个使用地方是在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>&copyright;</copyright>

</website>

此时,在解析XML文件时,&name;值为cnblog,而&copyright;的值为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的定义模板,我们可以定义schemaLocationXSD文件引入,比如Spring配置文件中的用法:

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xmlns:context="http:

抱歉!评论已关闭.