package WWW::OpenSearch::Response;
use strict;
use warnings;
use base qw( HTTP::Response Class::Accessor::Fast );
use XML::Feed;
use Data::Page;
use WWW::OpenSearch::Agent;
use WWW::OpenSearch::Request;
__PACKAGE__->mk_accessors( qw( feed pager ) );
=head1 NAME
WWW::OpenSearch::Response - Encapsulate a response received from
an A9 OpenSearch compatible engine
=head1 SYNOPSIS
use WWW::OpenSearch;
my $url = "http://bulkfeeds.net/opensearch.xml";
my $engine = WWW::OpenSearch->new($url);
# Retrieve page 4 of search results for "iPod"
my $response = $engine->search("iPod",{ startPage => 4 });
for my $item (@{$response->feed->items}) {
print $item->{description};
}
# Retrieve page 3 of results
$response = $response->previous_page;
# Retrieve page 5 of results
$response = $response->next_page;
=head1 DESCRIPTION
WWW::OpenSearch::Response is a module designed to encapsulate a
response received from an A9 OpenSearch compatible engine.
See http://opensearch.a9.com/spec/1.1/response/ for details.
=head1 CONSTRUCTOR
=head2 new( $response )
Constructs a new instance of WWW::OpenSearch::Response from the
WWWW::OpenSearch:Response returned by the search request.
=head1 METHODS
=head2 parse_response( )
Parses the content of the HTTP response using XML::Feed. If successful,
parse_feed( ) is also called.
=head2 parse_feed( )
Parses the XML::Feed originally parsed from the HTTP response content.
Sets the pager object appropriately.
=head2 previous_page( ) / next_page( )
Performs another search on the parent object, returning a
WWW::OpenSearch::Response instance containing the previous/next page
of results. If the current response includes a <link rel="previous/next"
href="..." /> tag, the page will simply be the parsed content of the URL
specified by the tag's href attribute. However, if the current response does not
include the appropriate link, a new query is constructed using the startPage
or startIndex query arguments.
=head2 _get_link( $type )
Gets the href attribute of the first link whose rel attribute
is equal to $type.
=head1 ACCESSORS
=head2 feed( )
=head2 pager( )
=head1 AUTHOR
=over 4
=item * Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>
=item * Brian Cassidy E<lt>bricas@cpan.orgE<gt>
=back
=head1 COPYRIGHT AND LICENSE
Copyright 2005-2010 by Tatsuhiko Miyagawa and Brian Cassidy
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
sub new {
my $class = shift;
my $response = shift;
my $self = bless $response, $class;
return $self unless $self->is_success;
$self->parse_response;
return $self;
}
sub parse_response {
my $self = shift;
my $content = $self->content;
my $feed = XML::Feed->parse( \$content );
return if XML::Feed->errstr;
$self->feed( $feed );
$self->parse_feed;
}
sub parse_feed {
my $self = shift;
my $pager = Data::Page->new;
my $feed = $self->feed;
my $format = $feed->format;
my $ns = $self->request->opensearch_url->ns;
# TODO
# adapt these for any number of opensearch elements in
# the feed or in each entry
if ( my $atom = $feed->{ atom } ) {
my $total = $atom->get( $ns, 'totalResults' );
my $perpage = $atom->get( $ns, 'itemsPerPage' );
my $start = $atom->get( $ns, 'startIndex' );
$pager->total_entries( $total );
$pager->entries_per_page( $perpage );
$pager->current_page( $start ? ( $start - 1 ) / $perpage + 1 : 0 );
}
elsif ( my $rss = $feed->{ rss } ) {
if ( my $page = $rss->channel->{ $ns } ) {
$pager->total_entries( $page->{ totalResults } );
$pager->entries_per_page( $page->{ itemsPerPage } );
my $start = $page->{ startIndex };
$pager->current_page(
$start ? ( $start - 1 ) / $page->{ itemsPerPage } + 1 : 0 );
}
}
$self->pager( $pager );
}
sub next_page {
my $self = shift;
return $self->_get_page( 'next' );
}
sub previous_page {
my $self = shift;
return $self->_get_page( 'previous' );
}
sub _get_page {
my ( $self, $direction ) = @_;
my $pager = $self->pager;
my $pagermethod = "${direction}_page";
my $page = $pager->$pagermethod;
return unless $page;
my $params;
my $osu = $self->request->opensearch_url;
# this code is too fragile -- deparse depends on the order of query
# params and the like. best just to use the last query params and
# do the paging from there.
#
# if( lc $osu->method ne 'post' ) { # force query build on POST
# my $link = $self->_get_link( $direction );
# if( $link ) {
# $params = $osu->deparse( $link );
# }
# }
# rebuild the query
if ( !$params ) {
$params = $self->request->opensearch_params;
# handle paging via a page #
$params->{ startPage } = $page;
# handle paging via an index
if ( exists $params->{ startIndex } ) {
# start index is pre-existing
if ( $params->{ startIndex } ) {
if ( $direction eq 'previous' ) {
$params->{ startIndex } -= $pager->entries_per_page;
}
else {
$params->{ startIndex } += $pager->entries_per_page;
}
}
# start index did not exist previously
else {
if ( $direction eq 'previous' ) {
$params->{ startIndex } = 1;
}
else {
$params->{ startIndex } = $pager->entries_per_page + 1;
}
}
}
}
my $agent = WWW::OpenSearch::Agent->new;
return $agent->search( WWW::OpenSearch::Request->new( $osu, $params ) );
}
sub _get_link {
my $self = shift;
my $type = shift;
my $feed = $self->feed->{ atom };
return unless $feed;
for ( $feed->link ) {
return $_->href if $_->rel eq $type;
}
return;
}
1;