Как стать автором
Обновить

Сам себе RSS ридер

Время на прочтение 12 мин
Количество просмотров 14K
Однажды, в середине 5-го курса, попросила меня одногрупница помочь ей с лабами по
C#, так как его она только изучала. Узнав задание – «написать RSS ридер» — и оценив
ситуацию – конец семестра – я решил ей помочь, так как RSS ридер нужен был самому.


Немного теории



RSS – это формат передачи веб-контента. Название технологии — акроним «Really Simple
Syndication», то есть, «по-настоящему простая передача информации».
RSS — это диалект XML. Все файлы RSS обязаны соответствовать спецификации XML1.0, опубликованной на веб-сайте консорциума WWW (W3C).
На высшем уровне документ RSS представляет собой элемент <rss> с обязательным атрибутом
version, указывающим версию
RSS(кстати, я свое приложение делал опираясь на RSS 2.0). Дочерний элемент <rss>
— один элемент <channel>, который включает информацию о канале (метаданные)
и его содержимое.
Пример файла RSS 2.0 выглядит так: 
<?xml version=«1.0»?>
<
rss version=«2.0»>

    <channel>

       <title>Liftoff News</title>

       <link>liftoff.msfc.nasa.gov</link>

       <description>Liftoff to Space Exploration.</description>

  
      <item>

          <title>Star City</title>

          <link>liftoff.msfc.nasa.gov/news/2003/news-starcity.asp</link>

          <description>
             How do Americans get ready to work with Russians aboard the
             International Space Station? They take a crash course in culture, language
             and protocol at Russia's Star City.
          </description>

          <pubDate>Tue, 03 Jun 2003 09:39:21 GMT</pubDate>
        
</
item>

  
      <item>

          <title>Space Exploration</title>

          <link>liftoff.msfc.nasa.gov</link>

          <description>
             Sky watchers in Europe, Asia, and parts of Alaska and Canada
             will experience a partial eclipse of the Sun on Saturday, May 31st.
          </description>

          <pubDate>Fri, 30 May 2003 11:06:42 GMT</pubDate>
 

       </item>

    </channel>
</
rss>



Достаточно простой файл. Что же он нам предоставляет? Во-первых, канал (Channel,
он же Feed) и различную информацию о нем — заголовок, ссылку на официальный сайт,
описание канала и т.д.). Во-вторых, список статей/новостей/записей (item) называйте
как хотите, а так же свойства этих записей: заголовок, ссылка, описание, дата публикации.
На самом деле свойств, как канала, так и записи может быть гораздо больше. Все они
приведены в спецификации rss 2.0. Ее перевод, сделанный Алексеем Бешеновым, можно найти здесь. Последнюю версию спецификации можно
найти здесь.
Для работы с информацией предоставляемой rss нам понадобится 3 класса: класс для
хранения канала, назовем его RssFeed;
класс для хранения списка записей – RssItems; класс для хранения
записи – RssItem.

Создание формы


Открываем Microsoft Visual Studio 2005 (<span
lang=«ru»>линуксойды открывают MonoDevelop) и создаем новое приложение. Назовем его
RssReader. Интерфейс бедет простейший:

Первый и основной контрол, который нужно разместить на форме – это TableLayoutPanel,
растянутый на всю форму (Dock=Fill). Это очень удобный контрол, он представляет
собой таблицу, в каждой ячейке которой можно разместить какой либо элемент интерфейса
(элемент может занимать несколько столбцов и/или строк таблицы одновременно). Размеры
столбцов могут быть фиксированными либо указываться в процентах от размера таблицы.
Это очень удобно при изменении размеров таблицы.
Назначение остальных элементов, думаю, понятно: TextBox — ввод адреса канала; Button – кнопка для обновления фида; в ListView
– выводится список записей; в WebBrowser – выводится содержание
записи.
Теперь начнем разработку самого ридера. Начнем мы ее с низов, а именно с класса<span
lang=«en-us»> RssItem, затем создадим класс<span
lang=«en-us»> RssItems,
последним будет написан
RssFeed
.

RssItem


Этот класс предоставляет нам информацию о записи. Согласно спецификации rss 2.0
«все элементы <item> являются необязательными, однако, по крайней мере,
<title> или <description> должен существовать».
Добавим к проекту новый класс, назовем его RssItem. В итоге получаем следующее:
using
System;
using System.Collections.Generic;
using
System.Text;

namespace RssReader

{
   class RssItem
   {
   }
}

Наш класс будет хранить минимальную информацию о записи: title (заголовок), link
(ссылка на полный текст) и description (краткий обзор сообщения). Добавим к
классу 3 публичных поля, для хранения этой информации:
class RssItem
 {
   public String title; // заголовок записи
   public
String link; // ссылка на
полный текст
   public String description;// описание записи
 }


Теперь добавим конструктор, который будет заполнять эти свойства. На вход конструктору
должна передаваться конкретная запись из rss. Так как rss это всего лишь диалект
XML, то передаем мы на вход конструктору ветвь <item>. Да. Далее конструктор
циклически перебирает каждый тег находящийся внутри полученной записи и, встречая
нужный тег, записывать из него информацию в соответствующее свойство класса. Реализовано
это может быть так:
///

/// Конструктор для заполнения записи

///

/// <param
name=«ItemTag»>xml-тэг для чтения

public

RssItem(XmlNode ItemTag)

{

  //
просматриваем все теги записи


  foreach (XmlNode xmlTag in
ItemTag.ChildNodes)

  {

    // проверяем имя тега, если
соответствует одному из укаазных,


    // то в
соответствующее свойство объекта записывается содержимое тега


    switch (xmlTag.Name)

    {  
     case «title»:

        {

          this.Title = xmlTag.InnerText;

          break;
        }  
     case
«description»:

        {  
          this.Description = xmlTag.InnerText;

          break;

        }

      case
«link»:

        {

          this.Link
= xmlTag.InnerText;

          break;

        }

    }

  }

}

Да, кстати, так как мы работаем с <font color="#2B91AF"
face=«Courier New» size=«2»>XmlNode, нужно включить
соответствующую сборку в <font color="#0000ff" face=«Courier New»
size=«2»>using секцию:
using
System.Xml;


RssItems


Этот класс у нас представляет собой список всех записей фида. Его будем реализовывать
посредствам генериков, а именно моего любимого генерик класса
List.
А нравится мне этот генерик тем, что предоставляет очень удобные методы работы с
массивами данных.
Добавим к проекту новый класс, и назовем его RssItems. Получаем следующее:
using
System;
using
System.Collections.Generic;

using
System.Text;
namespace

RssReader

{

  class RssItems

  {

  }

}

Далее наследуем RssItems от
List, вместо T указав
тот тип, объекты которого будут храниться в списке. Заодно переопределим метод Contains,
для определения существования записи в списке по ее заголовку.

///

/// Проверка существования указаного
элемента в списке

///


/// Объект для сравнения


/// true, если объект в списке есть, иначе
false


new
public

bool Contains(RssItem Item)

{

  foreach (RssItem itemForCheck in this)

  {

  //
Сравниваем заголовки записей


    if
(Item.Title == itemForCheck.Title)

    {

      // нашли
совпадение. возвращаем истину


      return
true;

    }

  }

  //
совпадений не найдено. возвращаем лож


  return
false;

}

Так же было бы не плохо иметь возможность поулчить интересующую нас запись из списка
используя ее заголовок. Для этого напишем еще один метод, подобный методу Contains:
///

/// Получить запись из списка, по ее
заголовку

///


/// Заголовок записи


/// Если запиь существует, то она возвнащается,
иначе возвращается null

public
RssItem GetItem(String Title)

{   foreach (RssItem itemForCheck in this)

  {

    //
Сравниваем заголовок записей с запросом


    if
(Item.Title == Title)

    {

      // нашли совпадение.
возвращаем найденую запись


      return
itemForCheck;

    }

  }

  // совпадений не найдено.

  return null;

}

RssFeed


Вот и добрались до основного класса нашего ридера.
Этот класс будет хранить информацию о канале. Согласно спецификации rss 2.0 к обязательным
элементам канала относятся: <title> название
канала, по которому люди будут ссылаться на сервис; <link> URL веб-сайта, связанного с каналом; <description> фраза или предложение для описания канала.
Снова добавляем новый класс к проекту и называем его RssFeed.
using
System;
using
System.Collections.Generic;

using
System.Text;


namespace RssReader


    class RssFeed

    {

    }
}

Так как все вышеперечисленные свойсвта канала обязательны, то нам необходимо добавить
их и в наш класс. Так же мы добавляем свойство типа RssItems, для хранения списка записей канала:
class RssFeed

{
  public String Title;
// заголовок канала

  public
String Description; //
описание канаала


  public
String Link; // ссылка на
связаный с каналом веб-сайт


  public
RssItems Items; // список записей канала

}

Все что осталось сделать теперь, это написать конструктор класса, который будет
получать, в качестве параметра, ссылку на rss канал, и, если rss там действительно
существует, заполнять свойства создаваемого объекта данными из rss (надеюсь по коду
вопросов не возникнет, я постарался его подробно прокомментировать):
///

/// Конструктор для заполнения данных
канала

///

/// Адрес канала

public

RssFeed(String Url)

{

  //
Инициализируем список записей


  Items = new
RssItems();

  // Создаем ридер для чтения Rss из
указаного адреса


  XmlTextReader
xmlTextReader = New XmlTextReader(Url);

  // создаем новый xml документ, для записи в него оплученого
RSS


  XmlDocument xmlDoc = New
XmlDocument();

  try

  {

    // загружаем RSS в документ с помощью ридера

    xmlDoc.Load(xmlTextReader);

    // закрываем ридер за
ненадобностью


    xmlTextReader.Close();

    //
так как вся информация об RSS-фиде записана между тегов ,
    // грузим получаем эту ветку.

    XmlNode channelXmlNode = xmlDoc.GetElementsByTagName(«channel»)[0];

    // если
ветка существует, то начинаем заоплнять свойства объекта 
    // данными из ветки

    if
(channelXmlNode != null)

    {

      // перебираем всех потомков тега

      foreach (XmlNode
channelNode in channelXmlNode.ChildNodes)

      {

        // если имя тега-потомка с интересующим нас, то
записываем его данные


        // в определенное
совйство объекта


        switch
(channelNode.Name)

        {

          case
«title»:

            {

              Title =
channelNode.InnerText;

              break;

            }

          case «description»:

            {

              Description =
channelNode.InnerText;

              break;

            }

          case «link»:

            {

              Link = channelNode.InnerText;

              break;

            }

          case «item»: // если имя проверяемого тега равно item, то

            {

              // создаем из этого тега новый
объект типа запись


              Rss<span
lang=«en-us»>Item
channelItem = new
RssItem (channelNode);

              // и добавляем его к
списку записей канала


              Items.Add(channelItem);

              break;

            }

        }

      }

    }

    else
//
если в полученом файле тега не найдено, то выбрасываем исключение

    {

      throw New Exception(«Ошибка в XML. Описание канала не найдено!»);

    }

  }

  // если url канала указане не верно то выбрасываем
исключение о недоступности источника
  
  catch

(System.Net.WebException ex)

  {

    if (ex.Status
== System.Net.WebExceptionStatus.NameResolutionFailure)

      throw new Exception(«Невозможно соединиться с указаным источником.\r\n» +
Url);

    else throw
ex;

  }

  // если в качестве адреса RSS был указан
локальный пути, который еще и не существует,


  //
то выбрасываем соответствующее исключение


  catch
(System.IO.FileNotFoundException)

  {

    throw New
Exception(«Файл „ + Url +
не найден!»
);

  }

  // ну и на последок, ловим все
остальные исключения, и передаем их дальше, как есть


  catch (Exception ex)

  {

    throw
ex;

  }

  finally

  {

    //
закрываем ридер


    xmlTextReader.Close();

  }

}

Финальная стадия


Теперь, все что осталось сделать, это написать код обработчиков событий нажатия
кнопки «обновить» и выбора элемента в ListView, а так же добавить глобальную переменную CurrentFeed, в которой будет храниться загруженный канал:

//
Глобальная переменная хранящая данные канала


RssFeed
CurrentFeed;


// оброботка нажатия на кнопку «Обновить»

private
void btRefresh_Click(object sender, EventArgs
e)

{

  // Проверяем задан ли адрес

  if (!String.IsNullOrEmpty(tbUrl.Text))

  {

    // Очищаем ListView перед добавлением новых данных

    lvNews.Clear();

    // Инициализируем канал

    CurrentFeed = new RssFeed(tbUrl.Text);

    foreach (Rss<span
lang=«en-us»>Item
feedItem in
CurrentFeed.Items)

    {

      // создаем элемент для вывода в ListView

      ListViewItem listViewItem = new ListViewItem(feedItem.Title);

      // задаем его имя

      listViewItem.Name = feedItem.Title;

      // заносим его в ListView

      lvNews.Items.Add(listViewItem);

    }

  }

}
 
// Оброботка смены выбора элемента в ListView

private
void lvNews_SelectedIndexChanged(object sender, EventArgs
e)

{

  // получаем связаную с выбраным ListViewItem новость

  if (lvNews.SelectedItems.Count > 0
&& // проверяем что чтото действительно выбрано

      CurrentFeed != null
&& // проверяем, что канал инициализирован

      CurrentFeed.Items.Count > 0
// проверяем существование записей в канале

      )

  {

    // выводим полный текст выбраной записи

    wbDescription.DocumentText =
CurrentFeed.Items.GetItem(lvNews.SelectedItems[0].Text).Description;
  }
}

Заключение


В итоге мы получили простой RSS-ридер, который может читать фиды стандарта 2.0.
В следующей статье я постараюсь рассказать, как можно сделать наши классы более
универсальными, а так же как можно организовать хранение истории посещенных
лент.
Скачать исходники написанного ридера можно здесь.

PS: конструктивная критика, а так же предложения и пожелания приветствуются.

Рамиль Алиякберов a.k.a. R@Me0!

* Все исходные коды были подсвечены с помощью Source Code Highlighter.
Теги:
Хабы:
+34
Комментарии 44
Комментарии Комментарии 44

Публикации

Истории

Работа

.NET разработчик
65 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн