File Coverage

blib/lib/WebService/Libris.pm
Criterion Covered Total %
statement 108 142 76.0
branch 16 28 57.1
condition 1 10 10.0
subroutine 29 35 82.8
pod 7 9 77.7
total 161 224 71.8


line stmt bran cond sub pod time code
1             package WebService::Libris;
2 2     2   56701 use Mojo::Base -base;
  2         37712  
  2         18  
3 2     2   2382 use Mojo::UserAgent;
  2         839139  
  2         36  
4 2     2   88 use Mojo::URL;
  2         11  
  2         14  
5              
6 2     2   115 use 5.010;
  2         9  
  2         108  
7 2     2   14 use strict;
  2         3  
  2         84  
8 2     2   12 use warnings;
  2         4  
  2         10686  
9              
10             my %default_typemap = (
11             bib => 'Book',
12             book => 'Book',
13             auth => 'Author',
14             author => 'Author',
15             library => 'Library',
16             );
17              
18             has 'id';
19             has 'type';
20             has '_dom';
21             has 'cache';
22              
23             has 'type_map';
24              
25             =head1 NAME
26              
27             WebService::Libris - Access book meta data from libris.kb.se
28              
29             =head1 VERSION
30              
31             Version 0.08
32              
33             Note that the API is still subject to change.
34              
35             =cut
36              
37             our $VERSION = '0.08';
38              
39              
40             =head1 SYNOPSIS
41              
42             use WebService::Libris;
43             use 5.010;
44             binmode STDOUT, ':encoding(UTF-8)';
45              
46             my $book = WebService::Libris->new(
47             type => 'book',
48             # Libris ID
49             id => '9604288',
50             # optional but recommended:
51             cache_dir = '/tmp/webservice-libris/',
52             );
53             print $book->title;
54              
55             my $books = WebService::Libris->search(
56             term => 'Astrid Lindgren',
57             page => 1,
58             );
59             while (my $b = $books->next) {
60             say $b->title;
61             say ' isbn: ', $b->isbn;
62             say ' date: ', $b->date;
63             }
64              
65             =head1 DESCRIPTION
66              
67             The Swedish public libraries and the national library of Sweden have a common
68             catalogue containing meta data of the books they have available.
69              
70             This includes many contemporary as well as historical books.
71              
72             The catalogue is available online at L, and can be
73             queried with a public API.
74              
75             This module is a wrapper around two of their APIs (xsearch and RDF responses).
76              
77             =head1 METHODS
78              
79             =head2 new
80              
81             my $obj = WebService::Libris->new(
82             type => 'author',
83             id => '246603',
84             );
85              
86             Creates an object of the C class or a subclass thereof
87             (denoted by C in the argument list). C can currently be one of
88             (synonyms on one line)
89              
90             auth author
91             bib book
92             library
93              
94             The C argument is mandatory, and must contain the Libris ID of the object
95             you want to retrieve. If you don't know the Libris ID, use one of the
96             C functions instead.
97              
98             =head2 direct_search
99              
100             my $hashref = WebService::Libris->direct_search(
101             term => 'Your Searchterms Here',
102             page => 1, # page size is 200
103             full => 1, # return all available information
104             );
105              
106             Returns a hashref directly from the JSON response of the xsearch API
107             described at L.
108              
109             This is more efficient than a C<< WebService::Libris->search >> call, because
110             it does only one query (whereas C<< ->search >> does one additional request
111             per result object), but it's not as convenient, and does not allow browsing of
112             related entities (such as authors and libraries).
113              
114             =head2 search
115              
116             my @books = WebService::Libris->search(
117             term => 'Your Search Term Here',
118             page => 1,
119             );
120             for my $book (@books) {
121             say $book->title;
122             }
123              
124             Searches the xsearch API for arbitrary search terms, and returns a
125             C of books.
126              
127             See the C method above for a short discussion.
128              
129             =head2 search_for_isbn
130              
131             my $book = WebService::Libris->search_for_isbn('9170370192');
132              
133             Looks up a book by ISBN
134              
135             =head1 Less interesting methods
136              
137             The following methods aren't usually useful for the casual user, more
138             for those who want to extend or subclass this module.
139              
140             =head2 rdf_url
141              
142             Returns the RDF resource URL for the current object. Mostly useful for internal purposes.
143              
144             =head2 dom
145              
146             Returns the L object from the web services response.
147             Does a request to the web service if no DOM was stored previously.
148              
149             Only useful for you if you want to extract more data from a response
150             than the object itself provides.
151              
152             =head2 id
153              
154             Returns the libris ID of the object. Only makes sense for subclasses.
155              
156             =head2 type
157              
158             Returns the short type name (C, C, C). Only makes sense
159             for subclasses.
160              
161             =head2 fragments
162              
163             Must be overridden in a subclass to return a list of
164             the last two junks of the RDF resource URL, that is the short
165             type name and the libris ID.
166              
167             =head1 AUTHOR
168              
169             Moritz Lenz, C<< >>
170              
171             =head1 BUGS
172              
173             Please report any bugs or feature requests at
174             L
175              
176             =head1 SUPPORT
177              
178             You can find documentation for this module with the perldoc command.
179              
180             perldoc WebService::Libris
181              
182             You can also look for information at:
183              
184             =over 4
185              
186             =item * Bug tracker:
187              
188             L
189              
190             =item * AnnoCPAN: Annotated CPAN documentation
191              
192             L
193              
194             =item * CPAN Ratings
195              
196             L
197              
198             =item * Search CPAN
199              
200             L
201              
202             =back
203              
204             =head1 BUGS AND LIMITATIONS
205              
206             Nearly no error checking is done. So beware!
207              
208             =head1 ACKNOWLEDGEMENTS
209              
210             Thanks go to the Kungliga biblioteket (National Library of Sweden) for
211             providing the libris.kb.se service and API.
212              
213             =head1 LICENSE AND COPYRIGHT
214              
215             Copyright 2011 Moritz Lenz.
216              
217             This program is free software; you can redistribute it and/or modify it
218             under the terms of either: the GNU General Public License as published
219             by the Free Software Foundation; or the Artistic License.
220              
221             See L for more information.
222              
223             =cut
224              
225             sub new {
226 15     15 1 2151 my ($class, %opts) = @_;;
227 15         24 my $c;
228 15 50       39 if ($opts{type}) {
229 15 50       33 if ($opts{type_map}) {
230 0   0     0 $c = $opts{type_map}{lc $opts{type}}
231             // $default_typemap{lc $opts{type}};
232             } else {
233 15         56 $c = $default_typemap{lc $opts{type}};
234             }
235             }
236 15 100       47 if (my $cache_dir = delete $opts{cache_dir}) {
237 1         522 require WebService::Libris::FileCache;
238 1         10 $opts{cache} = WebService::Libris::FileCache->new(
239             directory => $cache_dir,
240             );
241             }
242 15 50       31 if ($c) {
243 15         28 $class = __PACKAGE__ . "::" . $c;
244 15 50   1   1128 eval "use $class; 1" or die $@;
  1     1   654  
  1     1   3  
  1     1   11  
  1     1   743  
  1     1   6  
  1     1   13  
  1     1   714  
  1     1   3  
  1     1   8  
  1     1   7  
  1     1   2  
  1     1   6  
  1     1   5  
  1     1   2  
  1         6  
  1         6  
  1         3  
  1         6  
  1         6  
  1         2  
  1         6  
  1         6  
  1         2  
  1         8  
  1         7  
  1         2  
  1         6  
  1         7  
  1         3  
  1         5  
  1         6  
  1         3  
  1         14  
  1         5  
  1         2  
  1         6  
  1         6  
  1         3  
  1         6  
  1         5  
  1         3  
  1         6  
  1         5  
  1         2  
  1         6  
245 15         133 return bless \%opts, $class;
246             } else {
247 0         0 return bless \%opts, $class;
248             }
249             }
250              
251              
252             sub rdf_url {
253 0     0 1 0 my $self = shift;
254 0         0 my ($key, $id) = $self->fragments;
255 0         0 "http://libris.kb.se/data/$key/$id?format=application%2Frdf%2Bxml";
256             }
257              
258             sub dom {
259 19     19 1 30 my $self = shift;
260              
261 19 100       492 unless ($self->_dom) {
262 3 50       94 if ($self->cache) {
263 3         24 my $key = join '/', $self->fragments;
264 3 50       75 if (my $r = $self->cache->get($key)) {
265 3         23855 $self->_dom($r);
266             } else {
267 0         0 my $dom = $self->_request_dom;
268 0         0 $self->cache->set($key, $dom);
269 0         0 $self->_dom($dom);
270             }
271             } else {
272 0         0 $self->_dom($self->_request_dom);
273             }
274             }
275 19         600 $self->_dom;
276             }
277              
278             sub _request_dom {
279 0     0   0 my $self = shift;
280 0         0 Mojo::UserAgent->new()->get($self->rdf_url)->res->dom;
281             }
282              
283             sub direct_search {
284 0     0 1 0 my ($self, %opts) = @_;
285 0   0     0 my $terms = $opts{term} // die "Search term missing";
286 0   0     0 my $page = $opts{page} // 1;
287 0         0 my %q = (
288             query => $terms,
289             n => 200, # max. number of results
290             start => 1 + 200 * ($page - 1),
291             format => 'json',
292             );
293 0 0       0 $q{format_level} = 'full' if $opts{full};
294 0         0 my $url = Mojo::URL->new('http://libris.kb.se/xsearch');
295 0         0 $url->query(%q);
296 0         0 my $res = Mojo::UserAgent->new()->get($url)->res;
297 0         0 $res->json;
298             }
299              
300             sub search {
301 0     0 1 0 my ($self, %opts) = @_;
302 0         0 my $json = $self->direct_search(%opts);
303 0         0 my @ids = map { (split '/', $_->{identifier})[-1] }
  0         0  
304 0         0 @{ $json->{xsearch}{list} };
305 0         0 WebService::Libris::Collection->new(
306             type => 'bib',
307             ids => \@ids,
308             cache => $self->cache,
309             );
310             }
311              
312             sub search_for_isbn {
313 0     0 1 0 my ($self, $isbn) = @_;
314 0         0 my $res = Mojo::UserAgent->new->max_redirects(1)
315             ->get("http://libris.kb.se/hitlist?q=linkisxn:$isbn");
316 0         0 my $url = $res->res->headers->location;
317 0 0       0 return unless $url;
318 0         0 my ($type, $libris_id) = (split '/', $url)[-2, -1];
319 0         0 $self->new(type => $type, id => $libris_id, cache => $self->cache);
320             }
321              
322             sub fragments {
323 0     0 1 0 die "Must be overridden in subclasses";
324             }
325              
326             sub list_from_dom {
327 2     2 0 6 my ($self, $search_for) = @_;
328 2         4 my $key;
329             my @result;
330 0         0 my %seen;
331             $self->dom->find($search_for)->each(sub {
332 16     16   4532 my $d = shift;
333 16   33     56 my $resource_url = $d->attr('rdf:resource')
334             // $d->attr('rdf:about');
335 16 100       711 return unless $resource_url;
336 14         63 my ($k, $id) = $self->fragment_from_resource_url($resource_url);
337 14 50       79 return if $seen{"$k/$id"}++;
338 14         342 push @result, __PACKAGE__->new(
339             type => $k,
340             id => $id,
341             cache => $self->cache,
342             );
343 2         9 });
344 2         65 @result;
345             }
346              
347             sub fragment_from_resource_url {
348 14     14 0 20 my ($self, $url) = @_;
349 14         96 (split '/', $url)[-2, -1];
350             }
351              
352             sub _make_text_accessor {
353 2     2   5 my $package = shift;
354 2         7 for (@_) {
355 8         11 my ($name, $look_for);
356 8 100       19 if (ref($_) eq 'ARRAY') {
357 1         3 ($name, $look_for) = @$_;
358             } else {
359 7         9 $name = $_;
360 7         9 $look_for = $_;
361             }
362 2     2   27 no strict 'refs';
  2         4  
  2         345  
363 8         49 *{"${package}::$name"} = sub {
364 6     6   3673 my $thing;
365 6 50       28 ($thing = shift->dom->at($look_for)) && $thing->text;
366 8         30 };
367             }
368             }
369              
370              
371             1; # End of WebService::Libris