1. XSL处理模型
XSL是一个模板语言,而不是一个程序语言。这意味着
stylesheet
制 定了一个输出样本,而不是使用程序按步骤生成。一个
stylesheet
包 含了一个混合的输出样本,并且为每个样本佩戴了输出指令。每一个晓得输出样本 加上处理指令就构成了一个模板。
通常情况,你要为文档内的每个元素书写一个模板。这样能够让你每次只专注于一个 元素,并持有一个
stylesheet
模块。XSL的强大之 处在于它能够递归的处理模板,也就是说,每个模板只处理它对应的一个元素,然后调 用其它模板来处理它的子元素,以此类推。一个XML文档总是用一个根元素作为顶层 元素,并包含可以嵌套的子元素,XSL模板总是从顶层开始扫描,并照层级来处理元素 。
现在用Docbook的
<para>
元 素为例子,要把它转换成HTML,你希望使用HTML标记
<p>
来 包围要输出的内容。但是DocBook的
<para>
元 素可以包含任何
in-line
类型的元素来标记内容;不用担心,你可以让其它对应的模板来处理这些元素,因此你的
<para>
XSL模板会是下面这样的 简单:
元素
<xsl:template>
表 示开始一个新的模板,属性
match
声明什么元素要 被应用模板,在这个例子中将匹配任何的
<para>
元 素。模板指出要输出一个
<p>
并 执行<xsl:apply-template>指令。这将告诉XSL处理器在
stylesheet
内寻找所有的模板并将其应用到段落中的元素 。如果
stylesheet
每个模板都含有一个
<xsl:apply-template>
指令,那 么将会递归执行下去。当执行到
stylesheet
的末 尾时,模板将输出一个结束的
</p>
标 签。
1.1. 重要的 上下文环境
既然不需要用线性的步骤来书写你的文档,那么一个用来描述在什么地方应用这些模 板的
“上下文环境”是 尤为重要的。大多数模板都提供了一个
match
属性 来描述上下文环境,这是一个纯粹的表达式语言:
XPath,用来标识你文档中的哪个部分将被应用这个模板。简单的上下文环境通常只声明一 个元素名,就像上面的例子。但是你也可以指定元素的子元素,子元素也可以指定属 性值,指定的元素成一个队列的形式,以此类推。下面的模板例子描述如何匹配 DocBook的
<formalpara>
元 素
上面定义了三个模板,第一个应用于
<formalpara>
元 素本身,另外两个应用其子元素。在第二个模板中的
match
属 性是一个
XPath表达式,用来表示这里 的
<title>
元素是一个 直接隶属与
<formalpara>
元 素的子元素。这就区分与在DocBook中的其它
<title>
元 素。
XPath表达式是控制模板如何应用 的关键。
通常情况,XSL处理器的内部规则是在面对高层模板和底层模板时,优先应用底层模板 ,这里的层次是元素的层级,文档顶层是高层元素,以此类推。这能够让你更细致的 控制模板应用,但是在你没有为复杂的符合元素提供细致的上下文环境时,XSL处理器 也对其提供了备选方案。这个特性在上面的例子中得以体现,对于
formalpara/para
,例子中提供的第三个模板,
<para>
元素作为
<formalpara>
元素的子元素,处 理器将使用单独的方式来处理它,它将不会再输出已经被父元素输出的
<p>
HTML标签。如果
formalpara/para
模板没有包含在上面的例子中,那么处理 器将使用备选的模板
match="para"
,这个模板在上 一个例子中定义。这样处理器将输出第二层
<p>
HTML 标签。
你也可以使用XSL中的
modes
来控制模板上下文 环境,这种方式已经被广泛应用与DocBook stylesheet中。
Modes
能够让你使用不同的方式来处理相同的输入。在
<xsl:template>
模板定义中使 用
mode
属性将会为模板指定一个
mode
命名。这种情况下,当有两个模板指定了相同的
mode
属性值,处理器将把
math
的 属性值和
mode
的属性值通过表达式
and连接来作为一个过滤条件,也就是说,当
mode
属性值相同时则继续使用
match
属 性值匹配来区分使用哪个模板。这就让你对一个元素定义了两个不同的模板来针对 不同的上下文环境。例如,下面对DocBook的
<listitem>
元 素定义了两个不同的模板:
第一个模板应用于普通的列表输出情况,模板输出
<li>
HTML 标签来。第二个模板定义为
<xsl:apply-templates select="$target" mode="xref"/>
,这种情况下用来专门处理
<xref>
元素。在这个 例子中,
mode
属性的值决定应用第二个模板,它将 初始带有序号的列表。因为在输出
<xref>
元 素时经常会有这样的需求。
请记住,
mode
的设置不会自动的贯穿处理子模 板
<xsl:apply-templates/>
。 当子模板含有
mode
属性时,你可以有两个选择来处 理:
-
要想继续使用
mode
模式,也就是使用<xsl:apply-templates mode="mode"/>
模 板来处理子元素,处理器将查找具有相同mode
属性 值的模板来应用子元素。注意,这样的话你就没有备选方案,如果模板没有指定mode
属性值,子元素将不会有模板匹配,也就不会被模板 处理。如果你想使用没有mode
属性的模板作为备选 ,那么在stylesheet
中加入下面的模板:这样的话,对于任何子元素,如果模板没有配备mode
属 性值,那么模板也将会被应用 -
使用通常的
无mode
模板,对子元素使用<xsl:apply-templates/>
,你 可以定义无mode
模板
1.2. 编程特性
尽管XSL是模板驱动的,但是它同样具有很多传统编程语言的特性。下面一些例子来 自与DockBook stylesheet
然而你不能像在其它编程语言那样来使用上面这些结构,因为
变量
在特别的情况下会具有完全不同的行为。
1.2.1. 使用 变量和参数
XSL提供两种元素来让你指派值到变量上:
<xsl:variable>
和
<xsl:param>
。它们 享用共同的命名空间和语法,都使用
$name
来引用变 量。这两个元素最主要的不同是
param's
的默认值 能够被模板调用的
<xsl:with-param>
所 取代,就如上面最后一个例子所示。
下面两个例子同样来自DocBook:
在上面两个元素中,
param
和
variable
的名字都是通过
name
属 性来指定的,可以看到
param
的名字是
cols
,
variable
的名字是
segnum
。它们的值可以通过两种方式来提供,参数 的例子是通过元素的内容值
“1”来 赋值的,而变量的例子是通过
select
属性值来赋值 的,这个属性值是一个表达式的结果,而元素本身并没有内容值。
对于新接触XSL的用户来说变量的特性有点古怪,当你给一个变量赋值后,你就不能在 它的应用周期内改变它的值,如果这样做会报错。所以你不能像在使用其它编程语 言那样对变量进行动态存储,变量在它的应用周期内持有的是固定值,并在应用周期 结束时销毁。这个特性是在设计XSL时就决定了,因为XSL是模板驱动而非流程驱动的 。这意味着它没有固定的执行顺序,所以你无法依赖一个能够改变值的变量。要想 正确的使用变量,你必须理解变量的周期是如何定义的。
如果一个变量定义在所有模板的外部,那么它就被认为是一个全局变量,它对所有模 板都生效。全局变量的值是固定不变的,也不能被任何模板所重新赋值。但是你可以 在模板内创建一个与全局变量同名的本地变量,然后赋予其它的值。本地变量只能 在其自己的应用周期内起作用。
定义在模板里的本地变量只会在它被允许的周期内生效,也就是对在它之后的同胞和 后裔有效。要想理解这个周期,你必须明白XSL指令其实就是纯粹的XML元素,并内嵌 在XML家族层级结构中。它们通常是指父级、子级、同级、祖先级和后裔级。在 XML家族层级中,给一个变量赋值就像发布一个公告给你希望听到家族成员一样。你 只能把公告发布给比你年龄低的同级(包括你自己)和它们的后裔级,也就是说定义在 你前面的年长的同级将不会听到公告,更不用说你的父级和祖先了。如果你发布不 同的公告内容但是用相同的公告名给相同的被通知成员,那将出现错误,(言外之义,你重新给变量赋值了)。请记住这里的家族并不是你的文档元素,而只是在你
stylesheet
中的XSL指令。手工编写
stylesheet
将对你跟踪周期很有帮助,XSL元素缩进和嵌套 将帮助你理解周期。下面的代码片段来自DocBook stylesheet中的
pi.xsl
文件,举例说明两个变量周期的不同。
变量
pi
的周期开始于第8行,也就是模板定义 它的位置,结束于第20行它最后一个同级兄弟结束的地方
[1]。变量
rest
的周 期开始于13行,结束与15行。幸运的是,15行的输出表达式赶在周期结束前使用了变 量值。
让我们来看看当在变量的周期内使用
<xsl:apply-templates/>
会 如何?被应用的模板内会得到变量值吗?答案是否定的。因为被应用的模板生效周期 并没有真正的在变量周期内,它在
stylesheet
的其 它地方退出,并不是在变量的低龄同级和后裔内退出。
要想传值给一个模板,你可以使用
<xsl:with-param/>
传 递一个参数。这种参数传递通常被用在使用
<xsl:call-templates/>
调 用指定模板,尽管你也可以使用
<xsl:apply-templates/>
调 用模板,但是通常被调用的模板希望传入一个与
<xsl:param/>
定 义同名的参数。这样就可以在模板内使用这个参数值。任何传入的参数名如果在模 板内没有被定义将被忽略处理。
下面参数传递的例子来自
docbook.xsl
:
上面一个命名为
head.content
的模板被调用 ,在调用周期内传递了一个名为
node
的参数,参数值 是变量
$doc
。上面被调用的模板看上去会是下面 的样子:
模板期望一个参数是因为模板定义中声明了一个
<xsl:param/>
,并且名字和传入参数名相同。模板内的
<xsl:param/>
提 供了一个默认值,如果传入的参数名没有与其匹配,那么将在模板内使用默认值。
1.3. 生成 HTML
从你的DocBook文件生成HTML需要使用HTML版本的
stylesheet
,这些由
stylesheet
的HTML驱动文件
docbook/html/docbook.xsl
来完成。这是一个主
stylesheet
文件,它使用
<xsl:include/>
导 入其它组件文件组装一个完整的
stylesheet
用来 生成HTML
DocBook stylesheet生成HTML的方式是通过应用模板来输出一些文本内容和HTML元素的混合 体。从
docbook.xsl
的顶层开始:
模板匹配到你输入文档的根元素,然后就开始递归应用模板。首先定义了一个变量
doc
,然后输出两个HTML元素
<html>
和
<head>
。接着调用名为
head.content
模板来处理HTML的
<head>
,关闭
<head>
后就开始
<body>
。这里使用
<xsl:apply-templates/>
来递 归处理输入文档中的内容,最终关闭像HTML文件的输出。
简单的HTML元素可以用不带任何属性的元素生成,如
<html>
,但是如果HTML元素输出 依赖上下文环境,你就需要一个强大的机制来选取输出元素并且还会生成它们的属 性和属性值。下面的代码片段来自于
sections.xsl
,其展示了用
<xsl:element>
和
<xsl:attibute>
来生 成HTML的头标签
整个例子生成了一个单独的HTML头元素。第1行定义了一个HTML元素,例子中元素的 名字是一个带有变量
$level
的表达式,变量是通过 参数出入模板的。这样的话模板就会生成
<hi>
、
<h2>
、等等。具体生 成哪个依赖于上下文环境。第2行为头元素添加了一个属性
class="title"
。第3-5行添加了属性
style="clear all"
,但是只适用于头元素的层级数小于3 的情况。第6行打开一个锚元素
<a>
。 看上去没带有任何属性,其实是在第7-9行为
<a>
元 素添加了
name
属性。这个例子描述XSL管理的输出 元素是一个活的元素,而不只是一个文本串。第10行输出头元素的标题文本,同样是 通过传递参数的形式获得,然后关闭HTML粗体标签。第11行使用
</a>
关闭锚标签,第12行是头元 素的关闭标签,这就结束了头元素的定义。
当你随着模板的递归来处理元素时,可能会疑惑在你文档里的内容文本是如何被模板 输出的,在
docbook.xsl
文件中你会找到如下的模板,它专门用来内容文本。
这个模板的主体由文本节点的值组成,它只是文本。通常,如果你的
stylesheet
中没有提供匹配的模板,XSL处理器都有一些内 建的模板来获取内容文本。上面的模板就是提供这样的功能,只不过它明确定义在
stylesheet
文件中。
1.4. 生成格 式化对象(FO)
使用
fo
版本的
stylesheet
可 以把你的DocBook XML生成格式化对象。这里需要在你的
stylesheet
中 使用
docbook/fo/docbook.xsl
。在你的主
stylesheet
文件中使用
<xsl:include>
引 入所有的组件组装成完整的
stylesheet
来生成格 式化对象。生成格式化对象只完成了输出过程的一半,你还需要使用
XSL-FO
处理器,比如FOP。
DocBook的fo stylesheet和HTML stylesheet的工作方式类似,就是用
<fo:something>
形式的标签代 替相应的HTML标签。例如,输出
in-line
类型并且使 用
monospace
字体,fo的形式会是如下的样子:
输出一个DocBook
<filename>
元 素,在
docbook/fo/inline.xsl
中的模板定义看起 来像如下的样子:
在XSL标准中指定了很多XSL-FO标签和属性的规范,要描述在DocBook中如何遵循这些 规范显然已经超出本书的范围。庆幸的是,这些只是中间格式,你不许要马上去处理 ,除非你正在自己编写
stylesheets
。