О трансформаторах, версиях XSLT и переносимости кода, используещего временные деревья

Содержание

Задача. Разобрать по косточкам теорию и практику использования временных деревьев в XSLT.

Весь написанный нами XSL-код исполняет XSL-трансформатор (он же XSL-процессор). Для нас важной характеристикой трансформатора является его поддержка XSLT версии 2.0. Важно это потому, что в версии 2.0 магический тип данных Result Tree Fragment был истреблен как класс (см. второй пункт) и ему на смену пришли полноценные временные деревья. То есть для XSLT 2.0 временные деревья являются родным понятием, и нам не нужно делать никаких преобразований типа exsl:node-set(RTF). Поэтому, написав:

<xsl:variable name="ER_episodes">
    <item>24 Hours</item>
    <item>Day One</item>
    <item>Going Home</item>
</xsl:variable>

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

<xsl:value-of select="$ER_episodes/item[2]" />

Забегая немного вперед, скажу, что производители трансформаторов очень неохотно реализуют поддержку XSLT 2.0, поэтому не стоит привыкать к такой роскоши. Это вдвойне обидно потому, что, кроме нативных временных деревьев, в XSLT 2.0 есть куча других полезных функций, которых нам порой так не хватает. Но есть и хорошая новость — в большинстве процессоров присутствует та или иная поддержка EXSLT. При этом модуль common (а значит, и наша заветная функция node-set()) обычно входит в эту поддержку. Вообще, EXSLT содержит много разных модулей, среди которых и расширенная работа со строками, и регулярные выражения, и пользовательские XPath-функции, и генерация случайного числа. Все зависит лишь от поддержки каждого модуля трансформаторами.

А трансформаторов этих в природе существует не меньше десятка. Однако если говорить о серверной части сайта, то на момент написания данной статьи более или менее распространены четыре — libxslt, Xalan, Saxon и MSXML. Пару слов о каждом из них.

libxslt

libxslt — самый широко используемый и, наверно, самый старинный процессор, который базируется на другой бородатой библиотеке libxml. Написаны они на C, что позволяет им «выстреливать», как АК-47, на любых трансформациях. Сейчас он понимает только XSLT 1.0, но похоже, что работа над поддержкой XSLT 2.0 ведется.

В libxslt реализованы почти все модули EXSLT, в том числе функция node-set(), поэтому на этом процессоре временные деревья можно использовать без каких-либо опасений.

Xalan

Xalan — трансформатор Apache, имеющий версии на Java и C++. Тем не менее, ассоциируется он в первую очередь с Java, потому что входит в стандартную поставку JDK. Поработав с этим процессором довольно продолжительное время на нескольких проектах, могу сказать, что с ним иногда случаются необъяснимые глюки. Оно, в общем, и неудивительно — последняя версия выпускалась в 2007 году. Короче, этот трансформатор потихоньку устаревает, и его место в Java-сообществе занимает Saxon.

В Xalan тоже реализована функция node-set() из EXSLT.

Saxon

Saxon — самый перспективный трансформатор, написанный не кем иным, как небритым мужиком Майклом Кэем. Есть две реализации — на Java и на .NET. Этот трансформатор выделяется на фоне других полной поддержкой XSLT 2.0.

В Saxon временные деревья являются полноценным типом данных (XSLT 2.0 ведь), и никаких преобразований функцией node-set() делать не нужно, все работает из коробки. Однако поддержка EXSLT в Saxon тоже есть, а это значит, что код можно сделать переносимым. Поэтому моя рекомендация: даже если на проекте Saxon, все равно следует вызывать exsl:node-set(). Это позволит при необходимости использовать этот же XSL-код на других трансформаторах.

MSXML

MSXML — набор XML-библиотек Microsoft, в состав которого входит и XSL-трансформатор. Написано это все лучше не знать на чем, но работает, в отличие от всех предыдущих трансформаторов, конечно же, только на Windows. Поддерживается только XSLT 1.0.

Насчет EXSLT: он не поддерживается. Вместо этого предлагается пользоваться расширением самого Microsoft, в котором есть аналогичная функция node-set(). В общем, это лучше, чем ничего, но использовать тот же код на других трансформаторах, понятно, не получится. Замечу, что и libxslt, и Xalan, и Saxon тоже имеют свои личные проприетарные расширения, но при этом они не отключают поддержку EXSLT, проявляя заботу о переносимости кода. А Редмонд в своем репертуаре. Мочить.

Однако 10 лет верстки под IE6 прибавили человечеству изобретательности в борьбе с пакостями Microsoft, поэтому предлагаю хак, найденный в блоге одного хорошего человека. Следим за руками:

<xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    xmlns:msxml="urn:schemas-microsoft-com:xslt"
    extension-element-prefixes="exsl msxml">

    <!-- Что-то похожее на CSS-expression -->
    <msxml:script language="JScript" implements-prefix="exsl">
        this['node-set'] = function(x) {
            return x;
        }
    </msxml:script>

    <xsl:template match="/">
        <xsl:variable name="ER_episodes">
            <item>24 Hours</item>
            <item>Day One</item>
            <item>Going Home</item>
        </xsl:variable>

        <xsl:value-of select="exsl:node-set($ER_episodes)/item[2]" />
    </xsl:template>

</xsl:stylesheet>

На всех четырех трансформаторах получим:

Day One

Этот <msxml:script>, работая в MSXML, перенаправляет вызов exsl:node-set(RTF) на msxml:node-set(RTF), при этом не мешая работать другим трансформаторам, которые просто игнорируют незнакомую конструкцию. Кросстрансформаторность такая получается.

Данный прием сильно помогает, когда требуется повышенная переносимость XSL-кода (например, на клиентской стороне в браузере), и работает, даже если <msxml:script> определить в импортируемом XSL-файле.

main.xsl:

<xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    extension-element-prefixes="exsl">

    <xsl:import href="import.xsl" />

    <xsl:template match="/">
        <xsl:variable name="ER_episodes">
            <item>24 Hours</item>
            <item>Day One</item>
            <item>Going Home</item>
        </xsl:variable>

        <xsl:value-of select="exsl:node-set($ER_episodes)/item[2]" />
    </xsl:template>

</xsl:stylesheet>

import.xsl:

<xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    xmlns:msxml="urn:schemas-microsoft-com:xslt"
    extension-element-prefixes="exsl msxml">

    <msxml:script language="JScript" implements-prefix="exsl">
        this['node-set'] = function(x) {
            return x;
        }
    </msxml:script>

</xsl:stylesheet>

Подытоживая, приведу сводную таблицу, отражающую все вышесказанное.

libxslt Xalan Saxon MSXML
Версия XSLT 1.0 1.0 2.0 1.0
Поддержка exsl:node-set() + + + ?
URI личного расширения http://xmlsoft.org/XSLT/namespace http://xml.apache.org/xalan http://saxon.sf.net/ urn:schemas-microsoft-com:xslt
Функция node-set() из личного расширения node-set(RTF) nodeset(RTF) ? node-set(RTF)

Следующая часть будет посвящена производительности трансформаторов при работе с временными деревьями.

Временные деревья в XSLT

  1. Часть первая. Что такое временное дерево
  2. Часть вторая. Применение временных деревьев № 1 — хранение статических данных в коде XSL-шаблона
  3. Часть третья. Применение временных деревьев № 2 — передача сложных параметров
  4. Часть четвертая. Применение временных деревьев № 3 — шаблонизация однотипных строк
  5. Часть пятая. Применение временных деревьев № 4 — подстройка под готовый шаблон
  6. Часть шестая. Применение временных деревьев № 5 — многоступенчатая трансформация
  7. Часть седьмая. О трансформаторах, версиях XSLT и переносимости кода, используещего временные деревья
  8. Часть восьмая. О производительности при работе с временными деревьями
  9. Часть девятая. Тонкости использования временных деревьев


12 июля 2011 г.
Александр Самиляк
http://www.artlebedev.ru/tools/technogrette/xslt/temptrees-7/