Обработка LiveJournal RSS стандартными средствами PHP
RSS — это стандарт XML-документа, разработанный первоначально Netscape, а далее доработанный под патронажем W3C. Задача стандарта — передавать краткую выжимку обновляемой информации в качестве так называемого «канала» (channel). Чаще всего в формате RSS предоставляются последние новости или анонсы информационных материалов. Интерес разработчиков, обращенный к обмену информацией в Рунете, постепенно возрастает, а в зарубежном сегменте Сети данный стандарт уже используется достаточно часто и находит все новых и новых приверженцев.
Для того, чтобы понять, как устроен RSS 2.0, достаточно ознакомиться со спецификацией стандарта и просмотреть примеры. Настоятельно рекомендую ознакомиться с этим документом.
Задача.
Ныне популярный сервис LiveJournal предоставляет возможность доступа к RSS-каналам дневников. Именно это и натолкнуло меня на мысль о том, что дневник можно «прикрутить» к любому сайту, даже будучи бесплатным пользователем. Оговорюсь, разработчики Живого Журнала предоставляют относительно удобные средства интегрирования журнала и сайта только платным пользователям.
LiveJournal RSS.
http://www.livejournal.com/users/bikman/data/rss — по этому адресу располагается RSS-канал дневника. Конечно, имя пользователя (в данном случае bikman) нужно изменить на то, которое интересует читателя. Я же во всех примерах буду использовать свой жж. Думаю, большой проблемой смена имени пользователя не станет.
Лучшим способом понять, как устроен RSS, автоматически генерируемый Живым Журналом, является анализ примера, приведенного ниже:
<?xml version="1.0" encoding="utf-8" ?> <rss version="2.0"> <channel> <title>bikman's</title> <link>http://www.livejournal.com/users/bikman/</link> <description>bikman's - LiveJournal.com</description> <lastBuildDate> Wed, 14 Jan 2004 01:20:41 GMT </lastBuildDate> <generator>LiveJournal / LiveJournal.com</generator> <image> <url>http://userpic.livejournal.com/8412362/1106951</url> <title>bikman's</title> <link>http://www.livejournal.com/users/bikman/</link> <width>100</width> <height>100</height> </image> <item> <guid isPermaLink="true"> http://www.livejournal.com/users/bikman/94200.html</guid> <pubDate> Wed, 14 Jan 2004 01:10:34 GMT </pubDate> <title>Заголовок второго поста.</title> <link>http://www.livejournal.com/users/bikman/94200.html</link> <description> Здесь находится текст второго поста. </description> </item> <item> <guid isPermaLink="true"> http://www.livejournal.com/users/bikman/94200.html</guid> <pubDate> Wed, 14 Jan 2004 01:10:34 GMT </pubDate> <title>Заголовок второго поста.</title> <link>http://www.livejournal.com/users/bikman/94200.html</link> <description> Здесь находится текст второго поста. </description> </item> </channel> </rss>
Согласно стандарту RSS 2.0 элементов <item> может быть сколь угодно много. Я умышленно привожу только два, будучи уверенным, что этого вполне достаточно для понимания устройства данного конкретного документа. Стандартно LiveJournal генерирует 25 элементов <item>.
PHP и XML.
Поскольку одним из самых распространенных языков в Сети является PHP, я как раз и опишу, как воспользоваться этим интерпретатором для «сращивания» дневника и сайта.
Широко распространенных способов обработки XML-документов существует два — Event-based APIs и Document Object Model (DOM) APIs. В PHP стандартная поддержка XML организована с помощью Event-based API (основана на событиях). Это диктует принципы кодирования. Я не стану подробно останавливаться на вопросе обработки XML-документов. Полагаю, читателю, знакомому с вопросом, это покажется неинтересным, а для непосвященных в таинства XML, напечатаны тонны книг высоко профессиональных авторов.
Функции PHP предназначенные для обработки XML.
- xml_parser_create()
Создает экземпляр обработчика XML.
- xml_set_element_handler(parser, startElementFunction, endElementFunction)
Функция определяет обратные вызовы, которые должен осуществлять обработчик при нахождении открывающих и закрывающих тэгов.
startElementFunction определяет функцию для открывающих тэгов, endElementFunction соответственно для закрывающих. parser — это объект, возвращенный функцией xml_parser_create().
- xml_set_chracter_data_handler(parser, characterDataFunction)
Данная функция определяет обратный вызов, осуществляемый при обработке события, связанного с нахождением данных, содержащихся между XML-тэгами. Назначение параметров аналогично xml_set_element_handler().
- xml_parse(parser, data, endOfDocument)
Эта функция инициализирует анализ переданного ей XML-кода. Параметр endOfDocument должен быть равен true, если параметр data содержит конец документа, или же false, если data не включает в себя документ до конца. Это позволяет обработчику корректно обрабатывать незавершенные тэги и прочие ошибки форматирования документа, возникающие в связи с тем, что переменная data не может содержать данные длиной более 4-х килобайт.
- xml_parser_free(parser)
Функция освобождает память, занятую объектом parser.
Описание данных функций — это всего лишь краткий ликбез. Более подробную информацию по данной теме можно получить на официальном сайте PHP.
Основные принципы и алгоритмы.
Прежде чем приводить пример реализации задачи, кратко опишу основные идеи и алгоритмы.
Естественно, сначала стоит создать HTML-страницу, на которой будет отображаться форматированная информация, полученная из RSS-канала.
Сперва создадим класс RSSParser, внутри которого будет выполняться вся работа по разбору XML, и который будет выводить на печать (или же в поток вывода от сервера к браузеру) форматированные HTML-данные.
После создания класса, получим RSS-данные от сервиса LiveJournal и инициализируем обработчик XML, который будет использовать для событийной обработки (Event-based API) класс RSSParser.
Пример реализации.
Подробные разъяснения приведены после примера.
<html> <head> <title>Заголовок сайта</title> </head> <body> <?php class RSSParser { var $insideItem = false; var $tag = ""; var $title = ""; var $description = ""; var $originalLink = ""; var $dt = ""; function startElement($parser, $tagName, $attrs) { if($this->insideItem) { $this->tag = $tagName; } elseif($tagName == "ITEM") { $this->insideItem = true; } } function endElement($parser, $tagName) { if($tagName == "ITEM") { printf("<h2>%s</h2>", $this->title); printf("<h3>%s</h3>", $this->dt); printf("<p>%s</p>", $this->description); printf("<p><a href=\"%s\" target=\"_blank\">%s</a></p>", trim($this->originalLink), " Коментарии "); $this->title = ""; $this->originalLink = ""; $this->description = ""; $this->dt = ""; $this->insideItem = false; } } function characterData($parser, $data) { if($this->insideItem) { switch($this->tag) { case "TITLE": $this->title .= $data; break; case "DESCRIPTION": $this->description .= $data; break; case "LINK": $this->originalLink .= $data; break; case "PUBDATE": $this->dt .= $data; break; } } } } $xml_parser = xml_parser_create("UTF-8"); $rss_parser = new RSSParser(); xml_set_object($xml_parser, &$rss_parser); xml_set_element_handler($xml_parser, "startElement", "endElement"); xml_set_character_data_handler($xml_parser, "characterData"); $fp = fopen("http://www.livejournal.com/users/bikman/data/rss", "r") or die("Error reading RSS data!"); while($data = fread($fp, 4096)) { xml_parse($xml_parser, $data, feof($fp)) or die("Error parsing RSS data!"); } fclose($fp); xml_parser_free($xml_parser); ?> </body> </html>
Стоит учитывать, что чаще всего XML-документы хранятся в Unicode кодировке UTF-8, а в Рунете наиболее часто используемой кодировкой является Windows-1251. В этом раскладе приходится решать проблему перекодировки, или же достаточно сохранить саму страницу в UTF-8 и указать браузеру, что используется именно эта кодировка.
Для перекодировки следует использовать функции iconv() и mb_convert_encoding(). Поскольку данные функции являются дополнительными для интерпретатора PHP и содержатся в подключаемых модулях, я не могу гарантировать их работоспособность в условиях конкретного сервера и оставляю это на совести читателя, считая, что подал идеи для решения проблемы переокдировки.
Согласно стандарту RSS 2.0 дата в RSS-документах должна храниться в формате, описанном в RFC 822 (Sat, 07 Sep 2002 00:00:01 GMT). Читателя может не устроить данный формат даты, и ему может потребоваться его изменить. Могу предложить вот такой способ.
Вместо:
printf("<h3>%s</h3>", $this->dt);
Сделать так:
printf("<h3>%s</h3>", date("d.m.y - H.i.s", strtotime($this->dt));
Чтобы выбрать любой другой необходимый читателю формат, достаточно изменить строку, передаваемую в функцию date() в качестве формата. За подробными объяснениями следует обратиться к описанию функции на официальном сайте PHP.
Разъяснения.
Я умышленно привел весь код решения задачи целиком, чтобы не сдерживать опытного читателя, коему не требуются дополнительные пояснения и достаточно только просмотра кода. Ниже по тексту я рассмотрю подробно механизм анализа XML-документа, основанного на событиях, и постараюсь максимально понятно разъяснить приведенный код.
Класс RSSParser.
Лично я считаю (и не только я), что использование директивы global в скриптах является примером «ленивого» программирования. Поэтому предлагаю реализацию функций обратного вызова на события обработчика XML оформить в виде специального класса RSSParser.
class RSSParser { var $insideItem = false; var $tag = ""; var $title = ""; var $description = ""; var $originalLink = ""; var $dt = "";
Класс объявляется стандартной директивой class и содержит несколько свойств. Свойство insideItem нужно для того, чтобы отследить момент, когда обработчик находится внутри тэга <item>. В этот момент оно устанавливается в true.
Остальные свойства отвечают за информацию, которую несет в себе запись в дневнике. Это дата (dt), текст (description) и заголовок (title).
Свойство tag сохраняет в себе тот XML-тэг, внутри которого находится обработчик.
function startElement($parser, $tagName, $attrs) { if($this->insideItem) { $this->tag = $tagName; } elseif($tagName == "ITEM") { $this->insideItem = true; } }
Функция обратного вызова startElement($parser, $tagName, $attrs) начинает работать в тот момент, когда обработчик натыкается на открывающий тэг. Параметр parser — это уже известный объект, созданный функцией xml_parser_create(). Параметр tagName — название того тэга, который анализирует обработчик XML, а параметр attrs — это атрибуты текущего тэга.
Стоит отметить, что имена тэгов в PHP коде должны задаваться только(!) прописными буквами.
function endElement($parser, $tagName) { if($tagName == "ITEM") { printf("<h2>%s</h2>", $this->title); printf("<h3>%s</h3>", $this->dt); printf("<p>%s</p>", $this->description); printf("<p><a href=\"%s\" target=\"_blank\">%s</a></p>", trim($this->originalLink), "Коментарии"); $this->title = ""; $this->originalLink = ""; $this->description = ""; $this->dt = ""; $this->insideItem = false; } }
Функция endElement($parser, $tagName) отвечает за обработку события, возникающего при обнаружении закрывающего тэга. Параметр tagName содержит имя этого тэга.
Именно эта функция выводит всю информацию, которую получает класс во время обработки XML-документа. Она же обнуляет все внутренние переменные по завершении вывода.
function characterData($parser, $data) { if($this->insideItem) { switch($this->tag) { case "TITLE": $this->title .= $data; break; case "DESCRIPTION": $this->description .= $data; break; case "LINK": $this->originalLink .= $data; break; case "PUBDATE": $this->dt .= $data; break; } } }
Функция characterData($parser, $data), как было сказано выше, обрабатывает данные, которые находятся внутри XML-тэгов, присваивая свойствам класса соответствующие значения.
Создание класса требуется для того, чтобы передавать параметры из одной функции обратного вызова в другую (их передают свойства класса), не используя директиву global.
Основной код.
На мой взгляд, код, выполняющий инициализацию обработчика и получение данных из RSS-канала достаточно тривиален.
$xml_parser = xml_parser_create("UTF-8"); $rss_parser = new RSSParser(); xml_set_object($xml_parser, &$rss_parser); xml_set_element_handler($xml_parser, "startElement", "endElement"); xml_set_character_data_handler($xml_parser, "characterData"); $fp = fopen("http://www.livejournal.com/users/bikman/data/rss", "r") or die("Error reading RSS data!"); while($data = fread($fp, 4096)) { xml_parse($xml_parser, $data, feof($fp)) or die("Error parsing RSS data!"); } fclose($fp); xml_parser_free($xml_parser);
Заключение.
На этом я заканчиваю статью, посвященную обработке RSS-каналов, которые автоматически генерирует сервис LiveJournal. Думаю, что мой вариант скрипта можно легко адаптировать под конкретные нужды. Он вполне общий, чтобы не ограничивать фантазию программиста.
Примечание:
Хочу выразить благодарность Кэвину Янку (Kevin Yank) за его статью на SitePoint — «PHP and XML: Parsing RSS 1.0», которая послужила вдохновением для моих изысканий. Вы можете посмотреть результат работы кода и скачать исходный код.
Источник: http://bikman.ru/texts/techarticle/ljrssphp/ © Дмитрий Бикман 2002—2004