第14章 XSL变换
可扩展的样式语言(Extensible Style Language,XSL)包括变换语言(transformation language)和格式化语言(formatting language)。每种语言都是一个XML应用程序。变换语言提供定义规则的元素如何将XML文档变换成另一个XML文档。被变换的XML文档可能使用原文档的标记和DTD,或者使用一组完全不同的标记。特别是,可能会使用XSL第二部分(格式化对象)定义的标记。本章涉及到XSL变换语言中的部分内容。
本章的主要内容如下:
· 理解XSL、XSL变换和模板 
· 计算节点的值 
· 处理多个元素 
· 用表达式选择节点 
· 理解缺省的模板规则 
· 确定输出要包含的内容 
· 复制当前节点 
· 对节点进行计数、对输出元素分类以及插入CDATA和<符号 
· 设置模式特性 
· 定义并创建命名模板 
· 删除和保留空格 
· 基于输入来改变输出 
· 合并多个样式单 
14.1 何为XSL?
变换和格式两部分可相互独立地起作用。例如,变换语言可将XML文档变换成结构整洁的HTML文件,并且完全忽略XSL格式化对象。Internet Explorer 5.0支持这种XSL样式,这在第5章已讨论过,本章着重讨论这种样式。
此外,以XSL格式化对象编写的文档,并非绝对要求在另一个XML文档上使用XSL变换部分才能产生。例如,很容易想象到这样的一个转换器:它是用Java语言写成的,可读取TeX或PDF文件,并把这些文件翻译成XSL格式化对象(尽管直到1999年夏天仍没有这样的一种转换器存在)。
实际上,XSL是两种语言,而不是一种。第一种语言是变换语言,第二种是格式化语言。变换语言是一种很有用的语言,它与格式化语言无关。它能够把数据从一种XML表示移到另一种表示,这种功能,使它成为基于XML的电子商务、电子数据交换、元数据交换以及应用于需要在相同数据的不同XML表示之间进行转换的重要组成部分。由于缺乏对人们要浏览的显示器上显示数据的了解,这些用途还要结合起来使用。它们纯粹是用来将数据从一种计算机系统或程序移到另一种计算机系统或程序中。
因此,许多早期的XSL实现都毫无例外地将焦点集中在变换部分,而忽略了格式化对象。这些是不完善的实施方案,但仍然是很有用的。并非所有的数据最终都必须显示在计算机显示器上或打印到纸上。
第15章“XSL格式化对象”将涉及XSL格式化语言。
 
有关XSL警告语
XSL仍然处于开发中。XSL语言在过去发生了根本性的变化,将来肯定会再发生变化。本章是根据1999年4月21日的XSL规范草案(第四稿)写成的。读者阅读此书时,此XSL草案可能已经被取代了,精确的XSL句法将会变化。我希望本章与实际的规范不会相差太大。但是,如果的确有不一致的地方,应将本书中的例子与最新规范进行对比。
糟糕的是,仍然没有任何软件能实现1999年4月21日的XSL规范草案(第四稿)的所有内容,甚至不能实现XSL变换的部分。现有的所有产品只能实现当前草案的不同子集。而且,许多产品(包括Internet Explorer 5.0和XT)加入的元素并没有出现在当前XSL草案规范中。最后一点是,大多数至少要实现部分XSL内容的产品在其可实现的部分中也存在着很严重的程序错误(bug)。因此,在不同的软件中,只有廖廖无几的几个例子能准确地以相同的方式工作。
当然,随着此项标准向最后版本改进时,当开发商解决了自己产品中的程序错误并实现没有被实现的内容时,以及当出版的更多软件支持XSL时,最终这种情况是可以得到修正的。在达到此目的之前,还得面对这样的选择:要么忍痛使用目前不完善的、未完成的XSL,并且试图避开遇到的所有程序错误和疏忽,要么使用更确定的技术(如CSS),直到XSL更加可靠为止。
 
14.2 XSL变换概述
在XSL变换中,XSL处理程序读取XML文档和XSL样式单。基于处理程序在XSL样式单中找到的指令,输出新的XML文档。
14.2.1 树形结构
就像第6章学到的那样,每个结构整洁的XML文档都是树形结构(tree)。树形结构是一种数据结构,它是由连接起来的节点(node)组成的,这些节点起始于一个称为根节点(root)的单节点。根节点连接它的子节点,每个子节点可以连接零个或多个它自己的子节点,依次类推。没有自己的子节点的节点称为叶节点(leave)。树形结构的图表更像家谱,列出各个先辈的后代。树形结构最有用的特征是每个节点及其子节点也会形成树形结构。因此,一个树形结构就是所有树形结构的分级结构,在此分级结构中,各树形结构都是由更小的树形结构建立的。
XML树形结构的节点就是元素及元素的内容。但是,对于XSL,特性、命名域(namespace)、处理指令以及注释必须也作为节点看待。而且文档的根节点必须与根(基本)元素区别开来。因此,XSL处理程序假定XML树形结构包含下列七类节点:
1.根节点
2.元素
3.文本
4.特性
5.命名域
6.处理指令
7.注释
例如,对于清单14-1中的XML文档,它显示的是元素周期表,在本章我将用这个元素周期表作为例子(更恰当地说,是周期表中的前两个元素)。
完整的元素周期表放在本书所附光盘中的examples/periodic_table文件夹中的allelements.xml文件中。
根节点PERIODIC_TABLE元素包含ATOM子元素。每个ATOM元素含有各种子元素,以便提供原子序数、原子量、符号、沸点等等信息。UNITS特性为具有单位的元素指定单位。
这里使用ELEMENT比ATOM或许更恰当。但是,写成ELEMENT元素难以区分化学元素和XML元素。因此,起码出于本章的考虑,ATOM似乎更具可读性。
 
清单14-1:氢和氦元素的XML周期表
<?xml version=”1.0”?>
<?xml-stylesheet type=”text/xsl” href=”14-2.xsl”?>
<PERIODIC TABLE>
<ATOM STATE=”GAS”>
<NAME>Hydrogen</NAME>
<SYMBOL>H</SYMBOL>
<ATOMIC_NUMBER>l</ATOMIC_NUMBER>
<ATOMIC_WLIGHT>1.00794</ATOMIC_WEIGHT>
<BOILING_POINT UNITS=”Kelvin”>20.28</BOILING_POINT>
<MELTING_POINT UNITS=”Kelvin”>13.81</MELTING_POINT>
<DENSITY UNITS=”grdMS/cubic centimeter”><!- At 300K ->
0.0899
</DENSITY>
</ATOM>
<ATOM STATE=”GAS”>
<NAME>Helium</NAME>
<SYMBOL>He</SYMBOL>
<ATOMIC_NUMBER>2</ATOMIC_NUMBER>
<ATOMIC_WEIGHT>4.0026</ATOMIC_WEIGHT>
<BOILING_POINT UNITS=”Kelvin”>4.216</BOILING_POINT>
<MELTING_POINT UNITS=”Kelvin”>0.95</MELTING_POINT>
<DENSITY UNITS=”grams/cubic centimeter”><!- At 300K ->
0.1785
</DENSITY>
</ATOM>
</PERIODIC_TABLE>
图14-1显示的是本文档作为树形结构的图解。它起始于顶端的根节点(不同于根元素!),包括两个子节点:xml-stylesheet处理指令和根元素PERIODIC_TABLE。(XML声明对XSL处理程序是不可见的,因而不包括在XSL处理程序进行操作的树形结构中)。PERIODIC_TABLE元素包括两个子节点,即两个ATOM元素。每个ATOM元素都一个STATE特性的特性节点和各种子元素节点。每个子元素包括其内容的节点,以及任何特性节点和拥有的注释。注意,在特殊情况下,许多节点可以是除元素之外的任何内容。节点可为文本、特性、注释和处理指令。与CSS1不同,XSL不限于只和全部元素一起使用,还有一个更加独特地查看文档的方法:能够根据注释、特性、处理指令等设计样式。
像XML声明一样,内部的DTD子集或DOCTYPE声明不是树形结构的一部分。但是,通过使用#FIXED或缺省特性值的<!ATTLIST>声明,它可能具有将特性节点添加到某些元素中的效果。
 
 
图14-1 以树形图表示的清单14-1
XSL变换语言通过将XML树形结构变换成另一个XML树形结构来操作。这种语言含有操作符,此操作符用来从树形结构中选择特定节点、对节点重新排序以及输出节点。如果有一个节点是元素节点,那么它本身可能就是整个树形结构。请记住,所有的用于输入和输出的操作符都只能操作一个树形结构。它们不是用于变换任意数据的通用的正常表达语言。
14.2.2 XSL 样式单文档
更准确地说,当输入时,XSL变换接受以XML文档表示的树形结构,而输出时,则产生也以XML文档来表示的新的树形结构。因此,XSL变换部分也称为树形结构建立部分。输入和输出的内容必须是XML文档。不能使用XSL来变换成非XML格式(如PDF、TeX、Microsoft Word、PostScript、MIDI或其他)或从非XML格式进行变换。可使用XSL将XML变换为一中间格式(如TeXML),然后使用另外的非XSL软件来将这个中间格式变换成期望的格式。HTML和SGML都是介乎于两者之间的情况,因为它们非常接近于XML。可使用XSL将符合XML的结构完整性规则的HTML和SGML文档变换成XML或者相反。但是,XSL不能处理在大多数Web站点上和文档生成系统中遇到的各种各样非结构整洁的HTML和SGML文档。要牢记的首要问题是,XSL变换语言对于XML到XML的变换是可行的,但对于其他方面则不行。
XSL文档包含一组模板规则和其他规则。模板规则拥有模式(pattern)以及模板(template),模式用来指定模板规则所适用的树形结构,而模板是用来在与此模式匹配时进行输出。当XSL处理程序使用XSL样式单来格式化XML文档时,它对XML文档树形结构进行扫描,依次浏览每个子树形结构。当读完XML文档中的每个树形结构时,处理程序就把它与样式单中每个模板规则的模式进行比较。当处理程序找到与模板规则的模式相匹配的树形结构时,它就输出此规则的模板。这个模板通常包括一些标记、新的数据和从原XML文档的树形结构中复制来的数据。
XSL使用XML来描述这些规则、模板和模式。XSL文档本身也是xsl:stylesheet元素。每个模板规则都是xsl:template元素。规则的模式是xsl:template元素的match特性值。输出模板是xsl:template元素的内容。模板中所有的指令都是由一个或另一个XSL元素来完成的,而这些指令是来完成某种动作,如选择输入树形结构中要包括在输出树形结构的部分。这些由元素名上的xsl:前缀来标识。没有xsl:前缀的元素为结果树部分。
更恰当地说,作为XSL指令的所有元素都是xsl命名域的一部分。有关命名域将在第18章“命名域”中讨论。在那以前,只要了解所有的XSL元素的名称都是以xsl:开头就可以了。
清单14-2显示的是一个非常简单的XSL样式单,它有两个模板规则。第一个模板规则与根元素PFRIODIC_TABLE相匹配,它使用html元素来代替此元素。html元素的内容是将文档中的其他模板应用于PFRIODIC_TABLE元素中所获得的结果。
第二个模板与ATOM元素匹配,它用输出文档中的P元素代替输入文档中的每个ATOM元素。xsl:apply-templates规则将匹配的源元素的文本插入到输出文档中。因此,P元素的内容将是包含在相应的ATOM元素中的文本(但不是标记)。下面,将更进一步讨论这些元素的精确语法。
清单14-2:有两个模板规则的周期表XSL样式单
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/XSL/Transform/1.0">
<xsl:template match="PERIODIC_TABLE">
<html>
<xsl:apply-templates/>
</html>
</xsl:template>
<xsl:template match="ATOM">
<P>
<xsl:apply templates/>
</P>
</xsl:template>
</xsl:stylesheet>
14.2.3 在何处进行XML变换
使用XSL样式单可有三种主要方式将XML文档变换成其他格式(如HTML):
1.XML文档和相关的样式单都是用于客户端(Web浏览器)的,然后客户端程序按照样式单中指定的格式变换文档,并将它呈现给用户。
2.服务器将XSL样式单应用于XML文档,以便此文档能够变换成其他某种格式(通常为HTML),并把变换后的文档发送到客户端程序(Web浏览器)。
3.第三个程序将原XML文档变换成其他某种格式(常常为HTML)后,才把此文档放置在服务器上。服务器和客户程序只处理变换后的文档。
这三种方法尽管都使用相同的XML文档和XSL样式,但每一种都使用不同的软件。将XML文档发送到Internet Explorer 5.0的普通Web服务器使用的就是第一种方法。使用IBM alphaWork的XML功能与服务小程序兼容的Web服务器就是第二种方法的例证。使用命令行XT程序来将XML文档变换成HTML文档,然后将HTML文档放置在Web服务器上,采用的就是第三种方法。但是,这些方法都使用(至少在理论上是如此)相同的XSL语言。
本章中,我将重点介绍第三种方法,其主要原因是在撰写本书时,像James Clark的XT或IBM的LotusXSL这样的专用转换程序能够最完善、最精确地实现目前的XSL规范。此外,该方法提供了与先前的Web浏览器和服务器的最广泛的兼容性,而第一种方法要求浏览器比大多数用户使用的更新;第二种方法要求专门的Web服务器软件。但是,实际上,要求不同的服务器比要求特定客户来得简单。因为可以安装自己的专门服务器软件,但不能要求用户都安装特定的客户软件。
14.2.4 如何使用XT
XT是Java 1.1的字符模式的应用程序。要使用它,需要安装与Java 1.1兼容的虚拟机,如Sun的Java开发包(Java Development Kit,JDK)或Java的运行时环境(Java Runtime Environment,JRE)、Apple的Macintosh Runtime for Java 2.1(MRJ)或Microsoft的虚拟机。还需要安装符合SAX的XML分析程序,如James Clark的XP,这也是一个Java应用程序。
 在撰写本书时,可在http://www.jclark.com/xml/xt.html站点上找到XT程序,而在访问http://www.jclark.com/xml/xp/index.html处找到.XP程序。当然,这些URL都随时间可能发生变化。甚至无法担保在你读到此书时XT就能存在。但是,尽管我在本章中使用XT,但使用任何XSL处理程序(执行1999年4月21日制定的XSL规范工作草案的树形结构部分)时,这些实例都能运行。另外的可能性是IBM alphaWork的LotusXSL(可在http://www.a1phaworks.ibm.com/tech/LotusXSL处得到)。当使用执行XSL近期草案标准的软件时,这些例子可能运行,也可能不运行,尽管我希望这些例子更接近于近期标准。我将在我自己的Web站点(http://metalab.unc.edu/xml/books/bible/)上发布任何更新内容。
含有XT main方法的Java类是com.jclark.xsl.sax.Driver。假设Java的CLASSPATH环境变量包括xt.jar和sax.jar文件(这两个文件在XT发行版中),那么在命令解释程序的提示符或DOS窗口中键入下面的代码,即可运行XT:
C:\>java
-Dcom.jclark.xsl.sax.parser=com.jclark.xml.sax.CommentDriver
com.jclark.xsl.sax.Driver 14-1.xml 14-2.xsl 14-3.html
这一命令行运行java解释程序,将com.jclark.xsl.sax.parser Java的环境变量设置为com.jclark.xml.sax.CommentDriver,后者表示用于解析输入文档的Java类的完整名称。此类必须在类路径中。此处我使用XP语法分析器,但任何符合SAX的语法分析器都可以做到。接下来就是含有XT程序的main()方法的Java类名称(com.jclark.xsl.sax.Driver)。最后,是输入XML文档(14-1.xml)、输入XSL样式单(14-2.xsl)和输出的HTML文件(14-3.html)的名称。如果忽略最后一个参数,那么变换后的文档将打印在控制台上。
如果正在使用Windows,并已安装了Microsoft Java虚拟机,就可以使用XT的单机可执行版。这样,由于它包括XP语法分析器,并且不要求提供CLASSPATH环境变量,所以使用起来就稍微容易一些。对于本程序,可简单地将xt.exe文件放置在自己的路径中,并键入下列句子:
C:\> xt 14-1.xml 14-2.xsl 14-3.html
清单14-2像第6章讨论过的那样将输入文档变换成结构整洁的HTML文件。但是,只要编写的样式单支持这种变换,则可从任何XML应用程序变换到其他应用程序。例如,可以设想有这样的一个样式单,它把VML文档变换到SVG文档:
% java
-Dcom.jclark.xsl.sax.parser=com.jclark.xml.sax.CommentDriver
com.jclark.xsl.sax.Driver pinktriangle.vml
VmlToSVG.xsl -out pinktriangle.svg
当然,尽管其他大多数命令行XSL处理程序具有不同的命令行参数和选项,但它们的表现形式相似。如果这些程序不是用Java来编写,由于不需要配置CLASSPATH,使用起来可能稍微容易些。
清单14-3显示的是通过XT使用清单14-2中的XSL样式单来运行清单14-1时的输出结果。请注意,XT并不简化它所产生的具有许多空白的HTML。但这并不重要,因为最终是在Web浏览器中浏览此文件,而Web浏览器又会将空白截去。图14-2显示的是加载到Netscape Navigator 4.5中的清单14-3。由于清单14-3显示标准的HTML,所以不需要具有XML功能的浏览器来浏览此文档。
清单14-3:将清单14-2中的样式单应用于清单14-1中的XML后产生的HTML
<html>
<P>
Hydrogen
H
1
1.00794
20.28
13.81
0.0899
</P>
<P>
Helium
He
2
4.0026
4.216
0.95
0.1785
</P>
</html>
 
图14-2 将清单14-2中的XSL样式单应用于清单14-1中的XML而生成的页面
14.2.5 直接显示带有XSL样式单的XML文件
无需预处理XML文件,就可以向客户端发送XML文件和描述如何显示此文件的XSL文件。客户程序负责将样式单应用于文档,并按照要求加以显示。这种情况要求客户端所做的工作更多,但服务器的负载要小得多。在这种情况下,XSL样式单必须将文档变换成客户端能够理解的XML应用程序。尽管将来有些浏览器很可能能够处理XSL格式化对象,但HTML是很有希望的选择方案。
将XSL样式单与XML文档相链接是很容易的,只需要紧跟在XML声明之后插入序言中的xml-stylesheet处理指令。这种处理指令应有text/xsl值的type特性,以及其值为指向此样式单的URL的href特性。例如:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="14-2.xsl"?>
这也是将CSS样式单与文档链接的方法。这里的唯一区别是type特性具有text/xsl值,而不是text/css值。
Internet Explorer 5.0的XSL支持在许多方面与1999年4月21日制定的草案有差异。首先,它期望XSL元素放在http://www.w3.org/TR/WD-xsl命名域中,而不是http://www.w3.org/XSL/Transform/1.0命名域,尽管xsl前缀仍然使用。其次,当元素不与任何模板相匹配时,并不执行此元素的缺省规则。因此,在Internet Explorer中浏览文档时,需要从根元素开始为分级结构中的每个元素提供一个模板。清单14-4显示了这种情况。三条规则依次与根节点、根元素PERIODIC_TABLE和ATOM相匹配。图14-3显示的是使用此样式单将清单14-1加载到Internet Explorer 5.0中之后的XML文档。
清单14-4:将清单14-2调整为可在Internet Explorer 5.0下运行的样式单
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl">
<xsl:template match="/">
<html>
<xsl:apply-templates/>
</html>
</xsl:template>
<xsl:template match="PERIODIC_TABLE">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="ATOM">
<P>
<xsl:value-of select="."/>
</P>
</xsl:template>
</xsl:stylesheet>
 
 
图14-3 将清单14-4中调整过的XSL样式单应用于清单14-1中的XML文档, 在Internet Explorer 5.0中生成的页面
理想的情况是,相同的XML文档既可用于直接显示也可以预处理成HTML。不幸的是,XT不接受http://www.w3.org/TR/WD-xsl命名域,而IE 5不接受http://www.w3.org/XSL/Transform/1.0命名域。 由于不同的处理程序在对改进的XSL规范各部分的支持方面起到各有所长的作用,所以我们仍然处于这种痛苦的境地。
在本章剩下来的部分,在将文档装入浏览器之前,我将把它简单地预处理成HTML格式。
 
14.3 XSL模板
由xsl:template元素定义的模板规则是XSL样式单的最重要的部分。每个模板规则都是一个xsl:template元素。这些规则将特定的输出与特定的输入相关联。每个xsl:template元素都有一个match特性,用来指定要将此模板应用于输入文档的哪个节点。
xsl:template元素的内容是要运用的实际模板。模板可能既包含逐字显示在输出文档中的文本,同时也包含从输入XML文档将数据复制到结果的XSL指令。因为所有的XSL指令都放在xsl命名域中(即它们都是以xsl:开头),所以要区分元素(这些元素就是复制到输出的实际数据)和XSL指令是很容易的。例如,下面为一个应用于输入树形结构根节点的模板:
<xsl:template match=”/”>
<html>
<head>
</head>
<body>
</body>
</html>
</xsl:template>
当XSL处理程序读取此输入文档时,它所看到的第一个节点就是根节点。下面的规则与此根节点相匹配,并告诉XSL处理程序发送此文本:
<html>
<head>
</head>
<body>
</body>
</html>
这种文本就是结构整洁的HTML。由于XSL文档本身就是XML文档,所以其内容(包括模板在内)也必须是结构整洁的XML。
如果要在XSL样式单中使用上面的规则,并且只在XSL样式单中使用的话,那么输出就只限于上面的六个标记。(实际上,可压缩为四个等价的标记:<html> <head/> <body/> </html>)。这是由于在规则中没有任何指令告诉格式化程序沿树形结构逐级下移,以便寻找与样式单中的模板进一步的匹配。
14.3.1 xsl:apply-templates元素
要达到根节点以外的地方,就要告诉格式化引擎处理根节点的子节点。一般来说,为了包括子节点中的内容,需递归处理整个XML文档中的节点。可以用来达到此目的的元素就是xsl:apply-templates。把xsl:apply-templates放在输出模板中,就可以告诉格式化程序把与源元素匹配的每个子元素同样式单中的模板相比较。用于匹配节点的模板本身可能包含xsl:apply-templates元素,以便搜索与其子节点的匹配。当格式化引擎处理节点时,此节点是作为整个树形结构来看待的。这是树形结构的优点所在。每个部分都是以处理整体相同的方式来处理。例如,清单14-5就是使用xsl:apply-templates元素来处理子节点的XSL样式单。
清单14-5:递归处理根下子节点的XSL样式单
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/XSL/Transform/1.0">
<xsl:template match="/">
<html>
<xsl:apply-templates/>
</html>
</xsl:template>
<xsl:template match="PERIODIC_TABLE">
<body>
<xsl:apply-templates/>
</body>
</xsl:template>
<xsl:template match="ATOM">
An Atom
</xsl:template>
</xsl:stylesheet>
当本样式单应用于清单14-1时,将进行以下处理:
1.将根节点与样式单中的所有模板规则进行比较,它与第一个模板规则相匹配。
2.写出<html>标记。
3.xsl:apply-templates元素使格式化引擎处理子节点。
A.将根节点的第一个子节点(xml-stylesheet指令)与模板规则相比较,此子节点与任何一个模板规则都不匹配,所以不产生任何输出。
B.将根节点的第二个子节点(根元素PERIODIC_TABLE)与模板规则相比较,此子节点与第二个模板规则相匹配。
C.写出<body>标记。
D.body元素中的xsl:apply-templates元素使格式化引擎处理PERIODIC_TABLE的子节点。
a.将PERIODIC_TABLE元素的第一个子元素(即氢的ATOM元素)与模板规则进行比较,此子元素与第三个模板规则相匹配。
b.输出文本An Atom。
c.将PERIODIC_TABLE元素的第二个子元素(即氦的ATOM元素)与模板规则进行比较,此子元素与第三个模板规则相匹配。
d.输出文本An Atom。
E.写出</body>标记。
4.写出</html>标记。
5.处理完成。
最后的结果为:
<html><body>
An Atom
 
An Atom
</body></html>
14.3.2 select特性
为了用ATOM元素的名称(由其NAME子元素给出)来代替An Atom文本,需要指定模板应用于ATOM元素的NAME子元素。为了选择一组特定的子元素,而不是所有的子元素,可向xsl:apply-templates提供select特性,用来指定要选择的子元素。见下面的例子:
<xsl:template match=”ATOM”>
<xsl:apply-templates select=”NAME”/>
</xsl:template>
select特性使用同一类型的模式作为xsl:template元素的match特性。目前,我们坚持使用简单的元素名称;但本章后面有关匹配和选择模式的部分,将讨论select和match更多的可能用法,如果不存在select特性,那么选择所有的子元素。
将上面的规则加到清单14-5的样式单,并应用于清单14-5,其结果如下:
<html><head/><body>
Hydrogen
Helium
</body></html>
 
14.4 使用xsl:value-of来计算节点值
xsl:value-of元素把输入文档中的节点值复制到输出文档中。xsl:value-of 元素的select特性指定正在获取的是哪个节点值。
例如,假设要将文字An Atom代替为由NAME子元素内容给出的ATOM元素的名称,可用<xsl:value-of select="NAME"/>代替An Atom,如下所示:
<xsl:template match="ATOM">
<xsl:value of select="NAME"/> 
</xsl:template>
然后,当将样式单应用于清单14-1时,产生如下文本:
<html><head/><body>
Hydrogen
Helium
</body></html>
选择其值的项目(本例中的NAME元素)是与源节点有关的。源节点是由模板来匹配的项目(本例中的特指ATOM元素)。因此,当氢的ATOM与<xsl:template match= "ATOM">相匹配,氢的ATOM的NAME元素就由xsl:value-of选定了。当氦的ATOM与<xsl:template match= "ATOM">相匹配时,氦的ATOM的NAME元素就由xsl:value-of选定了。
节点的值总是字符串,有时可能为空字符串。此字符串的精确内容由节点的类型而定。最普通的节点类型为元素,元素节点的值特别简单,就是在元素的开始标记和结束标记之间的所有可析字符数据(但不是标记!)。例如,清单14-1中的第一个ATOM元素如下所示:
<ATOM STATE="GAS">
<NAME>Hydrogen</NAME>
<SYMBOL>H</SYMBOL>
<ATOMIC_NUMBER>l</ATOMIC_NUMBER>
<ATOMIC_WEIGHT>1.00794</ATOMIC_WEIGHT>
<OXIDATION_STATES>1</OXIDATION_STATES>
<BOILING_POINT UNITS="Kelvin">20.28</BOILING_POINT>
<MELTING_POINT UNITS="Kelvin">13.81</MELTING_POINT>
<DENSITY UNITS="grams/cubic centimeter"><!- At 300K ->
0.0899
</DENSITY>
</ATOM>
元素的值显示如下:
Hydrogen
H
1
1.00794
1
20.28
13.81
0.0899
我通过删除所有的标记和注释后计算出了这些值。包括空格在内的其他一切内容都完整无缺地保留着。其他六个节点类型的值也可以类似的非常明显的方式加以计算。表14-1总结了这些值的结果。
表14-1 节点值
节点类型  值 
根节点  根元素的值 
元素  包括在元素中的所有可析的字符数据(包括元素的任何后代中的字符数据) 
文本  节点的文本;实际上为节点本身 
特性  标准化的特性值(详细说明见XML 1.0推荐的第3.3.3节);主要为实体还原后的特性值,截去前导和后随的空格;不包括特性名、等号或引号 
命名域  用于命名域的URL 
处理指令  处理指令的值;不包括<?或?>以及处理指令名 
注释  注释文本,不包括<!--和-—>  
14.5 使用xsl:for-each处理多个元素
xsl:value-of元素只用于能够不含糊地确定要获取哪个节点值的上下文中。如果有多个可能项可供选择,那么只选择第一项。例如,由于普通的PERIODIC_TABLE元素包含一个以上的ATOM,所以下列的规则较差:
<xsl:template match="PERIODIC_TABLE">
<xsl:value-of select="ATOM"/>
</xsl:template>
有两种方法可依次处理多个元素。第一种方法已经看到了。只需要按下列方式与select特性(它选择想要包括的特定元素)一起使用xsl:apply-templates:
<xsl:template match="PERIODIC_TABLE">
<xsl:apply-templates select="ATOM"/>
</xsl:template>
<xsl:template match="ATOM">
<xsl:value-of select="."/>
</xsl:template>
第二个模板中的select="."告诉格式化程序取匹配的元素(本例中的ATOM)的值。
第二种方法是使用xsl:for-each。xsl:for-each元素依次处理由其select特性选择的每个元素。不过,无需任何附加的模板。例如:
<xsl:template match="PERIODIC_TABLE">
<xsl:for-each select="ATOM">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
如果省略select特性,那么处理源节点(本例中的PERIODIC_TABLE)的所有子节点。
<xsl:template match="PERIODIC_TABLE">
<xsl:for-each>
<xsl:value-of select="ATOM"/>
</xsl:for-each>
</xsl:template>
 
14.6 匹配节点的模式
xsl:template元素的match特性支持复杂的语法,允许人们精确地表达想要和不想要与哪个节点匹配。xsl:apply-templates、xsl:value-of、xsl:for-each、xsl:copy-of和xsl:sort的select特性支持功能更加强大的语法的超集,允许人们精确地表达想要和不想要选择哪个节点。下面讨论匹配和选择节点的各种模式。
14.6.1 匹配根节点
为了使输出的文档结构整洁。从XSL变换的第一个输出内容应为输出文档的根元素。因此,XSL样式单一般以应用于根节点的规则开始。要在规则中指定根节点,可将其match特性设置为合适的值。例如:
<xsl:template match="/">
<html>
<xsl:apply-templates/>
</html>
</xsl:template>
本规则应用于根节点,并且只应用于输入树形结构的根节点。当读取到此根节点时,就输出<html>标记,处理根节点的子节点,然后输出</html>标记。本规则推翻了根节点的缺省规则。清单14-6显示了应用于根节点的带有单一规则的样式单。
清单14-6:用于根节点的带有单一规则的XSL样式单
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/XSL/Transform/1.0">
<xsl:template match="/">
<html>
<head>
<title>Atomic Number vs. Atomic Weight</title>
</head>
<body>
<table>
Atom data will go here
< /table>
</body>
</html >
</xsl:template>
</xsl:stylesheet>
由于本样式单只为根节点提供一条规则,并且由于规则的模板未指明对子节点进行进一步的处理,因而只是按原样输出,所以在模板中所看到的所有内容都将插入到结果文档中。换句话说,将清单14-6中的样式单应用于清单14-1(或其他任何结构整洁的XML文档)中,所获得的结果如下:
<html><head><title>Atomic Number vs. Atomic
Weight</title></head><body><table>
Atom data will go here
</table></body></html>
14.6.2 匹配元素名
正如前面介绍的那样,最基本的模式只包含一个元素名,用来匹配所有带有该名的元素。例如,下面的模板与ATOM元素相匹配,并将ATOM元素的ATOMIC_NUMBER的子元素标成粗体:
<xsl:template match=”ATOM”>
<b><xsl:value-of select=”ATOMIC_NUMBER”/><b>
</xsl:template>
清单14-7显示的是扩充了清单14-6的样式单。首先,在根节点的规则模板中包括了xsl:apply-templates元素。此规则使用select特性来确保只有PERIODIC_TABLE元素获得处理。
其次,使用match=“PERIODIC_TABLE”语句创建了只适用于PERIODIC_TABLE元素的规则。本规则设置周期表的标题,然后应用模板来从ATOM元素中生成周期表的主体。
最后,ATOM规则使用<xsl:apply-templates select=“NAME”/>、<xsl:apply-templates select=“ATOMIC_NUMBER”/>和<xsl:apply templates select=“ATOMIC_WEIGHT”/>,明确地选择ATOM元素的NAME、ATOMIC_NUMBER和ATOMIC_WEIGHT子元素。它们都包装在HTML的tr和td元素中,以便最终的结果是与原子量相匹配的原子序数表。图14-4显示将清单14-7中的样式单应用于整个周期表文档中的输出结果。
对本样式单需要注意的是:在输入文档中的NAME、ATOMIC_NUMBER和ATOMIC_WEIGHT元素的精确顺序是不重要的。它们在输出文档中以选择它们的顺序出现,也就是说首先为原子序数,然后是原子量。相反,在输入文档中,各个原子依字母顺序排序。以后,将会看到如何使用xsl:sort元素来改变这个顺序,以便使用更常规的原子序数的顺序来排列原子。
清单14-7:利用select的施用于元素的特定类的模板
<?xml version=”1.0”?>
<xsl:stylesheet
xmlns:xsl=”http://www.w3.org/XSL/Transform/1.0”>
<xsl:template match=”/”>
<html>
<head>
<title>Atomic Number vs. Atomic Weight</title>
</head>
<body>
<xsl:apply-templates select=”PERIODIC_TABLE”/>
</body>
</html>
</xsl:template>
<xsl:template match=”PERIODIC_TABLE”>
<hl>Atomic Number vs. Atomic Weight</hl>
<table>
<th>Element</th>
<th>Atomic Number</th>
<th>Atomic Weight</th>
<xsl:apply-templates select=”ATOM”/>
</table>
</xsl:template>
<xsl:template match=”ATOM”>
<tr>
<td><xsl:value-of select=”NAME”/></td>
<td><xsl:value-of select=”ATOMIC_NUMBER”/></td>
<td><xsl:value-of select=”ATOMIC_WEIGHT”/></td>
</tr>
</xsl:template>
</xsl:stylesheet>
 
图14-4 Netscape Navigator 4.5中显示的原子序数与原子量的关系表
14.6.3 使用/字符匹配子节点
在match特性中并不局限于当前节点的子节点,可使用/符号来匹配指定的元素后代。当单独使用/符号时,它表示引用根节点。但是,在两个名称之间使用此符号时,表示第二个是第一个的子代。例如,ATOM/NAME引用NAME元素,NAME元素为ATOM元素的子元素。
在xsl:template元素中,这种方法能够用来只与某些给定类型的元素进行匹配。例如,下面的模板规则将ATOM子元素的SYMBOL元素标记为strong。此规则与不是ATOM元素的直系子元素的SYMBOL元素无关。
<xsl:template match="ATOM/SYMBOL">
<strong><xsl:value-of select="."/></strong>
</xsl:template>
请记住,本规则选择的是作为ATOM元素子元素的SYMBOL元素,而不是选择拥有SYMBOL子元素的ATOM元素。换句话说,在<xsl:value-of select="."/>中的.符号引用的是SYMBOL,而不是ATOM。
 
将模式写成一行的形成,就可以指定更深层的匹配。例如,PERIODIC_TABLE / ATOM / NAME选择的是其父为ATOM元素(其父为PERIODIC_TABLE元素)的NAME元素。
还可以使用*通配符来代替层次结构中的任意元素名。例如,下面的模板规则应用于PERIODIC_TABLE孙元素的所有SYMBOL元素。
<xsl:template match="PERIODIC_TABLE/*/SYMBOL">
<strong><xsl:value-of select="."/></strong>
</xsl:template>
最后一点,就如上面所看到的那样,单独的/本身,表示选择文档的根节点。例如,下面的规则应用于文档根元素的所有PERIODIC_TABLE元素。
<xsl:template match="/PERIODIC_TABLE">
<html><xsl:apply templates/></html>
</xsl:template>
虽然 / 引用根节点,但/* 则引用任意根元素。例如,
<xsl:template match="/*">
<html>
<head>
<title>Atomic Number vs. Atomic Weight</title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
14.6.4 使用//符号匹配子代
有时候,尤其是使用不规则的层次时,更容易的方法就是越过中间节点、只选择给定类型的所有元素而不管这些元素是不是直系子、孙、重孙或其他所有的元素。双斜杠(//)引用任意级别的后代元素。例如,下面的模板规则应用于PERIODIC_TABLE的所有NAME子代,而不管它们具有何种层次的关系:
<xsl:template match=" PERIODIC_TABLE //NAME">
<i><xsl:value-of select="."/></i>
</xsl:template>
周期表实例相当简单,一看就懂,但这种技巧在更深层次,尤其是当元素包含该类的其他元素时(例如ATOM包含ATOM),就显得更加重要。
模式开头的操作符选择根节点的任何子节点。例如,下面的模板规则处理所有的ATOMIC_NUMBER元素,而同时完全忽略其位置:
<xsl:template match="// ATOMIC_NUMBER ">
<i><xsl:value-of select="."/></i>
</xsl:template>
14.6.5 通过ID匹配
有人或许想把一特定的样式应用于特定的单一元素中,而不改变该类型的所有其他元素。在XSL中实现此目的的最简单的方法是,将样式与元素的ID匦韵喙亓?墒褂胕d()选择符(其中包括以单引号括起来的ID值)做到这一点。例如,下面的规则使带有ID值为e47的元素变为粗体:
<xsl:template match=”id(‘e47’)”>
<b><xsl:value-of select=”.”/></b> 
</xsl:template>
当然,上面假设以此方式选择的元素具有在源文档的DTD中声明为ID类型的特性。但是,通常情况并非如此。首先,许多文档没有DTD,只不过结构整洁,但不合法。即使有DTD,也无法确保任何元素都有ID类型的特性。可以在样式单中使用xsl:key元素,用来把输入文档中的特定特性声明为应该作为ID来看待。
14.6.6 使用@来匹配特性
正如第5章已经看到的那样,@符号根据特性名与特性相匹配,并选择节点。方法很简单,只需在要选择的特性前加上@符号。例如,清单14-8显示一样式单,用它来输出一张原子序数和熔点对照的表格。不仅写出了MELTING_POINT的值,而且也写出了UNITS特性的值。这是由于<xsl :value-of select="@UNITS"/>所获得的结果。
清单14-8:使用@来选择UNITS特性的XSL样式单
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/XSL/Transform/1.0">
<xsl:template match="/PERIODIC_TABLE">
<html>
<body>
<hl>Atomic Number vs. Melting Point</hl>
<table>
<th>Element</th>
<th>Atomic Number</th>
<th>Melting Point</th>
<xsl:apply-templates/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="ATOM">
<tr>
<td><xsl:value-of select="NAME"/></td>
<td><xsl:value-of select="ATOMIC_NUMBER"/></td>
<td><xsl:apply-templates select="MELTING_POINT"/></td>
</tr>
</xsl:template>
<xsl:template match="MELTING_POINT">
<xsl:value-of select="." />
<xsl:value-of select="@UNITS"/>
</xsl:template>
</xsl:stylesheet>
回想一下,特性节点的值只是此特性的字符串值。一旦应用清单14-8中的样式单,ATOM元素就会格式化成如下形成:
<tr><td>Hydrogen</td><td>l</td><td>13.8lKelvin</td></tr>
 
<tr><td>Helium</td><td>2</td><td>0.95Kelvin</td></tr>
可以使用各种层次操作符将特性与元素组合起来。例如,BOILING_POINT/@UNITS引用BOILING_POINT元素的UNITS特性。ATOM/*/@UNITS就能匹配ATOM子元素的任何UNITS元素。当与模板规则中的特性匹配时,这种做法是特别有用的。必须记住,要匹配的是特性节点,而不是包含它的元素。最常见的错误是,不知不觉地将特性节点与包含它的元素节点搞混淆。例如,请看下面的规则,它试图将模板应用于具有UNITS特性的所有子元素:
<xsl:template match="ATOM">
<xsl:apply-templates select="@UNITS"/>
</xsl:template>
上面语句实际上做的是,将模板应用于ATOM元素中并不存在的UNITS特性。
也可以使用*来选择元素的所有特性,例如,BOILING_POINT/@*可选择BOILING_POINT元素的所有特性。
14.6.7 使用comments()来匹配注释
大多数时候,可能应该完全忽略XML文档中的注释。要使注释成为文档的必不可少的部分,确实不是好主意。但是,当不得不选择注释时,XSL确实提供了选择注释的手段。
为了选择注释,可使用comment()模式。尽管此模式有类似函数的圆括号,但实际上决不带任何参数。要区分不同的注释不太容易。例如,回想一下DENSITY元素具有如下的形式:
<DENSITY UNITS=”grams/cubic centimeter”><!- At 300K ->
6.51
</DENSITY>
此模板规则不仅输出密度的值和单位,而且还打印测量密度的条件:
<xsl:template match=”DENSITY”>
<xsl:value-of select=”.”/>
<xsl:value-of select=”@UNITS”/>
<xsl:apply-templates select=”comment()”/>
</xsl:template>
清单14-1使用注释而不是特性或元素来指定条件,就是为了用于本例。实际应用时,决不要将重要信息放在注释中。XSL允许人们选择注释的唯一真实的理由是,为了用样式单把一种标记语言变换成另一种标记语言,同时又能使注释保持不变。选择注释的任何其他方面的用途都意味着原文档设计得不好。下面的规则匹配所有的注释,并使用xsl:comment元素将它们再次复制出来。
<xsl:template match=”comment()”>
<xsl:comment><xsl:value-of select=”.”/></xsl:comment>
</xsl:template>
可是,要注意,用于施加模板的缺省规则对注释无效。因此,遇到注释时,如果要使缺省规则起作用,需要包括xsl:apply-templates元素,无论注释放在何处,此元素都能选择注释。
使用层次操作符可以选择特定的注释。例如,下面的规则匹配DENSITY元素内部的注释:
<xsl:template match=”DENSITY/comment()”>
<xsl:comment><xsl:value-of select=”.” /></xsl:comment>
</xsl:template>
14.6.8 使用pi()来匹配处理指令
谈到编写结构化的、智能化的、可维护的XML时,处理指令并不比注释好。但是都有一些必需的应用,其中包括将样式单附加到文档上。
pi()函数选择处理指令。pi()的参数是放在引号内的字符串,表示要选择的处理指令的名称。如果没有参数,则匹配当前节点的第一个处理指令子节点。但是,可以使用层次操作符。例如,下面的规则匹配根节点的第一个处理指令子节点(很可能是xml-stylesheet处理指令)。xsl:pi元素使用指定的名称和输出文档中的值来插入一个处理指令。
<xsl:template match="/pi()">
<xsl:pi name="xml-stylesheet">
type="text/xsl" value="auto.xsl"
</xsl:pi>
</xsl:template/>
下列规则也匹配xml-stylesheet处理指令,但是通过其名称来匹配的:
<xsl:template match="pi( xml-stylesheet )">
<xsl:pi name="xml-stylesheet">
<xsl:value-of select="."/>
</xsl:pi>
</xsl:template/>
事实上,区分根元素和根节点的主要原因之一就是,为了读取和处理序言中的处理指令。尽管xml-stylesheet处理指令使用“名称=值”这样的句法,但XSL并不把它们当做特性看待,这是因为处理指令不是元素。处理指令的值只是跟在其名称后面的空格和结束符?>之间的所有内容。
用来施加模板的缺省规则并不匹配处理指令。因此,遇到xml-stylesheet处理指令时,如果要使缺省规则起作用,需要包括xsl:apply-templates元素,此元素在适当的地方匹配缺省规则。例如,下面这个用于根节点的模板确实将模板应用于处理指令:
<xsl:template match="/">
<xsl:apply-templates select="pi()"/>
<xsl:apply-templates select="*"/>
</xsl:template>
14.6.9 用text()来匹配文本节点
尽管文本节点的值包括在选择的元素值部分中,但它们作为节点通常被忽视。但是,text()操作符确实能够明确选择一个元素的文本子元素。尽管这种操作符有圆括号,但不需要任何参数。例如:
<xsl:template match="SYMBOL">
<xsl:value-of select="text()"/>
</xsl:template>
此操作符存在的主要原因是为了用于缺省规则。无论作者是否指定缺省规则,XSL处理程序必须提供下列的缺省规则:
<xsl:template match="text()">
<xsl:value-of select="."/>
</xsl:template>
这意味着无论何时将模板应用于文本节点,就会输出此节点的文本。如果并不需要这种缺省行为,可以将其推翻。例如,在样式单中,包括下列空模板规则,将会阻止输出文本节点,除非另外的规则明确地匹配。
<xsl:template rnatch="text()">
</xsl:template>
14.6.10 使用“或”操作符|
竖线(|)允许一条模板规则匹配多种模式。如果节点与某种模式相匹配,则此节点将激活该模板。例如,下面模板规则与ATOMIC_NUMBER和ATOMIC_WEIGHT元素都匹配:
<xsl:template match=”ATOMIC_NUMBER|ATOMIC_WEIGHT”>
<B><xsl:apply-templates/></B>
</xsl:template>
也可以在|两边加入空格,这样使代码更清晰。例如:
<xsl:template match=”ATOMIC_NUMBER | ATOMIC_WEIGHT”>
<B><xsl:apply-templates/></B>
</xsl:template>
还可以顺次使用两个以上的模式。例如,下面的模板规则作用于A
 
14.7 选择节点的表达式
在xsl:apply-templates、xsl: value-of、xsl:for-each、xsl:copy-of和xsl:sort中,可使用select特性来精确指定对哪个节点进行操作。此特性值即为表达式(expression)。表达式是前节讨论的匹配模式的超集。也就是说,所有的匹配模式都是选择表达式,但并非所有的选择表达式都是匹配模式。让我们来回顾一下,匹配模式能够使用元素名、子元素、后代以及特性来匹配节点,除此之外,还可以对这些项目进行简单的测试。选择表达式可以使用所有的这些条件来选择节点,而且还可以通过参考父元素、同属元素来选择节点,以及通过更加复杂的测试来选择节点。此外,表达式并不局限于只生成一组节点列表,而且还产生布尔值、数值和字符串。
14.7.1 节点轴
表达式并不局限于指定当前节点的子节点和后代节点。XSL提供许多轴(axe),使用这些轴可以从相对于当前节点(通常为模板匹配的节点)的树形结构的不同部分进行选择。表14-2概述了这些轴及其含义。
 
表14-2 表达式的轴
轴  选自于 
from-ancestors()  当前节点的父节点、当前节点的父节点的父节点、当前节点的父节点的父节点的父节点,依次类推至根节点 
from-ancestors-or-self()  当前节点的后代以及当前节点本身 
from-attributes()  当前节点的特性 
from-children()  当前节点的直系子节点 
from-descendants()  当前节点的子节点、当前节点的子节点的子节点,依次类推 
from-descendants-or-self()  当前节点本身及其后代节点 
from-following()  起始于当前节点末尾之后的所有节点 
from-following-siblings()  起始于当前节点末尾之后并且与当前节点具有同一个父节点的所有节点 
from-parent()  当前节点的单一父节点 
from-preceding()  当前节点开始之前开始的所有节点 
from-preceding- siblings()  当前节点开始之前开始的所有节点并且与当前节点具有同一个父节点的所有节点 
from-self()  当前节点 
from-following和from-preceding轴不可靠,可能不会包括在XSL的最终发布版中。如果包括在XSL的最终发布版中,其准确的含义可能会改变。
  
这些轴的功能是选择表14-2第二列中列出的节点。圆括号中包括要进一步对此节点列表筛选的选择表达式。例如,可能包括由下列模板规则选择的元素名称:
<xsl:template match="ATOM">
<tr>
<td>
<xsl:value-of select="from-children(NAME)"/)
</td>
<td>
<xsl:value-of select="from-children(ATOMIC_NUMBER)"/>
</td>
<td>
<xsl:value-of select="from-children(ATOMIC_WEIGHT)"/>
</td>
</tr>
</xsl:template>
此模板规则匹配ATOM元素。当ATOM元素匹配时,NAME元素、ATOMIC_NUMBER元素和ATOMIC_WEIGHT都从此匹配的ATOM元素的子元素中选择,并作为表的单元格输出。(如果有多个期待的元素&#0;&#0;例如,三个NAME元素&#0;&#0;那么,只选择第一个。)
from-children()轴不允许做单独使用元素名不能做的任何事情。实际上,select="ATOMIC_WEIGHT"只是select = "from-children (ATOMIC_WEIGHT)”的缩写形式。但是,其他轴更令人感兴趣。
在匹配模式时引用父元素是不合法的,但在选择表达式中引用则是合法。要引用父元素,可使用from-parent()轴。例如,下面的规则输出具有BOILING_POINT子元素的ATOM元素的值:
<xsl:template match="ATOM/BOILING_POINT">
<P><xsl:value-of select="from-parent(ATOM)"/></P>
</xsl:template>
这里匹配的是BOILING_POINT子元素,但输出的是ATOM父元素。
有些放射性原子(如钋)其半衰期是如此之短,以致无法测量重要的性质(如沸点和熔点)。所以并非所有的ATOM元素都必须有BOILING_POINT子元素。上面的规则可用来只输出实际上有沸点的元素。清单14-10是此例的扩展,它匹配BOILING_POINT元素,但实际上使用from-parent(ATOM)输出ATOM父元素。
清单14-10:只输出有已知熔点的元素的样式单
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/XSL/Transform/1.0">
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="PERIODIC_TABLE"/>
</body>
</html>
</xsl:template>
<xsl:template match="PERIODIC_TABLE">
<hl>Elements with known Melting Points</hl>
<xsl:apply-templates select="//MELTING_POINT"/>
</xsl:template>
<xsl:template match="MELTING_POINT">
<P>
<xsl:value-of select="from-parent(ATOM)"/>
</P>
</xsl:template>
</xsl:stylesheet>
偶尔,可能需要选择给定类型元素的最近的祖先。使用from-ancestors()就可以做到这一点。例如,下面的规则插入最近的PERIODIC_TABLE元素(包含匹配的SYMBOL元素)的值。
<xsl:template match="SYMBOL">
<xsl:value-of select="from-ancestors(PERIODIC_TABLE)"/>
</xsl:template>
from-ancestors-or-self()函数的作用与from-ancestors()函数相似,所不同的是,如果当前节点与参数类型匹配,那么它的返回值为其本身,而不是真正的祖先。例如,下面的规则匹配所有元素。如果匹配的元素是PERIODIC_TABLE,那么在xsl:value-of中选择的正是PERIODIC_TABLE。
<xsl:template match="*">
<xsl:value-of select="from-ancestors-or-self(PERIODIC_TABLE)"/>
</xsl:template>
14.7.1.1 节点类型
from-axis()函数的参数,除了可以使用节点名称和通配符之外,还可以是下列四个节点类型函数之一:
· comment() 
· text() 
· pi() 
· node() 
comment()节点类型选择注释节点。text()节点类型选择文本节点。pi()节点类型选择处理指令节点,而node()节点类型选择任何类型的节点(*通配符只选择元素节点)。pi()节点类型还有一个可选的参数,用来指定要选择的处理指令的名称。
例如,下面的规则同时使用带有node()节点类型的from-self(),将匹配的ATOM元素封装在P元素中:
<xsl:template match="ATOM">
<P><xsl:value-of select="from-self(node())"/></P>
</xsl:template>
这里,选择from-self(node())与选择ATOM是不同的。下面的这个规则获取ATOM元素的ATOM子元素的值。此值不是匹配的ATOM元素的值,而是匹配ATOM元素的一个子元素的另一个ATOM元素值:
<xsl:template match="ATOM">
<P><xsl:value of select="ATOM"/></P>
</xsl:template>
14.7.1.2 层次操作符
可以使用/和//操作符来将选择表达式串联在一起。例如,清单14-11只将有熔点的那些元素的元素名、原子序数和熔点打印成表。要实现此目的,选择MELTING_POINT元素的父元素,然后使用select="from-parent(*)/from-children(NAME)"来查找父元素的NAME和ATOMIC_NUMBER子元素。
清单14-11:熔点与原子序数表
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/XSL/Transform/1.0">
<xsl:template match="/PERIODIC_TABLE">
<html>
<body>
<hl>Atomic Number vs. Melting Point</hl>
<table>
<th>Element</th>
<th>Atomic Number</th>
<th>Melting Point</th>
<xsl:apply-templates select "from-children(ATOM)"/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="ATOM">
<xsl:apply-templates
select="from-children(MELTING_POINT)"/>
</xsl:template>
<xsl:template match="MELTING_POINT">
<tr>
<td>
<xsl:value-of
select="from-parent(*)/from-children(NAME)"/>
</td>
<td>
<xsl:value-of
select="from-parent(*)/from-children(ATOMIC_NUMBER)"/>
</td>
<td>
<xsl:value-of select="from-self(*)"/>
<xsl:value-of select="from-attributes(UNITS)"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
这并不是解决这一问题的唯一方法。另外一种可能的方法是使用from-preceding-siblings()和from-following-siblings()轴或相对位置(前面或后面)不确定时同时使用两者。用于MELTING_POINT元素的必要模板规则如下所示:
<xsl:template match="MELTING_POINT">
<tr>
<td>
<xsl:value-of
select="from-preceding-siblings(NAME)
| from-following-siblings(NAME)"/>
</td>
<td>
<xsl:value-of
select="from-preceding-siblings(ATOMIC_NUMBER)
| from-following-siblings(ATOMIC_NUMBER)"/>
</td>
<td>
<xsl:value-of select="from-self(*)"/>
<xsl:value-of select="from-attributes(UNITS)"/>
</td>
</tr>
</xsl:template>
14.7.1.3 缩写句法
表14-2中的各种from-axis()函数对于轻松的打字工作来说过于冗长。XSL还定义了缩写句法,以便代替最常用的轴,在实际过程中使用更广。表14-3显示的是完整句法形式与缩写词的对等关系。
表14-3 选择表达式的缩写句法
缩写词  完整句法形式 
.  from-self(node()) 
..  from-parent(node()) 
Name  from-children(name) 
@name  from-attributes(name) 
//  /from-descendants-or-self(node())/ 
使用缩写句法重写清单14-11,得到清单14-12。但这两个样式单所获得的输出结果是完全一样的。
清单14-12:使用缩写句法获得的熔点和原子序数对照表
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/XSL/Transform/I.O">
<xsl:template match="/PERIODIC_TABLE">
<html>
<body>
<hl>Atomic Number vs. Melting Point</hl>
<table>
<th>Element</th>
<th>Atomic Number</th>
<th>Melting Point</th>
<xsl:apply-templates select="ATOM"/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="ATOM">
<xsl:apply-templates
select="MELTING_POINT"/>
</xsl:template>
<xsl:template match="MELTING_POINT">
<tr>
<td>
<xsl:value-of
select="../NAME"/>
</td>
<td>
<xsl:value-of
select="../ATOMIC_NUMBER"/>
</td>
<td>
<xsl:value-of select="."/>
<xsl:value-of select="@UNITS"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
匹配模式可以只使用缩写句法(并非使用所有的缩写句法)。对于选择表达式,只能使用表14-2中的from-axis()函数的完整句法形式。
14.7.2 表达式类型
每个表达式都计算出唯一的值。例如,表达式3+2运算值为5。上面所使用的表达式求出的都是节点集合。但是,在XSL中,有如下五种类型的表达式:
· 节点集合类型 
· 布尔类型 
· 数值类型 
· 字符串类型 
· 结果树形片段 
14.7.2.1 节点集合
节点集合(node set)是输入文档的一组节点的列表。表14-2中的from-axis()函数返回包含匹配节点的节点集合。哪些节点处于某一函数返回的节点集合中,这要根据当前节点(也可以认为是上下文节点)、函数的参数而定,当然也依赖于它是哪个函数。
习惯于面向对象语言(如Java和C++)的程序员可能将当前节点看作为调用函数的对象;也就是说,在a.doSomething(b, c)中,当前节点为a。但是,在XSL中,当前节点总是明确的;也就是说,按照定义a类的文件所规定的形式,更可能写成doSomething(b, c)形式。
例如当当前节点为例14-1中的PERIODIC_TABLE元素时,表达式select="from-children(ATOM)"返回的节点集合含有两个ATOM元素。当上下文节点为例14-1中的PERIODIC_TABLE元素时,表达式select="from-children(ATOM)/from-children(NAME)" 返回的节点集合含有<NAME> Hydrogen </NAME>和<NAME> Helium </NAME>两个元素节点。
上下文节点(context node)是上下文节点列表(context node list)的一个成员。上下文节点列表是同时都与同一个规则相匹配的元素集合,通常是xsl:apply-templates或xsl:for-each调用的结果。例如,当清单14-12应用于清单14-1时,ATOM模板调用两次,第一次用于氢原子,第二次用于氦原子。第一次调用时,上下文节点就是氢的ATOM元素。第二次调用时,上下文节点就是氦的ATOM元素。但是,在这两次调用中,上下文节点列表则是包含氢和氦的ATOM元素的集合。
表14-4列举了许多既可以作为参数,也可以作为上下文节点对节点集合进行操作的函数。
表14-4 对节点集合进行操作的函数
函数  返回值类型  返回值 
position()  数值  上下文节点列表中上下文节点的位置。列表中的第一个节点其位置为1 
last()  数值  上下文节点集合中的节点数 
count(node-set)  数值  在node-set参数指明的节点集合中的节点数 
id(string)  节点集合  节点集合,其中只有一个元素(在同一个文档的任何位置)其ID为string;或者空集合(如果任何元素都没有指定的ID) 
idref(node-set)  节点集合  节点集合,包括文档中的某些元素,其ID属性为在参数node-set中指明节点值中的(以空格分开)记号之一 
key(string name, string value)  节点集合  节点集合,包括文档中所有具有指定值的关键字的节点。关键字是使用顶层xsl:key元素来设置的 
keyref(string name, node set values)  节点集合  节点集合,包括文档中所有具有某种关键字节点,此关键字的值与第二个参数中的节点值相同 
doc(string URI)  节点集合  文档或由URI引用的文档部分中的节点集合;这些节点从URI使用的命名的anchor标记或XPointer中选择。如果没有命名的anchor标记或Xpointer,那么所指文档的根元素就存在于节点集合中。相对URI是相对于输入文档中的当前节点的 
docref(node set)  节点集合  节点集合,包括由URI引用的,其值为node set参数的所有节点 
local-part(node set)  字符串  node set参数中第一个节点的本地部分(命名域前缀后面的所有内容);当不使用任何参数时可用于获取上下文节点的本地部分 
namespace(node set)  字符串  节点集合中第一个节点命名域的URI;当不使用任何参数时,可用于获得上下文节点的命名域URI;如果节点处于缺省命名域内,则返回空字符 
qname(node set)  字符串  node set参数中第一个节点的合法名称(可以为前缀和本地部分);要获得上下文节点的合法名称,可不使用任何参数 
generate-id(node set)  字符串  参数node set中第一个节点的唯一标识符;不带参数使用时,可生成上下文节点的ID 
第18章“命名域”将讨论命名域URI、前缀和本地部分。
 
doc()和docref()函数有点模糊,特别是如果URI只引用非结构完整的XML节点或数据片段,就更是如此。细节还要留待XSL规范的未来版本加以澄清。
 
如果向这些函数传递了一个错误类型的参数,那么XSL试图将此参数转变成正确的类型;例如,将数字12转变成字符串“12”。但是,任何参数都不能转变成节点集合。
position()函数可用来对元素进行计数。清单14-13是一个样式单,它使用<xsl:value-of select = "position"/>,将元素在文档中的位置作为原子名的名称的前缀。
清单14-13:按照文档中的顺序对原子进行编号的样式单
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/XSL/Transform/1.0">
<xsl:template match="/PFRIODIC_TABLE">
<HTML>
<HEAD><TITLE>The Elements</TITLE></HEAD>
<BODY>
<xsl:apply-templates select="ATOM"/>
</BODY>
</HTML>
</xsl:template>
<xsl:template match="ATOM">
<P>
<xsl:value-of select="position()"/>.
<xsl:value-of select="NAME"/>
</P>
</xsl:template>
</xsl:stylesheet>
将此样式单应用于清单14-1时,其输出结果如下:
<HTML><HEAD><TITLE>The Elements</TITLE></HEAD><BODY><P>1.
Hydrogen</P><P>2.
Helium</P></BODY></HTML>
14.7.2.2 布尔类型
布尔值为两个值之一:true(真)或false(假)。XSL允许将任何类型的数据转变成布尔值。当在可望为布尔值的地方使用了字符串或数值或节点集合时,通常就暗示需要这样做,xsl:if元素的test特性中正是这样情况。根据下列这些规则,也可以使用boolean()函数来完成这种变换过程,此函数将任何类型(或如果不提供参数即为上下文节点)的参数转变成布尔值:
· 如果数值为零或NaN(一种特定的符号,意为Not a Number,即不是数字,用于表示被零除所获得的结果以及类似的非法操作),则此值为false,否则为true 
· 空节点集合为false;所有的其他节点集合为true 
· 空结果片段为false;所有其他结果片段都为true 
· 零长度字符串为false;所有其他字符串为true 
使用下列操作符所获得的表达式的结果也可以得到布尔值:
= 等于号
< 小于号(实际使用&lt;)
> 大于号
< = 小于等于(实际使用&lt;=)
> = 大于等于
在特性值中<符号是非法的。因此,必须用&lt;来代替,甚至作为小于运算符时也是如此。
 
这些运算符最常用于判断是否调用某个规则。选择表达式不仅含有选择某些节点的模式,而且还可以含有判断条件,从而使用此判断条件对所选的节点列表进一步筛选。例如,from-children(ATOM)选择当前节点的所有ATOM子节点。但是,from-children(ATOME[position()=1])只选择当前节点的第一个ATOM子节点。[position()=1]为一判断语句,它在节点上测试ATOM,返回一个布尔结果值,如果当前节点的位置等于1,则返回的结果为true,否则为false。对每个节点的测试都可能有任何判断值。但是,大于1的值则是不常见到的。
例如,下面的模板规则通过测试元素的位置是否等于1,来决定将此规则应用于周期表中第一个ATOM元素,而不是后续元素。
<xsl:template match="PERIODIC_TABLE/ATOM[position()=l]">
<xsl:value-of select="."/>
</xsl:template>
下面的模板规则通过测试元素的恢檬欠翊笥?,来将此规则应用于非PERIODIC_TABLE第一个子元素的所有ATOM元素:
<xsl:template match="PERIODIC_TABLE/ATOM[position()>1]">
<xsl:value-of select="."/>
</xsl:template>
关键字and和or根据正常的逻辑规则,将两个布尔表达式进行逻辑组合。例如,假设要将模板应用于ATOMIC_NUMBER元素,它既不是其父元素的第一个子元素,也不是其父元素的最后一个子元素;也就是说,它就是父元素本身。下面的模板规则使用and来完成此项工作:
<xsl:template
match="ATOMIC_NUMBER[position()=l and position()=last()]">
<xsl:value of select="."/>
</xsl:template>
下面的模板规则通过匹配位置是1还是最后一个,应用于其父元素中的第一个和最后一个ATOM元素:
<xsl:template match="ATOM[position()=l or positiono=last()]">
<xsl:value-of select="."/>
</xsl:template>
这是逻辑上的“或”,所以如果两个条件都为真,它也将匹配。也就是说,它将既与其父元素的第一个子元素ATOM元素进行匹配,也与其父元素的最后一个子元素ATOM元素进行匹配。
在XSL中没有not关键字,但有not()函数。将操作放在not()括号中,可实现对操作的取反。例如,下面的模板规则选择除其父元素的第一个子元素外的所有ATOM元素:
<xsl:template match="ATOM[not(position()=l)]">
<xsl:value-of select="."/>
</xsl:template>
下面的模板规则选择除其父元素的第一个和最后一个ATOM子元素外的所有ATOM元素:
<xsl:template match =
"ATOM[not(position()=l or position()=last())]">
<xsl:value of select="."/>
</xsl:template>
不存在“异或”操作符。但巧妙地使用not()、and和or可以形成“异或”效果。例如,下面的规则要么选择第一个子元素的ATOM元素,要么选择最后一个子元素的ATOM元素,但不会同时选择第一个和最后一个子元素的ATOM元素。
<xsl:template
match="ATOM[(position()=l or position()=last())
and not(position()=l and position()=last())]">
<xsl:value-of select="."/>
</xsl:template>
下列还有三个函数返回布尔值:
· true()总是返回true 
· false()总是返回false 
· lang(code)如果当前节点的语言(由xml:lang特性给出)与code参数相