XSLT, сортировка и группировка по дате года

Что касается Umbraco XSLT версии 1.

У меня ок. 150 новостей в XML. Скажем так (все это псевдокод, пока я не познакомлюсь с этим 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.

Я могу придумать только вложенный для каждого (псевдокод):

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 записей.

Есть какие-нибудь подсказки о том, как сортировать и выводить множество новостей, группировать их по годам и сгруппировать каждый год по месяцам?

Br. Андерс


person Tillebeck    schedule 20.11.2009    source источник


Ответы (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>

У него уже есть правильная структура, вы можете адаптировать внешний вид к своим потребностям.

Решение - двухэтапный подход Мюнчи к группировке. На первом этапе новости группируются по годам, на втором этапе - по годам-месяцам.

См. Мое объяснение <xsl:key> и key() здесь. Вам не нужно читать другой вопрос, хотя это аналогичная проблема. Просто прочтите нижнюю часть моего ответа.

person Tomalak    schedule 20.11.2009
comment
Интересное использование функции document() (пустой URL-адрес возвращает документ XSL-преобразования - я ожидал получить вместо этого обрабатываемый документ). Это где-то задокументировано и переносимо ли это на разные механизмы XSLT, совместимые с 1.0? - person Lucero; 20.11.2009
comment
Это задокументированное стандартное поведение. Все процессоры будут вести себя так. - person Tomalak; 20.11.2009
comment
Вот это да. Большое спасибо. Я новичок в XSLT и предполагаю, что там будет встроенная функция или около того :-) Ваша работа кажется законченным решением, и я начал включать ваше решение на веб-страницу. Тем не менее, все еще в процессе, но я отмечу это как ответ и продолжу читать о группировке Мюнчи и интеграции вашего кода в мой макрос. BR и спасибо, Андерс - person Tillebeck; 23.11.2009
comment
Добро пожаловать. :) В XSLT 2.0 значительно улучшена группировка, она намного естественнее, чем в 1.0. Но насколько я понимаю, Umbraco не поддерживает 2.0, правда? - person Tomalak; 23.11.2009
comment
Да, Umbraco поддерживает только версию 1.0. И я слышал что-то о том, что Microsoft пытается протолкнуть свой LINQ2XMl вместо разработки поддержки XSLT 2.0 в инфраструктуру .net (на которой основан Umbraco), не знаю, правильно ли это, я просто пытаюсь передать вход, который я получил от программист :-) - person Tillebeck; 23.11.2009
comment
Зная, что искать (мюнчианцы), я наткнулся на эту ссылку с мюнхенским примером для Умбрако. Так что это должно быть возможно: наш. umbraco.org/wiki/how-tos/xslt-useful-tips-and-snippets/ - person Tillebeck; 23.11.2009
comment
Эта ваша ссылка демонстрирует группировку Мюнчи по умолчанию, ничего специфического для Умбрако. Любой совместимый с XSLT 1.0 движок может делать такие вещи. - person Tomalak; 23.11.2009

Вам нужен так называемый метод Мюнхийская группировка, который решает именно эту проблему. проблема / шаблон для XSLT.

По сути, он группирует, находя уникальные ключи и перебирая записи, содержащиеся в используемом ключе.

person Lucero    schedule 20.11.2009
comment
Проголосовав за это и отправившись что-то проверять, выясняется, что я на самом деле не использую этот метод - я делал что-то не так! Я использую грубое решение: ‹xsl: for-each select = // date [not (@ year = previous :: date / @ year)]› упаковка ‹xsl: for-each select = // date [@year = current () / @ year] [not (@ month = previous :: date [@year = current () / @ year] / @ month)] ›- но для размера данных, которые у меня есть (и у вас) это работает! - person Murph; 20.11.2009
comment
Спасибо за ссылку, Lureco. Я начал читать о мюнчанской группировке. И спасибо за ваш комментарий, Мерф, это хорошая основа для быстрого и грязного решения, с которого я начну, пока я не заставлю мюнчианскую группировку работать. BR. Андерс - person Tillebeck; 23.11.2009

в дополнение к lucero, ознакомьтесь с проблемой группировки дубликатов Xsl, чтобы избежать проблем с удалением названий месяцев

person Ivo    schedule 20.11.2009

Вы не можете использовать month_counter ++ в XSLT, это не процедурный язык и не так работает XSLT. Так что бесполезно беспокоиться о том, что это будет неэффективно, если это не сработает.

Похоже, это большая головная боль в XSLT. Мой XSLT недостаточно свеж, чтобы попытаться его реализовать. Но есть два пути:

1)

  • используйте xsl: key для извлечения всех уникальных лет -
  • then iterate through these years. For each year do
  • use xsl:key to extract all months
  • For each month do

2) (кажется проще, если работает.)

  • sort them by date, save sorted array in variable
  • повторить эту переменную (важно, чтобы переменная содержала отсортированный массив)
  • каждый раз смотрите на предшествующего брата. Если его год / месяц не равен текущему элементу, напишите соответствующий заголовок

3) Забудьте о XSLT, используйте настоящий язык программирования.

person yu_sha    schedule 20.11.2009