.. / XSLT- примеры. Вып.4 Строковые операции (reverse, duplicate, replace, uppercase)

  1. xslt-recursive
  2. xslt-examples

Одна из проблем, с которой сталкиваются при изучении XSLT1.0, является скудость библиотеки встроенных функций, в том числе функций работы со строками. Кажется что в любом языке программирования должна иметь место функция замены строк, но увы, в XSLT1.0 ее нет. Нет и других относительно часто встречающихся функций. Предполагается, что подобные операции должны быть проведены на уровне подготовке данных.

В версии XSLT2 разработчики существенно расширили арсенал строковых (и не только) функций, но судя по тому сколько пришлось ждать полноценой поддержки XSLT2 в PHP, надо забыть об этом на несколько лет. Впрочем Microsoft тоже не спешит. 

Сейчас же нам приходится выкручиваться, изобретая собственные функции. Итак начнем.

Пример I. XSLT-uppercase. Перевод букв в верхний регистр

Вообще-то использовать такую функцию, применительно к  HTML  глупо, и даже вредно - для этого есть соответствующее CSS-свойство  Но в качестве учебного примера можно поиграть в собственную функцию XSLT-uppercase .

Те кто  немного знает Perl или PHP должны были с ней сталкиваться с функцией:

translate (arg1,arg2,arg3)

Функция translate() преобразует  строку arg1, заменяя в ней буквы встречающиеся в  строке arg2, в соответствии с их позицией заменены на символы из строки arg3. На основе этой функции и строится перевод букв в верхний регистр.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      
       <xsl:template match="/">            
           <xsl:call-template name="uppercase">            
               <xsl:with-param name="input"  select="'Превед Медвед!'" />            
           </xsl:call-template>            
       </xsl:template>            
      
       <xsl:template name="uppercase">            
           <xsl:param name="input"/>            
           <xsl:value-of select="translate($input,
           'йцукенгшщзхъфывапролджэячсмитьбю',   
           'ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ')"/>            
       </xsl:template>            
      
   </xsl:stylesheet>            

Аналогично будет работать и перевод в нижний регистр lowercase().

Пример II. XSLT-duplicate. Повтор строки

Этот пример использует рекурсивный вызов - один из основных в XSLT алгоритмов   построения циклов. Мы уже рассматривали его в примере построения алфавитных указателей

   <xsl:template match="*">

       <xsl:call-template name="duplicate">            
           <xsl:with-param name="count">              3</xsl:with-param>            
           <xsl:with-param name="input">              Ура!</xsl:with-param>            
       </xsl:call-template>            

   </xsl:template>            

   <xsl:template name="duplicate">            
       <xsl:param name="count" select="1"/>            
       <xsl:param name="input" select="1"/>            

       <xsl:choose>            
           <!--  выход из рекурсии  -->            
           <xsl:when test="$count <1"/>            
           <xsl:when test="not($input)"/>            
           <xsl:otherwise>            

               <xsl:value-of select="$input"/>            
               <!--  рекусивный вызов  -->            
               <xsl:call-template name="duplicate">            
                   <xsl:with-param name="count" select="$count - 1"/>            
                   <xsl:with-param name="input" select="$input"/>            
               </xsl:call-template>            

           </xsl:otherwise>            

       </xsl:choose>            

   </xsl:template>            

Процедура репликации - именованный шаблон (duplicate) на входе содержит два параметра count - количество повторов и входную строку input.  Внутри именованного шаблона происходит уменьшение count на единицу и рекурсивное обращение функции самой к себе. Рекурсия прекращается когда счетчик count становится равен нулю.  На каждом шаге производится вывод значения входной строки  input.

Алгоритм очень небыстрый. Количество итераций для решения этой задачи можно существенно сократить, если переписать алгоритм следующим образом:

<xsl:template match="*">

       <xsl:call-template name="duplicate">            
           <xsl:with-param name="count">              3</xsl:with-param>            
           <xsl:with-param name="input">              Ой!</xsl:with-param>            
       </xsl:call-template>            

   </xsl:template>            

   <xsl:template name="duplicate">            
       <xsl:param name="count" select="1"/>            
       <xsl:param name="input"/>            

       <xsl:choose>            

           <xsl:when test="not($count) or not($input)"/>            

           <xsl:when test="$count = 1">            
               <xsl:value-of select="$input"/>            
           </xsl:when>            

           <xsl:otherwise>            

               <xsl:if test="$count mod 2">            
                   <xsl:value-of select="$input"/>            
               </xsl:if>            

               <xsl:call-template name="duplicate">            
                   <xsl:with-param name="input" select="concat($input,$input)"/>            
                   <xsl:with-param name="count" select="floor($count div 2)"/>            
               </xsl:call-template>            

           </xsl:otherwise>            

       </xsl:choose>            

   </xsl:template>            

В этом примере на каждом шаге итерации происходит склейка входной строки и уменьшение счетчика в два раза. Цикл повторяется до тех пор, пока счетчик не станет равен нулю.

Пример III. XSLT-reverse. Вывод строки в обратном порядке

По существу пример мало чем отличается от предыдущего или рассмотренного ранее примера III в выпуске 3 (формирование алфавитных указателей)
Тот же самый цикл при помощи рекурсии. И входная строка, от которой на каждой итерации отщипывается последняя буква, до тех пор, пока строка не станет пустой. 

 

<xsl:template match="*">

       <xsl:call-template name="reverse">            
           <xsl:with-param name="input" select="123456789"/>            
       </xsl:call-template>            

   </xsl:template>            

   <xsl:template name="reverse">            
       <xsl:param name="input">              12345</xsl:param>            
       <xsl:param name="size" select="string-length($input)"/>            

       <xsl:choose>            
           <xsl:when test="$size <1"/>            
           <xsl:otherwise>            

               <xsl:value-of select="substring($input,$size,1)"/>            
              
               <!--  рекусивный вызов  -->            
               <xsl:call-template name="reverse">            
                   <xsl:with-param name="input" select="$input"/>            
                   <xsl:with-param name="size" select="$size - 1"/>            
               </xsl:call-template>            
  
           </xsl:otherwise>            
       </xsl:choose>            
  
   </xsl:template>              

Алгоритм так же можно существенно ускорить, если входную строку разбить на две части и для каждой из частей запустить свою рекурсию:

<xsl:template name="reverse">
      
       <xsl:param name="input"/>            
       <xsl:variable name="size" select="string-length($input)"/>            
      
       <xsl:choose>            
         
           <!--  вывод строки из одного символа -->            
           <xsl:when test="$size <2">            
               <xsl:value-of select="$input"/>            
           </xsl:when>            
          
           <!--  вывод реверса строки из двух символов -->            
           <xsl:when test="$size = 2">            
               <xsl:value-of select="substring($input,2,1)"/>            
               <xsl:value-of select="substring($input,1,1)"/>            
           </xsl:when>            
          
           <xsl:otherwise>            
               <!--  входная строка больше двух символов 
               разбивается пополам -->            
               <xsl:variable name="center" select="floor($size div 2)"/>            
              
               <!--  запускается отдельная  рекурсия 
               по второй половине строки  -->            
               <xsl:call-template name="reverse">            
                   <xsl:with-param name="input" 
                     select="substring($input,$center+1,$center+1)"/>            
               </xsl:call-template>            
              
               <!--  запускается отдельная  рекурсия 
               по первой половине строки  -->            
               <xsl:call-template name="reverse">            
                   <xsl:with-param name="input" 
                   select="substring($input,1,$center)"/>            
               </xsl:call-template>            
              
           </xsl:otherwise>            
       </xsl:choose>            
      
   </xsl:template>              

Пример IV. XSLT-replace. Замена подстроки

 В этом примере реализуется функция аналогичная php функции str_replace().

В качестве функции используется именованный шаблон replace. В нем задается входная строка input, образец поиска from и образец замены to.

В шалоне определяется первое вхождение образца поиска во входной строке. Затем строка ращепляется на две с использованием встроенных функций  substring-before() и substring-after(). Первая часть вместе с образом замены выводится в выходной поток, а вторая часть - подается в качестве входной для итерационного вызова шаблона replace.

   <xsl:template match="/">
       <xsl:call-template name="replace">            
           <xsl:with-param name="input" select="'Путин - наш президент. Путин - наше Все'"/>            
           <xsl:with-param name="from" select="'Путин'"/>            
           <xsl:with-param name="to" select="'Медведев'"/>            
       </xsl:call-template>            
   </xsl:template>            


   <xsl:template name="replace">            
       <xsl:param name="input"/>            
       <xsl:param name="from"/>            
       <xsl:param name="to"/>            

       <xsl:choose>            
           <xsl:when test="contains($input, $from)">            
               <!--   вывод подстроки предшествующей образцу  + вывод строки замены -->            
               <xsl:value-of select="substring-before($input, $from)"/>            
               <xsl:value-of select="$to"/>            


               <!--   вход в итерацию -->            
               <xsl:call-template name="replace">            
                   <!--  в качестве входного параметра задается подстрока после образца замены  -->            
                   <xsl:with-param name="input" select="substring-after($input, $from)"/>            
                   <xsl:with-param name="from" select="$from"/>            
                   <xsl:with-param name="to" select="$to"/>            
               </xsl:call-template>            

           </xsl:when>            
           <xsl:otherwise>            
               <xsl:value-of select="$input"/>            
           </xsl:otherwise>            
       </xsl:choose>            
   </xsl:template>              

Как обычно, все исходники можно найти в архиве xslt-examples.zip в директории ex4

ЗЫ

Salvatore Mangano XSLT Cookbook, Second Edition (O'Reilly)Примеры "быстрых" алгоритмов взяты из великолепной книги   Salvatore Mangano "XSLT Cookbook, Second Edition" В мае этого года она вышла в русском переводе и   до сих пор продается в онлайновых магазинах. Издана мизерным тиражом (2.000 экземпляров) и через год  станет таким же раритетом,  как и любая мало-мальски приличная приличная книги по XSLT

Жаль только, что при переиздании в русской версии была потеряна легко узнаваемая фирменная обложка  издательства O'Reilly Вместо нее  "дизайнер"   Мовчан А.Г.,   налепил какое-то несусветное говно в традиционном для издательства bhv стиле.  Впрочем, оригинальную  версию  изданную в O'Reilly я купил еще год назад.

  1. 2008-11-10
  2. xslt-recursive
  3. xslt-examples
  1. www.dpawson.co.uk/xsl/sect2/sect21.html - XSLT Questions and Answers - FAQ.
  2. snippets.dzone.com/tag/xslt - DZone Snippets: XSLT examples
  3. incrementaldevelopment.com/xsltrick/ - Gallery of Stupid XSL and XSLT Tricks
Go Index Test