Показаны сообщения с ярлыком XSL. Показать все сообщения
Показаны сообщения с ярлыком XSL. Показать все сообщения

12.10.2008

XSL: применение шаблона с «внутренним» условием

Часто бывает нужно применить шаблон для элемента только если этот элемент (или его содержимое) удовлетворяет некоторому условию. Простой пример: применить шаблон для элемента <Brands> если внутри есть хотя бы один элемент <Brand>. Рассмотрим несколько вариантов как можно сделать эту проверку.

Вариант 1

<xsl:template match="Something">
    <xsl:if test="Brands/Brand">
        <xsl:apply-templates select="Brands"/>
    </xsl:if>
</xsl:template>

<xsl:template match="Brands">
    <h2>Производители</h2>
    <ul>
        <xsl:apply-templates select="Brand"/>
    </ul>
</xsl:template>

Неудачное решение, поскольку, написав проверку в шаблоне для <Something>, мы поместили в этот шаблон знание об особенности трансформации <Brands>.

Вариант 2

<xsl:template match="Something">
    <xsl:apply-templates select="Brands[Brand]"/>
</xsl:template>

<xsl:template match="Brands">
    <h2>Производители</h2>
    <ul>
        <xsl:apply-templates select="Brand"/>
    </ul>
</xsl:template>

Немного компактнее, но проблема первого варианта осталась.

Вариант 3

<xsl:template match="Something">
    <xsl:apply-templates select="Brands"/>
</xsl:template>

<xsl:template match="Brands">
    <xsl:if test="Brand">
        <h2>Производители</h2>
        <ul>
            <xsl:apply-templates select="Brand"/>
        </ul>
    </xsl:if>
</xsl:template>

Гораздо лучше. Шаблон для <Something> просто вызывает шаблон для <Brands>, не заморачиваясь о внутреннем устройстве <Brands>. Шаблон для <Brands> сам разбирается как трансформировать <Brands>.

Вариант 4

<xsl:template match="Something">
    <xsl:apply-templates select="Brands"/>
</xsl:template>

<xsl:template match="Brands"/>
<xsl:template match="Brands[Brand]">
    <h2>Производители</h2>
    <ul>
        <xsl:apply-templates select="Brand"/>
    </ul>
</xsl:template>

Я предпочитаю этот вариант. Он также хорошо работает и в случае с несколькими условиями — вместо громоздкого <xsl:choose> мы получаем компактные шаблончики.

<xsl:template match="Brands"/>

<xsl:template match="Brands[Brand]">
    <h2>Производители</h2>
    <ul>
        <xsl:apply-templates select="Brand"/>
    </ul>
</xsl:template>

<xsl:template match="Brands[count(Brand) > 10]">
    <h2>Много производителей</h2>
    <!-- Выводим бренды в три колонки -->
</xsl:template>

20.05.2008

Nightmare.xsl

Вчера ночью, засыпая, подумал: «Так, по какому XSL-шаблону сон выводить будем?» Крыша поехала, ага :)

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()]

21.03.2008

XSLT: sort

Используя <xsl:sort> нужно иметь в виду два момента.

— При хождении от текущего элемента по осям preceding-sibling и following-sibling, мы ходим по исходному дереву.

<xsl:for-each select="item">
   <xsl:sort select="name" />
   <!-- preceding-sibling и following-sibling работают по исходным,
        не отсортированным данным -->
</xsl:for-each>

position() после <xsl:sort> возвращает позицию в отсортированном дереве.