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

    HTML и CSSXSLTJavaScriptИзображенияСофтEtc
    Александр Самиляк

    12 июля 2011


    Задача.

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

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


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

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


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

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


    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)


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

    1
    2
    3
    4
    5
    6
    7
    8
    9