Алексей Валиков - Технология XSLT
<xsl:variable name="y">
<xsl:call-template name="sqr">
<xsl:with-param name="x" select="6"/>
</xsl:call-template>
</xsl:variable>
Обратим внимание, что значение переменной y будет иметь вовсе не численный тип. Несмотря на то, что элемент
<xsl:value-of select="$y"/>
выведет строку "36", переменная у содержит не число, а дерево, и 36 лишь является результатом конвертации в строку при выполнении xsl:value-of.
Для того чтобы присвоить переменной результат выполнения именованного шаблона в виде булевого значения, строки или числа, следует воспользоваться промежуточной переменной для явного преобразования типов.
ПримерПосле выполнения действий
<xsl:variable name="result">
<xsl:call-template name="sqr">
<xsl:with-param name="x" select="6"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="sqr-string" select="string($result)"/>
<xsl:variable name="sqr-number" select="number($result)"/>
переменные sqr-string и sqr-number будут содержать строковое и численное значение результата вычисления соответственно.
Немного сложнее обстоит дело с булевым типом. При приведении дерева к булевому типу результатом всегда будет "истина", поэтому такое преобразование необходимо выполнить в два шага: сначала преобразовать дерево в число, только затем число в булевый тип.
ПримерВ следующем преобразовании шаблон с именем less-than сравнивает значения параметров x и y. Переменной less-than присваивается булевое значение результата сравнения.
Листинг 11.7. Вычисление булевого значения функции<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="result">
<xsl:call-template name="less-than">
<xsl:with-param name="x" select="2"/>
<xsl:with-param name="y" select="1"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="less-than" select="boolean(number($result))"/>
<xsl:value-of select="$less-than"/>
</xsl:template>
<xsl:template name="less-than">
<xsl:param name="x"/>
<xsl:param name="y"/>
<xsl:value-of select="number($x < $y)"/>
</xsl:template>
</xsl:stylesheet>
ПримерПростым примером шаблона-функции может быть шаблон, который форматирует дату в нужном виде, например 7 августа 93 года как "07-Aug-1993".
В качестве параметров этот шаблон будет принимать численные значения дня, месяца и года. Год, имеющий значение меньшее 25, мы будем считать принадлежащим новому тысячелетию.
Листинг 11.8. Шаблон, форматирующий дату<xsl:template name="format-date">
<xsl:param name="day"/>
<xsl:param name="month"/>
<xsl:param name="year"/>
<xsl:value-of select="format-number($day, '00')"/>
<xsl:text>-</xsl:text>
<xsl:choose>
<xsl:when test="$month = 1">Jan</xsl:when>
<xsl:when test="$month = 2">Feb</xsl:when>
<xsl:when test="$month = 3">Mar</xsl:when>
<xsl:when test="$month = 4">Apr</xsl:when>
<xsl:when test="$month = 5">May</xsl:when>
<xsl:when test="$month = 6">Jun</xsl:when>
<xsl:when test="$month = 7">Jul</xsl:when>
<xsl:when test="$month = 8">Aug</xsl:when>
<xsl:when-test="$month = 9">Sen</xsl:when>
<xsl:when test="$month = 10">Oct</xsl:when>
<xsl:when test="$month = 11">Nov</xsl:when>
<xsl:when test="$month = 12">Dec</xsl:when>
</xsl:choose>
<xsl:text>-</xsl:text>
<xsl:choose>
<xsl:when test="$year <= 25">
<xsl:value-of select="format-number($year +2000, '0000')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-number($year, '0000')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Рекурсия
Отсутствие в XSLT изменяемых переменных (оценим красоту этой тавтологии) как, впрочем, и многое другое, делает этот язык совершенно непохожим на многие классические языки программирования. В этом разделе мы опишем рекурсию [Кормен и др. 2000, Кнут 2000] — чрезвычайно простую, но в то же время исключительно мощную технику, которая в большинстве случаев компенсирует нехватку в XSLT переменных и других процедурных конструкций.
Не вдаваясь в строгие определения дискретной математики, можно сказать, что рекурсия это всего лишь описание объекта или вычисления в терминах самого себя. Пожалуй, самым простым примером рекурсии является факториал, функция, которая математически определяется как:
0!=1
n!=n×(n-1)!
Программа на процедурном языке (например, таком, как Java), вычисляющая факториал совершенно тривиальна:
int factorial(int n) {
if (n == 0) return 1;
else return n * factorial(n-1);
}
Попробуем запрограммировать факториал на XSLT. Мы уже научились создавать собственные функции (вернее, конструкции, похожие на них) с помощью одних только именованных шаблонов, значит написать функцию, которая бы вызывала сама себя, будет не так уж и сложно.
Листинг 11.9. Именованный шаблон, вычисляющий факториал<xsl:template name="factorial">
<xsl:param name="n"/>
<xsl:choose>
<xsl:when test="$n=0">1</xsl:when>
<xsl:otherwise>
<xsl:variable name="n-1">
<xsl:call-template name="factorial">
<xsl:with-param name="n" select="$n-1"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$n * number($n-1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Вызвав этот шаблон с параметром n равным 6 следующим образом:
<xsl:call-template name="factorial">
<xsl:with-param name="n" select="number(6)"/>
</xsl:call-template>
мы получим текстовый узел, значение которого будет равно "720".
Очевидным требованием к рекурсивным функциям является возможность выхода из рекурсии. Если бы в определении факториала не было указано, что 0!=1, вычисления так бы и продолжались без конца.
Главным минусом рекурсии является требовательность к ресурсам. Каждый раз, при вызове именованного шаблона, процессор должен будет каким-то образом сохранять в памяти передаваемые ему формальные параметры. Например, если мы попробуем сосчитать факториал от 170, процессору понадобится держать в памяти сразу 170 чисел. Безусловно, в случае с факториалом это не является большой проблемой — точность 64-битных чисел исчерпается гораздо раньше, чем закончится память, но в случае хранения в переменных действительно больших объемов информации (например, частей деревьев) такая угроза существует. Кроме того, рекурсивные решения, как правило, работают медленнее, чем решения, не использующие рекурсию.
Так в чем же смысл использования рекурсии? Дело в том, что вследствие определенных ограничений (связанных, в частности с неизменяемыми переменными) в XSLT существуют задачи, которые не могут быть реализованы иначе кроме как через рекурсию. Самым характерным примером такой задачи являются циклы.
Циклы
Цикл в общем смысле слова это повторение одних и тех же действий несколько раз. Если говорить об XSLT, то цикл это многократное выполнение одного и того же шаблона. Для подавляющего большинства случаев в преобразованиях достаточно бывает использовать такие элементы, как xsl:apply-templates и xsl:for-each, которые заставляют процессор выполнять одни и те же действия несколько раз в контексте каждого из узлов определенного множества.
Весомым ограничением такого рода циклической обработки является невозможность генерировать множества узлов. В текущей версии языка никакой другой тип не может быть приведен ко множеству узлов, значит, в любое из них могут входить только те узлы, которые изначально присутствуют в одном из обрабатываемых документов. Это означает, что ни xsl:apply-templates, ни xsl:for-each не могут быть использованы для того, чтобы реализовать простые while- или for-циклы для произвольных множеств.
Цикл while
Наиболее примитивной циклической конструкцией во многих языках программирования является цикл while (англ. пока). Цикл while, как правило, имеет следующий вид:
пока
верно условие
выполнять
действия
В качестве примера while-цикла напишем на языке Java программу вычисления факториала в итеративном стиле:
int factorial(int n) {
int i = n;
int result = 1;
while (i != 0) {
result = result * i;