Latest News

1st August 2008
Send to twitter Send to Facebook

It seems that a few of you are using or trying to use the XML::RSS::Podcast module I published on this blog. As was mentioned there, modern version of XML::RSS do not support the encode function required for generating well-formed XML documents.

That's all changed thanks to Rohan Carly. [Add your thanks to Rohan in the comments.]

I present the complete version of XML::RSS::Podcast below. If you find this module useful, I'll attempt to create a CPAN module for it.

package XML::RSS::Podcast;
use XML::RSS;
use HTML::Entities qw[encode_entities_numeric encode_entities];
@XML::RSS::Podcast::ISA = qw[XML::RSS];
our $VERSION = q[1.1];

# encode by Rohan Carly
# Stolen from XML::RSS::Private::Output::Base;
sub encode {
  my ($self,$text) = @_;
  return unless defined($text);
  
  my $encoded_text = '';
  
  while ($text =~ s,(.*?)(<!\[CDATA\[.*?\]\]>),,s) {
    # we use &named; entities here because it's HTML
    $encoded_text .= encode_entities($1) . $2;
  }
  
  # we use numeric entities here because it's XML
  $encoded_text .= encode_entities_numeric($text);
  
  return $encoded_text;
};

sub as_string {
  my $self = shift;
  return $self->as_podcast_rss;
}

sub as_podcast_rss {
  my $self = shift;
  my $enc = $self->{encoding};
  my $output = qq[<?xml version="1.0" encoding="$enc"?>
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" 
     version="2.0">];
          
  $output .= $self->podcast_start_channel;
  
  for my $i (@{$self->{items}}) {
    $output .= $self->podcast_item($i);
  }

  $output .= $self->podcast_end_channel;
  return $output .= "\n</rss>\n";
}

sub podcast_start_channel {
  my $self = shift;
  my @fields = qw[ttl title description link language 
                  pubDate lastBuildDate creator 
                  webMaster copyright
                 ];
  my @image_fields  = qw[title url description link     
                         width height];
  my @itunes_fields = qw[subtitle author summary
                         image explicit]; # Thanks Rohan
  
  my $output = "<channel>\n";
  
  for my $f (@fields) {
    if (defined($self->{channel}->{$f})) {
      my $s = $self->encode($self->{channel}->{$f});
      $output .= "\t<$f>$s</$f>\n";
    }
  }
  
  my $seen_image = 0;
  for my $f (@image_fields) {
    if (defined($self->{image}->{$f})) {
      unless ($seen_image) {
        $output .= "\t<image>\n";
        $seen_image = 1;
      }
      my $s = $self->encode($self->{image}->{$f});
      $output .= "\t\t<$f>$s</$f>\n";
    }
  }
  
  if ($seen_image) {
    $output .= "\t</image>\n";
  }
  
  # Owner name/email not handled
  for my $f (@itunes_fields) {
    if (defined($self->{channel}->{itunes}->{$f})) {
      my $s=$self->encode($self->{channel}->{itunes}->{$f});
      $output .= "\t<itunes:$f>$s</itunes:$f>\n";
    }
  }
  # Rohan's sub-cat handling code
  # Expects an array: [category, sub-category]
  if (ref $self->{channel}->{itunes}->{category} eq 'ARRAY') {
    my $major = $self->encode(${$self->{channel}->{itunes}->{category}}[0]);
    my $minor = $self->encode(${$self->{channel}->{itunes}->{category}}[1]);
    $output .= qq[\t<itunes:category text="$major">\n];
    $output .= qq[\t\t<itunes:category text="$minor">\n];
    $output .= qq[\t\t</itunes:category>\n];
    $output .= qq[\t</itunes:category>\n];
  }

  return $output . "\n";
}
  
sub podcast_end_channel {
  return "</channel>\n";
}

sub podcast_item {
  my $self = shift;
  my $item = shift;
  
  my @fields = qw[title guid pubDate description link];
  my @itunes_fields = qw[author subtitle summary 
                         duration keywords explicit];
  
  my $output = "\t<item>\n";
  for my $f (@fields) {
                     
    if (defined($item->{$f})) {
      $s = $self->encode($item->{$f});
      my $perma = "";
      if ($f eq "guid") {
        $perma = qq[isPermaLink="false"];
      }
      $output .= "\t\t<$f$perma>$s</$f>\n";
    }
  }
  
  if (ref $item->{enclosure}) {
    $output .= "<enclosure";
    for my $f (qw[url length type]) {
      if (defined $item->{enclosure}->{$f}) {
        $output .= sprintf("$f=%s", $self->encode($item->{enclosure}->{$f}));
      }
    }
    $output .= "/>";
  }
  
  for my $f (@itunes_fields) {
    if (defined $item->{itunes}->{$f}) {
      $s = $self->encode($item->{itunes}->{$f});
      $output .= "\t\t<itunes:$f>$s</itunes:$f>\n";
    }
  }
  return $output .= "\t</item>\n";
}
1;

For an example of usage, please see the previous post cited above. Report bugs in the comments or through email. Thanks.

re: XML::RSS::Podcast
:: 9th Aug 2008 - 16:08
Hi John... Thanks so much for the module. It's helped me solve a long nagging problem of creating podcasts rss feeds without an editor (from a web site scraper). I found a few tweeks in your latest code that were necessary to get it working correctly: I had to revert to the old enclosure code and tweek the output to insert a space between attributes:

I supply guids and I had to something similar to insert a space before permalink="false":

Also, when I try to supply a image, I get a HASH ref in the rss feed in the tag. I tried all sorts of combinations but couldn't get it to work.

I posted a working version at https://thesmithden.com/Podcast.txt with comments where I modified it. The resulting feed is at https://thesmithden.com/NC.xml

Again, great job! Thanks, Dave

About this blog

The taskboy blog is a exploration of computer technology by Joe Johnston. Topics of posts include practical examples Perl, PHP, Python and Java as well as book reviews, industry insights and miscellaneous good stuff.

Current Status

INTR40

Posted: Thu Aug 26 10:13:27 +0000 2010