XSLT,按年份排序和分组

Posted

技术标签:

【中文标题】XSLT,按年份排序和分组【英文标题】:XSLT, sort and group by year-date 【发布时间】:2010-12-18 17:29:21 【问题描述】:

关于 Umbraco XSLT 版本 1。

我有大约。 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 输出创建为新闻存档。类似的东西(标签不重要):

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>

但一位程序员指出,循环 10 年将产生 120 次循环,这是糟糕的编码。因为我认为 Umbraco 缓存了结果,所以我不太担心,而且在这种情况下会有一个最大值。 150 条记录。

任何关于如何排序和输出许多新闻项目并将它们按年分组并将每年按月分组的任何线索?

兄弟。安德斯

【问题讨论】:

【参考方案1】:

对于以下解决方案,我使用了这个 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 分组方法。在第一阶段,新闻项目按年分组,在第二阶段按年月分组。

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

【讨论】:

document() 函数的有趣用法(空 URL 返回 XSL 转换文档 - 我希望得到正在处理的文档)。这是否记录在某个地方并且可以跨不同的 1.0 兼容 XSLT 引擎移植? 这是记录在案的标准行为。所有处理器的行为都会如此。 哇。非常感谢。我是 XSLT 的新手,猜想会有一个内置函数左右:-) 你的工作似乎是一个完整的解决方案,我已经开始在网页上包含你的解决方案。虽然仍在进行中,但我会将其标记为答案并继续阅读有关 Muenchian 分组并将您的代码集成到我的宏中的内容。 BR,谢谢,安德斯 不客气。 :) XSLT 2.0 在分组方面得到了很大改进,它比 1.0 更自然。但据我了解,Umbraco 是不支持 2.0 的吧? 是的,Umbraco 仅支持 1.0 版。我听说微软试图将他们的 LINQ2XMl 推向 .net 框架(Umbraco 所基于)而不是开发对 XSLT 2.0 的支持,不知道它是否正确,我只是尝试传递我从一个程序员:-)【参考方案2】:

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

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

【讨论】:

对此投了赞成票并去检查了一些东西,结果发现我实际上并没有使用该方法-我做的不对!我正在运行粗略的解决方案: 包装 - 但是对于我拥有的数据大小(和你有)它有效! 感谢您的链接,Lureco。我已经开始阅读有关 Muenchian 分组的内容。感谢您的评论 Murph,这是我将开始使用的“快速和肮脏”解决方案的良好基础,直到我让“Muenchian 分组”工作。 BR。安德斯【参考方案3】:

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

【讨论】:

【参考方案4】:

您不能在 XSLT 中使用 month_counter++,它不是一种过程语言,也不是 XSLT 的工作方式。所以,如果这不起作用,那么担心效率低下是没有意义的。

这看起来像是 XSLT 中的一大痛点。我的 XSLT 还不够新鲜,无法尝试并实际实现它。但这里有两种方法:

1)

使用 xsl:key 提取所有唯一年份- 然后遍历这些年。每年做 使用 xsl:key 提取所有月份 每个月做

2)(如果可行的话,似乎更容易。)

按日期排序,将排序后的数组保存在变量中 迭代这个变量(变量保存排序数组很重要) 每次查看前面的兄弟姐妹。如果其年/月不等于当前元素,则编写相应的标题

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

【讨论】:

以上是关于XSLT,按年份排序和分组的主要内容,如果未能解决你的问题,请参考以下文章

s-s-rS 按年份组总计排序

WordPress 查询:按年份 DESC 对帖子进行分组,然后在每个组中按月 ASC 发布帖子

SQL: 一般情况按年分组,特殊年份按指定日期分组,SELECT语句怎么写?

DB2,按年份分组和求和,其中年份是列值

在python中按特定年份对数据进行分组

SQL 按年份分组给出不正确的结果