Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

RSS 2.0 Framework

0.00/5 (No votes)
19 Jan 2013 7  
RSS 2.0 framework implements the RSS 2.0 specification in strongly typed classes. The framework enables you to create and consume valid RSS 2.0 feeds in your code in just a few minutes.

Introduction 

Namespaces

The Raccoom.Xml namespace contains interfaces and classes that deal with RSS 2.1 and OPML 1.1 data. It provides strong typed classes which meet the required specifications. These classes help you to consume or create these kind of documents in your code, no knowledge about XML or the data specifications needed.

The Raccoom.Xml.Editors namespace contains a MDI based editor for RSS and OPML documents. You can load, save and edit your documents. There is also a built-in XML and HTML preview.

Raccoom.Xml.Test contains the NUnit based test cases. Basically, the tests create data and save it to the local storage. After loading into a new instance, it verifies that both instances are equal. Another test scenario takes care of the collection's add and remove methods.

Idea

RSS and OPML documents are XML documents, basically it's all about XML. The main code will parse XML files and store the gained data somewhere. Somewhere? I guess it would be nice and straight forward if I could use data binding, IntelliSense and an intuitive class model to deal with these kind of documents. The worst you can do is implement your XML parser directly into a UI control. But at the end of the day, we would like to present our data in a nice UI. So we have to consume the XML and display the containing data in a nice way. Like always, we should separate data from UI. That's where this framework comes in...

Design

Common

  • Every element has its corresponding interface/class implementation.
  • Solid design with strong typed classes and interfaces.
  • Strong typed collections used to connect classes together.
  • Comprehensive built-in documentation.
  • Comprehensive built-in TypeConverter and UITypeEditors for tricky values like RFC 822 DateTime and ISO Culture.
  • The Parser uses reflection to set the matching attributes from XML file and class instance.
  • The Parser is built with a non cached, fast, forward-only reading XmlTextReader.

Persistent Manager

Both document types implement the persistence manager interface. So if you are familiar with the interface methods, you can work with both document types. Basically, this interface is responsible to save, publish and transform the data.

Dirty State

Every class provides a PropertyChanged event which is raised when a class property has changed its value. The PropertyChangedEventArgs object specifies the name of the property that has changed. The property names are stored in a nested fields class that provide string constants for every property. If a child element is attached to a parent element, the parent element consumes the child's PropertyChanged event, this way the top level class will receive and pass on the dirty event for all nested elements. You just have to consume the PropertyChanged event on the top level class.

XML Parser

The main work was to write the parser. Because XML files are read non cached and only forward (performance issue), it's a little bit tricky. The main purpose was to be able to read every well formatted RSS 2.0 feed, no matter which element order or which elements really exist in the XML file. The most code I found on the net parse the XML file with an expected element order, or does support just a few elements.

Every class type in the framework supports two constructors. The standard constructor has no parameters and initializes the class instance with default values. The second uses an XmlTextReader instance, whose current position/state must meet the specific class type element, to initialize the class instance. Only the top level classes RssChannel and OpmlDocument provide a different second constructor. The top level classes provide overloaded constructors to initialize a new class instance from a URI or a file stream where the parser starts.

Initialize a new instance of the RssCloud class.

public RssCloud();
public RssCloud(XmlTextReader);

The implementation depends on the element type. Both parser work modes are based on the idea to read forward and find the corresponding properties on the current class instance via reflection.

Most elements contain XmlElementAttributes:

public RssImage(XmlTextReader xmlTextReader)
{
    System.Diagnostics.Debug.Assert(!xmlTextReader.IsEmptyElement);
    System.Reflection.PropertyInfo propertyInfo = null;
    while(!(xmlTextReader.Name == "image" && xmlTextReader.NodeType ==
    XmlNodeType.EndElement))
    {
        // Continue read
        xmlTextReader.Read();
        // find related property by name
        propertyInfo = GetType().GetProperty(xmlTextReader.Name,
          System.Reflection.BindingFlags.IgnoreCase |
          System.Reflection.BindingFlags.Public |
          System.Reflection.BindingFlags.Instance);
        if(propertyInfo!=null)
        {
            // set related property with data
            xmlTextReader.Read();
            propertyInfo.SetValue(this, xmlTextReader.Value,null);
        }
        // read till end tag
        while(xmlTextReader.NodeType!=XmlNodeType.EndElement)
        {
            xmlTextReader.Read();
        }
    }
}

Otherwise they contain XmlAttributes:

public RssCloud(XmlTextReader xmlTextReader)
{
    if(!xmlTextReader.HasAttributes) return;
    System.Reflection.PropertyInfo propertyInfo = null;
    //
    while(xmlTextReader.MoveToNextAttribute())
    {
        // find related property by name
        propertyInfo = GetType().GetProperty(xmlTextReader.Name,
          System.Reflection.BindingFlags.IgnoreCase |
          System.Reflection.BindingFlags.Public |
          System.Reflection.BindingFlags.Instance);
        if(propertyInfo!=null)
        {
            // set related property with data
            propertyInfo.SetValue(this, xmlTextReader.Value,null);
    }
    }
}

RSS

RSS is a web content syndication format. Its name is an acronym for Really Simple Syndication. RSS is a dialect of XML. All RSS files must conform to the XML 1.0 specification, as published on the World Wide Web Consortium (W3C) website. At the top level, a RSS document is a RSS element, with a mandatory attribute called version, that specifies the version of RSS that the document conforms to. Subordinate to the RSS element is a single channel element, which contains information about the channel (metadata) and its contents.

// create channel
RssChannel myChannel = new RssChannel();
myChannel.Title = "Sample Feed";
myChannel.Copyright = "email@adress.com";
myChannel.TTL = 20;
// create item
RssItem myRssItem = new RssItem();
myRssItem.Title = "Sample Rss Item";
myRssItem.Link = "http://www.codeproject.com";
myRssItem.Source.Url = "http://www.codeproject.com";
myRssItem.Source.Value = "Codeproject";
// add item
myChannel.Items.Add(myRssItem);
// save feed
myChannel.Save(@"c:\samplefeed.xml");

OPML

The purpose of this format is to provide a way to exchange information between outliners and Internet services that can be browsed or controlled through an outliner. The design goal is to have a transparently simple, self-documenting, extensible and human readable format that's capable of representing a wide variety of data that's easily browsed and edited. As the format evolves, this goal will be preserved. It should be possible for a reasonably technical person to fully understand the format with a quick read of a single Web page. It's an open format, meaning that other outliner vendors and service developers are free to use the format to be compatible with Radio UserLand or for any other purpose.

// create document
OpmlDocument myDocument = new OpmlDocument();
myDocument.Head.Title = "Sample document";
myDocument.Head.OwnerEmail = "email@adress.com";
myDocument.Head.OwnerName = "Chris";
// create outline
OpmlOutline myOutlineItem = new OpmlOutline();
myOutlineItem.Text = "Sample Item";
myOutlineItem.Description = "RTFM";
myOutlineItem.XmlUrl = "http://www.codeproject.com/rss.xml";
myOutlineItem.HtmlUrl = "http://www.codeproject.com";
myOutlineItem.IsBreakpoint = false;
myOutlineItem.IsComment = false;
// create sub item
OpmlOutline mySubOutlineItem = new OpmlOutline();
mySubOutlineItem.Text = "Sample Child Item";
// add sub item to item
myOutlineItem.Items.Add(mySubOutlineItem);
// add outline to document
myDocument.Body.Items.Add(myOutlineItem);
// save opml
myDocument.Save(@"c:\sample.opml");

Using the Code

The following samples deal with the RSS classes, but for OPML, it's similar because the operations are mostly based on the IPersistenceManager interface.

Consume Content from Internet

This sample shows how to consume The Code Project "Last 10 updates (category: All Topics)" RSS feed.

Uri uri = new
  Uri("http://www.codeproject.com/webservices/articlerss.aspx?cat=1");
RssChannel myRssChannel = new RssChannel(uri);
// write the channel title to the standard output stream.
System.Console.WriteLine(myRssChannel.Title);
// write each item's title to the standard output stream.
foreach(Raccoom.Xml.RssItem item in myRssChannel.Items)
{
    System.Console.WriteLine(item.Title);
}

This sample shows how to create and save RSS feeds to the local storage.

Raccoom.Xml.RssChannel myRssChannel = new Raccoom.Xml.RssChannel();
myRssChannel.Title = "Sample rss feed";
myRssChannel.Copyright = "(c) 2003 by Christoph Richner";
// add item to channel
Raccoom.Xml.RssItem item = new Raccoom.Xml.RssItem();
item.Title = "Raccoom RSS 2.0 Framework announced";
item.Link = "http://jerrymaguire.sytes.net";
myRssChannel.Items.Add(item);

Save Feeds

This sample shows different ways to store the feed:

// save feed to local storage
myRssChannel.Save(@"c:\cp.xml");
// upload feed to web server
myRssChannel.Publish(new Uri("http://www.mydomain.net"),
            "POST", "domain", "username", "password");
// create stream
System.IO.MemoryStream stream = new System.IO.MemoryStream();
myRssChannel.Write(stream);
stream.Close();

Transform Feeds

This sample shows how to consume and transform (XSLT/CSS) The Code Project feed:

// consume RSS feed
Uri uri = new
  Uri("http://www.codeproject.com/webservices/articlerss.aspx?cat=1");
RssChannel myChannel = new RssChannel(uri);
// transform to stream
System.IO.MemoryStream memoryStream =
  myChannel.Transform(new System.Xml.XmlTextReader("transform.xslt"));
// transform to HTML output file
myChannel.Transform(new System.Xml.XmlTextReader("transform.xslt"),
                                                      "myChannel.htm");
// transform to HTML and XML output file
myChannel.Transform(new System.Xml.XmlTextReader("transform.xslt"),
                                        "channel.xml", "channel.htm");

Editors

Introduction

There is an MDI parent form which can hold any number of child forms. The child forms represent the document types, RSS and OPML. The editor provides a most recently used menu and common dialogs for save, open and publish operations. The built-in preview windows are docked using Magic Library and my Docking Manager Extender component which are provided in release mode assemblies.

Type Converters

If you like to create well formatted documents which pass successfully the specification validators, you have to bear in mind that the input must also be valid. Here I present some type converters to edit kind of tricky values like RFC DateTime and ISO culture.

IsoCodeUITypeConverter

Inherits from System.ComponentModel.TypeConverter and lets you choose your ISO Code from a list filled with System.Globalization.CultureTypes.AllCultures.

RfcDateUITypeEditor

Inherits from System.Drawing.Design.UITypeEditor and makes it easy to select a date. It helps to format the DateTime to its RFC 822 value. DateTime natively supports RFC 822 DateTime format.

To format a DateTime instance as RFC 822 in your code, just type:

string rfcDateFormat = myDateTime.ToString("r");

SkipDayUITypeConverter

Inherits from System.Drawing.Design.UITypeEditor and makes it easy to edit values that are based on enumeration with FlagAttribute.

Drag'n'Drop Hyperlink from Browser to .NET Control

To allow drag'n'drop operations, you have to set AllowDrop to true, otherwise the DragXX messages are not sent. After enabling dropping, you have to consume DragEnter and DragDrop events.

We only allow dragging if the dragged data is UniformResourceLocator.

/// <summary>
/// Allow dragging of UniformResourceLocator (Hyperlinks)
/// </summary>
private void OnDragEnter(object sender, System.Windows.Forms.DragEventArgs e)
{
    if(e.Data.GetData("UniformResourceLocator",true)!=null)
    {
        e.Effect = DragDropEffects.Link;
    }
    else
    {
        e.Effect = DragDropEffects.None;
    }
}

Now that the user has released the left mouse button, we can get the UniformResourceLocator. It is important that you use a delegate to work on with the dropped hyperlink, otherwise the browser freezes until the event handler leaves, e.g. if you open a dialog that waits for user input.

/// <summary>
/// If valid UniformResourceLocator was dropped, get it
/// </summary>
private void OnDragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
    string hyperLinkUrl = null;
    string hyperLinkText = null,
    //
    try
    {
        hyperLinkUrl = e.Data.GetData(typeof(string)) as string;
        // some browser deliver url and text
        // in UniformResourceLocator (Firebird)
        string[] tokens = hyperLinkUrl.Split('\\');
        if(tokens.Length>1)
        {
            hyperLinkUrl = tokens[0];
            hyperLinkText = tokens[1];
        }
        // we have to read FILEGROUPDESCRIPTOR to get the text (IE)
        else
        {
            System.IO.Stream ioStream=
            (System.IO.Stream)e.Data.GetData("FileGroupDescriptor");
            byte[] contents = new Byte[512];
            ioStream.Read(contents,0,512);
            ioStream.Close();
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            //The magic number 76 is the size of that part of the
            //FILEGROUPDESCRIPTOR structure before
            // the filename starts - cribbed
            //from another usenet post.
            for (int i=76; contents[i] != 0; i++)
            {
                sb.Append((char)contents[i]);
            }
            if (!sb.ToString(sb.Length-4,4).ToLower().Equals(".url"))
            {
                throw new Exception("filename does not end in '.url'");
            }
            hyperLinkText = sb.ToString(0,sb.Length-4);
        }
        // do what ever you wanna do with the hyperlink
        this.BeginInvoke(.....)
    }
    catch (System.Exception ex)
    {
        System.Diagnostics.Trace.WriteLine(ex.Message);
    }
}

Conclusions

Feel free to use this framework in your application. Any feedback or criticism is appreciated.

Links

Blogs 

History 

  • 01.02.2004
    • Major update
  • 22.12.2003
    • RSS Editor enhanced
    • RssChannel.Transform() method added
  • 15.12.2003
    • Final release
  • Version 3.0.0.0 
    • Stable RFC Datetime parser 
    • IPersistentManager.Save method now provides an overloaded method taking an encoding parameter
    • Info: Visual Studio 2008 Project File Format

Have fun...

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here