В моей практике чаще всего в качестве шаблонизатора используется именно XSLT. Я не буду рассуждать о том, почему так происходит — о преимуществах данной технологии написано вполне достаточно. Но ещё больше написано о её недостатках. Считается, что XSLT является слишком многословным и тяжёлым для чтения, а также не самым производительным. В этой статье я постараюсь собрать несколько советов по улучшению качества XSLT-кода с точки зрения читабельности и выразительности. Некоторые из них также позволят XSLT работать несколько быстрее.
Многие «проблемы» XSLT связаны с тем, что мы слишком часто пытаемся писать на нём в процедурном стиле. Мы постоянно пытаемся сделать из него Smarty, но упираемся в один простой факт — XSLT является декларативным языком, как бы необычно это для нас не выглядело.
Например, мы пытаемся использовать именованные шаблоны, воспринимая их как процедуры, выводящие данные в определённом формате:
Наверное, многие программисты именно так написали свой первый шаблон. И он неплохо решает свою задачу. Декларативный XSLT предлагает немного другой подход:
Разница совсем не велика. Дело вкуса и стиля программирования. Давайте посмотрим, как шаблон будет использоваться в дальнейшем.
«Императивную» версию нашего шаблона мы бы стали использовать примерно так:
А «декларативную» — так:
Теперь разница стала куда заметнее:
Xsl:choose, пожалуй, одна из самых многословных конструкций в XSLT. Очень часто она используется примерно так:
Бывают случаи, когда её использовать просто необходимо. Но в данном примере, гораздо правильнее использовать внешний документ со словарём:
Обратиться к нему из шаблона можно с помощью функции document() и XPath:
Мне кажется, что такой код гораздо легче поддерживать и он является идеологически более правильным — данные должны оставаться данными, а не превращаться в код, который сложно прочесть.
Многие не знают, что в XSLT есть несколько «встроенных» преобразований, которые делают за программиста часть работы. Эти преобразования делаются на уровне XSLT-процессора, т.ч. в некоторых реализациях могут работать значительно производительнее «ручных».
Например, за счёт встроенных преобразований осуществляется рекурсивное применение шаблона к потомкам текущего узла. Это равносильно такому шаблону:
Причём, если в apply-templates используется mode, то дочерние узлы тоже будут преобразовываться с этим mode. Другой пример, это автоматический вывод текстовых узлов и атрибутов:
Это значит, что вместо
можно использовать
Это (возможно) будет работать быстрее и точно сделает наши шаблоны более гибкими и расширяемыми.
Раздел в стандарте.
Больше всего при чтении шаблонов меня раздражает подобный код:
В таких случаях гораздо удобнее использовать один <xsl:value-of/>, а всё остальное форматирование производить с помощью concat().
Вообще в XPath есть много встроенных функции, которые можно использовать для упрощения кода. Неплохую документацию с примерами можно найти здесь. Особенно стоит обратить внимание на substring(), translate() и т.п. Эти функции не стоит реализовывать на PHP или другом внешнем языке — XSLT-процессор всё равно сделает это лучше.
Xsl:element нужен исключительно в случаях, когда название элемента определяется динамически.
Во всех остальных случаях, можно писать сразу сами теги. Иначе, код становится слишком многословным безо всякой цели.
Я всё ещё собираю подобные советы, т.ч. если у вас есть, что добавить, обязательно отпишитесь в комментах.
Именованные шаблоны
Многие «проблемы» 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.
Я всё ещё собираю подобные советы, т.ч. если у вас есть, что добавить, обязательно отпишитесь в комментах.