代码之家  ›  专栏  ›  技术社区  ›  Toni Frankola

计算XSLT中的关键字数

  •  0
  • Toni Frankola  · 技术社区  · 15 年前

    假设我有以下XML结构:

    <entry>
      <countries>USA, Australia, Canada</countries>  
    </entry>
    <entry>
      <countries>USA, Australia</countries>
    </entry>
    <entry>
      <countries>Australia, Belgium</countries>
    </entry>
    <entry>
      <countries>Croatia</countries>
    </entry>
    

    我想统计一下这些条目中出现的每个国家的实例数。我只能使用客户端XSLT(不允许自定义服务器代码)。最终结果需要如下所示:

    Country    | Count
    -----------|--------
    Australia  |     3
    USA        |     2
    Belgium    |     1
    Canada     |     1
    Croatia    |     1
    

    正如Mike指出的,这种XML结构可以改进,但它是由第三方系统生成的,我无法更改它。

    有可能实现这种XSLT吗?如果有,如何实现?

    3 回复  |  直到 15 年前
        1
  •  2
  •   Tomalak    15 年前

    在XSLT1.0中,最好使用两步方法。

    1. 将逗号分隔的输入标记为单独的元素
    2. 分组讨论不同的元素

    步骤#1将输入标记化:

    <xsl:stylesheet
      version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    >
    
      <xsl:template match="/root">
        <countries>
          <xsl:apply-templates select="entry" />
        </countries>
      </xsl:template>
    
      <xsl:template match="entry">
        <xsl:call-template name="tokenize">
          <xsl:with-param name="input" select="countries" />
        </xsl:call-template>
      </xsl:template>
    
      <xsl:template name="tokenize">
        <xsl:param name="input" />
    
        <xsl:variable name="list" select="concat($input, ',')" />
        <xsl:variable name="head" select="substring-before($list, ',') " />
        <xsl:variable name="tail" select="substring-after($list, ',') " />
    
        <xsl:if test="normalize-space($head) != ''">
          <country>
            <xsl:value-of select="normalize-space($head)" />
          </country>
          <xsl:call-template name="tokenize">
            <xsl:with-param name="input" select="$tail" />
          </xsl:call-template>
        </xsl:if>
      </xsl:template>
    
    </xsl:stylesheet>
    

    生产:

    <countries>
      <country>USA</country>
      <country>Australia</country>
      <country>Canada</country>
      <country>USA</country>
      <country>Australia</country>
      <country>Australia</country>
      <country>Belgium</country>
      <country>Croatia</country>
    </countries>
    

    第2步将Muenchian分组应用于中间结果:

    <xsl:stylesheet 
      version="1.0" 
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    >
      <xsl:output method="text" />
    
      <xsl:key name="kCountry" match="country" use="." />
    
      <xsl:template match="/countries">
        <xsl:apply-templates select="country">
          <xsl:sort select="count(key('kCountry', .))" data-type="number" order="descending" />
          <xsl:sort select="." data-type="text" order="ascending" />
        </xsl:apply-templates>
      </xsl:template>
    
      <xsl:template match="country">
        <xsl:if test="generate-id() = generate-id(key('kCountry', .)[1])">
          <xsl:value-of select="." />
          <xsl:text>&#9;</xsl:text>
          <xsl:value-of select="count(key('kCountry', .))" />
          <xsl:text>&#10;</xsl:text>
        </xsl:if>
      </xsl:template>
    
    </xsl:stylesheet>
    

    生成想要的结果(格式设置留给读者作为练习):

    Australia  3
    USA        2
    Belgium    1
    Canada     1
    Croatia    1
    

    过程 可以 借助于 node-set() 扩展功能。但是,您将无法使用XSL键,这可能会导致大型输入的性能降低。YMMV。

    对步骤#1的必要修改是(使用MSXSL扩展,其他供应商在名称空间声明上有所不同,这降低了这种方法的可移植性):

    <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:template match="/root">
        <!-- store the list of <country>s as a result-tree-fragment -->
        <xsl:variable name="countries">
          <xsl:apply-templates select="entry" />
        </xsl:variable>
        <!-- convert the result-tree-fragment to a usable node-set -->
        <xsl:variable name="country" select="msxsl:node-set($countries)/country" />
    
        <!-- iteration, sorting and grouping in one step -->
        <xsl:for-each select="$country">
          <xsl:sort select="count($country[. = current()])" data-type="number" order="descending" />
          <xsl:sort select="." data-type="text" order="ascending" />
          <xsl:if test="generate-id() = generate-id($country[. = current()][1])">
            <xsl:value-of select="." />
            <xsl:text>&#9;</xsl:text>
            <xsl:value-of select="count($country[. = current()])" />
            <xsl:text>&#10;</xsl:text>
          </xsl:if>
        </xsl:for-each>
      </xsl:template>
    
      <!-- ... the remainder of the stylesheet #1 is unchanged ... -->
    
    </xsl:stylesheet>
    

    使用这种方法,不需要单独的第2步。结果与上述相同。对于较小的输入,性能上的差异不会明显。

        2
  •  0
  •   Community CDub    8 年前

    您不使用该格式的原因是:

    <entry>
      <countries>
        <country>USA</country>
        <country>Australia</country>
        <country>Canada</country>
      </countries>  
    </entry>
    

    您当前的方式与XML数据的存储方式并不匹配。

    正如您所说,您不能更改数据格式,请尝试以下组合: tokenize() count() ( 如果你有XSLT2支持,否则我认为你运气不好 ).

        3
  •  0
  •   Lloyd    15 年前
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:str="http://exslt.org/strings" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="xsl str msxsl" version="1.0">
      <xsl:import href="str.split.template.xsl"/>
      <xsl:output indent="yes"/>
    
      <xsl:template match="/">
        <xsl:variable name="countries">
          <xsl:call-template name="get-counties" />
        </xsl:variable>
    
        <table>
        <xsl:for-each select="msxsl:node-set($countries)/country[not(. = preceding::country)]">
          <xsl:variable name="name" select="./text()"/>
          <tr>
            <td>
              <xsl:value-of select="$name" />
            </td>
            <td>
              <xsl:value-of select="count(msxsl:node-set($countries)/country[. = $name])" />
            </td>
          </tr>
        </xsl:for-each>
        </table>
      </xsl:template>
    
      <xsl:template name="get-counties">
        <xsl:for-each select="//countries">
          <xsl:variable name="countries-raw">
            <xsl:call-template name="str:split">
              <xsl:with-param name="string" select="text()"/>
              <xsl:with-param name="pattern" select="','" />
            </xsl:call-template>
          </xsl:variable>
    
          <xsl:for-each select="msxsl:node-set($countries-raw)/token">
            <country>
              <xsl:value-of select="normalize-space(.)"/>
            </country>
          </xsl:for-each>
        </xsl:for-each>
      </xsl:template>
    </xsl:stylesheet>
    

    str.split。样板xsl是EXSLT的str模块的一部分( http://www.exslt.org/download.html ).