OPML in C#/ASP.Net

10/21/2008

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

   1: /*
   2: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System;
  24: using System.Text;
  25: using System.Xml;
  26: #endregion
  27:  
  28: namespace Utilities.FileFormats.OPMLHelper
  29: {
  30:     /// <summary>
  31:     /// OPML class
  32:     /// </summary>
  33:     public class OPML
  34:     {
  35:         #region Constructors
  36:         /// <summary>
  37:         /// Constructor
  38:         /// </summary>
  39:         public OPML()
  40:         {
  41:  
  42:         }
  43:  
  44:         /// <summary>
  45:         /// Constructor
  46:         /// </summary>
  47:         /// <param name="Location">Location of the OPML file</param>
  48:         public OPML(string Location)
  49:         {
  50:             XmlDocument Document = new XmlDocument();
  51:             Document.Load(Location);
  52:             foreach (XmlNode Children in Document.ChildNodes)
  53:             {
  54:                 if (Children.Name.Equals("opml", StringComparison.CurrentCultureIgnoreCase))
  55:                 {
  56:                     foreach (XmlNode Child in Children.ChildNodes)
  57:                     {
  58:                         if (Child.Name.Equals("body", StringComparison.CurrentCultureIgnoreCase))
  59:                         {
  60:                             Body = new Body((XmlElement)Child);
  61:                         }
  62:                         else if (Child.Name.Equals("head", StringComparison.CurrentCultureIgnoreCase))
  63:                         {
  64:                             Head = new Head((XmlElement)Child);
  65:                         }
  66:                     }
  67:                 }
  68:             }
  69:         }
  70:  
  71:         /// <summary>
  72:         /// Constructor
  73:         /// </summary>
  74:         /// <param name="Document">XmlDocument containing the OPML file</param>
  75:         public OPML(XmlDocument Document)
  76:         {
  77:             foreach (XmlNode Children in Document.ChildNodes)
  78:             {
  79:                 if (Children.Name.Equals("opml", StringComparison.CurrentCultureIgnoreCase))
  80:                 {
  81:                     foreach (XmlNode Child in Children.ChildNodes)
  82:                     {
  83:                         if (Child.Name.Equals("body", StringComparison.CurrentCultureIgnoreCase))
  84:                         {
  85:                             Body = new Body((XmlElement)Child);
  86:                         }
  87:                         else if (Child.Name.Equals("head", StringComparison.CurrentCultureIgnoreCase))
  88:                         {
  89:                             Head = new Head((XmlElement)Child);
  90:                         }
  91:                     }
  92:                 }
  93:             }
  94:         }
  95:         #endregion
  96:  
  97:         #region Properties
  98:  
  99:         /// <summary>
 100:         /// Body of the file
 101:         /// </summary>
 102:         public Body Body { get; set; }
 103:  
 104:         /// <summary>
 105:         /// Header information
 106:         /// </summary>
 107:         public Head Head { get; set; }
 108:  
 109:         #endregion
 110:  
 111:         #region Overridden Functions
 112:         public override string ToString()
 113:         {
 114:             StringBuilder OPMLString = new StringBuilder();
 115:             OPMLString.Append("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><opml version=\"2.0\">");
 116:             OPMLString.Append(Head.ToString());
 117:             OPMLString.Append(Body.ToString());
 118:             OPMLString.Append("</opml>");
 119:             return OPMLString.ToString();
 120:         }
 121:         #endregion
 122:     }
 123: }

Outline.cs

   1: /*
   2: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System;
  24: using System.Collections.Generic;
  25: using System.Text;
  26: using System.Xml;
  27: #endregion
  28:  
  29: namespace Utilities.FileFormats.OPMLHelper
  30: {
  31:     /// <summary>
  32:     /// Outline class
  33:     /// </summary>
  34:     public class Outline
  35:     {
  36:         #region Constructors
  37:  
  38:         /// <summary>
  39:         /// Constructor
  40:         /// </summary>
  41:         public Outline()
  42:         {
  43:             Outlines = new List<Outline>();
  44:         }
  45:  
  46:         /// <summary>
  47:         /// Constructors
  48:         /// </summary>
  49:         /// <param name="Element">Element containing outline information</param>
  50:         public Outline(XmlElement Element)
  51:         {
  52:             if (Element.Name.Equals("outline", StringComparison.CurrentCultureIgnoreCase))
  53:             {
  54:                 if (Element.Attributes["text"] != null)
  55:                 {
  56:                     Text = Element.Attributes["text"].Value;
  57:                 }
  58:                 else if (Element.Attributes["description"] != null)
  59:                 {
  60:                     Description = Element.Attributes["description"].Value;
  61:                 }
  62:                 else if (Element.Attributes["htmlUrl"] != null)
  63:                 {
  64:                     HTMLUrl = Element.Attributes["htmlUrl"].Value;
  65:                 }
  66:                 else if (Element.Attributes["type"] != null)
  67:                 {
  68:                     Type = Element.Attributes["type"].Value;
  69:                 }
  70:                 else if (Element.Attributes["language"] != null)
  71:                 {
  72:                     Language = Element.Attributes["language"].Value;
  73:                 }
  74:                 else if (Element.Attributes["title"] != null)
  75:                 {
  76:                     Title = Element.Attributes["title"].Value;
  77:                 }
  78:                 else if (Element.Attributes["version"] != null)
  79:                 {
  80:                     Version = Element.Attributes["version"].Value;
  81:                 }
  82:                 else if (Element.Attributes["xmlUrl"] != null)
  83:                 {
  84:                     XMLUrl = Element.Attributes["xmlUrl"].Value;
  85:                 }
  86:                 foreach (XmlNode Child in Element.ChildNodes)
  87:                 {
  88:                     try
  89:                     {
  90:                         if (Child.Name.Equals("outline", StringComparison.CurrentCultureIgnoreCase))
  91:                         {
  92:                             Outlines.Add(new Outline((XmlElement)Child));
  93:                         }
  94:                     }
  95:                     catch { }
  96:                 }
  97:             }
  98:         }
  99:  
 100:         #endregion
 101:  
 102:         #region Properties
 103:  
 104:         /// <summary>
 105:         /// Outline list
 106:         /// </summary>
 107:         public List<Outline> Outlines { get; set; }
 108:  
 109:         /// <summary>
 110:         /// Url of the XML file
 111:         /// </summary>
 112:         public string XMLUrl { get; set; }
 113:  
 114:         /// <summary>
 115:         /// Version number
 116:         /// </summary>
 117:         public string Version { get; set; }
 118:  
 119:         /// <summary>
 120:         /// Title of the item
 121:         /// </summary>
 122:         public string Title { get; set; }
 123:  
 124:         /// <summary>
 125:         /// Language used
 126:         /// </summary>
 127:         public string Language { get; set; }
 128:  
 129:         /// <summary>
 130:         /// Type
 131:         /// </summary>
 132:         public string Type { get; set; }
 133:  
 134:         /// <summary>
 135:         /// HTML Url
 136:         /// </summary>
 137:         public string HTMLUrl { get; set; }
 138:  
 139:         /// <summary>
 140:         /// Text
 141:         /// </summary>
 142:         public string Text { get; set; }
 143:  
 144:         /// <summary>
 145:         /// Description
 146:         /// </summary>
 147:         public string Description { get; set; }
 148:  
 149:         #endregion
 150:  
 151:         #region Overridden Functions
 152:  
 153:         public override string ToString()
 154:         {
 155:             StringBuilder OutlineString = new StringBuilder();
 156:             OutlineString.Append("<outline text=\"" + Text + "\"");
 157:             if (!string.IsNullOrEmpty(XMLUrl))
 158:             {
 159:                 OutlineString.Append(" xmlUrl=\"" + XMLUrl + "\"");
 160:             }
 161:             if (!string.IsNullOrEmpty(Version))
 162:             {
 163:                 OutlineString.Append(" version=\"" + Version + "\"");
 164:             }
 165:             if (!string.IsNullOrEmpty(Title))
 166:             {
 167:                 OutlineString.Append(" title=\"" + Title + "\"");
 168:             }
 169:             if (!string.IsNullOrEmpty(Language))
 170:             {
 171:                 OutlineString.Append(" language=\"" + Language + "\"");
 172:             }
 173:             if (!string.IsNullOrEmpty(Type))
 174:             {
 175:                 OutlineString.Append(" type=\"" + Type + "\"");
 176:             }
 177:             if (!string.IsNullOrEmpty(HTMLUrl))
 178:             {
 179:                 OutlineString.Append(" htmlUrl=\"" + HTMLUrl + "\"");
 180:             }
 181:             if (!string.IsNullOrEmpty(Text))
 182:             {
 183:                 OutlineString.Append(" text=\"" + Text + "\"");
 184:             }
 185:             if (!string.IsNullOrEmpty(Description))
 186:             {
 187:                 OutlineString.Append(" description=\"" + Description + "\"");
 188:             }
 189:             if (Outlines.Count > 0)
 190:             {
 191:                 OutlineString.Append(">\r\n");
 192:                 foreach (Outline Outline in Outlines)
 193:                 {
 194:                     OutlineString.Append(Outline.ToString());
 195:                 }
 196:                 OutlineString.Append("</outline>\r\n");
 197:             }
 198:             else
 199:             {
 200:                 OutlineString.Append(" />\r\n");
 201:             }
 202:             return OutlineString.ToString();
 203:         }
 204:  
 205:         #endregion
 206:     }
 207: }

Head.cs

   1: /*
   2: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System;
  24: using System.Text;
  25: using System.Xml;
  26: #endregion
  27:  
  28: namespace Utilities.FileFormats.OPMLHelper
  29: {
  30:     /// <summary>
  31:     /// Head class
  32:     /// </summary>
  33:     public class Head
  34:     {
  35:         #region Constructors
  36:         /// <summary>
  37:         /// Constructor
  38:         /// </summary>
  39:         public Head()
  40:         {
  41:             Docs = "http://www.opml.org/spec2";
  42:             DateCreated = DateTime.Now;
  43:             DateModified = DateTime.Now;
  44:         }
  45:  
  46:         /// <summary>
  47:         /// Constructor
  48:         /// </summary>
  49:         /// <param name="Element">XmlElement containing the header information</param>
  50:         public Head(XmlElement Element)
  51:         {
  52:             if (Element.Name.Equals("head", StringComparison.CurrentCultureIgnoreCase))
  53:             {
  54:                 foreach (XmlNode Child in Element.ChildNodes)
  55:                 {
  56:                     try
  57:                     {
  58:                         if (Child.Name.Equals("title", StringComparison.CurrentCultureIgnoreCase))
  59:                         {
  60:                             Title = Child.InnerText;
  61:                         }
  62:                         else if (Child.Name.Equals("ownerName", StringComparison.CurrentCultureIgnoreCase))
  63:                         {
  64:                             OwnerName = Child.InnerText;
  65:                         }
  66:                         else if (Child.Name.Equals("ownerEmail", StringComparison.CurrentCultureIgnoreCase))
  67:                         {
  68:                             OwnerEmail = Child.InnerText;
  69:                         }
  70:                         else if (Child.Name.Equals("dateCreated", StringComparison.CurrentCultureIgnoreCase))
  71:                         {
  72:                             DateCreated = DateTime.Parse(Child.InnerText);
  73:                         }
  74:                         else if (Child.Name.Equals("dateModified", StringComparison.CurrentCultureIgnoreCase))
  75:                         {
  76:                             DateModified = DateTime.Parse(Child.InnerText);
  77:                         }
  78:                         else if (Child.Name.Equals("docs", StringComparison.CurrentCultureIgnoreCase))
  79:                         {
  80:                             Docs = Child.InnerText;
  81:                         }
  82:                     }
  83:                     catch { }
  84:                 }
  85:             }
  86:         }
  87:  
  88:         #endregion
  89:  
  90:         #region Properties
  91:         /// <summary>
  92:         /// Title of the OPML document
  93:         /// </summary>
  94:         public string Title { get; set; }
  95:  
  96:         /// <summary>
  97:         /// Date it was created
  98:         /// </summary>
  99:         public DateTime DateCreated { get; set; }
 100:  
 101:         /// <summary>
 102:         /// Date it was last modified
 103:         /// </summary>
 104:         public DateTime DateModified { get; set; }
 105:  
 106:         /// <summary>
 107:         /// Owner of the file
 108:         /// </summary>
 109:         public string OwnerName { get; set; }
 110:  
 111:         /// <summary>
 112:         /// Owner's email address
 113:         /// </summary>
 114:         public string OwnerEmail { get; set; }
 115:  
 116:         /// <summary>
 117:         /// Location of the OPML spec
 118:         /// </summary>
 119:         public string Docs { get; set; }
 120:         #endregion
 121:  
 122:         #region Overridden Functions
 123:  
 124:         public override string ToString()
 125:         {
 126:             StringBuilder HeadString = new StringBuilder();
 127:             HeadString.Append("<head>");
 128:             HeadString.Append("<title>" + Title + "</title>\r\n");
 129:             HeadString.Append("<dateCreated>" + DateCreated.ToString("R") + "</dateCreated>\r\n");
 130:             HeadString.Append("<dateModified>" + DateModified.ToString("R") + "</dateModified>\r\n");
 131:             HeadString.Append("<ownerName>" + OwnerName + "</ownerName>\r\n");
 132:             HeadString.Append("<ownerEmail>" + OwnerEmail + "</ownerEmail>\r\n");
 133:             HeadString.Append("<docs>" + Docs + "</docs>\r\n");
 134:             HeadString.Append("</head>\r\n");
 135:             return HeadString.ToString();
 136:         }
 137:  
 138:         #endregion
 139:     }
 140: }

Body.cs

   1: /*
   2: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System;
  24: using System.Collections.Generic;
  25: using System.Text;
  26: using System.Xml;
  27: #endregion
  28:  
  29: namespace Utilities.FileFormats.OPMLHelper
  30: {
  31:     /// <summary>
  32:     /// Body class
  33:     /// </summary>
  34:     public class Body
  35:     {
  36:         #region Constructors
  37:         /// <summary>
  38:         /// Constructor
  39:         /// </summary>
  40:         public Body()
  41:         {
  42:             Outlines = new List<Outline>();
  43:         }
  44:  
  45:         /// <summary>
  46:         /// Constructor
  47:         /// </summary>
  48:         /// <param name="Element">XmlElement containing the body information</param>
  49:         public Body(XmlElement Element)
  50:         {
  51:             Outlines = new List<Outline>();
  52:             if (Element.Name.Equals("body", StringComparison.CurrentCultureIgnoreCase))
  53:             {
  54:                 foreach (XmlNode Child in Element.ChildNodes)
  55:                 {
  56:                     try
  57:                     {
  58:                         if (Child.Name.Equals("outline", StringComparison.CurrentCultureIgnoreCase))
  59:                         {
  60:                             Outlines.Add(new Outline((XmlElement)Child));
  61:                         }
  62:                     }
  63:                     catch { }
  64:                 }
  65:             }
  66:         }
  67:         #endregion
  68:  
  69:         #region Properties
  70:  
  71:         /// <summary>
  72:         /// List of outlines
  73:         /// </summary>
  74:         public List<Outline> Outlines { get; set; }
  75:  
  76:         #endregion
  77:  
  78:         #region Overridden Functions
  79:  
  80:         public override string ToString()
  81:         {
  82:             StringBuilder BodyString = new StringBuilder();
  83:             BodyString.Append("<body>");
  84:             foreach (Outline Outline in Outlines)
  85:             {
  86:                 BodyString.Append(Outline.ToString());
  87:             }
  88:             BodyString.Append("</body>");
  89:             return BodyString.ToString();
  90:         }
  91:  
  92:         #endregion
  93:     }
  94: }

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.



Comments