Сам собі RSS-рідер

Одного разу, в середині 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>http://liftoff.msfc.nasa.gov/</link>
<description> Liftoff to Space Exploration. </ description>
         <item>
          <title>Star City</title>
<link> http://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 9:39:21 GMT </ pubDate>
         </item>
         <item>
          <title>Space Exploration</title>
          <link>http://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 (лінуксойди відкривають MonoDevelop) і створюємо новий додаток. Назвемо його RssReader. Інтерфейс бедет найпростіший.

Перший і основний контрол, який потрібно розмістити на формі – це TableLayoutPanel, розтягнутий на всю форму (Dock = Fill). Це дуже зручний контрол, він являє собою таблицю, в кожній клітинці якої можна розмістити будь-якої елемент інтерфейсу (елемент може займати кілька стовпців і / або рядків таблиці одночасно). Розміри шпальт може бути фіксованими або зазначатися у відсотках від розміру таблиці. Це дуже зручно при зміні розмірів таблиці.
Призначення інших елементів, думаю, зрозуміло: TextBox – введення адреси каналу; Button – кнопка для поновлення фіда; в ListView – виводиться список записів; в WebBrowser – виводиться зміст запису.
Тепер почнемо розробку самого рідера. Почнемо ми її з низів, а саме з класу RssItem, потім створимо клас 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>. Так. Далі конструктор циклічно перебирає кожен тег що знаходиться всередині отриманої записи і, зустрічаючи потрібний тег, записувати з нього інформацію у відповідне властивість класу. Реалізовано це може бути так:


///
/ / / Конструктор для заповнення запису
///
/ / / 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;
        }
    }
  }
}

Так, до речі, так як ми працюємо з XmlNode, потрібно включити відповідну збірку в 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, то
            {
/ / Створюємо з цього тега новий об'єкт типу запис
              RssItem 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 ("Неможливо з'єднатися з вказаним джерелом.
” + 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 (RssItem 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: конструктивна критика, а також пропозиції та побажання вітаються.

 

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*