Итак, ввиду того что вчера мне дали заслуженный тиц 10, у блога около 10 подписчиков и 30 человек ежедневно заходит, я решила сегодня написать что-нибудь особенное. Сегодня я порадую всех изучающих технологию XSLT. Поскольку это самый известный, стандартизированный и распространённый шаблонизатор я расскажу о группировке в XSLT 1.0.
Кратко об XSLT
Краткий экскурс для коллег которые не сталкивались с XLST. XSLT - это Трансформация XSL (Extensible Stylesheet Language), на входе подаём XML, пропускаем через шаблон XSL и на выходе получаем HTML, (или xhtml, или другой xml). В серверных приложениях трансформацию обычно осуществляет библиотека libxml (и с ней libxsl), которые могут быть использованы как модули к php или другим языкам программирования. Также преобразования могут выполняться на стороне клиента. Когда сам браузер загружает xml-данные и xsl-шаблон и выполняет преобразование. К большому моему сожалению использование такой практики в информационных сайтах недопустимо, поскольку яндекс в отличие от гугла не индексирует xml. Конечно можно поисковикам отдавать одно, а пользователям другое, но это чревато.
Примеры:
http://us.blizzard.com/diablo3/?rhtml=n - Трансформация XSL в браузере пользователя
http://us.blizzard.com/diablo3/?rhtml=y - Трансформация происходит на сервере, клиент получает xhtml.
Группировка записей по месяцам
Если вы заходили на страницу «архив новостей», то видели, что все новости сгруппированы по месяцам, например так:
Октябрь
- 16 октября 2009. О трудностях перевода
- 15 октября 2009. Типы сертификатов SSL
- 6 октября 2009. Ещё об атрибуте alt
- 6 октября 2009. Как сделать чтобы письма не попадали в спам ящик (2 комментариев)
Причём появляются названия только тех месяцев для которых есть новости и соответственно месяцы сортируются по убыванию.
Сразу оговорюсь, что если вы используете шаблоны сделанные по стандарту XLST 2.0, которые начинается таким объявлением
<xsl:stylesheet version="2.0"
то у вас не будет проблем с группировкой и сортировкой, вы можете использовать функцию, for-each-group:
<xsl:for-each-group select="*" group-by="date/@month">
К сожалению в PHP вам не доступна версия шаблонов XSLT 2.0, поскольку с этим стандартом какие-то задержки непонятно у кого. В любом случае вы получите ошибку типа
LibXML Error 1: xsl:version: only 1.0 features are supported
а стандарт XSLT 1.0 (принятый в 1999 году), к сожалению не представляет нам никаких встроенных способов для группировки.
Группировка методом Мюнха
Ситуация улучшилась, когда в 2000 году Стив Мюнх (Steve Muench) в своей книге «Building Oracle XML Applications» придумал трюк использующий функцию id() и генерацию индексов (xsl:key()).
Статей рассказывающих про использование этой техники во всём интернете всего две:
- Grouping Using the Muenchian Method - весьма сложна для восприятия тем что не сразу разбираешься где функции, а где имена элементов. Местами написан вообще невоспринимаемый бред, типа
see whether a node set made up of the two nodes has one or two nodes in it - nodes can't be repeated in a node set, so if there's only one node in it, then they must be the same node
- и Создание алфавитных указателей средствами XSLT - Статья из техногрета студии Лебедева, из которой вообще ничего нельзя узнать, поскольку вместо принципа "от простого к сложному" автор выбрал принцип "напишу много кода чтоб никто ничего не понял" и ограничился скудными комментариями.
Итак, поставим задачу, например у нас есть новости в таком формате:
<?xml version="1.0" encoding="windows-1251"?> <?xml-stylesheet href="news_list.xsl" type="text/xsl"?> <bough_content><!-- Корневой элемент как обычно--> <news_types><!-- Элемент содержащий все типы новостей и указание на активный тип --> <type url="news" active="true">Блог</type> </news_types> <list_news><!-- Элемент содержащий все новости --> <news_entry id="36" type="news" link="http://usabili.ru/news/2009/06/30/valid_xhtml_variants.html" comments="0"> <date time="1246368780" day="30" month="06" month_rus="июня" year="2009"/> <caption>Вариации валидного xhtml</caption> </news_entry> <news_entry id="35" type="news" link="http://usabili.ru/news/2009/06/24/geo-targeting.html" comments="0"> <date time="1245838500" day="24" month="06" month_rus="июня" year="2009"/> <caption>Гео-таргетинг, как сделать своими руками.</caption> </news_entry> <news_entry id="404" type="news" link="http://usabili.ru/news/2009/06/16/web_statistic.html" comments="0"> <date time="1245163380" day="16" month="01" month_rus="июня" year="2009"/> <caption>Какая-то новость за январь.</caption> </news_entry> </list_news> </bough_content>
см. файл с вызовом шаблона news.xml
вообще для того чтобы нам показать название месяцев в шаблоне нам нужно эти данные в нём указать, например так:
<?xml version="1.0" encoding="windows-1251"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://foo.com" exclude-result-prefixes="m"> <xsl:output method="html" encoding="windows-1251" indent="yes"/> <m:months> <month name="Январь" value="1"/> <month name="Февраль" value="2"/> <month name="Март" value="3"/> <month name="Апрель" value="4"/> <month name="Май" value="5"/> <month name="Июнь" value="6"/> <month name="Июль" value="7"/> <month name="Август" value="8"/> <month name="Сентябрь" value="9"/> <month name="Октябрь" value="10"/> <month name="Ноябрь" value="11"/> <month name="Декабрь" value="12"/> </m:months>
Строка "xmlns:m="http://foo.com"" нужна для объявления собственных данных прямо в шаблоне XSL. Далее, зададим шаблон вывода одной конкретной новости:
<xsl:template match="list_news/news_entry"> <xsl:if test="caption!=''"> <li><xsl:value-of select="round(date/@day)"/> <xsl:value-of select="date/@month_rus"/> <xsl:value-of select="date/@year"/>.  <a href="{@link}"><xsl:value-of select="caption"/></a>  <xsl:if test="@comments>0">(<xsl:value-of select="@comments"/> комментариев)</xsl:if> </li> </xsl:if> </xsl:template>
До использования группировки я просто выводила новости помесячно, примерно так:
<h2>Март</h2> <ul><xsl:apply-templates select = "//list_news/news_entry[date/@month=3]"/></ul>
Итак... чтобы применить группировку нам нужен индекс (он же ключ, т.е. key()), назовём его articles-by-month. Ключ можно создать внутри <xsl:stylesheet/> но вне какого-либо <xsl:template/>
<xsl:key name="articles-by-month" match="/bough_content/list_news/news_entry" use="date/@month"/>
Тут мы сделали индекс (т.е. подготовку к выборке) для всех статей по месяцам. Узлы (nodes) которые мы хотим группировать указываем в атрибуте match, а значение которое мы хотим использовать для группировки указываем в атрибуте use. Как только мы определили индекс мы можем выбирать все новости за определённый месяц, например так
key('articles-by-month', '01')
Для вывода сгруппированных по месяцам записей у меня получился такой небольшой код:
<h1>Архив новостей</h1> <xsl:variable name="months" select="document('')//m:months"/> <xsl:for-each select="list_news/news_entry[count(. | key('articles-by-month', date/@month)[1]) = 1]"> <xsl:sort select="date/@month" order="descending"/> <h2> <xsl:variable name="this_month" select='date/@month'/> <xsl:value-of select="$months/month[round($this_month)]/@name"/> </h2> <ul><xsl:apply-templates select="key('articles-by-month', date/@month)" /></ul> </xsl:for-each>
здесь переменная "<xsl:variable name="months"" введена для понятности, в ней берётся значение объявленное в самом теле шаблона, в узле <m:months/>. Переменная this_month нужна затем что зайдя каким-то действием в дерево узлов шаблона нельзя получить узлы дерева данных, по крайней мере я не смогла.
В цикле мы проходимся по всем узлам отсортированным по месяцу. Запросом key('articles-by-month', date/@month)[1] мы выбираем первую статью из выборки по месяцу. В XSLT значение "." (точка) означает текущий узел. Оператор "|" (вертикальная палочка) означает бинарное сравнение. В результате мы выбираем все статьи которые равны первой статье из выборки по месяцу, который равен месяцу текущей статьи. Как нетрудно догадаться будут выбраны все первые статьи каждого месяца. Дальше довольно просто, так как мы уже сделали группировку по месяцам просто показываем название месяца и выводим выборку за этот месяц.
Весь шаблон вы можете посмотреть здесь: news_list.xsl
Спасибо за внимание =)
Комментарии:
4ch
29.09.2012 15:19:36