18 июл. 2013 г.

Delphi & Windows RSS Platform

Статья впервые была опубликована на сайте VR-Online под заголовком "RSS для ленивых".

Windows RSS Platform - это API, позволяющий приложениям работать с коллекцией RSS каналов, называемой также общим списком новостных каналов (Common Feed List), на которые подписан пользователь.




Windows RSS Platform позволяет вашим приложениям:

  • добавлять новые и просматривать ранее добавленные новостные каналы;
  • упорядочивать новостные каналы по каталогам;
  • отслеживать изменения и состояние загрузки содержимого каналов;
  • изменять время опроса каналов;
  • работать непосредственно с XML-кодом содержимого каналов.

Windows RSS Platform доступен в Windows XP SP2, Windows Vista и Windows Seven при условии, что в системе установлен Internet Exporer версии 7 или более свежей.

Поддерживаются форматы RSS и Atom, но их структура приводится к формату RSS 2.0.

Объектная модель

На рисунке показана объектная модель платформы


Windows RSS Platform - Объектная модель



Основным объектом платформы является FeedManager. С помощью этого объекта мы получаем доступ к общему списку новостных каналов. Структура этого списка повторяет структуру иерархической файловой системы, состоящей из каталогов и каналов. Каждый каталог может содержать в себе подкаталоги и/или каналы (как, например, на рисунке ниже).







Свойства объекта FeedManager: 
  • RootFolder - корневой каталог (объект FeedFolder);

Методы объекта FeedManager:
  •  GetFolder(<путь к каталогу>) - возвращает каталог (объект FeedFolder) по указанному пути или nil, если каталог не существует;
  • ExistsFolder(<путь к каталогу>) - проверяет наличие указанного каталога;
  • DeleteFolder(<путь к каталогу>) - удаляет указанный каталог, включая вложенные подкаталоги и каналы;
  • GetFeed(<путь к каналу>) - возвращает канал (объект Feed) по указанному пути или nil, если канал отсутствует;
  • GetFeedByUrl() - возвращает канал (объект Feed) по его адресу или nil, если канал отсутствует в списке;
  • ExistsFeed(<путь к каналу>) - проверяет наличие указанного канала;
  • IsSubscribed() - проверяет, подписан ли пользователь на указанный канал;
  • DeleteFeed(<путь к каналу>) - удаляет указанный канал из списка.

Для работы с каталогами воспользуемся объектом FeedFolder. Доступ к корневому каталогу можно получить, прочитав значение свойства FeedManager.RootFolder.

Свойства объекта FeedFolder: 
  • IsRoot - признак того, что каталог является корневым; 
  • SubFolders - коллекция подкаталогов (объект FeedsEnum); 
  • Name - имя каталога; 
  • Parent - родительский каталог (объект FeedFolder), если есть; 
  • Path - путь к каталогу; 
  • TotalItemCount - возвращает общее количество элементов всех каналов в каталоге (с учетом подкаталогов); 
  • TotalUnreadItemCount - возвращает общее количество непрочтенных элементов во всех каналах в каталоге; 
  • Feeds - коллекция каналов (объект FeedsEnum).

Методы объекта FeedFolder:

  • GetSubfolder(<имя подкаталога>) - возвращает подкаталог (объект FeedFolder) или nil, если подкаталог отсутствует; 
  • ExistsSubfolder(<имя подкаталога>) - проверяет наличие указанного подкаталога; 
  • CreateSubfolder(<имя подкаталога>) - создает подкаталог с указанным именем и возвращает указатель на него (объект FeedFolder); 
  • Delete - удаляет каталог, включая все подкаталоги и каналы; 
  • Move(<путь к каталогу>) - устанавливает новый путь к каталогу (перемещает каталог); 
  • Rename(<имя каталога>) - переименовывает каталог; 
  • CreateFeed(<имя канала>, ) - добавляет в каталог новый канал.

Чтобы получить доступ к каналам, просто прочтите в цикле элементы коллекции каналов FeedFolder.Feeds. Каждый канал описывается объектом Feed.

Свойства объекта Feed:

  • Copyright - сведения об авторских правах; 
  • Description - описание канала; 
  • Image - URL логотипа канала; 
  • Language - код языка канала; 
  • LastBuildDate - дата последнего изменения элементов канала; 
  • Link - ссылка на домашнюю страницу канала; 
  • PubDate - дата опубликования канала; 
  • Title - заголовок канала; 
  • Ttl - время жизни канала (количество минут, в течение которого XML-код канала будет храниться в кэше в неизменном виде); 
  • ItemCount - количество элементов канала; 
  • Items - коллекция элементов канала (объект FeedsEnum); 
  • LastWriteTime - дата и время создания или последнего изменения канала; 
  • LocalID - идентификатор канала;
  • Name - имя канала; 
  • Parent - каталог, в котором размещен канал (объект FeedFolder); 
  • Path - путь к каналу; 
  • UnreadItemCount - количество непрочтенных элементов канала;
  • Url - URL канала.

Методы объекта Feed:
  • MarkAllItemsRead - помечает все элементы канала как прочтенные; 
  • Xml(<максимальное количество элементов канала, которые необходимо вернуть>, <свойство, по которому необходимо отсортировать элементы>, <порядок сортировки>, <флаг фильтра элементов>, <дополнительные флаги>) - возвращает строку, содержащую XML-код элементов канала.

Параметры последнего метода стоит разобрать немного подробнее.

<Свойство, по которому необходимо отсортировать элементы> может принимать следующие значения:

  • FXSP_NONE - не сортировать элементы; 
  • FXSP_PUBDATE - сортировать по дате публикации; 
  • FXSP_DOWNLOADTIME - сортировать по дате получения (скачивания).

Значения параметра <Порядок сортировки> могут быть:

  • FXSO_NONE - не сортировать элементы (обязательно указывается, если вы указали параметр FXSP_NONE); 
  • FXSO_ASCENDING - сортировать по возрастанию; 
  • FXSO_DESCENDING - сортировать по убыванию.

<Флаг фильтра>:

  • FXFF_ALL - вернуть все элементы канала; 
  • FXFF_UNREAD - вернуть только непрочтенные элементы канала; 
  • FXFF_READ - вернуть только прочтенные элементы канала.

И, наконец, <Дополнительные флаги> могут принимать одно из значений:

  • FXIF_NONE - вернуть только стандартную разметку; 
  • FXIF_CF_EXTENSIONS - включить в разметку нестандартные тэги (если они присутствуют).

А как же элементы канала или сами новости? Они описываются объектом FeedItem. Чтобы прочитать содержание новостей, прочитайте в цикле свойства каждого элемента коллекции Feed.Items.

Свойства объекта FeedItem:

  • Author - e-mail автора новости; 
  • Comments - URL страницы комментариев к новости; 
  • Description - текст новости; 
  • GUID - GUID новости; 
  • Link - URL страницы с размещенной новостью; 
  • Modified - дата и время последнего изменения новости; 
  • PubDate - дата и время опубликования новости; 
  • Title - новостной заголовок; 
  • Enclosure - медиа-объект, связанный с новостью (описывается объектом FeedEnclosure); 
  • IsRead - признак того, что новость уже помечена как прочитанная; 
  • LocalID - идентификатор новости в общем списке новостных каналов; 
  • Parent - новостной канал (объект Feed).

Объект FeedItem имеет только один метод - Xml, и, как вы уже догадались, он возвращает строку с фрагментом XML-кода новости. Метод имеет единственный параметр, аналогичный параметру <Дополнительные флаги> в методе Feed.Xml.

Осталось только разобраться, каким образом мы можем получить новости. Для этой цели мы можем воспользоваться методами:

  1. FeedManager.AsyncSyncAll - принудительно обновляет содержимое всех каналов в общем списке; 
  2. Feed.Download - загружает содержимое канала с сервера и объединяет полученые элементы канала с ранее загруженными.

Немного о недостатках...

RSS Platform имеет некоторые ограничения:
  1. поддерживаются только стандартные тэги форматов RSS и Atom;
  2. не поддерживаются DTD-схемы описания XML-документов;
  3. платформа не может работать с сайтами с ограниченным доступом (с установленным паролем и т.п.);
  4. платформа не может работать с локальными RSS/Atom-файлами (нельзя указать путь к файлу на локальной машине); 
  5. общий список новостных каналов доступен всем пользователям на локальной машине;
  6. поддерживается работа только по протоколам HTTP и HTTPS (необходимо явно указывать протокол);
  7. API доступен при условии, что на компьютере пользователя установлен Internet Explorer 7 или более свежая версия.

Переходим к практике

Итак, мы уже ознакомились с теоретической частью, давайте теперь напишем какой-нибудь рабочий пример! Пусть это будет консольная утилита, которая соберет новости с указанных нами сайтов и покажет нам их в виде HTML-документа.

Для начала нам нужно получить модуль с описанием ранее рассмотренных объектов. Для этого в IDE в меню Component выберите пункт Import Component....

В диалоге Import Component выбираем Import a Type Library и жмем на кнопку Next.


Ждем, когда в диалоге загрузится список зарегистрированных библиотек типов, а дальше ищем библиотеку Microsoft Feeds Object Library.


В списке выбираем найденную библиотеку и опять жмем на кнопку Next.

Далее в строке Unit Dir Name указываем имя модуля и путь к нему. Флажок Generate Component Wrappers можно не устанавливать.


Next.

На последней странице диалога выбираем Create Unit и нажимаем кнопку Finish.


У нас есть модуль с описанием интерфейсов, приступаем к созданию консольного приложения. Для создания HTML-документа я выбрал THMLWriter, вы можете использовать любую удобную вам библиотеку.



program feeds_test;
 
{$APPTYPE CONSOLE}
 
uses
  Windows,
  SysUtils,
  Classes,
  Feeds,
  HTMLWriterUtils,
  HTMLWriterIntf,
  uHTMLWriter;
 
procedure DoCreateFeedsPage(SubsFeeds: TStrings; const HTMLFilename: string);
var
  FeedsManager: IFeedsManager;
  Root: IFeedFolder;
  Feed: IFeed;
  Items: IFeedsEnum;
  Item: IFeedItem;
  HTMLDoc, Body: IHTMLWriter;
  feedName, feedURL: string;
  k, j: Integer;
begin
  if not Assigned(SubsFeeds) then
    Exit;
 
  // создаем экземпляр менеджера новостных лент
  FeedsManager := CoFeedsManager.Create;
 
  HTMLDoc := HTMLWriterCreateDocument(dtXHTML10Transitional);
  Body := HTMLWriterCreate('body');
  try
    try
      Body.AddAttribute('style', 'body');
 
      // получаем указатель на корневой каталог списка новостных лент
      Root := FeedsManager.RootFolder as IFeedFolder;
 
      for k := 0 to SubsFeeds.Count - 1 do
      begin
        // получаем имя новостной ленты
        feedName := SubsFeeds.Names[k];
 
        // и ее URL
        feedUrl := SubsFeeds.Values[feedName];
 
        if (feedName = EmptyStr) or (feedUrl = EmptyStr) then
          Continue;
 
        // если корневой каталог списка содержит указатель на новостную ленту
        if Root.ExistsFeed(feedName) then
          Feed := Root.GetFeed(feedName) as IFeed // получаем указатель на нее
        else
          // в противном случае в корневом каталоге создаем указатель
          // на новостную ленту (осуществляем подписку)
          Feed := Root.CreateFeed(feedName, feedUrl) as IFeed;
 
        // загружаем элементы новостной ленты с указанного URL
        Feed.Download;
 
        // если лента содержит список новостей
        if Feed.ItemCount > 0 then
        begin
          Body.AddHeading1Text(Feed.Title);
          Body.AddHeading4Text(Feed.Description);
 
          // получаем список новостей в ленте
          Items := Feed.Items as IFeedsEnum;
 
          // перебираем список новостей
          for j := 0 to Items.Count - 1 do
          begin
            // получаем указатель на новость из ленты
            Item := Items.Item(j) as IFeedItem;
 
            Body.AddAnchor(Item.Link,
             Format('<h5>[%s] %s</h5>', [FormatDateTime('dd.mm.yyyy', Item.PubDate),
                                         Item.Title]));
            Body.AddDivTextWithStyle(Item.Description, 'div.sectionbody');
          end;
        end;
 
        // удаляем новостную ленту, чтобы не оставлять следов в общем списке,
        // иначе Outlook или IE нам покажут эту ленту
        Feed.Delete;
      end;
 
      Body.CloseTag(ucoUseCRLF);
 
      // Сохраняем все в HTML документ
      HTMLDoc
        .OpenHead
          .OpenMeta
            .AddMetaNamedContent('generator', 'feeds_test')
          .CloseTag(ucoUseCRLF)
          .AddTitle('RSS Feeds List')
          .AddTag('style', ctNormal, chaCanHaveAttributes)
            .AddAttribute('type="text/css"')
            .AddText('body {margin: 1em 5% 1em 5%;}')
            .AddText('h1, h2, h3, h4, h5, h6 {color: #527bbd;font-family: sans-serif;margin-top: 1.2em;margin-bottom: 0.5em;line-height: 1.3;}')
            .AddText('h1, h2, h3 {border-bottom: 2px solid silver;}')
            .AddText('h2 {padding-top: 0.5em;}')
            .AddText('h3 {float: left;}')
            .AddText('h3 + * {clear: left;}')
            .AddText('div.sectionbody {font-family: serif;margin-left: 0;}')
            .AddText('p {margin-top: 0.5em;margin-bottom: 0.5em;}')
          .CloseTag(ucoUseCRLF)
        .CloseTag(ucoUseCRLF)
        .OpenBody
          .AddText(Body.AsHTML)
        .CloseTag
        .CloseDocument
        .SaveToFile(HTMLFilename);
 
    except
      on E: Exception do
        Writeln(E.ClassName, ': ', E.Message);
    end;
  finally
    FeedsManager := nil;
 
    Body := nil;
    HTMLDoc := nil;
  end;
end;
 
var
  Subscriptions: TStrings; // коллекция опрашиваемых каналов
  Filename: string;        // имя файла HTML-документа
  i: Integer;
 
const
  SFilename = 'feeds.html';
  SSubscriptions: array [0..3] of string = (
    'VR-Online=http://vr-online.ru/rss.xml',
    'Облако проектов=http://projectscloud.ru/rss',
    'Yandex - IT-новости=http://news.yandex.ru/computers.rss',
    'iXBT=http://www.ixbt.com/export/news.rss'
    );
 
begin
  Subscriptions := TStringList.Create;
  try
    try
      Filename := ExtractFilePath(ParamStr(0)) + SFilename;
 
      for i := Low(SSubscriptions) to High(SSubscriptions) do
        Subscriptions.Add(SSubscriptions[i]);
 
      DoCreateFeedsPage(Subscriptions, Filename);
    except
      on E: Exception do
        Writeln(E.ClassName, ': ', E.Message);
    end;
  finally
    FreeAndNil(Subscriptions);
  end;
end.


Итак, как видите, ничего сложного! В этой статье я не рассмотрел обработку событий в Windows RSS Platform и работу с медиа-объектами, но, надеюсь, те из вас, кому данный материал показался интересным, сами смогут во всем разобраться. Мне остается только привести несколько ссылок по теме:

Windows RSS Platform
Пример простого RSS-агрегатора на Delphi
Исходник примера 

Комментариев нет:

Отправить комментарий