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