1 package OpenILS::Application::SuperCat;
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/;
11 # This is the client class, used for connecting to open-ils.storage
12 use OpenSRF::AppSession;
14 # This is an extention of Error.pm that supplies some error types to throw
15 use OpenSRF::EX qw(:try);
17 # This is a helper class for querying the OpenSRF Settings application ...
18 use OpenSRF::Utils::SettingsClient;
20 # ... and here we have the built in logging helper ...
21 use OpenSRF::Utils::Logger qw($logger);
23 # ... and this is our OpenILS object (en|de)coder and psuedo-ORM package.
24 use OpenILS::Utils::Fieldmapper;
27 # We'll be working with XML, so...
30 use Unicode::Normalize;
43 # we need an XML parser
44 $_parser = new XML::LibXML;
46 $logger->debug("Got here!");
49 $_xslt = new XML::LibXSLT;
51 # parse the MODS xslt ...
52 my $mods_xslt = $_parser->parse_file(
53 OpenSRF::Utils::SettingsClient
55 ->config_value( dirs => 'xsl' ).
56 "/MARC21slim2MODS.xsl"
58 # and stash a transformer
59 $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
60 $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
61 $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
62 $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
64 $logger->debug("Got here!");
66 # parse the RDFDC xslt ...
67 my $rdf_dc_xslt = $_parser->parse_file(
68 OpenSRF::Utils::SettingsClient
70 ->config_value( dirs => 'xsl' ).
71 "/MARC21slim2RDFDC.xsl"
73 # and stash a transformer
74 $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
75 $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
76 $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
78 $logger->debug("Got here!");
80 # parse the SRWDC xslt ...
81 my $srw_dc_xslt = $_parser->parse_file(
82 OpenSRF::Utils::SettingsClient
84 ->config_value( dirs => 'xsl' ).
85 "/MARC21slim2SRWDC.xsl"
87 # and stash a transformer
88 $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
89 $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
90 $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
92 $logger->debug("Got here!");
94 # parse the OAIDC xslt ...
95 my $oai_dc_xslt = $_parser->parse_file(
96 OpenSRF::Utils::SettingsClient
98 ->config_value( dirs => 'xsl' ).
99 "/MARC21slim2OAIDC.xsl"
101 # and stash a transformer
102 $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
103 $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
104 $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
106 $logger->debug("Got here!");
108 # parse the RSS xslt ...
109 my $rss_xslt = $_parser->parse_file(
110 OpenSRF::Utils::SettingsClient
112 ->config_value( dirs => 'xsl' ).
113 "/MARC21slim2RSS2.xsl"
115 # and stash a transformer
116 $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
118 $logger->debug("Got here!");
120 # and finally, a storage server session
121 $_storage = OpenSRF::AppSession->create( 'open-ils.storage' );
123 register_record_transforms();
128 sub register_record_transforms {
129 for my $type ( keys %record_xslt ) {
130 __PACKAGE__->register_method(
131 method => 'retrieve_record_transform',
132 api_name => "open-ils.supercat.record.$type.retrieve",
137 Returns the \U$type\E representation of the requested bibliographic record
142 desc => 'An OpenILS biblio::record_entry id',
146 { desc => "The bib record in \U$type\E",
155 my $stuff = NFC(shift());
156 $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
161 sub retrieve_record_marcxml {
169 ->request( 'open-ils.storage.direct.biblio.record_entry.retrieve' => $rid )
175 __PACKAGE__->register_method(
176 method => 'retrieve_record_marcxml',
177 api_name => 'open-ils.supercat.record.marcxml.retrieve',
182 Returns the MARCXML representation of the requested bibliographic record
187 desc => 'An OpenILS biblio::record_entry id',
191 { desc => 'The bib record in MARCXML',
196 sub retrieve_record_transform {
201 (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
203 my $marc = $_storage->request(
204 'open-ils.storage.direct.biblio.record_entry.retrieve',
208 return entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $marc ) )->toString);
212 sub retrieve_metarecord_mods {
220 # Get the metarecord in question
223 'open-ils.storage.direct.metabib.metarecord.retrieve' => $rid
226 # Now get the map of all bib records for the metarecord
229 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
233 $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
235 # and retrieve the lead (master) record as MODS
237 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
238 ->run($mr->master_record);
239 my $master_mods = $_parser->parse_string($master)->documentElement;
240 $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
242 # ... and a MODS clone to populate, with guts removed.
243 my $mods = $_parser->parse_string($master)->documentElement;
244 $mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
245 ($mods) = $mods->findnodes('//mods:mods');
246 $mods->removeChildNodes;
248 # Add the metarecord ID as a (locally defined) info URI
249 my $recordInfo = $mods
251 ->createElement("mods:recordInfo");
253 my $recordIdentifier = $mods
255 ->createElement("mods:recordIdentifier");
257 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
262 $recordIdentifier->appendTextNode(
263 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$id",
269 $recordInfo->appendChild($recordIdentifier);
270 $mods->appendChild($recordInfo);
272 # Grab the title, author and ISBN for the master record and populate the metarecord
273 my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
276 $title->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
277 $title = $mods->ownerDocument->importNode($title);
278 $mods->appendChild($title);
281 my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
283 $author->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
284 $author = $mods->ownerDocument->importNode($author);
285 $mods->appendChild($author);
288 my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
290 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
291 $isbn = $mods->ownerDocument->importNode($isbn);
292 $mods->appendChild($isbn);
295 # ... and loop over the constituent records
296 for my $map ( @$recs ) {
300 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
303 my $part_mods = $_parser->parse_string($rec);
304 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
305 ($part_mods) = $part_mods->findnodes('//mods:mods');
307 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
308 $node->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
309 $node = $mods->ownerDocument->importNode($node);
310 $mods->appendChild( $node );
313 my $relatedItem = $mods
315 ->createElement("mods:relatedItem");
317 $relatedItem->setAttribute( type => 'constituent' );
319 my $identifier = $mods
321 ->createElement("mods:identifier");
323 $identifier->setAttribute( type => 'uri' );
325 my $subRecordInfo = $mods
327 ->createElement("mods:recordInfo");
329 my $subRecordIdentifier = $mods
331 ->createElement("mods:recordIdentifier");
333 my $subid = $map->source;
334 $subRecordIdentifier->appendTextNode(
335 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
340 $subRecordInfo->appendChild($subRecordIdentifier);
342 $relatedItem->appendChild( $subRecordInfo );
344 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
345 $tor->setNamespace( "http://www.loc.gov/mods/", "mods", 1 ) if ($tor);
346 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
347 $relatedItem->appendChild($tor) if ($tor);
349 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
350 $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
351 $part_isbn = $mods->ownerDocument->importNode($part_isbn);
352 $relatedItem->appendChild( $part_isbn );
355 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
359 $mods->appendChild( $relatedItem );
363 $_storage->disconnect;
365 return entityize($mods->toString);
368 __PACKAGE__->register_method(
369 method => 'retrieve_metarecord_mods',
370 api_name => 'open-ils.supercat.metarecord.mods.retrieve',
375 Returns the MODS representation of the requested metarecord
379 { name => 'metarecordId',
380 desc => 'An OpenILS metabib::metarecord id',
384 { desc => 'The metarecord in MODS',
389 sub list_metarecord_formats {
392 { namespace_uri => 'http://www.loc.gov/mods/',
393 docs => 'http://www.loc.gov/mods/',
394 schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
399 for my $type ( keys %metarecord_xslt ) {
402 { namespace_uri => $metarecord_xslt{$type}{namespace_uri},
403 docs => $metarecord_xslt{$type}{docs},
404 schema_location => $metarecord_xslt{$type}{schema_location},
411 __PACKAGE__->register_method(
412 method => 'list_metarecord_formats',
413 api_name => 'open-ils.supercat.metarecord.formats',
418 Returns the list of valid metarecord formats that supercat understands.
421 { desc => 'The format list',
427 sub list_record_formats {
430 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
431 docs => 'http://www.loc.gov/marcxml/',
432 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
437 for my $type ( keys %record_xslt ) {
440 { namespace_uri => $record_xslt{$type}{namespace_uri},
441 docs => $record_xslt{$type}{docs},
442 schema_location => $record_xslt{$type}{schema_location},
449 __PACKAGE__->register_method(
450 method => 'list_record_formats',
451 api_name => 'open-ils.supercat.record.formats',
456 Returns the list of valid record formats that supercat understands.
459 { desc => 'The format list',
470 throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
471 unless (length($isbn) >= 10);
473 # Create a storage session, since we'll be making muliple requests.
476 # Find the record that has that ISBN.
477 my $bibrec = $_storage->request(
478 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
479 { tag => '020', subfield => 'a', value => { ilike => $isbn.'%'} }
482 # Go away if we don't have one.
483 return {} unless (@$bibrec);
485 # Find the metarecord for that bib record.
486 my $mr = $_storage->request(
487 'open-ils.storage.direct.metabib.metarecord_source_map.search.source.atomic',
491 # Find the other records for that metarecord.
492 my $records = $_storage->request(
493 'open-ils.storage.direct.metabib.metarecord_source_map.search.metarecord.atomic',
497 # Just to be safe. There's currently no unique constraint on sources...
498 my %unique_recs = map { ($_->source, 1) } @$records;
499 my @rec_list = sort keys %unique_recs;
501 # And now fetch the ISBNs for thos records.
502 my $recs = $_storage->request(
503 'open-ils.storage.direct.metabib.full_rec.search_where.atomic',
504 { tag => '020', subfield => 'a', record => \@rec_list }
507 # We're done with the storage server session.
508 $_storage->disconnect;
510 # Return the oISBN data structure. This will be XMLized at a higher layer.
512 { metarecord => $mr->[0]->metarecord,
513 record_list => { map { ($_->record, $_->value) } @$recs } };
516 __PACKAGE__->register_method(
518 api_name => 'open-ils.supercat.oisbn',
523 Returns the ISBN list for the metarecord of the requested isbn
528 desc => 'An ISBN. Duh.',
532 { desc => 'record to isbn map',