1 # We'll be working with XML, so...
4 use Unicode::Normalize;
6 # ... and this has some handy common methods
7 use OpenILS::Application::AppUtils;
9 my $parser = new XML::LibXML;
10 my $U = 'OpenILS::Application::AppUtils';
13 package OpenILS::Application::SuperCat;
17 use OpenILS::Utils::Normalize qw( naco_normalize );
19 # All OpenSRF applications must be based on OpenSRF::Application or
20 # a subclass thereof. Makes sense, eh?
21 use OpenILS::Application;
22 use base qw/OpenILS::Application/;
24 # This is the client class, used for connecting to open-ils.storage
25 use OpenSRF::AppSession;
27 # This is an extension of Error.pm that supplies some error types to throw
28 use OpenSRF::EX qw(:try);
30 # This is a helper class for querying the OpenSRF Settings application ...
31 use OpenSRF::Utils::SettingsClient;
33 # ... and here we have the built in logging helper ...
34 use OpenSRF::Utils::Logger qw($logger);
36 # ... and this is our OpenILS object (en|de)coder and psuedo-ORM package.
37 use OpenILS::Utils::Fieldmapper;
48 # we need an XML parser
49 $_parser = new XML::LibXML;
52 $_xslt = new XML::LibXSLT;
54 # parse the MODS xslt ...
55 my $mods33_xslt = $_parser->parse_file(
56 OpenSRF::Utils::SettingsClient
58 ->config_value( dirs => 'xsl' ).
59 "/MARC21slim2MODS33.xsl"
61 # and stash a transformer
62 $record_xslt{mods33}{xslt} = $_xslt->parse_stylesheet( $mods33_xslt );
63 $record_xslt{mods33}{namespace_uri} = 'http://www.loc.gov/mods/v3';
64 $record_xslt{mods33}{docs} = 'http://www.loc.gov/mods/';
65 $record_xslt{mods33}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-3.xsd';
67 # parse the MODS xslt ...
68 my $mods32_xslt = $_parser->parse_file(
69 OpenSRF::Utils::SettingsClient
71 ->config_value( dirs => 'xsl' ).
72 "/MARC21slim2MODS32.xsl"
74 # and stash a transformer
75 $record_xslt{mods32}{xslt} = $_xslt->parse_stylesheet( $mods32_xslt );
76 $record_xslt{mods32}{namespace_uri} = 'http://www.loc.gov/mods/v3';
77 $record_xslt{mods32}{docs} = 'http://www.loc.gov/mods/';
78 $record_xslt{mods32}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-2.xsd';
80 # parse the MODS xslt ...
81 my $mods3_xslt = $_parser->parse_file(
82 OpenSRF::Utils::SettingsClient
84 ->config_value( dirs => 'xsl' ).
85 "/MARC21slim2MODS3.xsl"
87 # and stash a transformer
88 $record_xslt{mods3}{xslt} = $_xslt->parse_stylesheet( $mods3_xslt );
89 $record_xslt{mods3}{namespace_uri} = 'http://www.loc.gov/mods/v3';
90 $record_xslt{mods3}{docs} = 'http://www.loc.gov/mods/';
91 $record_xslt{mods3}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-1.xsd';
93 # parse the MODS xslt ...
94 my $mods_xslt = $_parser->parse_file(
95 OpenSRF::Utils::SettingsClient
97 ->config_value( dirs => 'xsl' ).
98 "/MARC21slim2MODS.xsl"
100 # and stash a transformer
101 $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
102 $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
103 $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
104 $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
106 # parse the ATOM entry xslt ...
107 my $atom_xslt = $_parser->parse_file(
108 OpenSRF::Utils::SettingsClient
110 ->config_value( dirs => 'xsl' ).
111 "/MARC21slim2ATOM.xsl"
113 # and stash a transformer
114 $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
115 $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
116 $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
118 # parse the RDFDC xslt ...
119 my $rdf_dc_xslt = $_parser->parse_file(
120 OpenSRF::Utils::SettingsClient
122 ->config_value( dirs => 'xsl' ).
123 "/MARC21slim2RDFDC.xsl"
125 # and stash a transformer
126 $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
127 $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
128 $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
130 # parse the SRWDC xslt ...
131 my $srw_dc_xslt = $_parser->parse_file(
132 OpenSRF::Utils::SettingsClient
134 ->config_value( dirs => 'xsl' ).
135 "/MARC21slim2SRWDC.xsl"
137 # and stash a transformer
138 $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
139 $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
140 $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
142 # parse the OAIDC xslt ...
143 my $oai_dc_xslt = $_parser->parse_file(
144 OpenSRF::Utils::SettingsClient
146 ->config_value( dirs => 'xsl' ).
147 "/MARC21slim2OAIDC.xsl"
149 # and stash a transformer
150 $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
151 $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
152 $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
154 # parse the RSS xslt ...
155 my $rss_xslt = $_parser->parse_file(
156 OpenSRF::Utils::SettingsClient
158 ->config_value( dirs => 'xsl' ).
159 "/MARC21slim2RSS2.xsl"
161 # and stash a transformer
162 $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
164 # parse the FGDC xslt ...
165 my $fgdc_xslt = $_parser->parse_file(
166 OpenSRF::Utils::SettingsClient
168 ->config_value( dirs => 'xsl' ).
169 "/MARC21slim2FGDC.xsl"
171 # and stash a transformer
172 $record_xslt{fgdc}{xslt} = $_xslt->parse_stylesheet( $fgdc_xslt );
173 $record_xslt{fgdc}{docs} = 'http://www.fgdc.gov/metadata/csdgm/index_html';
174 $record_xslt{fgdc}{schema_location} = 'http://www.fgdc.gov/metadata/fgdc-std-001-1998.xsd';
176 register_record_transforms();
181 sub register_record_transforms {
182 for my $type ( keys %record_xslt ) {
183 __PACKAGE__->register_method(
184 method => 'retrieve_record_transform',
185 api_name => "open-ils.supercat.record.$type.retrieve",
189 { desc => "Returns the \U$type\E representation ".
190 "of the requested bibliographic record",
194 desc => 'An OpenILS biblio::record_entry id',
198 { desc => "The bib record in \U$type\E",
203 __PACKAGE__->register_method(
204 method => 'retrieve_isbn_transform',
205 api_name => "open-ils.supercat.isbn.$type.retrieve",
209 { desc => "Returns the \U$type\E representation ".
210 "of the requested bibliographic record",
218 { desc => "The bib record in \U$type\E",
230 return unless ($tree && ref($tree->$field));
232 my @things = $filter->($tree);
233 for my $v ( @{$tree->$field} ){
234 push @things, $filter->($v);
235 push @things, tree_walker($v, $field, $filter);
240 # find a label_sortkey for a call number with a label which is equal
241 # (or close to) a given label value
242 sub _label_sortkey_from_label {
243 my ($label, $_storage, $ou_ids, $cp_filter) = @_;
245 my $closest_cn = $_storage->request(
246 "open-ils.cstore.direct.asset.call_number.search.atomic",
247 { label => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
248 owning_lib => $ou_ids,
253 order_by => { acn => "oils_text_as_bytea(label), id" }
257 return $closest_cn->[0]->label_sortkey;
259 return '~~~'; #fallback to high ascii value, we are at the end
269 my $page_size = shift || 9;
270 my $page = shift || 0;
271 my $statuses = shift || [];
272 my $copy_locations = shift || [];
274 my ($before_limit,$after_limit) = (0,0);
275 my ($before_offset,$after_offset) = (0,0);
278 $before_limit = $after_limit = int($page_size / 2);
279 $after_limit += 1 if ($page_size % 2);
281 $before_offset = $after_offset = int($page_size / 2);
282 $before_offset += 1 if ($page_size % 2);
283 $before_limit = $after_limit = $page_size;
286 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
288 my $o_search = { shortname => $ou };
289 if (!$ou || $ou eq '-') {
290 $o_search = { parent_ou => undef };
293 my $orgs = $_storage->request(
294 "open-ils.cstore.direct.actor.org_unit.search",
297 flesh_fields => { aou => [qw/children/] }
301 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
303 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
308 if (@$statuses || @$copy_locations) {
313 call_number => { '=' => { '+acn' => 'id' } },
315 ((@$statuses) ? ( status => $statuses) : ()),
316 ((@$copy_locations) ? ( location => $copy_locations) : ())
322 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
325 my $before = $_storage->request(
326 "open-ils.cstore.direct.asset.call_number.search.atomic",
327 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
328 owning_lib => \@ou_ids,
333 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
334 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
335 limit => $before_limit,
336 offset => abs($page) * $page_size - $before_offset,
339 push @list, reverse(@$before);
343 my $after = $_storage->request(
344 "open-ils.cstore.direct.asset.call_number.search.atomic",
345 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
346 owning_lib => \@ou_ids,
351 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
352 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
353 limit => $after_limit,
354 offset => abs($page) * $page_size - $after_offset,
362 __PACKAGE__->register_method(
363 method => 'cn_browse',
364 api_name => 'open-ils.supercat.call_number.browse',
369 Returns the XML representation of the requested bibliographic record's holdings
374 desc => 'The target call number label',
376 { name => 'org_unit',
377 desc => 'The org unit shortname (or "-" or undef for global) to browse',
379 { name => 'page_size',
380 desc => 'Count of call numbers to retrieve, default is 9',
383 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
385 { name => 'statuses',
386 desc => 'Array of statuses to filter copies by, optional and can be undef.',
388 { name => 'locations',
389 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
393 { desc => 'Call numbers with owning_lib and record fleshed',
404 my $limit = shift || 10;
405 my $page = shift || 0;
406 my $statuses = shift || [];
407 my $copy_locations = shift || [];
410 my $offset = abs($page) * $limit;
411 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
413 my $o_search = { shortname => $ou };
414 if (!$ou || $ou eq '-') {
415 $o_search = { parent_ou => undef };
418 my $orgs = $_storage->request(
419 "open-ils.cstore.direct.actor.org_unit.search",
422 flesh_fields => { aou => [qw/children/] }
426 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
428 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
433 if (@$statuses || @$copy_locations) {
438 call_number => { '=' => { '+acn' => 'id' } },
440 ((@$statuses) ? ( status => $statuses) : ()),
441 ((@$copy_locations) ? ( location => $copy_locations) : ())
447 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
450 my $before = $_storage->request(
451 "open-ils.cstore.direct.asset.call_number.search.atomic",
452 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
453 owning_lib => \@ou_ids,
458 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
459 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
464 push @list, reverse(@$before);
468 my $after = $_storage->request(
469 "open-ils.cstore.direct.asset.call_number.search.atomic",
470 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
471 owning_lib => \@ou_ids,
476 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
477 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
487 __PACKAGE__->register_method(
488 method => 'cn_startwith',
489 api_name => 'open-ils.supercat.call_number.startwith',
494 Returns the XML representation of the requested bibliographic record's holdings
499 desc => 'The target call number label',
501 { name => 'org_unit',
502 desc => 'The org unit shortname (or "-" or undef for global) to browse',
504 { name => 'page_size',
505 desc => 'Count of call numbers to retrieve, default is 9',
508 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
510 { name => 'statuses',
511 desc => 'Array of statuses to filter copies by, optional and can be undef.',
513 { name => 'locations',
514 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
518 { desc => 'Call numbers with owning_lib and record fleshed',
524 sub new_books_by_item {
529 my $page_size = shift || 10;
530 my $page = shift || 1;
531 my $statuses = shift || [];
532 my $copy_locations = shift || [];
534 my $offset = $page_size * ($page - 1);
536 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
539 if ($ou && $ou ne '-') {
540 my $orgs = $_storage->request(
541 "open-ils.cstore.direct.actor.org_unit.search",
542 { shortname => $ou },
544 flesh_fields => { aou => [qw/children/] }
547 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
550 $logger->debug("Searching for records with new copies at orgs [".join(',',@ou_ids)."], based on $ou");
551 my $cns = $_storage->request(
552 "open-ils.cstore.json_query.atomic",
553 { select => { acn => ['record'],
554 acp => [{ aggregate => 1 => transform => max => column => create_date => alias => 'create_date'}]
556 from => { 'acn' => { 'acp' => { field => call_number => fkey => 'id' } } },
560 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
561 ((@$statuses) ? ( status => $statuses) : ()),
562 ((@$copy_locations) ? ( location => $copy_locations) : ())
564 '+acn' => { record => { '>' => 0 } },
566 order_by => { acp => { create_date => { transform => 'max', direction => 'desc' } } },
572 return [ map { $_->{record} } @$cns ];
574 __PACKAGE__->register_method(
575 method => 'new_books_by_item',
576 api_name => 'open-ils.supercat.new_book_list',
581 Returns the XML representation of the requested bibliographic record's holdings
585 { name => 'org_unit',
586 desc => 'The org unit shortname (or "-" or undef for global) to list',
588 { name => 'page_size',
589 desc => 'Count of records to retrieve, default is 10',
592 desc => 'The page of records to retrieve, calculated based on page_size. Starts at 1.',
594 { name => 'statuses',
595 desc => 'Array of statuses to filter copies by, optional and can be undef.',
597 { name => 'locations',
598 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
602 { desc => 'Record IDs',
611 return tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
613 __PACKAGE__->register_method(
614 method => 'general_browse',
615 api_name => 'open-ils.supercat.title.browse',
616 tag => 'tnf', subfield => 'a',
620 { desc => "Returns a list of the requested org-scoped record IDs held",
622 [ { name => 'value', desc => 'The target title', type => 'string' },
623 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
624 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
625 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
626 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
627 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
628 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
631 __PACKAGE__->register_method(
632 method => 'general_browse',
633 api_name => 'open-ils.supercat.author.browse',
634 tag => [qw/100 110 111/], subfield => 'a',
638 { desc => "Returns a list of the requested org-scoped record IDs held",
640 [ { name => 'value', desc => 'The target author', type => 'string' },
641 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
642 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
643 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
644 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
645 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
646 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
649 __PACKAGE__->register_method(
650 method => 'general_browse',
651 api_name => 'open-ils.supercat.subject.browse',
652 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
656 { desc => "Returns a list of the requested org-scoped record IDs held",
658 [ { name => 'value', desc => 'The target subject', type => 'string' },
659 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
660 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
661 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
662 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
663 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
664 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
667 __PACKAGE__->register_method(
668 method => 'general_browse',
669 api_name => 'open-ils.supercat.topic.browse',
670 tag => [qw/650 690/], subfield => 'a',
674 { desc => "Returns a list of the requested org-scoped record IDs held",
676 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
677 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
678 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
679 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
680 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
681 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
682 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
685 __PACKAGE__->register_method(
686 method => 'general_browse',
687 api_name => 'open-ils.supercat.series.browse',
688 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
692 { desc => "Returns a list of the requested org-scoped record IDs held",
694 [ { name => 'value', desc => 'The target series', type => 'string' },
695 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
696 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
697 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
698 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
699 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
700 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
710 my $subfield = shift;
713 my $page_size = shift || 9;
714 my $page = shift || 0;
715 my $statuses = shift || [];
716 my $copy_locations = shift || [];
718 my ($before_limit,$after_limit) = (0,0);
719 my ($before_offset,$after_offset) = (0,0);
722 $before_limit = $after_limit = int($page_size / 2);
723 $after_limit += 1 if ($page_size % 2);
725 $before_offset = $after_offset = int($page_size / 2);
726 $before_offset += 1 if ($page_size % 2);
727 $before_limit = $after_limit = $page_size;
730 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
733 if ($ou && $ou ne '-') {
734 my $orgs = $_storage->request(
735 "open-ils.cstore.direct.actor.org_unit.search",
736 { shortname => $ou },
738 flesh_fields => { aou => [qw/children/] }
741 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
744 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
749 my $before = $_storage->request(
750 "open-ils.cstore.json_query.atomic",
751 { select => { mfr => [qw/record value/] },
756 subfield => $subfield,
757 value => { '<' => lc($value) }
761 { select=> { acp => [ 'id' ] },
762 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
764 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
767 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
768 ((@$statuses) ? ( status => $statuses) : ()),
769 ((@$copy_locations) ? ( location => $copy_locations) : ())
776 { select=> { auri => [ 'id' ] },
777 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
779 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
780 '+auri' => { active => 't' }
787 order_by => { mfr => { value => 'desc' } },
788 limit => $before_limit,
789 offset => abs($page) * $page_size - $before_offset,
792 push @list, map { $_->{record} } reverse(@$before);
796 my $after = $_storage->request(
797 "open-ils.cstore.json_query.atomic",
798 { select => { mfr => [qw/record value/] },
803 subfield => $subfield,
804 value => { '>=' => lc($value) }
808 { select=> { acp => [ 'id' ] },
809 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
811 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
814 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
815 ((@$statuses) ? ( status => $statuses) : ()),
816 ((@$copy_locations) ? ( location => $copy_locations) : ())
823 { select=> { auri => [ 'id' ] },
824 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
826 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
827 '+auri' => { active => 't' }
834 order_by => { mfr => { value => 'asc' } },
835 limit => $after_limit,
836 offset => abs($page) * $page_size - $after_offset,
839 push @list, map { $_->{record} } @$after;
844 __PACKAGE__->register_method(
845 method => 'tag_sf_browse',
846 api_name => 'open-ils.supercat.tag.browse',
851 Returns a list of the requested org-scoped record IDs held
856 desc => 'The target MARC tag',
858 { name => 'subfield',
859 desc => 'The target MARC subfield',
862 desc => 'The target string',
864 { name => 'org_unit',
865 desc => 'The org unit shortname (or "-" or undef for global) to browse',
867 { name => 'page_size',
868 desc => 'Count of call numbers to retrieve, default is 9',
871 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
873 { name => 'statuses',
874 desc => 'Array of statuses to filter copies by, optional and can be undef.',
876 { name => 'locations',
877 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
881 { desc => 'Record IDs that have copies at the relevant org units',
886 sub general_authority_browse {
889 return authority_tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
891 __PACKAGE__->register_method(
892 method => 'general_authority_browse',
893 api_name => 'open-ils.supercat.authority.title.browse',
894 tag => ['130'], subfield => 'a',
898 { desc => "Returns a list of the requested authority record IDs held",
900 [ { name => 'value', desc => 'The target title', type => 'string' },
901 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
902 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
903 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
906 __PACKAGE__->register_method(
907 method => 'general_authority_browse',
908 api_name => 'open-ils.supercat.authority.author.browse',
909 tag => [qw/100 110 111/], subfield => 'a',
913 { desc => "Returns a list of the requested authority record IDs held",
915 [ { name => 'value', desc => 'The target author', type => 'string' },
916 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
917 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
918 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
921 __PACKAGE__->register_method(
922 method => 'general_authority_browse',
923 api_name => 'open-ils.supercat.authority.subject.browse',
924 tag => [qw/148 150 151 155/], subfield => 'a',
928 { desc => "Returns a list of the requested authority record IDs held",
930 [ { name => 'value', desc => 'The target subject', type => 'string' },
931 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
932 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
933 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
936 __PACKAGE__->register_method(
937 method => 'general_authority_browse',
938 api_name => 'open-ils.supercat.authority.topic.browse',
939 tag => ['150'], subfield => 'a',
943 { desc => "Returns a list of the requested authority record IDs held",
945 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
946 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
947 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
948 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
951 __PACKAGE__->register_method(
952 method => 'general_authority_browse',
953 api_name => 'open-ils.supercat.authority.title.refs.browse',
954 tag => ['130'], subfield => 'a',
958 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
960 [ { name => 'value', desc => 'The target title', type => 'string' },
961 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
962 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
963 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
966 __PACKAGE__->register_method(
967 method => 'general_authority_browse',
968 api_name => 'open-ils.supercat.authority.author.refs.browse',
969 tag => [qw/100 110 111/], subfield => 'a',
973 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
975 [ { name => 'value', desc => 'The target author', type => 'string' },
976 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
977 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
978 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
981 __PACKAGE__->register_method(
982 method => 'general_authority_browse',
983 api_name => 'open-ils.supercat.authority.subject.refs.browse',
984 tag => [qw/148 150 151 155/], subfield => 'a',
988 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
990 [ { name => 'value', desc => 'The target subject', type => 'string' },
991 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
992 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
993 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
996 __PACKAGE__->register_method(
997 method => 'general_authority_browse',
998 api_name => 'open-ils.supercat.authority.topic.refs.browse',
999 tag => ['150'], subfield => 'a',
1003 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1005 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1006 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1007 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1008 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1012 sub authority_tag_sf_browse {
1017 my $subfield = shift;
1019 my $page_size = shift || 9;
1020 my $page = shift || 0;
1022 # Match authority.full_rec normalization
1023 $value = naco_normalize($value, $subfield);
1025 my ($before_limit,$after_limit) = (0,0);
1026 my ($before_offset,$after_offset) = (0,0);
1029 $before_limit = $after_limit = int($page_size / 2);
1030 $after_limit += 1 if ($page_size % 2);
1032 $before_offset = $after_offset = int($page_size / 2);
1033 $before_offset += 1 if ($page_size % 2);
1034 $before_limit = $after_limit = $page_size;
1037 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1039 # .refs variant includes 4xx and 5xx variants for see / see also
1041 foreach my $tagname (@$tag) {
1042 push(@ref_tags, $tagname);
1043 if ($self->api_name =~ /\.refs\./) {
1044 push(@ref_tags, '4' . substr($tagname, 1, 2));
1045 push(@ref_tags, '5' . substr($tagname, 1, 2));
1051 my $before = $_storage->request(
1052 "open-ils.cstore.json_query.atomic",
1053 { select => { afr => [qw/record value/] },
1054 from => { 'are', 'afr' },
1056 '+afr' => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1057 '+are' => { 'deleted' => 'f' }
1059 order_by => { afr => { value => 'desc' } },
1060 limit => $before_limit,
1061 offset => abs($page) * $page_size - $before_offset,
1064 push @list, map { $_->{record} } reverse(@$before);
1068 my $after = $_storage->request(
1069 "open-ils.cstore.json_query.atomic",
1070 { select => { afr => [qw/record value/] },
1071 from => { 'are', 'afr' },
1073 '+afr' => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1074 '+are' => { 'deleted' => 'f' }
1076 order_by => { afr => { value => 'asc' } },
1077 limit => $after_limit,
1078 offset => abs($page) * $page_size - $after_offset,
1081 push @list, map { $_->{record} } @$after;
1084 # If we're not pulling in see/see also references, just return the raw list
1085 if ($self->api_name !~ /\.refs\./) {
1089 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1092 foreach my $record (@list) {
1093 next if exists $seen{$record};
1094 push @retlist, int($record);
1100 __PACKAGE__->register_method(
1101 method => 'authority_tag_sf_browse',
1102 api_name => 'open-ils.supercat.authority.tag.browse',
1106 { desc => <<" DESC",
1107 Returns a list of the requested authority record IDs held
1112 desc => 'The target Authority MARC tag',
1114 { name => 'subfield',
1115 desc => 'The target Authority MARC subfield',
1118 desc => 'The target string',
1120 { name => 'page_size',
1121 desc => 'Count of call numbers to retrieve, default is 9',
1124 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1128 { desc => 'Authority Record IDs that are near the target string',
1133 sub general_startwith {
1136 return tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1138 __PACKAGE__->register_method(
1139 method => 'general_startwith',
1140 api_name => 'open-ils.supercat.title.startwith',
1141 tag => 'tnf', subfield => 'a',
1145 { desc => "Returns a list of the requested org-scoped record IDs held",
1147 [ { name => 'value', desc => 'The target title', type => 'string' },
1148 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1149 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1150 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1151 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1152 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1153 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1156 __PACKAGE__->register_method(
1157 method => 'general_startwith',
1158 api_name => 'open-ils.supercat.author.startwith',
1159 tag => [qw/100 110 111/], subfield => 'a',
1163 { desc => "Returns a list of the requested org-scoped record IDs held",
1165 [ { name => 'value', desc => 'The target author', type => 'string' },
1166 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1167 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1168 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1169 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1170 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1171 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1174 __PACKAGE__->register_method(
1175 method => 'general_startwith',
1176 api_name => 'open-ils.supercat.subject.startwith',
1177 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
1181 { desc => "Returns a list of the requested org-scoped record IDs held",
1183 [ { name => 'value', desc => 'The target subject', type => 'string' },
1184 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1185 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1186 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1187 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1188 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1189 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1192 __PACKAGE__->register_method(
1193 method => 'general_startwith',
1194 api_name => 'open-ils.supercat.topic.startwith',
1195 tag => [qw/650 690/], subfield => 'a',
1199 { desc => "Returns a list of the requested org-scoped record IDs held",
1201 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1202 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1203 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1204 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1205 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1206 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1207 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1210 __PACKAGE__->register_method(
1211 method => 'general_startwith',
1212 api_name => 'open-ils.supercat.series.startwith',
1213 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
1217 { desc => "Returns a list of the requested org-scoped record IDs held",
1219 [ { name => 'value', desc => 'The target series', type => 'string' },
1220 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1221 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1222 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1223 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1224 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1225 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1230 sub tag_sf_startwith {
1235 my $subfield = shift;
1238 my $limit = shift || 10;
1239 my $page = shift || 0;
1240 my $statuses = shift || [];
1241 my $copy_locations = shift || [];
1243 my $offset = $limit * abs($page);
1244 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1247 if ($ou && $ou ne '-') {
1248 my $orgs = $_storage->request(
1249 "open-ils.cstore.direct.actor.org_unit.search",
1250 { shortname => $ou },
1252 flesh_fields => { aou => [qw/children/] }
1255 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1258 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
1263 my $before = $_storage->request(
1264 "open-ils.cstore.json_query.atomic",
1265 { select => { mfr => [qw/record value/] },
1270 subfield => $subfield,
1271 value => { '<' => lc($value) }
1275 { select=> { acp => [ 'id' ] },
1276 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1278 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1281 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1282 ((@$statuses) ? ( status => $statuses) : ()),
1283 ((@$copy_locations) ? ( location => $copy_locations) : ())
1290 { select=> { auri => [ 'id' ] },
1291 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1293 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1294 '+auri' => { active => 't' }
1301 order_by => { mfr => { value => 'desc' } },
1306 push @list, map { $_->{record} } reverse(@$before);
1310 my $after = $_storage->request(
1311 "open-ils.cstore.json_query.atomic",
1312 { select => { mfr => [qw/record value/] },
1317 subfield => $subfield,
1318 value => { '>=' => lc($value) }
1322 { select=> { acp => [ 'id' ] },
1323 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1325 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1328 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1329 ((@$statuses) ? ( status => $statuses) : ()),
1330 ((@$copy_locations) ? ( location => $copy_locations) : ())
1337 { select=> { auri => [ 'id' ] },
1338 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1340 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1341 '+auri' => { active => 't' }
1348 order_by => { mfr => { value => 'asc' } },
1353 push @list, map { $_->{record} } @$after;
1358 __PACKAGE__->register_method(
1359 method => 'tag_sf_startwith',
1360 api_name => 'open-ils.supercat.tag.startwith',
1364 { desc => <<" DESC",
1365 Returns a list of the requested org-scoped record IDs held
1370 desc => 'The target MARC tag',
1372 { name => 'subfield',
1373 desc => 'The target MARC subfield',
1376 desc => 'The target string',
1378 { name => 'org_unit',
1379 desc => 'The org unit shortname (or "-" or undef for global) to browse',
1381 { name => 'page_size',
1382 desc => 'Count of call numbers to retrieve, default is 9',
1385 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1387 { name => 'statuses',
1388 desc => 'Array of statuses to filter copies by, optional and can be undef.',
1390 { name => 'locations',
1391 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1395 { desc => 'Record IDs that have copies at the relevant org units',
1400 sub general_authority_startwith {
1403 return authority_tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1405 __PACKAGE__->register_method(
1406 method => 'general_authority_startwith',
1407 api_name => 'open-ils.supercat.authority.title.startwith',
1408 tag => ['130'], subfield => 'a',
1412 { desc => "Returns a list of the requested authority record IDs held",
1414 [ { name => 'value', desc => 'The target title', type => 'string' },
1415 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1416 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1417 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1420 __PACKAGE__->register_method(
1421 method => 'general_authority_startwith',
1422 api_name => 'open-ils.supercat.authority.author.startwith',
1423 tag => [qw/100 110 111/], subfield => 'a',
1427 { desc => "Returns a list of the requested authority record IDs held",
1429 [ { name => 'value', desc => 'The target author', type => 'string' },
1430 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1431 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1432 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1435 __PACKAGE__->register_method(
1436 method => 'general_authority_startwith',
1437 api_name => 'open-ils.supercat.authority.subject.startwith',
1438 tag => [qw/148 150 151 155/], subfield => 'a',
1442 { desc => "Returns a list of the requested authority record IDs held",
1444 [ { name => 'value', desc => 'The target subject', type => 'string' },
1445 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1446 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1447 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1450 __PACKAGE__->register_method(
1451 method => 'general_authority_startwith',
1452 api_name => 'open-ils.supercat.authority.topic.startwith',
1453 tag => ['150'], subfield => 'a',
1457 { desc => "Returns a list of the requested authority record IDs held",
1459 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1460 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1461 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1462 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1465 __PACKAGE__->register_method(
1466 method => 'general_authority_startwith',
1467 api_name => 'open-ils.supercat.authority.title.refs.startwith',
1468 tag => ['130'], subfield => 'a',
1472 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1474 [ { name => 'value', desc => 'The target title', type => 'string' },
1475 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1476 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1477 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1480 __PACKAGE__->register_method(
1481 method => 'general_authority_startwith',
1482 api_name => 'open-ils.supercat.authority.author.refs.startwith',
1483 tag => [qw/100 110 111/], subfield => 'a',
1487 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1489 [ { name => 'value', desc => 'The target author', type => 'string' },
1490 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1491 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1492 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1495 __PACKAGE__->register_method(
1496 method => 'general_authority_startwith',
1497 api_name => 'open-ils.supercat.authority.subject.refs.startwith',
1498 tag => [qw/148 150 151 155/], subfield => 'a',
1502 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1504 [ { name => 'value', desc => 'The target subject', type => 'string' },
1505 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1506 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1507 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1510 __PACKAGE__->register_method(
1511 method => 'general_authority_startwith',
1512 api_name => 'open-ils.supercat.authority.topic.refs.startwith',
1513 tag => ['150'], subfield => 'a',
1517 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1519 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1520 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1521 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1522 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1526 sub authority_tag_sf_startwith {
1531 my $subfield = shift;
1534 my $limit = shift || 10;
1535 my $page = shift || 0;
1537 # Match authority.full_rec normalization
1538 $value = naco_normalize($value, $subfield);
1540 my $ref_limit = $limit;
1541 my $offset = $limit * abs($page);
1542 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1545 # .refs variant includes 4xx and 5xx variants for see / see also
1546 foreach my $tagname (@$tag) {
1547 push(@ref_tags, $tagname);
1548 if ($self->api_name =~ /\.refs\./) {
1549 push(@ref_tags, '4' . substr($tagname, 1, 2));
1550 push(@ref_tags, '5' . substr($tagname, 1, 2));
1557 # Don't skip the first actual page of results in descending order
1558 $offset = $offset - $limit;
1560 my $before = $_storage->request(
1561 "open-ils.cstore.json_query.atomic",
1562 { select => { afr => [qw/record value/] },
1563 from => { 'afr', 'are' },
1565 '+afr' => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1566 '+are' => { deleted => 'f' }
1568 order_by => { afr => { value => 'desc' } },
1569 limit => $ref_limit,
1573 push @list, map { $_->{record} } reverse(@$before);
1577 my $after = $_storage->request(
1578 "open-ils.cstore.json_query.atomic",
1579 { select => { afr => [qw/record value/] },
1580 from => { 'afr', 'are' },
1582 '+afr' => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1583 '+are' => { deleted => 'f' }
1585 order_by => { afr => { value => 'asc' } },
1586 limit => $ref_limit,
1590 push @list, map { $_->{record} } @$after;
1593 # If we're not pulling in see/see also references, just return the raw list
1594 if ($self->api_name !~ /\.refs\./) {
1598 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1601 foreach my $record (@list) {
1602 next if exists $seen{$record};
1603 push @retlist, int($record);
1609 __PACKAGE__->register_method(
1610 method => 'authority_tag_sf_startwith',
1611 api_name => 'open-ils.supercat.authority.tag.startwith',
1615 { desc => <<" DESC",
1616 Returns a list of the requested authority record IDs held
1621 desc => 'The target Authority MARC tag',
1623 { name => 'subfield',
1624 desc => 'The target Authority MARC subfield',
1627 desc => 'The target string',
1629 { name => 'page_size',
1630 desc => 'Count of call numbers to retrieve, default is 9',
1633 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1637 { desc => 'Authority Record IDs that are near the target string',
1643 sub holding_data_formats {
1646 namespace_uri => 'http://www.loc.gov/MARC21/slim',
1647 docs => 'http://www.loc.gov/marcxml/',
1648 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
1652 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acn.formats', api_level => 1 );
1653 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acp.formats', api_level => 1 );
1654 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.auri.formats', api_level => 1 );
1657 __PACKAGE__->register_method(
1658 method => 'retrieve_uri',
1659 api_name => 'open-ils.supercat.auri.marcxml.retrieve',
1663 { desc => <<" DESC",
1664 Returns a fleshed call number object
1669 desc => 'An OpenILS asset::uri id',
1673 { desc => 'fleshed uri',
1681 my $args = shift || {};
1683 return OpenILS::Application::SuperCat::unAPI
1684 ->new(OpenSRF::AppSession
1685 ->create( 'open-ils.cstore' )
1687 "open-ils.cstore.direct.asset.uri.retrieve",
1691 auri => [qw/call_number_maps/],
1692 auricnm => [qw/call_number/],
1693 acn => [qw/owning_lib record prefix suffix/],
1700 __PACKAGE__->register_method(
1701 method => 'retrieve_copy',
1702 api_name => 'open-ils.supercat.acp.marcxml.retrieve',
1706 { desc => <<" DESC",
1707 Returns a fleshed call number object
1712 desc => 'An OpenILS asset::copy id',
1716 { desc => 'fleshed copy',
1724 my $args = shift || {};
1726 return OpenILS::Application::SuperCat::unAPI
1727 ->new(OpenSRF::AppSession
1728 ->create( 'open-ils.cstore' )
1730 "open-ils.cstore.direct.asset.copy.retrieve",
1734 acn => [qw/owning_lib record prefix suffix/],
1735 acp => [qw/call_number location status circ_lib stat_cat_entries notes parts/],
1742 __PACKAGE__->register_method(
1743 method => 'retrieve_callnumber',
1744 api_name => 'open-ils.supercat.acn.marcxml.retrieve',
1749 { desc => <<" DESC",
1750 Returns a fleshed call number object
1755 desc => 'An OpenILS asset::call_number id',
1759 { desc => 'call number with copies',
1763 sub retrieve_callnumber {
1767 my $args = shift || {};
1769 return OpenILS::Application::SuperCat::unAPI
1770 ->new(OpenSRF::AppSession
1771 ->create( 'open-ils.cstore' )
1773 "open-ils.cstore.direct.asset.call_number.retrieve",
1777 acn => [qw/owning_lib record copies uri_maps prefix suffix/],
1778 auricnm => [qw/uri/],
1779 acp => [qw/location status circ_lib stat_cat_entries notes parts/],
1787 __PACKAGE__->register_method(
1788 method => 'basic_record_holdings',
1789 api_name => 'open-ils.supercat.record.basic_holdings.retrieve',
1794 { desc => <<" DESC",
1795 Returns a basic hash representation of the requested bibliographic record's holdings
1800 desc => 'An OpenILS biblio::record_entry id',
1804 { desc => 'Hash of bib record holdings hierarchy (call numbers and copies)',
1808 sub basic_record_holdings {
1814 # holdings hold an array of call numbers, which hold an array of copies
1815 # holdings => [ label: { library, [ copies: { barcode, location, status, circ_lib } ] } ]
1818 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1820 my $tree = $_storage->request(
1821 "open-ils.cstore.direct.biblio.record_entry.retrieve",
1825 bre => [qw/call_numbers/],
1826 acn => [qw/copies owning_lib prefix suffix/],
1827 acp => [qw/location status circ_lib parts/],
1832 my $o_search = { shortname => uc($ou) };
1833 if (!$ou || $ou eq '-') {
1834 $o_search = { parent_ou => undef };
1837 my $orgs = $_storage->request(
1838 "open-ils.cstore.direct.actor.org_unit.search",
1841 flesh_fields => { aou => [qw/children/] }
1845 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1847 $logger->debug("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
1849 for my $cn (@{$tree->call_numbers}) {
1850 next unless ( $cn->deleted eq 'f' || $cn->deleted == 0 );
1853 for my $c (@{$cn->copies}) {
1854 next unless grep {$c->circ_lib->id == $_} @ou_ids;
1855 next unless ( $c->deleted eq 'f' || $c->deleted == 0 );
1861 $holdings{$cn->label}{'owning_lib'} = $cn->owning_lib->shortname;
1863 for my $cp (@{$cn->copies}) {
1865 next unless grep { $cp->circ_lib->id == $_ } @ou_ids;
1866 next unless ( $cp->deleted eq 'f' || $cp->deleted == 0 );
1868 push @{$holdings{$cn->label}{'copies'}}, {
1869 barcode => $cp->barcode,
1870 status => $cp->status->name,
1871 location => $cp->location->name,
1872 circlib => $cp->circ_lib->shortname
1881 #__PACKAGE__->register_method(
1882 # method => 'new_record_holdings',
1883 # api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
1888 # { desc => <<" DESC",
1889 #Returns the XML representation of the requested bibliographic record's holdings
1893 # { name => 'bibId',
1894 # desc => 'An OpenILS biblio::record_entry id',
1895 # type => 'number' },
1898 # { desc => 'Stream of bib record holdings hierarchy in XML',
1899 # type => 'string' }
1904 sub new_record_holdings {
1913 $paging = [-1,0] if (!$paging or !ref($paging) or @$paging == 0);
1914 my $limit = $$paging[0];
1915 my $offset = $$paging[1] || 0;
1917 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1918 my $_search = OpenSRF::AppSession->create( 'open-ils.search' );
1920 my $o_search = { shortname => uc($ou) };
1921 if (!$ou || $ou eq '-') {
1922 $o_search = { parent_ou => undef };
1925 my $one_org = $_storage->request(
1926 "open-ils.cstore.direct.actor.org_unit.search",
1930 my $count_req = $_search->request('open-ils.search.biblio.record.copy_count' => $one_org->id => $bib);
1931 my $staff_count_req = $_search->request('open-ils.search.biblio.record.copy_count.staff' => $one_org->id => $bib);
1933 my $orgs = $_storage->request(
1934 'open-ils.cstore.json_query.atomic',
1935 { from => [ 'actor.org_unit_descendants', defined($depth) ? ( $one_org->id, $depth ) : ( $one_org->id ) ] }
1939 my @ou_ids = map { $_->{id} } @$orgs;
1941 $logger->info("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
1943 my %subselect = ( '-or' => [
1944 { owning_lib => \@ou_ids },
1948 call_number => { '=' => {'+acn'=>'id'} },
1950 circ_lib => \@ou_ids
1956 if ($flesh and $flesh eq 'uris') {
1958 owning_lib => \@ou_ids,
1960 from => { auricnm => 'auri' },
1962 call_number => { '=' => {'+acn'=>'id'} },
1963 '+auri' => { active => 't' }
1970 my $cns = $_storage->request(
1971 "open-ils.cstore.direct.asset.call_number.search.atomic",
1978 acn => [qw/copies owning_lib uri_maps prefix suffix/],
1979 auricnm => [qw/uri/],
1980 acp => [qw/circ_lib location status stat_cat_entries notes parts/],
1981 asce => [qw/stat_cat/],
1983 ( $limit > -1 ? ( limit => $limit ) : () ),
1984 ( $offset ? ( offset => $offset ) : () ),
1985 order_by => { acn => { label_sortkey => {} } }
1989 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
1993 $client->respond("<holdings xmlns='http://open-ils.org/spec/holdings/v1'><counts>\n");
1995 my $copy_counts = $count_req->gather(1);
1996 my $staff_copy_counts = $staff_count_req->gather(1);
1998 for my $c (@$copy_counts) {
1999 $$c{transcendant} ||= 0;
2000 my $out = "<count type='public'";
2001 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2002 $client->respond("$out/>\n")
2005 for my $c (@$staff_copy_counts) {
2006 $$c{transcendant} ||= 0;
2007 my $out = "<count type='staff'";
2008 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2009 $client->respond("$out/>\n")
2012 $client->respond("</counts><volumes>\n");
2014 for my $cn (@$cns) {
2015 next unless (@{$cn->copies} > 0 or (ref($cn->uri_maps) and @{$cn->uri_maps}));
2017 # We don't want O:A:S:unAPI::acn to return the record, we've got that already
2018 # In the context of BibTemplate, copies aren't necessary because we pull those
2019 # in a separate call
2021 OpenILS::Application::SuperCat::unAPI::acn
2023 ->as_xml( {no_record => 1, no_copies => ($flesh ? 0 : 1)} )
2027 $client->respond("</volumes><subscriptions>\n");
2029 $logger->info("Searching for serial holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2031 %subselect = ( '-or' => [
2032 { owning_lib => \@ou_ids },
2035 where => { holding_lib => \@ou_ids }
2040 my $ssubs = $_storage->request(
2041 "open-ils.cstore.direct.serial.subscription.search.atomic",
2042 { record_entry => $bib,
2047 ssub => [qw/distributions issuances scaps owning_lib/],
2048 sdist => [qw/basic_summary supplement_summary index_summary streams holding_lib/],
2049 sstr => [qw/items/],
2050 sitem => [qw/notes unit/],
2051 sunit => [qw/notes location status circ_lib stat_cat_entries call_number/],
2052 acn => [qw/owning_lib prefix suffix/],
2054 ( $limit > -1 ? ( limit => $limit ) : () ),
2055 ( $offset ? ( offset => $offset ) : () ),
2067 date_expected => {},
2074 for my $ssub (@$ssubs) {
2075 next unless (@{$ssub->distributions} or @{$ssub->issuances} or @{$ssub->scaps});
2077 # We don't want O:A:S:unAPI::ssub to return the record, we've got that already
2078 # In the context of BibTemplate, copies aren't necessary because we pull those
2079 # in a separate call
2081 OpenILS::Application::SuperCat::unAPI::ssub
2083 ->as_xml( {no_record => 1, no_items => ($flesh ? 0 : 1)} )
2088 return "</subscriptions></holdings>\n";
2090 __PACKAGE__->register_method(
2091 method => 'new_record_holdings',
2092 api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2097 { desc => <<" DESC",
2098 Returns the XML representation of the requested bibliographic record's holdings
2103 desc => 'An OpenILS biblio::record_entry ID',
2105 { name => 'orgUnit',
2106 desc => 'An OpenILS actor::org_unit short name that limits the scope of returned holdings',
2109 desc => 'An OpenILS actor::org_unit_type depththat limits the scope of returned holdings',
2111 { name => 'hideCopies',
2112 desc => 'Flag that prevents the inclusion of copies in the returned holdings',
2113 type => 'boolean' },
2115 desc => 'Arry of limit and offset for holdings paging',
2119 { desc => 'Stream of bib record holdings hierarchy in XML',
2129 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2131 my $recs = $_storage->request(
2132 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2133 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2136 return undef unless (@$recs);
2138 return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
2140 __PACKAGE__->register_method(
2141 method => 'isbn_holdings',
2142 api_name => 'open-ils.supercat.isbn.holdings_xml.retrieve',
2146 { desc => <<" DESC",
2147 Returns the XML representation of the requested bibliographic record's holdings
2156 { desc => 'The bib record holdings hierarchy in XML',
2164 return '' unless $text;
2165 $text =~ s/&/&/gsom;
2166 $text =~ s/</</gsom;
2167 $text =~ s/>/>/gsom;
2168 $text =~ s/"/"/gsom;
2169 $text =~ s/'/'/gsom;
2173 sub recent_changes {
2176 my $when = shift || '1-01-01';
2179 my $type = 'biblio';
2182 if ($self->api_name =~ /authority/o) {
2183 $type = 'authority';
2187 my $axis = 'create_date';
2188 $axis = 'edit_date' if ($self->api_name =~ /edit/o);
2190 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2192 return $_storage->request(
2193 "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
2194 { $axis => { ">" => $when }, id => { '>' => 0 }, deleted => 'f', active => 't' },
2195 { order_by => { $hint => "$axis desc" }, limit => $limit }
2199 for my $t ( qw/biblio authority/ ) {
2200 for my $a ( qw/import edit/ ) {
2202 __PACKAGE__->register_method(
2203 method => 'recent_changes',
2204 api_name => "open-ils.supercat.$t.record.$a.recent",
2208 { desc => "Returns a list of recently ${a}ed $t records",
2212 desc => "Date to start looking for ${a}ed records",
2213 default => '1-01-01',
2217 desc => "Maximum count to retrieve",
2221 { desc => "An id list of $t records",
2229 sub retrieve_authority_marcxml {
2234 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2236 my $record = $_storage->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rid )->gather(1);
2237 return $U->entityize( $record->marc ) if ($record);
2241 __PACKAGE__->register_method(
2242 method => 'retrieve_authority_marcxml',
2243 api_name => 'open-ils.supercat.authority.marcxml.retrieve',
2247 { desc => <<" DESC",
2248 Returns the MARCXML representation of the requested authority record
2252 { name => 'authorityId',
2253 desc => 'An OpenILS authority::record_entry id',
2257 { desc => 'The authority record in MARCXML',
2262 sub retrieve_record_marcxml {
2267 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2269 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
2270 return $U->entityize( $record->marc ) if ($record);
2274 __PACKAGE__->register_method(
2275 method => 'retrieve_record_marcxml',
2276 api_name => 'open-ils.supercat.record.marcxml.retrieve',
2280 { desc => <<" DESC",
2281 Returns the MARCXML representation of the requested bibliographic record
2286 desc => 'An OpenILS biblio::record_entry id',
2290 { desc => 'The bib record in MARCXML',
2295 sub retrieve_isbn_marcxml {
2300 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2302 my $recs = $_storage->request(
2303 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2304 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2307 return undef unless (@$recs);
2309 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2310 return $U->entityize( $record->marc ) if ($record);
2314 __PACKAGE__->register_method(
2315 method => 'retrieve_isbn_marcxml',
2316 api_name => 'open-ils.supercat.isbn.marcxml.retrieve',
2320 { desc => <<" DESC",
2321 Returns the MARCXML representation of the requested ISBN
2326 desc => 'An ... um ... ISBN',
2330 { desc => 'The bib record in MARCXML',
2335 sub retrieve_record_transform {
2340 (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
2342 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2343 #$_storage->connect;
2345 my $record = $_storage->request(
2346 'open-ils.cstore.direct.biblio.record_entry.retrieve',
2350 return undef unless ($record);
2352 return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2355 sub retrieve_isbn_transform {
2360 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2362 my $recs = $_storage->request(
2363 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2364 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2367 return undef unless (@$recs);
2369 (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
2371 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2373 return undef unless ($record);
2375 return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2378 sub retrieve_record_objects {
2383 my $type = 'biblio';
2385 if ($self->api_name =~ /authority/) {
2386 $type = 'authority';
2389 $ids = [$ids] unless (ref $ids);
2390 $ids = [grep {$_} @$ids];
2392 return [] unless (@$ids);
2394 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2395 return $_storage->request("open-ils.cstore.direct.$type.record_entry.search.atomic" => { id => [grep {$_} @$ids] })->gather(1);
2397 __PACKAGE__->register_method(
2398 method => 'retrieve_record_objects',
2399 api_name => 'open-ils.supercat.record.object.retrieve',
2403 { desc => <<" DESC",
2404 Returns the Fieldmapper object representation of the requested bibliographic records
2409 desc => 'OpenILS biblio::record_entry ids',
2413 { desc => 'The bib records',
2418 __PACKAGE__->register_method(
2419 method => 'retrieve_record_objects',
2420 api_name => 'open-ils.supercat.authority.object.retrieve',
2424 { desc => <<" DESC",
2425 Returns the Fieldmapper object representation of the requested authority records
2429 { name => 'authIds',
2430 desc => 'OpenILS authority::record_entry ids',
2434 { desc => 'The authority records',
2439 sub retrieve_isbn_object {
2444 return undef unless ($isbn);
2446 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2447 my $recs = $_storage->request(
2448 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2449 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2452 return undef unless (@$recs);
2454 return $_storage->request(
2455 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
2456 { id => $recs->[0]->record }
2459 __PACKAGE__->register_method(
2460 method => 'retrieve_isbn_object',
2461 api_name => 'open-ils.supercat.isbn.object.retrieve',
2465 { desc => <<" DESC",
2466 Returns the Fieldmapper object representation of the requested bibliographic record
2475 { desc => 'The bib record',
2482 sub retrieve_metarecord_mods {
2487 my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
2489 # Get the metarecord in question
2492 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
2495 # Now get the map of all bib records for the metarecord
2498 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2499 {metarecord => $rid}
2502 $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
2504 # and retrieve the lead (master) record as MODS
2506 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2507 ->run($mr->master_record);
2508 my $master_mods = $_parser->parse_string($master)->documentElement;
2509 $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods" );
2510 $master_mods->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2512 # ... and a MODS clone to populate, with guts removed.
2513 my $mods = $_parser->parse_string($master)->documentElement;
2514 $mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # modsCollection element
2515 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2516 ($mods) = $mods->findnodes('//mods:mods');
2517 #$mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # mods element
2518 $mods->removeChildNodes;
2519 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2521 # Add the metarecord ID as a (locally defined) info URI
2522 my $recordInfo = $mods
2524 ->createElement("recordInfo");
2526 my $recordIdentifier = $mods
2528 ->createElement("recordIdentifier");
2530 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2535 $recordIdentifier->appendTextNode(
2536 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
2539 $recordInfo->appendChild($recordIdentifier);
2540 $mods->appendChild($recordInfo);
2542 # Grab the title, author and ISBN for the master record and populate the metarecord
2543 my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
2546 $title->setNamespace( "http://www.loc.gov/mods/", "mods" );
2547 $title->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2548 $title = $mods->ownerDocument->importNode($title);
2549 $mods->appendChild($title);
2552 my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
2554 $author->setNamespace( "http://www.loc.gov/mods/", "mods" );
2555 $author->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2556 $author = $mods->ownerDocument->importNode($author);
2557 $mods->appendChild($author);
2560 my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
2562 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2563 $isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2564 $isbn = $mods->ownerDocument->importNode($isbn);
2565 $mods->appendChild($isbn);
2568 # ... and loop over the constituent records
2569 for my $map ( @$recs ) {
2573 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2574 ->run($map->source);
2576 my $part_mods = $_parser->parse_string($rec);
2577 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods" );
2578 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2579 ($part_mods) = $part_mods->findnodes('//mods:mods');
2581 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
2582 $node->setNamespace( "http://www.loc.gov/mods/", "mods" );
2583 $node->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2584 $node = $mods->ownerDocument->importNode($node);
2585 $mods->appendChild( $node );
2588 my $relatedItem = $mods
2590 ->createElement("relatedItem");
2592 $relatedItem->setAttribute( type => 'constituent' );
2594 my $identifier = $mods
2596 ->createElement("identifier");
2598 $identifier->setAttribute( type => 'uri' );
2600 my $subRecordInfo = $mods
2602 ->createElement("recordInfo");
2604 my $subRecordIdentifier = $mods
2606 ->createElement("recordIdentifier");
2608 my $subid = $map->source;
2609 $subRecordIdentifier->appendTextNode(
2610 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
2615 $subRecordInfo->appendChild($subRecordIdentifier);
2617 $relatedItem->appendChild( $subRecordInfo );
2619 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
2620 $tor->setNamespace( "http://www.loc.gov/mods/", "mods" );
2621 $tor->setNamespace( "http://www.loc.gov/mods/", undef, 1 ) if ($tor);
2622 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
2623 $relatedItem->appendChild($tor) if ($tor);
2625 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
2626 $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2627 $part_isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2628 $part_isbn = $mods->ownerDocument->importNode($part_isbn);
2629 $relatedItem->appendChild( $part_isbn );
2632 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
2636 $mods->appendChild( $relatedItem );
2640 $_storage->disconnect;
2642 return $U->entityize($mods->toString);
2645 __PACKAGE__->register_method(
2646 method => 'retrieve_metarecord_mods',
2647 api_name => 'open-ils.supercat.metarecord.mods.retrieve',
2651 { desc => <<" DESC",
2652 Returns the MODS representation of the requested metarecord
2656 { name => 'metarecordId',
2657 desc => 'An OpenILS metabib::metarecord id',
2661 { desc => 'The metarecord in MODS',
2666 sub list_metarecord_formats {
2669 { namespace_uri => 'http://www.loc.gov/mods/',
2670 docs => 'http://www.loc.gov/mods/',
2671 schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
2676 for my $type ( keys %metarecord_xslt ) {
2679 { namespace_uri => $metarecord_xslt{$type}{namespace_uri},
2680 docs => $metarecord_xslt{$type}{docs},
2681 schema_location => $metarecord_xslt{$type}{schema_location},
2688 __PACKAGE__->register_method(
2689 method => 'list_metarecord_formats',
2690 api_name => 'open-ils.supercat.metarecord.formats',
2694 { desc => <<" DESC",
2695 Returns the list of valid metarecord formats that supercat understands.
2698 { desc => 'The format list',
2704 sub list_authority_formats {
2707 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
2708 docs => 'http://www.loc.gov/marcxml/',
2709 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
2714 # for my $type ( keys %record_xslt ) {
2717 # { namespace_uri => $record_xslt{$type}{namespace_uri},
2718 # docs => $record_xslt{$type}{docs},
2719 # schema_location => $record_xslt{$type}{schema_location},
2726 __PACKAGE__->register_method(
2727 method => 'list_authority_formats',
2728 api_name => 'open-ils.supercat.authority.formats',
2732 { desc => <<" DESC",
2733 Returns the list of valid authority formats that supercat understands.
2736 { desc => 'The format list',
2741 sub list_record_formats {
2744 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
2745 docs => 'http://www.loc.gov/marcxml/',
2746 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
2751 for my $type ( keys %record_xslt ) {
2754 { namespace_uri => $record_xslt{$type}{namespace_uri},
2755 docs => $record_xslt{$type}{docs},
2756 schema_location => $record_xslt{$type}{schema_location},
2763 __PACKAGE__->register_method(
2764 method => 'list_record_formats',
2765 api_name => 'open-ils.supercat.record.formats',
2769 { desc => <<" DESC",
2770 Returns the list of valid record formats that supercat understands.
2773 { desc => 'The format list',
2777 __PACKAGE__->register_method(
2778 method => 'list_record_formats',
2779 api_name => 'open-ils.supercat.isbn.formats',
2783 { desc => <<" DESC",
2784 Returns the list of valid record formats that supercat understands.
2787 { desc => 'The format list',
2800 throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
2801 unless (length($isbn) >= 10);
2803 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2805 # Create a storage session, since we'll be making muliple requests.
2808 # Find the record that has that ISBN.
2809 my $bibrec = $_storage->request(
2810 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2811 { tag => '020', subfield => 'a', value => { like => lc($isbn).'%'} }
2814 # Go away if we don't have one.
2815 return {} unless (@$bibrec);
2817 # Find the metarecord for that bib record.
2818 my $mr = $_storage->request(
2819 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2820 {source => $bibrec->[0]->record}
2823 # Find the other records for that metarecord.
2824 my $records = $_storage->request(
2825 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2826 {metarecord => $mr->[0]->metarecord}
2829 # Just to be safe. There's currently no unique constraint on sources...
2830 my %unique_recs = map { ($_->source, 1) } @$records;
2831 my @rec_list = sort keys %unique_recs;
2833 # And now fetch the ISBNs for thos records.
2837 'open-ils.cstore.direct.metabib.full_rec.search',
2838 { tag => '020', subfield => 'a', record => $_ }
2839 )->gather(1) for (@rec_list);
2841 # We're done with the storage server session.
2842 $_storage->disconnect;
2844 # Return the oISBN data structure. This will be XMLized at a higher layer.
2846 { metarecord => $mr->[0]->metarecord,
2847 record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
2850 __PACKAGE__->register_method(
2852 api_name => 'open-ils.supercat.oisbn',
2856 { desc => <<" DESC",
2857 Returns the ISBN list for the metarecord of the requested isbn
2862 desc => 'An ISBN. Duh.',
2866 { desc => 'record to isbn map',
2871 sub return_bib_search_aliases {
2874 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2876 my $cmsa = $_storage->request(
2877 'open-ils.cstore.direct.config.metabib_search_alias.search.atomic',
2878 { alias => { '!=' => undef } }
2882 if ($_->alias =~ /\./) {
2883 my ($qualifier, $name) = $_->alias =~ m/^(.+?)\.(.+)$/;
2884 $aliases{$qualifier}{$name}{'index'} = $_->alias;
2885 # We will add a 'title' property in a subsequent schema
2886 $aliases{$qualifier}{$name}{'title'} = $name;
2888 # au/kw/se/su/ti go into the default 'eg' qualifier
2889 $aliases{'eg'}{$_->alias}{'index'} = $_->alias;
2890 $aliases{'eg'}{$_->alias}{'title'} = $_->alias;
2897 __PACKAGE__->register_method(
2898 method => 'return_bib_search_aliases',
2899 api_name => 'open-ils.supercat.biblio.search_aliases',
2903 { desc => <<" DESC",
2904 Returns the set of qualified search aliases in the system
2908 { desc => 'Hash of qualified search aliases',
2914 package OpenILS::Application::SuperCat::unAPI;
2915 use base qw/OpenILS::Application::SuperCat/;
2918 die "dummy superclass, use a real class";
2924 return unless ($obj);
2926 $class = ref($class) || $class;
2928 if ($class eq __PACKAGE__) {
2929 return unless (ref($obj));
2930 $class .= '::' . $obj->json_hint;
2933 return bless { obj => $obj } => $class;
2938 return $self->{obj};
2941 package OpenILS::Application::SuperCat::unAPI::auri;
2942 use base qw/OpenILS::Application::SuperCat::unAPI/;
2948 my $xml = ' <uri xmlns="http://open-ils.org/spec/holdings/v1" ';
2949 $xml .= 'id="tag:open-ils.org:asset-uri/' . $self->obj->id . '" ';
2950 $xml .= 'use_restriction="' . $self->escape( $self->obj->use_restriction ) . '" ';
2951 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
2952 $xml .= 'href="' . $self->escape( $self->obj->href ) . '">';
2954 if (!$args->{no_volumes}) {
2955 if (ref($self->obj->call_number_maps) && @{ $self->obj->call_number_maps }) {
2956 $xml .= " <volumes>\n" . join(
2959 OpenILS::Application::SuperCat::unAPI
2960 ->new( $_->call_number )
2961 ->as_xml({ %$args, no_uris=>1, no_copies=>1 })
2962 } @{ $self->obj->call_number_maps }
2963 ) . " </volumes>\n";
2966 $xml .= " <volumes/>\n";
2970 $xml .= " </uri>\n";
2975 package OpenILS::Application::SuperCat::unAPI::acn;
2976 use base qw/OpenILS::Application::SuperCat::unAPI/;
2982 my $xml = ' <volume xmlns="http://open-ils.org/spec/holdings/v1" ';
2984 $xml .= 'id="tag:open-ils.org:asset-call_number/' . $self->obj->id . '" ';
2985 $xml .= 'lib="' . $self->escape( $self->obj->owning_lib->shortname ) . '" ';
2986 $xml .= 'opac_visible="' . $self->obj->owning_lib->opac_visible . '" ';
2987 $xml .= 'deleted="' . $self->obj->deleted . '" ';
2988 $xml .= 'label="' . $self->escape( $self->obj->label ) . '">';
2991 if (!$args->{no_copies}) {
2992 if (ref($self->obj->copies) && @{ $self->obj->copies }) {
2993 $xml .= " <copies>\n" . join(
2996 OpenILS::Application::SuperCat::unAPI
2998 ->as_xml({ %$args, no_volume=>1 })
2999 } @{ $self->obj->copies }
3003 $xml .= " <copies/>\n";
3007 if (!$args->{no_uris}) {
3008 if (ref($self->obj->uri_maps) && @{ $self->obj->uri_maps }) {
3009 $xml .= " <uris>\n" . join(
3012 OpenILS::Application::SuperCat::unAPI
3014 ->as_xml({ %$args, no_volumes=>1 })
3015 } @{ $self->obj->uri_maps }
3019 $xml .= " <uris/>\n";
3024 $xml .= ' <prefix ';
3025 $xml .= 'ident="' . $self->obj->prefix->id . '" ';
3026 $xml .= 'id="tag:open-ils.org:asset-call_number_prefix/' . $self->obj->prefix->id . '" ';
3027 $xml .= 'label_sortkey="'.$self->escape( $self->obj->prefix->label_sortkey ) .'">';
3028 $xml .= $self->escape( $self->obj->prefix->label ) .'</prefix>';
3031 $xml .= ' <suffix ';
3032 $xml .= 'ident="' . $self->obj->suffix->id . '" ';
3033 $xml .= 'id="tag:open-ils.org:asset-call_number_suffix/' . $self->obj->suffix->id . '" ';
3034 $xml .= 'label_sortkey="'.$self->escape( $self->obj->suffix->label_sortkey ) .'">';
3035 $xml .= $self->escape( $self->obj->suffix->label ) .'</suffix>';
3038 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3039 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3040 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3041 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3044 unless ($args->{no_record}) {
3045 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3047 my $r_doc = $parser->parse_string($self->obj->record->marc);
3048 $r_doc->documentElement->setAttribute( id => $rec_tag );
3049 $xml .= $U->entityize($r_doc->documentElement->toString);
3052 $xml .= " </volume>\n";
3057 package OpenILS::Application::SuperCat::unAPI::ssub;
3058 use base qw/OpenILS::Application::SuperCat::unAPI/;
3064 my $xml = ' <subscription xmlns="http://open-ils.org/spec/holdings/v1" ';
3066 $xml .= 'id="tag:open-ils.org:serial-subscription/' . $self->obj->id . '" ';
3067 $xml .= 'start="' . $self->escape( $self->obj->start_date ) . '" ';
3068 $xml .= 'end="' . $self->escape( $self->obj->end_date ) . '" ';
3069 $xml .= 'expected_date_offset="' . $self->escape( $self->obj->expected_date_offset ) . '">';
3072 if (!$args->{no_distributions}) {
3073 if (ref($self->obj->distributions) && @{ $self->obj->distributions }) {
3074 $xml .= " <distributions>\n" . join(
3077 OpenILS::Application::SuperCat::unAPI
3079 ->as_xml({ %$args, no_subscription=>1, no_issuance=>1 })
3080 } @{ $self->obj->distributions }
3081 ) . " </distributions>\n";
3084 $xml .= " <distributions/>\n";
3088 if (!$args->{no_captions_and_patterns}) {
3089 if (ref($self->obj->scaps) && @{ $self->obj->scaps }) {
3090 $xml .= " <captions_and_patterns>\n" . join(
3093 OpenILS::Application::SuperCat::unAPI
3095 ->as_xml({ %$args, no_subscription=>1 })
3096 } @{ $self->obj->scaps }
3097 ) . " </captions_and_patterns>\n";
3100 $xml .= " <captions_and_patterns/>\n";
3104 if (!$args->{no_issuances}) {
3105 if (ref($self->obj->issuances) && @{ $self->obj->issuances }) {
3106 $xml .= " <issuances>\n" . join(
3109 OpenILS::Application::SuperCat::unAPI
3111 ->as_xml({ %$args, no_subscription=>1, no_items=>1 })
3112 } @{ $self->obj->issuances }
3113 ) . " </issuances>\n";
3116 $xml .= " <issuances/>\n";
3121 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3122 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3123 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3124 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3127 unless ($args->{no_record}) {
3128 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3130 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3131 $r_doc->documentElement->setAttribute( id => $rec_tag );
3132 $xml .= $U->entityize($r_doc->documentElement->toString);
3135 $xml .= " </subscription>\n";
3140 package OpenILS::Application::SuperCat::unAPI::ssum_base;
3141 use base qw/OpenILS::Application::SuperCat::unAPI/;
3147 (my $type = ref($self)) =~ s/^.+([^:]+)$/$1/;
3149 my $xml = " <serial_summary xmlns=\"http://open-ils.org/spec/holdings/v1\" type=\"$type\" ";
3151 $xml .= "id=\"tag:open-ils.org:serial-summary-$type/" . $self->obj->id . '" ';
3152 $xml .= 'generated_coverage="' . $self->escape( $self->obj->generated_coverage ) . '" ';
3153 $xml .= 'show_generated="' . $self->escape( $self->obj->show_generated ) . '" ';
3154 $xml .= 'textual_holdings="' . $self->escape( $self->obj->textual_holdings ) . '">';
3157 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_summaries=>1 }) if (!$args->{no_distribution});
3159 $xml .= " </serial_summary>\n";
3165 package OpenILS::Application::SuperCat::unAPI::sssum;
3166 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3168 package OpenILS::Application::SuperCat::unAPI::sbsum;
3169 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3171 package OpenILS::Application::SuperCat::unAPI::sisum;
3172 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3174 package OpenILS::Application::SuperCat::unAPI::sdist;
3175 use base qw/OpenILS::Application::SuperCat::unAPI/;
3181 my $xml = ' <distribution xmlns="http://open-ils.org/spec/holdings/v1" ';
3183 $xml .= 'id="tag:open-ils.org:serial-distribution/' . $self->obj->id . '" ';
3184 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3185 $xml .= 'unit_label_prefix="' . $self->escape( $self->obj->unit_label_prefix ) . '" ';
3186 $xml .= 'unit_label_suffix="' . $self->escape( $self->obj->unit_label_suffix ) . '">';
3189 if (!$args->{no_distributions}) {
3190 if (ref($self->obj->streams) && @{ $self->obj->streams }) {
3191 $xml .= " <streams>\n" . join(
3194 OpenILS::Application::SuperCat::unAPI
3196 ->as_xml({ %$args, no_distribution=>1 })
3197 } @{ $self->obj->streams }
3198 ) . " </streams>\n";
3201 $xml .= " <streams/>\n";
3205 if (!$args->{no_summaries}) {
3206 $xml .= " <summaries>\n";
3210 OpenILS::Application::SuperCat::unAPI
3212 ->as_xml({ %$args, no_distribution=>1 }) : ""
3213 } ($self->obj->basic_summary, $self->obj->supplement_summary, $self->obj->index_summary)
3216 $xml .= " </summaries>\n";
3220 $xml .= ' <holding_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3221 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->holding_lib->id . '" ';
3222 $xml .= 'shortname="'.$self->escape( $self->obj->holding_lib->shortname ) .'" ';
3223 $xml .= 'name="'.$self->escape( $self->obj->holding_lib->name ) .'"/>';
3226 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_distributions=>1 }) if (!$args->{no_subscription});
3228 if (!$args->{no_record} && $self->obj->record_entry) {
3229 my $rec_tag = "tag:open-ils.org:serial-record_entry/".$self->obj->record_entry->id ;
3231 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3232 $r_doc->documentElement->setAttribute( id => $rec_tag );
3233 $xml .= $U->entityize($r_doc->documentElement->toString);
3236 $xml .= " </distribution>\n";
3241 package OpenILS::Application::SuperCat::unAPI::sstr;
3242 use base qw/OpenILS::Application::SuperCat::unAPI/;
3248 my $xml = ' <stream xmlns="http://open-ils.org/spec/holdings/v1" ';
3250 $xml .= 'id="tag:open-ils.org:serial-stream/' . $self->obj->id . '" ';
3251 $xml .= 'routing_label="' . $self->escape( $self->obj->routing_label ) . '">';
3254 if (!$args->{no_items}) {
3255 if (ref($self->obj->items) && @{ $self->obj->items }) {
3256 $xml .= " <items>\n" . join(
3259 OpenILS::Application::SuperCat::unAPI
3261 ->as_xml({ %$args, no_stream=>1 })
3262 } @{ $self->obj->items }
3266 $xml .= " <items/>\n";
3270 #XXX routing_list_user's?
3272 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_streams=>1 }) if (!$args->{no_distribution});
3274 $xml .= " </stream>\n";
3279 package OpenILS::Application::SuperCat::unAPI::sitem;
3280 use base qw/OpenILS::Application::SuperCat::unAPI/;
3286 my $xml = ' <serial_item xmlns="http://open-ils.org/spec/holdings/v1" ';
3288 $xml .= 'id="tag:open-ils.org:serial-item/' . $self->obj->id . '" ';
3289 $xml .= 'date_expected="' . $self->escape( $self->obj->date_expected ) . '"';
3290 $xml .= ' date_received="' . $self->escape( $self->obj->date_received ) .'"'if ($self->obj->date_received);
3292 if ($args->{no_issuance}) {
3293 my $siss = ref($self->obj->issuance) ? $self->obj->issuance->id : $self->obj->issuance;
3294 $xml .= ' issuance="tag:open-ils.org:serial-issuance/' . $siss . '"';
3299 if (ref($self->obj->notes) && $self->obj->notes) {
3300 $xml .= " <notes>\n";
3301 for my $note ( @{$self->obj->notes} ) {
3302 next unless ( $note->pub eq 't' );
3303 $xml .= sprintf(' <note date="%s" title="%s">%s</note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3306 $xml .= " </notes>\n";
3308 $xml .= " <notes/>\n";
3311 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->issuance )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_issuance});
3312 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->stream )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_stream});
3313 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->unit )->as_xml({ %$args, no_items=>1, no_volumes=>1 }) if ($self->obj->unit && !$args->{no_unit});
3314 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->uri )->as_xml({ %$args, no_items=>1, no_volumes=>1 }) if ($self->obj->uri && !$args->{no_uri});
3316 $xml .= " </serial_item>\n";
3321 package OpenILS::Application::SuperCat::unAPI::sunit;
3322 use base qw/OpenILS::Application::SuperCat::unAPI/;
3328 my $xml = ' <serial_unit xmlns="http://open-ils.org/spec/holdings/v1" '.
3329 'id="tag:open-ils.org:serial-unit/' . $self->obj->id . '" ';
3331 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3332 create_date edit_date copy_number circulate deposit ref holdable deleted
3333 deposit_amount price barcode circ_modifier circ_as_type opac_visible cost
3334 status_changed_time floating mint_condition detailed_contents sort_key summary_contents
3339 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3340 $xml .= ' <location ident="' . $self->obj->location->id . '">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3341 $xml .= ' <circlib ident="' . $self->obj->circ_lib->id . '">' . $self->escape( $self->obj->circ_lib->name ) . "</circlib>\n";
3343 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3344 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3345 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3346 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'"/>';
3349 $xml .= " <copy_notes>\n";
3350 if (ref($self->obj->notes) && $self->obj->notes) {
3351 for my $note ( @{$self->obj->notes} ) {
3352 next unless ( $note->pub eq 't' );
3353 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3358 $xml .= " </copy_notes>\n";
3359 $xml .= " <statcats>\n";
3361 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3362 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3363 next unless ( $sce->stat_cat->opac_visible eq 't' );
3364 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3368 $xml .= " </statcats>\n";
3370 unless ($args->{no_volume}) {
3371 if (ref($self->obj->call_number)) {
3372 $xml .= OpenILS::Application::SuperCat::unAPI
3373 ->new( $self->obj->call_number )
3374 ->as_xml({ %$args, no_copies=>1 });
3376 $xml .= " <volume/>\n";
3380 $xml .= " </serial_unit>\n";
3385 package OpenILS::Application::SuperCat::unAPI::scap;
3386 use base qw/OpenILS::Application::SuperCat::unAPI/;
3392 my $xml = ' <caption_and_pattern xmlns="http://open-ils.org/spec/holdings/v1" '.
3393 'id="tag:open-ils.org:serial-caption_and_pattern/' . $self->obj->id . '" ';
3395 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3396 create_date type active pattern_code enum_1 enum_2 enum_3 enum_4
3397 enum_5 enum_6 chron_1 chron_2 chron_3 chron_4 chron_5 start_date end_date
3400 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_captions_and_patterns=>1 }) if (!$args->{no_subscription});
3401 $xml .= " </caption_and_pattern>\n";
3406 package OpenILS::Application::SuperCat::unAPI::siss;
3407 use base qw/OpenILS::Application::SuperCat::unAPI/;
3413 my $xml = ' <issuance xmlns="http://open-ils.org/spec/holdings/v1" '.
3414 'id="tag:open-ils.org:serial-issuance/' . $self->obj->id . '" ';
3416 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" '
3417 for (qw/create_date edit_date label date_published holding_code holding_type holding_link_id/);
3421 if (!$args->{no_items}) {
3422 if (ref($self->obj->items) && @{ $self->obj->items }) {
3423 $xml .= " <items>\n" . join(
3426 OpenILS::Application::SuperCat::unAPI
3428 ->as_xml({ %$args, no_stream=>1 })
3429 } @{ $self->obj->items }
3433 $xml .= " <items/>\n";
3437 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_issuances=>1 }) if (!$args->{no_subscription});
3438 $xml .= " </issuance>\n";
3443 package OpenILS::Application::SuperCat::unAPI::acp;
3444 use base qw/OpenILS::Application::SuperCat::unAPI/;
3450 my $xml = ' <copy xmlns="http://open-ils.org/spec/holdings/v1" '.
3451 'id="tag:open-ils.org:asset-copy/' . $self->obj->id . '" ';
3453 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3454 create_date edit_date copy_number circulate deposit ref holdable deleted
3455 deposit_amount price barcode circ_modifier circ_as_type opac_visible
3460 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3461 $xml .= ' <location ident="' . $self->obj->location->id . '" opac_visible="'.$self->obj->location->opac_visible.'">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3462 $xml .= ' <circlib ident="' . $self->obj->circ_lib->id . '" opac_visible="'.$self->obj->circ_lib->opac_visible.'">' . $self->escape( $self->obj->circ_lib->name ) . "</circlib>\n";
3464 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3465 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3466 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3467 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'" opac_visible="'.$self->obj->circ_lib->opac_visible.'"/>';
3470 $xml .= " <monograph_parts>\n";
3471 if (ref($self->obj->parts) && $self->obj->parts) {
3472 for my $part ( @{$self->obj->parts} ) {
3473 $xml .= sprintf(' <monograph_part record="%s" sortkey="%s">%s</monograph_part>',$part->record, $self->escape($part->label_sortkey), $self->escape($part->label));
3478 $xml .= " </monograph_parts>\n";
3479 $xml .= " <copy_notes>\n";
3480 if (ref($self->obj->notes) && $self->obj->notes) {
3481 for my $note ( @{$self->obj->notes} ) {
3482 next unless ( $note->pub eq 't' );
3483 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3488 $xml .= " </copy_notes>\n";
3489 $xml .= " <statcats>\n";
3491 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3492 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3493 next unless ( $sce->stat_cat->opac_visible eq 't' );
3494 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3498 $xml .= " </statcats>\n";
3500 unless ($args->{no_volume}) {
3501 if (ref($self->obj->call_number)) {
3502 $xml .= OpenILS::Application::SuperCat::unAPI
3503 ->new( $self->obj->call_number )
3504 ->as_xml({ %$args, no_copies=>1 });
3506 $xml .= " <volume/>\n";
3510 $xml .= " </copy>\n";