XSL入门(翻译)

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-templates/>   </p> </xsl:template>

元素<xsl:template>表示开始一个新的模板,属性match声明什么元素要被应用模板,在这个例子中将匹配任何的<para>元素。模板指出要输出一个<p>并执行<xsl:apply-template>指令。这将告诉XSL处理器在stylesheet内寻找所有的模板并将其应用到段落中的元素。如果stylesheet每个模板都含有一个<xsl:apply-template>指令,那么将会递归执行下去。当执行到stylesheet的末尾时,模板将输出一个结束的</p>标签。

1.1. 重要的上下文环境

既然不需要用线性的步骤来书写你的文档,那么一个用来描述在什么地方应用这些模板的“上下文环境”是尤为重要的。大多数模板都提供了一个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表达式是控制模板如何应用的关键。

通常情况,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>元素定义了两个不同的模板:
<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模板

1.2. 编程特性

尽管XSL是模板驱动的,但是它同样具有很多传统编程语言的特性。下面一些例子来自与DockBook stylesheet
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> 

然而你不能像在其它编程语言那样来使用上面这些结构,因为变量在特别的情况下会具有完全不同的行为。

1.2.1. 使用变量和参数

XSL提供两种元素来让你指派值到变量上:<xsl:variable><xsl:param>。它们享用共同的命名空间和语法,都使用$name来引用变量。这两个元素最主要的不同是param's的默认值能够被模板调用的<xsl:with-param>所取代,就如上面最后一个例子所示。

下面两个例子同样来自DocBook:
<xsl:param name="cols">1</xsl:param> <xsl:variable name="segnum" select="position()"/>

在上面两个元素中,paramvariable的名字都是通过name属性来指定的,可以看到param的名字是colsvariable的名字是segnum。它们的值可以通过两种方式来提供,参数的例子是通过元素的内容值“1”来赋值的,而变量的例子是通过select属性值来赋值的,这个属性值是一个表达式的结果,而元素本身并没有内容值。

对于新接触XSL的用户来说变量的特性有点古怪,当你给一个变量赋值后,你就不能在它的应用周期内改变它的值,如果这样做会报错。所以你不能像在使用其它编程语言那样对变量进行动态存储,变量在它的应用周期内持有的是固定值,并在应用周期结束时销毁。这个特性是在设计XSL时就决定了,因为XSL是模板驱动而非流程驱动的。这意味着它没有固定的执行顺序,所以你无法依赖一个能够改变值的变量。要想正确的使用变量,你必须理解变量的周期是如何定义的。

如果一个变量定义在所有模板的外部,那么它就被认为是一个全局变量,它对所有模板都生效。全局变量的值是固定不变的,也不能被任何模板所重新赋值。但是你可以在模板内创建一个与全局变量同名的本地变量,然后赋予其它的值。本地变量只能在其自己的应用周期内起作用。

定义在模板里的本地变量只会在它被允许的周期内生效,也就是对在它之后的同胞和后裔有效。要想理解这个周期,你必须明白XSL指令其实就是纯粹的XML元素,并内嵌在XML家族层级结构中。它们通常是指父级、子级、同级、祖先级和后裔级。在XML家族层级中,给一个变量赋值就像发布一个公告给你希望听到家族成员一样。你只能把公告发布给比你年龄低的同级(包括你自己)和它们的后裔级,也就是说定义在你前面的年长的同级将不会听到公告,更不用说你的父级和祖先了。如果你发布不同的公告内容但是用相同的公告名给相同的被通知成员,那将出现错误,(言外之义,你重新给变量赋值了)。请记住这里的家族并不是你的文档元素,而只是在你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/>提供了一个默认值,如果传入的参数名没有与其匹配,那么将在模板内使用默认值。

1.3. 生成HTML

从你的DocBook文件生成HTML需要使用HTML版本的stylesheet,这些由stylesheet的HTML驱动文件docbook/html/docbook.xsl来完成。这是一个主stylesheet文件,它使用<xsl:include/>导入其它组件文件组装一个完整的stylesheet用来生成HTML

DocBook stylesheet生成HTML的方式是通过应用模板来输出一些文本内容和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>,但是如果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>

整个例子生成了一个单独的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文件中你会找到如下的模板,它专门用来内容文本。
<xsl:template match="text()">   <xsl:value-of select="."/> </xsl:template>

这个模板的主体由文本节点的值组成,它只是文本。通常,如果你的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的形式会是如下的样子:
<fo:inline-sequence  font-family="monospace">/usr/man</fo:inline-sequence>

输出一个DocBook <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>

在XSL标准中指定了很多XSL-FO标签和属性的规范,要描述在DocBook中如何遵循这些规范显然已经超出本书的范围。庆幸的是,这些只是中间格式,你不许要马上去处理,除非你正在自己编写stylesheets

posted on 2011-03-09 08:58 kuuyee 阅读(1554) 评论(1)  编辑  收藏 所属分类: 系统管理JEE

评论

# re: XSL入门(翻译)[未登录] 2011-03-09 09:18 paul

用他搞过代码生成工具  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

导航

<2011年3月>
272812345
6789101112
13141516171819
20212223242526
272829303112
3456789

统计

随笔分类(139)

Linux内核

搜索

积分与排名

最新评论

阅读排行榜