.. / XSLT- примеры. Вып.5 Вывод данных данных в несколько колонок HTML-таблицы

  1. xslt-examples
  2. xslt-group

В отличие от предыдущих четырех выпусков этот посвящен достаточно часто встречающейся задаче.  Предположем есть витрина магазина. Для нее сформирован список товаров. Товары нужно разместить в несколько колонок в таблице, так чтобы каждый товар попал в свою ячейку, как например это сделано на Амазоне

Рассмотрим несколько возможных вариантов группировки товаров по ячейкам таблицы. В качестве входного XML  возьмем известный из предыдущих выпусков список книг:

<books>
   <book author="Багряк П."  cost="123" type="old">   Синие люди</book>                
   <book author="Булгаков М."  cost="34" type="old">   Роковые яйца</book>           

     
....................................................

        
   <book author="Ленин В.И."  cost="344" type="old">   Что делать</book>                
            </books>   

 

Пример 1.  Вывод товаров в три колонки

На Амазоне товары размещены в три колонки. Ну и наш менеджер хочет чтобы было все как на Амазоне (типа такой же богатый будет как Джефф Безос)

<xsl:template match="books">
       <table>  
            <xsl:apply-templates select="book"/>  
       </table>         
   </xsl:template>     
   
   <xsl:template match="book">  
       <xsl:variable name="i" select="position()"/>  
        <xsl:if test="$i mod 3  = 1">  
        <tr class="{$i}">  
          <td>   <xsl:value-of select="." />   </td>  
          <td>   <xsl:value-of select="../book[$i+1]" />   </td>  
          <td>   <xsl:value-of select="../book[$i+2]" />   </td>  
      </tr>  
        </xsl:if>  
   </xsl:template>  
   
  

 

Первый шаблон match="books" запускает в обработку весь список книг. Внутри него вызывается шаблон обработки каждой книги в отдельности. В нем происходит группировка по колонкам.
В шаблоне match="book" производится проверка номера позиции position() книги в списке. И производится разбивка списка по тройке книг из которых формируется строка таблицы.

Сделали и порадовались. Но как обычно по ходу задачи аналитики уточнили задачу. Теперь они захотели изменить порядок следования товаров так, чтобы размещение товаров в таблице шло не по горизонтали, а по вертикали.  Т.е. чтобы порядок был таким:

1 4 7
2 5 8
3 6 9

Ну пожалуйста.

Пример 1a.  Изменение порядка вывода элементов в таблице

 

<xsl:template match="books">
       <xsl:variable name="N" select="ceiling(count(book) div 3)">   </xsl:variable>  
       <table>  
            <xsl:apply-templates select="book">  
                <xsl:with-param name="N" select="$N"/>  
            </xsl:apply-templates>  
       </table>         
   </xsl:template>     
   
   <xsl:template match="book">  
       <xsl:param name="N"/>  
       <xsl:variable name="i" select="position()"/>  
     <xsl:if test="$N >   = $i">  
         <tr >  
         <td class="{$i}">   <xsl:value-of select="."/>   </td>  
             <td>   <xsl:value-of select="../book[$i+$N]"/>   </td>  
             <td>   <xsl:value-of select="../book[$i+$N*2]"/>   </td>  
         </tr>  
     </xsl:if>  
  

   </xsl:template>   

Пример практически аналогичный предыдущему.  Но теперь тройки товаров в одной строке таблицы формируются по другому принципу.  Определяется шаг, на котором отстоят друг от друга товары в соседних ячейках одной строки (переменная N), очевидно этот шаг равен одной трети от общего числа книг в списке.

Шаблон match="book" проводит проверку позиции книги в списке и из первой трети формирует строки таблицы, размещая в первую строку текущий элемент. Во вторую ясейку строки  записываются книги отстоящие от текущей на N, в третью - на N*2

Сделали и порадовались, но новый редактор сайта захотел разместить все не в три, а в пять колонок как на Эльдорадо!  Поскольку завтра он или его сменщик снова изменит  число колонок желательно предусмотреть универсальный вариант, в котором количество колонок может быть произвольным и удовлетворит любого нового редактора/менеджера/аналитика.

Пример 2. Произвольное число колонок в таблице.

<xsl:template match="books">
       <table>  
           <xsl:for-each select="book[position() mod 3= 1]">  
               <tr>  
                   <xsl:apply-templates    select=".
following-sibling::book[position() <3]"/>  
               </tr>  
               </xsl:for-each>  
       </table>         
   </xsl:template>     
   
   <xsl:template match="book">  
       <td>  
           <xsl:value-of select="." />  
       </td>  
   </xsl:template>   

Здесь я немного отступаю от xslt fun style и ввожу для разнообразия оператор цикла. Который делает абсолютно то же самое самое что условие if в предыдущем примере (1) - запускает обработку каждого третьего элемента списка. При этом остальные элементы обрабатываются отдельно.

Условие для формирование тройки элементов на каждом шаге использует ось following-sibling - ось последующих за текущим элементов.  Запускается на обработку текущий элемент и следующие за ним два элемента.  Образец шаблона match="book" здесь производит только запись книги в ячейку таблицы.

Все здорово. Но новый главный редактор захотел в каждой ячейки таблицы дать рамочку. А в нашем шаблоне список книг не кратен трем. В результате последняя ячейка не выводитс и таблица получается немного с обгрызенным углом. Придется все переделывать и делать еще более универсальное решение.

Пример 3.

   
<xsl:template match="books">  
       <xsl:variable name="ColNum" select="3"/>  
       <xsl:variable name="Colbook" select="count(book)">   </xsl:variable>  

     <table>  
         <xsl:for-each select="book[position() mod $ColNum= 1]">  
             
         <tr>  
             <xsl:apply-templates    
               select=".|following-sibling::book[position() <$ColNum]"/>  
                   <xsl:call-template name="zeroCell">  
                       <xsl:with-param name="nCol" 
                        select="position() * $ColNum - $Colbook"/>  
                   </xsl:call-template>  
         </tr>  
         </xsl:for-each>  
     </table>         
 </xsl:template>     
   
<xsl:template match="book">  
    <td>  
        <xsl:value-of select="." />  
    </td>  
</xsl:template>  
   
            <xsl:template name="zeroCell">  
   <xsl:param name="nCol" />  
   <xsl:for-each select="preceding-sibling::node()[position() <= $nCol]">  
    <td>   </td>  
   </xsl:for-each>  

</xsl:template>       

 Пример практически копирует предыдущий. Но как обещано он сделан более универсальным. Количество колонок ColNum вынесено в отделную переменную, а втеле цикла for  добавлен вызов именованного шаблона zeroCell, который добавляет в  концевую строку таблицы пустые ячейки.

Здесь, возможно надо объяснить тонкость связанную с вычислением position(). Здесь вычисляет позиция не в общем списке книг, а в наборе book[position() mod $ColNum= 1] Поэтому значение position для последнего элемента списка фактически равно одной трети (мы задали вывод в  три колонки) от общего числа книг.  Поэтому в именованном шаблоне в последней строке добавляется (count(book) mod 3) пустые ячейки.

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

  1. 2008-11-21
  2. xslt-examples
  3. xslt-group
  1. www.dpawson.co.uk/xsl/sect2/N7450.html - Оформление таблиц в "XSLT Questions and Answers - FAQ" Dave Pawson?s
  2. erum.ru/article/19 - XSLT- примеры. Вып.1 Группировки в XSLT
  3. www.artlebedev.ru/tools/technogrette/xslt/alpha-index/ - Вариант разбиения таблицы у Артемия Лебедева
Go Index Test