О трансформаторах, версиях 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