Build an ASP.NET Atom to RSS Feed Converter

by Peter A. Bromberg, Ph.D.

Peter Bromberg
"There art two cardinal sins from which all others spring: Impatience and Laziness." -- Kafka

I don't know about you, but I'm usually at my best when I can take either code I've already written, or someone else's code, put together different pieces that I need, make any improvements or refactoring, and have what I need for an end product. I've seen people slave over a daunting new challenge and concept that they've set for themselves, such as



writing a custom DAL (Data Access Layer) and almost completely ignore some of the best practices code out there that others are literally giving away. To me, this is simply being ignorant or stubborn. That's not to say I don't write completely original new code; I certainly do. But my philosophy is first, to try not to reinvent the wheel, unless it is obvious that's the best approach.

Developers who become extremely proficient at using, combining, refactoring and enhancing other's code are far more valuable to their employers than those who stubbornly insist on reinventing the wheel in the name of originality, in my opinion. They are more productive and they are able to engineer and deliver working codebases faster than those "other guys". In addition, there is a huge repository of quality code that developers have put out on the Web in articles, source code repositories such as gotdotnet workspaces and code-samples, or Sourceforge.net, and in their articles or .NET - related web sites. I've certainly contributed, both in articles here and on MSDN Academic Alliance site, and at gotdotnet.com.

This afternoon while adding a partner site link to dotnetslackers.com, I heard from Sonu Kapoor, an MVP who runs the site, that they might have difficulty parsing my Atom feed from my UnBlog which they had listed. And he's right. Most newsreaders can handle any version of syndication xml, but Atom has some complicated namespace stuff in it that stops a lot of developers who don't generally spend their days doing XML. (By the way, we've relented, so if you run a quality, .NET-related site and would like to exchange reciprocal "partners" links, let us know.)

So being a good developer who believes in "not reinventing the wheel", I set out to go find some Atom-to-RSS C# code. Guess what? There's hardly anything out there! But not anymore, dear reader- because now you are going to be able to get the code right here, since I had to stop and "roll my own"!

I already had some code I wrote based on a previous article about Google's newsgroup feed options that will parse an Atom feed into a DataTable, correctly handling the namespaces, so all I really needed to do was get rid of the DataTable construction and instead, using an XmlTextWriter, create the Xml document for the RSS 2.0 feed inline at the same time that I was getting the elements from the Atom feed.

So now that you know the history of the world, let's take a look at the code for the class I've cobbled together:

namespace AtomToRSS

{

    using System;

    using System.IO;

    using System.Text;

    using System.Xml;

 

    public class Converter

    {

        private Converter()

        {

        }

 

        public static XmlDocument AtomToRss20(XmlDocument atomDoc)

        {

            XmlDocument xmlDoc = atomDoc;

            XmlNamespaceManager mgr = new XmlNamespaceManager(xmlDoc.NameTable);

            mgr.AddNamespace("atom", "http://purl.org/atom/ns#");

            const string rssVersion = "2.0";

            const string rssLanguage = "en-US";

            string rssGenerator = "PAB.AtomFeedConverter";

            MemoryStream ms = new MemoryStream();

            XmlTextWriter xt = new XmlTextWriter(ms, null);

            xt.Formatting = Formatting.Indented;

            string feedTitle = xmlDoc.SelectSingleNode("//atom:title", mgr).InnerText;

            string feedLink = xmlDoc.SelectNodes("//atom:link/@href", mgr)[2].InnerText;

            string rssDescription = xmlDoc.SelectSingleNode("//atom:tagline", mgr).InnerText;

            xt.WriteStartElement("rss");

            xt.WriteAttributeString("version", rssVersion);

            xt.WriteStartElement("channel");

            xt.WriteElementString("title", feedTitle);

            xt.WriteElementString("link", feedLink);

            xt.WriteElementString("description", rssDescription);

            xt.WriteElementString("language", rssLanguage);

            xt.WriteElementString("generator", rssGenerator);

            XmlNodeList items = xmlDoc.SelectNodes("//atom:entry", mgr);

            if (items == null)

                throw new FormatException("Atom feed is not in expected format. ");

            else

            {

                string title = String.Empty;

                string link = String.Empty;

                string description = String.Empty;

                string author = String.Empty;

                string pubDate = String.Empty;

                for (int i = 0; i < items.Count; i++)

                {

                    XmlNode nodTitle = items[i];

                    title = nodTitle.SelectSingleNode("atom:title", mgr).InnerText;

                  link = items[i].SelectSingleNode("atom:link[@rel='alternate']", mgr).Attributes["href"].InnerText;

                    description = items[i].SelectSingleNode("atom:content", mgr).InnerText;

                    author = items[i].SelectSingleNode("//atom:name", mgr).InnerText;

                    pubDate = items[i].SelectSingleNode("atom:issued", mgr).InnerText;

                    xt.WriteStartElement("item");

                    xt.WriteElementString("title", title);

                    xt.WriteElementString("link", link);

                    xt.WriteElementString("pubDate", Convert.ToDateTime(pubDate).ToUniversalTime().ToString(@"ddd, dd MMM yyyy HH:mm:ss G\MT"));

                    xt.WriteElementString("author", author);

                    xt.WriteElementString("description", description);

                    xt.WriteEndElement();

                }

                xt.WriteEndElement();

                xt.Flush();

                xt.Close();

            }

            XmlDocument retDoc = new XmlDocument();

            string outStr = Encoding.UTF8.GetString(ms.ToArray());

            retDoc.LoadXml(outStr);

            ms.Close();           

            return retDoc;

        }

    } // end class Converter

}

So, you can see from examining the above that essentially I'm doing an "in-line" translation. Yes, of course I could have used an XslTransform, but in this case I chose not to do so. Handling it this way I think provides a little more flexiibility - for example, what if I wanted to apply some sort of "cleanup" code to the description element? With this approach, I can do so easily. I want to mention also that this particular code is specifically designed to parse Blogger Atom feeds. For example, the link element that has a valid link to the Permalink page of a Bloggger item is [1] out of several links in the "entry" aggregate. So i use an XPath expression with "[rel='alternate'] to snag the correct link element. Therefore, this may require some tweaking to handle other Atom feeds because of the funky way that the Atom spec is written.

Now here is how this would be used in a web page that expects the url to an Atom feed on the querystring:

private void Page_Load(object sender, System.EventArgs e)

        {

            if(Request.QueryString["atomfeeduri"]==null)

            {

                Response.Write("usage: thisurl.aspx?atomfeeduri=&lt;uri to atom feed to convert&gt;");

                Response.End();

            }

            string uriToLoad = (string) Request.QueryString["atomfeeduri"];

            XmlDocument doc = new XmlDocument();

            doc.Load(uriToLoad);

            XmlDocument doc2 = AtomToRSS.Converter.AtomToRss20(doc);

            Response.ContentType="text/xml";

            Response.Write(doc2.OuterXml );

        }

Note that this querystring arrangement is not designed for complex multi-element querystrings such as are composed from requests for Google news feeds and similar custom feeds. However if you wanted to provide for these, some custom parsing of the Request.QueryString NameValueCollection could certainly do the trick. Here is some sample code for the web page's Page_Load handler that would do this:

// Note the following code is intended to "parse" custom querystrings such as:
// http://news.google.com/news?ned=us&topic=h&output=atom
NameValueCollection nvCol=Request.QueryString;
string adjustedQuery=String.Empty;
foreach(string key in nvCol.AllKeys)
adjustedQuery+=key+"="+nvCol[key]+"&";
adjustedQuery=adjustedQuery.Replace("atomfeeduri=","");
adjustedQuery=adjustedQuery.Substring(0,adjustedQuery.Length-1) ;
string uriToLoad = adjustedQuery; // was:(string) Request.QueryString["atomfeeduri"];
XmlDocument doc = new XmlDocument();
doc.Load(uriToLoad);

The above snippet is included in the download below, you only need to uncomment it and comment the orignal uriToLoad= line.

Easy! And here is a live link to this page, which converts my own "petesbloggerama.blogspot.com" Atom feed to RSS!

Have fun, and practice safe Blogging!

You can download a Visual Studio.NET 2003 Solution with all the code here.

 


Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.
Article Discussion: