]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Ingest.pm
Increase support for serials in Evergreen.
[working/Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Ingest.pm
1 package OpenILS::Application::Ingest;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4
5 use Unicode::Normalize;
6 use OpenSRF::EX qw/:try/;
7
8 use OpenSRF::AppSession;
9 use OpenSRF::Utils::SettingsClient;
10 use OpenSRF::Utils::Logger qw/:level/;
11
12 use OpenILS::Application::AppUtils;
13 use OpenILS::Utils::ScriptRunner;
14 use OpenILS::Utils::Fieldmapper;
15 use OpenSRF::Utils::JSON;
16
17 use OpenILS::Utils::Fieldmapper;
18
19 use XML::LibXML;
20 use XML::LibXSLT;
21 use Time::HiRes qw(time);
22
23 our %supported_formats = (
24     mods33    => {ns => 'http://www.loc.gov/mods/v3'},
25     mods32    => {ns => 'http://www.loc.gov/mods/v3'},
26     mods3    => {ns => 'http://www.loc.gov/mods/v3'},
27     mods    => {ns => 'http://www.loc.gov/mods/'},
28     marcxml    => {ns => 'http://www.loc.gov/MARC21/slim'},
29     srw_dc    => {ns => 'info:srw/schema/1/dc-schema'},
30     oai_dc    => {ns => 'http://www.openarchives.org/OAI/2.0/oai_dc/'},
31     rdf_dc    => {ns => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'},
32     atom    => {ns => 'http://www.w3.org/2005/Atom'},
33     rss091    => {ns => 'http://my.netscape.com/rdf/simple/0.9/'},
34     rss092    => {ns => ''},
35     rss093    => {ns => ''},
36     rss094    => {ns => ''},
37     rss10    => {ns => 'http://purl.org/rss/1.0/'},
38     rss11    => {ns => 'http://purl.org/net/rss1.1#'},
39     rss2    => {ns => ''},
40 );
41
42
43 my $log = 'OpenSRF::Utils::Logger';
44
45 my  $parser = XML::LibXML->new();
46 my  $xslt = XML::LibXSLT->new();
47
48 my  $mods_sheet;
49 my  $mads_sheet;
50 my  $xpathset = {};
51 sub initialize {}
52 sub child_init {}
53
54 sub post_init {
55
56     unless (keys %$xpathset) {
57         $log->debug("Running post_init", DEBUG);
58
59         my $xsldir = OpenSRF::Utils::SettingsClient->new->config_value(dirs => 'xsl');
60
61         unless ($supported_formats{mods}{xslt}) {
62             $log->debug("Loading MODS XSLT", DEBUG);
63              my $xslt_doc = $parser->parse_file( $xsldir . "/MARC21slim2MODS.xsl");
64             $supported_formats{mods}{xslt} = $xslt->parse_stylesheet( $xslt_doc );
65         }
66
67         unless ($supported_formats{mods3}{xslt}) {
68             $log->debug("Loading MODS v3 XSLT", DEBUG);
69              my $xslt_doc = $parser->parse_file( $xsldir . "/MARC21slim2MODS3.xsl");
70             $supported_formats{mods3}{xslt} = $xslt->parse_stylesheet( $xslt_doc );
71         }
72
73         unless ($supported_formats{mods32}{xslt}) {
74             $log->debug("Loading MODS v32 XSLT", DEBUG);
75              my $xslt_doc = $parser->parse_file( $xsldir . "/MARC21slim2MODS32.xsl");
76             $supported_formats{mods32}{xslt} = $xslt->parse_stylesheet( $xslt_doc );
77         }
78
79         unless ($supported_formats{mods33}{xslt}) {
80             $log->debug("Loading MODS v33 XSLT", DEBUG);
81              my $xslt_doc = $parser->parse_file( $xsldir . "/MARC21slim2MODS33.xsl");
82             $supported_formats{mods33}{xslt} = $xslt->parse_stylesheet( $xslt_doc );
83         }
84
85         my $req = OpenSRF::AppSession
86                 ->create('open-ils.cstore')
87                 
88                 # XXX testing new metabib field use for faceting
89                 #->request( 'open-ils.cstore.direct.config.metabib_field.search.atomic', { id => { '!=' => undef } } )
90                 ->request( 'open-ils.cstore.direct.config.metabib_field.search.atomic', { search_field => 't' } )
91
92                 ->gather(1);
93
94         if (ref $req and @$req) {
95             for my $f (@$req) {
96                 $xpathset->{ $f->field_class }->{ $f->name }->{xpath} = $f->xpath;
97                 $xpathset->{ $f->field_class }->{ $f->name }->{id} = $f->id;
98                 $xpathset->{ $f->field_class }->{ $f->name }->{format} = $f->format;
99                 $log->debug("Loaded XPath from DB: ".$f->field_class." => ".$f->name." : ".$f->xpath, DEBUG);
100             }
101         }
102     }
103 }
104
105 # --------------------------------------------------------------------------------
106 # Biblio ingest
107
108 package OpenILS::Application::Ingest::Biblio;
109 use base qw/OpenILS::Application::Ingest/;
110 use Unicode::Normalize;
111
112 sub rw_biblio_ingest_single_object {
113     my $self = shift;
114     my $client = shift;
115     my $bib = shift;
116
117     my ($blob) = $self->method_lookup("open-ils.ingest.full.biblio.object.readonly")->run($bib);
118     return undef unless ($blob);
119
120     $bib->fingerprint( $blob->{fingerprint}->{fingerprint} );
121     $bib->quality( $blob->{fingerprint}->{quality} );
122
123     my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
124
125     my $xact = $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
126     my $tmp;
127
128     # update uri stuff ...
129
130     # gather URI call numbers for this record
131     my $uri_cns = $u->{call_number} = $cstore->request(
132         'open-ils.cstore.direct.asset.call_number.id_list.atomic' => { record => $bib->id, label => '##URI##' }
133     )->gather(1);
134
135     if (@$uri_cns) {
136         # gather the maps for those call numbers
137         my $uri_maps = $u->{call_number} = $cstore->request(
138             'open-ils.cstore.direct.asset.uri_call_number_map.id_list.atomic' => { call_number => $uri_cns }
139         )->gather(1);
140     
141         # delete the old maps
142         $cstore->request( 'open-ils.cstore.direct.asset.uri_call_number_map.delete' => $_ )->gather(1) for (@$uri_maps);
143     
144         # and delete the call numbers if there are no more URIs
145         if (!@{ $blob->{uri} }) {
146             $cstore->request( 'open-ils.cstore.direct.asset.call_number.delete' => $_ )->gather(1) for (@$uri_cns);
147         }
148     }
149
150     # now, add CNs, URIs and maps
151     my %new_cns_by_owner;
152     my %new_uris_by_owner;
153     for my $u ( @{ $blob->{uri} } ) {
154
155         my $owner = $u->{call_number}->owning_lib;
156
157         if ($u->{call_number}->isnew) {
158             if ($new_cns_by_owner{$owner}) {
159                 $u->{call_number} = $new_cns_by_owner{$owner};
160             } else {
161                 $u->{call_number}->clear_id;
162                 $u->{call_number} = $new_cns_by_owner{$owner} = $cstore->request(
163                     'open-ils.cstore.direct.asset.call_number.create' => $u->{call_number}
164                 )->gather(1);
165             }
166         }
167
168         if ($u->{uri}->isnew) {
169             if ($new_uris_by_owner{$owner}) {
170                 $u->{uri} = $new_uris_by_owner{$owner};
171             } else {
172                 $u->{uri} = $new_uris_by_owner{$owner} = $cstore->request(
173                     'open-ils.cstore.direct.asset.uri.create' => $u->{uri}
174                 )->gather(1);
175             }
176         }
177
178         my $umap = Fieldmapper::asset::uri_call_number_map->new;
179         $umap->uri($u->{uri}->id);
180         $umap->call_number($u->{call_number}->id);
181
182         $cstore->request( 'open-ils.cstore.direct.asset.uri_call_number_map.create' => $umap )->gather(1) if (!$tmp);
183     }
184
185     # update full_rec stuff ...
186     $tmp = $cstore->request(
187         'open-ils.cstore.direct.metabib.full_rec.id_list.atomic',
188         { record => $bib->id }
189     )->gather(1);
190
191     $cstore->request( 'open-ils.cstore.direct.metabib.full_rec.delete' => $_ )->gather(1) for (@$tmp);
192     $cstore->request( 'open-ils.cstore.direct.metabib.full_rec.create' => $_ )->gather(1) for (@{ $blob->{full_rec} });
193
194     # update rec_descriptor stuff ...
195     $tmp = $cstore->request(
196         'open-ils.cstore.direct.metabib.record_descriptor.id_list.atomic',
197         { record => $bib->id }
198     )->gather(1);
199
200     $cstore->request( 'open-ils.cstore.direct.metabib.record_descriptor.delete' => $_ )->gather(1) for (@$tmp);
201     $cstore->request( 'open-ils.cstore.direct.metabib.record_descriptor.create' => $blob->{descriptor} )->gather(1);
202
203     # deal with classed fields...
204     for my $class ( qw/title author subject keyword series/ ) {
205         $tmp = $cstore->request(
206             "open-ils.cstore.direct.metabib.${class}_field_entry.id_list.atomic",
207             { source => $bib->id }
208         )->gather(1);
209
210         $cstore->request( "open-ils.cstore.direct.metabib.${class}_field_entry.delete" => $_ )->gather(1) for (@$tmp);
211     }
212     for my $obj ( @{ $blob->{field_entries} } ) {
213         my $class = $obj->class_name;
214         $class =~ s/^Fieldmapper:://o;
215         $class =~ s/::/./go;
216         $cstore->request( "open-ils.cstore.direct.$class.create" => $obj )->gather(1);
217     }
218
219     # update MR map ...
220
221     $tmp = $cstore->request(
222         'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
223         { source => $bib->id }
224     )->gather(1);
225
226     $cstore->request( 'open-ils.cstore.direct.metabib.metarecord_source_map.delete' => $_->id )->gather(1) for (@$tmp);
227
228     # get the old MRs
229     my $old_mrs = $cstore->request(
230         'open-ils.cstore.direct.metabib.metarecord.search.atomic' => { id => [map { $_->metarecord } @$tmp] }
231     )->gather(1) if (@$tmp);
232
233     $old_mrs = [] if (!ref($old_mrs));
234
235     my $mr;
236     for my $m (@$old_mrs) {
237         if ($m->fingerprint eq $bib->fingerprint) {
238             $mr = $m;
239         } else {
240             my $others = $cstore->request(
241                 'open-ils.cstore.direct.metabib.metarecord_source_map.id_list.atomic' => { metarecord => $m->id }
242             )->gather(1);
243
244             if (!@$others) {
245                 $cstore->request(
246                     'open-ils.cstore.direct.metabib.metarecord.delete' => $m->id
247                 )->gather(1);
248             }
249
250             $m->isdeleted(1);
251         }
252     }
253
254     my $holds;
255     if (!$mr) {
256         # Get the matchin MR, if any.
257         $mr = $cstore->request(
258             'open-ils.cstore.direct.metabib.metarecord.search',
259             { fingerprint => $bib->fingerprint }
260         )->gather(1);
261
262         $holds = $cstore->request(
263             'open-ils.cstore.direct.action.hold_request.search.atomic',
264             { hold_type => 'M', target => [ map { $_->id } grep { $_->isdeleted } @$old_mrs ] }
265         )->gather(1) if (@$old_mrs);
266
267         if ($mr) {
268             for my $h (@$holds) {
269                 $h->target($mr);
270                 $cstore->request( 'open-ils.cstore.direct.action.hold_request.update' => $h )->gather(1);
271                 $h->ischanged(1);
272             }
273         }
274     }
275
276     if (!$mr) {
277         $mr = new Fieldmapper::metabib::metarecord;
278         $mr->fingerprint( $bib->fingerprint );
279         $mr->master_record( $bib->id );
280         $mr->id(
281             $cstore->request(
282                 "open-ils.cstore.direct.metabib.metarecord.create",
283                 $mr => { quiet => 'true' }
284             )->gather(1)
285         );
286
287         for my $h (grep { !$_->ischanged } @$holds) {
288             $h->target($mr);
289             $cstore->request( 'open-ils.cstore.direct.action.hold_request.update' => $h )->gather(1);
290         }
291     } else {
292         my $mrm = $cstore->request(
293             'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
294             { metarecord => $mr->id }
295         )->gather(1);
296
297         if (@$mrm) {
298             my $best = $cstore->request(
299                 "open-ils.cstore.direct.biblio.record_entry.search",
300                 { id => [ map { $_->source } @$mrm ] },
301                 { 'select'    => { bre => [ qw/id quality/ ] },
302                   order_by    => { bre => "quality desc" },
303                   limit        => 1,
304                 }
305             )->gather(1);
306
307             if ($best->quality > $bib->quality) {
308                 $mr->master_record($best->id);
309             } else {
310                 $mr->master_record($bib->id);
311             }
312         } else {
313             $mr->master_record($bib->id);
314         }
315
316         $mr->clear_mods;
317
318         $cstore->request( 'open-ils.cstore.direct.metabib.metarecord.update' => $mr )->gather(1);
319     }
320
321     my $mrm = new Fieldmapper::metabib::metarecord_source_map;
322     $mrm->source($bib->id);
323     $mrm->metarecord($mr->id);
324
325     $cstore->request( 'open-ils.cstore.direct.metabib.metarecord_source_map.create' => $mrm )->gather(1);
326     $cstore->request( 'open-ils.cstore.direct.biblio.record_entry.update' => $bib )->gather(1);
327
328     $cstore->request( 'open-ils.cstore.transaction.commit' )->gather(1) || return undef;;
329     $cstore->disconnect;
330
331     return $bib->id;
332 }
333 __PACKAGE__->register_method(  
334     api_name    => "open-ils.ingest.full.biblio.object",
335     method        => "rw_biblio_ingest_single_object",
336     api_level    => 1,
337     argc        => 1,
338 );                      
339
340 sub rw_biblio_ingest_single_record {
341     my $self = shift;
342     my $client = shift;
343     my $rec = shift;
344
345     OpenILS::Application::Ingest->post_init();
346     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
347     $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
348
349     my $r = $cstore->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )->gather(1);
350
351     $cstore->request('open-ils.cstore.transaction.rollback')->gather(1);
352     $cstore->disconnect;
353
354     return undef unless ($r and @$r);
355
356     return ($self->method_lookup("open-ils.ingest.full.biblio.object")->run($r))[0];
357 }
358 __PACKAGE__->register_method(  
359     api_name    => "open-ils.ingest.full.biblio.record",
360     method        => "rw_biblio_ingest_single_record",
361     api_level    => 1,
362     argc        => 1,
363 );                      
364
365 sub rw_biblio_ingest_record_list {
366     my $self = shift;
367     my $client = shift;
368     my @rec = ref($_[0]) ? @{ $_[0] } : @_ ;
369
370     OpenILS::Application::Ingest->post_init();
371     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
372     $cstore->request('open-ils.cstore.transaction.begin')->gather(1);
373
374     my $r = $cstore->request( 'open-ils.cstore.direct.biblio.record_entry.search.atomic' => { id => $rec } )->gather(1);
375
376     $cstore->request('open-ils.cstore.transaction.rollback')->gather(1);
377     $cstore->disconnect;
378
379     return undef unless ($r and @$r);
380
381     my $count = 0;
382     $count += ($self->method_lookup("open-ils.ingest.full.biblio.object")->run($_))[0] for (@$r);
383
384     return $count;
385 }
386 __PACKAGE__->register_method(  
387     api_name    => "open-ils.ingest.full.biblio.record_list",
388     method        => "rw_biblio_ingest_record_list",
389     api_level    => 1,
390     argc        => 1,
391 );                      
392
393 sub ro_biblio_ingest_single_object {
394     my $self = shift;
395     my $client = shift;
396     my $bib = shift;
397     my $xml = OpenILS::Application::AppUtils->entityize($bib->marc);
398     my $max_cn = shift;
399     my $max_uri = shift;
400
401     my $cstore = OpenSRF::AppSession->connect( 'open-ils.cstore' );
402
403     if (!$max_cn) {
404         my $cn = $cstore->request( 'open-ils.cstore.direct.asset.call_number.search' => { id => { '!=' => undef } }, { limit => 1, order_by => { acn => 'id desc' } } )->gather(1);
405         $max_cn = int($cn->id) + 1000;
406     }
407
408     if (!$max_uri) {
409         my $cn = $cstore->request( 'open-ils.cstore.direct.asset.call_number.search' => { id => { '!=' => undef } }, { limit => 1, order_by => { acn => 'id desc' } } )->gather(1);
410         $max_uri = int($cn->id) + 1000;
411     }
412
413     $cstore->disconnect;
414
415     my $document = $parser->parse_string($xml);
416
417     my @uris = $self->method_lookup("open-ils.ingest.856_uri.object")->run($bib, $max_cn, $max_uri);
418     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.biblio.xml")->run($document);
419     my @mXfe = $self->method_lookup("open-ils.ingest.extract.field_entry.all.xml")->run($document);
420     my ($fp) = $self->method_lookup("open-ils.ingest.fingerprint.xml")->run($xml);
421     my ($rd) = $self->method_lookup("open-ils.ingest.descriptor.xml")->run($xml);
422
423     $_->source($bib->id) for (@mXfe);
424     $_->record($bib->id) for (@mfr);
425     $rd->record($bib->id) if ($rd);
426
427     return { full_rec => \@mfr, field_entries => \@mXfe, fingerprint => $fp, descriptor => $rd, uri => \@uris };
428 }
429 __PACKAGE__->register_method(  
430     api_name    => "open-ils.ingest.full.biblio.object.readonly",
431     method        => "ro_biblio_ingest_single_object",
432     api_level    => 1,
433     argc        => 1,
434 );                      
435
436 sub ro_biblio_ingest_single_xml {
437     my $self = shift;
438     my $client = shift;
439     my $xml = OpenILS::Application::AppUtils->entityize(shift);
440
441     my $document = $parser->parse_string($xml);
442
443     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.biblio.xml")->run($document);
444     my @mXfe = $self->method_lookup("open-ils.ingest.extract.field_entry.all.xml")->run($document);
445     my ($fp) = $self->method_lookup("open-ils.ingest.fingerprint.xml")->run($xml);
446     my ($rd) = $self->method_lookup("open-ils.ingest.descriptor.xml")->run($xml);
447
448     return { full_rec => \@mfr, field_entries => \@mXfe, fingerprint => $fp, descriptor => $rd };
449 }
450 __PACKAGE__->register_method(  
451     api_name    => "open-ils.ingest.full.biblio.xml.readonly",
452     method        => "ro_biblio_ingest_single_xml",
453     api_level    => 1,
454     argc        => 1,
455 );                      
456
457 sub ro_biblio_ingest_single_record {
458     my $self = shift;
459     my $client = shift;
460     my $rec = shift;
461
462     OpenILS::Application::Ingest->post_init();
463     my $r = OpenSRF::AppSession
464             ->create('open-ils.cstore')
465             ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )
466             ->gather(1);
467
468     return undef unless ($r and @$r);
469
470     my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.xml.readonly")->run($r->marc);
471
472     $_->source($rec) for (@{$res->{field_entries}});
473     $_->record($rec) for (@{$res->{full_rec}});
474     $res->{descriptor}->record($rec);
475
476     return $res;
477 }
478 __PACKAGE__->register_method(  
479     api_name    => "open-ils.ingest.full.biblio.record.readonly",
480     method        => "ro_biblio_ingest_single_record",
481     api_level    => 1,
482     argc        => 1,
483 );                      
484
485 sub ro_biblio_ingest_stream_record {
486     my $self = shift;
487     my $client = shift;
488
489     OpenILS::Application::Ingest->post_init();
490
491     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
492
493     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
494     
495         my $rec = $resp->content;
496         last unless (defined $rec);
497
498         $log->debug("Running open-ils.ingest.full.biblio.record.readonly ...");
499         my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.record.readonly")->run($rec);
500
501         $_->source($rec) for (@{$res->{field_entries}});
502         $_->record($rec) for (@{$res->{full_rec}});
503
504         $client->respond( $res );
505     }
506
507     return undef;
508 }
509 __PACKAGE__->register_method(  
510     api_name    => "open-ils.ingest.full.biblio.record_stream.readonly",
511     method        => "ro_biblio_ingest_stream_record",
512     api_level    => 1,
513     stream        => 1,
514 );                      
515
516 sub ro_biblio_ingest_stream_xml {
517     my $self = shift;
518     my $client = shift;
519
520     OpenILS::Application::Ingest->post_init();
521
522     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
523
524     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
525     
526         my $xml = $resp->content;
527         last unless (defined $xml);
528
529         $log->debug("Running open-ils.ingest.full.biblio.xml.readonly ...");
530         my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.xml.readonly")->run($xml);
531
532         $client->respond( $res );
533     }
534
535     return undef;
536 }
537 __PACKAGE__->register_method(  
538     api_name    => "open-ils.ingest.full.biblio.xml_stream.readonly",
539     method        => "ro_biblio_ingest_stream_xml",
540     api_level    => 1,
541     stream        => 1,
542 );                      
543
544 sub rw_biblio_ingest_stream_import {
545     my $self = shift;
546     my $client = shift;
547
548     OpenILS::Application::Ingest->post_init();
549
550     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
551
552     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
553     
554         my $bib = $resp->content;
555         last unless (defined $bib);
556
557         $log->debug("Running open-ils.ingest.full.biblio.xml.readonly ...");
558         my ($res) = $self->method_lookup("open-ils.ingest.full.biblio.xml.readonly")->run($bib->marc);
559
560         $_->source($bib->id) for (@{$res->{field_entries}});
561         $_->record($bib->id) for (@{$res->{full_rec}});
562
563         $client->respond( $res );
564     }
565
566     return undef;
567 }
568 __PACKAGE__->register_method(  
569     api_name    => "open-ils.ingest.full.biblio.bib_stream.import",
570     method        => "rw_biblio_ingest_stream_import",
571     api_level    => 1,
572     stream        => 1,
573 );                      
574
575
576 # --------------------------------------------------------------------------------
577 # Authority ingest
578
579 package OpenILS::Application::Ingest::Authority;
580 use base qw/OpenILS::Application::Ingest/;
581 use Unicode::Normalize;
582
583 sub ro_authority_ingest_single_object {
584     my $self = shift;
585     my $client = shift;
586     my $bib = shift;
587     my $xml = OpenILS::Application::AppUtils->entityize($bib->marc);
588
589     my $document = $parser->parse_string($xml);
590
591     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.authority.xml")->run($document);
592
593     $_->record($bib->id) for (@mfr);
594
595     return { full_rec => \@mfr };
596 }
597 __PACKAGE__->register_method(  
598     api_name    => "open-ils.ingest.full.authority.object.readonly",
599     method        => "ro_authority_ingest_single_object",
600     api_level    => 1,
601     argc        => 1,
602 );                      
603 __PACKAGE__->register_method(  
604     api_name    => "open-ils.ingest.full.serial.object.readonly",
605     method        => "ro_authority_ingest_single_object",
606     api_level    => 1,
607     argc        => 1,
608 );                      
609
610
611 sub ro_authority_ingest_single_xml {
612     my $self = shift;
613     my $client = shift;
614     my $xml = OpenILS::Application::AppUtils->entityize(shift);
615
616     my $document = $parser->parse_string($xml);
617
618     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.authority.xml")->run($document);
619
620     return { full_rec => \@mfr };
621 }
622 __PACKAGE__->register_method(  
623     api_name    => "open-ils.ingest.full.authority.xml.readonly",
624     method        => "ro_authority_ingest_single_xml",
625     api_level    => 1,
626     argc        => 1,
627 );                      
628
629 sub ro_authority_ingest_single_record {
630     my $self = shift;
631     my $client = shift;
632     my $rec = shift;
633
634     OpenILS::Application::Ingest->post_init();
635     my $r = OpenSRF::AppSession
636             ->create('open-ils.cstore')
637             ->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rec )
638             ->gather(1);
639
640     return undef unless ($r and @$r);
641
642     my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($r->marc);
643
644     $_->record($rec) for (@{$res->{full_rec}});
645     $res->{descriptor}->record($rec);
646
647     return $res;
648 }
649 __PACKAGE__->register_method(  
650     api_name    => "open-ils.ingest.full.authority.record.readonly",
651     method        => "ro_authority_ingest_single_record",
652     api_level    => 1,
653     argc        => 1,
654 );                      
655
656 sub ro_authority_ingest_stream_record {
657     my $self = shift;
658     my $client = shift;
659
660     OpenILS::Application::Ingest->post_init();
661
662     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
663
664     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
665     
666         my $rec = $resp->content;
667         last unless (defined $rec);
668
669         $log->debug("Running open-ils.ingest.full.authority.record.readonly ...");
670         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.record.readonly")->run($rec);
671
672         $_->record($rec) for (@{$res->{full_rec}});
673
674         $client->respond( $res );
675     }
676
677     return undef;
678 }
679 __PACKAGE__->register_method(  
680     api_name    => "open-ils.ingest.full.authority.record_stream.readonly",
681     method        => "ro_authority_ingest_stream_record",
682     api_level    => 1,
683     stream        => 1,
684 );                      
685
686 sub ro_authority_ingest_stream_xml {
687     my $self = shift;
688     my $client = shift;
689
690     OpenILS::Application::Ingest->post_init();
691
692     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
693
694     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
695     
696         my $xml = $resp->content;
697         last unless (defined $xml);
698
699         $log->debug("Running open-ils.ingest.full.authority.xml.readonly ...");
700         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($xml);
701
702         $client->respond( $res );
703     }
704
705     return undef;
706 }
707 __PACKAGE__->register_method(  
708     api_name    => "open-ils.ingest.full.authority.xml_stream.readonly",
709     method        => "ro_authority_ingest_stream_xml",
710     api_level    => 1,
711     stream        => 1,
712 );                      
713
714 sub rw_authority_ingest_stream_import {
715     my $self = shift;
716     my $client = shift;
717
718     OpenILS::Application::Ingest->post_init();
719
720     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
721
722     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
723     
724         my $bib = $resp->content;
725         last unless (defined $bib);
726
727         $log->debug("Running open-ils.ingest.full.authority.xml.readonly ...");
728         my ($res) = $self->method_lookup("open-ils.ingest.full.authority.xml.readonly")->run($bib->marc);
729
730         $_->record($bib->id) for (@{$res->{full_rec}});
731
732         $client->respond( $res );
733     }
734
735     return undef;
736 }
737 __PACKAGE__->register_method(  
738     api_name    => "open-ils.ingest.full.authority.bib_stream.import",
739     method        => "rw_authority_ingest_stream_import",
740     api_level    => 1,
741     stream        => 1,
742 );                      
743
744 # --------------------------------------------------------------------------------
745 # Serial ingest
746
747 package OpenILS::Application::Ingest::Serial;
748 use base qw/OpenILS::Application::Ingest/;
749 use Unicode::Normalize;
750
751 sub ro_serial_ingest_single_object {
752     my $self = shift;
753     my $client = shift;
754     my $bib = shift;
755     my $xml = OpenILS::Application::AppUtils->entityize($bib->marc);
756
757     my $document = $parser->parse_string($xml);
758
759     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.serial.xml")->run($document);
760
761     $_->record($bib->id) for (@mfr);
762
763     return { full_rec => \@mfr };
764 }
765 __PACKAGE__->register_method(  
766     api_name    => "open-ils.ingest.full.serial.object.readonly",
767     method        => "ro_serial_ingest_single_object",
768     api_level    => 1,
769     argc        => 1,
770 );                      
771
772 sub ro_serial_ingest_single_xml {
773     my $self = shift;
774     my $client = shift;
775     my $xml = OpenILS::Application::AppUtils->entityize(shift);
776
777     my $document = $parser->parse_string($xml);
778
779     my @mfr = $self->method_lookup("open-ils.ingest.flat_marc.serial.xml")->run($document);
780
781     return { full_rec => \@mfr };
782 }
783 __PACKAGE__->register_method(  
784     api_name    => "open-ils.ingest.full.serial.xml.readonly",
785     method        => "ro_serial_ingest_single_xml",
786     api_level    => 1,
787     argc        => 1,
788 );                      
789
790 sub ro_serial_ingest_single_record {
791     my $self = shift;
792     my $client = shift;
793     my $rec = shift;
794
795     OpenILS::Application::Ingest->post_init();
796     my $r = OpenSRF::AppSession
797             ->create('open-ils.cstore')
798             ->request( 'open-ils.cstore.direct.serial.record_entry.retrieve' => $rec )
799             ->gather(1);
800
801     return undef unless ($r and @$r);
802
803     my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($r->marc);
804
805     $_->record($rec) for (@{$res->{full_rec}});
806     $res->{descriptor}->record($rec);
807
808     return $res;
809 }
810 __PACKAGE__->register_method(  
811     api_name    => "open-ils.ingest.full.serial.record.readonly",
812     method        => "ro_serial_ingest_single_record",
813     api_level    => 1,
814     argc        => 1,
815 );                      
816
817 sub ro_serial_ingest_stream_record {
818     my $self = shift;
819     my $client = shift;
820
821     OpenILS::Application::Ingest->post_init();
822
823     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
824
825     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
826     
827         my $rec = $resp->content;
828         last unless (defined $rec);
829
830         $log->debug("Running open-ils.ingest.full.serial.record.readonly ...");
831         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.record.readonly")->run($rec);
832
833         $_->record($rec) for (@{$res->{full_rec}});
834
835         $client->respond( $res );
836     }
837
838     return undef;
839 }
840 __PACKAGE__->register_method(  
841     api_name    => "open-ils.ingest.full.serial.record_stream.readonly",
842     method        => "ro_serial_ingest_stream_record",
843     api_level    => 1,
844     stream        => 1,
845 );                      
846
847 sub ro_serial_ingest_stream_xml {
848     my $self = shift;
849     my $client = shift;
850
851     OpenILS::Application::Ingest->post_init();
852
853     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
854
855     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
856     
857         my $xml = $resp->content;
858         last unless (defined $xml);
859
860         $log->debug("Running open-ils.ingest.full.serial.xml.readonly ...");
861         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($xml);
862
863         $client->respond( $res );
864     }
865
866     return undef;
867 }
868 __PACKAGE__->register_method(  
869     api_name    => "open-ils.ingest.full.serial.xml_stream.readonly",
870     method        => "ro_serial_ingest_stream_xml",
871     api_level    => 1,
872     stream        => 1,
873 );                      
874
875 sub rw_serial_ingest_stream_import {
876     my $self = shift;
877     my $client = shift;
878
879     OpenILS::Application::Ingest->post_init();
880
881     my $ses = OpenSRF::AppSession->create('open-ils.cstore');
882
883     while (my ($resp) = $client->recv( count => 1, timeout => 5 )) {
884     
885         my $bib = $resp->content;
886         last unless (defined $bib);
887
888         $log->debug("Running open-ils.ingest.full.serial.xml.readonly ...");
889         my ($res) = $self->method_lookup("open-ils.ingest.full.serial.xml.readonly")->run($bib->marc);
890
891         $_->record($bib->id) for (@{$res->{full_rec}});
892
893         $client->respond( $res );
894     }
895
896     return undef;
897 }
898 __PACKAGE__->register_method(  
899     api_name    => "open-ils.ingest.full.serial.bib_stream.import",
900     method        => "rw_serial_ingest_stream_import",
901     api_level    => 1,
902     stream        => 1,
903 );                      
904
905
906 # --------------------------------------------------------------------------------
907 # MARC index extraction
908
909 package OpenILS::Application::Ingest::XPATH;
910 use base qw/OpenILS::Application::Ingest/;
911 use Unicode::Normalize;
912
913 # give this an XML documentElement and an XPATH expression
914 sub xpath_to_string {
915     my $xml = shift;
916     my $xpath = shift;
917     my $ns_uri = shift;
918     my $ns_prefix = shift;
919     my $unique = shift;
920
921     $xml->setNamespace( $ns_uri, $ns_prefix, 1 ) if ($ns_uri && $ns_prefix);
922
923     my $string = "";
924
925     # grab the set of matching nodes
926     my @nodes = $xml->findnodes( $xpath );
927     for my $value (@nodes) {
928
929         # grab all children of the node
930         my @children = $value->childNodes();
931         for my $child (@children) {
932
933             # add the childs content to the growing buffer
934             my $content = quotemeta($child->textContent);
935             next if ($unique && $string =~ /$content/);  # uniquify the values
936             $string .= $child->textContent . " ";
937         }
938         if( ! @children ) {
939             $string .= $value->textContent . " ";
940         }
941     }
942
943     $string =~ s/(\w+)\/(\w+)/$1 $2/sgo;
944     $string =~ s/(\d{4})-(\d{4})/$1 $2/sgo;
945
946     return NFD($string);
947 }
948
949 sub class_index_string_xml {
950     my $self = shift;
951     my $client = shift;
952     my $xml = shift;
953     my @classes = @_;
954
955     OpenILS::Application::Ingest->post_init();
956     $xml = $parser->parse_string(OpenILS::Application::AppUtils->entityize($xml)) unless (ref $xml);
957
958     my %transform_cache;
959     
960     for my $class (@classes) {
961         my $class_constructor = "Fieldmapper::metabib::${class}_field_entry";
962         for my $type ( keys %{ $xpathset->{$class} } ) {
963
964             my $def = $xpathset->{$class}->{$type};
965             my $sf = $OpenILS::Application::Ingest::supported_formats{$def->{format}};
966
967             my $document = $xml;
968
969             if ($sf->{xslt}) {
970                 $document = $transform_cache{$def->{format}} || $sf->{xslt}->transform($xml);
971                 $transform_cache{$def->{format}} = $document;
972             }
973
974             my $value =  xpath_to_string(
975                     $document->documentElement    => $def->{xpath},
976                     $sf->{ns}            => $def->{format},
977                     1
978             );
979
980             next unless $value;
981
982             $value = NFD($value);
983             $value =~ s/\pM+//sgo;
984             $value =~ s/\pC+//sgo;
985             $value =~ s/\W+$//sgo;
986
987             $value =~ s/\b\.+\b//sgo;
988             $value = lc($value);
989
990             my $fm = $class_constructor->new;
991             $fm->value( $value );
992             $fm->field( $xpathset->{$class}->{$type}->{id} );
993             $client->respond($fm);
994         }
995     }
996     return undef;
997 }
998 __PACKAGE__->register_method(  
999     api_name    => "open-ils.ingest.field_entry.class.xml",
1000     method        => "class_index_string_xml",
1001     api_level    => 1,
1002     argc        => 2,
1003     stream        => 1,
1004 );                      
1005
1006 sub class_index_string_record {
1007     my $self = shift;
1008     my $client = shift;
1009     my $rec = shift;
1010     my @classes = shift;
1011
1012     OpenILS::Application::Ingest->post_init();
1013     my $r = OpenSRF::AppSession
1014             ->create('open-ils.cstore')
1015             ->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rec )
1016             ->gather(1);
1017
1018     return undef unless ($r and @$r);
1019
1020     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($r->marc, @classes)) {
1021         $fm->source($rec);
1022         $client->respond($fm);
1023     }
1024     return undef;
1025 }
1026 __PACKAGE__->register_method(  
1027     api_name    => "open-ils.ingest.field_entry.class.record",
1028     method        => "class_index_string_record",
1029     api_level    => 1,
1030     argc        => 2,
1031     stream        => 1,
1032 );                      
1033
1034 sub all_index_string_xml {
1035     my $self = shift;
1036     my $client = shift;
1037     my $xml = shift;
1038
1039     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($xml, keys(%$xpathset))) {
1040         $client->respond($fm);
1041     }
1042     return undef;
1043 }
1044 __PACKAGE__->register_method(  
1045     api_name    => "open-ils.ingest.extract.field_entry.all.xml",
1046     method        => "all_index_string_xml",
1047     api_level    => 1,
1048     argc        => 1,
1049     stream        => 1,
1050 );                      
1051
1052 sub all_index_string_record {
1053     my $self = shift;
1054     my $client = shift;
1055     my $rec = shift;
1056
1057     OpenILS::Application::Ingest->post_init();
1058     my $r = OpenSRF::AppSession
1059             ->create('open-ils.cstore')
1060             ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )
1061             ->gather(1);
1062
1063     return undef unless ($r and @$r);
1064
1065     for my $fm ($self->method_lookup("open-ils.ingest.field_entry.class.xml")->run($r->marc, keys(%$xpathset))) {
1066         $fm->source($rec);
1067         $client->respond($fm);
1068     }
1069     return undef;
1070 }
1071 __PACKAGE__->register_method(  
1072     api_name    => "open-ils.ingest.extract.field_entry.all.record",
1073     method        => "all_index_string_record",
1074     api_level    => 1,
1075     argc        => 1,
1076     stream        => 1,
1077 );                      
1078
1079 # --------------------------------------------------------------------------------
1080 # Flat MARC
1081
1082 package OpenILS::Application::Ingest::FlatMARC;
1083 use base qw/OpenILS::Application::Ingest/;
1084 use Unicode::Normalize;
1085
1086
1087 sub _marcxml_to_full_rows {
1088
1089     my $marcxml = shift;
1090     my $xmltype = shift || 'metabib';
1091
1092     my $type = "Fieldmapper::${xmltype}::full_rec";
1093
1094     my @ns_list;
1095     
1096     my ($root) = $marcxml->findnodes('//*[local-name()="record"]');
1097
1098     for my $tagline ( @{$root->getChildrenByTagName("leader")} ) {
1099         next unless $tagline;
1100
1101         my $ns = $type->new;
1102
1103         $ns->tag( 'LDR' );
1104         my $val = $tagline->textContent;
1105         $val = NFD($val);
1106         $val =~ s/\pM+//sgo;
1107         $val =~ s/\pC+//sgo;
1108         $val =~ s/\W+$//sgo;
1109         $ns->value( $val );
1110
1111         push @ns_list, $ns;
1112     }
1113
1114     for my $tagline ( @{$root->getChildrenByTagName("controlfield")} ) {
1115         next unless $tagline;
1116
1117         my $ns = $type->new;
1118
1119         $ns->tag( $tagline->getAttribute( "tag" ) );
1120         my $val = $tagline->textContent;
1121         $val = NFD($val);
1122         $val =~ s/\pM+//sgo;
1123         $val =~ s/\pC+//sgo;
1124         $val =~ s/\W+$//sgo;
1125         $ns->value( $val );
1126
1127         push @ns_list, $ns;
1128     }
1129
1130     for my $tagline ( @{$root->getChildrenByTagName("datafield")} ) {
1131         next unless $tagline;
1132
1133         my $tag = $tagline->getAttribute( "tag" );
1134         my $ind1 = $tagline->getAttribute( "ind1" );
1135         my $ind2 = $tagline->getAttribute( "ind2" );
1136
1137         for my $data ( @{$tagline->getChildrenByTagName('subfield')} ) {
1138             next unless $data;
1139
1140             my $ns = $type->new;
1141
1142             $ns->tag( $tag );
1143             $ns->ind1( $ind1 );
1144             $ns->ind2( $ind2 );
1145             $ns->subfield( $data->getAttribute( "code" ) );
1146             my $val = $data->textContent;
1147             $val = NFD($val);
1148             $val =~ s/\pM+//sgo;
1149             $val =~ s/\pC+//sgo;
1150             $val =~ s/\W+$//sgo;
1151             $val =~ s/(\d{4})-(\d{4})/$1 $2/sgo;
1152             $val =~ s/(\w+)\/(\w+)/$1 $2/sgo;
1153             $ns->value( lc($val) );
1154
1155             push @ns_list, $ns;
1156         }
1157
1158         if ($xmltype eq 'metabib' and $tag eq '245') {
1159                $tag = 'tnf';
1160     
1161             for my $data ( @{$tagline->getChildrenByTagName('subfield')} ) {
1162                 next unless ($data and $data->getAttribute( "code" ) eq 'a');
1163     
1164                 $ns = $type->new;
1165     
1166                 $ns->tag( $tag );
1167                 $ns->ind1( $ind1 );
1168                 $ns->ind2( $ind2 );
1169                 $ns->subfield( $data->getAttribute( "code" ) );
1170                 my $val = substr( $data->textContent, $ind2 );
1171                 $val = NFD($val);
1172                 $val =~ s/\pM+//sgo;
1173                 $val =~ s/\pC+//sgo;
1174                 $val =~ s/\W+$//sgo;
1175                 $val =~ s/(\w+)\/(\w+)/$1 $2/sgo;
1176                 $val =~ s/(\d{4})-(\d{4})/$1 $2/sgo;
1177                 $ns->value( lc($val) );
1178     
1179                 push @ns_list, $ns;
1180             }
1181         }
1182     }
1183
1184     $log->debug("Returning ".scalar(@ns_list)." Fieldmapper nodes from $xmltype xml");
1185     return @ns_list;
1186 }
1187
1188 sub flat_marc_xml {
1189     my $self = shift;
1190     my $client = shift;
1191     my $xml = shift;
1192
1193     $log->debug("processing [$xml]");
1194
1195     $xml = $parser->parse_string(OpenILS::Application::AppUtils->entityize($xml)) unless (ref $xml);
1196
1197     my $type = 'metabib';
1198     $type = 'authority' if ($self->api_name =~ /authority/o);
1199     $type = 'serial' if ($self->api_name =~ /serial/o);
1200
1201     OpenILS::Application::Ingest->post_init();
1202
1203     $client->respond($_) for (_marcxml_to_full_rows($xml, $type));
1204     return undef;
1205 }
1206 __PACKAGE__->register_method(  
1207     api_name    => "open-ils.ingest.flat_marc.authority.xml",
1208     method        => "flat_marc_xml",
1209     api_level    => 1,
1210     argc        => 1,
1211     stream        => 1,
1212 );                      
1213 __PACKAGE__->register_method(  
1214     api_name    => "open-ils.ingest.flat_marc.biblio.xml",
1215     method        => "flat_marc_xml",
1216     api_level    => 1,
1217     argc        => 1,
1218     stream        => 1,
1219 );                      
1220 __PACKAGE__->register_method(  
1221     api_name    => "open-ils.ingest.flat_marc.serial.xml",
1222     method        => "flat_marc_xml",
1223     api_level    => 1,
1224     argc        => 1,
1225     stream        => 1,
1226 );                      
1227
1228 sub flat_marc_record {
1229     my $self = shift;
1230     my $client = shift;
1231     my $rec = shift;
1232
1233     my $type = 'biblio';
1234     $type = 'authority' if ($self->api_name =~ /authority/o);
1235     $type = 'serial' if ($self->api_name =~ /serial/o);
1236
1237     OpenILS::Application::Ingest->post_init();
1238     my $r = OpenSRF::AppSession
1239             ->create('open-ils.cstore')
1240             ->request( "open-ils.cstore.direct.${type}.record_entry.retrieve" => $rec )
1241             ->gather(1);
1242
1243
1244     return undef unless ($r and $r->marc);
1245
1246     my @rows = $self->method_lookup("open-ils.ingest.flat_marc.$type.xml")->run($r->marc);
1247     for my $row (@rows) {
1248         $client->respond($row);
1249         $log->debug(OpenSRF::Utils::JSON->perl2JSON($row), DEBUG);
1250     }
1251     return undef;
1252 }
1253 __PACKAGE__->register_method(  
1254     api_name    => "open-ils.ingest.flat_marc.biblio.record_entry",
1255     method        => "flat_marc_record",
1256     api_level    => 1,
1257     argc        => 1,
1258     stream        => 1,
1259 );                      
1260 __PACKAGE__->register_method(  
1261     api_name    => "open-ils.ingest.flat_marc.authority.record_entry",
1262     method        => "flat_marc_record",
1263     api_level    => 1,
1264     argc        => 1,
1265     stream        => 1,
1266 );                      
1267 __PACKAGE__->register_method(  
1268     api_name    => "open-ils.ingest.flat_marc.serial.record_entry",
1269     method        => "flat_marc_record",
1270     api_level    => 1,
1271     argc        => 1,
1272     stream        => 1,
1273 );                      
1274
1275 # --------------------------------------------------------------------------------
1276 # URI extraction
1277
1278 package OpenILS::Application::Ingest::Biblio::URI;
1279 use base qw/OpenILS::Application::Ingest/;
1280 use Unicode::Normalize;
1281 use OpenSRF::EX qw/:try/;
1282
1283
1284 sub _extract_856_uris {
1285
1286     my $rec   = shift;
1287     my $max_cn = shift;
1288     my $max_uri = shift;
1289     my @objects;
1290     
1291     my $recid = $rec->id;
1292     my $marcxml = $rec->marc;
1293
1294     my $document = $parser->parse_string($marcxml);
1295     my @nodes = $document->findnodes('//*[local-name()="datafield" and @tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]');
1296
1297     my $cstore = OpenSRF::AppSession->connect('open-ils.cstore');
1298
1299     my %cn_cache;
1300
1301     for my $node (@nodes) {
1302         # first, is there a URI?
1303         my $href = $node->findvalue('*[local-name()="subfield" and @code="u"]/text()');
1304         next unless ($href);
1305
1306         # now, find the best possible label
1307         my $label = $node->findvalue('*[local-name()="subfield" and @code="y"]/text()');
1308         $label ||= $node->findvalue('*[local-name()="subfield" and @code="3"]/text()');
1309         $label ||= $href;
1310
1311         # look for use info
1312         my $use = $node->findvalue('*[local-name()="subfield" and @code="z"]/text()');
1313         $use ||= $node->findvalue('*[local-name()="subfield" and @code="2"]/text()');
1314         $use ||= $node->findvalue('*[local-name()="subfield" and @code="n"]/text()');
1315
1316         # moving on to the URI owner
1317         my $owner = $node->findvalue('*[local-name()="subfield" and @code="w"]/text()');
1318         $owner ||= $node->findvalue('*[local-name()="subfield" and @code="n"]/text()');
1319         $owner ||= $node->findvalue('*[local-name()="subfield" and @code="9"]/text()'); # Evergreen special sauce
1320
1321         $owner =~ s/^.*?\((\w+)\).*$/$1/o; # unwrap first paren-enclosed string and then ...
1322
1323         # no owner? skip it :(
1324         next unless ($owner);
1325
1326         my $org = $cstore
1327             ->request( 'open-ils.cstore.direct.actor.org_unit.search' => { shortname => $owner} )
1328             ->gather(1);
1329
1330         next unless ($org);
1331
1332         # now we can construct the uri object
1333         my $uri = $cstore
1334             ->request( 'open-ils.cstore.direct.asset.uri.search' => { label => $label, href => $href, use_restriction => $use, active => 't' } )
1335             ->gather(1);
1336
1337         if (!$uri) {
1338             $uri = Fieldmapper::asset::uri->new;
1339             $uri->isnew( 1 );
1340             $uri->id( $$max_uri++ );
1341             $uri->label($label);
1342             $uri->href($href);
1343             $uri->active('t');
1344             $uri->use_restriction($use);
1345         }
1346
1347         # see if we need to create a call number
1348         my $cn = $cn_cache{$org->id};
1349         $cn = $cn->clone if ($cn);
1350         $cn->clear_isnew if ($cn);
1351
1352         $cn ||= $cstore
1353             ->request( 'open-ils.cstore.direct.asset.call_number.search' => { owning_lib => $org->id, record => $recid, label => '##URI##' } )
1354             ->gather(1);
1355
1356         if (!$cn) {
1357             $cn = Fieldmapper::asset::call_number->new;
1358             $cn->isnew( 1 );
1359             $cn->deleted('f');
1360             $cn->id( $$max_cn++ );
1361             $cn->owning_lib( $org->id );
1362             $cn->record( $recid );
1363             $cn->create_date( 'now' );
1364             $cn->creator( $rec->creator );
1365             $cn->editor( $rec->editor );
1366             $cn->edit_date( 'now' );
1367             $cn->label( '##URI##' );
1368         }
1369
1370         $cn_cache{$org->id} = $cn;
1371
1372         push @objects, { uri => $uri, call_number => $cn };
1373     }
1374
1375     $log->debug("Returning ".scalar(@objects)." URI nodes for record $recid");
1376     return @objects;
1377 }
1378
1379 sub get_uris_record {
1380     my $self = shift;
1381     my $client = shift;
1382     my $rec = shift;
1383
1384     OpenILS::Application::Ingest->post_init();
1385     my $r = OpenSRF::AppSession
1386             ->create('open-ils.cstore')
1387             ->request( "open-ils.cstore.direct.biblio.record_entry.retrieve" => $rec )
1388             ->gather(1);
1389
1390     return undef unless ($r and $r->marc);
1391
1392     $client->respond($_) for (_extract_856_uris($r));
1393     return undef;
1394 }
1395 __PACKAGE__->register_method(  
1396     api_name    => "open-ils.ingest.856_uri.record",
1397     method        => "get_uris_record",
1398     api_level    => 1,
1399     argc        => 1,
1400     stream        => 1,
1401 );                      
1402
1403 sub get_uris_object {
1404     my $self = shift;
1405     my $client = shift;
1406     my $obj = shift;
1407     my $max_cn = shift;
1408     my $max_uri = shift;
1409
1410     return undef unless ($obj and $obj->marc);
1411
1412     $client->respond($_) for (_extract_856_uris($obj, \$max_cn, \$max_uri));
1413     return undef;
1414 }
1415 __PACKAGE__->register_method(  
1416     api_name    => "open-ils.ingest.856_uri.object",
1417     method        => "get_uris_object",
1418     api_level    => 1,
1419     argc        => 1,
1420     stream        => 1,
1421 );                      
1422
1423
1424 # --------------------------------------------------------------------------------
1425 # Fingerprinting
1426
1427 package OpenILS::Application::Ingest::Biblio::Fingerprint;
1428 use base qw/OpenILS::Application::Ingest/;
1429 use Unicode::Normalize;
1430 use OpenSRF::EX qw/:try/;
1431
1432 sub biblio_fingerprint_record {
1433     my $self = shift;
1434     my $client = shift;
1435     my $rec = shift;
1436
1437     OpenILS::Application::Ingest->post_init();
1438
1439     my $r = OpenSRF::AppSession
1440             ->create('open-ils.cstore')
1441             ->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rec )
1442             ->gather(1);
1443
1444     return undef unless ($r and $r->marc);
1445
1446     my ($fp) = $self->method_lookup('open-ils.ingest.fingerprint.xml')->run($r->marc);
1447     $log->debug("Returning [$fp] as fingerprint for record $rec", INFO);
1448     $fp->{quality} = int($fp->{quality});
1449     return $fp;
1450 }
1451 __PACKAGE__->register_method(  
1452     api_name    => "open-ils.ingest.fingerprint.record",
1453     method        => "biblio_fingerprint_record",
1454     api_level    => 1,
1455     argc        => 1,
1456 );                      
1457
1458 our $fp_script;
1459 sub biblio_fingerprint {
1460     my $self = shift;
1461     my $client = shift;
1462     my $xml = OpenILS::Application::AppUtils->entityize(shift);
1463
1464     $log->internal("Got MARC [$xml]");
1465
1466     if(!$fp_script) {
1467         my @pfx = ( "apps", "open-ils.ingest","app_settings" );
1468         my $conf = OpenSRF::Utils::SettingsClient->new;
1469
1470         my $libs        = $conf->config_value(@pfx, 'script_path');
1471         my $script_file = $conf->config_value(@pfx, 'scripts', 'biblio_fingerprint');
1472         my $script_libs = (ref($libs)) ? $libs : [$libs];
1473
1474         $log->debug("Loading script $script_file for biblio fingerprinting...");
1475         
1476         $fp_script = new OpenILS::Utils::ScriptRunner
1477             ( file        => $script_file,
1478               paths        => $script_libs,
1479               reset_count    => 100 );
1480     }
1481
1482     $fp_script->insert('environment' => {marc => $xml} => 1);
1483
1484     my $res = $fp_script->run || ($log->error( "Fingerprint script died!  $@" ) && return undef);
1485     $log->debug("Script for biblio fingerprinting completed successfully...");
1486
1487     return $res;
1488 }
1489 __PACKAGE__->register_method(  
1490     api_name    => "open-ils.ingest.fingerprint.xml",
1491     method        => "biblio_fingerprint",
1492     api_level    => 1,
1493     argc        => 1,
1494 );                      
1495
1496 our $rd_script;
1497 sub biblio_descriptor {
1498     my $self = shift;
1499     my $client = shift;
1500     my $xml = OpenILS::Application::AppUtils->entityize(shift);
1501
1502     $log->internal("Got MARC [$xml]");
1503
1504     if(!$rd_script) {
1505         my @pfx = ( "apps", "open-ils.ingest","app_settings" );
1506         my $conf = OpenSRF::Utils::SettingsClient->new;
1507
1508         my $libs        = $conf->config_value(@pfx, 'script_path');
1509         my $script_file = $conf->config_value(@pfx, 'scripts', 'biblio_descriptor');
1510         my $script_libs = (ref($libs)) ? $libs : [$libs];
1511
1512         $log->debug("Loading script $script_file for biblio descriptor extraction...");
1513         
1514         $rd_script = new OpenILS::Utils::ScriptRunner
1515             ( file        => $script_file,
1516               paths        => $script_libs,
1517               reset_count    => 100 );
1518     }
1519
1520     $log->debug("Setting up environment for descriptor extraction script...");
1521     $rd_script->insert('environment.marc' => $xml => 1);
1522     $log->debug("Environment building complete...");
1523
1524     my $res = $rd_script->run || ($log->error( "Descriptor script died!  $@" ) && return undef);
1525     $log->debug("Script for biblio descriptor extraction completed successfully");
1526
1527     my $d1 = $res->date1;
1528     if ($d1 && $d1 ne '    ') {
1529         $d1 =~ tr/ux/00/;
1530         $res->date1( $d1 );
1531     }
1532
1533     my $d2 = $res->date2;
1534     if ($d2 && $d2 ne '    ') {
1535         $d2 =~ tr/ux/99/;
1536         $res->date2( $d2 );
1537     }
1538
1539     return $res;
1540 }
1541 __PACKAGE__->register_method(  
1542     api_name    => "open-ils.ingest.descriptor.xml",
1543     method        => "biblio_descriptor",
1544     api_level    => 1,
1545     argc        => 1,
1546 );                      
1547
1548
1549 1;
1550