XSLT - группировка и сортировка по атрибутам - PullRequest
2 голосов
/ 11 октября 2011

У меня есть xml, который содержит цены на разные даты

<tour id="12314">
  <available date="2012-04-19" price="533" /> 
  <available date="2012-05-09" price="670" /> 
  <available date="2012-05-25" price="600" /> 
  <available date="2012-06-05" price="710" /> 
  <available date="2012-06-08" price="710" /> 
  <available date="2012-06-15" price="710" /> 
  <available date="2012-06-20" price="705" /> 
</tour>

Мое требование - получить узлы, которые имеют самую низкую цену за каждый месяц, используя XSLT. пример: желаемый результат:

 <available dt="2012-04-19" price="533" /> 
 <available dt="2012-05-25" price="600" /> 
 <available dt="2012-06-20" price="705" /> 

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

<xsl:for-each select="tour[@id='12314']/available">
    <xsl:sort select="substring(@dt,1,7)"/>
    <xsl:sort select="@price"/>
    <!-- I would like to access the available node which has the cheapest price for each month -->
</xsl:for-each>

Любая помощь будет высоко ценится

Ответы [ 2 ]

2 голосов
/ 11 октября 2011

XSLT 1.0 Решение, использующее мюнхенский метод и простую сортировку:

  <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="xml" indent="yes"/>

  <xsl:key name="months" match="available" use="substring(@date, 6, 2)"/>

  <xsl:template match="/tour[@id = '12314']">
    <result>
      <xsl:for-each select="./available[generate-id() = generate-id(key('months',substring(@date, 6, 2) )[1])]">
        <xsl:for-each select="key('months',substring(@date, 6, 2))">
          <xsl:sort select="@price" order="ascending"/>
          <xsl:if test="position() = 1">
            <xsl:copy-of select="current()"/>
          </xsl:if>
        </xsl:for-each>
      </xsl:for-each>
    </result>
  </xsl:template>
</xsl:stylesheet>

Когда к этому xml применяется вышеуказанное преобразование:

 <tour id="12314">
  <available date="2012-04-19" price="533" /> 
  <available date="2012-05-09" price="670" /> 
  <available date="2012-05-25" price="600" /> 
  <available date="2012-06-05" price="710" /> 
  <available date="2012-06-08" price="710" /> 
  <available date="2012-06-15" price="710" /> 
  <available date="2012-06-20" price="705" /> 
</tour>

Вывод:

<?xml version="1.0" encoding="UTF-8"?>
<result>
   <available date="2012-04-19" price="533"/>
   <available date="2012-05-25" price="600"/>
   <available date="2012-06-20" price="705"/>
</result>

Логика: сначала я группирую доступные узлы на основе подстроки атрибута @date, а затем для каждого из этих уникальных месяцев собираю все доступные узлы, сортирую их по возрастанию и просто печатаю узел с минимальной ценой, которая составляетОпределение 1-го узла обусловлено сортировкой.Надеюсь, это помогло:)

1 голос
/ 11 октября 2011

Я предлагаю в общей сложности три альтернативных решения каждое короткое и простое (без вложенных <xsl:for-each> и без сортировки). Если это возможно, я бы рекомендовал использовать решение XSLT 2.0.

I. Два альтернативных решения XSLT 1.0:

1. Без ключей :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="available">
  <xsl:if test=
  "not(@price
      >
       ( preceding-sibling::available
       |
        following-sibling::available
        )
           [substring(@date, 1, 7)
           =
            substring(current()/@date, 1, 7)
           ]
           /@price
       )">
   <xsl:call-template name="identity"/>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

2. Использование клавиш :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kDateByMonth" match="available"
          use="substring(@date, 1, 7)"/>

 <xsl:template match=
  "available
      [generate-id()
      =
       generate-id(key('kDateByMonth',
                       substring(@date, 1, 7)
                       )[1]
                   )
      ]
  ">
  <xsl:variable name="vsameMonth" select=
   "key('kDateByMonth',
         substring(@date, 1, 7)
         )
   "/>

  <xsl:copy-of select=
    "$vsameMonth[not(@price > $vsameMonth/@price)][1]
    "/>
 </xsl:template>
</xsl:stylesheet>

когда любое из двух приведенных выше преобразований применяется к предоставленному документу XML :

желаемый, правильный результат выдается :

<tour id="12314">
   <available date="2012-04-19" price="533"/>
   <available date="2012-05-25" price="600"/>
   <available date="2012-06-20" price="705"/>
</tour>

Примечание : В вопросе не было указано, что выводить, если более одного тура в месяц имеют одинаковую минимальную цену. Первое преобразование будет выводить все такие туры (и, вероятно, даст выбор читателю), а второе преобразование выводит только один такой тур в месяц. Оба преобразования могут быть изменены для реализации другого поведения.

II. Решение XSLT 2.0 :

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/*">
  <xsl:for-each-group select="*"
                       group-by="substring(@date, 1,7)">
    <xsl:copy-of select=
     "current-group()
        [@price
        =
         min(current-group()/@price/number())
         ]
         [1]"/>
  </xsl:for-each-group>
 </xsl:template>
</xsl:stylesheet>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...