Использование xslt-шаблонов в реальных проектах

от автора

В статье вы не найдёте сравнительных тестов шаблонизаторов. Зато найдёте информацию об использовании xslt в качестве шаблонизатора на реальных проектах. Рассмотрены возможности именованных шаблонов, использование шаблонов-функций, справочников.

1. Структура проекта

Обычно страница сайта состоит из нескольких общих блоков (меню, футер, …) и контентной части, которую для общности будем называть основным блоком. Все эти блоки размещаются внутри некоторого индексного шаблона, который знает в каком месте какой блок отобразить: меню должно быть вверху, основной блок в центре, а футер внизу.

Получаем следующую структуру

/themes - здесь раполагаются все шаблоны /themes/index/main.xsl - индексный шаблон /themes/models/user.xsl - именованные шаблоны, которые относятся к модели пользователя /themes/inc/functions.xsl - именованные шаблоны-функции /themes/blocks/footer.xsl - шаблон футера /themes/blocks/menu.xsl - шаблон меню /themes/cabinet/main.xsl - шаблон основного блока главной страницы кабинета пользователя 

Контроллер главной страницы кабинета пользователя работает следующим образом:

  1. получает данные для основного блока, обрабатывает их с помощью /themes/cabinet/main.xsl и результат (готовый html) помещает в итоговый xml
  2. аналогично обрабатывает данные для других блоков (меню, футер) и результат помещает в xml
  3. итоговый xml, в котором находятся данные всех блоков, обрабатывается с помощью индексного шаблона /themes/index/main.xsl и результат отдаёт пользователю.


Индексный шаблон /themes/index/main.xsl может выглядет следующим образом:

<xsl:template match="page">        <head>         <title><xsl:value-of select="title" /></title>     </head>     <body>                 <div class="page-container">             <xsl:value-of select="blocks/menu_top/html" disable-output-escaping="yes"/>                         <div class="main">                          <xsl:value-of select="blocks/content/html" disable-output-escaping="yes"/>             </div>                         <xsl:value-of select="blocks/footer/html" disable-output-escaping="yes"/>         </div>     </body> </xsl:template> 

2. Именованные шаблоны

Шаблон xslt принимает данные в виде xml-документа. Это удобно тем, что мы можем оперировать целыми узлами. Например для вывода имени пользователя у нас может быть такой шаблон

<xsl:template name="inc_show_user">     <xsl:param name="user"/>     <img src="/img/{$user/userpic}.png"/>     <xsl:value-of select="concat($user/first_name, ' ', $user/last_name)"/> </xsl:template> 

который располагается в файле /themes/models/user.xsl.

Мы можем использовать этот шаблон как для отображения текущего пользователя

<xsl:call-template name="inc_show_user">     <xsl:with-param name="user" select="/*/cur_user"/> </xsl:call-template> 

так и для отображения списка пользователей

<xsl:for-each select="users/item">     <xsl:call-template name="inc_show_user">         <xsl:with-param name="user" select="."/>     </xsl:call-template>             </xsl:for-each> 

Такое единство отображения сущностей позволяет быстро изменять их отображение. Конечно не у всех пользователей есть картинка, а значит и выводить её нужно не для всех

<xsl:template name="inc_show_user">     <xsl:param name="user"/>     <xsl:choose>         <xsl:when test="$user/userpic>0">             <img src="/img/{$user/userpic}.png"/>                 </xsl:when>         <xsl:otherwise>             <img src="/img/default.png"/>         </xsl:otherwise>     </xsl:choose>         <xsl:value-of select="concat($user/first_name, ' ', $user/last_name)"/> </xsl:template> 

3. Импорт шаблонов

Для того чтобы в шаблоне блока иметь доступ к отображению сущности «пользователь» мы должны подключить файл /themes/models/user.xsl.
Для шаблона /themes/cabinet/main.xsl подключение будет выглядеть так

<xsl:import href="../models/user.xsl"/> 

(xsl:import должен описываться сразу после xsl:stylesheet)

4. Ни строчки php-кода в представлении

Патерн MVC предполагает разделение модели, логики и представления. Логика приложения запрашивает необходимые данные у модели и передаёт их в представление. Представление должно получить необходимое количество данных, чтобы их отобразить пользователю. Т.е. в представлении мы должны только вывести их и не должны как-либо ещё преобразовывать данные. Мы не должны получать имя пользователя по его id, не должны получать текущее время, и т.д. все эти данные уже должны быть доступны для представления. Если каких-либо данных не хватает, значит контроллер должен их предоставить.

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

Не всегда есть все необходимые средства для получения необходимого результата. Например, вывод окончания для числа. Думаю у многих есть подобная функция

function str_plural_form($n, $form1='штука', $form2='штуки', $form5='штук'){   $lastN=$num%10;   $lastT=$num%100;   if($lastT>=10 && $lastT<=20){     return $form5;   }   switch ($lastN){     case 1:       return $form1;     case 2:     case 3:     case 4:       return $form2;     default:       return $form5;   } } 

И даже больше, xslt позволяет вызвать эту функцию прямо из шаблона

<xsl:value-of select="php:function('str_plural_form', 1*$cnt_users, 'пользователь', 'пользователя', 'пользователей')"/> 

Но это не только противоречит заголовку раздела, но и является неким атавизмом. Лучше избегать вызовов php-функций внутри xslt-шаблонов.
Что же делать? Есть 2 выхода:

  1. пусть контролер вызывает str_plural_form и отдаёт нужные данные
  2. сделать именованный шаблон-функцию, которую мы поместим в /themes/inc/functions.xsl

<xsl:template name="f_plural_form">     <xsl:param name="num"></xsl:param>     <xsl:param name="format">### ###</xsl:param>     <xsl:param name="is_show_num">1</xsl:param>     <xsl:param name="space"/>          <xsl:param name="str1">штука</xsl:param>     <xsl:param name="str2">штуки</xsl:param>     <xsl:param name="str5">штук</xsl:param>          <xsl:if test="$is_show_num=1">       <xsl:value-of select="format-number($num, $format)"/>       <xsl:choose>         <xsl:when test="$space!=''">           <xsl:value-of select="$space" disable-output-escaping="yes"/>             </xsl:when>         <xsl:otherwise>           <xsl:text> </xsl:text>         </xsl:otherwise>       </xsl:choose>     </xsl:if>          <xsl:variable name="lastN" select="$num mod 10"/>     <xsl:variable name="lastT" select="$num mod 100"/>          <xsl:choose>       <xsl:when test="$lastT>=10 and 20>=$lastT">         <xsl:value-of select="$str5" disable-output-escaping="yes"/>       </xsl:when>       <xsl:when test="$lastN=1">         <xsl:value-of select="$str1" disable-output-escaping="yes"/>       </xsl:when>             <xsl:when test="$lastN=2 or $lastN=3 or $lastN=4">         <xsl:value-of select="$str2" disable-output-escaping="yes"/>       </xsl:when>       <xsl:otherwise>         <xsl:value-of select="$str5" disable-output-escaping="yes"/>       </xsl:otherwise>           </xsl:choose> </xsl:template> 

Вызов функции будет выглядеть так

<xsl:call-template name="f_plural_form">   <xsl:with-param name="is_show_num">1</xsl:with-param>   <xsl:with-param name="num" select="$cnt_users"/>   <xsl:with-param name="str1">пользователь</xsl:with-param>   <xsl:with-param name="str2">пользователя</xsl:with-param>   <xsl:with-param name="str5">пользователей</xsl:with-param> </xsl:call-template> 

5. Справочники

Вернёмся к выводу информации о пользователе. К примеру на странице форума нам нужно вывести

  • список постов с именами пользователей,
  • список самых активных пользователей,
  • список пользователей которые в данный момент просматривают эту страницу.

Можно решить задачу влоб. При получении каждого из списков делать LEFT JOIN users и получать необходимые данные для вывода информации о пользователе. Но есть и отрицательные моменты такого решения. Первое — возможная избыточность данных (пользователи из списков могут повторяться), второе — дополнительная нагрузка на sql-сервер.

Другой вариант решения задачи. Получить все списки. Затем из этих списков получить набор user_id. И по этому набору сделать один запрос к таблице users. Результат сложить в xml по известному адресу, например /ref_users.
В итоге у нас должен получиться xml-документ с узлами posts, active_users, online_users, ref_users.

Для вывода информации о пользователе сделаем такой именованный шаблон

<xsl:template name="inc_show_user_by_id">     <xsl:param name="user_id"/>          <!-- поиск пользователя в справочнике по его id -->     <xsl:variable name="cur_user" select="/*/ref_users/item[user_id=$user_id]"/>     <xsl:call-template name="inc_show_user">         <xsl:with-param name="user" select="$cur_user"/>     </xsl:call-template> </xsl:template> 

и сохраним его в /themes/models/user.xsl. Это шаблон для вывода пользователя по его id.

Вывести список постов с информацией о пользователе можно так

<xsl:for-each select="posts/item">     <xsl:call-template name="inc_show_user_by_id">         <!-- передаём в шаблон user_id автора поста -->         <xsl:with-param name="user_id" select="user_id"/>     </xsl:call-template>          <!-- далее вывод самого поста -->     <!-- ... --> </xsl:for-each> 

Заключение

Статья получилась объёмной, поэтому не рассмотренными остались вопросы организации шаблонов для ajax, «абстрактные шаблоны», поддержка нескольких языков. А также вопросы скорости и кеширования.

ссылка на оригинал статьи http://habrahabr.ru/post/151241/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *