Закрыть Авторизация

     

Обработка 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

Подпишись!
Детальное описание Периферийные устройства тут.