10.5 使用XML Schema验证XML文档

早期的JAXP不支持使用XML Schema来验证XML文档,而从JAXP 1.3(作为JDK 1.5的部分)起,它开始支持使用XML Schema来验证XML文档的有效性,这标志着JAXP对XML各种规范的支持更趋完备。javax.xml.validation下的类可提供使用XML Schema验证XML文档的有效性支持。

10.5.1 SchemaFactory和验证

不管是使用DOM方式还是SAX方式来解析XML文档,都可以使用XML Schema来验证XML文档的有效性。

alt 如果使用DOM方式来解析XML文档,可以通过DocumentBuilderFactory对象的setSchema(Schema schema)方法来启用XML Schema验证XML文档的有效性。

alt 如果使用SAX方式来解析XML文档,则可以通过SAXParserFactory对象的setSchema(Schema schema)方法来启用XML Schema验证XML文档的有效性。

不管使用哪种方式来启用XML Schema验证XML文档的有效性,都需要为setSchema()方法传入一个Schema对象,其中的Schema对象就代表一份XML Schema。

JAXP通常采用工厂方法来获取Schema对象。也就是说,为了获取Schema对象,必须先获取SchemaFactory对象。程序通常调用SchemaFactory的newInstance(String schemaLanguage)方法来获取SchemaFactory对象。


alt学生提问:上面的SchemaFactory的newInstance()方法中怎么还有一个schemaLanguage参数,它代表什么呢?


答:在前面介绍XML Schema语义约束时,我们所介绍的Schema都是采用XML语言定义的,这也是目前主流的Schema定义语言。但实际上Schema还允许使用其他语言来定义,例如使用RELAX NG,JAXP提供了对多种Schema定义语言的支持,因此在创建SchemaFactory对象时需要指定Schema的定义语言。目前Schema主要支持XML Schema 1.0和RELAX NG 1.0两种Schema定义语言。

alt


为了创建合适的SchemaFactory对象,程序必须指定Schema的定义语言,JAXP 1.4支持表10.3所示的两种Schema的定义语言:

表10.3 JAXP 1.4所支持的Schema定义语言

alt

为newInstance()方法指定了正确的Schema定义语言后,该方法将会创建一个SchemaFactory子类的实例,但具体返回哪个子类的实例则取决于底层XML解析器的实现。newInstance()方法将按如下规则决定使用SchemaFactory的哪个子类:

(1)按系统属性

newInstance()方法将会查看是否包含了javax.xml.validation.SchemaFactory:schemaLanguage(其中的schemaLanguage代表传给newInstance()方法的schemaLanguage参数值)系统属性。newInstance()方法将使用该系统属性值来选择SchemaFactory的子类。

(2)按jaxp.properties属性文件

如果JAXP在%JAVA_HOME%\jre\lib路径下找到一个名为jaxp.properties的文件,且该文件是一个标准的属性文件,则newInstance()方法将按该文件设置的属性来选择SchemaFactory的子类。

(3)按JAR包里META-INF\services下的文件

newInstance()方法会自动搜索类加载路径下的所有JAR包,如果在任何JAR包的META-INF\services下找到javax.xml.validation.SchemaFactory文件,则该方法将会根据该文件的内容(文件内容是SchemaFactory的子类的类名)来选择SchemaFactory的子类。

用WinRAR打开xercesImpl.jar文件,进入META-INF\services路径下,即可看到图10.16所示窗口。

alt

图10.16 xercesImpl.jar中的SchemaFactory的子类设置

打开图10.16所示文件列表中的javax.xml.validation.SchemaFactory文件,可看到如下文件内容:org.apache.xerces.jaxp.validation.XMLSchemaFactory

这表明SAXParserFactory.newInstance()方法将返回org.apache.xerces.jaxp.validation包下XMLSchemaFactory类的实例。

(4)使用默认解析器

得到合适的XMLSchemaFactory对象之后,程序可调用该对象的下列方法之一来创建Schema对象:

alt Schema newSchema(File schema):根据指定文件代表的Schema文档创建Schema对象。

alt Schema newSchema(Source schema):根据指定Source对象代表的Schema文档创建Schema对象。

alt Schema newSchema(Source[]schemas):根据多个Source对象代表的多个Schema文档创建Schema对象。

alt Schema newSchema(URL schema):根据指定URL代表的Schema文档创建Schema对象。

需要指出的是,如果传给XMLSchemaFactory对象newSchema()方法的Schema文档本身无效,该方法将抛出org.xml.sax.SAXParseException异常。

根据指定Schema文档创建了Schema对象之后,程序就可将该Schema传给DOM解析器工厂或SAX解析器工厂,接着由该DOM解析器工厂或SAX解析器工厂创建的解析器将会启用Schema来验证XML文档的有效性。

下列程序示范了如何利用Schema来验证XML文档有效性:

程序清单:codes\10\10.5\src\lee\SchemaValidate.java

alt

上面的程序分别示范了基于DOM和SAX解析机制如何使用Schema来验证XML文档的有效性,这两种解析机制下使用Schema验证XML文档有效性并没有太大的差异,都只需将Schema对象传给各自的解析器工厂即可。

上面的程序中①、②两行代码分别启用了DOM解析器工厂和SAX解析器工厂的命名空间支持,这是必需的。因为本程序所处理的XML文档里的元素使用了命名空间支持,各元素名之前都指定了限定短名。

与使用DTD验证XML文档有效性相同的是,为了保证程序能自己处理验证过程中发生的验证错误,程序同样为DOM解析器和SAX解析器指定了ErrorHandler监听器。

如果上面的程序验证的XML文档不是有效的XML文档,则运行上面的程序将看到图10.17所示结果。

alt

图10.17 使用Schema验证XML文档有效性

从上面的代码可以看出,虽然DOM模型解析XML文档时不像SAX解析机制那样完全基于事件机制,但如果想让解析程序获取DOM解析过程和DTD验证过程中所发生的错误,依然需要为DOM解析器注册ErrorHandler监听器,这也是采用事件机制的处理方式。

实际上,如果需要获取DOM解析过程中的错误信息和EntityResolver处理信息,则需要采用事件机制。因此DocumentBuilder提供了如下两个方法来注册事件监听器:

alt void setEntityResolver(EntityResolver er):注册监听处理实体事件的监听器。

alt void setErrorHandler(ErrorHandler eh):注册监听解析错误事件的监听器。

如果只是需要利用Schema验证XML文档的有效性,其实完全无须解析XML文档,只要简单地利用JAXP提供的Validator接口即可,该接口提供了一个validate(Source source)方法用于验证指定XML文档。

为了在程序中获取Validator对象,只需调用Schema对象提供的newValidator()方法来创建一个Validator对象即可。如下程序示范了如何利用Validator来验证XML文档的有效性:

程序清单:codes\10\10.5\src\lee\OnlyValidate.java

alt

在使用Validator验证XML文档的有效性时,同样需要设置一个ErrorHandler来监听解析过程中所发生的错误(如①号代码所示),这个ErrorHandler与监听DOM解析错误和SAX解析错误的ErrorHandler完全一样。

由此可见,使用Validator来验证XML文档也非常简单,只要为Validator注册一个合适的ErrorHandler监听器,然后调用Validator对象的validate()方法验证指定XML文档即可。

10.5.2 获取节点的类型信息

JAXP提供了ValidatorHandler来获取XML各节点(元素、属性)的类型信息,它实现了ContentHandler接口,但并不能独立作为ContentHandler使用。

JAXP对ValidatorHandler和ContentHandler的设计有点奇怪:它使用ValidatorHandler来组合ContentHandler(或者说包装ContentHandler),包装ContentHandler后的ValidatorHandler就可以作为ContentHandler来使用了——它此时已经具备了作为ContentHandler来监听SAX解析事件的能力。

在使用ValidatorHandler作为ContentHandler监听器来监听SAX解析事件时,ValidatorHandler比简单的ContentHandler多一个功能:通过调用ValidatorHandler的getTypeInfoProvider()方法可以返回一个TypeInfoProvider对象。

TypeInfoProvider对象专门用于访问元素和属性的类型信息,它提供了如下几个方法:

alt TypeInfo getAttributeTypeInfo(int index):获取指定属性的类型信息。

alt TypeInfo getElementTypeInfo():获取指定元素的类型信息。

alt boolean isIdAttribute(int index):判断指定属性是否为ID类型。

alt boolean isSpecified(int index):当指定属性具有默认值,且该XML文档中没有为该属性指定属性值时返回true。

上面4个方法中,前面2个方法返回一个TypeInfo对象,该对象专门用于封装元素和属性的类型信息,通过该对象就可以获取关于元素和属性的详细信息:

alt String getTypeName():获取指定类型的类型名。

alt String getTypeNamespace():获取指定类型所属的命名空间。

alt boolean isDerivedFrom(String typeNamespaceArg, String typeNameArg, int derivationMethod):如果指定类型是从typeNamespaceArg命名空间下的typeNameArg类型以derivationMethod方式派生而来,则该方法返回true。

通过上面的介绍不难看出,使用ValidatorHandler获取元素和属性类型信息的难点在于理解ValidatorHandler和ContentHandler的关系:程序需要使用ValidatorHandler来包装ContentHandler,然后将ValidatorHandler当成ContentHandler使用。

得到合适的ValidatorHandler之后,程序就可以通过ValidatorHandler来获取TypeInfoProvider对象,从而获取各元素和属性的类型信息了。下列程序示范了如何获取各元素和属性的类型信息:

程序清单:codes\10\10.5\src\lee\GetTypeInfo.java

alt

上面的程序中的①、②、③号代码体现了使用ValidatorHandler的关键步骤:

(1)创建ValidatorHandler对象。

(2)使用ValidatorHandler包装ContentHandler对象。

(3)使用ValidatorHandler作为SAX的ContentHandler监听器,监听SAX解析事件。

运行上面的程序,可看到图10.18所示结果。

alt

图10.18 使用ValidatorHandler获取XML元素和属性的类型信息