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 UITypeEditor
s 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))
{
xmlTextReader.Read();
propertyInfo = GetType().GetProperty(xmlTextReader.Name,
System.Reflection.BindingFlags.IgnoreCase |
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance);
if(propertyInfo!=null)
{
xmlTextReader.Read();
propertyInfo.SetValue(this, xmlTextReader.Value,null);
}
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())
{
propertyInfo = GetType().GetProperty(xmlTextReader.Name,
System.Reflection.BindingFlags.IgnoreCase |
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance);
if(propertyInfo!=null)
{
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.
RssChannel myChannel = new RssChannel();
myChannel.Title = "Sample Feed";
myChannel.Copyright = "email@adress.com";
myChannel.TTL = 20;
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";
myChannel.Items.Add(myRssItem);
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.
OpmlDocument myDocument = new OpmlDocument();
myDocument.Head.Title = "Sample document";
myDocument.Head.OwnerEmail = "email@adress.com";
myDocument.Head.OwnerName = "Chris";
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;
OpmlOutline mySubOutlineItem = new OpmlOutline();
mySubOutlineItem.Text = "Sample Child Item";
myOutlineItem.Items.Add(mySubOutlineItem);
myDocument.Body.Items.Add(myOutlineItem);
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);
System.Console.WriteLine(myRssChannel.Title);
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";
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:
myRssChannel.Save(@"c:\cp.xml");
myRssChannel.Publish(new Uri("http://www.mydomain.net"),
"POST", "domain", "username", "password");
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:
Uri uri = new
Uri("http://www.codeproject.com/webservices/articlerss.aspx?cat=1");
RssChannel myChannel = new RssChannel(uri);
System.IO.MemoryStream memoryStream =
myChannel.Transform(new System.Xml.XmlTextReader("transform.xslt"));
myChannel.Transform(new System.Xml.XmlTextReader("transform.xslt"),
"myChannel.htm");
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
.
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.
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;
string[] tokens = hyperLinkUrl.Split('\\');
if(tokens.Length>1)
{
hyperLinkUrl = tokens[0];
hyperLinkText = tokens[1];
}
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();
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);
}
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
- 22.12.2003
- RSS Editor enhanced
RssChannel.Transform()
method added
- 15.12.2003
- 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...