65.9K
CodeProject 正在变化。 阅读更多。
Home

递归 XSL 模板

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.13/5 (4投票s)

2006年12月22日

2分钟阅读

viewsIcon

52177

downloadIcon

284

本文将演示模板的递归特性。

Recursive XSL Templates

引言

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的声明性,我们仍然可以实现乍一看似乎不可能实现的事情。仔细观察并以声明方式思考,我们可以解决问题。

© . All rights reserved.