XSLT用来解析XML文档并按照规定的样式输出数据。在XSLT中,我们通常使用for-each元素来遍历XML中的循环节点并输出内容,for-each元素允许你对要遍历的节点进行排序,参考文章“xslt中的for-each排序”。可是,如何在使用for-each元素时对要遍历的节点进行distinct操作以消除重复节点呢?先看下面的XML片段:
< address >
< state >FL </ state >
</ address >
< address >
< state >GA </ state >
</ address >
< address >
< state >MN </ state >
</ address >
< address >
< state >FL </ state >
</ address >
</ addresses >
如何编写XSLT代码让其输出为下面的内容?
< state >FL </ state >
< state >GA </ state >
< state >MN </ state >
</ states >
注意,上面的XML片段中,节点<address/>为重复节点,并且子节点<state/>存在重复的值,在输出的内容中将去掉这些具有重复节点。我们可以定义一个Key元素:
Key元素必须定义在元素xsl:template的外面,与元素xsl:template平级。在上面的key元素中,我们将key应用到addresses/address节点上,并规定该key的表达式为节点state的值。函数generate-id()用于返回唯一标识指定节点的字符串值。然后,我们在for-each元素中这样使用:
< 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: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元素进行分组输出:
< 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代码如下:
< 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的使用中有许多的技巧,灵活掌握这些技巧可以大大缩短我们的开发时间。