递归 XSL 模板






4.13/5 (4投票s)
2006年12月22日
2分钟阅读

52177

284
本文将演示模板的递归特性。
引言
XSL是一种声明式编程语言。一旦声明的变量就不能重新赋值。对于来自过程式编程背景的程序员来说,执行高级任务通常很困难。使用XSL,我们可以解决复杂的问题,这些问题乍一看往往似乎难以甚至不可能解决。在本文中,我将演示模板的递归特性。在典型的产品目录显示案例中,我们的需求是在每个单元格中显示产品详细信息,并且应允许用户选择列数。以下是示例XML文件
<data>
<product name="Heine HKL with trolleystand"
price="230.45" weight="34.4kg" />
<product name="Universal clamp and Projector"
price="670.45" weight="10.64kg" />
<product name="Examination Lamp, Universal Mount"
price="25.45" weight="1.08kg" />
<product name="Provita Examination Lamp, Mobile Base"
price="215.45" weight="1.4kg" />
<product name="35Watt Flexible Arm Light to fit Rail system"
price="130.45" weight="11.67kg" />
.
.
.
.
.
.
</data>
假设我们从业务组件获取上述数据,每个元素`row`对应一个产品,其属性包含产品特定的数据。每个产品及其详细信息将渲染在一个单元格中。并且列数应在运行时可定义。以下是执行渲染的简短XSL
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:param name="numCols" select="3"/>
<table cellpadding="0" cellspacing="5" border="0">
<xsl:call-template name="renderColumns">
<xsl:with-param name="listrows" select="//product"/>
<xsl:with-param name="startindex" select="1"/>
<xsl:with-param name="numofCols" select="$numCols"/>
</xsl:call-template>
</table>
</xsl:template>
<xsl:template name="renderColumns">
<xsl:param name="listrows" />
<xsl:param name="startindex"/>
<xsl:param name="numofCols"/>
<xsl:if test="count($listrows) > 0">
<tr>
<xsl:apply-templates
select="$listrows[position() >=
$startindex and position() <
($startindex+$numofCols)]" mode="rows">
</xsl:apply-templates>
</tr>
<xsl:call-template name="renderColumns">
<xsl:with-param name="listrows"
select="$listrows[position() >= $startindex+$numofCols]"/>
<xsl:with-param name="startindex" select="$startindex"/>
<xsl:with-param name="numofCols" select="$numofCols"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="node()" mode="rows">
<td nowrap="true">
<table style="width:100%;border-right: thin solid;
border-top: thin solid; border-left:
thin solid; border-bottom: thin solid;">
<xsl:apply-templates select="@*"/>
</table>
</td>
</xsl:template>
<xsl:template match="@*">
<tr>
<td style="font-size: larger; text-transform:
uppercase; background-color: gainsboro">
<xsl:value-of select="name()"/>
</td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
解释
<xsl:template match="/">
<xsl:param name="numCols" select="3"/>
就像C或C++程序员使用函数或对象方法组织和重用代码一样,在XSL中,我们可以使用模板组织代码。上述代码是根模板,将由XSLT处理器调用。我们使用`xsl:param
`声明一个参数,其名称为`numCols`。用户可以传递此参数;如果用户未在此参数中提供任何值,则默认情况下,其值为3。此变量将指定要渲染的列数。
<xsl:call-template name="renderColumns">
<xsl:with-param name="listrows" select="//product"/>
<xsl:with-param name="startindex" select="1"/>
<xsl:with-param name="numofCols" select="$numCols"/>
</xsl:call-template>
我们正在调用`renderColumns`模板,并将三个参数传递给它。在`listrows`中,我们选择所有产品元素,`startindex`表示起始索引,`numofCols`将控制要渲染的行数。
<xsl:template name="renderColumns">
<xsl:param name="listrows" />
<xsl:param name="startindex"/>
<xsl:param name="numofCols"/>
<xsl:if test="count($listrows) > 0">
<tr>
<xsl:apply-templates
select="$listrows[position() >=
$startindex and position() <
($startindex+$numofCols)]"
mode="rows">
</xsl:apply-templates>
</tr>
<xsl:call-template name="renderColumns">
<xsl:with-param name="listrows"
select="$listrows[position() >= $startindex+$numofCols]"/>
<xsl:with-param name="startindex" select="$startindex"/>
<xsl:with-param name="numofCols" select="$numofCols"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
在XSL模板`renderColumns`中,我们选择位置大于或等于起始索引且位置小于或等于`startindex`和`numofcols`之和的元素。在渲染这些子集元素后,我们通过选择位置大于`startindex`和`numofCols`之和的子集元素(这些元素已经渲染)来递归调用`renderColumns`。为了退出此递归循环,我们在开始处有一个测试条件,该条件检查`listrows`变量中元素的数量。由于我们在递归调用时仅选择尚未渲染的元素,因此每次调用的节点集将减少已渲染的元素数量。为了渲染行,在此调用模板中,我们使用以下模板
<xsl:template match="node()" mode="rows">
<td nowrap="true">
<table style="width:100%;border-right: thin solid;
border-top: thin solid; border-left:
thin solid; border-bottom: thin solid;">
<xsl:apply-templates select="@*"/>
</table>
</td>
</xsl:template>
在其中我们将属性节点转换为元素并调用另一个模板
<xsl:template match="@*">
<tr>
<td style="font-size: larger; text-transform:
uppercase; background-color: gainsboro">
<xsl:value-of select="name()"/>
</td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:template>
该模板执行在单元格中渲染产品详细信息的工作。
结论
即使变量在XSL中变量的整个生命周期内都是常量,由于XSL的声明性,我们仍然可以实现乍一看似乎不可能实现的事情。仔细观察并以声明方式思考,我们可以解决问题。