Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Consuming an OPML file from Monad
Mike Taulty's Blog

Mike's Badges

Follow on Twitter
View mike's profile on slideshare
Add to Technorati Favorites
CW Blog Awards
Continuing by "beginner's Monad theme" ( where I am the beginner ) I had a go at consuming an OPML file from Monad.
 
Now, I'm a beginner on so many levels here. Firstly, I don't understand OPML so I went to try and find a spec (think I ended up at www.opml.org). What I ended up realising was that there doesn't seem to really be a spec for OPML - it has some structure to it but when you hit the <outline> tag it seems you can just add any old attribute you like.
 
So, I went off the format that RSS Bandit generates when you export a list of RSS subscriptions as OPML. I wrote a new shell Cmdlet based on that and this is it (not rocket science + apologies for the recursion and the DOM - making life easier for myself).
 
using System;
using System.Management.Automation;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.XPath;
 
namespace Mtaulty.Msh.Commands
{
  public class OpmlOutline
  {
    public OpmlOutline()
    {
    }
    public OpmlOutline(string title, string desc, string xmlUrl,
      string htmlUrl, string path)
    {
      this.title = title;
      this.description = desc;
      this.xmlUrl = xmlUrl;
      this.htmlUrl = htmlUrl;
      this.path = path;
    }
 
    private string xmlUrl;
 
    public string XmlUrl
    {
      get { return xmlUrl; }
      set { xmlUrl = value; }
    }
    private string htmlUrl;
 
    public string HtmlUrl
    {
      get { return htmlUrl; }
      set { htmlUrl = value; }
    }
 
    private string title;
 
    public string Title
    {
      get { return title; }
      set { title = value; }
    }
    private string description;
 
    public string Description
    {
      get { return description; }
      set { description = value; }
    }
    private string path;
 
    public string Path
    {
      get { return path; }
      set { path = value; }
    }
 
  }
 
  [Cmdlet("get", "opmlfromfile")]
  public class GetOpmlFromFileCommand : Cmdlet
  {
    private string opmlFile;
 
    [Parameter
      (
        Position = 0,
        Mandatory = true,
        HelpMessage = "Name of the opml file to consume",
        ValueFromPipeline = true
      )
    ]
    public string OpmlFile
    {
      get { return opmlFile; }
      set { opmlFile = value; }
    }
    protected override void EndProcessing()
    {
      try
      {
        using (FileStream fs = new FileStream(opmlFile, FileMode.Open,
          FileAccess.Read))
        {
          XmlDocument doc = new XmlDocument();
          doc.Load(fs);
 
          XPathNavigator navigator = doc.CreateNavigator();
          RecurseOutlineNodes(string.Empty, navigator, "/opml/body/outline");
 
          fs.Close();
        }
      }
      catch (Exception ex)
      {
        ThrowTerminatingError(new ErrorRecord(ex, null, ErrorCategory.NotSpecified,
          null));
      }
    }
    private void RecurseOutlineNodes(string path,
      XPathNavigator navigator, string xpath)
    {
      XPathNodeIterator iterator = navigator.Select(xpath);
 
      while (iterator.MoveNext())
      {
        if (iterator.Current.HasChildren)
        {
          string subFolder = iterator.Current.GetAttribute("title",
            string.Empty);
 
          if (subFolder != null)
          {
            RecurseOutlineNodes(path + "/" + subFolder,
              iterator.Current, "./outline");
          }
        }
        else
        {
          OpmlOutline newOutline = GetOutlineItem(path, iterator.Current);
 
          WriteObject(newOutline);
        }
      }
    }
    private static OpmlOutline GetOutlineItem(string path, XPathNavigator current)
    {
      OpmlOutline outline = null;
 
      string title = current.GetAttribute("title",
        string.Empty);
 
      string description = current.GetAttribute("description",
        string.Empty);
 
      string xmlUrl = current.GetAttribute("xmlUrl",
        string.Empty);
 
      string htmlUrl = current.GetAttribute("htmlUrl",
        string.Empty);
 
      if (ValidString(title) && ValidString(xmlUrl))
      {
        outline = new OpmlOutline(title, description,
          xmlUrl, htmlUrl, path);
      }
      return (outline);
    }
    private static bool ValidString(string s)
    {
      return ((s != null) && (s != string.Empty));
    }
  }
}
 
Once again, I added that to my shell and now I can do something like;
 
**********************
MSH Transcript Start
Start time: 20060113115425
Username  : EUROPE\mtaulty
Machine   : MTAULTYVISTA (Microsoft Windows NT 6.0.5270.0)
**********************
Transcript started, output file is c:\temp\trans.txt
MSH C:\Users\mtaulty> get-opmlfromfile w:\temp\feeds.opml
 

XmlUrl      : http://adrianba.net/rss/rss.aspx
HtmlUrl     : http://adrianba.net/
Title       : Adrian Bateman
Description : exploring the .net development space
Path        : /UK Bloggers on MSDN/UK .NET Bloggers
 
XmlUrl      : http://bitarray.co.uk/ben/Rss.aspx
HtmlUrl     : http://bitarray.co.uk/ben/
Title       : Ben Lovell
Description : .NET ramblings
Path        : /UK Bloggers on MSDN/UK .NET Bloggers
 
XmlUrl      : http://blog.opennetcf.org/ncowburn/blogxbrowsing.asmx/GetRss?
HtmlUrl     :
http://blog.opennetcf.org/ncowburn/
Title       : Neil Cowburn
Description : Co-Founder of OpenNETCF
Path        : /UK Bloggers on MSDN/UK .NET Bloggers
 
XmlUrl      : http://blog.vbug.net/SyndicationService.asmx/GetRss
HtmlUrl     : http://blog.vbug.net/
Title       : Graham Parker
Description : The VBUG Blog
Path        : /UK Bloggers on MSDN/UK .NET Bloggers
Once again - I'm not sure if this kind of command really makes sense in Monad in that it might make more sense to implement a provider that lets you navigate around the OPML file rather than a command like this.

Posted Fri, Jan 13 2006 4:24 AM by mtaulty
(C) Mike Taulty, 2009. All rights reserved. The information in this weblog is provided "AS IS" with no warranties, and confers no rights. This weblog does not represent the thoughts, intentions, plans or strategies of my employer. It is solely my opinion. Inappropriate comments will be deleted at the authors discretion. All code samples are provided "AS IS" without warranty of any kind, either express or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems