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

Blogs

Mike Taulty's Blog

Elsewhere

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