2010年2月10日- 作为读者评论的后续活动,作者对Validation下的最后一段进行了更新,只有模式是线程安全的,而验证程序和模式工厂则不是。
验证是一个强大的工具。 它使您能够快速检查输入内容是否大致符合您的期望,并快速拒绝任何与流程无法处理的文档。 如果数据有问题,最好早发现。
在可扩展标记语言(XML)的上下文中,验证通常涉及使用几种架构语言(例如,万维网联合会(W3C)XML架构语言(XSD),RELAX NG,文档类型)中的文档内容编写详细的规范。定义(DTD)和Schematron。 有时验证是在解析时执行的,有时是在解析之后立即执行的。 但是,通常是在对输入进行任何进一步处理之前完成。 (此描述用粗笔描画-有例外。)
直到最近,程序要求进行验证的确切应用程序编程接口(API)随架构语言和解析器的不同而变化。 通常在XML的简单API(SAX),文档对象模型(DOM)和XML处理的Java™API(JAXP)中将DTD和XSD作为配置选项进行访问。 RELAX NG需要自定义库和API。 Schematron可能会使用XML(TrAX)的Transformations API; 甚至其他模式语言也要求程序员学习更多的API,即使它们执行的基本操作相同。
Java 5引入了javax.xml.validation
包,以向验证服务提供独立于模式语言的接口。 当单独安装JAXP 1.3时,此包在Java 1.3和更高版本中也可用。 在其他产品中,该库的实现包含在Xerces 2.8中。
验证方式
javax.xml.validation
API使用三个类来验证文档: SchemaFactory
, Schema
和Validator
。 它还广泛使用了TrAX的javax.xml.transform.Source
接口来表示XML文档。 简而言之, SchemaFactory
会从中读取创建Schema
对象的架构文档(通常是XML文件)。 Schema
对象创建一个Validator
对象。 最后, Validator
对象验证表示为Source
的XML文档。
清单1显示了一个简单的程序,用于根据DocBook XSD模式验证在命令行上输入的URL。
清单1.验证可扩展超文本标记语言(XHTML)文档
import java.io.*;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;
import org.xml.sax.SAXException;
public class DocbookXSDCheck {
public static void main(String[] args) throws SAXException, IOException {
// 1. Lookup a factory for the W3C XML Schema language
SchemaFactory factory =
SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
// 2. Compile the schema.
// Here the schema is loaded from a java.io.File, but you could use
// a java.net.URL or a javax.xml.transform.Source instead.
File schemaLocation = new File("/opt/xml/docbook/xsd/docbook.xsd");
Schema schema = factory.newSchema(schemaLocation);
// 3. Get a validator from the schema.
Validator validator = schema.newValidator();
// 4. Parse the document you want to check.
Source source = new StreamSource(args[0]);
// 5. Check the document
try {
validator.validate(source);
System.out.println(args[0] + " is valid.");
}
catch (SAXException ex) {
System.out.println(args[0] + " is not valid because ");
System.out.println(ex.getMessage());
}
}
}
这是使用Java 2 Software Development Kit(JDK)5.0附带的Xerces版本检查无效文档时的一些典型输出:
file:///Users/elharo/CS905/Course_Notes.xml is not valid because cvc-complex-type.2.3: Element 'legalnotice' cannot have character [children], because the type's content type is element-only.
您可以轻松更改要验证的架构,要验证的文档,甚至架构语言。 但是,在所有情况下,验证都遵循以下五个步骤:
- 加载用于模式编写语言的模式工厂。
- 从源代码编译模式。
- 从编译的架构中创建一个验证器。
- 为要验证的文档创建一个
Source
对象。StreamSource
通常是最简单的。 - 验证输入源。 如果文档无效,则
validate()
方法将引发SAXException
。 否则,它将安静地返回。
您可以连续多次重复使用相同的验证器和相同的架构。 但是,只有架构是线程安全的。 验证器和模式工厂不是。 如果同时在多个线程中进行验证,请确保每个线程都有自己的Validator
和SchemaFactory
对象。
根据文档指定的架构进行验证
一些文档通常使用xsi:noNamespaceSchemaLocation
和/或xsi:schemaLocation
属性来指定希望对其进行验证的架构,如下所示:
<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.example.com/document.xsd">
...
如果在不指定URL,文件或源的情况下创建模式,则Java语言会创建一种语言,该语言会在正在验证的文档中查找以查找应使用的模式。 例如:
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = factory.newSchema();
但是,通常这不是您想要的。 通常,文档使用者应选择架构,而不是文档生产者。 此外,这种方法仅适用于XSD。 所有其他架构语言都需要一个明确指定的架构位置。
抽象工厂
SchemaFactory
是一个抽象工厂。 抽象工厂设计模式使这一API能够支持许多不同的模式语言和对象模型。 单个实现通常仅支持众多语言和模型的一部分。 但是,一旦学习了用于根据RELAX NG模式验证DOM文档的API(例如),就可以使用相同的API来针对W3C模式验证JDOM文档。
例如, 清单2显示了一个根据DocBook的RELAX NG模式验证DocBook文档的程序。 它与清单1几乎相同。 唯一更改的是架构的位置和标识架构语言的URL。
清单2.使用RELAX NG验证DocBook文档
import java.io.*;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;
import org.xml.sax.SAXException;
public class DocbookRELAXNGCheck {
public static void main(String[] args) throws SAXException, IOException {
// 1. Specify you want a factory for RELAX NG
SchemaFactory factory
= SchemaFactory.newInstance("http://relaxng.org/ns/structure/1.0");
// 2. Load the specific schema you want.
// Here I load it from a java.io.File, but we could also use a
// java.net.URL or a javax.xml.transform.Source
File schemaLocation = new File("/opt/xml/docbook/rng/docbook.rng");
// 3. Compile the schema.
Schema schema = factory.newSchema(schemaLocation);
// 4. Get a validator from the schema.
Validator validator = schema.newValidator();
// 5. Parse the document you want to check.
String input
= "file:///Users/elharo/Projects/workspace/CS905/build/Java_Course_Notes.xml";
// 6. Check the document
try {
validator.validate(source);
System.out.println(input + " is valid.");
}
catch (SAXException ex) {
System.out.println(input + " is not valid because ");
System.out.println(ex.getMessage());
}
}
}
如果使用现有的Sun JDK运行此程序,而没有额外的库,则可能会看到以下内容:
Exception in thread "main" java.lang.IllegalArgumentException:
http://relaxng.org/ns/structure/1.0
at javax.xml.validation.SchemaFactory.newInstance(SchemaFactory.java:186)
at DocbookRELAXNGCheck.main(DocbookRELAXNGCheck.java:14)
这是因为,开箱即用,JDK不包含RELAX NG验证器。 如果无法识别模式语言,则SchemaFactory.newInstance()
会抛出IllegalArgumentException
。 但是,如果您安装诸如jing和JAXP 1.3适配器之类的RELAX NG库,则它应产生与W3C模式相同的答案。
识别模式语言
javax.xml.constants
类定义几个常量以标识模式语言:
-
XMLConstants.W3C_XML_SCHEMA_NS_URI
:http://www.w3.org/2001/XMLSchema
:http://www.w3.org/2001/XMLSchema
-
XMLConstants.RELAXNG_NS_URI
:http://relaxng.org/ns/structure/1.0
XMLConstants.RELAXNG_NS_URI
-
XMLConstants.XML_DTD_NS_URI
:http://www.w3.org/TR/REC-xml
:http://www.w3.org/TR/REC-xml
这不是封闭列表。 实现是免费的,可以将其他URL添加到此列表中以标识其他架构语言。 通常,URL是模式语言的名称空间统一资源标识符(URI)。 例如,URL http://www.ascc.net/xml/schematron
标识Schematron模式。
Sun的JDK 5仅支持XSD模式。 尽管支持DTD验证,但是无法通过javax.xml.validation
API进行访问。 对于DTD,必须使用常规的SAX XMLReader
类。 但是,您可以安装其他库,以添加对这些和其他架构语言的支持。
模式工厂如何定位
Java编程语言不限于单个架构工厂。 当您将标识特定模式语言的URI传递给SchemaFactory.newInstance()
,它将按此顺序搜索以下位置以找到匹配的工厂:
- 由
"javax.xml.validation.SchemaFactory: schemaURL "
系统属性命名的类 - 在
$java.home/lib/jaxp.properties
文件中找到的由"javax.xml.validation.SchemaFactory: schemaURL "
属性命名的类 - 在任何可用的Java归档(JAR)文件的META-INF / services目录中找到
javax.xml.validation.SchemaFactory
服务提供者 - JDK 5中的平台默认
SchemaFactory
,com.sun.org.apache.xerces.internal.jaxp.validation.xs.SchemaFactoryImpl
为了增加对自己的自定义模式语言和相应的验证器的支持,您要做的就是编写SchemaFactory
, Schema
和Validator
子类,它们知道如何处理模式语言。 然后,在这四个位置之一中安装JAR。 这对于添加约束是很有用的,这些约束使用Java之类的图灵完备语言比声明性语言(如W3C XML Schema语言)更容易检查。 您可以定义一种迷你模式语言,编写一个快速实现,然后将其插入验证层。
错误处理程序
模式的默认响应是,如果有问题,则抛出SAXException
如果没有问题,则不执行任何操作。 但是,可以提供SAX ErrorHandler
来接收有关文档问题的更多详细信息。 例如,假设您要记录所有验证错误,但是当您遇到一个验证错误时,您不想停止处理。 您可以安装清单3中所示的错误处理程序。
清单3.仅记录非致命有效性错误的错误处理程序
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
public class ForgivingErrorHandler implements ErrorHandler {
public void warning(SAXParseException ex) {
System.err.println(ex.getMessage());
}
public void error(SAXParseException ex) {
System.err.println(ex.getMessage());
}
public void fatalError(SAXParseException ex) throws SAXException {
throw ex;
}
}
要安装此错误处理程序,请创建它的一个实例,然后将该实例传递给Validator
的setErrorHandler()
方法:
ErrorHandler lenient = new ForgivingErrorHandler();
validator.setErrorHandler(lenient);
模式扩充
有些模式所做的不只是验证。 除了提供有关文档是否有效的问题的真假答案外,他们还使用其他信息来扩充文档。 例如,它们可以提供默认属性值。 他们还可以将int或gYear之类的类型分配给元素或属性。 验证器可以创建这种类型增强的文档,并将其写入到javax.xml.transform.Result
对象中。 您需要做的就是传递一个Result
作为第二个参数来验证。 例如, 清单4既验证了输入文档,又根据输入与模式的组合创建了增强的DOM文档。
清单4.使用模式扩充文档
import java.io.*;
import javax.xml.transform.dom.*;
import javax.xml.validation.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
public class DocbookXSDAugmenter {
public static void main(String[] args)
throws SAXException, IOException, ParserConfigurationException {
SchemaFactory factory
= SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
File schemaLocation = new File("/opt/xml/docbook/xsd/docbook.xsd");
Schema schema = factory.newSchema(schemaLocation);
Validator validator = schema.newValidator();
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware(true); // never forget this
DocumentBuilder builder = domFactory.newDocumentBuilder();
Document doc = builder.parse(new File(args[0]));
DOMSource source = new DOMSource(doc);
DOMResult result = new DOMResult();
try {
validator.validate(source, result);
Document augmented = (Document) result.getNode();
// do whatever you need to do with the augmented document...
}
catch (SAXException ex) {
System.out.println(args[0] + " is not valid because ");
System.out.println(ex.getMessage());
}
}
}
此过程无法将任意源转换为任意结果。 它对于流源和结果根本不起作用。 可以将SAX源扩展为SAX结果,将DOM源扩展为DOM结果。 但是SAX来源不能增加到DOM结果,反之亦然。 如果需要这样做,请首先将匹配结果扩充为SAX(对于SAX来说是SAX,对于DOM是DOM),然后使用TrAX的标识转换来更改模型。
不过,不建议使用此技术。 将文档所需的所有信息放在实例中比在实例和模式之间分割文档要可靠得多。 您可能会验证,但并非所有人都会。
类型信息
W3C XML Schema Language很大程度上基于类型的概念。 元素和属性声明为int,double,date,duration,person,PhoneNumber或其他任何您可以想象的类型。 Java验证API包括一种报告此类类型的方法,尽管它出人意料地独立于该软件包的其余部分。
类型由org.w3c.dom.TypeInfo
对象标识。 清单5中概述了这个简单的界面,它告诉您类型的本地名称和名称空间URI。 您还可以判断一个类型是否以及如何从另一个类型派生。 除此之外,了解类型取决于您的程序。 Java语言不会告诉您这是什么意思,也不会将数据转换为Java类型(例如double
或java.util.Date
。
清单5. DOM TypeInfo接口
package org.w3c.dom;
public interface TypeInfo {
public static final int DERIVATION_RESTRICTION;
public static final int DERIVATION_EXTENSION;
public static final int DERIVATION_UNION;
public String getTypeName();
public String getTypeNamespace()
public boolean isDerivedFrom(String namespace, String name, int derivationMethod);
}
要获取TypeInfo
对象,请向Schema
对象请求ValidatorHandler
而不是Validator
。 ValidatorHandler
实现SAX的ContentHandler
接口。 然后,您将此处理程序安装在SAX解析器中。
您还可以在ValidatorHandler
(而不是解析器中)安装自己的ContentHandler
。 ValidatorHandler
会将增强事件转发到您的ContentHandler
。
ValidatorHandler
使TypeInfoProvider
可用, ContentHandler
可以随时调用该TypeInfoProvider
来查找当前元素的类型或其属性之一。 它还可以告诉您属性是否为ID,以及该属性是在文档中显式指定还是在架构中默认设置。 清单6总结了该类。
清单6. TypeInfoProvider类
package javax.xml.validation;
public abstract class TypeInfoProvider {
public abstract TypeInfo getElementTypeInfo();
public abstract TypeInfo getAttributeTypeInfo(int index);
public abstract boolean isIdAttribute(int index);
public abstract boolean isSpecified(int index);
}
最后,使用SAX XMLReader
解析文档。 清单7显示了一个简单的程序,该程序使用所有这些类和接口来打印出文档中所有元素类型的名称。
清单7.列出元素类型
import java.io.*;
import javax.xml.validation.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
public class TypeLister extends DefaultHandler {
private TypeInfoProvider provider;
public TypeLister(TypeInfoProvider provider) {
this.provider = provider;
}
public static void main(String[] args) throws SAXException, IOException {
SchemaFactory factory
= SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
File schemaLocation = new File("/opt/xml/docbook/xsd/docbook.xsd");
Schema schema = factory.newSchema(schemaLocation);
ValidatorHandler vHandler = schema.newValidatorHandler();
TypeInfoProvider provider = vHandler.getTypeInfoProvider();
ContentHandler cHandler = new TypeLister(provider);
vHandler.setContentHandler(cHandler);
XMLReader parser = XMLReaderFactory.createXMLReader();
parser.setContentHandler(vHandler);
parser.parse(args[0]);
}
public void startElement(String namespace, String localName,
String qualifiedName, Attributes atts) throws SAXException {
String type = provider.getElementTypeInfo().getTypeName();
System.out.println(qualifiedName + ": " + type);
}
}
这是在典型的DocBook文档上运行此代码的输出的开始:
book: #AnonType_book
title: #AnonType_title
subtitle: #AnonType_subtitle
info: #AnonType_info
copyright: #AnonType_copyright
year: #AnonType_year
holder: #AnonType_holder
author: #AnonType_author
personname: #AnonType_personname
firstname: #AnonType_firstname
othername: #AnonType_othername
surname: #AnonType_surname
personblurb: #AnonType_personblurb
para: #AnonType_para
link: #AnonType_link
如您所见,DocBook模式为大多数元素分配匿名复杂类型。 显然,这从一个模式到下一个模式将有所不同。
结论
如果每个人只讲一种语言,世界将是一个贫穷的地方。 如果程序员只有一种编程语言可供选择,他们将不满意。 不同的语言更好地适合不同的任务,有些任务需要不止一种语言。 XML模式没有什么不同。 您可以从大量有用的模式语言中进行选择。 在具有javax.xml.validation
Java 5中,您具有可以处理所有这些内容的API。
翻译自: https://www.ibm.com/developerworks/java/library/x-javaxmlvalidapi/index.html