OPML file format in C#

Got to move those feeds from one aggregator that Google shuts down to another.
Oct 21 2008 by James Craig

I like RSS/Atom. Before they were in heavy use I might be able to keep up to date on a subject, maybe if I spent all my time researching it. However, thanks to RSS/Atom, I don't have to do that any more. However they have caused some extreme pain on my end... Mainly moving the feeds from one reader to another... However that was prior to the advent of OPML.

OPML is a very basic XML based format that was designed for outlines. It has been used in the past for a couple of things but probably the most popular is in feed aggregation. And to be more specific moving those feeds from one aggregator to another. Most feed readers today support the format (for both import and export). As such, you'll most likely want to use the format yourself if your going to use any sort of feed aggregation. In order to make your lives a bit easier, I made a set of helper classes to export/import OPML files:

OPML.cs #

  /\*
Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.\*/

#region Usings
using System;
using System.Text;
using System.Xml;
#endregion

namespace Utilities.FileFormats.OPMLHelper
{
/// <summary>
/// OPML class
/// </summary>
public class OPML
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public OPML()
{

}

/// <summary>
/// Constructor
/// </summary>
/// <param name="Location">Location of the OPML file</param>
public OPML(string Location)
{
XmlDocument Document = new XmlDocument();
Document.Load(Location);
foreach (XmlNode Children in Document.ChildNodes)
{
if (Children.Name.Equals("opml", StringComparison.CurrentCultureIgnoreCase))
{
foreach (XmlNode Child in Children.ChildNodes)
{
if (Child.Name.Equals("body", StringComparison.CurrentCultureIgnoreCase))
{
Body = new Body((XmlElement)Child);
}
else if (Child.Name.Equals("head", StringComparison.CurrentCultureIgnoreCase))
{
Head = new Head((XmlElement)Child);
}
}
}
}
}

/// <summary>
/// Constructor
/// </summary>
/// <param name="Document">XmlDocument containing the OPML file</param>
public OPML(XmlDocument Document)
{
foreach (XmlNode Children in Document.ChildNodes)
{
if (Children.Name.Equals("opml", StringComparison.CurrentCultureIgnoreCase))
{
foreach (XmlNode Child in Children.ChildNodes)
{
if (Child.Name.Equals("body", StringComparison.CurrentCultureIgnoreCase))
{
Body = new Body((XmlElement)Child);
}
else if (Child.Name.Equals("head", StringComparison.CurrentCultureIgnoreCase))
{
Head = new Head((XmlElement)Child);
}
}
}
}
}
#endregion

#region Properties

/// <summary>
/// Body of the file
/// </summary>
public Body Body { get; set; }

/// <summary>
/// Header information
/// </summary>
public Head Head { get; set; }

#endregion

#region Overridden Functions
public override string ToString()
{
StringBuilder OPMLString = new StringBuilder();
OPMLString.Append("<?xml version=\\"1.0\\" encoding=\\"ISO-8859-1\\"?><opml version=\\"2.0\\">");
OPMLString.Append(Head.ToString());
OPMLString.Append(Body.ToString());
OPMLString.Append("</opml>");
return OPMLString.ToString();
}
#endregion
}
}

Outline.cs #

  /\*
Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.\*/

#region Usings
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
#endregion

namespace Utilities.FileFormats.OPMLHelper
{
/// <summary>
/// Outline class
/// </summary>
public class Outline
{
#region Constructors

/// <summary>
/// Constructor
/// </summary>
public Outline()
{
Outlines = new List<Outline>();
}

/// <summary>
/// Constructors
/// </summary>
/// <param name="Element">Element containing outline information</param>
public Outline(XmlElement Element)
{
if (Element.Name.Equals("outline", StringComparison.CurrentCultureIgnoreCase))
{
if (Element.Attributes\["text"\] != null)
{
Text = Element.Attributes\["text"\].Value;
}
else if (Element.Attributes\["description"\] != null)
{
Description = Element.Attributes\["description"\].Value;
}
else if (Element.Attributes\["htmlUrl"\] != null)
{
HTMLUrl = Element.Attributes\["htmlUrl"\].Value;
}
else if (Element.Attributes\["type"\] != null)
{
Type = Element.Attributes\["type"\].Value;
}
else if (Element.Attributes\["language"\] != null)
{
Language = Element.Attributes\["language"\].Value;
}
else if (Element.Attributes\["title"\] != null)
{
Title = Element.Attributes\["title"\].Value;
}
else if (Element.Attributes\["version"\] != null)
{
Version = Element.Attributes\["version"\].Value;
}
else if (Element.Attributes\["xmlUrl"\] != null)
{
XMLUrl = Element.Attributes\["xmlUrl"\].Value;
}
foreach (XmlNode Child in Element.ChildNodes)
{
try
{
if (Child.Name.Equals("outline", StringComparison.CurrentCultureIgnoreCase))
{
Outlines.Add(new Outline((XmlElement)Child));
}
}
catch { }
}
}
}

#endregion

#region Properties

/// <summary>
/// Outline list
/// </summary>
public List<Outline> Outlines { get; set; }

/// <summary>
/// Url of the XML file
/// </summary>
public string XMLUrl { get; set; }

/// <summary>
/// Version number
/// </summary>
public string Version { get; set; }

/// <summary>
/// Title of the item
/// </summary>
public string Title { get; set; }

/// <summary>
/// Language used
/// </summary>
public string Language { get; set; }

/// <summary>
/// Type
/// </summary>
public string Type { get; set; }

/// <summary>
/// HTML Url
/// </summary>
public string HTMLUrl { get; set; }

/// <summary>
/// Text
/// </summary>
public string Text { get; set; }

/// <summary>
/// Description
/// </summary>
public string Description { get; set; }

#endregion

#region Overridden Functions

public override string ToString()
{
StringBuilder OutlineString = new StringBuilder();
OutlineString.Append("<outline text=\\"" + Text + "\\"");
if (!string.IsNullOrEmpty(XMLUrl))
{
OutlineString.Append(" xmlUrl=\\"" + XMLUrl + "\\"");
}
if (!string.IsNullOrEmpty(Version))
{
OutlineString.Append(" version=\\"" + Version + "\\"");
}
if (!string.IsNullOrEmpty(Title))
{
OutlineString.Append(" title=\\"" + Title + "\\"");
}
if (!string.IsNullOrEmpty(Language))
{
OutlineString.Append(" language=\\"" + Language + "\\"");
}
if (!string.IsNullOrEmpty(Type))
{
OutlineString.Append(" type=\\"" + Type + "\\"");
}
if (!string.IsNullOrEmpty(HTMLUrl))
{
OutlineString.Append(" htmlUrl=\\"" + HTMLUrl + "\\"");
}
if (!string.IsNullOrEmpty(Text))
{
OutlineString.Append(" text=\\"" + Text + "\\"");
}
if (!string.IsNullOrEmpty(Description))
{
OutlineString.Append(" description=\\"" + Description + "\\"");
}
if (Outlines.Count > 0)
{
OutlineString.Append(">\\r\\n");
foreach (Outline Outline in Outlines)
{
OutlineString.Append(Outline.ToString());
}
OutlineString.Append("</outline>\\r\\n");
}
else
{
OutlineString.Append(" />\\r\\n");
}
return OutlineString.ToString();
}

#endregion
}
}

Head.cs #

  /\*
Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.\*/

#region Usings
using System;
using System.Text;
using System.Xml;
#endregion

namespace Utilities.FileFormats.OPMLHelper
{
/// <summary>
/// Head class
/// </summary>
public class Head
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public Head()
{
Docs = "http://www.opml.org/spec2";
DateCreated = DateTime.Now;
DateModified = DateTime.Now;
}

/// <summary>
/// Constructor
/// </summary>
/// <param name="Element">XmlElement containing the header information</param>
public Head(XmlElement Element)
{
if (Element.Name.Equals("head", StringComparison.CurrentCultureIgnoreCase))
{
foreach (XmlNode Child in Element.ChildNodes)
{
try
{
if (Child.Name.Equals("title", StringComparison.CurrentCultureIgnoreCase))
{
Title = Child.InnerText;
}
else if (Child.Name.Equals("ownerName", StringComparison.CurrentCultureIgnoreCase))
{
OwnerName = Child.InnerText;
}
else if (Child.Name.Equals("ownerEmail", StringComparison.CurrentCultureIgnoreCase))
{
OwnerEmail = Child.InnerText;
}
else if (Child.Name.Equals("dateCreated", StringComparison.CurrentCultureIgnoreCase))
{
DateCreated = DateTime.Parse(Child.InnerText);
}
else if (Child.Name.Equals("dateModified", StringComparison.CurrentCultureIgnoreCase))
{
DateModified = DateTime.Parse(Child.InnerText);
}
else if (Child.Name.Equals("docs", StringComparison.CurrentCultureIgnoreCase))
{
Docs = Child.InnerText;
}
}
catch { }
}
}
}

#endregion

#region Properties
/// <summary>
/// Title of the OPML document
/// </summary>
public string Title { get; set; }

/// <summary>
/// Date it was created
/// </summary>
public DateTime DateCreated { get; set; }

/// <summary>
/// Date it was last modified
/// </summary>
public DateTime DateModified { get; set; }

/// <summary>
/// Owner of the file
/// </summary>
public string OwnerName { get; set; }

/// <summary>
/// Owner's email address
/// </summary>
public string OwnerEmail { get; set; }

/// <summary>
/// Location of the OPML spec
/// </summary>
public string Docs { get; set; }
#endregion

#region Overridden Functions

public override string ToString()
{
StringBuilder HeadString = new StringBuilder();
HeadString.Append("<head>");
HeadString.Append("<title>" + Title + "</title>\\r\\n");
HeadString.Append("<dateCreated>" + DateCreated.ToString("R") + "</dateCreated>\\r\\n");
HeadString.Append("<dateModified>" + DateModified.ToString("R") + "</dateModified>\\r\\n");
HeadString.Append("<ownerName>" + OwnerName + "</ownerName>\\r\\n");
HeadString.Append("<ownerEmail>" + OwnerEmail + "</ownerEmail>\\r\\n");
HeadString.Append("<docs>" + Docs + "</docs>\\r\\n");
HeadString.Append("</head>\\r\\n");
return HeadString.ToString();
}

#endregion
}
}

Body.cs #

  /\*
Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.\*/

#region Usings
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
#endregion

namespace Utilities.FileFormats.OPMLHelper
{
/// <summary>
/// Body class
/// </summary>
public class Body
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public Body()
{
Outlines = new List<Outline>();
}

/// <summary>
/// Constructor
/// </summary>
/// <param name="Element">XmlElement containing the body information</param>
public Body(XmlElement Element)
{
Outlines = new List<Outline>();
if (Element.Name.Equals("body", StringComparison.CurrentCultureIgnoreCase))
{
foreach (XmlNode Child in Element.ChildNodes)
{
try
{
if (Child.Name.Equals("outline", StringComparison.CurrentCultureIgnoreCase))
{
Outlines.Add(new Outline((XmlElement)Child));
}
}
catch { }
}
}
}
#endregion

#region Properties

/// <summary>
/// List of outlines
/// </summary>
public List<Outline> Outlines { get; set; }

#endregion

#region Overridden Functions

public override string ToString()
{
StringBuilder BodyString = new StringBuilder();
BodyString.Append("<body>");
foreach (Outline Outline in Outlines)
{
BodyString.Append(Outline.ToString());
}
BodyString.Append("</body>");
return BodyString.ToString();
}

#endregion
}
}

It's very similar to the RSS helper that I created a little while back. Once you have your OPML file created/loaded, all you need to do is call ToString and it will output the OPML file to a string, properly formatted. As far as the various fields, I named them after the format itself. So the outlines are exactly like the outlines in the format description (minus some fields that aren't used that much in RSS aggregation), the xmlUrl is the xmlUrl, the title is the title... You get the point. That should help you out since the files aren't exactly commented at this point in time. However, I'll be adding this to the utilities library fairly soon (assuming my week isn't too crazy) and adding those comments and maybe some extra functionality.
Anyway, download the code, leave feedback, and happy coding.