Address a few RSS and Atom feed validation problems:
authordbs <dbs@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Sun, 29 Jun 2008 07:07:00 +0000 (07:07 +0000)
committerdbs <dbs@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Sun, 29 Jun 2008 07:07:00 +0000 (07:07 +0000)
  * Make OPAC URLs absolute (needs refinement to respect locale/skin)
  * Push datetime creation down to the feed and item level
  * Atom:
    * Merge summary fields into a single element
  * RSS2:
    * Generate a channel/description field
    * Merge item/description fields into a single element
    * Use RFC-822 dates (adds DateTime::Format::Mail as a prerequisite)
    * Set guid isPermaLink attribute false as we're using tag: URIs
    * Place alternate link elements in the xhtml namespace
    * Generate a link element with no attributes

git-svn-id: svn://svn.open-ils.org/ILS/trunk@9950 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/src/extras/Makefile.install
Open-ILS/src/perlmods/OpenILS/WWW/SuperCat.pm
Open-ILS/src/perlmods/OpenILS/WWW/SuperCat/Feed.pm
Open-ILS/xsl/MARC21slim2ATOM.xsl
Open-ILS/xsl/MARC21slim2RSS2.xsl

index 26dd7e6..980f7e0 100644 (file)
@@ -101,6 +101,7 @@ DEBS =  \
     libclass-dbi-abstractsearch-perl\
     libtemplate-perl\
     libtext-aspell-perl\
+    libdatetime-format-mail-perl\
     libdatetime-timezone-perl\
     libdatetime-perl\
     libunix-syslog-perl\
index a348336..f3bf99d 100644 (file)
@@ -412,7 +412,7 @@ sub unapi {
 
                $feed->root($root);
                $feed->creator($host);
-               $feed->update_ts(gmtime_ISO8601());
+               $feed->update_ts();
                $feed->link( unapi => $base) if ($flesh_feed);
 
                print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
@@ -656,7 +656,9 @@ sub supercat {
 
                $feed->root($root);
                $feed->creator($host);
-               $feed->update_ts(gmtime_ISO8601());
+
+               $feed->update_ts();
+
                $feed->link( unapi => $base) if ($flesh_feed);
 
                print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
@@ -745,7 +747,7 @@ sub bookbag_feed {
 
        $feed->title("Items in Book Bag [".$bucket->name."]");
        $feed->creator($host);
-       $feed->update_ts(gmtime_ISO8601());
+       $feed->update_ts();
 
        $feed->link(alternate => $base . "/rss2-full/$id" => 'application/rss+xml');
        $feed->link(atom => $base . "/atom-full/$id" => 'application/atom+xml');
@@ -754,7 +756,7 @@ sub bookbag_feed {
 
        $feed->link(
                OPAC =>
-               '/opac/en-US/skin/default/xml/rresult.xml?rt=list&' .
+               $host . '/opac/en-US/skin/default/xml/rresult.xml?rt=list&' .
                        join('&', map { 'rl=' . $_->target_biblio_record_entry } @{$bucket->items} ),
                'text/html'
        );
@@ -814,7 +816,7 @@ sub changes_feed {
        }
 
        $feed->creator($host);
-       $feed->update_ts(gmtime_ISO8601());
+       $feed->update_ts();
 
        $feed->link(alternate => $base . "/rss2-full/$rtype/$axis/$limit/$date" => 'application/rss+xml');
        $feed->link(atom => $base . "/atom-full/$rtype/$axis/$limit/$date" => 'application/atom+xml');
@@ -823,7 +825,7 @@ sub changes_feed {
 
        $feed->link(
                OPAC =>
-               '/opac/en-US/skin/default/xml/rresult.xml?rt=list&' .
+               $host . '/opac/en-US/skin/default/xml/rresult.xml?rt=list&' .
                        join('&', map { 'rl=' . $_} @$list ),
                'text/html'
        );
@@ -1069,7 +1071,7 @@ sub opensearch_feed {
        $feed->title("Search results for [$terms] at ".$org_unit->[0]->name);
 
        $feed->creator($host);
-       $feed->update_ts(gmtime_ISO8601());
+       $feed->update_ts();
 
        $feed->_create_node(
                $feed->{item_xpath},
index ba9d3cc..bce56dd 100644 (file)
@@ -6,6 +6,9 @@ use XML::LibXML;
 use XML::LibXSLT;
 use OpenSRF::Utils::SettingsClient;
 use CGI;
+use DateTime;
+use DateTime::Format::Mail;
+
 
 sub exists {
        my $class = shift;
@@ -225,6 +228,7 @@ sub creator {};
 
 package OpenILS::WWW::SuperCat::Feed::atom;
 use base 'OpenILS::WWW::SuperCat::Feed';
+use OpenSRF::Utils qw/:datetime/;
 
 sub new {
        my $class = shift;
@@ -244,7 +248,8 @@ sub title {
 
 sub update_ts {
        my $self = shift;
-       my $text = shift;
+       # ATOM demands RFC-3339 compliant datetime formats
+       my $text = shift || gmtime_ISO8601();
        $self->_create_node($self->{item_xpath},'http://www.w3.org/2005/Atom','updated', $text);
 }
 
@@ -317,11 +322,15 @@ sub title {
        my $self = shift;
        my $text = shift;
        $self->_create_node('/rss/channel',undef,'title', $text);
+       # RSS2 demands a /channel/description element; just dupe title until we give
+       # users the ability to provide a description for their bookbags
+       $self->_create_node('/rss/channel',undef,'description', $text);
 }
 
 sub update_ts {
        my $self = shift;
-       my $text = shift;
+       # RSS2 demands RFC-822 compliant datetime formats
+       my $text = shift || DateTime::Format::Mail->format_datetime(DateTime->now());
        $self->_create_node($self->{item_xpath},undef,'lastBuildDate', $text);
 }
 
@@ -337,17 +346,27 @@ sub link {
        my $id = shift;
        my $mime = shift || "application/x-$type+xml";
 
-       $type = 'self' if ($type eq 'rss2');
-
-       $self->_create_node(
-               $self->{item_xpath},
-               undef,
-               'link',
-               $id,
-               { rel => $type,
-                 type => $mime,
-               }
-       );
+       if ($type eq 'rss2' or $type eq 'alternate') {
+               # Just link to ourself using standard RSS2 link element
+               $self->_create_node(
+                       $self->{item_xpath},
+                       undef,
+                       'link',
+                       $id,
+                       undef
+               );
+       } else {
+               # Alternate link: use XHTML link element
+               $self->_create_node(
+                       $self->{item_xpath},
+                       'http://www.w3.org/1999/xhtml',
+                       'xhtml:link',
+                       $id,
+                       { rel => $type,
+                         type => $mime,
+                       }
+               );
+       }
 }
 
 sub id {
@@ -372,11 +391,33 @@ sub new {
 
 sub update_ts {
        my $self = shift;
+       # RSS2 demands RFC-822 compliant datetime formats
        my $text = shift;
+       if (!$text) {
+               # No date passed in, default to now
+               $text = DateTime::Format::Mail->format_datetime(DateTime->now());
+       } elsif ($text =~ m/^\s*(\d{4})\.?\s*$/o) {
+               # Publication date is just a year, convert accordingly
+               my $year = DateTime->new(year=>$1);
+               $text = DateTime::Format::Mail->format_datetime($year);
+       }
        $self->_create_node($self->{item_xpath},undef,'pubDate', $text);
 }
 
+sub id {
+       my $self = shift;
+       my $id = shift;
 
+       $self->_create_node(
+               $self->{item_xpath},
+               undef,
+               'guid',
+               $id,
+               {
+                       isPermaLink=>"false"
+               }
+       );
+}
 
 #----------------------------------------------------------
 
index e706763..dd0c7e1 100644 (file)
@@ -24,6 +24,7 @@
                                </id>
                        </xsl:for-each>
 
+                       <!-- Spec wants RFC 3339 format - fix it outside of XSL? -->
                        <xsl:for-each select="marc:controlfield[@tag=005]">
                                <updated>
                                        <xsl:value-of select="."/>
                                </rights>
                        </xsl:for-each>
 
+                       <!-- Spec wants RFC 3339 format - fix it outside of XSL? -->
                        <xsl:for-each select="marc:datafield[@tag=260]/marc:subfield[@code='c']">
                                <published>
                                        <xsl:value-of select="."/>
                                </published>                            
                        </xsl:for-each>
 
-                       <xsl:for-each select="marc:datafield[500&lt;@tag][@tag&lt;=599][not(@tag=506 or @tag=530 or @tag=540 or @tag=546)]">
-                               <summary>
-                                       <xsl:value-of select="marc:subfield[@code='a']"/>
-                               </summary>
-                       </xsl:for-each>
+                       <!--
+                       Spec wants zero or one summary elements per item; best option
+                       would be to test for one of these elements and only create
+                       if one exists, but for now we simply merge all candidates
+                       -->
+                       <summary>
+                               <xsl:for-each select="marc:datafield[500&lt;@tag][@tag&lt;=599][not(@tag=506 or @tag=530 or @tag=540 or @tag=546)]">
+                                               <xsl:value-of select="marc:subfield[@code='a']"/>
+                               </xsl:for-each>
+                       </summary>
 
                        <xsl:for-each select="marc:datafield[@tag=600 or @tag=610 or @tag=611 or @tag=630 or @tag=650 or @tag=653]">
                                <category>
index 8461557..bb8f852 100644 (file)
@@ -78,6 +78,7 @@
                                </dc:publisher>
                        </xsl:for-each>
 
+                       <!-- this is supposed to be RFC-822 compliant -->
                        <xsl:for-each select="marc:datafield[@tag=260]/marc:subfield[@code='c']">
                                <pubDate>
                                        <xsl:value-of select="."/>
                                </dc:format>
                        </xsl:for-each>
 
-                       <xsl:for-each select="marc:datafield[500&lt;@tag][@tag&lt;=599][not(@tag=506 or @tag=530 or @tag=540 or @tag=546)]">
-                               <description>
-                                       <xsl:value-of select="marc:subfield[@code='a']"/>
-                               </description>
-                       </xsl:for-each>
+                       <!-- specification only allows one description element per item -->
+                       <description>
+                               <xsl:for-each select="marc:datafield[500&lt;@tag][@tag&lt;=599][not(@tag=506 or @tag=530 or @tag=540 or @tag=546)]">
+                                               <xsl:value-of select="marc:subfield[@code='a']"/>
+                               </xsl:for-each>
+                       </description>
 
                        <xsl:for-each select="marc:datafield[@tag=600]">
                                <dc:subject>