级别: 中级 Benoit Marchal (bmarchal@pineapplesoft.com), 顾问, Pineapplesoft
2003 年 8 月 01 日 这篇技巧文章向您演示了如何使用 XML 模式和 JAXP 1.2 实现健壮的文档验证。文中包含了 SAX 和 DOM 解析器的示例。
对模式的大多数讨论都集中于最可能出现的那些词汇表以及如何有效组织模式上(Russian Doll(意为“俄罗斯娃娃”)、Venetian blind(意为“软百叶窗”)Salami Slice(意为“意大利香肠片”)等等)。同样,有关哪一种模式语言最适合的争论仍在进行中 — 是 DTD、W3C 的 XML Schema 还是 OASIS 的 Relax NG?
这些是重要的考虑事项。然而,当您设计 XML 应用程序时,搞清楚用模式做什么更重要。这篇技巧文章讨论了用于 XML 处理的 Java API(Java API for XML Processing, JAXP)1.2 中的新功能,在您针对模式进行文档验证时,这些功能为您带来了更多的灵活性。
验证文档
通常,作为错误处理过程的一部分,应用程序会针对一组已知的模式对 XML 文档进行验证。模式描述了词汇表:元素名称、属性及其数据类型(如 integer、string 和 date)。如果某个文档针对某个模式进行验证,那么它就要符合应用程序所识别的词汇表。验证非常有用 — 毕竟,如果应用程序不能识别那些元素,处理文档又有什么意义呢?
然而要想使验证发挥作用,就必须让应用程序针对已知模式进行验证,而直到 JAXP 1.2 出现为止,这还是说起来容易做起来难。例如,如何以可移植的方式将文档与其模式关联起来呢?在大多数情况下,可以通过
xsi:schemaLocation 属性做到这一点。该属性带有几对名称空间 URI 和相关联的模式文件(没有名称空间的文档则采用
xsi:noNamespaceSchemaLocation 属性)。在清单 1 中,
schemaLocation 属性将
http://ananas.org/2003/tips/validate 名称空间与文件
simple.xsd 关联起来。
清单 1. 带有 xsi:schemaLocation 属性的 XML 文档
<?xml version="1.0"?>
<simple:Root
xmlns:simple="http://ananas.org/2003/tips/validate"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ananas.org/2003/tips/validate simple.xsd">
Document content comes here.
</simple:Root>
|
虽然该属性是管理 XML 模式的一种简单解决方案,但它却有一个大缺陷:它假定您的应用程序可以控制
xsi:schemaLocation 属性。而实际上,您的应用程序有可能能够控制该属性,也有可能无法控制该属性。请考虑下列情形:
- 一个 XML 编辑器,如 Corel XMetaL 或 XMLmind XML Editor,它让您更改
xsi:schemaLocation 属性,使得它指向适当的文件。
- 诸如 XM 或 Cocoon 之类的 Web 发布框架可能会期望作者正确地设置
xsi:schemaLocation ,而当多人参与该站点的开发时,情况可能就不如所愿。
- 处理传入的 XML 文档的电子商务服务器需要验证这些文档,但可能无法信任另一方已经正确地设置了模式。
正如上面这些方案所说明的那样,
xsi:schemaLocation 属性在小型应用程序中工作得很好,而在分布程度越高的环境对其进行管理则越困难。此外还有一些问题,例如,模式以相同的名称存储在不同计算机上的可能性很小。
如果存在文档不正确的风险,应用程序就肯定要验证文档。如果验证取决于文档内容,如
xsi:schemaLocation ,那么验证不可能是健壮的。凭什么说该属性要比文档的其它部分更正确呢?显然需要另一个解决方案,一个让应用程序拥有更多控制权的解决方案。
JAXP 1.2 中的模式支持
 |
URI 和特性
JAXP 使用 URI 作为特性和属性的标识,这与将 URI 用作名称空间标识是一致的。遗憾的是,这样使用 URI 可能会略微引起一些混淆。用户已经习惯性地认为以
http:// 打头的任何内容都是网站。这里可不是这样:那些 URI 是标识,如果您试图在浏览器中访问它们,那么您极有可能收到“404 - File not found”这条错误消息。
|
|
模式规范正确地认识到了由
xsi:schemaLocation 所导致的健壮性不够的问题。根据该规范,
xsi:schemaLocation 仅仅是对解析器的一个提示,解析器可以使用其它方法来确定要应用何种模式。遗憾的是,该规范并没有指明这些其它方法是什么。JAXP 1.2 是 JAXP 的一个维护发行版,它在 Java 平台上提供了一种标准机制,从而填补了这一领域的空白。
本质上,JAXP 1.2 定义了两种新特性(用于 SAX 解析器)和两种新属性(用于 DOM 解析器),它们控制着模式验证。第一个特性(
http://java.sun.com/xml/jaxp/properties/schemaLanguage )指定要使用的模式语言。该特性目前暂时只能接受
http://www.w3.org/2001/XMLSchema 这个值(这是 W3C 对 XML 模式的建议)。将来的发行版可能会支持表示 Relax NG 或其它模式语言的其它值。
第二个特性(
http://java.sun.com/xml/jaxp/properties/schemaSource )设置模式的位置。它是最有趣的一个特性。它接受许多值,如:
- 带有模式 URI 的字符串。
- 带有模式内容的
InputStream 对象。
- 指向模式的
InputSource 对象。
- 指向模式文件的
File 对象。
- 具有上面某个已定义类型的数组。如果应用程序接受可以遵循其它模式的文档,那么该数组是非常有用的。
SAX 示例
清单 2演示了如何使用 JAXP 1.2 中的新特性,通过 SAX 解析器来验证文档。要使用 SAX 解析器来验证文档,请执行以下操作:
- 创建一个
SAXParserFactory 对象。
- 将 namespace-aware 和 validating 特性设置为 true。
- 获取
SAXParser 对象。
- 设置模式语言和模式源的特性(这是 JAXP 1.2 和模式中新出现的)。
- 解析文档。解析器必须有权访问
ErrorHandler 对象。
清单 2. ValidateSAX.java 演示了 JAXP 1.2
package org.ananas.tips;
import java.io.*;
import org.xml.sax.*;
import javax.xml.parsers.*;
public class ValidateSAX
{
public static String SCHEMA_LANGUAGE =
"http://java.sun.com/xml/jaxp/properties/schemaLanguage",
XML_SCHEMA =
"http://www.w3.org/2001/XMLSchema",
SCHEMA_SOURCE =
"http://java.sun.com/xml/jaxp/properties/schemaSource";
public final static void main(String[] args)
throws IOException, SAXException, ParserConfigurationException
{
if(args.length < 2)
{
System.err.println("usage is:");
System.err.println(" java -jar tips.jar -validatesax "
+ "input.xml schema.xsd");
return;
}
File input = new File(args[0]),
schema = new File(args[1]);
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(true);
SAXParser parser = factory.newSAXParser();
try
{
parser.setProperty(SCHEMA_LANGUAGE,XML_SCHEMA);
parser.setProperty(SCHEMA_SOURCE,schema);
}
catch(SAXNotRecognizedException x)
{
System.err.println("Your SAX parser is not JAXP 1.2 compliant.");
}
parser.parse(input,new ErrorPrinter());
}
}
|
 |
ErrorHandler 和验证
validating 特性告诉解析器向其
ErrorHandler 对象报告验证错误。实际上,这意味着如果您不注册
ErrorHandler ,您就不会看到错误消息。有些程序员期望解析器在无法验证文档时抛出一个异常,但 SAX 解析器并不这么做。
|
|
要测试
清单 2,需要一个符合 JAXP 1.2 的解析器。查看您最喜欢的解析器的文档,或者下载最新版本的 Apache Xerces(我使用 V2.4.0 来准备这篇技巧文章)。如果您的解析器不符合 JAXP 1.2,那么当您试图设置该特性时,它会抛出一个
SAXNotRecognizedException 异常。这提示您应该升级到最新版本的 Xerces。
清单 2 仅仅注册了一个
DefaultHandler 对象,该对象只是在控制台上打印验证错误,如清单 3 所示。您的应用程序可以注册更有趣的处理程序,如一个处理文档内容的处理程序。
package org.ananas.tips;
import java.text.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
public class ErrorPrinter
extends DefaultHandler
{
private MessageFormat message =
new MessageFormat("({0}: {1}, {2}): {3}");
private void print(SAXParseException x)
{
String msg = message.format(new Object[]
{
x.getSystemId(),
new Integer(x.getLineNumber()),
new Integer(x.getColumnNumber()),
x.getMessage()
});
System.out.println(msg);
}
public void warning(SAXParseException x)
{
print(x);
}
public void error(SAXParseException x)
{
print(x);
}
public void fatalError(SAXParseException x)
throws SAXParseException
{
print(x);
throw x;
}
}
|
DOM 会怎样呢?
JAXP 1.2 也定义了用于 DOM 解析器的模式支持,如
清单 4中所示。这个过程非常类似于 SAX 解析器的过程,唯一的差异在于您是在工厂(factory)对象上设置属性而不是在解析器对象上设置特性。详细的过程是:
- 创建一个
DOMBuilderFactory 对象。
- 将 namespace-aware 和 validating 特性设置为
true 。
- 设置模式语言和模式源的属性。如果您的解析器不符合 JAXP 1.2,那么它将抛出一个
IllegalArgumentException 异常。
- 获取
DocumentBuilder 对象(解析器)。
- 向解析器注册
ErrorHandler 对象。
- 解析文档。
这个示例仅仅演示了验证。您的应用程序可以用解析树做更多有趣的事情。
清单 4. ValidateDOM.java
package org.ananas.tips;
import java.io.*;
import org.xml.sax.*;
import javax.xml.parsers.*;
public class ValidateDOM
{
public static String SCHEMA_LANGUAGE =
"http://java.sun.com/xml/jaxp/properties/schemaLanguage",
XML_SCHEMA =
"http://www.w3.org/2001/XMLSchema",
SCHEMA_SOURCE =
"http://java.sun.com/xml/jaxp/properties/schemaSource";
public final static void main(String[] args)
throws IOException, SAXException, ParserConfigurationException
{
if(args.length < 2)
{
System.err.println("usage is:");
System.err.println(" java -jar tips.jar -validatedom "
+ "input.xml schema.xsd");
return;
}
File input = new File(args[0]),
schema = new File(args[1]);
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
factory.setValidating(true);
try
{
factory.setAttribute(SCHEMA_LANGUAGE,XML_SCHEMA);
factory.setAttribute(SCHEMA_SOURCE,schema);
}
catch(IllegalArgumentException x)
{
System.err.println("Your DOM parser is not JAXP 1.2 compliant.");
}
DocumentBuilder parser = factory.newDocumentBuilder();
parser.setErrorHandler(new ErrorPrinter());
parser.parse(input);
}
}
|

 |

|
开发更健壮的 XML 应用程序
在使用 XML 模式实现健壮的验证时,请记住:(几乎是肯定地)当您的应用程序验证文档时,应用程序不应该取决于文档本身的正确性。更具体地说,它不应当靠拥有正确的
xsi:schemaLocation 属性的文档。
参考资料
关于作者
对本文的评价
|