Eric Armstrong

用于转换的XML样式表语言定义了用于寻址XML数据(XPath)以及指定这些数据转换的机制,以便将其转换成其他形式。JAXB包含两种XSLT实现、一个解释版本(Xalan)和一个编译版本(XSLTC),这些能让你将所要转换的预编译版本保存为translet,以便在稍后进行有效的运行时处理。

本章介绍了如何使用XalanXSLTC,如何将一个文档对象模型(DOM)作为XML文件写出来,如何从任意的数据文件生成一个DOM以将其转换成XML。最后还讲述了如何将XML数据转换成不同的形式,并剖析XPath寻址机制的神秘过程。


注意:本章中的例子可以在<JWSDP_HOME>/docs/tutorial/examples/jaxp/xslt/samples中找到。

XSLTXPath介绍

XML样式表语言有三个主要组成部分:

XSL-FO

 “流对象”标准。这是目前最大的组成部分,该标准给出了用于描述字体大小、页面布

局以及信息如何从一个页面“流”到另一个页面的机制。该组成部分不属于JAXP的范畴,也不是本教程的讨论范围。

XSLT

它就是转换语言,定义了从XML到一些其他格式的转换。例如,可以利用XSLT来产生HTML或者不同的XML结构。甚至可以利用它来生成纯文本或者将这些信息转换成其他的文档格式。(在从任意的数据结构生成XML中,一个很灵活的应用将用它来操作非XML的数据)

XPath

实际上,XSLT是一种能让你在遇到一个特定元素时,规定做什么事的语言。但是要想为XML数据结构的不同部分写出程序,还需要能够指定某一时刻正在谈论的部分。XPath就是这种规范语言。它是一个寻址机制,能让你指定到某个元素的路径,从而可以区分<article><title><person><title>。这样就能够为各种<title>元素描述不同种类的转换。

本节的其余部分描述了组成JAXP转换API的包,接着讨论了用来选择XalanXSLTC转换引擎的工厂配置参数。

JAXP转换包

组成JAXP转换API的包的描述如下:

javax.xml.transform

这个包定义了用来获得Transformer对象的工厂类。接着可以利用输入(源)和输出(结果)对象来配置转换器,并调用它的transform()方法来进行转换。源和结果对象是用来自其他三个包的对象创建的。

(得到的是Xalan解释转换器还是XSLTC编译转换器,取决于工厂配置设置,这马上就会讲到)。

javax.xml.transform.dom

定义了DOMSourceDOMResult类,这些类允许将DOM用作某个转换的输入或输出。

javax.xml.transform.sax

定义了SAXSource SAXResult类,这些类允许将SAX事件生成器用作某个转换的输入,或者将SAX事件用作SAX事件处理器的输出。

javax.xml.transform.stream

定义了StreamSourceStreamResult类,这些类允许将一个I/O流用作某个转换的输入或输出。

选择转换引擎

本节提供的信息有助于在解释转换器(Xalan)和编译转换器(XSLTC)之间作出选择。

性能考虑

对于一个单次(single-pass)转换,解释转换器(Xalan)的速度比编译转换器(XSLTC)稍快,因为它不在作为translet运行的小Java类中生成和保存字节码。

但在需要多次用到一个转换时,使用XSLTC转换引擎就好的多,这是因为在这种情况下它更符合内存需求和性能指标。

XSLTC translet很小,因为它只实现样式表实际上进行的那些转换。并且它速度也很快,一方面是由于小,另一方面是由于解释样式表所需的词汇处理已经进行过了。最后,由于translet的体积小,所以加载速度更快,且占用的系统资源更少。

例如,一个需要运行很长一段时间的servlet会从XSLTC中受益。同样,使用XSLTC时,从命令行运行的转换速度也更快。关于这个过程的更多信息,请参见从命令行转换

除了能缓存tranletXSLTC还提供了很多有助于最大化性能的其他选项:

  • 内联控制

默认情况下,XSLTC会“内联”转换码,这意味着负责转换元素的代码中包含该元素所有子元素的转换码。

对于小型和中等的样式表,该实现生成的代码可能是最快的。但复杂样式表生成的translet则可能会大的出奇。

为了解决这个问题,XSLTC允许禁用内联功能。为此,在从命令行编译XSLTC translet时需要使用-n选项。当利用JAXP工厂类生成XSLTC转换器时,需要使用工厂的setAttribute()方法来设置"disable-inlining"特性,代码如下:

TransformerFactory tf = new TransformerFactory();
tf.setAttribute("disable-inlining", Boolean.TRUE);

  • 文档模型缓存

XSLTCXML数据上运行时,创建了自己的内部文档对象模型(这和你见过的W3C DOM类似,只不过简单了点)。由于该文档模型的构建很费时间,XSLTC就提供了一种缓存该模型的方法,以帮助加速子元素的转换。

这个特性在提供XML文档的servlet中迟早会有用。如果在访问Web上的这些文档时,它们被转换成了HTML,那么缓存这些文档的内存中表示(in-memory representation)可能对性能产生巨大的影响。下面是一些可以使用的示例代码:

final SAXParser parser = factory.newSAXParser();
final XMLReader reader = parser.getXMLReader();

XSLTCSource source = new XSLTCSource();
source.build(reader, xmlfile);

于是source对象不需要重新读取文件就可以在多个转换中重复使用。

  • 缓存编译过的样式表

XSLTC还允许保存编译过的样式表,这样就能用它们来更快地创建多个Transformer对象。例如,这种能力能加快多线程servlet的启动速度。如果该servlet为输入请求生成了一百个线程,它可以一次性地编译样式表,然后利用这个编译过的版本为每个线程生成转换器。

预编译过的样式表存放在Templates对象中。直接创建Transformer对象(不使用Templates对象)时使用的代码如下:

TransformerFactory factory =
    TransformerFactory.newInstance();
Transformer xformer = factory.newTransformer(myStyleSheet);
xformer.transform(myXmlInput,
    new StreamResult(System.out));

但也可以创建一个可以保存和重新利用的中间Templates对象,代码如下:

TransformerFactory factory =
    TransformerFactory.newInstance();
Templates templates = factory.newTemplates(myStyleSheet);
Transformer xformer = templates.newTransformer();
xformer.transform(myXmlInput,
    new StreamResult(System.out));


注意:为获得XSLT的最大性能,在设计样式表时有很多事情是需要做的,同时有很多事情是需要避免的。关于这个话题的更多信息,见http://xml.apache.org/xalan-j/xsltc/xsltc_performance.html


功能考虑

虽然从性能上考虑XSLTC对于很多应用来说是一个更好的选择,但在功能上Xalan却有自己的优势。支持标准的查询语言SQL就是其中之一。

作出选择

得到的是Xalan转换引擎还是XSLTC转换引擎,取决于工厂配置设置。默认情况下,JAXP工厂创建的是Xalan转换器。为了获得XSLTC转换器,最好的方法是设置如下的TransformationFactory系统属性。

javax.xml.transform.TransformerFactory=
    org.apache.xalan.xsltc.trax.TransformerFactoryImpl 

但有时候设置系统属性是不可能的,因为该应用是一个servlet,并且更改系统属性会影响运行在同一个容器中的其他servlet。这种情况下,可以直接实例化XSLTC转换引擎,命令如下:

new org.apache.xalan.xsltc.trax.TransformerFactoryImpl(..) 

还可以将工厂值传递给应用,然后使用ClassLoader在运行时创建它的一个实例。


注意:要想明确地指定Xalan转换器,需要使用值org.apache.xalan.processor.TransformerFactoryImpl,而不是org.apache.xalan.xsltc.trax.TransformerFactoryImpl


有一个“智能转换器”,它在生成Transformer对象时使用Xalan转换引擎,而在生成中间Templates对象时使用XSLTC转换引擎。为了获得该智能转换器的实例,需要使用值org.apache.xalan.xsltc.trax.SmartTransformerImpl,以设置转换器工厂的系统属性,或者直接利用那个类来实例化解析器。

XPath的工作原理

XPath规范是各种规范的基础,这些规范包括像XPointer这样的XSLT和链接/寻址规范。所以理解XPath是使用很多高级XML用法的基础条件。本节完整地介绍了XSLT上下文中的XPATH,供后面需要时参考。


注意:在本教程中,只有看了本节后面的使用XSLT转换XML数据才可能真正地利用XPath。但是如果你愿意,可以跳过本节直接跳到下一节作为XML文件写出DOM。(在那一节的结尾会提示你回到这里,切记!)


XPATH表达式

通常,XPath表达式规定了选中一组XML节点的模式(pattern)。XSLT模板在应用转换时使用这些模式(而XPointer增加了用于定义点(point)或范围(range)的机制,因此XPath表达式可用于寻址)。

XPath表达式中的节点不仅仅指的是元素。它们也可以是文本、属性以及其他东西。实际上,XPath规范定义了一个抽象的文档模型,该模型定义了七种不同的节点,它们是:

·  

·   元素

·   文本

·   属性

·   注释

·   处理指令

·   命名空间


注意:XML数据的根元素是通过元素节点形成的。XPath根节点包含了文档的根元素,以及与该文档有关的其他信息。


XSLT/XPath数据模型

DOM一样,XSLT/XPath数据模型也由一个包含各种节点的树组成。在任意给定的元素节点下,都有文本节点、属性节点、元素节点、注释节点和处理指令节点。

在该抽象模型中,没有语法上的区别,所有数据都是规范化的。比如在文本节点中,文本是否定义在CDATA部分中或者它是否包含实体引用,都无所谓。文本节点由规范化的数据组成,因为它在所有的解析都完成之后才生成。所以,不管该文本包含在像&lt;这样的实体引用中还是在CDATA部分中,它都包含一个<字符。(同样,不管该文本利用&amp;传送还是在CDATA部分中传送,它都包含一个&字符。)

该教程的这一节主要讨论元素节点和文本节点。

模板和上下文

XSLT模板是一组适用于XPATH表达式选定的节点的格式化指令。在样式表中,XSLT模板如下所示:

<xsl:template match="//LIST">
    ...
</xsl:template> 

表达式//LIST从输入流中选中一组LIST节点。该模板中的附加指令会告诉系统如何处理。

被这样一个表达式选中的一组节点定义了上下文,并且模板中其他表达式的值就是在这个上下文中计算出来的。在确定上下文中包含了多少个节点时,它被作为一个整体考虑。

上下文也可以看成是该组中的单个成员,因为每个成员是逐个被处理的。例如,在LIST处理模板中,表达式@type指当前LIST节点的type属性。(同样,表达式@*指当前LIST元素的所有属性)。

基本的XPath寻址

XML文档是一个树型结构(分层)的节点集合。和分层目录结构一样,该树型结构规定了一个指向某一节点的路径。(此规范因此而得名:XPath。)实际上,很多目录路径的符号在传输过程中保持原样。

·   正斜杠/用作路径的分隔符。

·   到文档根部的绝对路径以/开始。

·   到给定位置的相对路径以其他符号开始。

·   双句点..表示当前节点的父节点。

·   单句点.表示当前节点。

例如,在XHTML文档(和HTML类似的一种XML文档,只不过根据XML的规则它的形式是well-formed)中,路径/h1/h2/表示h1下面的h2元素。(回想一下,在XML中元素名区分大小写,所以这种类型的规范在XHTML中要比在纯HTML中有效,因为HTML不区分大小写。)

在像 XSLT这样的模式匹配规范中,规范/h1/h2选中所有位于h1元素之下的h2元素。要想选中某一特定的h2元素,需要利用方括号[]进行索引(就像数组中那样)。例如,路径/h1[4]/h2[5]选中了第四个h1元素下的第五个h2元素。


注意:在XHTML中,所有的元素名都是小写的。这是XML文档中常见的命名约定。本教程中的名称用大写只是为了读起来方便。所以,在该XSLT教程中其他部分中,所有的XML元素名都用大写表示。(属性名仍用小写表示。)


XPath表达式中规定的名称指的是一个元素。例如,/h1/h2中的h1表示h1元素。要想引用某个属性,需要在该属性名的前面加上一个@符号。例如,@type表示元素的type属性。如果已经有了一个具有LIST元素的XML文档,表达式LIST/@type将选中LIST元素的type属性。


注意:由于该表达式不是以/开始的,所以这个引用指定了一个与当前上下文有关的list节点——所谓当前上下文是指文档中的当前位置。


基本的XPath表达式

所有的XPath表达式都利用了XPath定义的通配符、运算符和函数。关于这些内容你很快就会学到。这里,我们先看一对最常用的XPath表达式。只是简单的介绍一下,不作深入讨论。

表达式@type="unordered"指定了一个名为type的属性,它的值为"unordered"。并且我们已经知道像LIST/@type这样的表达式指定了LIST元素的type属性。

将这两个符号结合起来将看到一些很有趣的事!在XPath中,与索引有关的方括号([])的含义通常被延伸,它还能指定选择条件(selection criteria)。所以表达式LIST[@type="unordered"]选中所有type值为"unordered"LIST元素。

元素的表达式也差不多,其中每个元素都有一个相关的字符串值(string-value)。(稍后你将会看到如何为一个复杂元素确定字符串值。但现在我们仍然讲只有一个文本字符串的简单元素。)。

假定你在公司创建了一个由PROJECT元素和ACTIVITY元素组成的 XML结构,这两种元素有一个文本字符串,其中字符串包括项目名、用来列出相关人员的多个PERSON元素,以及一个可选的记录项目状态的STATUS元素。下面是其他一些使用了扩展方括号的例子:

·   /PROJECT[.="MyProject"]——选中一个名为MyProjectPROJECT

·   /PROJECT[STATUS]——选中所有具有STATUS子元素的项目。

·   /PROJECT[STATUS="Critical"]——选中所有那些具有STATUS子元素,且该子元素包含字符串值"Critical"的项目。

组合索引地址

XPath规范定义了很少的几个寻址机制,并且它们还可以按照各种方法结合起来使用。由此,XPath为一个相对简单的规范提供了十分丰富的表现力。本节展示了另外两种有趣的组合:

·   LIST[@type="ordered"][3]——选中所有类型为"ordered"LIST元素,并返回其中的第三个。

·   LIST[3][@type="ordered"]——选中第三个LIST元素,但前提是它的类型为"ordered"


注意:XPath规范的第2.5</