文章标题
stylesheet
制定了一个输出样本,而不是使用程序按步骤生成。一个
stylesheet
包含了一个混合的输出样本,并且为每个样本佩戴了输出指令。每一个晓得输出样本加上处理指令就构成了一个模板。
stylesheet
模块。XSL的强大之处在于它能够递归的处理模板,也就是说,每个模板只处理它对应的一个元素,然后调用其它模板来处理它的子元素,以此类推。一个XML文档总是用一个根元素作为顶层元素,并包含可以嵌套的子元素,XSL模板总是从顶层开始扫描,并照层级来处理元素。
<para>
元素为例子,要把它转换成HTML,你希望使用HTML标记
<p>
来包围要输出的内容。但是DocBook的
<para>
元素可以包含任何
in-line
类型的元素来标记内容;不用担心,你可以让其它对应的模板来处理这些元素,因此你的
<para>
XSL模板会是下面这样的简单:
<xsl:template match="para"> <p> <xsl:apply-templates/> </p> </xsl:template>
<xsl:template>
表示开始一个新的模板,属性
match
声明什么元素要被应用模板,在这个例子中将匹配任何的
<para>
元素。模板指出要输出一个
<p>
并执行<xsl:apply-template>指令。这将告诉XSL处理器在
stylesheet
内寻找所有的模板并将其应用到段落中的元素。如果
stylesheet
每个模板都含有一个
<xsl:apply-template>
指令,那么将会递归执行下去。当执行到
stylesheet
的末尾时,模板将输出一个结束的
</p>
标签。
match
属性来描述上下文环境,这是一个纯粹的表达式语言:
XPath ,用来标识你文档中的哪个部分将被应用这个模板。简单的上下文环境通常只声明一个元素名,就像上面的例子。但是你也可以指定元素的子元素,子元素也可以指定属性值,指定的元素成一个队列的形式,以此类推。下面的模板例子描述如何匹配DocBook的
<formalpara>
元素
<xsl:template match= "formalpara" > <p> <xsl:apply-templates/> </p> </xsl:template> <xsl:template match= "formalpara/title" > <b><xsl:apply-templates/></b> <xsl:text> </xsl:text> </xsl:template> <xsl:template match= "formalpara/para" > <xsl:apply-templates/> </xsl:template>
<formalpara>
元素本身,另外两个应用其子元素。在第二个模板中的
match
属性是一个
XPath 表达式,用来表示这里的
<title>
元素是一个直接隶属与
<formalpara>
元素的子元素。这就区分与在DocBook中的其它
<title>
元素。
XPath 表达式是控制模板如何应用的关键。
formalpara/para
,例子中提供的第三个模板,
<para>
元素作为
<formalpara>
元素的子元素,处理器将使用单独的方式来处理它,它将不会再输出已经被父元素输出的
<p>
HTML标签。如果
formalpara/para
模板没有包含在上面的例子中,那么处理器将使用备选的模板
match="para"
,这个模板在上一个例子中定义。这样处理器将输出第二层
<p>
HTML标签。
modes
来控制模板上下文环境,这种方式已经被广泛应用与DocBook stylesheet中。
Modes
能够让你使用不同的方式来处理相同的输入。在
<xsl:template>
模板定义中使用
mode
属性将会为模板指定一个
mode
命名。这种情况下,当有两个模板指定了相同的
mode
属性值,处理器将把
math
的属性值和
mode
的属性值通过表达式
and 连接来作为一个过滤条件,也就是说,当
mode
属性值相同时则继续使用
match
属性值匹配来区分使用哪个模板。这就让你对一个元素定义了两个不同的模板来针对不同的上下文环境。例如,下面对DocBook的
<listitem>
元素定义了两个不同的模板:
<xsl:template match= "listitem" > <li><xsl:apply-templates/></li> </xsl:template> <xsl:template match= "listitem" mode= "xref" > <xsl:number format= "1" /> </xsl:template>
<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
中加入下面的模板:
<xsl:template match= "*" mode= "mode" > <xsl:apply-templates select= "." /> </xsl:template>
mode
属性值,那么模板也将会被应用
无mode
模板,对子元素使用
<xsl:apply-templates/>
,你可以定义
无mode
模板
Assign a value to a variable: <xsl:variable name= "refelem" select= "name($target)" /> If statement: <xsl:if test= "$show.comments" > <i><xsl:call-template name= "inline.charseq" /></i> </xsl:if> Case statement: <xsl:choose> <xsl:when test= "@columns" > <xsl:value-of select= "@columns" /> </xsl:when> <xsl:otherwise> 1</xsl:otherwise> </xsl:choose> Call a template by name like a subroutine, passing parameter values and accepting a return value: <xsl:call-template name= "xref.xreflabel" > <xsl:with-param name= "target" select= "$target" /> </xsl:call-template>
变量
在特别的情况下会具有完全不同的行为。
<xsl:variable>
和
<xsl:param>
。它们享用共同的命名空间和语法,都使用
$name
来引用变量。这两个元素最主要的不同是
param's
的默认值能够被模板调用的
<xsl:with-param>
所取代,就如上面最后一个例子所示。
<xsl:param name= "cols" > 1</xsl:param> <xsl:variable name= "segnum" select= "position()" />
param
和
variable
的名字都是通过
name
属性来指定的,可以看到
param
的名字是
cols
,
variable
的名字是
segnum
。它们的值可以通过两种方式来提供,参数的例子是通过元素的内容值
“1 ” 来赋值的,而变量的例子是通过
select
属性值来赋值的,这个属性值是一个表达式的结果,而元素本身并没有内容值。
stylesheet
中的XSL指令。手工编写
stylesheet
将对你跟踪周期很有帮助,XSL元素缩进和嵌套将帮助你理解周期。下面的代码片段来自DocBook stylesheet中的
pi.xsl
文件,举例说明两个变量周期的不同。
1 <xsl:template name= "dbhtml-attribute" > 2 ... 3 <xsl:choose> 4 <xsl:when test= "$count>count($pis)" > 5 <!-- not found --> 6 </xsl:when> 7 <xsl:otherwise> 8 <xsl:variable name= "pi" > 9 <xsl:value-of select= "$pis[$count]" /> 10 </xsl:variable> 11 <xsl:choose> 12 <xsl:when test= "contains($pi,concat($attribute, '='))" > 13 <xsl:variable name= "rest" select= "substring-after($pi,concat($attribute,'='))" /> 14 <xsl:variable name= "quote" select= "substring($rest,1,1)" /> 15 <xsl:value-of select= "substring-before(substring($rest,2),$quote)" /> 16 </xsl:when> 17 <xsl:otherwise> 18 ... 19 </xsl:otherwise> 20 </xsl:choose> 21 </xsl:otherwise> 22 </xsl:choose> 23 </xsl:template>
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
:
<xsl:call-template name= "head.content" > <xsl:with-param name= "node" select= "$doc" /> </xsl:call-template>
head.content
的模板被调用,在调用周期内传递了一个名为
node
的参数,参数值是变量
$doc
。上面被调用的模板看上去会是下面的样子:
<xsl:template name= "head.content" > <xsl:param name= "node" select= "." /> ...
<xsl:param/>
,并且名字和传入参数名相同。模板内的
<xsl:param/>
提供了一个默认值,如果传入的参数名没有与其匹配,那么将在模板内使用默认值。
stylesheet
,这些由
stylesheet
的HTML驱动文件
docbook/html/docbook.xsl
来完成。这是一个主
stylesheet
文件,它使用
<xsl:include/>
导入其它组件文件组装一个完整的
stylesheet
用来生成HTML
docbook.xsl
的顶层开始:
<xsl:template match= "/" > <xsl:variable name= "doc" select= "*[1]" /> <html> <head> <xsl:call-template name= "head.content" > <xsl:with-param name= "node" select= "$doc" /> </xsl:call-template> </head> <body> <xsl:apply-templates/> </body> </html> </xsl:template>
doc
,然后输出两个HTML元素
<html>
和
<head>
。接着调用名为
head.content
模板来处理HTML的
<head>
,关闭
<head>
后就开始
<body>
。这里使用
<xsl:apply-templates/>
来递归处理输入文档中的内容,最终关闭像HTML文件的输出。
<html>
,但是如果HTML元素输出依赖上下文环境,你就需要一个强大的机制来选取输出元素并且还会生成它们的属性和属性值。下面的代码片段来自于
sections.xsl
,其展示了用
<xsl:element>
和
<xsl:attibute>
来生成HTML的头标签
1 <xsl:element name= "h{$level}" > 2 <xsl:attribute name= "class" > title</xsl:attribute> 3 <xsl:if test= "$level < 3" > 4 <xsl:attribute name= "style" > clear: all</xsl:attribute> 5 </xsl:if> 6 <a> 7 <xsl:attribute name= "name" > 8 <xsl:call-template name= "object.id" /> 9 </xsl:attribute> 10 <b><xsl:copy-of select= "$title" /></b> 11 </a> 12 </xsl:element>
$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
文件中你会找到如下的模板,它专门用来内容文本。
<xsl:template match= "text()" > <xsl:value-of select= "." /> </xsl:template>
stylesheet
中没有提供匹配的模板,XSL处理器都有一些内建的模板来获取内容文本。上面的模板就是提供这样的功能,只不过它明确定义在
stylesheet
文件中。
fo
版本的
stylesheet
可以把你的DocBook XML生成格式化对象。这里需要在你的
stylesheet
中使用
docbook/fo/docbook.xsl
。在你的主
stylesheet
文件中使用
<xsl:include>
引入所有的组件组装成完整的
stylesheet
来生成格式化对象。生成格式化对象只完成了输出过程的一半,你还需要使用
XSL-FO
处理器,比如FOP。
<fo:something>
形式的标签代替相应的HTML标签。例如,输出
in-line
类型并且使用
monospace
字体,fo的形式会是如下的样子:
<fo:inline-sequence font-family= "monospace" > /usr/man</fo:inline-sequence>
<filename>
元素,在
docbook/fo/inline.xsl
中的模板定义看起来像如下的样子:
<xsl:template match= "filename" > <xsl:call-template name= "inline.monoseq" /> </xsl:template> <xsl:template name= "inline.monoseq" > <xsl:param name= "content" > <xsl:apply-templates/> </xsl:param> <fo:inline-sequence font-family= "monospace" > <xsl:copy-of select= "$content" /> </fo:inline-sequence> </xsl:template>
stylesheets
。