Pull to refresh

Производительный и читабельный XSLT: сборник советов

Reading time 7 min
Views 10K
В моей практике чаще всего в качестве шаблонизатора используется именно XSLT. Я не буду рассуждать о том, почему так происходит — о преимуществах данной технологии написано вполне достаточно. Но ещё больше написано о её недостатках. Считается, что XSLT является слишком многословным и тяжёлым для чтения, а также не самым производительным. В этой статье я постараюсь собрать несколько советов по улучшению качества XSLT-кода с точки зрения читабельности и выразительности. Некоторые из них также позволят XSLT работать несколько быстрее.

Именованные шаблоны

Многие «проблемы» XSLT связаны с тем, что мы слишком часто пытаемся писать на нём в процедурном стиле. Мы постоянно пытаемся сделать из него Smarty, но упираемся в один простой факт — XSLT является декларативным языком, как бы необычно это для нас не выглядело.
Например, мы пытаемся использовать именованные шаблоны, воспринимая их как процедуры, выводящие данные в определённом формате:

<xsl:template name="CreateItemLink">
    <xsl:param name="item"/>
    <a href="/item/?id={$item/id}">
        <xsl:value-of select="$item/name"/>
    </a><br/>
</xsl:template>


* This source code was highlighted with Source Code Highlighter.

Наверное, многие программисты именно так написали свой первый шаблон. И он неплохо решает свою задачу. Декларативный XSLT предлагает немного другой подход:

<xsl:template match="item">
    <a href="/item/?id={id}">
        <xsl:value-of select="name"/>
    </a><br/>
</xsl:template>


* This source code was highlighted with Source Code Highlighter.

Разница совсем не велика. Дело вкуса и стиля программирования. Давайте посмотрим, как шаблон будет использоваться в дальнейшем.


Xsl:for-each и xsl:apply-templates

«Императивную» версию нашего шаблона мы бы стали использовать примерно так:

<xsl:template match="/">
    <h1>My market</h1>
    <xsl:for-each select="/root/market/item">
        <xsl:call-template name="CreateItemLink">
            <xsl:with-param name="item" select="."/>
        </xsl:call-template>
    </xsl:for-each>
</xsl:template>


* This source code was highlighted with Source Code Highlighter.

А «декларативную» — так:

<xsl:template match="/">
    <h1>My market</h1>
    <xsl:apply-templates select="/root/market/item"/>
</xsl:template>


* This source code was highlighted with Source Code Highlighter.

Теперь разница стала куда заметнее:
  • Больше нет «нагромождения тегов», которым так славится XSLT.
  • Больше нет цикла xsl:for-each — его делает за нас XSLT-процессор, который может сделать существенную оптимизацию.
  • Наш код сильно уменьшился по объёму — не критично, но приятно.

Xsl:Choose

Xsl:choose, пожалуй, одна из самых многословных конструкций в XSLT. Очень часто она используется примерно так:

<xsl:template match="product">
    ...
    <xsl:choose>
        <xsl:when test="currencyCode = 'eur'">
            <xsl:value-of select="'Euros'"/>
        </xsl:when>
        <xsl:when test="currencyCode = 'usd'">
            <xsl:value-of select="'Dollars'"/>
        </xsl:when>
        <xsl:when test="currencyCode = 'cad'">
            <xsl:value-of select="'Canadian dollars'"/>
        </xsl:when>
        ...
    </xsl:choose>
    ...
</xsl:template>


* This source code was highlighted with Source Code Highlighter.

Бывают случаи, когда её использовать просто необходимо. Но в данном примере, гораздо правильнее использовать внешний документ со словарём:

<?xml version="1.0"?>
<currencies>
    <currency>
        <code>eur</code>
        <name>Euros</name>
    </currency>
    <currency>
        <code>usd</code>
        <name>Dollars</name>
    </currency>
    <currency>
        <code>cad</code>
        <name>Canadian dollars</name>
    </currency>
    ...
</currencies>


* This source code was highlighted with Source Code Highlighter.

Обратиться к нему из шаблона можно с помощью функции document() и XPath:

<xsl:variable name="currencies"
    select="document('cur.xml')/currencies"/>

<xsl:template match="product">
    ...
    <xsl:value-of select="$currencies/currency[code=currencyCode]/name"/>
    ...
</xsl:template>


* This source code was highlighted with Source Code Highlighter.

Мне кажется, что такой код гораздо легче поддерживать и он является идеологически более правильным — данные должны оставаться данными, а не превращаться в код, который сложно прочесть.

Встроенные преобразования

Многие не знают, что в XSLT есть несколько «встроенных» преобразований, которые делают за программиста часть работы. Эти преобразования делаются на уровне XSLT-процессора, т.ч. в некоторых реализациях могут работать значительно производительнее «ручных».
Например, за счёт встроенных преобразований осуществляется рекурсивное применение шаблона к потомкам текущего узла. Это равносильно такому шаблону:

<xsl:template match="*|/">
    <xsl:apply-templates/>
</xsl:template>


* This source code was highlighted with Source Code Highlighter.

Причём, если в apply-templates используется mode, то дочерние узлы тоже будут преобразовываться с этим mode. Другой пример, это автоматический вывод текстовых узлов и атрибутов:

<xsl:template match="text()|@*">
    <xsl:value-of select="."/>
</xsl:template>


* This source code was highlighted with Source Code Highlighter.

Это значит, что вместо

<xsl:template match="name">
    <name><xsl:value-of select="."/></name>
</xsl:template>


* This source code was highlighted with Source Code Highlighter.

можно использовать

<xsl:template match="name">
    <name><xsl:apply-templates/></name>
</xsl:template>


* This source code was highlighted with Source Code Highlighter.

Это (возможно) будет работать быстрее и точно сделает наши шаблоны более гибкими и расширяемыми.
Раздел в стандарте.

Функция concat()

Больше всего при чтении шаблонов меня раздражает подобный код:

<xsl:value-of select="$string1" />
<xsl:text> - </xsl:text>
<xsl:value-of select="$string2" />
<xsl:text>...</xsl:text>


* This source code was highlighted with Source Code Highlighter.

В таких случаях гораздо удобнее использовать один <xsl:value-of/>, а всё остальное форматирование производить с помощью concat().

<xsl:value-of select="concat($string1, ' - ', $string2, '...')" />

* This source code was highlighted with Source Code Highlighter.

Вообще в XPath есть много встроенных функции, которые можно использовать для упрощения кода. Неплохую документацию с примерами можно найти здесь. Особенно стоит обратить внимание на substring(), translate() и т.п. Эти функции не стоит реализовывать на PHP или другом внешнем языке — XSLT-процессор всё равно сделает это лучше.

Используем xsl:element по назначению

Xsl:element нужен исключительно в случаях, когда название элемента определяется динамически.

<xsl:variable name="s">hello</xsl:variable>
<xsl:element name="{$s}">
    <xsl:attribute name="site">habr.ru</xsl:attribute>
    <xsl:value-of select="{concat('Привет ', $username)}"/>
</xsl:element>


* This source code was highlighted with Source Code Highlighter.

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

<hello site="http://habr.ru/">
    <xsl:value-of select="{concat('Привет ', $username)}"/>
</hello>


* This source code was highlighted with Source Code Highlighter.

Я всё ещё собираю подобные советы, т.ч. если у вас есть, что добавить, обязательно отпишитесь в комментах.
Tags:
Hubs:
+58
Comments 51
Comments Comments 51

Articles