用于转换的XML样式表语言定义了用于寻址XML数据(XPath)以及指定这些数据转换的机制,以便将其转换成其他形式。JAXB包含两种XSLT实现、一个解释版本(Xalan)和一个编译版本(XSLTC),这些能让你将所要转换的预编译版本保存为translet,以便在稍后进行有效的运行时处理。
本章介绍了如何使用Xalan和XSLTC,如何将一个文档对象模型(DOM)作为XML文件写出来,如何从任意的数据文件生成一个DOM以将其转换成XML。最后还讲述了如何将XML数据转换成不同的形式,并剖析XPath寻址机制的神秘过程。
注意:本章中的例子可以在<
JWSDP_HOME>/docs/tutorial/examples/jaxp/xslt/samples
中找到。
XSLT和XPath介绍
“流对象”标准。这是目前最大的组成部分,该标准给出了用于描述字体大小、页面布
局以及信息如何从一个页面“流”到另一个页面的机制。该组成部分不属于JAXP的范畴,也不是本教程的讨论范围。
它就是转换语言,定义了从XML到一些其他格式的转换。例如,可以利用XSLT来产生HTML或者不同的XML结构。甚至可以利用它来生成纯文本或者将这些信息转换成其他的文档格式。(在从任意的数据结构生成XML中,一个很灵活的应用将用它来操作非XML的数据)
实际上,XSLT是一种能让你在遇到一个特定元素时,规定做什么事的语言。但是要想为XML数据结构的不同部分写出程序,还需要能够指定某一时刻正在谈论的部分。XPath就是这种规范语言。它是一个寻址机制,能让你指定到某个元素的路径,从而可以区分
<article><title>
和
<person><title>
。这样就能够为各种
<title>
元素描述不同种类的转换。
本节的其余部分描述了组成JAXP转换API的包,接着讨论了用来选择Xalan或XSLTC转换引擎的工厂配置参数。
JAXP转换包
这个包定义了用来获得Transformer
对象的工厂类。接着可以利用输入(源)和输出(结果)对象来配置转换器,并调用它的
transform()
方法来进行转换。源和结果对象是用来自其他三个包的对象创建的。
(得到的是Xalan解释转换器还是XSLTC编译转换器,取决于工厂配置设置,这马上就会讲到)。
定义了DOMSource
和DOMResult
类,这些类允许将
DOM
用作某个转换的输入或输出。
定义了SAXSource
和SAXResult
类,这些类允许将
SAX
事件生成器用作某个转换的输入,或者将
SAX
事件用作
SAX
事件处理器的输出。
定义了StreamSource
和StreamResult
类,这些类允许将一个
I/O
流用作某个转换的输入或输出。
选择转换引擎
本节提供的信息有助于在解释转换器(Xalan)和编译转换器(XSLTC)之间作出选择。
性能考虑
对于一个单次(single-pass)转换,解释转换器(Xalan)的速度比编译转换器(XSLTC)稍快,因为它不在作为translet运行的小Java类中生成和保存字节码。
但在需要多次用到一个转换时,使用XSLTC转换引擎就好的多,这是因为在这种情况下它更符合内存需求和性能指标。
XSLTC translet很小,因为它只实现样式表实际上进行的那些转换。并且它速度也很快,一方面是由于小,另一方面是由于解释样式表所需的词汇处理已经进行过了。最后,由于translet的体积小,所以加载速度更快,且占用的系统资源更少。
例如,一个需要运行很长一段时间的servlet会从XSLTC中受益。同样,使用XSLTC时,从命令行运行的转换速度也更快。关于这个过程的更多信息,请参见从命令行转换。
除了能缓存tranlet,XSLTC还提供了很多有助于最大化性能的其他选项:
默认情况下,XSLTC会“内联”转换码,这意味着负责转换元素的代码中包含该元素所有子元素的转换码。
对于小型和中等的样式表,该实现生成的代码可能是最快的。但复杂样式表生成的translet则可能会大的出奇。
为了解决这个问题,XSLTC允许禁用内联功能。为此,在从命令行编译XSLTC translet时需要使用-n选项。当利用JAXP工厂类生成XSLTC转换器时,需要使用工厂的setAttribute()
方法来设置
"disable-inlining"
特性,代码如下:
TransformerFactory tf = new TransformerFactory();
tf.setAttribute("disable-inlining", Boolean.TRUE);
XSLTC在XML数据上运行时,创建了自己的内部文档对象模型(这和你见过的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
系统属性。
但有时候设置系统属性是不可能的,因为该应用是一个servlet,并且更改系统属性会影响运行在同一个容器中的其他servlet。这种情况下,可以直接实例化XSLTC转换引擎,命令如下:
还可以将工厂值传递给应用,然后使用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部分中或者它是否包含实体引用,都无所谓。文本节点由规范化的数据组成,因为它在所有的解析都完成之后才生成。所以,不管
该文本包含在像
<
这样的实体引用中还是在
CDATA
部分中,它都
包含一个<
字符。(同样,不管该文本利用
&
传送还是在
CDATA
部分中传送,它都包含一个
&
字符。)
模板和上下文
XSLT模板是一组适用于XPATH表达式选定的节点的格式化指令。在样式表中,XSLT模板如下所示:
表达式//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"]
——
选中一个名为
MyProject
的PROJECT。
· /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节中列出了更多的地址运算符组合。这无疑是这个用于定义XSLT转换的规范中最有用的一节。
通配符
根据定义,无限制条件的XPath表达式选中一组与指定模式匹配的XML节点。例如,/HEAD与所有的顶级HEAD条目匹配,而/HEAD[1]只匹配其中的第一个。
表8-1列出了XPath表达式中可用的通配符,以扩展模式匹配的范围。
比如在项目数据库的例子中, /*/PERSON[.="Fred"]
与所有包括
Fred的PROJECT
或ACTIVITY
元素匹配。
扩展路径寻址
到目前为止,我们见过的所有模式已经精确地指定了分层结构中的级数。例如,/HEAD指定了分层结构中第一级的所有HEAD元素,而/*/*
指定了分层结构中第二级中的所有元素。为了指定分层结构的中间级,可以使用双斜杠
(//
)。例如,XPath表达式//
会选中文档中所有的
paragraph
元素,不管它们位于何处。
//
模式还可用在路径内部。所以表达式
/HEAD/LIST//PARA
表示以
/HEAD/LIST
开始的子树中所有的段元素。
XPath数据类型和运算符
XPath表达式生成一组节点、一个字符串、一个布尔值(true/false),或者一个数字。表8-2列出了XPath表达式中可用的运算符。
注意:“运算符优先级”正是解决这一问题的术语,“a + b * c
是表示
(a+b) * c
,还是
a + (b*c)
呢?
”。(运算符优先级和上表中所描述的大致一样)。
元素的字符串值
在继续我们的研究之前,有必要了解一下如何确定更复杂元素的字符串值。现在就开始吧。
元素的字符串值是所有子孙文本节点级联在一起构成的,不管这些节点有多深。所以,对于像
<PARA>
的字符串值为
"This paragraph contains a bold word"。尤其要注意<B>
是
<PARA>
的子元素,并且包含在所有子元素中的文本级联在一起构成了该字符串值。
同样,有必要知道XPath在抽象数据模型中定义的文本是完全规范化的。所以不管XML结构中是包含实体引用<
还是
CDATA
部分中的
"<
",元素的字符串值都包含"<
"字符。因此,当生成HTML或者具有XSLT样式表的XML时,"<
"必须被转换成<
,或者包含在一个
CDATA部分中。同样,"&
"也需要转换成&
。
XPath函数
本节最后将概括一下XPath函数。可以利用XPath函数选中一类节点,方法和使用前面见过的元素规范一样。其他函数返回字符串、数字或者布尔值。例如,表达式/PROJECT/text()
得到
PROJECT
节点的字符串值。
很多函数依赖于当前的上下文。比如在上例中,用于每个text()
函数调用的上下文是当前选中的
PROJECT
节点。
XPath函数太多了,这里不能一一详细描述。本节列出了可用的XPath函数,并总结了它们的用途。
注意:只要大致浏览一下该函数列表,知道里面有什么东西就行了。详细内容可以参考 XPath规范中的第4节。
节点集函数
很多XPath表达式选中了一组节点。本质上,它们返回了一个节点集(node-set)。有一个函数也完成同样的功能。
(只有在文档具有DTD时,元素才有ID。因为DTD规定了哪些属性具有ID类型。)
位置函数
例如:/HEAD[position() <= 5]
选中前五个
HEAD
元素。
例如:/HEAD[count(HEAD)=0]
选中所有无子标题的
HEAD
元素。
字符串函数
· concat(string, string, ...)
——连接字符串值。
· starts-with(string1, string2)
——
若
string1
以
string2
开始,则
返回真。
· contains(string1, string2)
——若
string1
包含
string2
,则返回真。
· substring-before(string1, string2)
——在
string1
中出现
string2
之前,返回
string1
的起始部分。
· substring-after(string1, string2)
——在
string1
中出现
string2
之后,
返回
string1
中的剩余部分。
· substring(string, idx)
——返回索引位置到结尾之间的子字符串,其中第一个字符的索引等于
1
。
· substring(string, idx, len)
——返回从索引位置开始、具有特定长度的子字符串。
· string-length()
——返回上下文节点字符串值的大小。
上下文节点是指当前选定的节点——即XPath表达式选中的、其中应用了像string-length()
这样的函数的节点。
· string-length(string)
——返回指定字符串的大小。
· normalize-space()
——返回当前节点的规范化字符串值(首尾都无空白,且连续的空白字符被转换成一个空格)。
· normalize-space(string)
——返回指定字符串的规范化字符串值。
· translate(string1, string2, string3)
——转换
string1
,用
string3
中的字符替换
string2
中相应的字符。
注意:XPath定义了三种方法来获得元素的文本:text()
、string(object)
以及字符串值。其中字符串值暗含在像
/PROJECT[PERSON="Fred"]
这样的表达式中的元素名中。
布尔函数
· lang(string)
——若上下文节点的语言(由
xml:Lang
属性指定)与指定语言相同(或者是它的子语言),返回真。例如:
Lang("en")
对于
<PARA_xml:Lang="en">...</PARA>
为真。
数值函数
· sum(...)
——返回特定节点组中各个节点数值的总和。
转换函数
· string(...)
——返回数字、布尔值或者节点组的字符串值。
· boolean(...)
——返回数字、字符串或节点组的布尔值(一个非零数字、非空节点组和非空的字符串都为真)。
· number(...)
——返回布尔值、字符串或节点组的数值(真为
1
,假为
0
,包含某个数字的字符串的值为该数字本身,节点组的值是从它的字符串值转换而来)。
命名空间函数
· local-name()
——返回当前节点除命名空间前缀之外的名称。
· local-name(...)
——返回指定节点组除命名空间前缀之外的名称。
· namespace-uri()
——返回从当前节点开始的命名空间
URI
。
· namespace-uri(...)
——返回从指定节点组中第一个节点开始的命名空间
URI
。
· name()
——返回当前节点的扩展名(
URI
加本地名)。
· name(...)
——返回指定节点组中第一个节点的扩展名(
URI
加本地名)。
小结
XPath运算符、函数、通配符和节点寻址机制可以按照各种方式进行组合。有了前面的介绍,你在指定特定用途的模式时应该很得心应手。