Циклы

Цикл while
Цикл for
Метод Пиза для for-цикла

Цикл в общем смысле слова — это повторение одних и тех же действий несколько раз. Если говорить об 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;
    i--;
  }
  return result;
}

В этой функции условием является отличие значения переменной i от 0, а действиями — умножение значения переменной result на значение переменной i, и уменьшение значения этой переменной на 1.

Цикл while не может быть запрограммирован в XSLT итеративно потому как действия, как правило, изменяют значения переменных, в контексте которых вычисляется условие, определяющее, продолжать выполнение цикла или нет. Дадим другую общую запись цикла while, выделив изменение переменных:

пока
  верно условие(x1,x2,...,xn)
выполнить
  x1' := функция1(x1,x2,...,xn)
  x2' := функция2(x1,x2,...,xn)
  ...
  xn' := функцияn(x1,x2,...,xn)
  действия(x1,x2,...,xn)
  x1 := x1'
  x2 := x2'
  ...
  xn := xn'
иначе
  вернуть результат(x1,...,xn)

Переопределение значений переменных x1,...,xn в этом случае выполняют n функций: функция1,...,функцияn. И если изменить значение переменной мы не могли, переопределить связанное с ней в значение мы вполне в состоянии, добавив в контекст новый параметр или переменную с тем же именем.

Теперь мы можем записать весь цикл while как одну рекурсию:

while(x1,...,xn) ::=
  если
    выполняется условие(x1,...,xn)
  то
    действия(x1,...,xn)
    while(функция1(x1,...,xn),
          функция2(x1,...,xn),
          ...,
          функцияn(x1,...,xn))
  иначе
    результат(x1,...,xn)

Теперь уже совершенно очевидно, как while-цикл должен выглядеть в преобразовании:

Пример 11.10. Шаблон цикла while в общем виде

<xsl:template name="while">
  <xsl:param name="x1"/>
  <!-- ... -->
  <xsl:param name="xn"/>
  <xsl:choose>
    <xsl:when test="условие($x1,...,$xn)">
      <!-- Действия -->
      <xsl:call-template name="while">
        <xsl:with-param name="x1" select="функция1($x1,...$xn)"/>
        <!-- ... -->
        <xsl:with-param name="xn" select="функцияn($x1,...$xn)"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="результат($x1,...,$xn)"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

В качестве примера приведём while-цикл для программы, вычисляющей факториал. Java-код был следующим:

while (i != 0)
{
  result = result * i;
  i--;
}

В этом цикле участвуют две переменные — i и result. Функции, использующиеся в этом цикле, запишутся следующим образом:

условие($i, $result)        ::=    ($i != 0)
функцияi($i, $result)       ::=    ($i - 1)
функцияresult($i, $result)  ::=    ($i * $result)
результат($I, $result)      ::=    ($result)

Именованный шаблон для этого случая будет иметь вид:

Пример 11.11. Пример шаблона цикла while

<xsl:template name="while">
  <xsl:param name="i"/>
  <xsl:param name="result"/>
  <xsl:choose>
    <xsl:when test="$i != 0">
      <xsl:call-template name="while">
        <xsl:with-param name="i" select="$i - 1"/>
        <xsl:with-param name="result" select="$result * $i"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$result"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Вызвать этот шаблон можно следующим образом:

<xsl:template match="/">
  <xsl:call-template name="while">
    <xsl:with-param name="i" select="6"/>
    <xsl:with-param name="result" select="1"/>
  </xsl:call-template>
</xsl:template>

Результатом будет, естественно, число 720.