在XSLT中对for-each语句使用distinct操作

  XSLT用来解析XML文档并按照规定的样式输出数据。在XSLT中,我们通常使用for-each元素来遍历XML中的循环节点并输出内容,for-each元素允许你对要遍历的节点进行排序,参考文章“xslt中的for-each排序”。可是,如何在使用for-each元素时对要遍历的节点进行distinct操作以消除重复节点呢?先看下面的XML片段:

< addresses >
   < address >
     < state >FL </ state >
   </ address >
   < address >
     < state >GA </ state >
   </ address >
   < address >
     < state >MN </ state >
   </ address >
   < address >
     < state >FL </ state >
   </ address >
</ addresses >

  如何编写XSLT代码让其输出为下面的内容?

< states >
   < state >FL </ state >
   < state >GA </ state >
   < state >MN </ state >
</ states >

  注意,上面的XML片段中,节点<address/>为重复节点,并且子节点<state/>存在重复的值,在输出的内容中将去掉这些具有重复节点。我们可以定义一个Key元素: 

< xsl:key  name =”distinctState”  match =”addresses/address”  use =”./state”></xsl:key>

   Key元素必须定义在元素xsl:template的外面,与元素xsl:template平级。在上面的key元素中,我们将key应用到addresses/address节点上,并规定该key的表达式为节点state的值。函数generate-id()用于返回唯一标识指定节点的字符串值。然后,我们在for-each元素中这样使用:

< xsl:key  name ="distinctState"  match ="addresses/address"  use ="./state" ></ xsl:key >

< xsl:template  match ="/" >
   < states >
       < xsl:for-each  select ="addresses/address[generate-id() = generate-id(key('distinctState', ./state))]" >
         < state >
           < xsl:value-of  select ="./state" ></ xsl:value-of >
         </ state >
       </ xsl:for-each >
   </ states >
</ xsl:template>

   key元素的表达式中也可以使用函数来进行更加精确的匹配,如:

< xsl:key  name ="distinctState"  match ="/Customers/Customer"  use ="substring(Address, string-length(Address)-1)" ></ xsl:key >

< xsl:template  match ="/" >
   < xsl:for-each  select ="Customers/Customer[generate-id() = generate-id(key('distinctState', substring(Address, (string-length(Address)-1))))]" >
     < xsl:call-template  name ="AggregateForState" >
       < xsl:with-param  name ="state"  select ="substring(Address, (string-length(Address)-1))" />
     </ xsl:call-template >
   </ xsl:for-each >
</ xsl:template >

  在看一个复杂点的例子,对XML元素进行分组输出: 

< items >
   < item >
     < name >name1 </ name >
     < group >group1 </ group >
   </ item >
   < item >
     < name >name2 </ name >
     < group >group1 </ group >
   </ item >
   < item >
     < name >name3 </ name >
     < group >group2 </ group >
   </ item >
   < item >
     < name >name4 </ name >
     < group >group2 </ group >
   </ item >
   < item >
     < name >name5 </ name >
     < group >group2 </ group >
   </ item >
   < item >
     < name >name6 </ name >
     < group >group1 </ group >
   </ item >
   < item >
     < name >name7 </ name >
     < group >group3 </ group >
   </ item >
   < item >
     < name >name8 </ name >
     < group >group3 </ group >
   </ item >
   < item >
     < name >name9 </ name >
     < group >group4 </ group >
   </ item >
   < item >
     < name >name10 </ name >
     < group >group1 </ group >
   </ item >
</ items >

  我们希望编写XSLT将上面的XML解析成下面的样子:

 

在XSLT中对for-each语句使用distinct操作

  完整的XSLT代码如下:

<? xml version="1.0" encoding="utf-8" ?>
< xsl:stylesheet  version ="1.0"  xmlns:xsl ="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl
="urn:schemas-microsoft-com:xslt"  exclude-result-prefixes ="msxsl"
>
   < xsl:output  method ="html"  indent ="yes"  omit-xml-declaration ="yes" />

   < xsl:key  name ="distinctState"  match ="items/item"  use ="./group" />

   < xsl:template  match ="/" >
     < xsl:variable  name ="tabStr" >
       < xsl:for-each  select ="items/item[generate-id()=generate-id(key('distinctState', ./group))]" >
         < xsl:value-of  select ="./group" />
         < xsl:if  test ="position()!=last()" >| </ xsl:if >
       </ xsl:for-each >
     </ xsl:variable >

    tabStr:  < xsl:value-of  select ="$tabStr" />
     < br />
     < br />
     < xsl:for-each  select ="items/item[generate-id()=generate-id(key('distinctState', ./group))]" >
       < xsl:variable  name ="tabName" >
         < xsl:call-template  name ="output-tokens" >
           < xsl:with-param  name ="list"  select ="$tabStr"   />
           < xsl:with-param  name ="separator" >| </ xsl:with-param >
           < xsl:with-param  name ="pos"  select ="position()" />
         </ xsl:call-template >
       </ xsl:variable >
       < xsl:value-of  select ="$tabName" />
       < br />
       < hr />
       < xsl:for-each  select ="//items/item" >
         < xsl:if  test ="./group = $tabName" >
           < xsl:value-of  select ="name" />
           < br />
         </ xsl:if >
       </ xsl:for-each >
       < br />
     </ xsl:for-each >
   </ xsl:template >

   < xsl:template  name ="output-tokens" >
     < xsl:param  name ="list"   />
     < xsl:param  name ="separator"   />
     < xsl:param  name ="pos"   />
     < xsl:variable  name ="newlist"  select ="concat(normalize-space($list), $separator)"   />
     < xsl:variable  name ="first"  select ="substring-before($newlist, $separator)"   />
     < xsl:variable  name ="remaining"  select ="substring-after($newlist, $separator)"   />

     < xsl:choose >
       < xsl:when  test ="$pos = 1" >
         < xsl:value-of  select ="$first" />
       </ xsl:when >
       < xsl:otherwise >
         < xsl:call-template  name ="output-tokens" >
           < xsl:with-param  name ="list"  select ="$remaining"   />
           < xsl:with-param  name ="separator"  select ="$separator"   />
           < xsl:with-param  name ="pos"  select ="$pos - 1" />
         </ xsl:call-template >
       </ xsl:otherwise >
     </ xsl:choose >
   </ xsl:template >

</ xsl:stylesheet >

  在上面的代码中,我们首先定义了元素key,并应用到节点items/item上,表达式为“./group”。变量tabStr用来存放以字符“|”分隔的group节点的值,并使用了distinct操作。接下来我们在页面上打印了变量tabStr的值。紧接着的for-each元素则将整个XML文档按照group分组进行输出。注意自定义的template “output-tokens”,在其中使用了一点技巧用来按不同的分组找出对应的分组名称,类似于C#中将字符串使用Split函数存放到数组中。有关output-tokens模板的技巧可以参考我的另一篇文章“在xslt中实现split方法对查询字符串进行分隔”。

  在XSLT的使用中有许多的技巧,灵活掌握这些技巧可以大大缩短我们的开发时间。

你可能感兴趣的:(distinct)