d0b923c1160e3eb153346e7284bbd92e2749593b
[libwww-opensearch-perl.git] / lib / WWW / OpenSearch / Response.pm
1 package WWW::OpenSearch::Response;
2
3 use strict;
4 use warnings;
5
6 use base qw( HTTP::Response Class::Accessor::Fast );
7
8 use XML::Feed;
9 use Data::Page;
10 use WWW::OpenSearch::Agent;
11 use WWW::OpenSearch::Request;
12
13 __PACKAGE__->mk_accessors( qw( feed pager ) );
14
15 =head1 NAME
16
17 WWW::OpenSearch::Response - Encapsulate a response received from
18 an A9 OpenSearch compatible engine
19
20 =head1 SYNOPSIS
21     
22     use WWW::OpenSearch;
23     
24     my $url = "http://bulkfeeds.net/opensearch.xml";
25     my $engine = WWW::OpenSearch->new($url);
26     
27     # Retrieve page 4 of search results for "iPod"
28     my $response = $engine->search("iPod",{ startPage => 4 });
29     for my $item (@{$response->feed->items}) {
30         print $item->{description};
31     }
32     
33     # Retrieve page 3 of results
34     $response = $response->previous_page;
35     
36     # Retrieve page 5 of results
37     $response = $response->next_page;
38     
39 =head1 DESCRIPTION
40
41 WWW::OpenSearch::Response is a module designed to encapsulate a
42 response received from an A9 OpenSearch compatible engine.
43 See http://opensearch.a9.com/spec/1.1/response/ for details.
44
45 =head1 CONSTRUCTOR
46
47 =head2 new( $response )
48
49 Constructs a new instance of WWW::OpenSearch::Response from the
50 WWWW::OpenSearch:Response returned by the search request.
51
52 =head1 METHODS
53
54 =head2 parse_response( )
55
56 Parses the content of the HTTP response using XML::Feed. If successful,
57 parse_feed( ) is also called.
58
59 =head2 parse_feed( )
60
61 Parses the XML::Feed originally parsed from the HTTP response content.
62 Sets the pager object appropriately.
63
64 =head2 previous_page( ) / next_page( )
65
66 Performs another search on the parent object, returning a
67 WWW::OpenSearch::Response instance containing the previous/next page
68 of results. If the current response includes a <link rel="previous/next"
69 href="..." /> tag, the page will simply be the parsed content of the URL
70 specified by the tag's href attribute. However, if the current response does not
71 include the appropriate link, a new query is constructed using the startPage
72 or startIndex query arguments.
73
74 =head2 _get_link( $type )
75
76 Gets the href attribute of the first link whose rel attribute
77 is equal to $type.
78
79 =head1 ACCESSORS
80
81 =head2 feed( )
82
83 =head2 pager( )
84
85 =head1 AUTHOR
86
87 =over 4
88
89 =item * Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>
90
91 =item * Brian Cassidy E<lt>bricas@cpan.orgE<gt>
92
93 =back
94
95 =head1 COPYRIGHT AND LICENSE
96
97 Copyright 2007 by Tatsuhiko Miyagawa and Brian Cassidy
98
99 This library is free software; you can redistribute it and/or modify
100 it under the same terms as Perl itself. 
101
102 =cut
103
104 sub new {
105     my $class    = shift;
106     my $response = shift;
107     
108     my $self = bless $response, $class;
109
110     return $self unless $self->is_success;
111     
112     $self->parse_response;
113     
114     return $self;
115 }
116
117 sub parse_response {
118     my $self = shift;
119
120     my $content = $self->content;
121     my $feed    = XML::Feed->parse( \$content );
122
123     return if XML::Feed->errstr;
124     $self->feed( $feed );
125     
126     $self->parse_feed;
127 }
128
129 sub parse_feed {
130     my $self  = shift;
131     my $pager = Data::Page->new;
132
133     my $feed   = $self->feed;
134     my $format = $feed->format;
135     my $ns     = $self->request->opensearch_url->ns;
136     
137     # TODO
138     # adapt these for any number of opensearch elements in
139     # the feed or in each entry
140     
141     if( my $atom = $feed->{ atom } ) {
142         my $total   = $atom->get( $ns, 'totalResults' );
143         my $perpage = $atom->get( $ns, 'itemsPerPage' );
144         my $start   = $atom->get( $ns, 'startIndex' );
145         
146         $pager->total_entries( $total );
147         $pager->entries_per_page( $perpage );
148         $pager->current_page( $start ? ( $start - 1 ) / $perpage + 1 : 0 )
149     }
150     elsif( my $rss = $feed->{ rss } ) {
151         if ( my $page = $rss->channel->{ $ns } ) {
152             $pager->total_entries(    $page->{ totalResults } );
153             $pager->entries_per_page( $page->{ itemsPerPage } );
154             my $start = $page->{ startIndex };
155             $pager->current_page( $start ? ( $start - 1 ) / $page->{ itemsPerPage } + 1 : 0 )
156         }
157     }    
158     $self->pager( $pager );
159 }
160
161 sub next_page {
162     my $self  = shift;
163     return $self->_get_page( 'next' );
164 }
165
166 sub previous_page {
167     my $self  = shift;
168     return $self->_get_page( 'previous' );
169 }
170
171 sub _get_page {
172     my( $self, $direction ) = @_;    
173     my $pager       = $self->pager;
174     my $pagermethod = "${direction}_page";
175     my $page        = $pager->$pagermethod;
176     return unless $page;
177
178     my $params;
179     my $osu = $self->request->opensearch_url;
180
181 #    this code is too fragile -- deparse depends on the order of query
182 #    params and the like. best just to use the last query params and
183 #    do the paging from there.
184 #
185 #    if( lc $osu->method ne 'post' ) { # force query build on POST
186 #        my $link = $self->_get_link( $direction );
187 #        if( $link ) {
188 #            $params = $osu->deparse( $link );
189 #        }
190 #    }
191
192     # rebuild the query
193     if( !$params ) {
194         $params = $self->request->opensearch_params;
195
196         # handle paging via a page #
197         $params->{ startPage } = $page;
198
199         # handle paging via an index
200         if( exists $params->{ startIndex } ) {
201             # start index is pre-existing
202             if( $params->{ startIndex } ) {
203                 if( $direction eq 'previous' ) {
204                     $params->{ startIndex } -= $pager->entries_per_page
205                 }
206                 else {
207                     $params->{ startIndex } += $pager->entries_per_page;
208                 }
209             }
210             # start index did not exist previously
211             else {
212                 if( $direction eq 'previous' ) {
213                     $params->{ startIndex } = 1
214                 }
215                 else {
216                     $params->{ startIndex } = $pager->entries_per_page + 1;
217                 }
218
219             }
220         }
221     }
222
223     my $agent = WWW::OpenSearch::Agent->new;
224     return $agent->search( WWW::OpenSearch::Request->new(
225         $osu, $params
226     ) );
227 }
228
229 sub _get_link {
230     my $self = shift;
231     my $type = shift;
232     my $feed = $self->feed->{ atom };
233     
234     return unless $feed;
235     
236     for( $feed->link ) {
237         return $_->href if $_->rel eq $type;
238     }
239
240     return;
241 }
242
243 1;