26.03.2008

XSLT: сборная солянка

Несколько неочевидных моментов по 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, что соответствует спецификации.

Unknown комментирует...

Степан, у меня следущая задачка... Если есть желание, возможность и время подскажите.
Вот такой 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, задача решается с помощью рекурсии.