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;
39 use OpenILS::Utils::CStoreEditor q/:funcs/;
48 %authority_browse_axis_cache,
52 # we need an XML parser
53 $_parser = new XML::LibXML;
56 $_xslt = new XML::LibXSLT;
58 # parse the MODS xslt ...
59 my $mods33_xslt = $_parser->parse_file(
60 OpenSRF::Utils::SettingsClient
62 ->config_value( dirs => 'xsl' ).
63 "/MARC21slim2MODS33.xsl"
65 # and stash a transformer
66 $record_xslt{mods33}{xslt} = $_xslt->parse_stylesheet( $mods33_xslt );
67 $record_xslt{mods33}{namespace_uri} = 'http://www.loc.gov/mods/v3';
68 $record_xslt{mods33}{docs} = 'http://www.loc.gov/mods/';
69 $record_xslt{mods33}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-3.xsd';
71 # parse the MODS xslt ...
72 my $mods32_xslt = $_parser->parse_file(
73 OpenSRF::Utils::SettingsClient
75 ->config_value( dirs => 'xsl' ).
76 "/MARC21slim2MODS32.xsl"
78 # and stash a transformer
79 $record_xslt{mods32}{xslt} = $_xslt->parse_stylesheet( $mods32_xslt );
80 $record_xslt{mods32}{namespace_uri} = 'http://www.loc.gov/mods/v3';
81 $record_xslt{mods32}{docs} = 'http://www.loc.gov/mods/';
82 $record_xslt{mods32}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-2.xsd';
84 # parse the MODS xslt ...
85 my $mods3_xslt = $_parser->parse_file(
86 OpenSRF::Utils::SettingsClient
88 ->config_value( dirs => 'xsl' ).
89 "/MARC21slim2MODS3.xsl"
91 # and stash a transformer
92 $record_xslt{mods3}{xslt} = $_xslt->parse_stylesheet( $mods3_xslt );
93 $record_xslt{mods3}{namespace_uri} = 'http://www.loc.gov/mods/v3';
94 $record_xslt{mods3}{docs} = 'http://www.loc.gov/mods/';
95 $record_xslt{mods3}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-1.xsd';
97 # parse the MODS xslt ...
98 my $mods_xslt = $_parser->parse_file(
99 OpenSRF::Utils::SettingsClient
101 ->config_value( dirs => 'xsl' ).
102 "/MARC21slim2MODS.xsl"
104 # and stash a transformer
105 $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
106 $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
107 $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
108 $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
110 # parse the ATOM entry xslt ...
111 my $atom_xslt = $_parser->parse_file(
112 OpenSRF::Utils::SettingsClient
114 ->config_value( dirs => 'xsl' ).
115 "/MARC21slim2ATOM.xsl"
117 # and stash a transformer
118 $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
119 $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
120 $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
122 # parse the RDFDC xslt ...
123 my $rdf_dc_xslt = $_parser->parse_file(
124 OpenSRF::Utils::SettingsClient
126 ->config_value( dirs => 'xsl' ).
127 "/MARC21slim2RDFDC.xsl"
129 # and stash a transformer
130 $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
131 $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
132 $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
134 # parse the SRWDC xslt ...
135 my $srw_dc_xslt = $_parser->parse_file(
136 OpenSRF::Utils::SettingsClient
138 ->config_value( dirs => 'xsl' ).
139 "/MARC21slim2SRWDC.xsl"
141 # and stash a transformer
142 $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
143 $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
144 $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
146 # parse the OAIDC xslt ...
147 my $oai_dc_xslt = $_parser->parse_file(
148 OpenSRF::Utils::SettingsClient
150 ->config_value( dirs => 'xsl' ).
151 "/MARC21slim2OAIDC.xsl"
153 # and stash a transformer
154 $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
155 $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
156 $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
158 # parse the RSS xslt ...
159 my $rss_xslt = $_parser->parse_file(
160 OpenSRF::Utils::SettingsClient
162 ->config_value( dirs => 'xsl' ).
163 "/MARC21slim2RSS2.xsl"
165 # and stash a transformer
166 $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
168 # parse the FGDC xslt ...
169 my $fgdc_xslt = $_parser->parse_file(
170 OpenSRF::Utils::SettingsClient
172 ->config_value( dirs => 'xsl' ).
173 "/MARC21slim2FGDC.xsl"
175 # and stash a transformer
176 $record_xslt{fgdc}{xslt} = $_xslt->parse_stylesheet( $fgdc_xslt );
177 $record_xslt{fgdc}{docs} = 'http://www.fgdc.gov/metadata/csdgm/index_html';
178 $record_xslt{fgdc}{schema_location} = 'http://www.fgdc.gov/metadata/fgdc-std-001-1998.xsd';
180 register_record_transforms();
182 register_new_authorities_methods();
187 sub register_record_transforms {
188 for my $type ( keys %record_xslt ) {
189 __PACKAGE__->register_method(
190 method => 'retrieve_record_transform',
191 api_name => "open-ils.supercat.record.$type.retrieve",
195 { desc => "Returns the \U$type\E representation ".
196 "of the requested bibliographic record",
200 desc => 'An OpenILS biblio::record_entry id',
204 { desc => "The bib record in \U$type\E",
209 __PACKAGE__->register_method(
210 method => 'retrieve_isbn_transform',
211 api_name => "open-ils.supercat.isbn.$type.retrieve",
215 { desc => "Returns the \U$type\E representation ".
216 "of the requested bibliographic record",
224 { desc => "The bib record in \U$type\E",
231 sub register_new_authorities_methods {
232 my %register_args = (
233 method => "generic_new_authorities_method",
237 desc => q/Generated method/,
240 desc => "An axis, an authority tag number, or a bibliographic tag number, depending on invocation",
243 desc => "A search term",
246 desc => "zero-based page number of results",
248 {name => "page size",
249 desc => "number of results per page",
253 desc => "A list of authority record IDs", type => "array"
258 foreach my $how (qw/axis atag btag/) {
259 foreach my $action (qw/browse_center browse_top
260 search_rank search_heading/) {
262 $register_args{api_name} =
263 "open-ils.supercat.authority.$action.by_$how";
264 __PACKAGE__->register_method(%register_args);
266 $register_args{api_name} =
267 "open-ils.supercat.authority.$action.by_$how.refs";
268 __PACKAGE__->register_method(%register_args);
274 sub generic_new_authorities_method {
278 # We want to be extra careful with these arguments, since the next
279 # thing we're doing with them is passing them to a DB procedure.
282 my $page = int(shift || 0);
283 my $page_size = shift;
285 # undef ok, but other non numbers not ok
286 $page_size = int($page_size) if defined $page_size;
288 # Figure out how we were called and what DB procedure we'll call in turn.
289 $self->api_name =~ /\.by_(\w+)($|\.)/;
293 $self->api_name =~ /authority\.(\w+)\./;
296 my $method = "${metaaxis}_$action";
297 $method .= "_refs" if $refs;
299 # Match authority.full_rec normalization
300 # XXX don't know whether we need second arg 'subfield'?
301 $term = naco_normalize($term);
303 my $storage = create OpenSRF::AppSession("open-ils.storage");
304 my $list = $storage->request(
305 "open-ils.storage.authority.in_db.browse_or_search",
306 $method, $what, $term, $page, $page_size
320 return unless ($tree && ref($tree->$field));
322 my @things = $filter->($tree);
323 for my $v ( @{$tree->$field} ){
324 push @things, $filter->($v);
325 push @things, tree_walker($v, $field, $filter);
330 # find a label_sortkey for a call number with a label which is equal
331 # (or close to) a given label value
332 sub _label_sortkey_from_label {
333 my ($label, $_storage, $ou_ids, $cp_filter) = @_;
335 my $closest_cn = $_storage->request(
336 "open-ils.cstore.direct.asset.call_number.search.atomic",
337 { label => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
338 (scalar(@$ou_ids) ? (owning_lib => $ou_ids) : ()),
343 order_by => { acn => "oils_text_as_bytea(label), id" }
347 return $closest_cn->[0]->label_sortkey;
349 return '~~~'; #fallback to high ascii value, we are at the end
359 my $page_size = shift || 9;
360 my $page = shift || 0;
361 my $statuses = shift || [];
362 my $copy_locations = shift || [];
364 my ($before_limit,$after_limit) = (0,0);
365 my ($before_offset,$after_offset) = (0,0);
368 $before_limit = $after_limit = int($page_size / 2);
369 $after_limit += 1 if ($page_size % 2);
371 $before_offset = $after_offset = int($page_size / 2);
372 $before_offset += 1 if ($page_size % 2);
373 $before_limit = $after_limit = $page_size;
376 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
379 # if OU is not specified, or if '-' is specified, do not enter the block
380 unless (!$ou || $ou eq '-') {
381 # check if the shortname of the top org_unit is specified
382 my $top_org = $_storage->request(
383 "open-ils.cstore.direct.actor.org_unit.search",
384 { parent_ou => undef }
387 if ($ou eq $top_org->shortname) {
388 $logger->debug("Searching for CNs at top org $ou");
390 my $o_search = { shortname => $ou };
392 my $orgs = $_storage->request(
393 "open-ils.cstore.direct.actor.org_unit.search",
396 flesh_fields => { aou => [qw/children/] }
400 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
402 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
409 if (@$statuses || @$copy_locations) {
415 call_number => { '=' => { '+acn' => 'id' } },
417 ((@$statuses) ? ( status => $statuses) : ()),
418 ((@$copy_locations) ? ( location => $copy_locations) : ())
424 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
427 my $before = $_storage->request(
428 "open-ils.cstore.direct.asset.call_number.search.atomic",
429 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
430 (scalar(@ou_ids) ? (owning_lib => \@ou_ids) : ()),
435 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
436 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
437 limit => $before_limit,
438 offset => abs($page) * $page_size - $before_offset,
441 push @list, reverse(@$before);
445 my $after = $_storage->request(
446 "open-ils.cstore.direct.asset.call_number.search.atomic",
447 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
448 (scalar(@ou_ids) ? (owning_lib => \@ou_ids) : ()),
453 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
454 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
455 limit => $after_limit,
456 offset => abs($page) * $page_size - $after_offset,
464 __PACKAGE__->register_method(
465 method => 'cn_browse',
466 api_name => 'open-ils.supercat.call_number.browse',
471 Returns the XML representation of the requested bibliographic record's holdings
476 desc => 'The target call number label',
478 { name => 'org_unit',
479 desc => 'The org unit shortname (or "-" or undef for global) to browse',
481 { name => 'page_size',
482 desc => 'Count of call numbers to retrieve, default is 9',
485 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
487 { name => 'statuses',
488 desc => 'Array of statuses to filter copies by, optional and can be undef.',
490 { name => 'locations',
491 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
495 { desc => 'Call numbers with owning_lib and record fleshed',
506 my $limit = shift || 10;
507 my $page = shift || 0;
508 my $statuses = shift || [];
509 my $copy_locations = shift || [];
512 my $offset = abs($page) * $limit;
513 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
515 my $o_search = { shortname => $ou };
516 if (!$ou || $ou eq '-') {
517 $o_search = { parent_ou => undef };
520 my $orgs = $_storage->request(
521 "open-ils.cstore.direct.actor.org_unit.search",
524 flesh_fields => { aou => [qw/children/] }
528 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
530 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
535 if (@$statuses || @$copy_locations) {
541 call_number => { '=' => { '+acn' => 'id' } },
543 ((@$statuses) ? ( status => $statuses) : ()),
544 ((@$copy_locations) ? ( location => $copy_locations) : ())
550 my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
553 my $before = $_storage->request(
554 "open-ils.cstore.direct.asset.call_number.search.atomic",
555 { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
556 owning_lib => \@ou_ids,
561 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
562 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
567 push @list, reverse(@$before);
571 my $after = $_storage->request(
572 "open-ils.cstore.direct.asset.call_number.search.atomic",
573 { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
574 owning_lib => \@ou_ids,
579 flesh_fields => { acn => [qw/record owning_lib prefix suffix/] },
580 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
590 __PACKAGE__->register_method(
591 method => 'cn_startwith',
592 api_name => 'open-ils.supercat.call_number.startwith',
597 Returns the XML representation of the requested bibliographic record's holdings
602 desc => 'The target call number label',
604 { name => 'org_unit',
605 desc => 'The org unit shortname (or "-" or undef for global) to browse',
607 { name => 'page_size',
608 desc => 'Count of call numbers to retrieve, default is 9',
611 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
613 { name => 'statuses',
614 desc => 'Array of statuses to filter copies by, optional and can be undef.',
616 { name => 'locations',
617 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
621 { desc => 'Call numbers with owning_lib and record fleshed',
627 sub new_books_by_item {
632 my $page_size = shift || 10;
633 my $page = shift || 1;
634 my $statuses = shift || [];
635 my $copy_locations = shift || [];
637 my $offset = $page_size * ($page - 1);
639 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
642 if ($ou && $ou ne '-') {
643 my $orgs = $_storage->request(
644 "open-ils.cstore.direct.actor.org_unit.search",
645 { shortname => $ou },
647 flesh_fields => { aou => [qw/children/] }
650 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
653 $logger->debug("Searching for records with new copies at orgs [".join(',',@ou_ids)."], based on $ou");
654 my $cns = $_storage->request(
655 "open-ils.cstore.json_query.atomic",
656 { select => { acn => ['record'],
657 acp => [{ aggregate => 1 => transform => max => column => create_date => alias => 'create_date'}]
659 from => { 'acn' => { 'acp' => { field => call_number => fkey => 'id' } } },
663 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
664 ((@$statuses) ? ( status => $statuses) : ()),
665 ((@$copy_locations) ? ( location => $copy_locations) : ())
667 '+acn' => { record => { '>' => 0 } },
669 order_by => { acp => { create_date => { transform => 'max', direction => 'desc' } } },
675 return [ map { $_->{record} } @$cns ];
677 __PACKAGE__->register_method(
678 method => 'new_books_by_item',
679 api_name => 'open-ils.supercat.new_book_list',
684 Returns the XML representation of the requested bibliographic record's holdings
688 { name => 'org_unit',
689 desc => 'The org unit shortname (or "-" or undef for global) to list',
691 { name => 'page_size',
692 desc => 'Count of records to retrieve, default is 10',
695 desc => 'The page of records to retrieve, calculated based on page_size. Starts at 1.',
697 { name => 'statuses',
698 desc => 'Array of statuses to filter copies by, optional and can be undef.',
700 { name => 'locations',
701 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
705 { desc => 'Record IDs',
714 return tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
716 __PACKAGE__->register_method(
717 method => 'general_browse',
718 api_name => 'open-ils.supercat.title.browse',
719 tag => 'tnf', subfield => 'a',
723 { desc => "Returns a list of the requested org-scoped record IDs held",
725 [ { name => 'value', desc => 'The target title', type => 'string' },
726 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
727 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
728 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
729 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
730 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
731 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
734 __PACKAGE__->register_method(
735 method => 'general_browse',
736 api_name => 'open-ils.supercat.author.browse',
737 tag => [qw/100 110 111/], subfield => 'a',
741 { desc => "Returns a list of the requested org-scoped record IDs held",
743 [ { name => 'value', desc => 'The target author', type => 'string' },
744 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
745 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
746 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
747 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
748 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
749 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
752 __PACKAGE__->register_method(
753 method => 'general_browse',
754 api_name => 'open-ils.supercat.subject.browse',
755 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
759 { desc => "Returns a list of the requested org-scoped record IDs held",
761 [ { name => 'value', desc => 'The target subject', type => 'string' },
762 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
763 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
764 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
765 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
766 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
767 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
770 __PACKAGE__->register_method(
771 method => 'general_browse',
772 api_name => 'open-ils.supercat.topic.browse',
773 tag => [qw/650 690/], subfield => 'a',
777 { desc => "Returns a list of the requested org-scoped record IDs held",
779 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
780 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
781 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
782 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
783 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
784 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
785 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
788 __PACKAGE__->register_method(
789 method => 'general_browse',
790 api_name => 'open-ils.supercat.series.browse',
791 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
795 { desc => "Returns a list of the requested org-scoped record IDs held",
797 [ { name => 'value', desc => 'The target series', type => 'string' },
798 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
799 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
800 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
801 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
802 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
803 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
813 my $subfield = shift;
816 my $page_size = shift || 9;
817 my $page = shift || 0;
818 my $statuses = shift || [];
819 my $copy_locations = shift || [];
821 my ($before_limit,$after_limit) = (0,0);
822 my ($before_offset,$after_offset) = (0,0);
825 $before_limit = $after_limit = int($page_size / 2);
826 $after_limit += 1 if ($page_size % 2);
828 $before_offset = $after_offset = int($page_size / 2);
829 $before_offset += 1 if ($page_size % 2);
830 $before_limit = $after_limit = $page_size;
833 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
836 if ($ou && $ou ne '-') {
837 my $orgs = $_storage->request(
838 "open-ils.cstore.direct.actor.org_unit.search",
839 { shortname => $ou },
841 flesh_fields => { aou => [qw/children/] }
844 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
847 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
852 my $before = $_storage->request(
853 "open-ils.cstore.json_query.atomic",
854 { select => { mfr => [qw/record value/] },
859 subfield => $subfield,
860 value => { '<' => lc($value) }
864 { select=> { acp => [ 'id' ] },
865 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
867 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
870 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
871 ((@$statuses) ? ( status => $statuses) : ()),
872 ((@$copy_locations) ? ( location => $copy_locations) : ())
879 { select=> { auri => [ 'id' ] },
880 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
882 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
883 '+auri' => { active => 't' }
890 order_by => { mfr => { value => 'desc' } },
891 limit => $before_limit,
892 offset => abs($page) * $page_size - $before_offset,
895 push @list, map { $_->{record} } reverse(@$before);
899 my $after = $_storage->request(
900 "open-ils.cstore.json_query.atomic",
901 { select => { mfr => [qw/record value/] },
906 subfield => $subfield,
907 value => { '>=' => lc($value) }
911 { select=> { acp => [ 'id' ] },
912 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
914 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
917 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
918 ((@$statuses) ? ( status => $statuses) : ()),
919 ((@$copy_locations) ? ( location => $copy_locations) : ())
926 { select=> { auri => [ 'id' ] },
927 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
929 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
930 '+auri' => { active => 't' }
937 order_by => { mfr => { value => 'asc' } },
938 limit => $after_limit,
939 offset => abs($page) * $page_size - $after_offset,
942 push @list, map { $_->{record} } @$after;
947 __PACKAGE__->register_method(
948 method => 'tag_sf_browse',
949 api_name => 'open-ils.supercat.tag.browse',
954 Returns a list of the requested org-scoped record IDs held
959 desc => 'The target MARC tag',
961 { name => 'subfield',
962 desc => 'The target MARC subfield',
965 desc => 'The target string',
967 { name => 'org_unit',
968 desc => 'The org unit shortname (or "-" or undef for global) to browse',
970 { name => 'page_size',
971 desc => 'Count of call numbers to retrieve, default is 9',
974 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
976 { name => 'statuses',
977 desc => 'Array of statuses to filter copies by, optional and can be undef.',
979 { name => 'locations',
980 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
984 { desc => 'Record IDs that have copies at the relevant org units',
989 sub grab_authority_browse_axes {
990 my ($self, $client, $full) = @_;
992 unless(scalar(keys(%authority_browse_axis_cache))) {
993 my $axes = new_editor->search_authority_browse_axis([
994 { code => { '<>' => undef } },
995 { flesh => 2, flesh_fields => { aba => ['fields'], acsaf => ['bib_fields','sub_entries'] } }
997 $authority_browse_axis_cache{$_->code} = $_ for (@$axes);
1002 map { $authority_browse_axis_cache{$_} } sort keys %authority_browse_axis_cache
1005 return [keys %authority_browse_axis_cache];
1008 __PACKAGE__->register_method(
1009 method => 'grab_authority_browse_axes',
1010 api_name => 'open-ils.supercat.authority.browse_axis_list',
1014 { desc => "Returns a list of valid authority browse/startswith axes",
1016 { name => 'full', desc => 'Optional. If true, return array containing the full object for each axis, sorted by code. Otherwise just return an array of the codes.', type => 'number' }
1018 'return' => { desc => 'Axis codes or whole axes, see "full" param', type => 'array' }
1022 sub axis_authority_browse {
1027 $axis =~ s/^authority\.//;
1028 $axis =~ s/(\.refs)$//;
1031 return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
1034 for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
1035 push @tags, $f->tag;
1037 push @tags, $_->tag for @{$f->sub_entries};
1041 return authority_tag_sf_browse($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
1043 __PACKAGE__->register_method(
1044 method => 'axis_authority_browse',
1045 api_name => 'open-ils.supercat.authority.browse.by_axis',
1049 { desc => "Returns a list of the requested authority record IDs held",
1051 [ { name => 'axis', desc => 'The target axis', type => 'string' },
1052 { name => 'value', desc => 'The target value', type => 'string' },
1053 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1054 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1055 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1061 sub general_authority_browse {
1064 return authority_tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
1066 __PACKAGE__->register_method(
1067 method => 'general_authority_browse',
1068 api_name => 'open-ils.supercat.authority.title.browse',
1069 tag => ['130'], subfield => 'a',
1073 { desc => "Returns a list of the requested authority record IDs held",
1075 [ { name => 'value', desc => 'The target title', type => 'string' },
1076 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1077 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1078 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1081 __PACKAGE__->register_method(
1082 method => 'general_authority_browse',
1083 api_name => 'open-ils.supercat.authority.author.browse',
1084 tag => [qw/100 110 111/], subfield => 'a',
1088 { desc => "Returns a list of the requested authority record IDs held",
1090 [ { name => 'value', desc => 'The target author', type => 'string' },
1091 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1092 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1093 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1096 __PACKAGE__->register_method(
1097 method => 'general_authority_browse',
1098 api_name => 'open-ils.supercat.authority.subject.browse',
1099 tag => [qw/148 150 151 155/], subfield => 'a',
1103 { desc => "Returns a list of the requested authority record IDs held",
1105 [ { name => 'value', desc => 'The target subject', type => 'string' },
1106 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1107 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1108 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1111 __PACKAGE__->register_method(
1112 method => 'general_authority_browse',
1113 api_name => 'open-ils.supercat.authority.topic.browse',
1114 tag => ['150'], subfield => 'a',
1118 { desc => "Returns a list of the requested authority record IDs held",
1120 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1121 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1122 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1123 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1126 __PACKAGE__->register_method(
1127 method => 'general_authority_browse',
1128 api_name => 'open-ils.supercat.authority.title.refs.browse',
1129 tag => ['130'], subfield => 'a',
1133 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1135 [ { name => 'value', desc => 'The target title', type => 'string' },
1136 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1137 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1138 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1141 __PACKAGE__->register_method(
1142 method => 'general_authority_browse',
1143 api_name => 'open-ils.supercat.authority.author.refs.browse',
1144 tag => [qw/100 110 111/], subfield => 'a',
1148 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1150 [ { name => 'value', desc => 'The target author', type => 'string' },
1151 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1152 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1153 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1156 __PACKAGE__->register_method(
1157 method => 'general_authority_browse',
1158 api_name => 'open-ils.supercat.authority.subject.refs.browse',
1159 tag => [qw/148 150 151 155/], subfield => 'a',
1163 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1165 [ { name => 'value', desc => 'The target subject', type => 'string' },
1166 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1167 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1168 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1171 __PACKAGE__->register_method(
1172 method => 'general_authority_browse',
1173 api_name => 'open-ils.supercat.authority.topic.refs.browse',
1174 tag => ['150'], subfield => 'a',
1178 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1180 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1181 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1182 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1183 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1189 sub authority_tag_sf_browse {
1194 my $subfield = shift;
1196 my $page_size = shift || 9;
1197 my $page = shift || 0;
1199 # Match authority.full_rec normalization
1200 $value = naco_normalize($value, $subfield);
1202 my ($before_limit,$after_limit) = (0,0);
1203 my ($before_offset,$after_offset) = (0,0);
1206 $before_limit = $after_limit = int($page_size / 2);
1207 $after_limit += 1 if ($page_size % 2);
1209 $before_offset = $after_offset = int($page_size / 2);
1210 $before_offset += 1 if ($page_size % 2);
1211 $before_limit = $after_limit = $page_size;
1214 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1216 # .refs variant includes 4xx and 5xx variants for see / see also
1218 foreach my $tagname (@$tag) {
1219 push(@ref_tags, $tagname);
1220 if ($self->api_name =~ /\.refs\./) {
1221 push(@ref_tags, '4' . substr($tagname, 1, 2));
1222 push(@ref_tags, '5' . substr($tagname, 1, 2));
1228 my $before = $_storage->request(
1229 "open-ils.cstore.json_query.atomic",
1230 { select => { afr => [qw/record value/] },
1232 where => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1233 order_by => { afr => { value => 'desc' } },
1234 limit => $before_limit,
1235 offset => abs($page) * $page_size - $before_offset,
1238 push @list, map { $_->{record} } reverse(@$before);
1242 my $after = $_storage->request(
1243 "open-ils.cstore.json_query.atomic",
1244 { select => { afr => [qw/record value/] },
1246 where => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1247 order_by => { afr => { value => 'asc' } },
1248 limit => $after_limit,
1249 offset => abs($page) * $page_size - $after_offset,
1252 push @list, map { $_->{record} } @$after;
1255 # If we're not pulling in see/see also references, just return the raw list
1256 if ($self->api_name !~ /\.refs\./) {
1260 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1263 foreach my $record (@list) {
1264 next if exists $seen{$record};
1265 push @retlist, int($record);
1271 __PACKAGE__->register_method(
1272 method => 'authority_tag_sf_browse',
1273 api_name => 'open-ils.supercat.authority.tag.browse',
1277 { desc => <<" DESC",
1278 Returns a list of the requested authority record IDs held
1283 desc => 'The target Authority MARC tag',
1285 { name => 'subfield',
1286 desc => 'The target Authority MARC subfield',
1289 desc => 'The target string',
1291 { name => 'page_size',
1292 desc => 'Count of call numbers to retrieve, default is 9',
1295 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1299 { desc => 'Authority Record IDs that are near the target string',
1304 sub general_startwith {
1307 return tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1309 __PACKAGE__->register_method(
1310 method => 'general_startwith',
1311 api_name => 'open-ils.supercat.title.startwith',
1312 tag => 'tnf', subfield => 'a',
1316 { desc => "Returns a list of the requested org-scoped record IDs held",
1318 [ { name => 'value', desc => 'The target title', type => 'string' },
1319 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1320 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1321 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1322 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1323 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1324 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1327 __PACKAGE__->register_method(
1328 method => 'general_startwith',
1329 api_name => 'open-ils.supercat.author.startwith',
1330 tag => [qw/100 110 111/], subfield => 'a',
1334 { desc => "Returns a list of the requested org-scoped record IDs held",
1336 [ { name => 'value', desc => 'The target author', type => 'string' },
1337 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1338 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1339 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1340 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1341 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1342 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1345 __PACKAGE__->register_method(
1346 method => 'general_startwith',
1347 api_name => 'open-ils.supercat.subject.startwith',
1348 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
1352 { desc => "Returns a list of the requested org-scoped record IDs held",
1354 [ { name => 'value', desc => 'The target subject', type => 'string' },
1355 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1356 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1357 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1358 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1359 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1360 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1363 __PACKAGE__->register_method(
1364 method => 'general_startwith',
1365 api_name => 'open-ils.supercat.topic.startwith',
1366 tag => [qw/650 690/], subfield => 'a',
1370 { desc => "Returns a list of the requested org-scoped record IDs held",
1372 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1373 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1374 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1375 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1376 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1377 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1378 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1381 __PACKAGE__->register_method(
1382 method => 'general_startwith',
1383 api_name => 'open-ils.supercat.series.startwith',
1384 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
1388 { desc => "Returns a list of the requested org-scoped record IDs held",
1390 [ { name => 'value', desc => 'The target series', type => 'string' },
1391 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1392 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1393 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1394 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1395 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1396 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1401 sub tag_sf_startwith {
1406 my $subfield = shift;
1409 my $limit = shift || 10;
1410 my $page = shift || 0;
1411 my $statuses = shift || [];
1412 my $copy_locations = shift || [];
1414 my $offset = $limit * abs($page);
1415 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1418 if ($ou && $ou ne '-') {
1419 my $orgs = $_storage->request(
1420 "open-ils.cstore.direct.actor.org_unit.search",
1421 { shortname => $ou },
1423 flesh_fields => { aou => [qw/children/] }
1426 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1429 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
1434 my $before = $_storage->request(
1435 "open-ils.cstore.json_query.atomic",
1436 { select => { mfr => [qw/record value/] },
1441 subfield => $subfield,
1442 value => { '<' => lc($value) }
1446 { select=> { acp => [ 'id' ] },
1447 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1449 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1452 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1453 ((@$statuses) ? ( status => $statuses) : ()),
1454 ((@$copy_locations) ? ( location => $copy_locations) : ())
1461 { select=> { auri => [ 'id' ] },
1462 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1464 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1465 '+auri' => { active => 't' }
1472 order_by => { mfr => { value => 'desc' } },
1477 push @list, map { $_->{record} } reverse(@$before);
1481 my $after = $_storage->request(
1482 "open-ils.cstore.json_query.atomic",
1483 { select => { mfr => [qw/record value/] },
1488 subfield => $subfield,
1489 value => { '>=' => lc($value) }
1493 { select=> { acp => [ 'id' ] },
1494 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1496 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1499 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1500 ((@$statuses) ? ( status => $statuses) : ()),
1501 ((@$copy_locations) ? ( location => $copy_locations) : ())
1508 { select=> { auri => [ 'id' ] },
1509 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1511 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1512 '+auri' => { active => 't' }
1519 order_by => { mfr => { value => 'asc' } },
1524 push @list, map { $_->{record} } @$after;
1529 __PACKAGE__->register_method(
1530 method => 'tag_sf_startwith',
1531 api_name => 'open-ils.supercat.tag.startwith',
1535 { desc => <<" DESC",
1536 Returns a list of the requested org-scoped record IDs held
1541 desc => 'The target MARC tag',
1543 { name => 'subfield',
1544 desc => 'The target MARC subfield',
1547 desc => 'The target string',
1549 { name => 'org_unit',
1550 desc => 'The org unit shortname (or "-" or undef for global) to browse',
1552 { name => 'page_size',
1553 desc => 'Count of call numbers to retrieve, default is 9',
1556 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1558 { name => 'statuses',
1559 desc => 'Array of statuses to filter copies by, optional and can be undef.',
1561 { name => 'locations',
1562 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1566 { desc => 'Record IDs that have copies at the relevant org units',
1571 sub axis_authority_startwith {
1576 $axis =~ s/^authority\.//;
1577 $axis =~ s/(\.refs)$//;
1580 return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
1583 for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
1584 push @tags, $f->tag;
1586 push @tags, $_->tag for @{$f->sub_entries};
1590 return authority_tag_sf_startwith($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
1592 __PACKAGE__->register_method(
1593 method => 'axis_authority_startwith',
1594 api_name => 'open-ils.supercat.authority.startwith.by_axis',
1598 { desc => "Returns a list of the requested authority record IDs held",
1600 [ { name => 'axis', desc => 'The target axis', type => 'string' },
1601 { name => 'value', desc => 'The target value', type => 'string' },
1602 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1603 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1604 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1610 sub general_authority_startwith {
1613 return authority_tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1615 __PACKAGE__->register_method(
1616 method => 'general_authority_startwith',
1617 api_name => 'open-ils.supercat.authority.title.startwith',
1618 tag => ['130'], subfield => 'a',
1622 { desc => "Returns a list of the requested authority record IDs held",
1624 [ { name => 'value', desc => 'The target title', type => 'string' },
1625 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1626 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1627 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1630 __PACKAGE__->register_method(
1631 method => 'general_authority_startwith',
1632 api_name => 'open-ils.supercat.authority.author.startwith',
1633 tag => [qw/100 110 111/], subfield => 'a',
1637 { desc => "Returns a list of the requested authority record IDs held",
1639 [ { name => 'value', desc => 'The target author', type => 'string' },
1640 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1641 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1642 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1645 __PACKAGE__->register_method(
1646 method => 'general_authority_startwith',
1647 api_name => 'open-ils.supercat.authority.subject.startwith',
1648 tag => [qw/148 150 151 155/], subfield => 'a',
1652 { desc => "Returns a list of the requested authority record IDs held",
1654 [ { name => 'value', desc => 'The target subject', type => 'string' },
1655 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1656 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1657 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1660 __PACKAGE__->register_method(
1661 method => 'general_authority_startwith',
1662 api_name => 'open-ils.supercat.authority.topic.startwith',
1663 tag => ['150'], subfield => 'a',
1667 { desc => "Returns a list of the requested authority record IDs held",
1669 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1670 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1671 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1672 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1675 __PACKAGE__->register_method(
1676 method => 'general_authority_startwith',
1677 api_name => 'open-ils.supercat.authority.title.refs.startwith',
1678 tag => ['130'], subfield => 'a',
1682 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1684 [ { name => 'value', desc => 'The target title', type => 'string' },
1685 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1686 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1687 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1690 __PACKAGE__->register_method(
1691 method => 'general_authority_startwith',
1692 api_name => 'open-ils.supercat.authority.author.refs.startwith',
1693 tag => [qw/100 110 111/], subfield => 'a',
1697 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1699 [ { name => 'value', desc => 'The target author', type => 'string' },
1700 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1701 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1702 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1705 __PACKAGE__->register_method(
1706 method => 'general_authority_startwith',
1707 api_name => 'open-ils.supercat.authority.subject.refs.startwith',
1708 tag => [qw/148 150 151 155/], subfield => 'a',
1712 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1714 [ { name => 'value', desc => 'The target subject', type => 'string' },
1715 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1716 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1717 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1720 __PACKAGE__->register_method(
1721 method => 'general_authority_startwith',
1722 api_name => 'open-ils.supercat.authority.topic.refs.startwith',
1723 tag => ['150'], subfield => 'a',
1727 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1729 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1730 { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1731 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1732 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1738 sub authority_tag_sf_startwith {
1743 my $subfield = shift;
1746 my $limit = shift || 10;
1747 my $page = shift || 0;
1749 # Match authority.full_rec normalization
1750 $value = naco_normalize($value, $subfield);
1752 my $ref_limit = $limit;
1753 my $offset = $limit * abs($page);
1754 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1757 # .refs variant includes 4xx and 5xx variants for see / see also
1758 foreach my $tagname (@$tag) {
1759 push(@ref_tags, $tagname);
1760 if ($self->api_name =~ /\.refs\./) {
1761 push(@ref_tags, '4' . substr($tagname, 1, 2));
1762 push(@ref_tags, '5' . substr($tagname, 1, 2));
1769 # Don't skip the first actual page of results in descending order
1770 $offset = $offset - $limit;
1772 my $before = $_storage->request(
1773 "open-ils.cstore.json_query.atomic",
1774 { select => { afr => [qw/record value/] },
1776 where => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1777 order_by => { afr => { value => 'desc' } },
1778 limit => $ref_limit,
1782 push @list, map { $_->{record} } reverse(@$before);
1786 my $after = $_storage->request(
1787 "open-ils.cstore.json_query.atomic",
1788 { select => { afr => [qw/record value/] },
1790 where => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1791 order_by => { afr => { value => 'asc' } },
1792 limit => $ref_limit,
1796 push @list, map { $_->{record} } @$after;
1799 # If we're not pulling in see/see also references, just return the raw list
1800 if ($self->api_name !~ /\.refs\./) {
1804 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1807 foreach my $record (@list) {
1808 next if exists $seen{$record};
1809 push @retlist, int($record);
1815 __PACKAGE__->register_method(
1816 method => 'authority_tag_sf_startwith',
1817 api_name => 'open-ils.supercat.authority.tag.startwith',
1821 { desc => <<" DESC",
1822 Returns a list of the requested authority record IDs held
1827 desc => 'The target Authority MARC tag',
1829 { name => 'subfield',
1830 desc => 'The target Authority MARC subfield',
1833 desc => 'The target string',
1835 { name => 'page_size',
1836 desc => 'Count of call numbers to retrieve, default is 10',
1839 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1843 { desc => 'Authority Record IDs that are near the target string',
1849 sub holding_data_formats {
1852 namespace_uri => 'http://www.loc.gov/MARC21/slim',
1853 docs => 'http://www.loc.gov/marcxml/',
1854 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
1858 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acn.formats', api_level => 1 );
1859 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acp.formats', api_level => 1 );
1860 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.auri.formats', api_level => 1 );
1863 __PACKAGE__->register_method(
1864 method => 'retrieve_uri',
1865 api_name => 'open-ils.supercat.auri.marcxml.retrieve',
1869 { desc => <<" DESC",
1870 Returns a fleshed call number object
1875 desc => 'An OpenILS asset::uri id',
1879 { desc => 'fleshed uri',
1887 my $args = shift || {};
1889 return OpenILS::Application::SuperCat::unAPI
1890 ->new(OpenSRF::AppSession
1891 ->create( 'open-ils.cstore' )
1893 "open-ils.cstore.direct.asset.uri.retrieve",
1897 auri => [qw/call_number_maps/],
1898 auricnm => [qw/call_number/],
1899 acn => [qw/owning_lib record prefix suffix/],
1906 __PACKAGE__->register_method(
1907 method => 'retrieve_copy',
1908 api_name => 'open-ils.supercat.acp.marcxml.retrieve',
1912 { desc => <<" DESC",
1913 Returns a fleshed call number object
1918 desc => 'An OpenILS asset::copy id',
1922 { desc => 'fleshed copy',
1930 my $args = shift || {};
1932 return OpenILS::Application::SuperCat::unAPI
1933 ->new(OpenSRF::AppSession
1934 ->create( 'open-ils.cstore' )
1936 "open-ils.cstore.direct.asset.copy.retrieve",
1940 acn => [qw/owning_lib record prefix suffix/],
1941 acp => [qw/call_number location status circ_lib stat_cat_entries notes parts/],
1948 __PACKAGE__->register_method(
1949 method => 'retrieve_callnumber',
1950 api_name => 'open-ils.supercat.acn.marcxml.retrieve',
1955 { desc => <<" DESC",
1956 Returns a fleshed call number object
1961 desc => 'An OpenILS asset::call_number id',
1965 { desc => 'call number with copies',
1969 sub retrieve_callnumber {
1973 my $args = shift || {};
1975 return OpenILS::Application::SuperCat::unAPI
1976 ->new(OpenSRF::AppSession
1977 ->create( 'open-ils.cstore' )
1979 "open-ils.cstore.direct.asset.call_number.retrieve",
1983 acn => [qw/owning_lib record copies uri_maps prefix suffix/],
1984 auricnm => [qw/uri/],
1985 acp => [qw/location status circ_lib stat_cat_entries notes parts/],
1993 __PACKAGE__->register_method(
1994 method => 'basic_record_holdings',
1995 api_name => 'open-ils.supercat.record.basic_holdings.retrieve',
2000 { desc => <<" DESC",
2001 Returns a basic hash representation of the requested bibliographic record's holdings
2006 desc => 'An OpenILS biblio::record_entry id',
2010 { desc => 'Hash of bib record holdings hierarchy (call numbers and copies)',
2014 sub basic_record_holdings {
2020 # holdings hold an array of call numbers, which hold an array of copies
2021 # holdings => [ label: { library, [ copies: { barcode, location, status, circ_lib } ] } ]
2024 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2026 my $tree = $_storage->request(
2027 "open-ils.cstore.direct.biblio.record_entry.retrieve",
2031 bre => [qw/call_numbers/],
2032 acn => [qw/copies owning_lib prefix suffix/],
2033 acp => [qw/location status circ_lib parts/],
2038 my $o_search = { shortname => uc($ou) };
2039 if (!$ou || $ou eq '-') {
2040 $o_search = { parent_ou => undef };
2043 my $orgs = $_storage->request(
2044 "open-ils.cstore.direct.actor.org_unit.search",
2047 flesh_fields => { aou => [qw/children/] }
2051 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
2053 $logger->debug("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2055 for my $cn (@{$tree->call_numbers}) {
2056 next unless ( $cn->deleted eq 'f' || !$cn->deleted );
2059 for my $c (@{$cn->copies}) {
2060 next unless grep {$c->circ_lib->id == $_} @ou_ids;
2061 next unless _cp_is_visible($cn, $c);
2067 $holdings{$cn->label}{'owning_lib'} = $cn->owning_lib->shortname;
2069 for my $cp (@{$cn->copies}) {
2071 next unless grep { $cp->circ_lib->id == $_ } @ou_ids;
2072 next unless _cp_is_visible($cn, $cp);
2074 push @{$holdings{$cn->label}{'copies'}}, {
2075 barcode => $cp->barcode,
2076 status => $cp->status->name,
2077 location => $cp->location->name,
2078 circlib => $cp->circ_lib->shortname
2087 sub _cp_is_visible {
2092 if ( ($cp->deleted eq 'f' || !$cp->deleted) &&
2093 $cp->location->opac_visible eq 't' &&
2094 $cp->status->opac_visible eq 't' &&
2095 $cp->opac_visible eq 't' &&
2096 $cp->circ_lib->opac_visible eq 't' &&
2097 $cn->owning_lib->opac_visible eq 't'
2105 #__PACKAGE__->register_method(
2106 # method => 'new_record_holdings',
2107 # api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2112 # { desc => <<" DESC",
2113 #Returns the XML representation of the requested bibliographic record's holdings
2117 # { name => 'bibId',
2118 # desc => 'An OpenILS biblio::record_entry id',
2119 # type => 'number' },
2122 # { desc => 'Stream of bib record holdings hierarchy in XML',
2123 # type => 'string' }
2128 sub new_record_holdings {
2137 $paging = [-1,0] if (!$paging or !ref($paging) or @$paging == 0);
2138 my $limit = $$paging[0];
2139 my $offset = $$paging[1] || 0;
2141 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2142 my $_search = OpenSRF::AppSession->create( 'open-ils.search' );
2144 my $o_search = { shortname => uc($ou) };
2145 if (!$ou || $ou eq '-') {
2146 $o_search = { parent_ou => undef };
2149 my $one_org = $_storage->request(
2150 "open-ils.cstore.direct.actor.org_unit.search",
2154 my $count_req = $_search->request('open-ils.search.biblio.record.copy_count' => $one_org->id => $bib);
2155 my $staff_count_req = $_search->request('open-ils.search.biblio.record.copy_count.staff' => $one_org->id => $bib);
2157 my $orgs = $_storage->request(
2158 'open-ils.cstore.json_query.atomic',
2159 { from => [ 'actor.org_unit_descendants', defined($depth) ? ( $one_org->id, $depth ) : ( $one_org->id ) ] }
2163 my @ou_ids = map { $_->{id} } @$orgs;
2165 $logger->info("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2167 my %subselect = ( '-or' => [
2168 { owning_lib => \@ou_ids },
2172 call_number => { '=' => {'+acn'=>'id'} },
2174 circ_lib => \@ou_ids
2181 # we are dealing with -full or -uris, so we need to flesh things out
2184 # either way we're going to need uris
2185 # get all the uris up the tree (see also ba47ecc6196)
2187 my $uri_orgs = $_storage->request(
2188 'open-ils.cstore.json_query.atomic',
2189 { from => [ 'actor.org_unit_ancestors', $one_org->id ] }
2192 my @uri_ou_ids = map { $_->{id} } @$uri_orgs;
2194 # we have a -uris, just get the uris
2197 owning_lib => \@uri_ou_ids,
2200 from => { auricnm => 'auri' },
2202 call_number => { '=' => {'+acn'=>'id'} },
2203 '+auri' => { active => 't' }
2207 # we have a -full, get all the things
2208 } elsif ($flesh == 1) {
2209 %subselect = ( '-or' => [
2210 { owning_lib => \@ou_ids },
2214 call_number => { '=' => {'+acn'=>'id'} },
2216 circ_lib => \@ou_ids
2222 { owning_lib => \@uri_ou_ids },
2224 from => { auricnm => 'auri' },
2226 call_number => { '=' => {'+acn'=>'id'} },
2227 '+auri' => { active => 't' }
2236 my $cns = $_storage->request(
2237 "open-ils.cstore.direct.asset.call_number.search.atomic",
2244 acn => [qw/copies owning_lib uri_maps prefix suffix/],
2245 auricnm => [qw/uri/],
2246 acp => [qw/circ_lib location status stat_cat_entries notes parts/],
2247 asce => [qw/stat_cat/],
2249 ( $limit > -1 ? ( limit => $limit ) : () ),
2250 ( $offset ? ( offset => $offset ) : () ),
2251 order_by => { acn => { label_sortkey => {} } }
2255 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2259 $client->respond("<holdings xmlns='http://open-ils.org/spec/holdings/v1'><counts>\n");
2261 my $copy_counts = $count_req->gather(1);
2262 my $staff_copy_counts = $staff_count_req->gather(1);
2264 for my $c (@$copy_counts) {
2265 $$c{transcendant} ||= 0;
2266 my $out = "<count type='public'";
2267 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2268 $client->respond("$out/>\n")
2271 for my $c (@$staff_copy_counts) {
2272 $$c{transcendant} ||= 0;
2273 my $out = "<count type='staff'";
2274 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2275 $client->respond("$out/>\n")
2278 $client->respond("</counts><volumes>\n");
2280 for my $cn (@$cns) {
2281 next unless (@{$cn->copies} > 0 or (ref($cn->uri_maps) and @{$cn->uri_maps}));
2283 # We don't want O:A:S:unAPI::acn to return the record, we've got that already
2284 # In the context of BibTemplate, copies aren't necessary because we pull those
2285 # in a separate call
2287 OpenILS::Application::SuperCat::unAPI::acn
2289 ->as_xml( {no_record => 1, no_copies => ($flesh ? 0 : 1)} )
2293 $client->respond("</volumes><subscriptions>\n");
2295 $logger->info("Searching for serial holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2297 %subselect = ( '-or' => [
2298 { owning_lib => \@ou_ids },
2301 where => { holding_lib => \@ou_ids },
2307 my $ssubs = $_storage->request(
2308 "open-ils.cstore.direct.serial.subscription.search.atomic",
2309 { record_entry => $bib,
2314 ssub => [qw/distributions issuances scaps owning_lib/],
2315 sdist => [qw/basic_summary supplement_summary index_summary streams holding_lib/],
2316 sstr => [qw/items/],
2317 sitem => [qw/notes unit/],
2318 sunit => [qw/notes location status circ_lib stat_cat_entries call_number/],
2319 acn => [qw/owning_lib prefix suffix/],
2321 ( $limit > -1 ? ( limit => $limit ) : () ),
2322 ( $offset ? ( offset => $offset ) : () ),
2334 date_expected => {},
2341 for my $ssub (@$ssubs) {
2342 next unless (@{$ssub->distributions} or @{$ssub->issuances} or @{$ssub->scaps});
2344 # We don't want O:A:S:unAPI::ssub to return the record, we've got that already
2345 # In the context of BibTemplate, copies aren't necessary because we pull those
2346 # in a separate call
2348 OpenILS::Application::SuperCat::unAPI::ssub
2350 ->as_xml( {no_record => 1, no_items => ($flesh ? 0 : 1)} )
2355 return "</subscriptions></holdings>\n";
2357 __PACKAGE__->register_method(
2358 method => 'new_record_holdings',
2359 api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2364 { desc => <<" DESC",
2365 Returns the XML representation of the requested bibliographic record's holdings
2370 desc => 'An OpenILS biblio::record_entry ID',
2372 { name => 'orgUnit',
2373 desc => 'An OpenILS actor::org_unit short name that limits the scope of returned holdings',
2376 desc => 'An OpenILS actor::org_unit_type depththat limits the scope of returned holdings',
2378 { name => 'hideCopies',
2379 desc => 'Flag that prevents the inclusion of copies in the returned holdings',
2380 type => 'boolean' },
2382 desc => 'Arry of limit and offset for holdings paging',
2386 { desc => 'Stream of bib record holdings hierarchy in XML',
2396 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2398 my $recs = $_storage->request(
2399 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2400 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2403 return undef unless (@$recs);
2405 return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
2407 __PACKAGE__->register_method(
2408 method => 'isbn_holdings',
2409 api_name => 'open-ils.supercat.isbn.holdings_xml.retrieve',
2413 { desc => <<" DESC",
2414 Returns the XML representation of the requested bibliographic record's holdings
2423 { desc => 'The bib record holdings hierarchy in XML',
2431 return '' unless $text;
2432 $text =~ s/&/&/gsom;
2433 $text =~ s/</</gsom;
2434 $text =~ s/>/>/gsom;
2435 $text =~ s/"/"/gsom;
2436 $text =~ s/'/'/gsom;
2440 sub recent_changes {
2443 my $when = shift || '1-01-01';
2446 my $type = 'biblio';
2449 if ($self->api_name =~ /authority/o) {
2450 $type = 'authority';
2454 my $axis = 'create_date';
2455 $axis = 'edit_date' if ($self->api_name =~ /edit/o);
2457 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2459 return $_storage->request(
2460 "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
2461 { $axis => { ">" => $when }, id => { '>' => 0 }, deleted => 'f', active => 't' },
2462 { order_by => { $hint => "$axis desc" }, limit => $limit }
2466 for my $t ( qw/biblio authority/ ) {
2467 for my $a ( qw/import edit/ ) {
2469 __PACKAGE__->register_method(
2470 method => 'recent_changes',
2471 api_name => "open-ils.supercat.$t.record.$a.recent",
2475 { desc => "Returns a list of recently ${a}ed $t records",
2479 desc => "Date to start looking for ${a}ed records",
2480 default => '1-01-01',
2484 desc => "Maximum count to retrieve",
2488 { desc => "An id list of $t records",
2496 sub retrieve_authority_marcxml {
2501 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2503 my $record = $_storage->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rid )->gather(1);
2504 return $U->entityize( $record->marc ) if ($record);
2508 __PACKAGE__->register_method(
2509 method => 'retrieve_authority_marcxml',
2510 api_name => 'open-ils.supercat.authority.marcxml.retrieve',
2514 { desc => <<" DESC",
2515 Returns the MARCXML representation of the requested authority record
2519 { name => 'authorityId',
2520 desc => 'An OpenILS authority::record_entry id',
2524 { desc => 'The authority record in MARCXML',
2529 sub retrieve_record_marcxml {
2534 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2536 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
2537 return $U->entityize( $record->marc ) if ($record);
2541 __PACKAGE__->register_method(
2542 method => 'retrieve_record_marcxml',
2543 api_name => 'open-ils.supercat.record.marcxml.retrieve',
2547 { desc => <<" DESC",
2548 Returns the MARCXML representation of the requested bibliographic record
2553 desc => 'An OpenILS biblio::record_entry id',
2557 { desc => 'The bib record in MARCXML',
2562 sub retrieve_isbn_marcxml {
2567 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2569 my $recs = $_storage->request(
2570 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2571 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2574 return undef unless (@$recs);
2576 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2577 return $U->entityize( $record->marc ) if ($record);
2581 __PACKAGE__->register_method(
2582 method => 'retrieve_isbn_marcxml',
2583 api_name => 'open-ils.supercat.isbn.marcxml.retrieve',
2587 { desc => <<" DESC",
2588 Returns the MARCXML representation of the requested ISBN
2593 desc => 'An ... um ... ISBN',
2597 { desc => 'The bib record in MARCXML',
2602 sub retrieve_record_transform {
2607 (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
2609 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2610 #$_storage->connect;
2612 my $record = $_storage->request(
2613 'open-ils.cstore.direct.biblio.record_entry.retrieve',
2617 return undef unless ($record);
2619 return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2622 sub retrieve_isbn_transform {
2627 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2629 my $recs = $_storage->request(
2630 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2631 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2634 return undef unless (@$recs);
2636 (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
2638 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2640 return undef unless ($record);
2642 return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2645 sub retrieve_record_objects {
2650 my $type = 'biblio';
2652 if ($self->api_name =~ /authority/) {
2653 $type = 'authority';
2656 $ids = [$ids] unless (ref $ids);
2657 $ids = [grep {$_} @$ids];
2659 return [] unless (@$ids);
2661 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2662 return $_storage->request("open-ils.cstore.direct.$type.record_entry.search.atomic" => { id => [grep {$_} @$ids] })->gather(1);
2664 __PACKAGE__->register_method(
2665 method => 'retrieve_record_objects',
2666 api_name => 'open-ils.supercat.record.object.retrieve',
2670 { desc => <<" DESC",
2671 Returns the Fieldmapper object representation of the requested bibliographic records
2676 desc => 'OpenILS biblio::record_entry ids',
2680 { desc => 'The bib records',
2685 __PACKAGE__->register_method(
2686 method => 'retrieve_record_objects',
2687 api_name => 'open-ils.supercat.authority.object.retrieve',
2691 { desc => <<" DESC",
2692 Returns the Fieldmapper object representation of the requested authority records
2696 { name => 'authIds',
2697 desc => 'OpenILS authority::record_entry ids',
2701 { desc => 'The authority records',
2706 sub retrieve_isbn_object {
2711 return undef unless ($isbn);
2713 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2714 my $recs = $_storage->request(
2715 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2716 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2719 return undef unless (@$recs);
2721 return $_storage->request(
2722 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
2723 { id => $recs->[0]->record }
2726 __PACKAGE__->register_method(
2727 method => 'retrieve_isbn_object',
2728 api_name => 'open-ils.supercat.isbn.object.retrieve',
2732 { desc => <<" DESC",
2733 Returns the Fieldmapper object representation of the requested bibliographic record
2742 { desc => 'The bib record',
2749 sub retrieve_metarecord_mods {
2754 my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
2756 # Get the metarecord in question
2759 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
2762 # Now get the map of all bib records for the metarecord
2765 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2766 {metarecord => $rid}
2769 $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
2771 # and retrieve the lead (master) record as MODS
2773 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2774 ->run($mr->master_record);
2775 my $master_mods = $_parser->parse_string($master)->documentElement;
2776 $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods" );
2777 $master_mods->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2779 # ... and a MODS clone to populate, with guts removed.
2780 my $mods = $_parser->parse_string($master)->documentElement;
2781 $mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # modsCollection element
2782 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2783 ($mods) = $mods->findnodes('//mods:mods');
2784 #$mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # mods element
2785 $mods->removeChildNodes;
2786 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2788 # Add the metarecord ID as a (locally defined) info URI
2789 my $recordInfo = $mods
2791 ->createElement("recordInfo");
2793 my $recordIdentifier = $mods
2795 ->createElement("recordIdentifier");
2797 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2802 $recordIdentifier->appendTextNode(
2803 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
2806 $recordInfo->appendChild($recordIdentifier);
2807 $mods->appendChild($recordInfo);
2809 # Grab the title, author and ISBN for the master record and populate the metarecord
2810 my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
2813 $title->setNamespace( "http://www.loc.gov/mods/", "mods" );
2814 $title->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2815 $title = $mods->ownerDocument->importNode($title);
2816 $mods->appendChild($title);
2819 my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
2821 $author->setNamespace( "http://www.loc.gov/mods/", "mods" );
2822 $author->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2823 $author = $mods->ownerDocument->importNode($author);
2824 $mods->appendChild($author);
2827 my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
2829 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2830 $isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2831 $isbn = $mods->ownerDocument->importNode($isbn);
2832 $mods->appendChild($isbn);
2835 # ... and loop over the constituent records
2836 for my $map ( @$recs ) {
2840 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2841 ->run($map->source);
2843 my $part_mods = $_parser->parse_string($rec);
2844 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods" );
2845 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2846 ($part_mods) = $part_mods->findnodes('//mods:mods');
2848 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
2849 $node->setNamespace( "http://www.loc.gov/mods/", "mods" );
2850 $node->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2851 $node = $mods->ownerDocument->importNode($node);
2852 $mods->appendChild( $node );
2855 my $relatedItem = $mods
2857 ->createElement("relatedItem");
2859 $relatedItem->setAttribute( type => 'constituent' );
2861 my $identifier = $mods
2863 ->createElement("identifier");
2865 $identifier->setAttribute( type => 'uri' );
2867 my $subRecordInfo = $mods
2869 ->createElement("recordInfo");
2871 my $subRecordIdentifier = $mods
2873 ->createElement("recordIdentifier");
2875 my $subid = $map->source;
2876 $subRecordIdentifier->appendTextNode(
2877 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
2882 $subRecordInfo->appendChild($subRecordIdentifier);
2884 $relatedItem->appendChild( $subRecordInfo );
2886 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
2887 $tor->setNamespace( "http://www.loc.gov/mods/", "mods" );
2888 $tor->setNamespace( "http://www.loc.gov/mods/", undef, 1 ) if ($tor);
2889 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
2890 $relatedItem->appendChild($tor) if ($tor);
2892 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
2893 $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2894 $part_isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2895 $part_isbn = $mods->ownerDocument->importNode($part_isbn);
2896 $relatedItem->appendChild( $part_isbn );
2899 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
2903 $mods->appendChild( $relatedItem );
2907 $_storage->disconnect;
2909 return $U->entityize($mods->toString);
2912 __PACKAGE__->register_method(
2913 method => 'retrieve_metarecord_mods',
2914 api_name => 'open-ils.supercat.metarecord.mods.retrieve',
2918 { desc => <<" DESC",
2919 Returns the MODS representation of the requested metarecord
2923 { name => 'metarecordId',
2924 desc => 'An OpenILS metabib::metarecord id',
2928 { desc => 'The metarecord in MODS',
2933 sub list_metarecord_formats {
2936 { namespace_uri => 'http://www.loc.gov/mods/',
2937 docs => 'http://www.loc.gov/mods/',
2938 schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
2943 for my $type ( keys %metarecord_xslt ) {
2946 { namespace_uri => $metarecord_xslt{$type}{namespace_uri},
2947 docs => $metarecord_xslt{$type}{docs},
2948 schema_location => $metarecord_xslt{$type}{schema_location},
2955 __PACKAGE__->register_method(
2956 method => 'list_metarecord_formats',
2957 api_name => 'open-ils.supercat.metarecord.formats',
2961 { desc => <<" DESC",
2962 Returns the list of valid metarecord formats that supercat understands.
2965 { desc => 'The format list',
2971 sub list_authority_formats {
2974 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
2975 docs => 'http://www.loc.gov/marcxml/',
2976 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
2978 marc21 => { docs => 'http://www.loc.gov/marc/' }
2982 # for my $type ( keys %record_xslt ) {
2985 # { namespace_uri => $record_xslt{$type}{namespace_uri},
2986 # docs => $record_xslt{$type}{docs},
2987 # schema_location => $record_xslt{$type}{schema_location},
2994 __PACKAGE__->register_method(
2995 method => 'list_authority_formats',
2996 api_name => 'open-ils.supercat.authority.formats',
3000 { desc => <<" DESC",
3001 Returns the list of valid authority formats that supercat understands.
3004 { desc => 'The format list',
3009 sub list_record_formats {
3012 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
3013 docs => 'http://www.loc.gov/marcxml/',
3014 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
3017 { marc21 => { docs => 'http://www.loc.gov/marc/' } }
3020 for my $type ( keys %record_xslt ) {
3023 { namespace_uri => $record_xslt{$type}{namespace_uri},
3024 docs => $record_xslt{$type}{docs},
3025 schema_location => $record_xslt{$type}{schema_location},
3032 __PACKAGE__->register_method(
3033 method => 'list_record_formats',
3034 api_name => 'open-ils.supercat.record.formats',
3038 { desc => <<" DESC",
3039 Returns the list of valid record formats that supercat understands.
3042 { desc => 'The format list',
3046 __PACKAGE__->register_method(
3047 method => 'list_record_formats',
3048 api_name => 'open-ils.supercat.isbn.formats',
3052 { desc => <<" DESC",
3053 Returns the list of valid record formats that supercat understands.
3056 { desc => 'The format list',
3069 throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
3070 unless (length($isbn) >= 10);
3072 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
3074 # Create a storage session, since we'll be making muliple requests.
3077 # Find the record that has that ISBN.
3078 my $bibrec = $_storage->request(
3079 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
3080 { tag => '020', subfield => 'a', value => { like => lc($isbn).'%'} }
3083 # Go away if we don't have one.
3084 return {} unless (@$bibrec);
3086 # Find the metarecord for that bib record.
3087 my $mr = $_storage->request(
3088 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
3089 {source => $bibrec->[0]->record}
3092 # Find the other records for that metarecord.
3093 my $records = $_storage->request(
3094 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
3095 {metarecord => $mr->[0]->metarecord}
3098 # Just to be safe. There's currently no unique constraint on sources...
3099 my %unique_recs = map { ($_->source, 1) } @$records;
3100 my @rec_list = sort keys %unique_recs;
3102 # And now fetch the ISBNs for thos records.
3106 'open-ils.cstore.direct.metabib.full_rec.search',
3107 { tag => '020', subfield => 'a', record => $_ }
3108 )->gather(1) for (@rec_list);
3110 # We're done with the storage server session.
3111 $_storage->disconnect;
3113 # Return the oISBN data structure. This will be XMLized at a higher layer.
3115 { metarecord => $mr->[0]->metarecord,
3116 record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
3119 __PACKAGE__->register_method(
3121 api_name => 'open-ils.supercat.oisbn',
3125 { desc => <<" DESC",
3126 Returns the ISBN list for the metarecord of the requested isbn
3131 desc => 'An ISBN. Duh.',
3135 { desc => 'record to isbn map',
3140 sub return_bib_search_aliases {
3143 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
3145 my $cmsa = $_storage->request(
3146 'open-ils.cstore.direct.config.metabib_search_alias.search.atomic',
3147 { alias => { '!=' => undef } }
3151 if ($_->alias =~ /\./) {
3152 my ($qualifier, $name) = $_->alias =~ m/^(.+?)\.(.+)$/;
3153 $aliases{$qualifier}{$name}{'index'} = $_->alias;
3154 # We will add a 'title' property in a subsequent schema
3155 $aliases{$qualifier}{$name}{'title'} = $name;
3157 # au/kw/se/su/ti go into the default 'eg' qualifier
3158 $aliases{'eg'}{$_->alias}{'index'} = $_->alias;
3159 $aliases{'eg'}{$_->alias}{'title'} = $_->alias;
3166 __PACKAGE__->register_method(
3167 method => 'return_bib_search_aliases',
3168 api_name => 'open-ils.supercat.biblio.search_aliases',
3172 { desc => <<" DESC",
3173 Returns the set of qualified search aliases in the system
3177 { desc => 'Hash of qualified search aliases',
3183 package OpenILS::Application::SuperCat::unAPI;
3184 use base qw/OpenILS::Application::SuperCat/;
3187 die "dummy superclass, use a real class";
3193 return unless ($obj);
3195 $class = ref($class) || $class;
3197 if ($class eq __PACKAGE__) {
3198 return unless (ref($obj));
3199 $class .= '::' . $obj->json_hint;
3202 return bless { obj => $obj } => $class;
3207 return $self->{obj};
3210 package OpenILS::Application::SuperCat::unAPI::auri;
3211 use base qw/OpenILS::Application::SuperCat::unAPI/;
3217 my $xml = ' <uri xmlns="http://open-ils.org/spec/holdings/v1" ';
3218 $xml .= 'id="tag:open-ils.org:asset-uri/' . $self->obj->id . '" ';
3219 $xml .= 'use_restriction="' . $self->escape( $self->obj->use_restriction ) . '" ';
3220 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3221 $xml .= 'href="' . $self->escape( $self->obj->href ) . '">';
3223 if (!$args->{no_volumes}) {
3224 if (ref($self->obj->call_number_maps) && @{ $self->obj->call_number_maps }) {
3225 $xml .= " <volumes>\n" . join(
3228 OpenILS::Application::SuperCat::unAPI
3229 ->new( $_->call_number )
3230 ->as_xml({ %$args, no_uris=>1, no_copies=>1 })
3231 } @{ $self->obj->call_number_maps }
3232 ) . " </volumes>\n";
3235 $xml .= " <volumes/>\n";
3239 $xml .= " </uri>\n";
3244 package OpenILS::Application::SuperCat::unAPI::acn;
3245 use base qw/OpenILS::Application::SuperCat::unAPI/;
3251 my $xml = ' <volume xmlns="http://open-ils.org/spec/holdings/v1" ';
3253 $xml .= 'id="tag:open-ils.org:asset-call_number/' . $self->obj->id . '" ';
3254 $xml .= 'lib="' . $self->escape( $self->obj->owning_lib->shortname ) . '" ';
3255 $xml .= 'opac_visible="' . $self->obj->owning_lib->opac_visible . '" ';
3256 $xml .= 'deleted="' . $self->obj->deleted . '" ';
3257 $xml .= 'label="' . $self->escape( $self->obj->label ) . '">';
3260 if (!$args->{no_copies}) {
3261 if (ref($self->obj->copies) && @{ $self->obj->copies }) {
3262 $xml .= " <copies>\n" . join(
3265 OpenILS::Application::SuperCat::unAPI
3267 ->as_xml({ %$args, no_volume=>1 })
3268 } @{ $self->obj->copies }
3272 $xml .= " <copies/>\n";
3276 if (!$args->{no_uris}) {
3277 if (ref($self->obj->uri_maps) && @{ $self->obj->uri_maps }) {
3278 $xml .= " <uris>\n" . join(
3281 OpenILS::Application::SuperCat::unAPI
3283 ->as_xml({ %$args, no_volumes=>1 })
3284 } @{ $self->obj->uri_maps }
3288 $xml .= " <uris/>\n";
3293 $xml .= ' <prefix ';
3294 $xml .= 'ident="' . $self->obj->prefix->id . '" ';
3295 $xml .= 'id="tag:open-ils.org:asset-call_number_prefix/' . $self->obj->prefix->id . '" ';
3296 $xml .= 'label_sortkey="'.$self->escape( $self->obj->prefix->label_sortkey ) .'">';
3297 $xml .= $self->escape( $self->obj->prefix->label ) .'</prefix>';
3300 $xml .= ' <suffix ';
3301 $xml .= 'ident="' . $self->obj->suffix->id . '" ';
3302 $xml .= 'id="tag:open-ils.org:asset-call_number_suffix/' . $self->obj->suffix->id . '" ';
3303 $xml .= 'label_sortkey="'.$self->escape( $self->obj->suffix->label_sortkey ) .'">';
3304 $xml .= $self->escape( $self->obj->suffix->label ) .'</suffix>';
3307 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3308 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3309 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3310 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3313 unless ($args->{no_record}) {
3314 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3316 my $r_doc = $parser->parse_string($self->obj->record->marc);
3317 $r_doc->documentElement->setAttribute( id => $rec_tag );
3318 $xml .= $U->entityize($r_doc->documentElement->toString);
3321 $xml .= " </volume>\n";
3326 package OpenILS::Application::SuperCat::unAPI::ssub;
3327 use base qw/OpenILS::Application::SuperCat::unAPI/;
3333 my $xml = ' <subscription xmlns="http://open-ils.org/spec/holdings/v1" ';
3335 $xml .= 'id="tag:open-ils.org:serial-subscription/' . $self->obj->id . '" ';
3336 $xml .= 'start="' . $self->escape( $self->obj->start_date ) . '" ';
3337 $xml .= 'end="' . $self->escape( $self->obj->end_date ) . '" ';
3338 $xml .= 'expected_date_offset="' . $self->escape( $self->obj->expected_date_offset ) . '">';
3341 if (!$args->{no_distributions}) {
3342 if (ref($self->obj->distributions) && @{ $self->obj->distributions }) {
3343 $xml .= " <distributions>\n" . join(
3346 OpenILS::Application::SuperCat::unAPI
3348 ->as_xml({ %$args, no_subscription=>1, no_issuance=>1 })
3349 } @{ $self->obj->distributions }
3350 ) . " </distributions>\n";
3353 $xml .= " <distributions/>\n";
3357 if (!$args->{no_captions_and_patterns}) {
3358 if (ref($self->obj->scaps) && @{ $self->obj->scaps }) {
3359 $xml .= " <captions_and_patterns>\n" . join(
3362 OpenILS::Application::SuperCat::unAPI
3364 ->as_xml({ %$args, no_subscription=>1 })
3365 } @{ $self->obj->scaps }
3366 ) . " </captions_and_patterns>\n";
3369 $xml .= " <captions_and_patterns/>\n";
3373 if (!$args->{no_issuances}) {
3374 if (ref($self->obj->issuances) && @{ $self->obj->issuances }) {
3375 $xml .= " <issuances>\n" . join(
3378 OpenILS::Application::SuperCat::unAPI
3380 ->as_xml({ %$args, no_subscription=>1, no_items=>1 })
3381 } @{ $self->obj->issuances }
3382 ) . " </issuances>\n";
3385 $xml .= " <issuances/>\n";
3390 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3391 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3392 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3393 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3396 unless ($args->{no_record}) {
3397 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3399 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3400 $r_doc->documentElement->setAttribute( id => $rec_tag );
3401 $xml .= $U->entityize($r_doc->documentElement->toString);
3404 $xml .= " </subscription>\n";
3409 package OpenILS::Application::SuperCat::unAPI::ssum_base;
3410 use base qw/OpenILS::Application::SuperCat::unAPI/;
3416 (my $type = ref($self)) =~ s/^.+([^:]+)$/$1/;
3418 my $xml = " <serial_summary xmlns=\"http://open-ils.org/spec/holdings/v1\" type=\"$type\" ";
3420 $xml .= "id=\"tag:open-ils.org:serial-summary-$type/" . $self->obj->id . '" ';
3421 $xml .= 'generated_coverage="' . $self->escape( $self->obj->generated_coverage ) . '" ';
3422 $xml .= 'show_generated="' . $self->escape( $self->obj->show_generated ) . '" ';
3423 $xml .= 'textual_holdings="' . $self->escape( $self->obj->textual_holdings ) . '">';
3426 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_summaries=>1 }) if (!$args->{no_distribution});
3428 $xml .= " </serial_summary>\n";
3434 package OpenILS::Application::SuperCat::unAPI::sssum;
3435 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3437 package OpenILS::Application::SuperCat::unAPI::sbsum;
3438 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3440 package OpenILS::Application::SuperCat::unAPI::sisum;
3441 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3443 package OpenILS::Application::SuperCat::unAPI::sdist;
3444 use base qw/OpenILS::Application::SuperCat::unAPI/;
3450 my $xml = ' <distribution xmlns="http://open-ils.org/spec/holdings/v1" ';
3452 $xml .= 'id="tag:open-ils.org:serial-distribution/' . $self->obj->id . '" ';
3453 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3454 $xml .= 'unit_label_prefix="' . $self->escape( $self->obj->unit_label_prefix ) . '" ';
3455 $xml .= 'unit_label_suffix="' . $self->escape( $self->obj->unit_label_suffix ) . '">';
3458 if (!$args->{no_distributions}) {
3459 if (ref($self->obj->streams) && @{ $self->obj->streams }) {
3460 $xml .= " <streams>\n" . join(
3463 OpenILS::Application::SuperCat::unAPI
3465 ->as_xml({ %$args, no_distribution=>1 })
3466 } @{ $self->obj->streams }
3467 ) . " </streams>\n";
3470 $xml .= " <streams/>\n";
3474 if (!$args->{no_summaries}) {
3475 $xml .= " <summaries>\n";
3479 OpenILS::Application::SuperCat::unAPI
3481 ->as_xml({ %$args, no_distribution=>1 }) : ""
3482 } ($self->obj->basic_summary, $self->obj->supplement_summary, $self->obj->index_summary)
3485 $xml .= " </summaries>\n";
3489 $xml .= ' <holding_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3490 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->holding_lib->id . '" ';
3491 $xml .= 'shortname="'.$self->escape( $self->obj->holding_lib->shortname ) .'" ';
3492 $xml .= 'name="'.$self->escape( $self->obj->holding_lib->name ) .'"/>';
3495 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_distributions=>1 }) if (!$args->{no_subscription});
3497 if (!$args->{no_record} && $self->obj->record_entry) {
3498 my $rec_tag = "tag:open-ils.org:serial-record_entry/".$self->obj->record_entry->id ;
3500 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3501 $r_doc->documentElement->setAttribute( id => $rec_tag );
3502 $xml .= $U->entityize($r_doc->documentElement->toString);
3505 $xml .= " </distribution>\n";
3510 package OpenILS::Application::SuperCat::unAPI::sstr;
3511 use base qw/OpenILS::Application::SuperCat::unAPI/;
3517 my $xml = ' <stream xmlns="http://open-ils.org/spec/holdings/v1" ';
3519 $xml .= 'id="tag:open-ils.org:serial-stream/' . $self->obj->id . '" ';
3520 $xml .= 'routing_label="' . $self->escape( $self->obj->routing_label ) . '">';
3523 if (!$args->{no_items}) {
3524 if (ref($self->obj->items) && @{ $self->obj->items }) {
3525 $xml .= " <items>\n" . join(
3528 OpenILS::Application::SuperCat::unAPI
3530 ->as_xml({ %$args, no_stream=>1 })
3531 } @{ $self->obj->items }
3535 $xml .= " <items/>\n";
3539 #XXX routing_list_user's?
3541 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_streams=>1 }) if (!$args->{no_distribution});
3543 $xml .= " </stream>\n";
3548 package OpenILS::Application::SuperCat::unAPI::sitem;
3549 use base qw/OpenILS::Application::SuperCat::unAPI/;
3555 my $xml = ' <serial_item xmlns="http://open-ils.org/spec/holdings/v1" ';
3557 $xml .= 'id="tag:open-ils.org:serial-item/' . $self->obj->id . '" ';
3558 $xml .= 'date_expected="' . $self->escape( $self->obj->date_expected ) . '"';
3559 $xml .= ' date_received="' . $self->escape( $self->obj->date_received ) .'"'if ($self->obj->date_received);
3561 if ($args->{no_issuance}) {
3562 my $siss = ref($self->obj->issuance) ? $self->obj->issuance->id : $self->obj->issuance;
3563 $xml .= ' issuance="tag:open-ils.org:serial-issuance/' . $siss . '"';
3568 if (ref($self->obj->notes) && $self->obj->notes) {
3569 $xml .= " <notes>\n";
3570 for my $note ( @{$self->obj->notes} ) {
3571 next unless ( $note->pub eq 't' );
3572 $xml .= sprintf(' <note date="%s" title="%s">%s</note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3575 $xml .= " </notes>\n";
3577 $xml .= " <notes/>\n";
3580 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->issuance )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_issuance});
3581 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->stream )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_stream});
3582 $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});
3583 $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});
3585 $xml .= " </serial_item>\n";
3590 package OpenILS::Application::SuperCat::unAPI::sunit;
3591 use base qw/OpenILS::Application::SuperCat::unAPI/;
3597 my $xml = ' <serial_unit xmlns="http://open-ils.org/spec/holdings/v1" '.
3598 'id="tag:open-ils.org:serial-unit/' . $self->obj->id . '" ';
3600 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3601 create_date edit_date copy_number circulate deposit ref holdable deleted
3602 deposit_amount price barcode circ_modifier circ_as_type opac_visible cost
3603 status_changed_time floating mint_condition detailed_contents sort_key summary_contents
3608 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3609 $xml .= ' <location ident="' . $self->obj->location->id . '">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3610 $xml .= ' <circlib ident="' . $self->obj->circ_lib->id . '">' . $self->escape( $self->obj->circ_lib->name ) . "</circlib>\n";
3612 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3613 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3614 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3615 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'"/>';
3618 $xml .= " <copy_notes>\n";
3619 if (ref($self->obj->notes) && $self->obj->notes) {
3620 for my $note ( @{$self->obj->notes} ) {
3621 next unless ( $note->pub eq 't' );
3622 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3627 $xml .= " </copy_notes>\n";
3628 $xml .= " <statcats>\n";
3630 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3631 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3632 next unless ( $sce->stat_cat->opac_visible eq 't' );
3633 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3637 $xml .= " </statcats>\n";
3639 unless ($args->{no_volume}) {
3640 if (ref($self->obj->call_number)) {
3641 $xml .= OpenILS::Application::SuperCat::unAPI
3642 ->new( $self->obj->call_number )
3643 ->as_xml({ %$args, no_copies=>1 });
3645 $xml .= " <volume/>\n";
3649 $xml .= " </serial_unit>\n";
3654 package OpenILS::Application::SuperCat::unAPI::scap;
3655 use base qw/OpenILS::Application::SuperCat::unAPI/;
3661 my $xml = ' <caption_and_pattern xmlns="http://open-ils.org/spec/holdings/v1" '.
3662 'id="tag:open-ils.org:serial-caption_and_pattern/' . $self->obj->id . '" ';
3664 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3665 create_date type active pattern_code enum_1 enum_2 enum_3 enum_4
3666 enum_5 enum_6 chron_1 chron_2 chron_3 chron_4 chron_5 start_date end_date
3669 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_captions_and_patterns=>1 }) if (!$args->{no_subscription});
3670 $xml .= " </caption_and_pattern>\n";
3675 package OpenILS::Application::SuperCat::unAPI::siss;
3676 use base qw/OpenILS::Application::SuperCat::unAPI/;
3682 my $xml = ' <issuance xmlns="http://open-ils.org/spec/holdings/v1" '.
3683 'id="tag:open-ils.org:serial-issuance/' . $self->obj->id . '" ';
3685 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" '
3686 for (qw/create_date edit_date label date_published holding_code holding_type holding_link_id/);
3690 if (!$args->{no_items}) {
3691 if (ref($self->obj->items) && @{ $self->obj->items }) {
3692 $xml .= " <items>\n" . join(
3695 OpenILS::Application::SuperCat::unAPI
3697 ->as_xml({ %$args, no_stream=>1 })
3698 } @{ $self->obj->items }
3702 $xml .= " <items/>\n";
3706 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_issuances=>1 }) if (!$args->{no_subscription});
3707 $xml .= " </issuance>\n";
3712 package OpenILS::Application::SuperCat::unAPI::acp;
3713 use base qw/OpenILS::Application::SuperCat::unAPI/;
3719 my $xml = ' <copy xmlns="http://open-ils.org/spec/holdings/v1" '.
3720 'id="tag:open-ils.org:asset-copy/' . $self->obj->id . '" ';
3722 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3723 create_date edit_date copy_number circulate deposit ref holdable deleted
3724 deposit_amount price barcode circ_modifier circ_as_type opac_visible
3729 $xml .= ' <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3730 $xml .= ' <location ident="' . $self->obj->location->id . '" opac_visible="'.$self->obj->location->opac_visible.'">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3731 $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";
3733 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3734 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3735 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3736 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'" opac_visible="'.$self->obj->circ_lib->opac_visible.'"/>';
3739 $xml .= " <monograph_parts>\n";
3740 if (ref($self->obj->parts) && $self->obj->parts) {
3741 for my $part ( @{$self->obj->parts} ) {
3742 $xml .= sprintf(' <monograph_part record="%s" sortkey="%s">%s</monograph_part>',$part->record, $self->escape($part->label_sortkey), $self->escape($part->label));
3747 $xml .= " </monograph_parts>\n";
3748 $xml .= " <copy_notes>\n";
3749 if (ref($self->obj->notes) && $self->obj->notes) {
3750 for my $note ( @{$self->obj->notes} ) {
3751 next unless ( $note->pub eq 't' );
3752 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3757 $xml .= " </copy_notes>\n";
3758 $xml .= " <statcats>\n";
3760 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3761 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3762 next unless ( $sce->stat_cat->opac_visible eq 't' );
3763 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3767 $xml .= " </statcats>\n";
3769 unless ($args->{no_volume}) {
3770 if (ref($self->obj->call_number)) {
3771 $xml .= OpenILS::Application::SuperCat::unAPI
3772 ->new( $self->obj->call_number )
3773 ->as_xml({ %$args, no_copies=>1 });
3775 $xml .= " <volume/>\n";
3779 $xml .= " </copy>\n";