The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package WWW::Scraper::ISBN;

use strict;
use warnings;

our $VERSION = '1.01';

#----------------------------------------------------------------------------
# Library Modules

use Carp;
use WWW::Scraper::ISBN::Record;
use WWW::Scraper::ISBN::Driver;

eval "use Business::ISBN";
my $business_isbn_loaded = ! $@;

#----------------------------------------------------------------------------
# Public API

# Preloaded methods go here.
sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;

    my $self = {
        DRIVERS => []
    };

    bless ($self, $class);
    return $self;
}

sub drivers {
    my $self = shift;
    while ($_ = shift) {  push @{$self->{DRIVERS}}, $_; }
    foreach my $driver ( @{ $self->{DRIVERS} }) {
        require "WWW/Scraper/ISBN/".$driver."_Driver.pm";
    }
    return @{ $self->{DRIVERS} };
}

sub reset_drivers {
    my $self = shift;
    $self->{DRIVERS} = [];
    return @{ $self->{DRIVERS} };
}

sub search {
    my ($self,$isbn) = @_;

    croak("Invalid ISBN specified [].\n") unless($isbn);        

    if($business_isbn_loaded) {
        # Business::ISBN has strong validation algorithms
        my $isbn_object = Business::ISBN->new($isbn);
        croak("Invalid ISBN specified [$isbn].\n") unless($isbn_object && $isbn_object->is_valid);
    } else {
        # our fallback just validates it looks like an ISBN
        my $isbn_object = WWW::Scraper::ISBN::Driver->new();
        croak("Invalid ISBN specified [$isbn].\n") unless($isbn_object && $isbn_object->is_valid($isbn));        
    }

    croak("No search drivers specified.\n")
        if( $self->drivers == 0 );

    my $record = WWW::Scraper::ISBN::Record->new();
    $record->isbn($isbn);
    foreach my $driver_name (@{ $self->{DRIVERS} }) {
        my $driver = "WWW::Scraper::ISBN::${driver_name}_Driver"->new();
        $driver->search($record->isbn);
        if ($driver->found) {
            $record->found("1");
            $record->found_in("$driver_name");
            $record->book($driver->book);
            last;
        }

        $record->error($record->error.$driver->error)
            if ($driver->error);
    }

    return $record;
}

1;

__END__

=head1 NAME

WWW::Scraper::ISBN - Retrieve information about books from online sources.

=head1 SYNOPSIS

  use WWW::Scraper::ISBN;

  my $scraper = WWW::Scraper::ISBN->new();
  $scraper->drivers("Driver1", "Driver2");
  
  my $isbn = "123456789X";
  my $record = $scraper->search($isbn);
  if($record->found) {
    print "Book ".$record->isbn." found by driver ".$record->found_in."\n";
    my $book = $record->book;

    # do stuff with book hash
    
    print $book->{'title'};
    print $book->{'author'};
    
    # etc
  
  } else {
    print $record->error;
  }

=head1 REQUIRES

Requires the following modules be installed:

=over 4

=item L<WWW::Scraper::ISBN::Record>

=item L<Carp>

=back

=head1 DESCRIPTION

The WWW::Scraper::ISBN class was built as a way to retrieve information on 
books from multiple sources easily. It utilizes at least one driver implemented
as a subclass of L<WWW::Scraper::ISBN::Driver>, each of which is designed to 
scrape from a single source.  Because we found that different sources had 
different information available on different books, we designed a basic 
interface that could be implemented in whatever ways necessary to retrieve 
the desired information.

=head1 METHODS

=over 4

=item C<new()>

Class constructor.  Returns a reference to an empty scraper object.  No 
drivers by default

=item C<drivers() or drivers($DRIVER1, $DRIVER2)>

  foreach my $driver ( $scraper->drivers() ) { ... }

  $scraper->drivers("DRIVER1", "DRIVER2");

Accessor/Mutator method which handles the drivers that this instance of the 
WWW::Scraper::ISBN class should utilize.  The appropriate driver module must be
installed (e.g. WWW::Scraper::ISBN::DRIVER1_Driver for "DRIVER1", etc.).  The 
order of arguments determines the order in which the drivers will be searched.

When this method is called, it loads the specified drivers using 'require'.

Must be set before C<search()> method is called.

=item C<reset_drivers>

  $scraper->reset_drivers;

Sets the list of drivers to an empty array.  Will disable search feature until
a new driver is specified.

=item C<search($isbn)>

  my $record = $scraper->search("123456789X");

Searches for information on the given ISBN number.  It goes through the drivers
in the order they are specified, stopping when the book is found or all drivers
are exhausted.  It returns a L<WWW::Scraper::ISBN::Record> object, which will 
have its C<found()> field set according to whether or not the search was 
successful.

If you have L<Business::ISBN> installed, the method will attempt to validate 
the given isbn.

=back

=head1 CODE EXAMPLE

  use WWW::Scraper::ISBN;

  # instantiate the object
  my $scraper = WWW::Scraper::ISBN->new();

  # load the drivers.  requires that 
  # WWW::Scraper::ISBN::LOC_Driver and
  # WWW::Scraper::ISBN::ISBNnu_Driver 
  # be installed
  $scraper->drivers("LOC", "ISBNnu"); 

  @isbns = [ "123456789X", "132457689X", "987654321X" ];

  foreach my $num (@isbns) {
    $scraper->isbn($num);
    $scraper->search($scraper->isbn);
    if ($scraper->found) {
      my $b = $scraper->book;
      print "Title: ".$b->{'title'}."\n";
      print "Author: ".$b->{'author'}."\n\n";
    } else {
      print "Book: ".$scraper->isbn." not found.\n\n";
    }
  }

=head1 SEE ALSO

=over 4

=item L<WWW::Scraper::ISBN::Driver>

=item L<WWW::Scraper::ISBN::Record>

=back

=head1 AUTHOR

  2004-2013 Andy Schamp, E<lt>andy@schamp.netE<gt>
  2013-2014 Barbie, E<lt>barbie@cpan.orgE<gt>

=head1 COPYRIGHT AND LICENSE

  Copyright 2004-2013 by Andy Schamp
  Copyright 2013-2014 by Barbie

  This distribution is free software; you can redistribute it and/or
  modify it under the Artistic Licence v2.

=cut