使用 XPath 2.0 和 XSLT 2.0 节省开发时间并减少代码量

http://tech.ddvip.com   2007年10月25日    社区交流

内容摘要:XPath 2.0 和 XSLT 2.0 中有三个有趣的新特性,分别是:item 数据类型、to 运算符和 序列 的概念。构建一个示例应用程序,在其中使用这些特性生成 XML 文档的复杂 HTML 视图,使用 XSLT 2.0 中的新特性,您可以创建更短、更易于维护的样式表。在此期间,稍微关注一下 XSLT 2.0 中的数据类型,并学习使用新的 <xsl:function> 元素。

  本文示例源代码或素材下载

  XPath 2.0 和 XSLT 2.0 中主要的新概念之一是,一切都是序列。在 XPath 1.0 和 XSLT 1.0 中,您通常要使用节点树。解析后的 XML 文档是一个节点树,包含了文档节点及其后代节点。您可以使用该节点树查找根元素及其所有的后代元素、属性和兄弟元素的节点。(XML 文件中根元素之外的所有注释或处理说明都被看作根元素的兄弟元素。)

  当您在 XPath 2.0 和 XSLT 2.0 中使用 XML 文档时,序列的使用方式与 XPath 1.0 和 XSLT 1.0 中树结构的使用方式相同。序列包含了一个 item(文档节点),它的使用方式与以前相同。但是,您可以创建原子值 序列。您将在后面的一个示例应用程序中管理一组锦标赛数据,这是一场由 16 支队伍参加的单场淘汰赛,取自该程序的 清单 1 展示了一个原子值序列的示例。

  清单 1. 一个原子值序列

<xsl:variable name="seeds" as="xs:integer*">
 <xsl:sequence
  select="(1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15)"/>
</xsl:variable>

  此代码定义了变量 $seeds。如您所料,新的 <xsl:sequence> 元素定义了 item 序列。此时,item 是 XML Schema xs:integer。新的 as 属性定义了变量的数据类型,而星号(xs:integer*)指序列包含零或更多整数。在 XPath 1.0 和 XSLT 1.0 中,您将创建 16 个不同的文本节点,然后将这些节点聚合到一个变量中。在 XPath 2.0 和 XSLT 2.0 中,序列作为一个一维数值数组使用,这正是示例应用程序中所需要的用法。

  序列遵循了一些规则。首先,序列不能包含其他序列。如果您使用一个由三个 item 组成的序列,和其后的另外一个由三个 item 组成的序列,一起创建新序列,则得到的结果是一个包含六个 item 的新序列。其次,序列允许您混合使用节点和 item。您可以创建一个序列,其中包含 清单 1 所示的原子值,以及 清单 2 所示的所有 <contestant> 元素。

  清单 2. 节点和原子值序列

<xsl:variable name="seeds" as="item()*">
 <xsl:for-each select="/bracket/contestants/contestant">
  <xsl:copy-of select="."/>
 </xsl:for-each>
 <xsl:sequence
  select="(1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15)"/>
</xsl:variable>

  $seeds 变量包含了所有的竞争节点和您先前使用的 16 个原子值。注意,变量的数据类型是 item()*。每个 item 是一个节点或原子值,因此此变量可以包含任何内容。

  现在,您已经了解了序列和 item 的基本信息,我们开始考察 to 运算符,它也是 XPath 2.0 和 XSLT 2.0 中的新特性。它让您能够选择一些整数。例如,您可以创建如 清单 3 所示的序列。

  清单 3. 使用 to 运算符创建的整数序列

<xsl:variable name="range" as="item()*">
 <xsl:sequence select="1 to 16"/>
</xsl:variable>

  此代码创建的变量 $range 包含了从 1 到 16 的整数。在同一个应用程序中,您可以使用 to 运算符作为循环机制,如 清单 4 所示。

  清单 4. 使用 to 运算符实现循环

<xsl:for-each select="1 to 32">
 <!-- Do something useful here -->
</xsl:for-each>

  在您构建样式表之前,请仔细查看示例应用程序。

  理解示例应用程序

  在本文的示例应用程序中,您将为 16 支队伍参加的单场淘汰锦标赛管理赛事数据。如您所料,锦标赛数据以 XML 格式表示。您将创建一个 XSLT 2.0 样式表,将 XML 数据转换为 HTML 表以显示锦标赛的结果。清单 5 展示了 XML 文档的格式。

  清单 5. 含有锦标赛数据的 XML 文档

<?xml version="1.0" encoding="UTF-8"?>
<!-- tourney.xml -->
<bracket>
  <title>RSDC Smackdown</title>
  <contestants>
   <contestant seed="1" image="images/homerSimpson.png">Donuts</contestant>
   <contestant seed="2" image="images/caffeine.png">Caffeine</contestant>
   <contestant seed="3" image="images/fearlessFreep.png">Fearless Freep</contestant>
   <contestant seed="4" image="images/wmd.jpg">Weapons of Mass Destruction</contestant>
   <contestant seed="5" image="images/haroldPie.jpg">Pie</contestant>
   <contestant seed="6" image="images/adamAnt.png">Adam Ant</contestant>
   <contestant seed="7" image="images/georgeWBush.jpg">Misunderestimated</contestant>
   <contestant seed="8" image="images/sillyPutty.jpg">Silly Putty</contestant>
   <contestant seed="9" image="images/krazyGlue.jpg">Krazy Glue</contestant>
   <contestant seed="10" image="images/snoopDogg.png">Biz-Implification</contestant>
   <contestant seed="11" image="images/atomAnt.png">Atom Ant</contestant>
   <contestant seed="12" image="images/ajaxcan.png">AJAX</contestant>
   <contestant seed="13" image="images/darthVader.jpg">Darth Vader</contestant>
   <contestant seed="14" image="images/nastyCanasta.png">Nasty Canasta</contestant>
   <contestant seed="15" image="images/jcp.png">Java Community Process</contestant>
   <contestant seed="16" image="images/andre.png">Andre the Giant</contestant>
  </contestants>
  <results>
   <result round="1" firstSeed="1" secondSeed="16" winnerSeed="1"/>
   <result round="1" firstSeed="8" secondSeed="9" winnerSeed="9"/>
   <result round="1" firstSeed="5" secondSeed="12" winnerSeed="5"/>
   <result round="1" firstSeed="4" secondSeed="13" winnerSeed="4"/>
   <result round="1" firstSeed="6" secondSeed="11" winnerSeed="11"/>
   <result round="1" firstSeed="3" secondSeed="14" winnerSeed="3"/>
   <result round="1" firstSeed="7" secondSeed="10" winnerSeed="10"/>
   <result round="1" firstSeed="2" secondSeed="15" winnerSeed="2"/>
   <result round="2" firstSeed="1" secondSeed="9" winnerSeed="1"/>
   <result round="2" firstSeed="5" secondSeed="4" winnerSeed="5"/>
   <result round="2" firstSeed="11" secondSeed="3" winnerSeed="3"/>
   <result round="2" firstSeed="10" secondSeed="2" winnerSeed="2"/>
   <result round="3" firstSeed="1" secondSeed="5" winnerSeed="1"/>
   <result round="3" firstSeed="3" secondSeed="2" winnerSeed="2"/>
   <result round="4" firstSeed="1" secondSeed="2" winnerSeed="2"/>
  </results>
</bracket>   

  在 <title> 元素中,您可以看见锦标赛的名称 RSDC Smackdown。此文档表示了今年 IBM Rational Software Developer Conference 中某个会议的真实结果。

  一个方括号包含了 16 个 <contestant> 元素,每个元素拥有一个名称(它的文本)、一个 seed 和一个图像。16 支队伍的锦标赛包含了 15 场比赛。每场比赛由一个 <result> 元素表示。每场比赛具有四组关联数据:发生比赛的锦标赛回合(round 属性),两个竞争队伍的 seed(存储于 firstSeed 和 secondSeed 属性中),以及获胜者的 seed(winnerSeed 属性)。您的任务是获取此 XML 文档并将其转换为显示结果的 HTML 表格,如 图 1 所示。

  图 1. HTML 表中的锦标赛结果

使用 XPath 2.0 和 XSLT 2.0 节省开发时间并减少代码量

  该表为 32 行 5 列。您可以使用 XSLT 1.0 的方法构建 HTML 表,每次一行,如 清单 6 所示。

  清单 6. 构建 HTML 表,每次一行

<!-- Row 1 -->
<tr>
 <td style="border: none; border-top: solid; border-right: solid;">
  <xsl:text>[1] </xsl:text>
  <xsl:value-of select="$contestants[@seed='1']/>
 </td>
 <td style="border: none;>
  &nbsp;
 </td>
 <td style="border: none;>
  &nbsp;
 </td>
 <td style="border: none;>
  &nbsp;
 </td>
 <td style="border: none;>
  &nbsp;
 </td>
</tr>
<!-- Row 2 -->
. . .

  这种方法可行,但是样式表会难于维护。整个样式表中每行和每列都有重复的代码。这里的主要问题是,输出表中需要有 32 行。每一行包含来自 XML 文档中的元素数据(<contestant> 或 <result>)。不幸的是,您并没有 32 个元素可以迭代。您可以使用 <xsl:for-each select="contestants/contestant|results/result">,但是这些元素在表中的显示顺序并不符合要求。XSLT 1.0 没有提供什么工具帮助您解决这一问题。

  您可以重构代码以简化样式表。在样式和单元格内容方面有一些模式,您可以使用 XPath 2.0 和 XSLT 2.0 的新特性在这些模式中迭代。最终的样式表比(公认笨拙的)原始版本小了大约 70%。在构建该样式表之前,我们来查看一下如何重构代码。

  重构代码 —— 计算表单元格的样式

  要重构代码,首先将样式信息移到 CSS 样式表中。如 图 2 所示,HTML bracket 表有 5 个不同的单元格样式:None、MatchupStart、MatchupMiddle、MatchupEnd 和 Solid。

  图 2. HTML 表中的单元格边界样式

使用 XPath 2.0 和 XSLT 2.0 节省开发时间并减少代码量

  清单 7 显示了 CSS 代码的外观。

  清单 7. CSS 样式表

.None     { width: 20%; }
.MatchupStart { width: 20%;
         border: none; border-top: solid;
         border-right: solid; }
.MatchupMiddle { width: 20%;
         border: none; border-right: solid; }
.MatchupEnd  { width: 20%;
         border: none; border-bottom: solid;
         border-right: solid; }
.Solid     { width: 20%;
         border: solid; }

  边界样式的使用使查看锦标赛的结果非常容易。连接两个单元格的水平线表示这两个竞争者将会彼此遭遇;下面的列中使用实线边界的单元格指比赛的获胜者。在边界样式表中,您可以看到一个明确的模式:对于每一对竞争者,第一个竞争者前的单元格没有边界(样式 None),两个竞争者的单元格拥有一个实线边界(样式 Solid),两个竞争者之间的单元格在右侧有一个边界(样式 MatchupMiddle),而最后一个竞争者后的单元格没有边界(样式 None)。对第一列有少许不同。因为第一列中的竞争者对太过接近,所以第一个竞争者的单元格有上边界和右边界(样式 MatchupStart),而第二个竞争者的单元格有下边界和右边界(样式 MatchupEnd)。

  注意,每一列中竞争者对相距很远。在第一列中竞争者之间相距一个单元格的距离,在第二列中相距三个单元格的距离,在第三列中相距七个单元格的距离,在第四列中相距 15 个单元格的距离。每个距离的大小为 2 的幂次方减一,因此这里有一个明确的模式。

  表的一个通用模式是每一列包含一组重复的单元格。这五列的重复组(我称之为列的 period,因为没有更好的术语)的大小为 4、8、16、32 和 64。每个重复组包含两个竞争者及其之间、之前和之后的单元格。

  在每一列中,使用计算中用到的两个值:$period,表示重复组的大小;以及 $oneQuarter,表示 period 大小的四分之一(将 $period div 4 存储在一个变量中使代码更加整洁。)表 1 展示了单元格边界样式的通用规则。

  表 1. HTML 表中的单元格边界样式

公式第一列(period 4)第二列(period 8)第三列(period 16)第四列(period 32)第五列(period 64)
$row < $oneQuarter 或者 $row > $period - $oneQuarter N/A第 1 行,样式 None 第 1-3 行,样式 None 第 1-7 行,样式 None 第 1-15 行,样式 None
$row = $oneQuarter 第 1 行,样式 MatchupStart 第 2 行,样式 Solid 第 4 行,样式 Solid 第 8 行,样式 Solid 第 16 行,样式 Solid
$row > $oneQuarter 和 $row < $period - $oneQuarter 第 2 行,样式 MatchupMiddle 第 3-5 行,样式 MatchupMiddle 第 5-11 行,样式 MatchupMiddle 第 9-23 行,样式 MatchupMiddle 第 17-32 行,样式 None
$row = $period - $oneQuarter 第 3 行,样式 MatchupEnd 第 6 行,样式 Solid 第 12 行,样式 Solid 第 24 行,样式 Solid N/A
$row < $oneQuarter 或 $row > $period - $oneQuarter 第 4 行,样式 None 第 7-8 行,样式 None 第 13-16 行,样式 None 第 25-32 行,样式 None N/A

  现在,您已经创建了一种优雅的方式来指出表中任何一个给定单元格的样式。指定列号和行号后,这些公式将发挥它们的作用。

  重构代码 —— 计算表单元格的内容

  当然,您在表中创建单元格时也需要往单元格中放入适当的内容。这也有一个很好的模式。样式 None 或 MatchupMiddle 的部分没有任何内容。就是说您只需为其他三种样式寻找适当的内容。

  在 表 1 中,您可以看见,带内容的单元格拥有 $row = $oneQuarter 或 $row = $period - $oneQuarter 属性。对于第一列,您只需写入 seed 和相应竞争者的名称。比赛基于 seed 排名并形成了 表 2 所示的对。

  表 2. 比赛基于 seed 排名

[1] 对阵 [16]
[8] 对阵 [9]
[5] 对阵 [12]
[4] 对阵 [13]
[6] 对阵 [11]
[3] 对阵 [14]
[7] 对阵 [10]
[2] 对阵 [15]

  比赛安排的结果是如果 seed 排名较高的队伍一直赢,那么 seed 排名最高的队伍将一直对阵 seed 排名最低的队伍,seed 排名次高的队伍将一直对阵 seed 排名次低的队伍,依此类推。查看 seed 的顺序(1、16、8、9、5、12、4、13、6、11、3、14、7、10、2、15),您可以看见它们与 $seeds 序列中的值相匹配。竞争者在第一列中按此顺序显示。

  每隔一行显示一个竞争者,即,第 1 行拥有序列中的第一个 seed,第 3 行拥有序列中的第二个 seed,第 5 行拥有序列中的第三个 seed,等等依此类推。这里的模式是 $row + 1 div 2 = $index。换言之,获取行号,加 1,然后除以 2 得到 $seeds 序列中 seed 的索引。表单元格的内容是 seed 编号,后接具有该 seed 级别的竞争者的名称。

  第一列的内容现已处理好。如您所料,第 2 到 5 列就要复杂一些。您不需要查看 <contestant> 元素,而需要查看结果,显示于 15 个 <result> 元素中。

  第 2 列包含了第 1 回合的获胜者。就是指您需要查看具有 round="1" 属性的 <result> 元素。为了简化起见,第一场比赛(seed 1 对阵 seed 16)的获胜者存储于第一个 <result> 元素中,第二场比赛(seed 8 对阵 seed 9)的获胜者存储于第二个 <result> 元素中,等等依此类推。第二列的获胜者在第 2、6、10、14、18、22、26 和 30 行中显示。观察其模式,第 2 行使用第一个 <result> 元素,第 6 行使用第二个该元素,第 10 行使用第三个该元素。对于此列,如果您取得行号,加 2,然后除以 4,您将获得正确的 <result round="1"> 元素的位置。

  第 3 和 4 列按类似的方法处理。第 3 列的获胜者显示在第 4、12、20 和 28 行(此处的模式为加 4 然后除以 8)。第 4 列中的获胜者显示于第 8 和 24 行中(加 8 并除以 16)。第 5 列只显示了一个竞争者 — 整个锦标赛的获胜者。如果您位于第 16 行,您将在单个的 <result round="4"> 元素中显示获胜者。

  重构以找出您所搜寻的值的位置的方法是使用 ($row + $oneQuarter) div ($oneQuarter * 2)。使用重复模式的递增的大小使代码简化。对于第 1 列,计算后的位置是 seed 序列的索引;对于其他列,计算后的位置是正确的 <result> 元素的位置。

  现在,您已经掌握了一种优雅的方式,确定表中每个单元格的内容和样式。指定行号和列号后,您可以计算出所有需要了解的内容。

  利用 XPath 2.0 和 XSLT 2.0 的功能

  现在,您可以使用 XPath 2.0 和 XSLT 2.0 中的新技术简化样式表设计。开始时使用 to 运算符。如果您使用过程式编程语言构建表,那么可能要编写类似 清单 8 的代码。

  清单 8. 过程式方法处理样式表

   for (int row=1; row<=32; row++)
    for (int column=1; column<=5; column++)
     // Build each cell in the table here

  在 XPath 2.0 和 XSLT 2.0 中,您使用 <xsl:for-each> 代替过程式语言中使用的 for 循环。清单 9 展示了具体的做法。

  清单 9. 使用 to 运算符实现 for 循环

<xsl:for-each select="1 to 32">
 <xsl:variable name="outerIndex" select="."/>
  <tr>
   <xsl:for-each select="1 to 5">

  这里有一点复杂的部分需要处理。首先,在 XPath 1.0 和 XSLT 1.0 中,使用 <xsl:for-each> 为每次迭代更改上下文。例如,如果您使用 <xsl:for-each select="contestants/contestant>,上下文节点在每次迭代中是最新的 <contestant>。当您使用 to 运算符迭代各个整数时,上下文 item(2.0 中的上下文 item)是未定义的。如 清单 9 中所示,您需要保存外部 <xsl:for-each> 的当前值,因为它在内部 <xsl:for-each> 中不可用。

  但是这样处理更加麻烦。如果上下文 item 未定义,您就无法从文档中选择节点。如果您知道您位于第 1 行第 1 列,就可以从 $seeds 序列中获取第一个 item,因为 $seeds 是一个全局变量。这就告诉您需要找到 <contestant seed="1"> 元素。不幸的是,在文档中您什么也找不到。即使使用绝对的 XPath 表达式,如 /bracket/contestants/contestant[@seed='1'],也没有办法。因此,您需要将需要用到的节点存储为全局变量。清单 10 向您展示了在需要时如何访问全局变量。

  清单 10. 存储所需节点的全局变量

<xsl:variable name="results" select="/bracket/results"/>
<xsl:variable name="contestants" select="/bracket/contestants"/>

  如果您需要获取第 16 个 seed 竞争者的名称,XPath 表达式为 $contestants/contestant[@seed="16"]。您使用类似的方法访问 <result> 元素;如果您需要获取第 2 回合中第二场比赛的获胜者,表达式为 $results/result[@round="2"][2]/@winnerSeed。清单 11 给出了另外两个您可以用来生成 HTML 表的全局变量。

  清单 11. 其他有用的全局变量

<xsl:variable name="periods" as="xs:integer*">
 <xsl:sequence select="(4, 8, 16, 32, 64)"/>
</xsl:variable>
<xsl:variable name="backgroundColors" as="xs:string*">
 <xsl:sequence
  select="('background: #CCCCCC;', 'background: #9999CC;',
       'background: #99CCCC;', 'background: #CC99CC;',
       'background: #CCCC99;')"/>
</xsl:variable>

  这些变量存储了每一列的 period 值和背景颜色。要使用每个变量,您只需将列号用作您所需值的索引即可。

  XPath 2.0 和 XSLT 2.0 中另一个不错的增强是 <xsl:function> 元素。我们在样式表中创建两个函数:cellStyle 和 getResults。第一个函数返回每个单元格的边界样式,而第二个函数返回一场比赛结果(如果存在的话)。这两个函数的参数是单元格的行号和列号。清单 12 给出了 cellStyle 函数的代码。

  清单 12. cellStyle 函数

<xsl:function name="bracket:cellStyle" as="xs:string">
 <xsl:param name="row" as="xs:integer"/>
 <xsl:param name="column" as="xs:integer"/>
 
 <xsl:variable name="period" as="xs:integer"
  select="subsequence($periods, $column, 1)"/>
 <xsl:variable name="oneQuarter" as="xs:integer"
  select="$period div 4"/>
 <xsl:variable name="lastColumn" as="xs:boolean"
  select="$oneQuarter = count($contestants/contestant)"/>
 <xsl:variable name="position" select="$row mod $period"/>
 
 <xsl:value-of
  select="if ($position = $oneQuarter) then
        (if ($column = 1) then 'MatchupStart'
          else 'Solid')
      else if ($position = $period - $oneQuarter) then
           (if ($column = 1) then 'MatchupEnd'
             else 'Solid')
      else if ($lastColumn) then 'None'
      else if ($position &lt; $oneQuarter or
           $position &gt; $period - $oneQuarter) then 'None'
      else 'MatchupMiddle'"/>
</xsl:function>

  在您确定单元格样式之前,需要使用四个变量。您需要从全局变量 $periods 中检索 $period 的值。正如前面所提到的,$oneQuarter 就是 $period div 4。布尔值 $lastColumn 直接将 $oneQuarter 与锦标赛中竞争者的计数相比较。如果这些值相等,就处理上一列。最后,变量 $position 表示了模式中当前行的位置。换言之,对于第 1 和第 2 列,表中的第 9 行是重复组的第一行。使用模式中行的位置查看单元格的样式。

  XPath 2.0 和 XSLT 2.0 中提供了一个 if 运算符,它带有一个表达式(圆括号中),后接一个 then 和 else。所有这些内容都位于 <xsl:value-of> 的 select 属性中。在本例中,您使用了一个表达式替代冗长的 <xsl:choose> 元素。此处的代码很简单,给定了表的公式;如果逻辑更复杂一些,则可能使用 <xsl:choose> 更易于维护,不过那需要编写更多的代码。

  关于 <xsl:function> 的一些语法注意点:首先,您需要为函数声明新的名称空间。如果在 XPath 表达式中调用 bracket:cellStyle(),名称空间将告诉 XSLT 2.0 处理器如何查找函数。其次,注意,您使用 as="xs:string" 属性表明此函数返回一个字符串。<xsl:value-of> 元素返回五个样式名称中的一个;即函数的输出。

  getResults 函数稍微复杂一点,但不是很复杂,如 清单 13 所示。

  清单 13. getResults 函数

<xsl:function name="bracket:getResults" as="xs:string">
 <xsl:param name="row" as="xs:integer"/>
 <xsl:param name="column" as="xs:integer"/>
 
 <xsl:variable name="period" as="xs:integer"
  select="subsequence($periods, $column, 1)"/>
 <xsl:variable name="oneQuarter" as="xs:integer"
  select="$period div 4"/>
 <xsl:variable name="position" select="$row mod $period"/>
 
 <xsl:choose>
  <xsl:when test="$position = $oneQuarter
          or
          $position = $period - $oneQuarter">
   <xsl:variable name="round" select="$column - 1"/>
   <xsl:variable name="index"
    select="($row + $oneQuarter) div ($oneQuarter * 2)"/>
   <xsl:variable name="currentSeed"
    select="if ($column = 1) then
         subsequence($seeds, $index, 1)
        else
         $results/result[@round=$round][$index]/@winnerSeed"/>
   <xsl:choose>
    <xsl:when test="string-length(string($currentSeed))">
     <xsl:value-of
      select="concat('[', $currentSeed, '] ',
          $contestants/contestant[@seed=$currentSeed])"/>
    </xsl:when>
    <xsl:otherwise>
     <xsl:text>&#160;</xsl:text>
    </xsl:otherwise>
   </xsl:choose>
  </xsl:when>
  <xsl:otherwise>
   <xsl:text>&#160;</xsl:text>
  </xsl:otherwise>
 </xsl:choose>
</xsl:function>

  使用的参数与以前相同。这里需要对第 1 列做些特殊处理,该列中包含了来自 <contestant> 元素的数据,而其他列则包含了来自 <result> 元素的数据。与 cellStyle 函数一样,您将计算 $period 和 $position,并使用 $oneQuarter 变量简化公式。

  如果单元格中包含竞争者,则需要计算另外三个变量。$round 变量比列少一(第 2 列包含了第 1 回合的结果),您将根据先前讨论的公式计算 $index 变量。

  您需要执行两个步骤以查找适当的数据。首先,设置 $currentSeed 变量的值。如果是第 1 回合,则使用新的 subsequence 函数从 $seeds 变量中选择一个值。对于其他回合,则从适当的 <result> 元素中获取 winnerSeed 属性。

  其次,除了第 1 列的处理方法不同以外,您还需要考虑 <result> 元素没有获胜者的情况(winnerSeed="")。如果出现这种情况,则返回一个未破坏的空间(&#160;)。XSLT 2.0 处理数据类型的方式(将来有文章会讨论这个主题)要求您将 $currentSeed 转换为一个字符串,然后测试字符串的长度。如果字符串的长度为零,则返回一个未破坏的空间,否则,返回 seed 和竞争者的名称。

  最后要注意的是您在这里使用了 <xsl:choose>。 虽然您可以使用一个 XPath if 语句实现任何操作,但是代码显得有些笨拙。虽然 <xsl:choose> 有些冗长,但代码却更加整洁、更易于理解。

  现在您已经设置了所需的全局变量和函数,样式表的核心就显得简单优雅,如 清单 14 所示。

  清单 14. 样式表的核心

<xsl:for-each select="1 to 32">
 <xsl:variable name="outerIndex" select="."/>
 <tr>
  <xsl:for-each select="1 to 5">
   <td style="{subsequence($backgroundColors, ., 1)}"
     class="{bracket:cellStyle($outerIndex, .)}">
    <xsl:value-of
     select="bracket:getResults($outerIndex, .)"/>
   </td>
  </xsl:for-each>
 </tr>
</xsl:for-each>

  您使用了两个循环创建 32 行表的 5 列。对于每个单元格,背景色基于当前的列号。cellStyle 函数确定了边界样式(class="x"),而 getResults 函数确定了单元格的值。

  使用样式表

  如您所料,您需要一个 XSLT 2.0 处理器使用此样式表。这里我不会重新打印整个样式表,但是您必须使用 <xsl:stylesheet version="2.0"> 使处理器在 XSLT 2.0 模式下运行。我强烈建议使用 Michael Kay 的 Saxon 处理器(请参阅 参考资料 了解更多详细信息)。Dr. Kay 是 XSLT 2.0 规范的编辑,而 Saxon 在开发规范时在某种程度上是一个测试用例。如果您使用 Saxon,则使用此命令通过样式表 results-html.xsl 转换 XML 文件 tourney.xml 并将输出写入 results.html 文件:

  java net.sf.saxon.Transform -o results.html tourney.xml results-html.xsl

  要展示样式表的灵活性,则获取 XML 文件中的结果并运行转换。清单 15 展示了 <result> 元素的外观。

  清单 15. 带有不完整锦标赛数据的 XML 文档

<?xml version="1.0" encoding="UTF-8"?>
<!-- incomplete-tourney.xml -->
<bracket>
. . .
  <results>
   <result round="1" firstSeed="1" secondSeed="16" winnerSeed="1"/>
   <result round="1" firstSeed="8" secondSeed="9" winnerSeed="9"/>
   <result round="1" firstSeed="5" secondSeed="12" winnerSeed="5"/>
   <result round="1" firstSeed="4" secondSeed="13" winnerSeed="4"/>
   <result round="1" firstSeed="6" secondSeed="11" winnerSeed="11"/>
   <result round="1" firstSeed="3" secondSeed="14" winnerSeed="3"/>
   <result round="1" firstSeed="7" secondSeed="10" winnerSeed="10"/>
   <result round="1" firstSeed="2" secondSeed="15" winnerSeed="2"/>
   <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
   <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
   <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
   <result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
   <result round="3" firstSeed="" secondSeed="" winnerSeed=""/>
   <result round="3" firstSeed="" secondSeed="" winnerSeed=""/>
   <result round="4" firstSeed="" secondSeed="" winnerSeed=""/>
  </results>
</bracket>   

  清单 15 表示第一回合后锦标赛的状态。第 2、第 3 和第 4 回合的所有的 <result> 元素都有一个空的 winnerSeed 属性。尽管如此,样式表生成了正确的括弧,如 图 3 所示。

  图 3. 不完整锦标赛的括弧

使用 XPath 2.0 和 XSLT 2.0 节省开发时间并减少代码量

  结束语

  本文展示了 XPath 2.0 和 XSLT 2.0 的很多新特性。在示例应用程序中,您获取了一个笨拙的样式表并将其重构为一段更小更易于维护的代码。所做的部分工作用于分析代码以查找模式,但是如果没有 to 运算符、<xsl:function> 和 item 序列,简化样式表将非常困难。

  最重要的是,您创建了一些通用函数用于处理任意数量的竞争者。例如,要更改样式表以处理 32 支队伍参加的锦标赛,您需要更改 $seeds 和 $periods 的序列。您还需要使用 select="1 to $rounds" 替代 select="1 to 5",其中 $rounds 表示锦标赛的回合数。当然,最好的解决方案是创建 XSLT 2.0 函数,用于计算任何通用括弧的值,包括回合数、seed 序列(32 支队伍的括弧特性匹配,1和 32、2 和 31 等等)和各列的 period。这个问题留给读者做练习。

  问题的核心在于,您需要迭代表的 32 行,但是 XSLT 1.0 没有提供实际的实现方法。XPath 2.0 和 XSLT 2.0 的新特性可以帮助您解决这个问题。

来源:ibm    作者:Doug Tidwell    责编:豆豆技术应用

正在加载评论...