XSLT,按年份排序和分组

时间:2009-11-20 13:20:41

标签: xslt sorting umbraco foreach xslt-grouping

关于Umbraco XSLT版本1。

我有aprox。 XML中的150个新闻项目。让我们这样说(所有都是伪代码,直到我对这个xml / xslt更熟悉):

<news>
  <data alias=date>2008-10-20</data>
</news>
<news>
  <data alias=date>2009-11-25</data>
</news><news>
  <data alias=date>2009-11-20</data>
</news> etc. etc....

我想运行XML并将html-output创建为新闻档案。像(标签不重要)的东西:

2008
  Jan
  Feb
  ...
2009
  Jan
  Feb
  Mar
  etc. etc.

我只能想出一个嵌套的for-each(伪代码):

var year_counter = 2002
var month_counter = 1
<xsl:for-each select="./data [@alias = 'date']=year_counter">
  <xsl:for-each select="./data [@alias = 'date']=month_counter">
    <xsl:value-of select="data [@alias = 'date']>
  "...if month_counter==12 end, else month_counter++ ..."
  </xsl:for-each>
"... year_counter ++ ..."
</xsl:for-each>

但程序员pointet循环了10年将会产生120个循环,这是糟糕的编码。因为我认为Umbraco缓存结果我并不那么担心,加上在这种情况下会有最大值。 150条记录。

有关如何对每个新闻项目进行排序和输出以及每年在几年内对其进行分组和分组的任何线索?

溴。安德斯

4 个答案:

答案 0 :(得分:4)

对于以下解决方案,我使用了这个XML文件:

<root>
  <news>
    <data alias="date">2008-10-20</data>
  </news>
  <news>
    <data alias="date">2009-11-25</data>
  </news>
  <news>
    <data alias="date">2009-11-20</data>
  </news>
  <news>
    <data alias="date">2009-03-20</data>
  </news>
  <news>
    <data alias="date">2008-01-20</data>
  </news>
</root>

和这个XSLT 1.0转换:

<xsl:stylesheet 
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:cfg="http://tempuri.org/config"
  exclude-result-prefixes="cfg"
>
  <xsl:output method="xml" encoding="utf-8" />

  <!-- index news by their "yyyy" value (first 4 chars) -->
  <xsl:key 
    name="kNewsByY"  
    match="news" 
    use="substring(data[@alias='date'], 1, 4)" 
  />
  <!-- index news by their "yyyy-mm" value (first 7 chars) -->
  <xsl:key 
    name="kNewsByYM" 
    match="news" 
    use="substring(data[@alias='date'], 1, 7)" 
  />

  <!-- translation table (month number to name) -->
  <config xmlns="http://tempuri.org/config">
    <months>
      <month id="01" name="Jan" />
      <month id="02" name="Feb" />
      <month id="03" name="Mar" />
      <month id="04" name="Apr" />
      <month id="05" name="May" />
      <month id="06" name="Jun" />
      <month id="07" name="Jul" />
      <month id="08" name="Aug" />
      <month id="09" name="Sep" />
      <month id="10" name="Oct" />
      <month id="11" name="Nov" />
      <month id="12" name="Dec" />
    </months>
  </config>

  <xsl:template match="root">
    <xsl:copy>
      <!-- group news by "yyyy" -->
      <xsl:apply-templates mode="year" select="
        news[
          generate-id()
          =
          generate-id(key('kNewsByY', substring(data[@alias='date'], 1, 4))[1])
        ]
      ">
        <xsl:sort select="data[@alias='date']" order="descending" />
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

  <!-- year groups will be enclosed in a <year> element -->
  <xsl:template match="news" mode="year">
    <xsl:variable name="y" select="substring(data[@alias='date'], 1, 4)" />
    <year num="{$y}">
      <!-- group this year's news by "yyyy-mm" -->
      <xsl:apply-templates mode="month" select="
        key('kNewsByY', $y)[
          generate-id() 
          =
          generate-id(key('kNewsByYM', substring(data[@alias='date'], 1, 7))[1])
        ]
      ">
        <xsl:sort select="data[@alias='date']" order="descending" />
      </xsl:apply-templates>
    </year>
  </xsl:template>

  <!-- month groups will be enclosed in a <month> element -->
  <xsl:template match="news" mode="month">
    <xsl:variable name="ym" select="substring(data[@alias='date'], 1, 7)" />
    <xsl:variable name="m" select="substring-after($ym, '-')" />
    <!-- select the label of the current month from the config -->
    <xsl:variable name="label" select="document('')/*/cfg:config/cfg:months/cfg:month[@id = $m]/@name" />
    <month num="{$m}" label="{$label}">
      <!-- process news of the current "yyyy-mm" group -->
      <xsl:apply-templates select="key('kNewsByYM', $ym)">
        <xsl:sort select="data[@alias='date']" order="descending" />
      </xsl:apply-templates>
    </month>
  </xsl:template>

  <!-- for the sake of this example, news elements will just be copied -->
  <xsl:template match="news">
    <xsl:copy-of select="." />
  </xsl:template>
</xsl:stylesheet>

应用转换时,会生成以下输出:

<root>
  <year num="2009">
    <month num="11" label="Nov">
      <news>
        <data alias="date">2009-11-25</data>
      </news>
      <news>
        <data alias="date">2009-11-20</data>
      </news>
    </month>
    <month num="03" label="Mar">
      <news>
        <data alias="date">2009-03-20</data>
      </news>
    </month>
  </year>
  <year num="2008">
    <month num="10" label="Oct">
      <news>
        <data alias="date">2008-10-20</data>
      </news>
    </month>
    <month num="01" label="Jan">
      <news>
        <data alias="date">2008-01-20</data>
      </news>
    </month>
  </year>
</root>

它已经具有正确的结构,您可以根据自己的需要调整实际外观。

解决方案是两阶段Muenchian分组方法。在第一阶段,新闻项目按年份分组,第二阶段按年度分组。

请参阅我对<xsl:key>key() over here的解释。您不需要 来阅读其他问题,尽管这是一个类似的问题。请阅读我的答案的下半部分。

答案 1 :(得分:2)

您需要的是所谓的Muenchian Grouping方法,它正好解决了XSLT的这个问题/模式。

基本上,它通过查找唯一键并循环使用的键中包含的条目进行分组。

答案 2 :(得分:0)

除了lucero之外,请查看Xsl grouping duplicates problem以避免月名被删除的问题

答案 3 :(得分:0)

你不能在XSLT中使用month_counter ++,它不是一种过程语言,也不是XSLT的工作方式。因此,如果不能以这种方式工作,那么担心效率低下是毫无意义的。

这看起来像XSLT中的主要痛苦。我的XSLT还不够新鲜,无法尝试实际实现它。但这有两种方式:

1)

  • 使用xsl:key提取所有独特年份 -
  • 然后迭代这些年。做每年
  • 使用xsl:key提取所有月份
  • 每个月做一次

2)(如果有效,似乎更容易。)

  • 按日期对它们进行排序,将已排序的数组保存在变量
  • 迭代这个变量(变量保存排序数组很重要)
  • 每次看前兄弟。如果其年/月不等于当前元素,请编写相应的标题

3)忘记XSLT,使用真正的编程语言。