Алексей Валиков - Технология XSLT
<xsl:if test="$i < $n">
<option>
<xsl:value-of select="$i"/>
</option>
<xsl:call-template name="for">
<xsl:with-param name="i" select="$i + 1"/>
<xsl:with-param name="n" select="$n"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Листинг 11.15 Выходящий документ<option>0</option>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
<option>8</option>
<option>9</option>
Пожалуй, этим примером мы и закончим рассмотрение рекурсии. Осталось лишь добавить, что при всей своей простоте и вычислительной мощи, рекурсия является гораздо более требовательной к ресурсам техникой программирования, чем обычная итеративная обработка. Поэтому всегда следует тщательно оценивать, во что может вылиться использование рекурсии. В любом случае следует избегать глубоких рекурсий (функций, количество рекурсивных вызовов в которых может быть большим) и рекурсий, неэкономно использующих память.
Кроме того, большинство действий, выполнение которых в XSLT затруднено, в классических языках программирования выполняется, как правило, намного легче и эффективней. Поэтому, каждый раз, когда стоит вопрос об использовании рекурсии, наряду с ней следует рассматривать такую альтернативу, как использование расширений XSLT, написанных на обычном императивном языке.
Метод Пиза для for-цикла
Для простых for-циклов, которые должны выполниться строго определенное число раз, вместо рекурсии можно использовать весьма остроумный метод, предложенный Венделлом Пизом (Wendell Piez, Mullberry Technologies, Inc). Суть метода состоит в том, что хоть мы и не можем сгенерировать множество узлов, выбрать множество с определенным количеством узлов нам вполне по силам.
Для начала выберем какое-нибудь множество узлов документа преобразования:
<xsl:variable name="set" select="document('')//node()"/>
Затем для повторения определенных действий несколько раз используем конструкцию вида
<xsl:for-each select="$set[position() <= $number]">
<!-- Действия -->
</xsl:for-each>
где number указывает требуемое число итераций.
При использовании метода Пиза следует учитывать следующие особенности.
□ Множество узлов set не должно быть слишком большим — иначе его выбор будет неэффективным.
□ Множество узлов set обязательно должно содержать число итераций (number) узлов.
В целом же метод Пиза — классический пример эффективного применения инструментов не по назначению.
Операции над множествами
Рассматривая такой тип данных, как множества узлов, мы отмечали ограниченность операций, которые можно с ними производить. В частности, XSLT не предоставляет стандартных операторов для определения принадлежности одного множества другому, нахождения пересечений, разности множеств и так далее. Возможности, которые были представлены при описании этого типа данных, основанные на использовании оператора равенства, на самом деле реализуют далеко не математические операции над множествами.
В этом разделе мы рассмотрим иной подход к реализации операций над множествами, основанный на очень простом определении принадлежности узла множеству. Узел node принадлежит множеству nodeset тогда и только тогда, когда выполняется равенство
count($nodeset) = count($node | $nodeset)
Учитывая это обстоятельство, операции над множествами можно представить, как показано в табл. 11.1. Результирующее множество выделено штриховкой.
Таблица 11.1. Операции над множествами
Операция Графическое представление XPath-выражение Объединение $A | $B Пересечение $А[count(.|$B)=count($B)] Разность $A[count(.|$B)!=count($B)] Симметрическая разность $A[count(.|$B)!=count($B)] | $B[count(.|$A)!=count($A)]Приведенные выше методы были разработаны Майклом Кеем (Michael Kay, Software AG), Оливером Беккером (Oliver Becker, Humboldt-Universitat zu Berlin), Кеном Холманом (Ken Holman, Crane Softwrights Ltd.) и публикуются с любезного разрешения авторов.
Перенос строк и элементы BR
Большинству читателей, скорее всего, хорошо знаком такой элемент языка HTML, как BR, который используется для обозначения разрыва строки. В обычных текстовых файлах для той же самой цели используются символы с кодами #xA, #xD или их комбинации в зависимости от платформы. При совместном использовании неразмеченного текста и HTML часто возникает задача преобразования символов перевода строки в элементы BR и наоборот.
Замену элемента BR на текстовый узел, содержащий перевод строки, можно проиллюстрировать следующим тривиальным шаблоном.
Листинг 11.16. Шаблон замены элементов BR на перенос строки<xsl:template match="BR">
<xsl:text>
</xsl:text>
</xsl:template>
Гораздо сложнее написать шаблон, делающий обратную операцию, — замену символов переноса строки на элементы BR. В XSLT нет встроенного механизма для замены подстроки в строке (тем более на элемент), поэтому нам придется создать для этой цели собственный шаблон.
Для этой цели мы можем воспользоваться функциями substring-before и substring-after. Функция substring-before($str, $search-for) возвратит часть строки str, которая предшествует первому вхождению в нее подстроки search-for, а функция substring-after($str, $search-for) — последующую часть. То есть заменить первое вхождение можно шаблоном вида
<!-- ... -->
<xsl:value-of select = "substring-before($str, $search-for)"/>
<xsl:copy-of select = "$replace-with"/>
<xsl:value-of select = "substring-after($str, $search-for)"/>
<!-- ... -->
Для того же, чтобы заменить все вхождения, достаточно рекурсивно повторить операцию замены первого вхождения с той частью строки, которая следует за ним. Приведем шаблон, который выполняет эту операцию.
Листинг 11.17. Шаблон для замены подстроки в строке<xsl:template name="replace" match="text()" mode="replace">
<xsl:param name="str" select="."/>
<xsl:param name="search-for" select="'
'"/>
<xsl:param name="replace-with">
<xsl:element name="BR"/>
<xsl:text>
</xsl:text>
</xsl:param>
<xsl:choose>
<xsl:when test="contains($str, $search-for)">
<xsl:value-of select="substring-before($str, $search-for)"/>
<xsl:copy-of select="$replace-with"/>
<xsl:call-template name="replace">
<xsl:with-param name="str"
select="substring-after($str, $search-for)"/>
<xsl:with-param name="search-for" select="$search-for"/>
<xsl:with-param name="replace-with " select="$replace-with"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$str"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Шаблон, приведенный в этом листинге, может быть вызван двумя способами: элементом xsl:apply-templates в режиме replace (в этом случае он будет обрабатывать текстовые узлы выбранного множества), или при помощи именного вызова элементом xsl:call-template. Шаблон принимает на вход три параметра.
□ Параметр str, содержащий строку, в которой нужно произвести замену. По умолчанию этому параметру присваивается текстовое значение текущего узла.
□ Параметр search-for, содержащий подстроку, которую требуется найти и заменить в строке str. По умолчанию замене будут подлежать символы переноса строки, "&#хА;".
□ Параметр replace-with, содержащий объект, на который следует заменять подстроки search-for. По умолчанию эти подстроки будут заменяться на элемент BR и следующий за ним перенос строки, добавленный для лучшей читаемости.
В качестве примера отформатируем содержание следующего элемента:
<pre>One little rabbit
Two little rabbits
Three little rabbits</pre>
Запишем шаблон для обработки элемента pre:
<xsl:template match="pre">
<xsl:copy>
<xsl:apply-templates mode="replace"/>
</xsl:copy>
</xsl:template>
Результат его выполнения будет иметь следующий вид: