import of 0.08
[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 URI;
10 use Data::Page;
11
12 __PACKAGE__->mk_accessors( qw( feed pager parent ) );
13
14 =head1 NAME
15
16 WWW::OpenSearch::Response - Encapsulate a response received from
17 an A9 OpenSearch compatible engine
18
19 =head1 SYNOPSIS
20     
21     use WWW::OpenSearch;
22     
23     my $url = "http://bulkfeeds.net/opensearch.xml";
24     my $engine = WWW::OpenSearch->new($url);
25     
26     # Retrieve page 4 of search results for "iPod"
27     my $response = $engine->search("iPod",{ startPage => 4 });
28     for my $item (@{$response->feed->items}) {
29         print $item->{description};
30     }
31     
32     # Retrieve page 3 of results
33     $response = $response->previous_page;
34     
35     # Retrieve page 5 of results
36     $response = $response->next_page;
37     
38 =head1 DESCRIPTION
39
40 WWW::OpenSearch::Response is a module designed to encapsulate a
41 response received from an A9 OpenSearch compatible engine.
42 See http://opensearch.a9.com/spec/1.1/response/ for details.
43
44 =head1 CONSTRUCTOR
45
46 =head2 new( $parent, $response )
47
48 Constructs a new instance of WWW::OpenSearch::Response. Arguments
49 include the WWW::OpenSearch object which initiated the search (parent)
50 and the HTTP::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 =head2 parent( )
86
87 =head1 AUTHOR
88
89 =over 4
90
91 =item * Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>
92
93 =item * Brian Cassidy E<lt>bricas@cpan.orgE<gt>
94
95 =back
96
97 =head1 COPYRIGHT AND LICENSE
98
99 Copyright 2006 by Tatsuhiko Miyagawa and Brian Cassidy
100
101 This library is free software; you can redistribute it and/or modify
102 it under the same terms as Perl itself. 
103
104 =cut
105
106 sub new {
107     my $class    = shift;
108     my $parent   = shift;
109     my $response = shift;
110     
111     my $self = bless $response, $class;
112
113     $self->parent( $parent );
114     return $self unless $self->is_success;
115     
116     $self->parse_response;
117     
118     return $self;
119 }
120
121 sub parse_response {
122     my $self = shift;
123
124     my $content = $self->content;
125     my $feed    = XML::Feed->parse( \$content );
126
127     return if XML::Feed->errstr;
128     $self->feed( $feed );
129     
130     $self->parse_feed;
131 }
132
133 sub parse_feed {
134     my $self  = shift;
135     my $pager = Data::Page->new;
136
137     my $feed   = $self->feed;
138     my $format = $feed->format;
139     my $ns     = $self->parent->description->ns;
140     
141     # TODO
142     # adapt these for any number of opensearch elements in
143     # the feed or in each entry
144     
145     if( my $atom = $feed->{ atom } ) {
146         my $total   = $atom->get( $ns, 'totalResults' );
147         my $perpage = $atom->get( $ns, 'itemsPerPage' );
148         my $start   = $atom->get( $ns, 'startIndex' );
149         
150         $pager->total_entries( $total );
151         $pager->entries_per_page( $perpage );
152         $pager->current_page( $start ? ( $start - 1 ) / $perpage + 1 : 0 )
153     }
154     elsif( my $rss = $feed->{ rss } ) {
155         if ( my $page = $rss->channel->{ $ns } ) {
156             $pager->total_entries(    $page->{ totalResults } );
157             $pager->entries_per_page( $page->{ itemsPerPage } );
158             my $start = $page->{ startIndex };
159             $pager->current_page( $start ? ( $start - 1 ) / $page->{ itemsPerPage } + 1 : 0 )
160         }
161     }    
162     $self->pager( $pager );
163 }
164
165 sub next_page {
166     my $self  = shift;
167     return $self->_get_page( 'next' );
168 }
169
170 sub previous_page {
171     my $self  = shift;
172     return $self->_get_page( 'previous' );
173 }
174
175 sub _get_page {
176     my( $self, $direction ) = @_;    
177     my $pager       = $self->pager;
178     my $pagermethod = "${direction}_page";
179     my $page        = $pager->$pagermethod;
180     return unless $page;
181     
182     my $request = $self->request;
183     my $method  = lc $request->method;
184
185     if( $method ne 'post' ) { # force query build on POST
186         my $link = $self->_get_link( $direction );
187         return $self->parent->do_search( $link, $method ) if $link;
188     }
189     
190     my $template = $self->parent->description->get_best_url;
191     my( $param, $query );
192     if( $method eq 'post' ) {
193         my $uri = URI->new( 'http://foo.com/?' . $request->content );
194         $query = { $uri->query_form };
195     }
196     else {
197         $query = { $self->request->uri->query_form };
198     }
199
200     if( $param = $template->macros->{ startPage } ) {
201         $query->{ $param } = $pager->$pagermethod
202     }
203     elsif( $param = $template->macros->{ startIndex } ) {
204         if( $query->{ $param } ) {
205             $query->{ $param } = $direction eq 'previous'
206                 ? $query->{ $param } -= $pager->entries_per_page
207                 : $query->{ $param } += $pager->entries_per_page;
208         }
209         else {
210             $query->{ $param } = $direction eq 'previous'
211                 ? 1
212                 : $pager->entries_per_page + 1;
213         }
214     }
215
216     return $self->parent->do_search( $template->prepare_query( $query ), $method );
217 }
218
219 sub _get_link {
220     my $self = shift;
221     my $type = shift;
222     my $feed = $self->feed->{ atom };
223     
224     return unless $feed;
225     
226     for( $feed->link ) {
227         return $_->get( 'href' ) if $_->get( 'rel' ) eq $type;
228     }
229
230     return;
231 }
232
233 1;