XSLT в PHP

Прочитал я как-то статью о создании сайта под партнерскую программу GameBoss.
Дочитал до строчки: «Данные мы получаем от GameBoss в виде XML фида, т.е. нам необходимо их разобрать и сохранить в базе.», и решил пойти альтернативным путём. XML — это уже база данных.

   Конечно, для реализации подобного сайта база данных будет нужна в любом случае. Хотя-бы, для хранения аккаунтов пользователей. Но информацию об играх можно брать непосредственно из XML.
   Пример надуманный, и на полноценный сайт не претендует. Так-же в статье не будет подробного рассмотрения XSLT ( и XPath). В статье я хочу показать лёгкость работы с XSLT на PHP.

   Для работы с фидом я буду использовать XSLT.
В PHP5 по-умолчанию доступна поддержка XSLT. Класс для работы с ним называется XSLTProcessor. Если в подключённых модулях его нет (модуль xsl), то в консоле делаем следующее: sudo apt-get install php5-xsl (для Deb-based систем) , после чего рестартуем апач (sudo /etc/init.d/apache2 restart).
   Под Windows нужно раскомментировать строчку «;extension=php_xsl.dll». Строчка может выглядеть немного по-другому, в зависимости от варианта установки PHP.

   К сожалению, расширение базируется на libxml2, которая не поддерживает XSLT2.0. Но и на том спасибо.
Рассмотрим две страницы:

  • Главная страница
  • Страница игры

Каждая из страниц состоит из контролера (/*.php), который обрабатывает запрос, XSLT-документа (/*.xsl), который обрабатывает фид, и шаблона (/tpl/*.php).
Фид получаем по запросу вида http://gameboss.ru/x2.php?partner=[xxxxx]&limit=10000&genre=127&short=1&full=1&image=1,
где:

  • partner — Ваш партнерский ID, выдаётся после регистрации
  • limit — Требуемое количество игр в выдаче
  • genre — битовая маска жанров (127 = все игры)
  • short — если Вам нужно короткое описание игры
  • full — если Вам нужно полное описание игры
  • image — если Вам нужны скриншоты

Более подробно можно узнать на сайте партнерской программы.

Главная страница

   Она же index.php
Сначала некоторые объявления и впомогательная функция для пейджера:


<?php
//Pager function
function pager($curr, $count, $className, $by, $order) {
  $htm = "<ul class=\"$className\">";
  for($i=1; $i<=$count; $i++) {
    if ($i == $curr) {
      $htm .= "<li>$i</li>";
    } else {
      $htm .= "<li><a href=\"?page=$i&by=$by&order=$order\">$i</a></li>";
    }
  }
  return "$htm</ul>";
}

$feed = 'http://gameboss.ru/x2.php?partner=xxxxx&limit=10000&genre=127&short=1&full=1&image=1';
// Data-feed address

$file = 'games.xml';
//Cache

$gamePerPage = 9;
//Games per page

   Далее мы кешируем наш фид:


clearstatcache(); //Clear file status cache
if (file_exists($file)) {
  if (date("Ymd", filemtime($file)) < date("Ymd")) {
    file_put_contents($file, file_get_contents($feed));
  }
} else {
  file_put_contents($file, file_get_contents($feed));
}

   Суть в следующем: Если файл существует и дата последней модификации сегодняшняя, то используем этот файл, в противном случае обновляем его.
Дальше получаем из ГЕТа по-чём сортировать, как сортировать и номер страницы. Вычисляем позиции игр и подготавливаем наш XSLT-файл. Формируем стоку для сортировки и делаем замену по шаблону. О сортировке поговорим чуть-позже.


$by    = !empty($_GET['by'])? $_GET['by']: 'RATE';
$order = !empty($_GET['order'])? $_GET['order']: 'descending';
$page  = !empty($_GET['page'])? $_GET['page']: '1';

$from  = $gamePerPage * ($page-1);
$to    = $from + $gamePerPage;
$from += 1;

$sort_params = array(
  'RATE' => '<xsl:sort select="RATE" order="'. $order .'" data-type="number"/>',
  'NAME' => '<xsl:sort select="NAME" order="'. $order .'" data-type="text" lang="ru"/>'
);

$search = array("%%sort tag%%", "%%from%%", "%%to%%");
$replace = array($sort_params[$by], $from, $to);

$transformTemplate = str_replace($search, $replace, file_get_contents('index.xsl'));

   загружаем фид и получаем количество игр и страниц:


//LOAD XML FILE
$games = new DOMDocument();
$games->load($file);

//Get count of games and calculate count of pages
$gameCount = $games->getElementsByTagName('count')->item(0)->nodeValue;
$pageCount = ceil($gameCount/$gamePerPage);

   Инициируем XSLT процессор, загружаем xsl-документ и отдаём результат в браузер:


//START XSLT
$xslt = new XSLTProcessor();
$XSL = new DOMDocument();
$XSL->loadXML($transformTemplate, LIBXML_NOCDATA);
$xslt->importStylesheet($XSL);

//PRINT
$list = $xslt->transformToXML($games);
$pager = pager($page, $pageCount, 'pager', $by, $order);

include('tpl/main.php');

   Файл tpl/main.php содержит следующий шаблон:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>site.domain</title>
    <meta content="text/html; charset=utf-8" http-equiv="Content-type"/>
    <link rel="stylesheet" type="text/css" href="css/reset.css" />
    <link rel="stylesheet" type="text/css" href="css/main.css" />
  </head>
  <body>
    <div id="header"></div>
    <div class="sort">
      Sort by: RATE (<a href="?page=<?php echo $page;?>&by=RATE&order=ascending">ASC</a> | <a href="?page=<?php echo $page;?>&by=RATE&order=descending">DESC</a>),
               NAME (<a href="?page=<?php echo $page;?>&by=NAME&order=ascending">ASC</a> | <a href="?page=<?php echo $page;?>&by=NAME&order=descending">DESC</a>)
    </div>
    <div><?php echo $list;?></div>
    <div class="pager-wraper"><?php echo $pager;?></div>
    <div id="footer"></div>
  </body>
</html>

   Теперь поговорим об XSLT.
XSLT (Extensible Stylesheet Language Transformations) — это декларативный язык преобразования документов. В версии 1.0 преобразовывать можно только из XML. Часто выходным форматом является XML или HTML, но в принципе ограничений на выходной формат нет. XSLT использует XPath для для выбора частей исходного XML-документа. Подробно эти два языка здесь рассматриваться не будут.

   Теперь рассмотрим файл index.xsl:


<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <!-- template for root -->
  <xsl:template match="/">
    <ul class="list">
    <xsl:for-each select="response/result/ITEM">
      <!-- put xsl:sort tag here -->
      %%sort tag%%
      <!-- set limit -->
      <xsl:if test="(position() &gt;= %%from%%) and (position() &lt;= %%to%%)">
        <li>
          <!-- construct XML element -->
          <xsl:element name="div">
            <xsl:attribute name="class">gname</xsl:attribute>
            <xsl:element name="a">
              <xsl:attribute name="href">
              game.php?game=<xsl:value-of select="position()"></xsl:value-of>
              </xsl:attribute>
              <xsl:value-of select="NAME"></xsl:value-of>
            </xsl:element>
          </xsl:element>
          <xsl:element name="a">
            <xsl:attribute name="href">
            game.php?game=<xsl:value-of select="position()"></xsl:value-of>
            </xsl:attribute>
	          <xsl:element name="img">
	            <xsl:attribute name="class">ss</xsl:attribute>
	            <xsl:attribute name="src">
	              <xsl:value-of select="MEDIUM_PIC"></xsl:value-of>
	            </xsl:attribute>
	          </xsl:element>
          </xsl:element>
          <xsl:element name="div">
            <xsl:attribute name="class">grate</xsl:attribute>
            rate: <xsl:value-of select="RATE"></xsl:value-of>
          </xsl:element>
          <xsl:element name="div">
            <xsl:attribute name="class">gshort-descr</xsl:attribute>
            <xsl:value-of select="SHORTDESCR"></xsl:value-of>
          </xsl:element>
          <xsl:element name="div">
            <xsl:attribute name="class">down-link</xsl:attribute>
            <xsl:element name="a">
              <xsl:attribute name="href">
                <xsl:value-of select="DOWNLOAD_LINK"></xsl:value-of>
              </xsl:attribute>
              DOWNLOAD
            </xsl:element>
          </xsl:element>
        </li>
      </xsl:if>
    </xsl:for-each>
    </ul>
  </xsl:template>
</xsl:stylesheet>

   Во время обхода дерева XML XSLT-процессор применяет шаблоны преобразований к найденным тегам. У нас существует только один шаблон корня документа. В этом шаблоне мы формируем HTML-код ненумерованного списка из нашего документа. В цикле (<xsl:for-each select=«response/result/ITEM»>) перебираем все элементы, удовлетворяющие запросу «response/result/ITEM». Используя XSLT2.0-процессор мы могли-бы избавиться от вложенного тега <xsl:if/>, составив запрос следующим образом: response/result/ITEM[position() = n to m].

   Далее мы определяем критерии сортировки списков узлов. Элемент <xsl:sort/> имеет несколько атрибутов. Атрибут select определяет ключ сортировки для узла (элемента). Проводя аналогию с SQL, узел можно сравнить с таблицей, а ключ сортировки с полем этой таблицы в выражении ORDER BY. Атрибут order определяет порядок сортировки, и может принимать два значения: «ascending» и «descending». Атрибут data-type указывает тип ключа сортировки. Может принимать следующие значения: «text», «number» и QName. С первыми двумя всё должно быть понятно. Тип QName — это тип расширенного имени. Используется для работы с типами данных схем XML.

   С помощью тега <xsl:if/> мы реализуем педжинацию. Если позиция узла находится между %%from%% и %%to%%, мы обрабатываем этот узел, иначе — просто пропускаем его. Это можно сравнить с выражением LIMIT в SQL-запросе. Нужные нам узлы мы трансформируем в HTML. Название тега <xsl:element/> Должно говорить само за себя. Он конструирует произвольный XML-тег. Тег <xsl:value-of/> вставляет значение выбранного узла в виде текста. Значением атрибута select всё так-же является выражение XPath.

Страница игры

   Она же game.php.
По сравнению с index.php, здесь всё совсем просто:


<?php
$file = 'games.xml';
//Cache

$game = !empty($_GET['game'])? $_GET['game']: '1';
//Position of game

$transformTemplate = str_replace('%%game position%%', $game, file_get_contents('game.xsl'));

//LOAD XML FILE
$games = new DOMDocument();
$games->load($file);

//START XSLT
$xslt = new XSLTProcessor();
$XSL = new DOMDocument();
$XSL->loadXML($transformTemplate, LIBXML_NOCDATA);
$xslt->importStylesheet($XSL);
//PRINT
$info = $xslt->transformToXML($games);

include('tpl/game.php');

Файл tpl/game.php содержит следующий шаблон:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta content="text/html; charset=utf-8" http-equiv="Content-type"/>
    <link rel="stylesheet" type="text/css" href="css/reset.css" />
    <link rel="stylesheet" type="text/css" href="css/main.css" />
    <link rel="stylesheet" type="text/css" href="css/ui/jquery-ui-1.7.2.custom.css" />
    <script type="text/javascript" src="js/jquery-1.3.2.min.js"></script>
    <script type="text/javascript" src="js/jquery-ui-1.7.2.custom.min.js"></script>
    <script type="text/javascript" src="js/game.js"></script>
  </head>
  <body>
    <div id="header"></div>
    <div class="game"><?php echo $info;?></div>
    <div id="footer"></div>
    <div id="popup">
      <a id="dialogClose" href="#" class="ui-corner-all" role="button" unselectable="on" style="-moz-user-select: none;">
        <span class="ui-icon ui-icon-closethick" unselectable="on" style="-moz-user-select: none;">close
      </a>
      <div id="showImg">
      </div>
    </div>
 </body>
</html>

   Джаваскрипт подключается для полноразмерных скриншотов.
Файл game.xsl не сложнее index.xsl:


<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="result1">
    <xsl:apply-templates select="ITEM"/>
  </xsl:template>
  <!-- put position of game here -->
  <xsl:template match="ITEM[%%game position%%]">
    <h1>
      <xsl:value-of select="NAME"></xsl:value-of>
    </h1>
    <xsl:element name="div">
      <xsl:attribute name="class">game-prop</xsl:attribute>
	    Rate: <xsl:value-of select="RATE"></xsl:value-of> <br/>
	    Added: <xsl:value-of select="ADDED"></xsl:value-of> <br/>
	    Size: <xsl:value-of select="SIZE"></xsl:value-of> <br/>
    </xsl:element>
    <xsl:element name="img">
      <xsl:attribute name="class">game-img</xsl:attribute>
      <xsl:attribute name="src">
        <xsl:value-of select="MEDIUM_PIC"></xsl:value-of>
      </xsl:attribute>
    </xsl:element>
    <xsl:element name="div">
      <xsl:attribute name="class">game-descr</xsl:attribute>
      <xsl:value-of select="FULLDESCR"></xsl:value-of>
    </xsl:element>
    <xsl:element name="div">
      <xsl:attribute name="class">screens-text</xsl:attribute>
      СКИНШОТЫ:
    </xsl:element>
    <ul id="screens" class="screens">
      <xsl:for-each select="SCREENSHOT">
        <li>
          <xsl:element name="img">
            <xsl:attribute name="class">full</xsl:attribute>
	          <xsl:attribute name="src">
	            <xsl:value-of select="IMAGE"></xsl:value-of>
	          </xsl:attribute>
	        </xsl:element>
	        <xsl:element name="img">
	         <xsl:attribute name="class">thumbs</xsl:attribute>
	          <xsl:attribute name="src">
	            <xsl:value-of select="THUMBNAIL"></xsl:value-of>
	          </xsl:attribute>
	        </xsl:element>
	      </li>
	    </xsl:for-each>
    </ul>
	   <xsl:element name="div">
	     <xsl:attribute name="class">down-link</xsl:attribute>
	     <xsl:element name="a">
	       <xsl:attribute name="href">
	         <xsl:value-of select="DOWNLOAD_LINK"></xsl:value-of>
	       </xsl:attribute>
	       DOWNLOAD
	     </xsl:element>
    </xsl:element>
  </xsl:template>

  <xsl:template match="ITEM"></xsl:template>
  <xsl:template match="count"></xsl:template>
</xsl:stylesheet>

   Сылки:

Запись опубликована в рубрике PHP, XML. Добавьте в закладки постоянную ссылку.

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

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

This blog is kept spam free by WP-SpamFree.