#xslt
#xslt
Вопрос:
У меня есть 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>
Любая помощь будет высоко оценена
Ответ №1:
Решение 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. @_FailedDev: Это решение очень удобно в случае, если требуется только один из множества туров по минимальной цене. Мне кажется, что с помощью этого решения и без изменений для линейного поиска было бы невозможно вывести все туры с минимальной ценой. Что вы думаете?
2. @DimitreNovatchev Я основал свое решение на попытке OP, поскольку он использовал специальный идентификатор. Конечно, могут быть и более общие альтернативы — как вы указали 🙂
Ответ №2:
Я предлагаю в общей сложности три альтернативных решения, каждое из которых короткое и простое (без вложенности <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>