在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代码如下:

<?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的使用中有许多的技巧,灵活掌握这些技巧可以大大缩短我们的开发时间。

posted @ 2011-11-28 11:21  Jaxu  阅读(4536)  评论(8编辑  收藏  举报