My fifteen minutes of fame (or how to get OPML into HTML)

26 May 2005 : opml, php

A few days back as I was skimming through my plethora of rss feeds on Bloglines I came across a post from Tod Maffin looking for a programmer to write a simple script which would parse an OPML file of podcasts into HTML which could be included in a web page. The task was straightforward enough and given the ubiquity of OPML in RSS readers, I knew someone else would have at least written a basic parser in PHP that I could start with. I wrote back to Tod telling him that I was the man for the job and set about working on the script. I offered to do the script pro-bono as a way to help spread the word about public-radio podcasts which keep me entertained and amused throughout the day.

A quick google search brought me to Joe Grossberg’s PHP script for including his OPML Bloglines subscription list as an HTML un-ordered list on his blog. His code was very straightforward and well writen so I decided to use it as a base.

My first task was to strip out all the code that was particular to Bloglines OPML format and customize the script to the format of Tod’s OPML file (you can find an example here). This proved to be more difficult than I originally anticipated and I had to consult the PHP manual to understand how the PHP function, xml_parse_into_struct function worked in order to make sure I was pulling out the parsed podcast feed title and URL. However, after a few hours of trial and error, I was able to parse the OPML file correctly into an ordered list. I even added a small bit of code to ignore the first entry in his OPML file which is a link to add new podcasts. (Tod had hardcoded that link into his website and he only provided the link in the OPML file so that it could be used in the Ipodder directory to have podcast listeners contact him about new podcasts).

With the parsing complete, I turned to the task of displaying the podcast information in an HTML table. I took my cue from Tod’s orginial page which he had been manually updating as new podcasts were added. In that layout, podcasts were organized by categories and alternating categories had the same color so that it was easy to distinguish between categories. I wanted my script to generate the table entirely using PHP so I had to reaquaint myself with the process of writing code by hand because my script would have to add the table tags dynamically depending on how many podcasts were in the OPML file. After a few hours of tweaking the code and a night’s worth of sleep I finally had it generate a basic table which I could include in another PHP file.

In order to make the table a little easier to read, I used the trick of alternating colors for table rows that’s become popular around the Net and in applications like Apple’s iTunes. The easiest way I found to do this was to keep track of the number of podcasts in each category. Every time my script would go to write out the table cell definition for a podcast it would check to see if the podcast number was divisible by two meaning that it was even. Even numbered podcasts would be colored using one color and odd numbered podcasts would use a different color. To make things even more interesting, I kept track of the category number as well and wrote code to create two sets of even/odd color pairs – one for even numbered categories and other for odd-numbered categories. You can admire the results at http://todmaffin.com/feeds/ . http://publicradiofeeds.com.

With Tod’s permission I’m releasing the source code for my file under GPL so that others might benefit and improve on it. The file is called ipodder.opml.class.php. To use it, you’ll need to add the following code snippet as a table row in another .php file:

Anyways here is the link to the ipodder.opml.html.php.

I hope you find it useful in and of itself or as a point of departure for building your own OPML parser. Please email me with any feedback you might have at projects@rasheqrahman.com.


Ipodder Opml Class

25 May 2005 : code, opml, php, podcast, programming

<?php

/*************************************************************************************
* ipodder.opml.class.php
* ---------
* Author: Rasheq Rahman (rasheq.rahman@gmail.com)
* Copyright: (c) 2005 Rasheq Rahman
* Release Version: 1.0.0
* Date Started: 2005/05/20

* ipodder.opml.class.php is a PHP class to convert an Ipodder (http://www.ipodder.org) OPML file into a simple HTML table that can be inserted into a web page. More complete documentation can be found at http://www.rasheqrahman.com/.
*
* The orginial code for this class was written by Joe Grossberg. See http://www.joegrossberg.com/archives/001966.html for details.
*
*************************************************************************************
*
*
* ipodder.opml.class.php is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This class is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ipodder.opml.class.php; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
************************************************************************************/

class OPMLtoHTML
{
  var $index;
  var $vals;
  var $html = '';
  
  /* Set the color scheme for alternating podcasts and alternating categories
In the ipodder hierarchy, podcasts are organized into categories. A category might have one, two, etc. podcasts. To make the tables easier to read, I have implemented logic that sets different color schemes for alternating categories and alternating podcasts. You can use Hexadecimal colors or CSS color names. A good list is provided by the W3 Consortium at http://www.w3schools.com/css/css_colornames.asp
*/
  
  var $oddcategory_oddpodcast_color = "#D1CEF2"; // ex: category #1, podcast #1
  var $oddcategory_evenpodcast_color = "#E3E1F7"; // ex: category #1, podcast #2
  var $evencategory_oddpodcast_color = "#D8EDAF"; //ex: category #2, podcast #1
  var $evencategory_evenpodcast_color = "#EEF7DD"; //ex: category #2, podcast #2
  


  function OPMLtoHTML ($file)
  {
    $this->parse_opml($file);
    $this->generate_table();
  }

  function parse_opml ($file)
  {
    $fp = fopen($file, 'r');
    if (!$fp) return;
    $xml = fread($fp, filesize($file));
    fclose($fp);

    $parser = xml_parser_create('');
    $status = xml_parse_into_struct($parser, $xml, $this->vals, $this->index);
    xml_parser_free($parser);
    if (!$status) return;

    return true;
  }

  function generate_table () // creates table from parsed OPML file
  {
    $expand = $this->vals[$this->index['EXPANSIONSTATE'][0]]['value'];
    $expand = array_flip(preg_split('/,\s*/', $expand));
    
    $category_count = 1; // counter to track category # for color formatting
    
    $count = 0; // Not sure what this is used for but it was in joe's original script

    foreach ($this->index['OUTLINE'] as $i)
    {
      $type = $this->vals[$i]['type'];
      if ($type == 'cdata' or $i < $this->index['BODY'][0])
        continue; //skips over header informaition in OMPL file
        
       // code below is optional. You can use it create "indents" in the final HTML output so that code is nicely formatted
       
      if (empty($first_depth))
        $first_depth = $this->vals[$i]['level'] - 1;

      $depth = $this->vals[$i]['level'] - $first_depth;
      $indent = str_repeat(' ', $depth);

// when the last podcast is reached in each category, an extra row is added to provide a buffer for the next category.

      if ($type == 'close')
      {
        $category_count++;
     
        $this->html .= "<tr>\n<td height=".'"20px"'."></td>\n<td></td></tr>\n";
        continue;
      }
      
      $count++;

/*
the text (podcast name or category name) and $feed (rss/xml feed for podcast as parsed from the OPML file
*/

      $text = htmlentities($this->vals[$i]['attributes']['TEXT']);
      $feed = htmlentities($this->vals[$i]['attributes']['URL']);

      
            
      if ($type == 'complete')
     
      {
/*
skip over the first entry which is just a link to add a new podcast to the category. This is hardcoded in HTML already.
*/
        if ( preg_match("/\bClick to add your show to this listing\b/i", $text, $match))
        {
        continue;
        }
        
/*
identifies whether a category # is even/odd and the podcast # is even/odd. Apply appropriate formatting based on class variables defined above.
*/

        if ($category_count % 2)
         ($podcast_count % 2) ? $bgcolor = $this->oddcategory_oddpodcast_color : $bgcolor = $this->oddcategory_evenpodcast_color;
        else
         ($podcast_count % 2) ? $bgcolor = $this->evencategory_oddpodcast_color : $bgcolor = $this->evencategory_evenpodcast_color;

//formatting for podcast feed

        if ($feed)
        {
         $this->html .= "<tr> \n";
         $this->html .= $indent . $indent . '<td width="50px" align="center" bgcolor ="'. $bgcolor. '"><a href="' . $feed . '"><img alt="Click to select feed" src="http://www.scripting.com/images/xmlIcon2.gif" border="0" height="14" width="36"></a></td>'."\n";
        
//formatting for podcast name/description

        if ($text)
          $this->html .= $indent . $indent . '<td width="650px" align="left" bgcolor="' . $bgcolor. '" cellpadding="3"><B>'. $text . '</B>';
          $podcast_count++;
          
        }
        $this->html .= "</td>\n</tr>\n";
      }
      
//formatting for category name

      if ($type == 'open') // This for the section headers
       {
        $this->html .= '<tr><td colspan="2"><h3 align="left"><b>'. $text . "</b></h3></td></tr>\n";
        $podcast_count = 1; // counter to track podcast # for color formatting
       }
    }
  }
}

?>