File Coverage

blib/lib/Catmandu/Importer/SRU.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Catmandu::Importer::SRU;
2              
3 5     5   437178 use Catmandu::Sane;
  5         879898  
  5         40  
4 5     5   4504 use Catmandu::Importer::SRU::Parser;
  0            
  0            
5             use Catmandu::Util qw(:is);
6             use URI::Escape;
7             use Moo;
8             use Furl;
9             use Carp;
10             use XML::LibXML;
11             use XML::LibXML::XPathContext;
12              
13             with 'Catmandu::Importer';
14              
15             # required.
16             has base => (is => 'ro', required => 1);
17             has query => (is => 'ro', required => 1);
18             has version => (is => 'ro', default => sub { '1.1' });
19             has operation => (is => 'ro', default => sub { 'searchRetrieve' });
20             has recordSchema => (is => 'ro', default => sub { 'dc' });
21             has userAgent => (is => 'ro', default => sub { 'Mozilla/5.0' });
22             has furl => (is => 'ro', lazy => 1, builder => sub {
23             Furl->new( agent => $_[0]->userAgent );
24             });
25              
26             # optional.
27             has sortKeys => (is => 'ro');
28             has parser => (is => 'rw', default => sub { 'simple' }, coerce => \&_coerce_parser );
29              
30             # internal stuff.
31             has _currentRecordSet => (is => 'ro');
32             has _n => (is => 'ro', default => sub { 0 });
33             has _start => (is => 'ro', default => sub { 1 });
34             has _max_results => (is => 'ro', default => sub { 10 });
35              
36             # Internal Methods. ------------------------------------------------------------
37              
38             sub _coerce_parser {
39             my ($parser) = @_;
40              
41             return $parser if is_invocant($parser) or is_code_ref($parser);
42              
43             if (is_string($parser) && !is_number($parser)) {
44             my $class = $parser =~ /^\+(.+)/ ? $1
45             : "Catmandu::Importer::SRU::Parser::$parser";
46              
47             my $parser;
48             eval {
49             $parser = Catmandu::Util::require_package($class)->new;
50             };
51             if ($@) {
52             croak $@;
53             } else {
54             return $parser;
55             }
56             }
57              
58             return Catmandu::Importer::SRU::Parser->new;
59             }
60              
61             # Internal: HTTP GET something.
62             #
63             # $url - the url.
64             #
65             # Returns the raw response object.
66             sub _request {
67             my ($self, $url) = @_;
68              
69             my $res = $self->furl->get($url);
70             die $res->status_line unless $res->is_success;
71              
72             return $res;
73             }
74              
75             # Internal: Converts XML to a perl hash.
76             #
77             # $in - the raw XML input.
78             #
79             # Returns a hash representation of the given XML.
80             sub _hashify {
81             my ($self, $in) = @_;
82              
83             my $parser = XML::LibXML->new();
84             my $doc = $parser->parse_string($in);
85             my $root = $doc->documentElement;
86             my $xc = XML::LibXML::XPathContext->new( $root );
87             $xc->registerNs("srw","http://www.loc.gov/zing/srw/");
88             $xc->registerNs("d","http://www.loc.gov/zing/srw/diagnostic/");
89            
90             my $diagnostics = {};
91              
92             if ($xc->exists('/srw:searchRetrieveResponse/srw:diagnostics')) {
93             $diagnostics->{diagnostic} = [];
94              
95             for ($xc->findnodes('/srw:searchRetrieveResponse/srw:diagnostics/*')) {
96             my $uri = $xc->findvalue('./d:uri',$_);
97             my $message = $xc->findvalue('./d:message',$_);
98             my $details = $xc->findvalue('./d:details',$_);
99              
100             push @{$diagnostics->{diagnostic}} ,
101             { uri => $uri , message => $message , details => $details } ;
102             }
103             }
104              
105             my $records = { };
106              
107             if ($xc->exists('/srw:searchRetrieveResponse/srw:records')) {
108             $records->{record} = [];
109              
110             for ($xc->findnodes('/srw:searchRetrieveResponse/srw:records/srw:record')) {
111             my $recordSchema = $xc->findvalue('./srw:recordSchema',$_);
112             my $recordPacking = $xc->findvalue('./srw:recordPacking',$_);
113             my $recordData = '' . $xc->find('./srw:recordData/*',$_)->pop();
114             my $recordPosition = $xc->findvalue('./srw:recordPosition',$_);
115            
116             push @{$records->{record}} ,
117             { recordSchema => $recordSchema , recordPacking => $recordPacking ,
118             recordData => $recordData , recordPosition => $recordPosition };
119             }
120             }
121              
122             return { diagnostics => $diagnostics , records => $records };
123             }
124              
125             sub url {
126             my ($self) = @_;
127              
128             # construct the url
129             my $url = $self->base;
130             $url .= '?version=' . uri_escape($self->version);
131             $url .= '&operation=' .uri_escape($self->operation);
132             $url .= '&query=' . uri_escape($self->query);
133             $url .= '&recordSchema=' . uri_escape($self->recordSchema);
134             $url .= '&sortKeys=' . uri_esacpe($self->sortKeys) if $self->sortKeys;
135             $url .= '&startRecord=' . uri_escape($self->_start);
136             $url .= '&maximumRecords=' . uri_escape($self->_max_results);
137              
138             return $url;
139             }
140              
141             # Internal: gets the next set of results.
142             #
143             # Returns a array representation of the resultset.
144             sub _nextRecordSet {
145             my ($self) = @_;
146              
147             # fetch the xml response and hashify it.
148             my $res = $self->_request($self->url);
149             my $xml = $res->{content};
150             my $hash = $self->_hashify($xml);
151              
152             # sru specific error checking.
153             if (exists $hash->{'diagnostics'}->{'diagnostic'}) {
154             for my $error (@{$hash->{'diagnostics'}->{'diagnostic'}}) {
155             warn 'SRU DIAGNOSTIC: ', $error->{'message'} , ' : ' , $error->{'details'};
156             }
157             }
158              
159             # get to the point.
160             my $set = $hash->{'records'}->{'record'};
161              
162             # return a reference to a array.
163             return \@{$set};
164             }
165              
166             # Internal: gets the next record from our current resultset.
167             #
168             # Returns a hash representation of the next record.
169             sub _nextRecord {
170             my ($self) = @_;
171              
172             # fetch recordset if we don't have one yet.
173             $self->{_currentRecordSet} = $self->_nextRecordSet unless $self->_currentRecordSet;
174              
175             # check for a exhaused recordset.
176             if ($self->_n >= $self->_max_results) {
177             $self->{_start} += $self->_max_results;
178             $self->{_n} = 0;
179             $self->{_currentRecordSet} = $self->_nextRecordSet;
180             }
181              
182             # return the next record.
183             my $record = $self->_currentRecordSet->[$self->{_n}++];
184              
185             if (defined $record) {
186             if (is_code_ref($self->parser)) {
187             $record = $self->parser->($record);
188             } else {
189             $record = $self->parser->parse($record);
190             }
191             }
192             return $record;
193             }
194              
195             # Public Methods. --------------------------------------------------------------
196              
197             sub generator {
198             my ($self) = @_;
199              
200             return sub {
201             $self->_nextRecord;
202             };
203             }
204              
205             =head1 NAME
206              
207             Catmandu::Importer::SRU - Package that imports SRU data
208              
209             =head1 SYNOPSIS
210              
211             use Catmandu::Importer::SRU;
212              
213             my %attrs = (
214             base => 'http://www.unicat.be/sru',
215             query => '(isbn=0855275103 or isbn=3110035170 or isbn=9010017362 or isbn=9014026188)',
216             recordSchema => 'marcxml',
217             parser => 'marcxml'
218             );
219              
220             my $importer = Catmandu::Importer::SRU->new(%attrs);
221              
222             my $count = $importer->each(sub {
223             my $schema = $record->{recordSchema};
224             my $packing = $record->{recordPacking};
225             my $position = $record->{recordPosition};
226             my $data = $record->{recordData};
227             # ...
228             });
229              
230             # Using Catmandu::Importer::SRU::Package::marcxml, included in this release
231              
232             my $importer = Catmandu::Importer::SRU->new(
233             base => 'http://www.unicat.be/sru',
234             query => '(isbn=0855275103 or isbn=3110035170 or isbn=9010017362 or isbn=9014026188)',
235             recordSchema => 'marcxml' ,
236             parser => 'marcxml' ,
237             );
238              
239             # Using a homemade parser
240            
241             my $importer = Catmandu::Importer::SRU->new(
242             base => 'http://www.unicat.be/sru',
243             query => '(isbn=0855275103 or isbn=3110035170 or isbn=9010017362 or isbn=9014026188)',
244             recordSchema => 'marcxml' ,
245             parser => MyParser->new , # or parser => '+MyParser'
246             );
247              
248             =head1 CONFIGURATION
249              
250             =over
251              
252             =item base
253              
254             base URL of the SRU server (required)
255              
256             =item query
257              
258             CQL query (required)
259              
260             =item recordSchema
261              
262             set to C<dc> by default
263              
264             =item sortkeys
265              
266             optional sorting
267              
268             =item operation
269              
270             set to C<searchRetrieve> by default
271              
272             =item version
273              
274             set to C<1.1> by default.
275              
276             =item userAgent
277              
278             HTTP user agent, set to C<Mozilla/5.0> by default.
279              
280             =item furl
281              
282             Instance of L<Furl> or compatible class to fetch URLs with.
283              
284             =item parser
285              
286             Controls how records are parsed before importing. The following options
287             are possible:
288              
289             =over
290              
291             =item
292              
293             Instance of a Perl package that implements a C<parse> subroutine. See the
294             default value C<Catmandu::Importer::SRU::Parser> for an example.
295              
296             =item
297              
298             Name of a Perl package that implements a C<parse> subroutine. The name must be
299             prepended by C<+> or it prefixed with C<Catmandu::Importer::SRU::Parser::>. For
300             instance C<marcxml> will create a C<Catmandu::Importer::SRU::Parser::marcxml>.
301              
302             =item
303              
304             Function reference that gets passed the unparsed record.
305            
306             =back
307              
308             =back
309              
310             =head1 METHODS
311              
312             All methods of L<Catmandu::Importer> and by this L<Catmandu::Iterable> are
313             inherited. In addition the following methods are provided:
314              
315             =head2 url
316              
317             Return the current SRU request URL (useful for debugging).
318              
319             =head1 SEE ALSO
320              
321             L<Catmandu::Importer>,
322             L<Catmandu::Iterable>,
323             L<http://www.loc.gov/standards/sru/>
324              
325             =cut
326              
327             1;