在XSLT中对for-each语句使用distinct操作
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的使用中有许多的技巧,灵活掌握这些技巧可以大大缩短我们的开发时间。