Imported Upstream version 0.0602
[libwww-opensearch-perl.git] / lib / WWW / OpenSearch / Response.pm
diff --git a/lib/WWW/OpenSearch/Response.pm b/lib/WWW/OpenSearch/Response.pm
new file mode 100644 (file)
index 0000000..1cb653b
--- /dev/null
@@ -0,0 +1,233 @@
+package WWW::OpenSearch::Response;\r
+\r
+use strict;\r
+use warnings;\r
+\r
+use base qw( HTTP::Response Class::Accessor::Fast );\r
+\r
+use XML::Feed;
+use URI;\r
+use Data::Page;\r
+\r
+__PACKAGE__->mk_accessors( qw( feed pager parent ) );\r
+\r
+=head1 NAME\r
+\r
+WWW::OpenSearch::Response - Encapsulate a response received from\r
+an A9 OpenSearch compatible engine\r
+\r
+=head1 SYNOPSIS\r
+    \r
+    use WWW::OpenSearch;\r
+    \r
+    my $url = "http://bulkfeeds.net/opensearch.xml";\r
+    my $engine = WWW::OpenSearch->new($url);\r
+    \r
+    # Retrieve page 4 of search results for "iPod"\r
+    my $response = $engine->search("iPod",{ startPage => 4 });\r
+    for my $item (@{$response->feed->items}) {\r
+        print $item->{description};\r
+    }\r
+    \r
+    # Retrieve page 3 of results\r
+    $response = $response->previous_page;\r
+    \r
+    # Retrieve page 5 of results\r
+    $response = $response->next_page;\r
+    \r
+=head1 DESCRIPTION\r
+\r
+WWW::OpenSearch::Response is a module designed to encapsulate a\r
+response received from an A9 OpenSearch compatible engine.\r
+See http://opensearch.a9.com/spec/1.1/response/ for details.\r
+\r
+=head1 CONSTRUCTOR\r
+\r
+=head2 new( $parent, $response )\r
+\r
+Constructs a new instance of WWW::OpenSearch::Response. Arguments\r
+include the WWW::OpenSearch object which initiated the search (parent)\r
+and the HTTP::Response returned by the search request.\r
+\r
+=head1 METHODS\r
+\r
+=head2 parse_response( )\r
+\r
+Parses the content of the HTTP response using XML::Feed. If successful,\r
+parse_feed( ) is also called.\r
+\r
+=head2 parse_feed( )\r
+\r
+Parses the XML::Feed originally parsed from the HTTP response content.\r
+Sets the pager object appropriately.\r
+\r
+=head2 previous_page( ) / next_page( )\r
+\r
+Performs another search on the parent object, returning a\r
+WWW::OpenSearch::Response instance containing the previous/next page\r
+of results. If the current response includes a <link rel="previous/next"\r
+href="..." /> tag, the page will simply be the parsed content of the URL\r
+specified by the tag's href attribute. However, if the current response does not\r
+include the appropriate link, a new query is constructed using the startPage\r
+or startIndex query arguments.\r
+\r
+=head2 _get_link( $type )\r
+\r
+Gets the href attribute of the first link whose rel attribute\r
+is equal to $type.\r
+\r
+=head1 ACCESSORS\r
+\r
+=head2 feed( )\r
+\r
+=head2 pager( )\r
+\r
+=head2 parent( )\r
+\r
+=head1 AUTHOR\r
+\r
+=over 4\r
+\r
+=item * Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>\r
+\r
+=item * Brian Cassidy E<lt>bricas@cpan.orgE<gt>\r
+\r
+=back\r
+\r
+=head1 COPYRIGHT AND LICENSE\r
+\r
+Copyright 2006 by Tatsuhiko Miyagawa and Brian Cassidy\r
+\r
+This library is free software; you can redistribute it and/or modify\r
+it under the same terms as Perl itself. \r
+\r
+=cut\r
+\r
+sub new {\r
+    my $class    = shift;\r
+    my $parent   = shift;\r
+    my $response = shift;\r
+    \r
+    my $self = bless $response, $class;\r
+\r
+    $self->parent( $parent );\r
+    return $self unless $self->is_success;\r
+    \r
+    $self->parse_response;\r
+    \r
+    return $self;\r
+}\r
+\r
+sub parse_response {\r
+    my $self = shift;\r
+\r
+    my $content = $self->content;\r
+    my $feed    = XML::Feed->parse( \$content );\r
+\r
+    return if XML::Feed->errstr;\r
+    $self->feed( $feed );\r
+    \r
+    $self->parse_feed;\r
+}\r
+\r
+sub parse_feed {\r
+    my $self  = shift;\r
+    my $pager = Data::Page->new;\r
+\r
+    my $feed   = $self->feed;\r
+    my $format = $feed->format;\r
+    my $ns     = $self->parent->description->ns;\r
+    \r
+    # TODO\r
+    # adapt these for any number of opensearch elements in\r
+    # the feed or in each entry\r
+    \r
+    if( my $atom = $feed->{ atom } ) {\r
+        my $total   = $atom->get( $ns, 'totalResults' );\r
+        my $perpage = $atom->get( $ns, 'itemsPerPage' );\r
+        my $start   = $atom->get( $ns, 'startIndex' );\r
+        \r
+        $pager->total_entries( $total );\r
+        $pager->entries_per_page( $perpage );\r
+        $pager->current_page( $start ? ( $start - 1 ) / $perpage + 1 : 0 )\r
+    }\r
+    elsif( my $rss = $feed->{ rss } ) {\r
+       if ( my $page = $rss->channel->{ $ns } ) {\r
+            $pager->total_entries(    $page->{ totalResults } );\r
+            $pager->entries_per_page( $page->{ itemsPerPage } );\r
+            my $start = $page->{ startIndex };\r
+            $pager->current_page( $start ? ( $start - 1 ) / $page->{ itemsPerPage } + 1 : 0 )\r
+        }\r
+    }    \r
+    $self->pager( $pager );\r
+}\r
+\r
+sub next_page {\r
+    my $self  = shift;\r
+    return $self->_get_page( 'next' );\r
+}\r
+\r
+sub previous_page {\r
+    my $self  = shift;
+    return $self->_get_page( 'previous' );\r
+}
+
+sub _get_page {
+    my( $self, $direction ) = @_;    
+    my $pager       = $self->pager;
+    my $pagermethod = "${direction}_page";\r
+    my $page        = $pager->$pagermethod;\r
+    return unless $page;\r
+    
+    my $request = $self->request;
+    my $method  = lc $request->method;
+
+    if( $method ne 'post' ) { # force query build on POST\r
+        my $link = $self->_get_link( $direction );\r
+        return $self->parent->do_search( $link, $method ) if $link;
+    }\r
+    \r
+    my $template = $self->parent->description->get_best_url;
+    my( $param, $query );
+    if( $method eq 'post' ) {
+        my $uri = URI->new( 'http://foo.com/?' . $request->content );
+        $query = { $uri->query_form };
+    }
+    else {
+        $query = { $self->request->uri->query_form };
+    }\r
+\r
+    if( $param = $template->macros->{ startPage } ) {\r
+        $query->{ $param } = $pager->$pagermethod\r
+    }\r
+    elsif( $param = $template->macros->{ startIndex } ) {
+        if( $query->{ $param } ) {
+            $query->{ $param } = $direction eq 'previous'
+                ? $query->{ $param } -= $pager->entries_per_page
+                : $query->{ $param } += $pager->entries_per_page;
+        }
+        else {
+            $query->{ $param } = $direction eq 'previous'
+                ? 1
+                : $pager->entries_per_page + 1;
+        }\r
+    }\r
+\r
+    return $self->parent->do_search( $template->prepare_query( $query ), $method );\r
+}\r
+\r
+sub _get_link {\r
+    my $self = shift;\r
+    my $type = shift;\r
+    my $feed = $self->feed->{ atom };\r
+    \r
+    return unless $feed;\r
+    \r
+    for( $feed->link ) {\r
+        return $_->get( 'href' ) if $_->get( 'rel' ) eq $type;\r
+    }
+
+    return;\r
+}\r
+\r
+1;\r