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