Daniel Cazzulino's Blog : How to upgrade Atom 0.3 feeds on the fly with a custom XmlReader for use with WCF Syndication APIs

How to upgrade Atom 0.3 feeds on the fly with a custom XmlReader for use with WCF Syndication APIs

Even now that Atom 1.0 has been approved and official for some time, there’s a feed every now and then that still uses Atom 0.3 (i.e. Google News! http://news.google.com/?ned=us&topic=w&output=atom).

The .NET APIs for feeds, System.ServiceModel.Syndication, handles RSS 2.0 and Atom 1.0 fine, and you just have to make a single call to load a feed regardless of its format:

SyndicationFeed.Load(xmlReader);

I searched the web looking for an easy answer, but I couldn’t find any that would let me seamlessly plug into the .NET APIs to do the feed format upgrade on the fly. So I put one together myself :)

I found Aaron Cope had created XSLT stylesheets to transform Atom 0.3 to RSS 1.0 and 2.0, so I could use that. That used the XSLT Standard Library too. So, I wanted something that would be blazingly fast and transparent to users of WCF: I wanted a compiled XSLT and a custom XmlReader layered on top!

So, after a couple very minor fixes, I got Aaron’s XSLT to compile with the following command:

xsltc /out:Atom03ToRss20.dll /class:NetFx.ServiceModel.Syndication.Atom03ToRss20XslTransform atom03-to-rss20.xsl

That gave me an assembly I could reference, containing the specified type, which can be used as follows:

var atom03ToRss20 = new XslCompiledTransform();
atom03ToRss20.Load(typeof(Atom03ToRss20XslTransform));

// transform the document with the compiled XSLT into an output writer
atom03ToRss20.Transform(someXPathDocument, someXmlWriter);

That’s the fastest way of doing XSLT transformations in .NET, mind you.

Now for the custom XmlReader, I used a base class we created for the Mvp.Xml project, the XmlWrappingReader, which makes creating the derived LegacyFeedXmlReader a breeze:

#if NetFx    
    public class LegacyFeedXmlReader : XmlWrappingReader
#else
    internal class LegacyFeedXmlReader : XmlWrappingReader
#endif
    {
        static XslCompiledTransform atom03ToRss20;
        MemoryStream mem;

        static LegacyFeedXmlReader()
        {
            atom03ToRss20 = new XslCompiledTransform();
            atom03ToRss20.Load(typeof(Atom03ToRss20XslTransform));
        }

        public LegacyFeedXmlReader(XmlReader baseReader)
            : base(baseReader)
        {
            if (baseReader.ReadState == ReadState.Initial)
                baseReader.MoveToContent();
            
            UpgradeReader();
        }

        private void UpgradeReader()
        {
            if (BaseReader.NamespaceURI == "http://purl.org/atom/ns#")
            {
                var doc = new XPathDocument(BaseReader);
                mem = new MemoryStream();
                using (var writer = XmlWriter.Create(mem))
                {
                    atom03ToRss20.Transform(doc, writer);
                }

                mem.Position = 0;
                BaseReader = XmlReader.Create(mem, BaseReader.Settings);
            }
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (mem != null)
            {
                mem.Dispose();
            }
        }
    }

Note how the reader will only run the transformation and expose the transformed content if the root element namespace matches that of Atom 0.3 :). So, for Atom 1.0 and RSS 2.0 feeds it will do nothing and delegate all calls to the underlying reader.

Usage is straightforward: you just instantiate your XmlReader as usual, but wrap the new legacy one on top before passing it to the SyndicationFeed class:

WebRequest request = CreateWebRequest(feedUri, credentials, modifiedSince);
XmlReaderSettings settings = new XmlReaderSettings { CloseInput = true };
using (XmlReader reader = XmlReader.Create(request.GetResponse().GetResponseStream(), settings))
{
    using (var legacyReader = new LegacyFeedXmlReader(reader))
    {
        SyndicationFeed feed = SyndicationFeed.Load(legacyReader);
        return feed.Items;
    }
}

This code is part of the NetFx project, a collection of utilities to augment various pieces of .NET. You can grab just the Syndication folder if you want. The goal of the project is to provide reusable pieces that you can just use in isolation (i.e. linking or copy/pasting a file or a folder). For your convenience, you can also just download the ZIP.

Enjoy!

/kzu

/kzu dev↻d