![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Главная | Учебник | Статьи | FAQ | Книги | Ссылки |
Преобразование XML + XSLT с помощью Sablotron
Дмитрий Лебедев
Альтернативное введение в использование XSL Transformations в PHP при помощи Sablotron.
Данный материал следует воспринимать как альтернативное введение в использование XSLT с Sablotron в PHP.
Термины XSL и XSLT близки друг к другу, и новичкам их можно считать синонимами. Подробности, в чём же различия, описаны в спецификации XSL Transformations W3C.
Все, кто интересовался возможностями XSLT, читал стандартный пример из мануала, либо примеры, приводимые в статьях, посвящённых XSLT, на разных сайтах. Работающий пример из этой же серии:
<?php $xmlData = '<?xml version="1.0" encoding="Windows-1251"?> <document> <game> <title>Railroad Tycoon II Platinum</title> <genre>экономическая стратегия</genre> <designer>PopTop software</designer> <publisher>G.O.D. games</publisher> <year>2001</year> </game> <game> <title>Grand Prix 4</title> <genre>автосимулятор</genre> <designer>Geoff Crammond & Simergy</designer> <publisher>Infogrames Entertainment</publisher> <year>2002</year> </game> </document>'; $xslData = '<?xml version="1.0" encoding="windows-1251"?> <!DOCTYPE xsl:stylesheet> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html" indent="yes" encoding="Windows-1251"/> <xsl:template match="/"> <xsl:apply-templates/> </xsl:template> <xsl:template match="document"> <html><head> <title>Игры</title> </head> <body> <h1>Игры</h1> <table cellpadding="2" cellspacing="2" border="1"> <tr> <td>Название</td> <td>жанр</td> <td>год</td> <td>разработчик</td> <td>издатель</td> </tr> <xsl:apply-templates select="game"/> </table> </body></html> </xsl:template> <xsl:template match="game"> <tr> <td><b><xsl:value-of select="title"/></b></td> <td><xsl:value-of select="genre"/></td> <td><xsl:value-of select="year"/></td> <td><xsl:value-of select="designer"/></td> <td><xsl:value-of select="publisher"/></td> </tr> </xsl:template> </xsl:stylesheet>'; $xh = xslt_create(); $arguments = array( '/_xml' => $xmlData, '/_xsl' => $xslData ); $result = @xslt_process($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); if ($result) print ($result); else { print ("There was an error that occurred in the XSL transformation...\n"); print ("\tError number: " . xslt_errno($xh) . "\n"); print ("\tError string: " . xslt_error($xh) . "\n"); exit; } ?>
Подобных примеров в Сети полно. Все они хорошо показывают, что XSL-трансформация в php работает, но после их прочтения остаётся неясным, зачем XSL нужен, скорее даже наоборот — почему XSL не нужен.
"Действительно", — подумает читатель, — "если данные лежат в базе, зачем городить огород, формируя сперва XML, а затем ещё преобразовывать через XSL? С тем же успехом это сделает класс HTML-шаблона."
После этого разочарованный программист напрочь теряет интерес к XSL и вешает на технологию ярлык "ненужная заумь".
Вам, уважаемые читатели, повезло найти такой замечательный сайт, как "php в деталях". Здесь вы прочитаете о том, что XSL может не только преобразовывать XML в HTML, но и то, как можно при помощи XSL облегчить работу с php-скриптами.
Приведённый выше пример, хоть и слишком прост, хорошо иллюстрирует, каким образом делается XSL-преобразование в php.
Чтобы этот код работал, нужно установить XSLT-процессор Sablotron. На виндовой машине это делается так:
# положить iconv(-1.3).dll, expat.dll и sablot.dll в C:\windows\System (все файлы есть в стандартном дистрибутиве php) # открыть C:\windows\php.ini и в нём найти параметр extension_dir. Если значение параметра — "." или нечто вроде "./", исправить на, скажем, "f:\usr\local\php\extension" (или адрес директории, в которой у вас лежат/будут лежать расширения php). Теперь это будет директория расширений php. # положить в директорию расширений файл php_xslt.dll (это для php версии 4.2.x), либо php_sablot.dll (для версии 4.0.x) # в php.ini раскомментируйте строчку extension=php_xslt.dll (4.2.x) или extension=php_sablot.dll (4.0.x)
Это была краткая инструкция по установке. Я вам этого не говорил! С вопросами по установке просьба идти в специальный форум.
Как я уже писал ранее в статьях от 19.06.2001, 28.08.2001 и 29.05.2002, использование XSLT позволяет отделить от php-скриптов работу по форматированию и представлению данных. Это не только уменьшение объёма кода, но и вынос большого количества логических конструкций (if, else, switch), а следовательно, облегчение работы по написанию и отладке программ. Смею утверждать, что тот, кто не пробовал работать с XSLT, не представляет себе, насколько php-кодирование облегчится.
Впрочем, не надо обольщаться: если у вас было несколько конструкций if ... else в php-скрипте, они, скорее всего, появятся в том же количестве в XSL-файле.
Теперь к примерам.
Все усложнения, происходящие от необходимости выводить список в удобочитаемом виде, переносятся на плечи XSL. Пример #2. Список статей на сайте с подсветкой статьи, которую читают сейчас, чередование цвета в строках и нумерация списка.
XML: <current-date>2002-05-30</current-date> <list-article date="2002-10-03">Ловля ошибок в PHP</list-article> <list-article date="2002-10-02">Живой проект и мёртвый журнал</list-article> <list-article date="2002-06-03">Работа с MySQL. Часть 7. Деревья</list-article> <list-article date="2002-05-30">Ручная сортировка в веб-интерфейсе</list-article> <list-article date="2002-05-29">Как поладить дизайнеру с программистом</list-article> <list-article date="2002-05-27">Relax this is PHP</list-article> XSLT: ... <table> <xsl:apply-templates select="list-article"/> </table> ... <xsl:template match="list-article"> <tr> <xsl:if test="position() mod 2 = 1"> <xsl:attribute name="bgcolor">#cccccc</xsl:attribute> </xsl:if> <td> <xsl:value-of select="position()"> <a href="/{@date}.htm"><xsl:value-of select="."/></a> <xsl:if test="@date = ../current-date"> <</xsl:if> </td> </tr> </xsl:template>
Переводя на XML сайт с текстами (как этот), естественно хотеть сделать собственную разметку статей. Например, в контейнером important выделять очень важные места и иметь возможность выделять их не обязательно жирным шрифтом, но, может быть, цветом, CSS-стилем. Или писать цитаты как <quote>текст цитаты<quote> и иметь возможность менять стиль их оформления вместе с дизайном сайта.
Медленно продвигаясь от самого простого первого примера, многие натыкаются на эту проблему и не могут найти решения. Ведь если выделить абзац в тег <para> и делать для него шаблон, на первый взгляд, существуют три способа вывода содержимого:
# тег xsl:value-of выводит текст, но удаляет все теги в абзаце # тег xsl:copy-of выводит копию всего содержимого (без возможности применять шаблоны к детям — внутренним тегам) и самого контейнера <para>...</para> (что не очень красиво в HTML). # наконец, xsl:apply-templates применит шаблоны к детям, но пропустит текст
Проблема кажется безвыходной, но решение есть. Я использую "магические" шаблоны, которые выводят и текст и теги в нём со всеми атрибутами и без изменений. Пример #3:
XML: <text> <para>Данный пример использует <strong>магические шаблоны</strong> для разбора произвольной разметки. Это позволяет избежать таких жалоб: </para> <quote>Люди, памажите сами мы не местные! Не могу вывести теги в тексте при помощи value-of! </quote> <hr/> <strong>Запомните эти шаблоны раз и навсегда!</strong> <para>Тогда вы сможете обрабатывать <u>любой</u> <a href="http://www.txt.ru">текст</a> Почти любой. </para> </text> XSLT: <xsl:template match="text"><xsl:apply-templates/></xsl:template> <xsl:template match="strong"> <font color="#cc0000"><b><xsl:apply-templates/></b></font> </xsl:template> <!-- три магических шаблона --> <!-- 1. общий --> <xsl:template match="*"> <xsl:copy> <xsl:apply-templates select="@*" /> <xsl:apply-templates/> </xsl:copy> </xsl:template> <!-- 2. для текста --> <xsl:template match="text()"> <xsl:value-of select="." disable-output-escaping="yes"/> </xsl:template> <!-- 3. для тегов и аттрибутов --> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template>
Первым делом XSLT-процессор при вызове инструкции apply-templates ищет шаблон для каждого элемента. Для элемента strong шаблон есть, и именно в соответствии с ним такие элементы будут обработаны. Для гиперссылки шаблона нет, поэтому она будет выведена, как есть. Можно добавить в XSL шаблон и для ссылки, который бы выводил рядом с каждой текстовой ссылкой картинку для открытия её в новом окне:
<xsl:template match="a[@href]"> <xsl:copy-of select="."/> <a href="{@href}" target="_blank"><img src="/window.gif" width="15" height="15" alt="открыть в новом окне"/></a> </xsl:template>
* в шаблоне использован параметр match="a[@href]" — этот шаблон будет применён только к тем тегам ссылок, в которых есть поле href и пропустит якоря (<a name="xxx"></a>).
Кажущаяся необходимость писать валидный XML-код так же отпугивает многих неофитов XSLT. Хорошо, с завтрашнего дня будем писать статьи только валидно, благо дома можно проверить, нет ли в тексте XML-ошибки — mismatched tag или invalid token, — с этим как-нибудь справимся. Но ведь, по-хорошему, нужно и весь архив перевести в валидный код! И я так тоже думал, когда появилась возможность переделывать сайт на XML.
Решение проблемы довольно простое: не хочешь — не пиши валидно. Пиши, как привык, — без кавычек в атрибутах тегов, используй простой <br> и прочее. Достаточно заключить текст в контейнер <![CDATA[ ... ]]> (пример ниже).
Что касается , то здесь дела такие: элемента nbsp в XML нет. Есть lt, gt, quot, но не nbsp (вполне логично — это ведь non-braking space, который относится к форматированию и придуман для HTML). Поэтому его нужно объявить в документе, либо использовать только внутри <![CDATA[...]]>.
XML: <text> <bad-markup><![CDATA[В этом <a href=http://detail.phpclub.net>тексте</a> применена невалидная разметка. <br> И ничего страшного.]]></bad-markup> <quote>Люди, памажите, сами мы не местные!</quote> <hr/> <strong>Запомните и эти шаблоны тоже!</strong> </text> XSLT: <xsl:template match="text"><![CDATA[ >>> и в XSL можно делать то же самое! <<< ]]> <xsl:apply-templates/></xsl:template> <xsl:template match="bad-markup"> <xsl:value-of select="." disable-output-escaping="yes"/> </xsl:template> <xsl:template match="*"> <xsl:copy> <xsl:apply-templates select="@*" /> <xsl:apply-templates/> </xsl:copy> </xsl:template> <xsl:template match="text()"> <xsl:value-of select="." disable-output-escaping="yes"/> </xsl:template> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template>
Очень удобно! Большие изменения в архив вносить не придётся. Можно начать писать валидно, а продолжать как попало. А можно комбинировать эти два подхода. Чтобы не писать в архивные файлы тег CDATA, я сделал простое преобразование при помощи регулярных выражений (важно так же помнить, что один тег CDATA не должен содержать в себе другой).
$doc = preg_replace("~<(p|h[1-3]|pre)>(.*?)</\\1>~", "<\\1>\\2</\\1>", $doc);
Допустим, нам нужно сделать форму для редактирования статьи, в том числе её даты. Для удобства пользования надо сделать три раскрывающихся списка (далее — "крутилки") — дата от 1 до 31, месяц, год. Первое решение, которое приходит в голову — сделать HTML-код крутилок в php, вставить в XML в контейнере CDATA, а затем вывести в XSL с параметром disable-output-escaping="yes".
На самом деле, XSLT может и это. Достаточно вставить в данные XML число, номер месяца и год. Крутилки можно нарисовать сразу в XSLT.
Напишем шаблон, не предназначенный ни для какого элемента документа. Он будет вызываться командой xsl:call-template и получать два параметра: значение счётчика и максимум. Сперва он будет выводить нужные нам данные со значением счётчика, затем вызывать самого себя с параметрами максимум и счётчик, увеличенный на 1. Пример #5:
XML: <month-name>Январь</month-name> <month-name>Февраль</month-name> <month-name>Март</month-name> <month-name>Апрель</month-name> <month-name>Май</month-name> <month-name>Июнь</month-name> <month-name>Июль</month-name> <month-name>Август</month-name> <month-name>Сентябрь</month-name> <month-name>Октябрь</month-name> <month-name>Ноябрь</month-name> <month-name>Декабрь</month-name> <article> ... <day>7</day> <month>10</month> <year>2002</year> </article> XSLT: <xsl:template match="article"> ... <select name="d"> <xsl:call-template name="day"> <xsl:with-param name="count">1</xsl:with-param> </xsl:call-template> </select> <select name="m"> <xsl:call-template name="month"> <xsl:with-param name="count">1</xsl:with-param> </xsl:call-template> </select> ... </xsl:template> <xsl:template name="day"> <xsl:param name="count"/> <option value="{$count}"> <xsl:if test="$count = //artcile/day"> <xsl:attribute name="selected">yes</xsl:attribute> <xsl:if> <xsl:value-of select="$count"/> </option> <xsl:if test="$count < 31"> <xsl:call-template name="day"> <xsl:with-param name="count"> <xsl:value-of select="$count + 1"/> </xsl:with-param> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template name="month"> <xsl:param name="count"/> <option value="{$count}"> <xsl:if test="$count = //artcile/month"> <xsl:attribute name="selected">yes</xsl:attribute> <xsl:if> <xsl:value-of select="//month-name[position() = $count]"/> </option> <xsl:if test="$count < 12"> <xsl:call-template name="month"> <xsl:with-param name="count"> <xsl:value-of select="$count + 1"/> </xsl:with-param> </xsl:call-template> </xsl:if> </xsl:template>
Оставляю вам в качестве домашнего задания шаблон для вывода крутилки с годом.
Как видите, многое из того, что пишется в php-скриптах, даже при использовании класса шаблона, можно успешно спустить в XSLT. Но стОит ли заниматься этим?
Ответ зависит от условий работы в вашем проекте. О разделении функций формирования и представления данных я уже писал. Второй момент — технологичность работы.
Допустим, я захочу сделать на этом сайте меню быстрой навигации — раскрывающийся список со всеми статьями, — чтобы пользователь мог выбрать статью из списка и сразу перейти к ней. Ещё я захочу оставить список последних материалов (сейчас он находится справа вверху).
Если делать это при помощи класса шаблона типа FastTemplate, нужно два специальных блока и дополнительный код в php, который бы объявлял в шаблоне блок для списка всех статей и отдельно блок для списка 10 последних. Аналогичные действия необходимы в таком случае и при работе без класса шаблона. При работе с XML достаточно всего лишь одного набора данных "Дата => Статья", из которого в XSL-документе строятся и листбокс быстрого перехода, и список последних статей.
А если вдруг понадобится неважно для чего сделать другое оформление сайта (например, версия для WAP, или просто редизайн), в котором будет решено отказаться от списка 10 последних материалов. В случае первых двух технологий — класс шаблона и смешанный код — нужно будет убрать часть php кода, в случае XSLT изменения коснутся только XSL-файла. Такой процесс более технологичен, поскольку невозможно сделать новые ошибки в php-скриптах (а теперь представьте обратный случай — списка 10 последних статей не было, но его решили добавить!).
Итак, выбор остаётся за вами, а я как мог привёл сильные стороны технологии и доводы в пользу использования XML в проектах.