Несколько неочевидных моментов по XSLT.
1. avg, min, max
В XSLT нет агрегатных функций avg()
, min()
, max()
. Есть только count()
и sum()
.
Предположим, у нас есть такой XML:
<list>
<item>10</item>
<item>5</item>
<item>25</item>
</list>
Среднее значение можно посчитать так:
<xsl:variable name="avg" select="sum(/list/item) div count(/list/item)"/>
Минимум и максимум чуть сложнее. Бежим по отсортированным по возрастанию/убыванию нодам и берем первую:
<xsl:variable name="min">
<xsl:for-each select="/list/item">
<xsl:sort data-type="number" order="ascending"/>
<xsl:if test="position() = 1"><xsl:value-of select="."/></xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="max">
<xsl:for-each select="/list/item">
<xsl:sort data-type="number" order="descending"/>
<xsl:if test="position() = 1"><xsl:value-of select="."/></xsl:if>
</xsl:for-each>
</xsl:variable>
2. current()
current()
внутри квадратных скобок позволяет выйти из контекста ноды к которой применены эти скобки и вернуться к контексту текущей ноды (которая была заматчена шаблоном или заселекчена <xsl:for-each>
).
То есть вместо того, чтобы создавать переменную:
<xsl:template match="navigation">
<xsl:variable name="status" select="@status"/>
<xsl:value-of select="/captions/caption[@status = $status]"/>
</xsl:template>
можно написать так:
<xsl:template match="navigation">
<xsl:value-of select="/captions/caption[@status = current()/@status]"/>
</xsl:template>
3. concat()
Работает быстрее:
<xsl:value-of select="concat(@Name, ' ', @Surname)"/>
чем:
<xsl:value-of select="@Name"/>
<xsl:value-of select="' '"/>
<xsl:value-of select="@Surname"/>
Потому что в первом случае происходит только одна операция вставки в результирующее XML-дерево.
Не говоря уже о том, что первый вариант компактнее.
4. not()
vs. !=
В некоторых версиях XSLT-процессоров есть разница:
not(@name = 'Stepan')
<a name="Stepan"/> => false
<a name="Leechy"/> => true
<a/> => true
@name != 'Stepan'
<a name="Stepan"/> => false
<a name="Leechy"/> => true
<a/> => false
Пару раз когда использовал !=
сталкивался с тем, что вылезала бага при переносе кода с разработческого сервера на хостинг, где стоял другой XSLT-процессор. Поэтому рекомендую всегда использовать not()
.
5. Глобальные переменные → ускорение
Переменная Regions
вычисляется каждый раз при вызове шаблона для <MuscatRow>
:
<xsl:template match="MuscatRow">
<xsl:variable name="Regions" select="/Imprimatur/Element[@Name = 'Regions']/Document"/>
<xsl:value-of select="$Regions[@Name = current()/@Name]/content"/>
</xsl:template>
Используя глобальную переменную можно добиться некоторого ускорения. Переменная Regions
вычисляется один раз:
<xsl:variable name="Regions" select="/Imprimatur/Element[@Name = 'Regions']/Document"/>
<xsl:template match="MuscatRow">
<xsl:value-of select="$Regions[@Name = current()/@Name]/content"/>
</xsl:template>
Актуально при большом количестве <MuscatRow>
.
6. Отсчет preceding-sibling-ов
preceding-sibling[1] — это ближайший предыдущий элемент.
Получается вот что:
preceding-sibling[last()]
...
preceding-sibling[2]
preceding-sibling[1]
current item
following-sibling[1]
following-sibling[2]
...
following-sibling[last()]
6 комментариев:
Степан, не мог бы ты посоветовать хорошую книгу по XSLT?
В интернете ничего хорошего не нашел, разве что уровня "Hello world!".
Чувствую, что технология суперская, но не знаю откуда подступиться :-)
Могу порекомендовать классическую книгу Майкла Кэя XSLT. Справочник программиста
Также есть несколько неплохих ресурсов в интернете:
Пособие по XSLT (начни с этого)
Пособие по XPath
Справочник по XSLT
XSLT 2.0 решает - там и минимум и максимум есть (спецификацию XPath 2.0 почитайте)
not(name = 'Stepan') истинно, когда нет ни одной ноды name, равной 'Stepan'.
name != 'Stepan' истинно, когда 1) существует хоть одна нода name, 2) хотя бы одна нода name не равна 'Stepan' (при этом остальные ноды могут быть равны).
Если нода (или атрибут) не существует, то != всегда вернёт false, что соответствует спецификации.
Степан, у меня следущая задачка... Если есть желание, возможность и время подскажите.
Вот такой XML:
< basePrice>1500< /basePrice>
< units>
< unit price="5" qty="1">первый цена которого 5, кол-во 1< /unit>
< unit price="10" qty="2">второй цена которого 10, кол-во 2< /unit>
< unit price="15" qty="3">третий цена которого 15, кол-во 3< /unit>
< unit price="20" qty="4">четвертый цена которого 20, кол-во 4< /unit>
< /units>
А нужно посчитать сумму произведений. Тоесть общий результат должен посчитаться по формуле: basePrice + unitPrice1*unitQty1 + unitPrice2*unitQty2 +
unitPrice3*unitQty3 + unitPrice4*unitQty4 + ...
Подскажите как это можно посчитать
Это известная задача, решение можно поискать, набрав в Гугле "xslt sum of products".
Вот, например, несколько вариантов решения.
XSLT 2.0:
<xsl:sequence select="basePrice + sum(units/unit(price * qty))"/>
Или надо использовать расширение, например, EXSLT, в котором реализована функция node-set.
В XSLT 1.0 и без расширений, реализующих функцию node-set, задача решается с помощью рекурсии.
Отправить комментарий