XSLT用来解析XML文档并按照规定的样式输出数据。在XSLT中,我们通常使用元素来遍历XML中的循环节点并输出内容,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/>存在重复的值,在输出的内容中将去掉这些具有重复节点。我们可以定义一个元素:
< xsl:key name =”distinctState” match =”addresses/address” use =”./state”></xsl:key> Key元素必须定义在元素xsl:template的外面,与元素xsl:template平级。在上面的key元素中,我们将key应用到addresses/address节点上,并规定该key的表达式为节点state的值。函数用于返回唯一标识指定节点的字符串值。然后,我们在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代码如下:
<? 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的使用中有许多的技巧,灵活掌握这些技巧可以大大缩短我们的开发时间。