]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/SuperCat.pm
adding holdings data to marcxml/htmlcard output
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / SuperCat.pm
1 package OpenILS::Application::SuperCat;
2
3 use strict;
4 use warnings;
5
6 # All OpenSRF applications must be based on OpenSRF::Application or
7 # a subclass thereof.  Makes sense, eh?
8 use OpenSRF::Application;
9 use base qw/OpenSRF::Application/;
10
11 # This is the client class, used for connecting to open-ils.storage
12 use OpenSRF::AppSession;
13
14 # This is an extention of Error.pm that supplies some error types to throw
15 use OpenSRF::EX qw(:try);
16
17 # This is a helper class for querying the OpenSRF Settings application ...
18 use OpenSRF::Utils::SettingsClient;
19
20 # ... and here we have the built in logging helper ...
21 use OpenSRF::Utils::Logger qw($logger);
22
23 # ... and this is our OpenILS object (en|de)coder and psuedo-ORM package.
24 use OpenILS::Utils::Fieldmapper;
25
26
27 # We'll be working with XML, so...
28 use XML::LibXML;
29 use XML::LibXSLT;
30 use Unicode::Normalize;
31
32 use JSON;
33
34 our (
35   $_parser,
36   $_xslt,
37   %record_xslt,
38   %metarecord_xslt,
39   %holdings_data_cache,
40 );
41
42 sub child_init {
43         # we need an XML parser
44         $_parser = new XML::LibXML;
45
46         # and an xslt parser
47         $_xslt = new XML::LibXSLT;
48         
49         # parse the MODS xslt ...
50         my $mods3_xslt = $_parser->parse_file(
51                 OpenSRF::Utils::SettingsClient
52                         ->new
53                         ->config_value( dirs => 'xsl' ).
54                 "/MARC21slim2MODS3.xsl"
55         );
56         # and stash a transformer
57         $record_xslt{mods3}{xslt} = $_xslt->parse_stylesheet( $mods3_xslt );
58         $record_xslt{mods3}{namespace_uri} = 'http://www.loc.gov/mods/v3';
59         $record_xslt{mods3}{docs} = 'http://www.loc.gov/mods/';
60         $record_xslt{mods3}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-1.xsd';
61
62         # parse the MODS xslt ...
63         my $mods_xslt = $_parser->parse_file(
64                 OpenSRF::Utils::SettingsClient
65                         ->new
66                         ->config_value( dirs => 'xsl' ).
67                 "/MARC21slim2MODS.xsl"
68         );
69         # and stash a transformer
70         $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
71         $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
72         $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
73         $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
74
75         # parse the ATOM entry xslt ...
76         my $atom_xslt = $_parser->parse_file(
77                 OpenSRF::Utils::SettingsClient
78                         ->new
79                         ->config_value( dirs => 'xsl' ).
80                 "/MARC21slim2ATOM.xsl"
81         );
82         # and stash a transformer
83         $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
84         $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
85         $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
86
87         # parse the RDFDC xslt ...
88         my $rdf_dc_xslt = $_parser->parse_file(
89                 OpenSRF::Utils::SettingsClient
90                         ->new
91                         ->config_value( dirs => 'xsl' ).
92                 "/MARC21slim2RDFDC.xsl"
93         );
94         # and stash a transformer
95         $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
96         $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
97         $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
98
99         # parse the SRWDC xslt ...
100         my $srw_dc_xslt = $_parser->parse_file(
101                 OpenSRF::Utils::SettingsClient
102                         ->new
103                         ->config_value( dirs => 'xsl' ).
104                 "/MARC21slim2SRWDC.xsl"
105         );
106         # and stash a transformer
107         $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
108         $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
109         $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
110
111         # parse the OAIDC xslt ...
112         my $oai_dc_xslt = $_parser->parse_file(
113                 OpenSRF::Utils::SettingsClient
114                         ->new
115                         ->config_value( dirs => 'xsl' ).
116                 "/MARC21slim2OAIDC.xsl"
117         );
118         # and stash a transformer
119         $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
120         $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
121         $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
122
123         # parse the RSS xslt ...
124         my $rss_xslt = $_parser->parse_file(
125                 OpenSRF::Utils::SettingsClient
126                         ->new
127                         ->config_value( dirs => 'xsl' ).
128                 "/MARC21slim2RSS2.xsl"
129         );
130         # and stash a transformer
131         $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
132
133         # and finally, a storage server session
134
135         register_record_transforms();
136
137         return 1;
138 }
139
140 sub register_record_transforms {
141         for my $type ( keys %record_xslt ) {
142                 __PACKAGE__->register_method(
143                         method    => 'retrieve_record_transform',
144                         api_name  => "open-ils.supercat.record.$type.retrieve",
145                         api_level => 1,
146                         argc      => 1,
147                         signature =>
148                                 { desc     => "Returns the \U$type\E representation ".
149                                               "of the requested bibliographic record",
150                                   params   =>
151                                         [
152                                                 { name => 'bibId',
153                                                   desc => 'An OpenILS biblio::record_entry id',
154                                                   type => 'number' },
155                                         ],
156                                 'return' =>
157                                         { desc => "The bib record in \U$type\E",
158                                           type => 'string' }
159                                 }
160                 );
161         }
162 }
163
164
165 sub entityize {
166         my $stuff = NFC(shift());
167         $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
168         return $stuff;
169 }
170
171 sub new_record_holdings {
172         my $self = shift;
173         my $client = shift;
174         my $bib = shift;
175         my $ou = shift;
176
177         my $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
178         my $_cstore = OpenSRF::AppSession->create( 'open-ils.cstore' );
179
180         my $tree = $_cstore->request(
181                 "open-ils.cstore.direct.biblio.record_entry.retrieve",
182                 $bib,
183                 {flesh => 3, flesh_fields => [qw/call_numbers copies location status owning_lib circ_lib/] }
184         )->gather(1);
185
186         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
187         $year += 1900;
188         $month += 1;
189
190         my $xml = "<hold:volumes xmlns:hold='http://open-ils.org/spec/holdings/v1'>";
191
192         for my $cn (@{$tree->call_numbers}) {
193
194                 if ($ou ne '-') {
195                         next unless grep {$_->circ_lib->shortname =~ /^$ou/} @{$cn->copies};
196                 }
197
198                 (my $cn_class = $cn->class_name) =~ s/::/-/gso;
199                 $cn_class =~ s/Fieldmapper-//gso;
200                 my $cn_tag = sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:$cn_class/".$cn->id, $month, $day);
201
202                 my $cn_lib = $cn->owning_lib->shortname;
203
204                 my $cn_label = $cn->label;
205
206                 $xml .= "<hold:volume id='$cn_tag' lib='$cn_lib' label='$cn_label'><hold:copies>";
207                 
208                 for my $cp (@{$cn->copies}) {
209
210                         if ($ou ne '-') {
211                                 next unless $cp->circ_lib->shortname =~ /^$ou/;
212                         }
213
214                         (my $cp_class = $cp->class_name) =~ s/::/-/gso;
215                         $cp_class =~ s/Fieldmapper-//gso;
216                         my $cp_tag = sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:$cp_class/".$cp->id, $month, $day);
217
218                         my $cp_stat = $cp->status->name;
219
220                         my $cp_loc = $cp->location->name;
221
222                         my $cp_lib = $cp->circ_lib->shortname;
223
224                         my $cp_bc = $cp->barcode;
225
226                         $xml .= "<hold:copy id='$cp_tag' barcode='$cp_bc'><hold:status>$cp_stat</hold:status><hold:location>$cp_loc</hold:location><hold:circlib>$cp_lib</hold:circlib><hold:notes>";
227
228                         #for my $note ( @{$_storage->request( "open-ils.storage.direct.asset.copy_note.search.atomic" => {id => $cp->id, pub => "t" })->gather(1)} ) {
229                         #       $xml .= sprintf('<hold:note date="%s" title="%s">%s</hold:note>',$note->create_date, escape($note->title), escape($note->value));
230                         #}
231
232                         $xml .= "</hold:notes><hold:statcats>";
233
234                         #for my $sce ( @{$_storage->request( "open-ils.storage.direct.asset.stat_cat_entry_copy_map.search.atomic" => { owning_copy => $cp->id })->gather(1)} ) {
235                         #       my $sc = $holdings_data_cache{statcat}{$sce->stat_cat_entry};
236                         #       $xml .= sprintf('<hold:statcat>%s</hold:statcat>',escape($sc->value));
237                         #}
238
239                         $xml .= "</hold:statcats></hold:copy>";
240                 }
241                 
242                 $xml .= "</hold:copies></hold:volume>";
243         }
244
245         $xml .= "</hold:volumes>";
246
247         return $xml;
248 }
249 __PACKAGE__->register_method(
250         method    => 'new_record_holdings',
251         api_name  => 'open-ils.supercat.record.holdings_xml.retrieve',
252         api_level => 1,
253         argc      => 1,
254         signature =>
255                 { desc     => <<"                 DESC",
256 Returns the XML representation of the requested bibliographic record's holdings
257                   DESC
258                   params   =>
259                         [
260                                 { name => 'bibId',
261                                   desc => 'An OpenILS biblio::record_entry id',
262                                   type => 'number' },
263                         ],
264                   'return' =>
265                         { desc => 'The bib record holdings hierarchy in XML',
266                           type => 'string' }
267                 }
268 );
269
270
271
272 sub record_holdings {
273         my $self = shift;
274         my $client = shift;
275         my $bib = shift;
276
277         my $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
278
279         if (!$holdings_data_cache{status}) {
280                 $holdings_data_cache{status} = { map { ($_->id => $_) } @{ $_storage->request( "open-ils.storage.direct.config.copy_status.retrieve.all.atomic" )->gather(1) } };
281                 $holdings_data_cache{location} = { map { ($_->id => $_) } @{ $_storage->request( "open-ils.storage.direct.asset.copy_location.retrieve.all.atomic" )->gather(1) } };
282                 $holdings_data_cache{ou} =
283                 {
284                         map {
285                                 ($_->id => $_)
286                         } @{$_storage->request( "open-ils.storage.direct.actor.org_unit.search_where.atomic" => { id => { '>' => 0 } } )->gather(1)}
287                 };
288                 $holdings_data_cache{statcat} =
289                 {
290                         map {
291                                 ($_->id => $_)
292                         } @{$_storage->request( "open-ils.storage.direct.asset.stat_cat_entry.search_where.atomic" => { id => { '>' => 0 } } )->gather(1)}
293                 };
294         }
295
296
297         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
298         $year += 1900;
299         $month += 1;
300
301         my $xml = "<volumes xmlns='http://open-ils.org/spec/holdings/v1'>";
302         
303         for my $cn ( @{$_storage->request( "open-ils.storage.direct.asset.call_number.search.record.atomic" => $bib )->gather(1)} ) {
304                 (my $cn_class = $cn->class_name) =~ s/::/-/gso;
305                 $cn_class =~ s/Fieldmapper-//gso;
306                 my $cn_tag = sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:$cn_class/".$cn->id, $month, $day);
307
308                 my $cn_lib = $holdings_data_cache{ou}{$cn->owning_lib}->shortname;
309
310                 my $cn_label = $cn->label;
311
312                 $xml .= "<volume id='$cn_tag' lib='$cn_lib' label='$cn_label'><copies>";
313                 
314                 for my $cp ( @{$_storage->request( "open-ils.storage.direct.asset.copy.search.call_number.atomic" => $cn->id )->gather(1)} ) {
315                         (my $cp_class = $cn->class_name) =~ s/::/-/gso;
316                         $cp_class =~ s/Fieldmapper-//gso;
317                         my $cp_tag = sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:$cp_class/".$cp->id, $month, $day);
318
319                         my $cp_stat = $holdings_data_cache{status}{$cp->status}->name;
320
321                         my $cp_loc = $holdings_data_cache{location}{$cp->location}->name;
322
323                         my $cp_lib = $holdings_data_cache{ou}{$cp->circ_lib}->shortname;
324
325                         my $cp_bc = $cp->barcode;
326
327                         $xml .= "<copy id='$cp_tag' barcode='$cp_bc'><status>$cp_stat</status><location>$cp_loc</location><circlib>$cp_lib</circlib><notes>";
328
329                         for my $note ( @{$_storage->request( "open-ils.storage.direct.asset.copy_note.search.atomic" => {id => $cp->id, pub => "t" })->gather(1)} ) {
330                                 $xml .= sprintf('<note date="%s" title="%s">%s</note>',$note->create_date, escape($note->title), escape($note->value));
331                         }
332
333                         $xml .= "</notes><statcats>";
334
335                         for my $sce ( @{$_storage->request( "open-ils.storage.direct.asset.stat_cat_entry_copy_map.search.atomic" => { owning_copy => $cp->id })->gather(1)} ) {
336                                 my $sc = $holdings_data_cache{statcat}{$sce->stat_cat_entry};
337                                 $xml .= sprintf('<statcat>%s</statcat>',escape($sc->value));
338                         }
339
340                         $xml .= "</statcats></copy>";
341                 }
342                 
343                 $xml .= "</volume>";
344         }
345
346         $xml .= "</volumes>";
347
348         return $xml;
349 }
350
351 sub escape {
352         my $text = shift;
353         $text =~ s/&/&amp;/gsom;
354         $text =~ s/</&lt;/gsom;
355         $text =~ s/>/&gt;/gsom;
356         return $text;
357 }
358
359 sub recent_changes {
360         my $self = shift;
361         my $client = shift;
362         my $when = shift;
363         my $limit = shift;
364
365         if (!$when) {
366                 my ($d,$m,$y) = (localtime)[4,5,6];
367                 $when = sprintf('%4d-%02d-%02d', $y + 1900, $m + 1, $d);
368         }
369
370         my $type = 'biblio';
371         $type = 'authority' if ($self->api_name =~ /authority/o);
372
373         my $axis = 'create_date';
374         $axis = 'edit_date' if ($self->api_name =~ /edit/o);
375
376         my $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
377
378         return $_storage
379                 ->request(
380                         "open-ils.storage.id_list.$type.record_entry.search_where.atomic",
381                         { $axis => { ">" => $when } },
382                         { order_by => "$axis desc", limit => $limit } )
383                 ->gather(1);
384 }
385
386 for my $t ( qw/biblio authority/ ) {
387         for my $a ( qw/import edit/ ) {
388
389                 __PACKAGE__->register_method(
390                         method    => 'recent_changes',
391                         api_name  => "open-ils.supercat.$t.record.$a.recent",
392                         api_level => 1,
393                         argc      => 0,
394                         signature =>
395                                 { desc     => "Returns a list of recently ${a}ed $t records",
396                                   params   =>
397                                         [
398                                                 { name => 'when',
399                                                   desc => "Date to start looking for ${a}ed records",
400                                                   default => 'today',
401                                                   type => 'string' },
402
403                                                 { name => 'limit',
404                                                   desc => "Maximum count to retrieve",
405                                                   type => 'number' },
406                                         ],
407                                   'return' =>
408                                         { desc => "An id list of $t records",
409                                           type => 'array' }
410                                 },
411                 );
412         }
413 }
414
415
416 sub retrieve_record_marcxml {
417         my $self = shift;
418         my $client = shift;
419         my $rid = shift;
420
421         my $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
422
423         return
424         entityize(
425                 $_storage
426                         ->request( 'open-ils.storage.direct.biblio.record_entry.retrieve' => $rid )
427                         ->gather(1)
428                         ->marc
429         );
430 }
431
432 __PACKAGE__->register_method(
433         method    => 'retrieve_record_marcxml',
434         api_name  => 'open-ils.supercat.record.marcxml.retrieve',
435         api_level => 1,
436         argc      => 1,
437         signature =>
438                 { desc     => <<"                 DESC",
439 Returns the MARCXML representation of the requested bibliographic record
440                   DESC
441                   params   =>
442                         [
443                                 { name => 'bibId',
444                                   desc => 'An OpenILS biblio::record_entry id',
445                                   type => 'number' },
446                         ],
447                   'return' =>
448                         { desc => 'The bib record in MARCXML',
449                           type => 'string' }
450                 }
451 );
452
453 sub retrieve_record_transform {
454         my $self = shift;
455         my $client = shift;
456         my $rid = shift;
457
458         (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
459
460         my $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
461
462         warn "Fetching record entry $rid\n";
463         my $marc = $_storage->request(
464                 'open-ils.storage.direct.biblio.record_entry.retrieve',
465                 $rid
466         )->gather(1)->marc;
467         warn "Fetched record entry $rid\n";
468
469         return entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $marc ) )->toString);
470 }
471
472
473 sub retrieve_metarecord_mods {
474         my $self = shift;
475         my $client = shift;
476         my $rid = shift;
477
478         my $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
479
480         # We want a session
481         $_storage->connect;
482
483         # Get the metarecord in question
484         my $mr =
485         $_storage->request(
486                 'open-ils.storage.direct.metabib.metarecord.retrieve' => $rid
487         )->gather(1);
488
489         # Now get the map of all bib records for the metarecord
490         my $recs =
491         $_storage->request(
492                 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
493                 $rid
494         )->gather(1);
495
496         $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
497
498         # and retrieve the lead (master) record as MODS
499         my ($master) =
500                 $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
501                         ->run($mr->master_record);
502         my $master_mods = $_parser->parse_string($master)->documentElement;
503         $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
504
505         # ... and a MODS clone to populate, with guts removed.
506         my $mods = $_parser->parse_string($master)->documentElement;
507         $mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
508         ($mods) = $mods->findnodes('//mods:mods');
509         $mods->removeChildNodes;
510
511         # Add the metarecord ID as a (locally defined) info URI
512         my $recordInfo = $mods
513                 ->ownerDocument
514                 ->createElement("mods:recordInfo");
515
516         my $recordIdentifier = $mods
517                 ->ownerDocument
518                 ->createElement("mods:recordIdentifier");
519
520         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
521         $year += 1900;
522         $month += 1;
523
524         my $id = $mr->id;
525         $recordIdentifier->appendTextNode(
526                 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
527         );
528
529         $recordInfo->appendChild($recordIdentifier);
530         $mods->appendChild($recordInfo);
531
532         # Grab the title, author and ISBN for the master record and populate the metarecord
533         my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
534         
535         if ($title) {
536                 $title->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
537                 $title = $mods->ownerDocument->importNode($title);
538                 $mods->appendChild($title);
539         }
540
541         my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
542         if ($author) {
543                 $author->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
544                 $author = $mods->ownerDocument->importNode($author);
545                 $mods->appendChild($author);
546         }
547
548         my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
549         if ($isbn) {
550                 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
551                 $isbn = $mods->ownerDocument->importNode($isbn);
552                 $mods->appendChild($isbn);
553         }
554
555         # ... and loop over the constituent records
556         for my $map ( @$recs ) {
557
558                 # get the MODS
559                 my ($rec) =
560                         $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
561                                 ->run($map->source);
562
563                 my $part_mods = $_parser->parse_string($rec);
564                 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
565                 ($part_mods) = $part_mods->findnodes('//mods:mods');
566
567                 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
568                         $node->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
569                         $node = $mods->ownerDocument->importNode($node);
570                         $mods->appendChild( $node );
571                 }
572
573                 my $relatedItem = $mods
574                         ->ownerDocument
575                         ->createElement("mods:relatedItem");
576
577                 $relatedItem->setAttribute( type => 'constituent' );
578
579                 my $identifier = $mods
580                         ->ownerDocument
581                         ->createElement("mods:identifier");
582
583                 $identifier->setAttribute( type => 'uri' );
584
585                 my $subRecordInfo = $mods
586                         ->ownerDocument
587                         ->createElement("mods:recordInfo");
588
589                 my $subRecordIdentifier = $mods
590                         ->ownerDocument
591                         ->createElement("mods:recordIdentifier");
592
593                 my $subid = $map->source;
594                 $subRecordIdentifier->appendTextNode(
595                         sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
596                                 $month,
597                                 $day
598                         )
599                 );
600                 $subRecordInfo->appendChild($subRecordIdentifier);
601
602                 $relatedItem->appendChild( $subRecordInfo );
603
604                 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
605                 $tor->setNamespace( "http://www.loc.gov/mods/", "mods", 1 ) if ($tor);
606                 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
607                 $relatedItem->appendChild($tor) if ($tor);
608
609                 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
610                         $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
611                         $part_isbn = $mods->ownerDocument->importNode($part_isbn);
612                         $relatedItem->appendChild( $part_isbn );
613
614                         if (!$isbn) {
615                                 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
616                         }
617                 }
618
619                 $mods->appendChild( $relatedItem );
620
621         }
622
623         $_storage->disconnect;
624
625         return entityize($mods->toString);
626
627 }
628 __PACKAGE__->register_method(
629         method    => 'retrieve_metarecord_mods',
630         api_name  => 'open-ils.supercat.metarecord.mods.retrieve',
631         api_level => 1,
632         argc      => 1,
633         signature =>
634                 { desc     => <<"                 DESC",
635 Returns the MODS representation of the requested metarecord
636                   DESC
637                   params   =>
638                         [
639                                 { name => 'metarecordId',
640                                   desc => 'An OpenILS metabib::metarecord id',
641                                   type => 'number' },
642                         ],
643                   'return' =>
644                         { desc => 'The metarecord in MODS',
645                           type => 'string' }
646                 }
647 );
648
649 sub list_metarecord_formats {
650         my @list = (
651                 { mods =>
652                         { namespace_uri   => 'http://www.loc.gov/mods/',
653                           docs            => 'http://www.loc.gov/mods/',
654                           schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
655                         }
656                 }
657         );
658
659         for my $type ( keys %metarecord_xslt ) {
660                 push @list,
661                         { $type => 
662                                 { namespace_uri   => $metarecord_xslt{$type}{namespace_uri},
663                                   docs            => $metarecord_xslt{$type}{docs},
664                                   schema_location => $metarecord_xslt{$type}{schema_location},
665                                 }
666                         };
667         }
668
669         return \@list;
670 }
671 __PACKAGE__->register_method(
672         method    => 'list_metarecord_formats',
673         api_name  => 'open-ils.supercat.metarecord.formats',
674         api_level => 1,
675         argc      => 0,
676         signature =>
677                 { desc     => <<"                 DESC",
678 Returns the list of valid metarecord formats that supercat understands.
679                   DESC
680                   'return' =>
681                         { desc => 'The format list',
682                           type => 'array' }
683                 }
684 );
685
686
687 sub list_record_formats {
688         my @list = (
689                 { marcxml =>
690                         { namespace_uri   => 'http://www.loc.gov/MARC21/slim',
691                           docs            => 'http://www.loc.gov/marcxml/',
692                           schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
693                         }
694                 }
695         );
696
697         for my $type ( keys %record_xslt ) {
698                 push @list,
699                         { $type => 
700                                 { namespace_uri   => $record_xslt{$type}{namespace_uri},
701                                   docs            => $record_xslt{$type}{docs},
702                                   schema_location => $record_xslt{$type}{schema_location},
703                                 }
704                         };
705         }
706
707         return \@list;
708 }
709 __PACKAGE__->register_method(
710         method    => 'list_record_formats',
711         api_name  => 'open-ils.supercat.record.formats',
712         api_level => 1,
713         argc      => 0,
714         signature =>
715                 { desc     => <<"                 DESC",
716 Returns the list of valid record formats that supercat understands.
717                   DESC
718                   'return' =>
719                         { desc => 'The format list',
720                           type => 'array' }
721                 }
722 );
723
724
725 sub oISBN {
726         my $self = shift;
727         my $client = shift;
728         my $isbn = shift;
729
730         throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
731                 unless (length($isbn) >= 10);
732
733         my $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
734
735         # Create a storage session, since we'll be making muliple requests.
736         $_storage->connect;
737
738         # Find the record that has that ISBN.
739         my $bibrec = $_storage->request(
740                 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
741                 { tag => '020', subfield => 'a', value => { ilike => $isbn.'%'} }
742         )->gather(1);
743
744         # Go away if we don't have one.
745         return {} unless (@$bibrec);
746
747         # Find the metarecord for that bib record.
748         my $mr = $_storage->request(
749                 'open-ils.storage.direct.metabib.metarecord_source_map.search.source.atomic',
750                 $bibrec->[0]->record
751         )->gather(1);
752
753         # Find the other records for that metarecord.
754         my $records = $_storage->request(
755                 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
756                 $mr->[0]->metarecord
757         )->gather(1);
758
759         # Just to be safe.  There's currently no unique constraint on sources...
760         my %unique_recs = map { ($_->source, 1) } @$records;
761         my @rec_list = sort keys %unique_recs;
762
763         # And now fetch the ISBNs for thos records.
764         my $recs = $_storage->request(
765                 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
766                 { tag => '020', subfield => 'a', record => \@rec_list }
767         )->gather(1);
768
769         # We're done with the storage server session.
770         $_storage->disconnect;
771
772         # Return the oISBN data structure.  This will be XMLized at a higher layer.
773         return
774                 { metarecord => $mr->[0]->metarecord,
775                   record_list => { map { ($_->record, $_->value) } @$recs } };
776
777 }
778 __PACKAGE__->register_method(
779         method    => 'oISBN',
780         api_name  => 'open-ils.supercat.oisbn',
781         api_level => 1,
782         argc      => 1,
783         signature =>
784                 { desc     => <<"                 DESC",
785 Returns the ISBN list for the metarecord of the requested isbn
786                   DESC
787                   params   =>
788                         [
789                                 { name => 'isbn',
790                                   desc => 'An ISBN.  Duh.',
791                                   type => 'string' },
792                         ],
793                   'return' =>
794                         { desc => 'record to isbn map',
795                           type => 'object' }
796                 }
797 );
798
799 1;