File Coverage

blib/lib/Genealogy/ChroniclingAmerica.pm
Criterion Covered Total %
statement 79 95 83.1
branch 31 46 67.3
condition 5 11 45.4
subroutine 8 8 100.0
pod 2 2 100.0
total 125 162 77.1


line stmt bran cond sub pod time code
1             package Genealogy::ChroniclingAmerica;
2              
3             # https://chroniclingamerica.loc.gov/search/pages/results/?state=Indiana&andtext=james=serjeant&date1=1894&date2=1896&format=json
4 4     4   359048 use warnings;
  4         27  
  4         135  
5 4     4   23 use strict;
  4         8  
  4         77  
6 4     4   2561 use LWP::UserAgent;
  4         187270  
  4         154  
7 4     4   2833 use JSON;
  4         38205  
  4         26  
8 4     4   596 use URI;
  4         8  
  4         100  
9 4     4   20 use Carp;
  4         9  
  4         3173  
10              
11             =head1 NAME
12              
13             Genealogy::ChroniclingAmerica - Find URLs for a given person on the Library of Congress Newspaper Records
14              
15             =head1 VERSION
16              
17             Version 0.02
18              
19             =cut
20              
21             our $VERSION = '0.02';
22              
23             =head1 SYNOPSIS
24              
25             use HTTP::Cache::Transparent; # be nice
26             use Genealogy::ChroniclingAmerica;
27              
28             HTTP::Cache::Transparent::init({
29             BasePath => '/tmp/cache'
30             });
31             my $loc = Genealogy::ChroniclingAmerica->new({
32             firstname => 'John',
33             lastname => 'Smith',
34             state => 'Indiana',
35             date_of_death => 1862
36             });
37              
38             while(my $url = $loc->get_next_entry()) {
39             print "$url\n";
40             }
41              
42             =head1 SUBROUTINES/METHODS
43              
44             =head2 new
45              
46             Creates a Genealogy::ChroniclingAmerica object.
47              
48             It takes three mandatory arguments state, firstname and lastname.
49             State must be the full name, not an abbreviation.
50              
51             There are four optional arguments: middlename, date_of_birth, date_of_death, ua and host:
52             host is the domain of the site to search, the default is chroniclingamerica.loc.gov.
53             ua is a pointer to an object that understands get and env_proxy messages, such
54             as L.
55              
56             =cut
57              
58             sub new {
59 6     6 1 1579 my $proto = shift;
60 6   66     46 my $class = ref($proto) || $proto;
61              
62 6 100       28 return unless(defined($class));
63              
64 5         14 my %args;
65 5 100 33     35 if(ref($_[0]) eq 'HASH') {
    50          
    50          
66 4         10 %args = %{$_[0]};
  4         23  
67             } elsif(ref($_[0]) || !defined($_[0])) {
68 0         0 Carp::croak('Usage: ', __PACKAGE__, '->new(%args)');
69             } elsif(@_ % 2 == 0) {
70 1         8 %args = @_;
71             }
72              
73 5 50       21 unless($args{'firstname'}) {
74 0         0 Carp::croak('First name is not optional');
75 0         0 return; # Don't know why this is needed, but it is
76             }
77 5 50       18 unless(defined($args{'lastname'})) {
78 0         0 Carp::croak('Last name is not optional');
79 0         0 return;
80             }
81 5 50       18 unless($args{'state'}) {
82 0         0 Carp::croak('State is not optional');
83 0         0 return;
84             }
85              
86 5 50       19 Carp::croak('State needs to be the full name') if(length($args{'state'}) == 2);
87              
88 5   33     69 my $ua = $args{'ua'} || LWP::UserAgent->new(agent => __PACKAGE__ . "/$VERSION");
89 5 50       6885 $ua->env_proxy(1) unless(delete $args{'ua'});
90              
91 5         29534 my $rc = { ua => $ua };
92 5   50     41 $rc->{'host'} = $args{'host'} || 'chroniclingamerica.loc.gov';
93              
94 5         41 my %query_parameters = ( 'format' => 'json', 'state' => ucfirst(lc($args{'state'})) );
95 5 100       51 if($query_parameters{'state'} eq 'District of columbia') {
96 1         4 $query_parameters{'state'} = 'District of Columbia';
97             }
98 5         25 my $name = $args{'firstname'};
99 5 100       30 if($args{'middlename'}) {
100 1         6 $rc->{'name'} = "$name $args{middlename} $args{lastname}";
101 1         4 $name .= "=$args{middlename}";
102             } else {
103 4         20 $rc->{'name'} = "$name $args{lastname}";
104             }
105 5         19 $name .= "=$args{lastname}";
106              
107 5         19 $query_parameters{'andtext'} = $name;
108 5 50       20 if($args{'date_of_birth'}) {
109 5         16 $query_parameters{'date1'} = $args{'date_of_birth'};
110             }
111 5 100       19 if($args{'date_of_death'}) {
112 3         9 $query_parameters{'date2'} = $args{'date_of_death'};
113             }
114              
115 5         69 my $uri = URI->new("https://$rc->{host}/search/pages/results/");
116 5         16393 $uri->query_form(%query_parameters);
117 5         1020 my $url = $uri->as_string();
118             # ::diag(">>>>$url = ", $rc->{'name'});
119             # print ">>>>$url = ", $rc->{'name'}, "\n";
120              
121 5         60 my $resp = $ua->get($url);
122              
123 5 50       2649617 if($resp->is_error()) {
124 0         0 Carp::carp("API returned error on $url: ", $resp->status_line());
125 0         0 return;
126             }
127              
128 5 50       91 unless($resp->is_success()) {
129 0         0 die $resp->status_line();
130             }
131              
132 5         138 $rc->{'json'} = JSON->new();
133 5         31 my $data = $rc->{'json'}->decode($resp->content());
134              
135             # ::diag(Data::Dumper->new([$data])->Dump());
136              
137 5         1644 my $matches = $data->{'totalItems'};
138 5 50       24 if($data->{'itemsPerPage'} < $matches) {
139 0         0 $matches = $data->{'itemsPerPage'};
140             }
141              
142 5         17 $rc->{'matches'} = $matches;
143 5 100       23 if($matches) {
144 3         11 $rc->{'query_parameters'} = \%query_parameters;
145 3         13 $rc->{'items'} = $data->{'items'};
146 3         14 $rc->{'index'} = 0;
147             }
148              
149 5         415 return bless $rc, $class;
150             }
151              
152             =head2 get_next_entry
153              
154             Returns the next match as a URL.
155              
156             =cut
157              
158             sub get_next_entry
159             {
160 8     8 1 6940 my $self = shift;
161              
162 8 100       44 return if($self->{'matches'} == 0);
163              
164 6 100       24 if($self->{'index'} >= $self->{'matches'}) {
165 4         18 return;
166             }
167              
168 2         5 my $entry = @{$self->{'items'}}[$self->{'index'}];
  2         10  
169 2         6 $self->{'index'}++;
170              
171 2 50       9 if(!defined($entry->{'url'})) {
172 0         0 return $self->get_next_entry();
173             }
174              
175 2         20 my $text = $entry->{'ocr_eng'};
176              
177 2         865 $text =~ s/[\r\n]/ /g;
178 2 50       119 if($text !~ /$self->{'name'}/ims) {
179 0         0 return $self->get_next_entry();
180             }
181              
182             # ::diag(Data::Dumper->new([$entry])->Dump());
183              
184 2         13 my $resp = $self->{'ua'}->get($entry->{'url'});
185              
186 2 50       1060223 if($resp->is_error()) {
187             # print 'got: ', $resp->content(), "\n";
188 0         0 Carp::carp("get_next_entry: API returned error on $entry->{url}: ", $resp->status_line());
189 0         0 return;
190             }
191              
192 2 50       42 unless($resp->is_success()) {
193 0         0 die $resp->status_line();
194             }
195              
196 2         33 return $self->{'json'}->decode($resp->content())->{'pdf'};
197             }
198              
199             =head1 AUTHOR
200              
201             Nigel Horne, C<< >>
202              
203             =head1 BUGS
204              
205             If a middle name is given and no match is found,
206             it should search again without the middle name.
207              
208             Please report any bugs or feature requests to C,
209             or through the web interface at
210             L.
211             I will be notified, and then you'll
212             automatically be notified of progress on your bug as I make changes.
213              
214             =head1 SEE ALSO
215              
216             L
217             L
218              
219             =head1 SUPPORT
220              
221             You can find documentation for this module with the perldoc command.
222              
223             perldoc Genealogy::ChroniclingAmerica
224              
225             You can also look for information at:
226              
227             =over 4
228              
229             =item * RT: CPAN's request tracker
230              
231             L
232              
233             =item * CPAN Ratings
234              
235             L
236              
237             =item * Search CPAN
238              
239             L
240              
241             =back
242              
243             =head1 LICENSE AND COPYRIGHT
244              
245             Copyright 2018,2019 Nigel Horne.
246              
247             This program is released under the following licence: GPL2
248              
249             =cut
250              
251             1; # End of Genealogy::ChroniclingAmerica