JAXP全面介绍

来源:互联网 发布:免费qq营销软件 编辑:程序博客网 时间:2024/04/29 04:00

转自IBM的developerworks

Java API for XML Processing (JAXP) 允许使用几种不同的 API 来验证、解析和转换 XML。JAXP 既提供了使用方便性,又提供了开发商中立性。 本系列介绍 JAXP,由两部分组成。本文是第一部分,向您展示如何利用 API 的解析和验证特性。第二部分介绍使用 JAXP 进行 XSL 转换。

Java 技术和 XML 无疑是最近五年来最重要的编程开发工具。因此,用于在 Java 语言中处理 XML 的 API 就发展起来了。两个最流行的 —— 文档对象模型 (DOM) 和 Simple API for XML (SAX) —— 已经产生巨大的影响,JDOM 和数据绑定 API 也随之产生了(参阅 参考资料)。彻底理解其中一个或两个 API 是非常必要的;正确使用全部 API 会让您成为权威。但是,越来越多的 Java 开发人员发现他们不再需要广泛了解 SAX 和 DOM —— 这主要是由于 Sun Microsystems 的 JAXP 工具包。Java API for XML Processing (JAXP) 使得 XML 甚至对于 Java 初级开发人员也变得易于掌握,并大大提高了高级开发人员的能力。也就是说,即使使用 JAXP 的高级开发人员对于他们十分依赖的 API 也有误解。

本文假设您已基本了解 SAX 和 DOM。如果您完全不懂 XML 解析,那么可能需要首先阅读在线参考资料中有关 SAX 和 DOM 的信息,或者浏览我的书(参阅 参考资料)。您不需要精通回调或 DOM Node,但必须至少了解是 SAX 和 DOM 在解析 API。本文还有助于基本了解它们之间的差别。如果您掌握了这些基本知识,本文将对您更有帮助。

JAXP:是 API 还是抽象?

严格说来,JAXP 是 API,但更准确地说是抽象层。它没有提供解析 XML 的新方法,没有添加到 SAX 或 DOM,也没有为 Java 和 XML 处理提供新功能。(如果您还不相信这一点,那么阅读这篇文章算对了。)但是,JAXP 使得使用 DOM 和 SAX 来处理一些困难任务变得更容易。它还允许以开发商中立的方式处理一些在使用 DOM 和 SAX API 时可能遇到的特定于开发商的任务。

逐渐晋级

在 Java 平台的早期版本中,JAXP 是核心平台中单独的下载。在 Java 5.0 中,JAXP 已经是 Java 语言的主要产品。如果已经有最新版本的 JDK(参阅 参考资料),您就已经获得了 JAXP。

没有 SAX、DOM 或另一个 XML 解析 API,则无法解析 XML。我曾经看到过许多关于将 SAX、DOM、JDOM 和 dom4j 与 JAXP 进行比较的请求,但作这样的比较是不可能的,因为前面四个 API 与 JAXP 具有完全不同的用途。SAX、DOM、JDOM 和 dom4j 都解析 XML。JAXP 提供了一种到达这些解析器及其所涉及的数据的方法,但并未提供一种解析 XML 文档的新方法。如果您要正确使用 JAXP,则理解此差别是非常必要的。这还很有可能使您远远领先于您的 XML 开发同行。

如果仍有疑问,请确保您具有 JAXP 发行版(参阅 逐渐晋级)。启动 Web 浏览器并加载 JAXP API 文档。导航至位于 javax.xml.parsers 软件包中的 API 的解析部分。令人奇怪的是,您将只找到六个类。这个 API 到底怎么回事?所有这些类都位于现有解析器的顶部。其中两个类仅用于错误处理。JAXP 比人们想像的要简单得多。那么为何会有混淆呢?

位于顶部

甚至 JDOM 和 dom4j(参阅 参考资料)与 JAXP 一样都位于其他解析 API 的顶部。但这两个 API 都提供了从 SAX 或 DOM 中访问数据的不同模型,它们在内部使用 SAX(带有一些技巧和修改)来到达它们提供给用户的数据。

Sun 的 JAXP 和 Sun 的解析器

许多解析器/API 混淆来自于 Sun 软件包 JAXP 和该 JAXP 默认使用的解析器。在 JAXP 的早期版本中,Sun 包括 JAXP API(带有刚才提到的六个类和一些常用于转换的类) 一个叫做 Crimson 的解析器。Crimson 是 com.sun.xml 软件包的一部分。在 JAXP 的新版本中 —— 包括在 JDK 中 —— Sun 已经重新包装了 Apache Xerces 解析器(参阅 参考资料)。在这两种情况下,虽然解析器是 JAXP 发行版的一部分,但不是 JAXP API 的一部分。

可以认为是 JDOM 附带了 Apache Xerces 解析器。该解析器不是 JDOM 的一部分,但由 JDOM 使用,所以包括它是为了确保 JDOM 可以即装即用。同一原则适用于 JAXP,但并未明确公布:JAXP 附带解析器是为了可以立即使用。但是,许多人将 Sun 的解析器中包括的类作为 JAXP API 本身的一部分。例如,新闻组上的常见问题通常是“我如何使用 JAXP 附带的 XMLDocument 类?它的作用是什么?”答案有些复杂。

软件包名称中是什么?

当我第一次在 Java 1.5 中贸然打开源代码时,我惊奇于我所看到的 —— 或者更应该说是我没有 看到的。没有在正常中的软件包 org.apache.xerces 中找到 Xerces,因为 Sun 将 Xerces 类重新分配给了 com.sun.org.apache.xerces.internal。(我发现这有点不正常,但没有人问我。)在任何情况下,如果您在 JDK 中查找 Xerces,就能找到它。

首先,com.sun.xml.tree.XMLDocument 类不是 JAXP 的一部分。它是 Sun 的 Crimson 解析器的一部分,包装在 JAXP 的早期版本中。所以这个问题从一开始就令人误解。其次,JAXP 的主要用途是在处理解析器时提供开发商独立性。有了 JAXP,您可以用 Sun 的 XML 解析器、Apache 的 Xerces XML 解析器和 Oracle 的 XML 解析器来处理相同的代码。因而使用特定于 Sun 的类会违反使用 JAXP 的要点。是否弄清楚了本主题是如何变得复杂起来的?JAXP 发行版中的 API 和解析器 已经组合在一起,一些开发人员误将解析器中的类和特性作为 API 的一部分,反之亦然。

既然弄清楚了所有的混淆,那么您就可以深入了解一些代码和概念了。





回页首


SAX 入门

SAX 是事件驱动的 XML 处理方法。它由许多回调组成。例如,startElement() 回调在每次 SAX 解析器遇到元素的起始标记时被调用。characters() 回调为字符数据所调用,然后 endElement() 为元素的结束标记所调用。许多回调用于文档处理、错误和其他词汇结构。您明白了。SAX 程序员实现一个 SAX 接口来定义这些回调。SAX 还提供一个叫做 DefaultHandler 的类(在 org.xml.sax.helpers 软件包中)来实现所有这些回调,并提供所有回调方法默认的空实现。(您将看到,这对于下一节 处理 DOM 中讨论 DOM 是重点。)SAX 开发人员只需要继承该类,然后实现需要插入特定逻辑的方法。所以 SAX 中的关键是提供这些各种回调的代码,然后让解析器在适当的时候触发其中的一个。下面是典型的 SAX 例程:

  1. 使用特定开发商的解析器实现来创建 SAXParser 实例。
  2. 注册回调实现(例如,通过使用继承 DefaultHandler 的类)。
  3. 开始解析并在回调实现启动时停止。

JAXP 的 SAX 组件提供了完成所有这些操作的简单方法。没有 JAXP,SAX 解析器实例要么必须从开发商类(比如 org.apache.xerces.parsers.SAXParser)中直接实例化,要么必须使用一个叫做 XMLReaderFactory 的 SAX 帮助类(也在 org.xml.sax.helpers 软件包中)。第一种方法的问题很显然:它不是开发商中立的。第二种方法的问题在于,工厂需要使用解析器类的 String 名称作为参数(又是 Apache 类 org.apache.xerces.parsers.SAXParser)。可以通过传递不同的解析器类作为 String 而更改解析器。使用该方法,如果更改解析器名称,则不需要更改任何导入语句,但仍需要重新编译类。这显然不是最好的解决方案。如果能够不重新编译类而更改解析器就方便多了。

JAXP 提供了更好的备选方法:它允许将解析器作为 Java 系统特性。当然,从 Sun 中下载发行版时,您能得到使用 Sun 的 Xerces 版本的 JAXP 实现。更改解析器(比如更改为 Oracle 的解析器)需要更改类路径设置,从一个解析器实现移动到另一个解析器实现。但 需要重新编译代码。这就是 JAXP 的全部魔力 —— 抽象。

诡异的 SAX 开发人员

稍叉开一下话题。使用巧妙一点的编码,可以使得 SAX 应用程序从系统特性或特性文件中选择要使用的解析器类。但是,JAXP 提供了相同的行为,且无需任何工作,所以大多数人更愿意走 JAXP 路线。

SAX 解析器工厂一览

JAXP SAXParserFactory 类是能够轻易更改解析器实现的关键。必须创建该类的新实例(一会将用到它)。新实例创建之后,工厂提供一种方法用于获得具有 SAX 功能的解析器。实际上,JAXP 实现保护着开发商相关的代码,从而使您的代码完全不受污染。工厂还具有一些其他的有用特性。

除了创建 SAX 解析器实例的基本工作之外,工厂还允许设置配置选项。这些选项影响通过工厂获得的所有解析器实例。JAXP 1.3 中两个常用的选项是,用于设置名称空间意识的 setNamespaceAware(boolean awareness) 和用于打开 DTD 验证的 setValidating(boolean validating)。记住,一旦设置了这些选项,它们将影响在方法调用后从工厂获得的所有实例。

设置了工厂之后,调用 newSAXParser() 会返回 JAXP SAXParser 类立即可用的实例。该类包装底层的 SAX 解析器(SAX 类 org.xml.sax.XMLReader 的实例)。它还防止您使用解析器类的任何特定于开发商的附加项。(是否记得上文中有关 XmlDocument 类的 讨论?)该类允许启动实际的解析行为。清单 1 显示如何创建、配置和使用 SAX 工厂:


清单 1. 使用 SAXParserFactory

import java.io.OutputStreamWriter;import java.io.Writer;// JAXPimport javax.xml.parsers.FactoryConfigurationError;import javax.xml.parsers.ParserConfigurationException;import javax.xml.parsers.SAXParserFactory;import javax.xml.parsers.SAXParser;// SAXimport org.xml.sax.Attributes;import org.xml.sax.SAXException;import org.xml.sax.helpers.DefaultHandler;public class TestSAXParsing {    public static void main(String[] args) {        try {            if (args.length != 1) {                System.err.println ("Usage: java TestSAXParsing [filename]");                System.exit (1);            }            // Get SAX Parser Factory            SAXParserFactory factory = SAXParserFactory.newInstance();            // Turn on validation, and turn off namespaces            factory.setValidating(true);            factory.setNamespaceAware(false);            SAXParser parser = factory.newSAXParser();            parser.parse(new File(args[0]), new MyHandler());        } catch (ParserConfigurationException e) {            System.out.println("The underlying parser does not support " +                               " the requested features.");        } catch (FactoryConfigurationError e) {            System.out.println("Error occurred obtaining SAX Parser Factory.");        } catch (Exception e) {            e.printStackTrace();        }    }}class MyHandler extends DefaultHandler {    // SAX callback implementations from ContentHandler, ErrorHandler, etc.}

清单 1 中,可以看到在使用工厂时出现两个特定于 JAXP 的问题:无法获得或配置 SAX 工厂,及无法配置 SAX 解析器。第一个问题由 FactoryConfigurationError 表示,通常发生在无法获得 JAXP 实现或系统特性中指定的解析器时。第二个问题由 ParserConfigurationException 表示,发生在请求的特性在所使用的解析器中不可用时。两个问题都易于处理,且不应在使用 JAXP 时造成任何困难。事实上,您可能想要编写代码,来尝试设置几个特征并巧妙处理某个特性不可用时的情况。

SAXParser 实例是在获得工厂、关闭名称空间支持并打开验证时获得的;然后解析开始。SAX 解析器的 parse() 方法采用前面提到的 SAX HandlerBase 帮助类的一个实例,自定义处理器类继承自该类。请参阅代码发行版来查看该类的实现的完整 Java 清单(参阅 下载)。还传递 File 以进行解析。但是,SAXParser 类不只包含这一个方法。

使用 SAX 解析器

一旦具有 SAXParser 类的实例后,您可以做的远远不止于给它传递 File 来解析。由于大型应用程序中组件的通信方式,所以假设对象实例的创建者就是它的用户并不总是安全的。一个组件可能创建 SAXParser 实例,而另一个组件(可能由另一个开发人员编码)可能需要使用相同的实例。因此,JAXP 提供了确定解析器的设置的方法。例如,可以使用 isValidating() 来确定解析器是否将执行验证,使用 isNamespaceAware() 来查看解析器是否可以处理 XML 文档中的名称空间。这些方法可以为您提供关于解析器可以做什么的信息,但只带有 SAXParser 实例而非 SAXParserFactory 本身的用户无法更改这些特性。您必须在解析器工厂级别完成这一操作。

还有许多方法来请求文档的解析。并非只能接受 File 和 SAX DefaultHandler 实例,SAXParserparse() 方法还可以接受字符串格式的 SAX InputSource、Java InputStreamURL,它们全部具有 DefaultHandler 实例。所以仍可以解析包装在各种格式中的文档。

最后,可以获得底层 SAX 解析器(org.xml.sax.XMLReader 的实例),并直接通过 SAXParsergetXMLReader() 方法来使用它。一旦获得该底层实例,一般的 SAX 方法都可用。清单 2 显示 JAXP 中的核心类 SAXParser 类在 SAX 解析中的各种用法的例子:


清单 2. 使用 JAXP SAXParser

// Get a SAX Parser instanceSAXParser saxParser = saxFactory.newSAXParser();// Find out if validation is supportedboolean isValidating = saxParser.isValidating();// Find out if namespaces are supportedboolean isNamespaceAware = saxParser.isNamespaceAware();// Parse, in a variety of ways// Use a file and a SAX DefaultHandler instancesaxParser.parse(new File(args[0]), myDefaultHandlerInstance);// Use a SAX InputSource and a SAX DefaultHandler instancesaxParser.parse(mySaxInputSource, myDefaultHandlerInstance);// Use an InputStream and a SAX DefaultHandler instancesaxParser.parse(myInputStream, myDefaultHandlerInstance);// Use a URI and a SAX DefaultHandler instancesaxParser.parse("http://www.newInstance.com/xml/doc.xml",                myDefaultHandlerInstance);// Get the underlying (wrapped) SAX parserorg.xml.sax.XMLReader parser = saxParser.getXMLReader();// Use the underlying parserparser.setContentHandler(myContentHandlerInstance);parser.setErrorHandler(myErrorHandlerInstance);parser.parse(new org.xml.sax.InputSource(args[0]));

到此为止,已经针对 SAX 谈了许多,但还没有显示任何显著的或惊人的内容。JAXP 的附加功能相当小,尤其是涉及到 SAX 的地方。这个最小功能使得代码更易移植,让其他开发人员用任何 SAX 兼容的 XML 解析器来自由或商业地使用它。好了。使用 SAX 与 JAXP 再无其他内容。如果已经了解了 SAX,您已经成功了大约 98%。您只需要学习两个新类和一对 Java 异常,然后就可以开始行动了。如果从未用过 SAX,从现在开始也足够了。





回页首


处理 DOM

如果您认为需要休息一下来对付 DOM 的挑战,那么就休息一下吧。使用 DOM 与 JAXP 和使用 JAXP 与 SAX 几乎完全相同,惟一要做的就是更改类名和返回类型,这就足够了。如果理解 SAX 如何工作和 DOM 是什么,那就根本没有问题。

DOM 和 SAX 的主要差别是 API 本身的结构。SAX 由基于事件的回调集组成,而 DOM 具有内存树结构。在 SAX 中,决不会有需要处理的数据结构(除非开发人员手动创建一个)。因此,SAX 没有提供修改 XML 文档的能力。DOM 提供了此功能。org.w3c.dom.Document 类表示 XML 文档,由表示元素、属性和其他 XML 构造的 DOM 节点组成。所以 JAXP 不需要启动 SAX 回调;它只负责从解析中返回 DOM Document 对象。

DOM 解析器工厂一览

基本了解了 DOM 以及 DOM 和 SAX 之间的差别之后,就不需要了解其他内容了。清单 3 看起来与 清单 1 中的 SAX 代码十分相似。首先,获得 DocumentBuilderFactory(与清单 1 中获得 SAXParserFactory 的方法一样)。然后,配置工厂来处理验证和名称空间(与 SAX 中的方法一样)。其次,从工厂中检索与 SAXParser 类似的 DocumentBuilder 实例(与 SAX 中的方法一样)。 然后进行解析,得到的 DOM Document 对象传递给输出 DOM 树的方法:


清单 3. 使用 DocumentBuilderFactory

import java.io.File;import java.io.IOException;import java.io.OutputStreamWriter;import java.io.Writer;// JAXPimport javax.xml.parsers.FactoryConfigurationError;import javax.xml.parsers.ParserConfigurationException;import javax.xml.parsers.DocumentBuilderFactory;import javax.xml.parsers.DocumentBuilder;// DOMimport org.w3c.dom.Document;import org.w3c.dom.DocumentType;import org.w3c.dom.NamedNodeMap;import org.w3c.dom.Node;import org.w3c.dom.NodeList;public class TestDOMParsing {    public static void main(String[] args) {        try {            if (args.length != 1) {                System.err.println ("Usage: java TestDOMParsing " +                                    "[filename]");                System.exit (1);            }            // Get Document Builder Factory            DocumentBuilderFactory factory =                 DocumentBuilderFactory.newInstance();            // Turn on validation, and turn off namespaces            factory.setValidating(true);            factory.setNamespaceAware(false);            DocumentBuilder builder = factory.newDocumentBuilder();            Document doc = builder.parse(new File(args[0]));            // Print the document from the DOM tree and            //   feed it an initial indentation of nothing            printNode(doc, "");        } catch (ParserConfigurationException e) {            System.out.println("The underlying parser does not " +                               "support the requested features.");        } catch (FactoryConfigurationError e) {            System.out.println("Error occurred obtaining Document " +                               "Builder Factory.");        } catch (Exception e) {            e.printStackTrace();        }    }    private static void printNode(Node node, String indent)  {        // print the DOM tree    }}

该代码可以出现两个问题(与 JAXP 中的 SAX 一样):FactoryConfigurationErrorParserConfigurationException。每个问题的原因都于 SAX 中的一样。一个问题出现在实现类中(导致 FactoryConfigurationError),另一个问题是提供的解析器不支持请求的特性(导致 ParserConfigurationException)。在这方面,DOM 和 SAX 之间的惟一差别是,在 DOM 中用 DocumentBuilderFactory 替代 SAXParserFactory,用 DocumentBuilder 替代 SAXParser。 仅此一点。(可以查看完整的代码清单,其中包括用于输出 DOM 树的方法;请参阅 下载。)

使用 DOM 解析器

一旦拥有 DOM 工厂后,就可以获得 DocumentBuilder 实例。可用于 DocumentBuilder 实例的方法与可用于对应 SAX 实例的方法非常相似。主要差别在于 parse() 方法的变种不接受 SAX DefaultHandler 类的实例。而是返回一个表示已解析的 XML 文档的 DOM Document 实例。其余的惟一差别就是两个方法提供了类似 SAX 的功能:

  • setErrorHandler(),执行 SAX ErrorHandler 实现来处理解析时可能出现的问题。
  • setEntityResolver(),执行 SAX EntityResolver 实现来处理实体解析。

清单 4 显示这些方法的实际例子:


清单 4. 使用 JAXP DocumentBuilder

// Get a DocumentBuilder instanceDocumentBuilder builder = builderFactory.newDocumentBuilder();// Find out if validation is supportedboolean isValidating = builder.isValidating();// Find out if namespaces are supportedboolean isNamespaceAware = builder.isNamespaceAware();// Set a SAX ErrorHandlerbuilder.setErrorHandler(myErrorHandlerImpl);// Set a SAX EntityResolverbuilder.setEntityResolver(myEntityResolverImpl);// Parse, in a variety of ways// Use a fileDocument doc = builder.parse(new File(args[0]));// Use a SAX InputSourceDocument doc = builder.parse(mySaxInputSource);// Use an InputStreamDocument doc = builder.parse(myInputStream, myDefaultHandlerInstance);// Use a URI Document doc = builder.parse("http://www.newInstance.com/xml/doc.xml");

如果您在阅读 DOM 这一节时感到一点乏味,那么您并不孤单;我在编写时也感到有些乏味,因为将已经学过的有关 SAX 的知识应用到 DOM 是如此简单。





回页首


执行验证

在 Java 5.0(和 JAXP 1.3 中),JAXP 引进一种新方法来验证文档。不是仅仅在 SAX 或 DOM 工厂上使用 setValidating() 方法,而是将验证划分到新 javax.xml.validation 软件包中的几个类中。由于篇幅所限,本文没有详细介绍验证的所有细微差别,包括 W3C XML Schema、DTD、RELAX NG 模式,以及其他约束模型,但如果已经具有一些约束,那么使用新验证模型并确保文档与约束相匹配则相当容易。

冗余并非总是好事

应该做的一件事是使用 setValidating(true)javax.xml.validation 软件包。您将会得到一些严重错误,其中大多数错误难以解决。最好是养成从不调用 setValidating()(默认为假)的习惯,而是使用新的 JAXP 验证框架。

首先,将约束模型(可能是某个磁盘上的文件)转化为 JAXP 可以使用的格式。将此文件加载到 Source 实例中。(我将在第 2 部分更详细地介绍 Source;现在只要知道它表示磁盘某个位置的文档,可以是 DOM Document 或其他文档。)然后,使用 SchemaFactory.newSchema(Source) 创建 SchemaFactory 并加载模式,这将返回一个新 Schema 对象。最后,使用此 Schema 对象,用 Schema.newValidator() 方法创建一个新 Validator 对象。清单 5 清楚地显示了全部操作:


清单 5. 使用 JAXP 验证框架

DocumentBuilder builder = factory.newDocumentBuilder();Document doc = builder.parse(new File(args[0]));// Handle validationSchemaFactory constraintFactory =     SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);Source constraints = new StreamSource(new File(args[1]));Schema schema = constraintFactory.newSchema(constraints);Validator validator = schema.newValidator();// Validate the DOM treetry {    validator.validate(new DOMSource(doc));    System.out.println("Document validates fine.");} catch (org.xml.sax.SAXException e) {    System.out.println("Validation error: " + e.getMessage());}

一旦掌握之后,这一切十分简单。亲自输入此代码,或查看完整清单(参阅 下载)。





回页首


更改解析器

更改 JAXP 工厂类使用的解析器很容易。更改解析器实际上意味着更改解析器工厂,因为所有 SAXParserDocumentBuilder 实例都来自这些工厂。工厂确定加载哪个解析器,所以必须更改工厂。要更改 SAXParserFactory 接口的实现,请设置 Java 系统特性 javax.xml.parsers.SAXParserFactory。如果未定义此特性,则返回默认实现(不管开发商指定哪个解析器)。同一规则适用于所使用的 DocumentBuilderFactory 实现。在这种情况下,将会查询 javax.xml.parsers.DocumentBuilderFactory 系统特性。

 

 

在 JAXP 的早期版本中,该首字母缩写代表 Java API for XML Parsing。在 第 1 部分 中了解到,JAXP 是位于 SAX 和 DOM 之上的层,它允许 Java 程序员执行开发商中立的 XML 解析。最初,这是 JAXP 的全部特性。不过俗话说的好,过去是过去,现在是现在。

过去,Java 和 XML 组合本身主要用于解析。Java 应用程序只需读入 XML 文档,然后按程序处理文档的数据。但随着 XML 消费应用程序流行起来,很显然,各种应用程序所执行的操作有许多重叠。对于所有优秀的软件,重叠将导致规范(而且每次都产生新的有用的 API)。

XML 的广泛使用所产生的第一个规范是 XSL(参阅 参考资料)。应用程序不断提取 XML 数据,添加一些格式化,然后将其显示在用户界面上——通常是作为 HTML、XHTML 或 WML。XSL 完成此任务,并在其上构建规范,从而允许应用程序抛弃其所有的专用转换代码。XSL 规范产生之后,XML 转换 API(Transformation API for XML,TrAX)随之出现(参阅 参考资料)。TrAX 提供了在 Java 应用程序中使用 XSL 的简单一致的方法。目前,JAXP——相当长的链(和介绍)中的最后一链——已经将 TrAX 合并到核心 Java 开发环境中。按照所有的发展,以及最近的添加(比如扩展验证和 XPath 支持),JAXP 现在代表 Java API for XML Processing。本文重点介绍使用 JAXP 进行处理而非解析。

由此及彼

理解 XSL 的基本程序流对于掌握 JAXP 如何处理转换是非常关键的。如果对 XSL 十分陌生,则需要快速回顾一下 XSL 基本概念。即使您是 XSL 专家也要忍受我如此絮叨。

来源 (XML)

使用 XSL 时,必须从 XML 开始。我知道这听起来理所当然,但还是值得说明一下。您可能习惯以 XML 文件开始(比如 phonebook.xml),并将其传递到 XSL 处理器中。JAXP 不仅允许您传递文件,它还允许您做好多事,在下一节 输入和输出 中将学习相关内容。

样式表 (XSL)

可能吸引大多数设计人员的是 XSL 样式表。样式表是一个指令集合,它指定特定类型的数据作为输入,并指定其他一组数据和格式化作为输出。但是切记,样式表应该对入站 XML 的结构进行操作,而不是对文档中的特定数据进行操作。这就确保样式表处理任何给定格式的 XML,而非特定的实例文档。

目标 (*ML)

最后需要记住,只能从 XSL 中输出格式良好的标记语言。不能输出 Microsoft Word 文档或 PDF。一定要使用标记语言,比如 XML、XHTML、WML 或其他良好的 *ML(标记语言)变种。

当然,对此我听到异议——已经看到了从 XML 输出 PDF 的应用程序,或将 XML 转换为 Excel 的应用程序。而且,可以接受特定格式的 XML 并将其转换为二进制格式的引擎确实存在。 但这并不属于 XSL 的领域;它是转换后处理。JAXP 会帮助转换 XML,但它不允许转换为二进制格式。





回页首


输入和输出

通过简单的回顾可能已经了解到,许多 XML 转换只是关于输入和输出的。导入 XML,对它进行操作,然后输出 *ML。在处理所有中间位(我意识到这是最有趣的地方)之前,将展示如何将数据输入到 JAXP 和如何将其输出返回。

JAXP 的灵活性

JAXP 不是一般的转换引擎。它不能将 Java 属性文件或一次性文本格式转换为 XML 或其他格式的标记语言。事实上,JAXP 甚至不接受 HTML,除非它是格式良好的(因此产生了一些 XHTML)。所以在试图使 JAXP 成为更一般的 API 之前,应该意识到必须使用格式良好的 XML,否则其他一切都没用。

JAXP 的灵活性在于可以如何表示 XML。显而易见的原因是,JAXP 可以将 XML 接受为文件或包装文件的流。但它还可以将输入接受为 DOM Document(这表示不可能存在于磁盘上的 XML 文档),或者接受为一系列 SAX 事件(这也表示 XML 文档)。使用这个附加的灵活性,可以将 JAXP 插入到任何 XML 事件链中。例如,如果具有一段代码,该代码使用 SAX 读入 XML 文档,然后将该数据的特定部分传递给另一个应用程序,则可以简单地在 SAX 代码和其他应用程序组件之间插入 JAXP。它可以消费 SAX 事件,按需转换数据,并将结果传递给接收应用程序组件。

这个故事的寓意是,可以以任何标准格式将 XML 传递给 JAXP,并以同样多的格式将其输出。即使目前不需要这种灵活性,或无法想像怎么可能使用所有这些不同格式,但是当您的需要变得更高级时,JAXP 已经准备好为您服务了。

输入来源

javax.xml.transform.Source 接口是 JAXP 的所有输入和转换 API 的基础。该接口只定义了两个方法——getSystemId()setSystemId(String systemId)。实际上,您将不会像处理 JAXP 提供的具体实现那样,过多地直接处理该接口:

接口编程 101

如果使用接口对您来说相当陌生,则要注意,清单 1 总是把接口放在等号 (=) 左边,把特定实现类放在右边。所以 Source 在左边,StreamSourceSAXSource 等实现类在右边。

  • javax.xml.transform.dom.DOMSource 将 DOM Node(及其孩子)传递给 JAXP。
  • javax.xml.transform.sax.SAXSource 将 SAX 回调结果(来自 XMLReader)传递给 JAXP。
  • javax.xml.transform.stream.StreamSource 将包装在 FileInputStreamReader 中的 XML 传递给 JAXP。

清单 1 展示了几种用于创建转换中使用的 Source 的方法:


清单 1. 使用 Source 接口的实现

// Create a Source from a file on diskSource fileSource =   new StreamSource(new File("phonebook.xml")); // Create a Source from a DOM treeDocument myDomDocument = getDocument();Source domSource = new DOMSource(myDomDocument);// Create a Source from an InputStreamBufferedInputStream bis =   new BufferedInputStream(getInputStream());Source streamSource = new StreamSource(bis);// Create a Source from a reader and SAX InputSourceXMLReader myXMLReader = getXMLReader();InputSource myInputSource = getInputSource();Source saxSource = new SAXSource(myXMLReader, myInputSource);

清单 1 几乎是自解释的。一旦获得 Source,就可以将 XML 输入 JAXP 的 XSL 处理部分。

输出结果

在讲述转换本身之前,将简单介绍一下 Source 的输出对应物——javax.xml.transform.Result。它甚至具有与 Source 相同的两个基本方法——getSystemId()setSystemId(String systemId)

与其输入对应物一样,一般使用 JAXP 的具体 Result 实现:

  • javax.xml.transform.dom.DOMResult 将转换后的内容传递到 DOM Node 中。
  • javax.xml.transform.sax.SAXResult 将转换的结果传递到 SAX ContentHandler 中。
  • javax.xml.transform.stream.StreamResult 将转换后的 *ML 传递到 FileOutputStreamWriter 中。

清单 2 展示了一些简单的例子,与 清单 1Source 的例子十分相似:


清单 2. 使用 Result 接口的实现

// Write to a file on diskResult fileResult =   new StreamResult(new File("output.xml")); // Write a Result to a DOM tree (inserted into the supplied Document)Document myDomDocument = getDocument();Result domResult = new DOMResult(myDomDocument);// Create a Result from an OutputStreamBufferedOutputStream bos =   new BufferedOutputStream(getOutputStream());Result streamResult = new StreamResult(bos);// Create a Result to write to a SAX ContentHandlerContentHandler myContentHandler = new MyContentHandler();Result saxResult = new SAXResult(myContentHandler);

一旦理解了 SourceResult 接口以及与 JAXP 绑定在一起的实现之后,就差不多已经掌握了 XML 转换。





回页首


使用 JAXP 执行转换

如果阅读 第 1 部分 距今有一段时间了,或者谈到 JAXP 和解析时仍有些生疏,就应该花时间回顾一下 SAXParserFactoryDOMBuilderFactory 类。您将发现,如果知道如何使用这些类,就已经能够完全理解 JAXP 转换如何工作了。

获得工厂

转换如此简单,以至于有点微不足道。首先,需要设置输入和输出接收器。将 Source 包装在输入 XML 文档和 XSL 样式表中。然后,创建一个接收器,以写入转换后的结果——然后将其包装在 Result 中。

其次,需要使用静态 newInstance() 方法创建 TransformerFactory。清单 3 展示了所有详细信息:


清单 3. 创建新 TransformerFactory 实例

try {  // Set up input documents  Source inputXML = new StreamSource(    new File("phonebook.xml"));  Source inputXSL = new StreamSource(    new File("phonebook.xsl"));  // Set up output sink  Result outputXHTML = new StreamResult(    new File("output.html"));  // Setup a factory for transforms  TransformerFactory factory = TransformerFactory.newInstance();} catch (TransformerConfigurationException e) {  System.out.println("The underlying XSL processor " +    "does not support the requested features.");} catch (TransformerException e) {  System.out.println("Error occurred obtaining " +    "XSL processor.");}

这一步没有太多内容。异常处理与代码本身所用时间一样多。对于 SAX 和 DOM 工厂类,一个异常处理已请求的但不受支持的特性,另一个异常处理实例化错误。

恒等转换

TransformerFactory.newTransformer() 的一个版本不接受任何参数(因此无 XSL 样式表)。这允许您执行恒等转换,它简单地将输入 XML 从一种形式(比如流)转换为另一种形式(比如 DOM 树)。您以一种格式提供 XML 作为 Source,然后以另一种格式将其推出作为 Result。应该记住这个有用的技巧。

工厂类本身用于获得 Transformer 的实例(在 下一小节 中讨论),并执行简单配置。可以使用 setFeature(String feature, boolean value) 方法来调用处理器上的特性。当然,工厂上设置的任何特性都应用于由此工厂创建的所有 Transformer 实例。

创建 Transformer

下一步是获得对象来执行实际转换。这是另一段相当令人厌烦的代码:只在工厂上调用 newTransformer(),并为该方法提供要使用的 XSL 样式表。清单 4 展示了详细操作:


清单 4. 使用 TransformerFactory 创建 Transformer

try {  // Set up input documents  Source inputXML = new StreamSource(    new File("phonebook.xml"));  Source inputXSL = new StreamSource(    new File("phonebook.xsl"));  // Set up output sink  Result outputXHTML = new StreamResult(    new File("output.html"));  // Setup a factory for transforms  TransformerFactory factory = TransformerFactory.newInstance();  // Get a transformer for this XSL  Transformer transformer = factory.newTransformer(inputXSL);} catch (TransformerConfigurationException e) {  System.out.println("The underlying XSL processor " +    "does not support the requested features.");} catch (TransformerException e) {  System.out.println("Error occurred obtaining " +    "XSL processor.");}

此处没有太多值得注意的地方;惟一需要确保具有的就是 Transformer 与特定样式表之间的连接。因为样式表用于创建 Transformer,这是惟一可以用于该实例的 XSL。如果想要使用不同的样式表执行附加转换,可以重用 TransformerFactory,但必须创建不同的 Transformer 实例,以与新样式表连接。

执行转换

一切就绪之后,只需要一行代码来执行转换。清单 5 展示了如何使用 transform() 方法。只需为它提供输入 XML 和输出接收器;样式表已经连接到要使用的 Transformer 实例上:


清单 5. 使用 transform() 方法

try {  // Set up input documents  Source inputXML = new StreamSource(    new File("phonebook.xml"));  Source inputXSL = new StreamSource(    new File("phonebook.xsl"));  // Set up output sink  Result outputXHTML = new StreamResult(    new File("output.html"));  // Setup a factory for transforms  TransformerFactory factory = TransformerFactory.newInstance();  // Get a transformer for this XSL  Transformer transformer = factory.newTransformer(inputXSL);  // Perform the transformation  transformer.transform(inputXML, outputXHTML);} catch (TransformerConfigurationException e) {  System.out.println("The underlying XSL processor " +    "does not support the requested features.");} catch (TransformerException e) {  System.out.println("Error occurred obtaining " +    "XSL processor.");}

调用该方法之后,转换的结果写出到所提供的 Result 中。在 清单 5 中,这是一个文件,但还可以将输出发送到 SAX ContentHandler 或 DOM Node 中。如果想要全部尝试,绑定的文件提供了简单的 XML 文件、XSL 样式表和源代码(参阅 下载)。





回页首


高速缓存 XSL 样式表

这一切都很简单,但这样使用 JAXP 有两个明显的限制:

  • 每次执行 transform()Transformer 对象都要处理 XSL 样式表。
  • Transformer 的实例不是线程安全的。在多个线程之间不能使用相同的实例。

这两个限制源自同一问题:Transformer 每次执行转换时,都必须重新处理 XSL。如果该处理出现在多个线程中,就可能发生严重问题。在线程问题之上,必须一再地付出处理 XSL 样式表的成本。毫无疑问,您非常希望知道如何解决这些问题,那就继续读下去。

加载模板

尚未讨论过的接口 javax.xml.transform.Templatesjavax.xml.transform.Transformer 相近。Templates 接口是线程安全的(解决了第二个限制),并代表已编译的样式表(解决第一个限制)。在讨论相关概念之前,请查看清单 6:


清单 6. 使用 JAXP Templates 接口

try {  // Set up input documents  Source inputXML = new StreamSource(    new File("phonebook.xml"));  Source inputXSL = new StreamSource(    new File("phonebook.xsl"));  // Set up output sink  Result outputXHTML = new StreamResult(    new File("output-templates.html"));  // Setup a factory for transforms  TransformerFactory factory = TransformerFactory.newInstance();  // Pre-compile instructions  Templates templates = factory.newTemplates(inputXSL);  // Get a transformer for this XSL  Transformer transformer = templates.newTransformer();  // Perform the transformation  transformer.transform(inputXML, outputXHTML);} catch (TransformerConfigurationException e) {  System.out.println("The underlying XSL processor " +    "does not support the requested features.");} catch (TransformerException e) {  System.out.println("Error occurred obtaining " +    "XSL processor.");}

清单 6 中的黑体行表示需要从 清单 5 进行的惟一更改。不是使用工厂来直接获得 Transformer,而是使用 newTemplates() 方法;这将返回一个 Templates 对象,它是线程安全的。可以将该对象传递到其他线程中的其他方法,而且根本无需担心。因为它将从传递它的 XSL 中预编译转换指令,所以传递给其他方法甚至线程是安全的。

然后,从 Templates.newTransformer() 方法中获得 Transformer 实例。在这个阶段无需指定 XSL,因为 Transformer 已经处理过了(事实上,它是已编译的 XSL,所以可以不更改样式表,如果您愿意的话)。除了多出的一行以及对现有行的一个更改之外,再无其他新内容。相当酷,考虑一下您的代码因为这个小小的更改变得有多好。

从 Transformer 到 Templates

最后一个值得考虑的问题是,何时使用从工厂直接获得的 Transformer 和何时使用 Templates 对象。我几乎总是选择使用 Templates 对象,因为使用 XSL 时,我总是重复使用相同的样式表。与其花时间在 XSL 上进行多次传递,我宁愿将指令预编译到 Templates 对象中,完成 XSL 处理。

也就是说,在一些情况下,最好是直接从 TransformerFactory 中拖出 Transformer。如果您知道您将只使用特定样式表执行单个转换,那么不预编译到 Templates 对象中会比较快,因为预编译需要略多一点的开销。但是,需要确保没有重用。在我的(完全不科学,使用了简短的样例)测试中,我发现如果使用一个 XSL 样式表两次,使用 Templates 对象和直接使用 Transformer 不分上下。 一旦使用三次以上,Templates 方法就要好多了。您还需要确保将没有任何线程技术问题;但这是一件简单的事情,所以我把它留给您应用在编程中。 一般地,使用 Templates 对象通常更安全。





回页首


更改 XSL 处理器

第 1 部分 中已经看到,通过更改系统属性,可以用自己的实现替换默认 JAXP 解析器实现。同一原则适用于 XSL 处理器。JAXP 预包装有 Xalan-J(参阅 参考资料),我总是使用它。但灵活性总是好事,而 JAXP 提供了这一点。

如果想要使用除 Xalan 之外的处理器,请为名为 javax.xml.transform.TransformerFactory 的系统属性提供一个值。需要为该属性分配要实例化的类的名称。该类应继承 javax.xml.transform.TransformerFactory(当然,这也是要设置的系统属性的名称),并填充方法其余的抽象。仅使用如下代码:

java -Djavax.xml.transform.TransformerFactory=[transformer.impl.class] TestTransformations       simple.xml simple.xsl

全部结束!

原创粉丝点击