1 # We'll be working with XML, so...
4 use Unicode::Normalize;
6 # ... and this has some handy common methods
7 use OpenILS::Application::AppUtils;
9 my $parser = new XML::LibXML;
10 my $U = 'OpenILS::Application::AppUtils';
13 package OpenILS::Application::SuperCat;
17 use OpenILS::Utils::Normalize qw( naco_normalize );
19 # All OpenSRF applications must be based on OpenSRF::Application or
20 # a subclass thereof. Makes sense, eh?
21 use OpenILS::Application;
22 use base qw/OpenILS::Application/;
24 # This is the client class, used for connecting to open-ils.storage
25 use OpenSRF::AppSession;
27 # This is an extension of Error.pm that supplies some error types to throw
28 use OpenSRF::EX qw(:try);
30 # This is a helper class for querying the OpenSRF Settings application ...
31 use OpenSRF::Utils::SettingsClient;
33 # ... and here we have the built in logging helper ...
34 use OpenSRF::Utils::Logger qw($logger);
36 # ... and this is our OpenILS object (en|de)coder and psuedo-ORM package.
37 use OpenILS::Utils::Fieldmapper;
48 # we need an XML parser
49 $_parser = new XML::LibXML;
52 $_xslt = new XML::LibXSLT;
54 # parse the MODS xslt ...
55 my $mods33_xslt = $_parser->parse_file(
56 OpenSRF::Utils::SettingsClient
58 ->config_value( dirs => 'xsl' ).
59 "/MARC21slim2MODS33.xsl"
61 # and stash a transformer
62 $record_xslt{mods33}{xslt} = $_xslt->parse_stylesheet( $mods33_xslt );
63 $record_xslt{mods33}{namespace_uri} = 'http://www.loc.gov/mods/v3';
64 $record_xslt{mods33}{docs} = 'http://www.loc.gov/mods/';
65 $record_xslt{mods33}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-3.xsd';
67 # parse the MODS xslt ...
68 my $mods32_xslt = $_parser->parse_file(
69 OpenSRF::Utils::SettingsClient
71 ->config_value( dirs => 'xsl' ).
72 "/MARC21slim2MODS32.xsl"
74 # and stash a transformer
75 $record_xslt{mods32}{xslt} = $_xslt->parse_stylesheet( $mods32_xslt );
76 $record_xslt{mods32}{namespace_uri} = 'http://www.loc.gov/mods/v3';
77 $record_xslt{mods32}{docs} = 'http://www.loc.gov/mods/';
78 $record_xslt{mods32}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-2.xsd';
80 # parse the MODS xslt ...
81 my $mods3_xslt = $_parser->parse_file(
82 OpenSRF::Utils::SettingsClient
84 ->config_value( dirs => 'xsl' ).
85 "/MARC21slim2MODS3.xsl"
87 # and stash a transformer
88 $record_xslt{mods3}{xslt} = $_xslt->parse_stylesheet( $mods3_xslt );
89 $record_xslt{mods3}{namespace_uri} = 'http://www.loc.gov/mods/v3';
90 $record_xslt{mods3}{docs} = 'http://www.loc.gov/mods/';
91 $record_xslt{mods3}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-1.xsd';
93 # parse the MODS xslt ...
94 my $mods_xslt = $_parser->parse_file(
95 OpenSRF::Utils::SettingsClient
97 ->config_value( dirs => 'xsl' ).
98 "/MARC21slim2MODS.xsl"
100 # and stash a transformer
101 $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
102 $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
103 $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
104 $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
106 # parse the ATOM entry xslt ...
107 my $atom_xslt = $_parser->parse_file(
108 OpenSRF::Utils::SettingsClient
110 ->config_value( dirs => 'xsl' ).
111 "/MARC21slim2ATOM.xsl"
113 # and stash a transformer
114 $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
115 $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
116 $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
118 # parse the RDFDC xslt ...
119 my $rdf_dc_xslt = $_parser->parse_file(
120 OpenSRF::Utils::SettingsClient
122 ->config_value( dirs => 'xsl' ).
123 "/MARC21slim2RDFDC.xsl"
125 # and stash a transformer
126 $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
127 $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
128 $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
130 # parse the SRWDC xslt ...
131 my $srw_dc_xslt = $_parser->parse_file(
132 OpenSRF::Utils::SettingsClient
134 ->config_value( dirs => 'xsl' ).
135 "/MARC21slim2SRWDC.xsl"
137 # and stash a transformer
138 $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
139 $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
140 $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
142 # parse the OAIDC xslt ...
143 my $oai_dc_xslt = $_parser->parse_file(
144 OpenSRF::Utils::SettingsClient
146 ->config_value( dirs => 'xsl' ).
147 "/MARC21slim2OAIDC.xsl"
149 # and stash a transformer
150 $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
151 $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
152 $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
154 # parse the RSS xslt ...
155 my $rss_xslt = $_parser->parse_file(
156 OpenSRF::Utils::SettingsClient
158 ->config_value( dirs => 'xsl' ).
159 "/MARC21slim2RSS2.xsl"
161 # and stash a transformer
162 $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
164 # parse the FGDC xslt ...
165 my $fgdc_xslt = $_parser->parse_file(
166 OpenSRF::Utils::SettingsClient
168 ->config_value( dirs => 'xsl' ).
169 "/MARC21slim2FGDC.xsl"
171 # and stash a transformer
172 $record_xslt{fgdc}{xslt} = $_xslt->parse_stylesheet( $fgdc_xslt );
173 $record_xslt{fgdc}{docs} = 'http://www.fgdc.gov/metadata/csdgm/index_html';
174 $record_xslt{fgdc}{schema_location} = 'http://www.fgdc.gov/metadata/fgdc-std-001-1998.xsd';
176 register_record_transforms();
181 sub register_record_transforms {
182 for my $type ( keys %record_xslt ) {
183 __PACKAGE__->register_method(
184 method => 'retrieve_record_transform',
185 api_name => "open-ils.supercat.record.$type.retrieve",
189 { desc => "Returns the \U$type\E representation ".
190 "of the requested bibliographic record",
194 desc => 'An OpenILS biblio::record_entry id',
198 { desc => "The bib record in \U$type\E",
203 __PACKAGE__->register_method(
204 method => 'retrieve_isbn_transform',
205 api_name => "open-ils.supercat.isbn.$type.retrieve",
209 { desc => "Returns the \U$type\E representation ".
210 "of the requested bibliographic record",
218 { desc => "The bib record in \U$type\E",
230 return unless ($tree && ref($tree->$field));
232 my @things = $filter->($tree);
233 for my $v ( @{$tree->$field} ){
234 push @things, $filter->($v);
235 push @things, tree_walker($v, $field, $filter);
246 my $page_size = shift || 9;
247 my $page = shift || 0;
248 my $statuses = shift || [];
249 my $copy_locations = shift || [];
251 my ($before_limit,$after_limit) = (0,0);
252 my ($before_offset,$after_offset) = (0,0);
255 $before_limit = $after_limit = int($page_size / 2);
256 $after_limit += 1 if ($page_size % 2);
258 $before_offset = $after_offset = int($page_size / 2);
259 $before_offset += 1 if ($page_size % 2);
260 $before_limit = $after_limit = $page_size;
263 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
265 my $o_search = { shortname => $ou };
266 if (!$ou || $ou eq '-') {
267 $o_search = { parent_ou => undef };
270 my $orgs = $_storage->request(
271 "open-ils.cstore.direct.actor.org_unit.search",
274 flesh_fields => { aou => [qw/children/] }
278 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
280 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
285 if (@$statuses || @$copy_locations) {
290 call_number => { '=' => { '+acn' => 'id' } },
292 ((@$statuses) ? ( status => $statuses) : ()),
293 ((@$copy_locations) ? ( location => $copy_locations) : ())
300 my $before = $_storage->request(
301 "open-ils.cstore.direct.asset.call_number.search.atomic",
302 { label => { "<" => $label },
303 owning_lib => \@ou_ids,
308 flesh_fields => { acn => [qw/record owning_lib/] },
309 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
310 limit => $before_limit,
311 offset => abs($page) * $page_size - $before_offset,
314 push @list, reverse(@$before);
318 my $after = $_storage->request(
319 "open-ils.cstore.direct.asset.call_number.search.atomic",
320 { label => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
321 owning_lib => \@ou_ids,
326 flesh_fields => { acn => [qw/record owning_lib/] },
327 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
328 limit => $after_limit,
329 offset => abs($page) * $page_size - $after_offset,
337 __PACKAGE__->register_method(
338 method => 'cn_browse',
339 api_name => 'open-ils.supercat.call_number.browse',
344 Returns the XML representation of the requested bibliographic record's holdings
349 desc => 'The target call number label',
351 { name => 'org_unit',
352 desc => 'The org unit shortname (or "-" or undef for global) to browse',
354 { name => 'page_size',
355 desc => 'Count of call numbers to retrieve, default is 9',
358 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
360 { name => 'statuses',
361 desc => 'Array of statuses to filter copies by, optional and can be undef.',
363 { name => 'locations',
364 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
368 { desc => 'Call numbers with owning_lib and record fleshed',
379 my $limit = shift || 10;
380 my $page = shift || 0;
381 my $statuses = shift || [];
382 my $copy_locations = shift || [];
385 my $offset = abs($page) * $limit;
386 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
388 my $o_search = { shortname => $ou };
389 if (!$ou || $ou eq '-') {
390 $o_search = { parent_ou => undef };
393 my $orgs = $_storage->request(
394 "open-ils.cstore.direct.actor.org_unit.search",
397 flesh_fields => { aou => [qw/children/] }
401 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
403 $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
408 if (@$statuses || @$copy_locations) {
413 call_number => { '=' => { '+acn' => 'id' } },
415 ((@$statuses) ? ( status => $statuses) : ()),
416 ((@$copy_locations) ? ( location => $copy_locations) : ())
423 my $before = $_storage->request(
424 "open-ils.cstore.direct.asset.call_number.search.atomic",
425 { label => { "<" => $label },
426 owning_lib => \@ou_ids,
431 flesh_fields => { acn => [qw/record owning_lib/] },
432 order_by => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
437 push @list, reverse(@$before);
441 my $after = $_storage->request(
442 "open-ils.cstore.direct.asset.call_number.search.atomic",
443 { label => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
444 owning_lib => \@ou_ids,
449 flesh_fields => { acn => [qw/record owning_lib/] },
450 order_by => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
460 __PACKAGE__->register_method(
461 method => 'cn_startwith',
462 api_name => 'open-ils.supercat.call_number.startwith',
467 Returns the XML representation of the requested bibliographic record's holdings
472 desc => 'The target call number label',
474 { name => 'org_unit',
475 desc => 'The org unit shortname (or "-" or undef for global) to browse',
477 { name => 'page_size',
478 desc => 'Count of call numbers to retrieve, default is 9',
481 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
483 { name => 'statuses',
484 desc => 'Array of statuses to filter copies by, optional and can be undef.',
486 { name => 'locations',
487 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
491 { desc => 'Call numbers with owning_lib and record fleshed',
497 sub new_books_by_item {
502 my $page_size = shift || 10;
503 my $page = shift || 1;
504 my $statuses = shift || [];
505 my $copy_locations = shift || [];
507 my $offset = $page_size * ($page - 1);
509 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
512 if ($ou && $ou ne '-') {
513 my $orgs = $_storage->request(
514 "open-ils.cstore.direct.actor.org_unit.search",
515 { shortname => $ou },
517 flesh_fields => { aou => [qw/children/] }
520 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
523 $logger->debug("Searching for records with new copies at orgs [".join(',',@ou_ids)."], based on $ou");
524 my $cns = $_storage->request(
525 "open-ils.cstore.json_query.atomic",
526 { select => { acn => ['record'],
527 acp => [{ aggregate => 1 => transform => max => column => create_date => alias => 'create_date'}]
529 from => { 'acn' => { 'acp' => { field => call_number => fkey => 'id' } } },
533 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
534 ((@$statuses) ? ( status => $statuses) : ()),
535 ((@$copy_locations) ? ( location => $copy_locations) : ())
537 '+acn' => { record => { '>' => 0 } },
539 order_by => { acp => { create_date => { transform => 'max', direction => 'desc' } } },
545 return [ map { $_->{record} } @$cns ];
547 __PACKAGE__->register_method(
548 method => 'new_books_by_item',
549 api_name => 'open-ils.supercat.new_book_list',
554 Returns the XML representation of the requested bibliographic record's holdings
558 { name => 'org_unit',
559 desc => 'The org unit shortname (or "-" or undef for global) to list',
561 { name => 'page_size',
562 desc => 'Count of records to retrieve, default is 10',
565 desc => 'The page of records to retrieve, calculated based on page_size. Starts at 1.',
567 { name => 'statuses',
568 desc => 'Array of statuses to filter copies by, optional and can be undef.',
570 { name => 'locations',
571 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
575 { desc => 'Record IDs',
584 return tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
586 __PACKAGE__->register_method(
587 method => 'general_browse',
588 api_name => 'open-ils.supercat.title.browse',
589 tag => 'tnf', subfield => 'a',
593 { desc => "Returns a list of the requested org-scoped record IDs held",
595 [ { name => 'value', desc => 'The target title', type => 'string' },
596 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
597 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
598 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
599 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
600 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
601 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
604 __PACKAGE__->register_method(
605 method => 'general_browse',
606 api_name => 'open-ils.supercat.author.browse',
607 tag => [qw/100 110 111/], subfield => 'a',
611 { desc => "Returns a list of the requested org-scoped record IDs held",
613 [ { name => 'value', desc => 'The target author', type => 'string' },
614 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
615 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
616 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
617 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
618 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
619 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
622 __PACKAGE__->register_method(
623 method => 'general_browse',
624 api_name => 'open-ils.supercat.subject.browse',
625 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
629 { desc => "Returns a list of the requested org-scoped record IDs held",
631 [ { name => 'value', desc => 'The target subject', type => 'string' },
632 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
633 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
634 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
635 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
636 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
637 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
640 __PACKAGE__->register_method(
641 method => 'general_browse',
642 api_name => 'open-ils.supercat.topic.browse',
643 tag => [qw/650 690/], subfield => 'a',
647 { desc => "Returns a list of the requested org-scoped record IDs held",
649 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
650 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
651 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
652 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
653 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
654 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
655 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
658 __PACKAGE__->register_method(
659 method => 'general_browse',
660 api_name => 'open-ils.supercat.series.browse',
661 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
665 { desc => "Returns a list of the requested org-scoped record IDs held",
667 [ { name => 'value', desc => 'The target series', type => 'string' },
668 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
669 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
670 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
671 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
672 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
673 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
683 my $subfield = shift;
686 my $page_size = shift || 9;
687 my $page = shift || 0;
688 my $statuses = shift || [];
689 my $copy_locations = shift || [];
691 my ($before_limit,$after_limit) = (0,0);
692 my ($before_offset,$after_offset) = (0,0);
695 $before_limit = $after_limit = int($page_size / 2);
696 $after_limit += 1 if ($page_size % 2);
698 $before_offset = $after_offset = int($page_size / 2);
699 $before_offset += 1 if ($page_size % 2);
700 $before_limit = $after_limit = $page_size;
703 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
706 if ($ou && $ou ne '-') {
707 my $orgs = $_storage->request(
708 "open-ils.cstore.direct.actor.org_unit.search",
709 { shortname => $ou },
711 flesh_fields => { aou => [qw/children/] }
714 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
717 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
722 my $before = $_storage->request(
723 "open-ils.cstore.json_query.atomic",
724 { select => { mfr => [qw/record value/] },
729 subfield => $subfield,
730 value => { '<' => lc($value) }
734 { select=> { acp => [ 'id' ] },
735 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
737 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
740 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
741 ((@$statuses) ? ( status => $statuses) : ()),
742 ((@$copy_locations) ? ( location => $copy_locations) : ())
749 { select=> { auri => [ 'id' ] },
750 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
752 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
753 '+auri' => { active => 't' }
760 order_by => { mfr => { value => 'desc' } },
761 limit => $before_limit,
762 offset => abs($page) * $page_size - $before_offset,
765 push @list, map { $_->{record} } reverse(@$before);
769 my $after = $_storage->request(
770 "open-ils.cstore.json_query.atomic",
771 { select => { mfr => [qw/record value/] },
776 subfield => $subfield,
777 value => { '>=' => lc($value) }
781 { select=> { acp => [ 'id' ] },
782 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
784 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
787 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
788 ((@$statuses) ? ( status => $statuses) : ()),
789 ((@$copy_locations) ? ( location => $copy_locations) : ())
796 { select=> { auri => [ 'id' ] },
797 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
799 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
800 '+auri' => { active => 't' }
807 order_by => { mfr => { value => 'asc' } },
808 limit => $after_limit,
809 offset => abs($page) * $page_size - $after_offset,
812 push @list, map { $_->{record} } @$after;
817 __PACKAGE__->register_method(
818 method => 'tag_sf_browse',
819 api_name => 'open-ils.supercat.tag.browse',
824 Returns a list of the requested org-scoped record IDs held
829 desc => 'The target MARC tag',
831 { name => 'subfield',
832 desc => 'The target MARC subfield',
835 desc => 'The target string',
837 { name => 'org_unit',
838 desc => 'The org unit shortname (or "-" or undef for global) to browse',
840 { name => 'page_size',
841 desc => 'Count of call numbers to retrieve, default is 9',
844 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
846 { name => 'statuses',
847 desc => 'Array of statuses to filter copies by, optional and can be undef.',
849 { name => 'locations',
850 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
854 { desc => 'Record IDs that have copies at the relevant org units',
859 sub general_authority_browse {
862 return authority_tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
864 __PACKAGE__->register_method(
865 method => 'general_authority_browse',
866 api_name => 'open-ils.supercat.authority.title.browse',
867 tag => ['130'], subfield => 'a',
871 { desc => "Returns a list of the requested authority record IDs held",
873 [ { name => 'value', desc => 'The target title', type => 'string' },
874 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
875 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
876 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
879 __PACKAGE__->register_method(
880 method => 'general_authority_browse',
881 api_name => 'open-ils.supercat.authority.author.browse',
882 tag => [qw/100 110 111/], subfield => 'a',
886 { desc => "Returns a list of the requested authority record IDs held",
888 [ { name => 'value', desc => 'The target author', type => 'string' },
889 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
890 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
891 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
894 __PACKAGE__->register_method(
895 method => 'general_authority_browse',
896 api_name => 'open-ils.supercat.authority.subject.browse',
897 tag => [qw/148 150 151 155/], subfield => 'a',
901 { desc => "Returns a list of the requested authority record IDs held",
903 [ { name => 'value', desc => 'The target subject', type => 'string' },
904 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
905 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
906 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
909 __PACKAGE__->register_method(
910 method => 'general_authority_browse',
911 api_name => 'open-ils.supercat.authority.topic.browse',
912 tag => ['150'], subfield => 'a',
916 { desc => "Returns a list of the requested authority record IDs held",
918 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
919 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
920 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
921 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
924 __PACKAGE__->register_method(
925 method => 'general_authority_browse',
926 api_name => 'open-ils.supercat.authority.title.refs.browse',
927 tag => ['130'], subfield => 'a',
931 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
933 [ { name => 'value', desc => 'The target title', type => 'string' },
934 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
935 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
936 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
939 __PACKAGE__->register_method(
940 method => 'general_authority_browse',
941 api_name => 'open-ils.supercat.authority.author.refs.browse',
942 tag => [qw/100 110 111/], subfield => 'a',
946 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
948 [ { name => 'value', desc => 'The target author', type => 'string' },
949 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
950 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
951 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
954 __PACKAGE__->register_method(
955 method => 'general_authority_browse',
956 api_name => 'open-ils.supercat.authority.subject.refs.browse',
957 tag => [qw/148 150 151 155/], subfield => 'a',
961 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
963 [ { name => 'value', desc => 'The target subject', type => 'string' },
964 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
965 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
966 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
969 __PACKAGE__->register_method(
970 method => 'general_authority_browse',
971 api_name => 'open-ils.supercat.authority.topic.refs.browse',
972 tag => ['150'], subfield => 'a',
976 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
978 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
979 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
980 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
981 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
985 sub authority_tag_sf_browse {
990 my $subfield = shift;
992 my $page_size = shift || 9;
993 my $page = shift || 0;
995 # Match authority.full_rec normalization
996 $value = naco_normalize($value, $subfield);
998 my ($before_limit,$after_limit) = (0,0);
999 my ($before_offset,$after_offset) = (0,0);
1002 $before_limit = $after_limit = int($page_size / 2);
1003 $after_limit += 1 if ($page_size % 2);
1005 $before_offset = $after_offset = int($page_size / 2);
1006 $before_offset += 1 if ($page_size % 2);
1007 $before_limit = $after_limit = $page_size;
1010 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1012 # .refs variant includes 4xx and 5xx variants for see / see also
1014 foreach my $tagname (@$tag) {
1015 push(@ref_tags, $tagname);
1016 if ($self->api_name =~ /\.refs\./) {
1017 push(@ref_tags, '4' . substr($tagname, 1, 2));
1018 push(@ref_tags, '5' . substr($tagname, 1, 2));
1024 my $before = $_storage->request(
1025 "open-ils.cstore.json_query.atomic",
1026 { select => { afr => [qw/record value/] },
1027 from => { 'are', 'afr' },
1029 '+afr' => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1030 '+are' => { 'deleted' => 'f' }
1032 order_by => { afr => { value => 'desc' } },
1033 limit => $before_limit,
1034 offset => abs($page) * $page_size - $before_offset,
1037 push @list, map { $_->{record} } reverse(@$before);
1041 my $after = $_storage->request(
1042 "open-ils.cstore.json_query.atomic",
1043 { select => { afr => [qw/record value/] },
1044 from => { 'are', 'afr' },
1046 '+afr' => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1047 '+are' => { 'deleted' => 'f' }
1049 order_by => { afr => { value => 'asc' } },
1050 limit => $after_limit,
1051 offset => abs($page) * $page_size - $after_offset,
1054 push @list, map { $_->{record} } @$after;
1057 # If we're not pulling in see/see also references, just return the raw list
1058 if ($self->api_name !~ /\.refs\./) {
1062 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1065 foreach my $record (@list) {
1066 next if exists $seen{$record};
1067 push @retlist, int($record);
1073 __PACKAGE__->register_method(
1074 method => 'authority_tag_sf_browse',
1075 api_name => 'open-ils.supercat.authority.tag.browse',
1079 { desc => <<" DESC",
1080 Returns a list of the requested authority record IDs held
1085 desc => 'The target Authority MARC tag',
1087 { name => 'subfield',
1088 desc => 'The target Authority MARC subfield',
1091 desc => 'The target string',
1093 { name => 'page_size',
1094 desc => 'Count of call numbers to retrieve, default is 9',
1097 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1101 { desc => 'Authority Record IDs that are near the target string',
1106 sub general_startwith {
1109 return tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1111 __PACKAGE__->register_method(
1112 method => 'general_startwith',
1113 api_name => 'open-ils.supercat.title.startwith',
1114 tag => 'tnf', subfield => 'a',
1118 { desc => "Returns a list of the requested org-scoped record IDs held",
1120 [ { name => 'value', desc => 'The target title', type => 'string' },
1121 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1122 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1123 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1124 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1125 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1126 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1129 __PACKAGE__->register_method(
1130 method => 'general_startwith',
1131 api_name => 'open-ils.supercat.author.startwith',
1132 tag => [qw/100 110 111/], subfield => 'a',
1136 { desc => "Returns a list of the requested org-scoped record IDs held",
1138 [ { name => 'value', desc => 'The target author', type => 'string' },
1139 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1140 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1141 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1142 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1143 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1144 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1147 __PACKAGE__->register_method(
1148 method => 'general_startwith',
1149 api_name => 'open-ils.supercat.subject.startwith',
1150 tag => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
1154 { desc => "Returns a list of the requested org-scoped record IDs held",
1156 [ { name => 'value', desc => 'The target subject', type => 'string' },
1157 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1158 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1159 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1160 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1161 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1162 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1165 __PACKAGE__->register_method(
1166 method => 'general_startwith',
1167 api_name => 'open-ils.supercat.topic.startwith',
1168 tag => [qw/650 690/], subfield => 'a',
1172 { desc => "Returns a list of the requested org-scoped record IDs held",
1174 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1175 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1176 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1177 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1178 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1179 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1180 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1183 __PACKAGE__->register_method(
1184 method => 'general_startwith',
1185 api_name => 'open-ils.supercat.series.startwith',
1186 tag => [qw/440 490 800 810 811 830/], subfield => 'a',
1190 { desc => "Returns a list of the requested org-scoped record IDs held",
1192 [ { name => 'value', desc => 'The target series', type => 'string' },
1193 { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1194 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1195 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' },
1196 { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1197 { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1198 'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1203 sub tag_sf_startwith {
1208 my $subfield = shift;
1211 my $limit = shift || 10;
1212 my $page = shift || 0;
1213 my $statuses = shift || [];
1214 my $copy_locations = shift || [];
1216 my $offset = $limit * abs($page);
1217 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1220 if ($ou && $ou ne '-') {
1221 my $orgs = $_storage->request(
1222 "open-ils.cstore.direct.actor.org_unit.search",
1223 { shortname => $ou },
1225 flesh_fields => { aou => [qw/children/] }
1228 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1231 $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
1236 my $before = $_storage->request(
1237 "open-ils.cstore.json_query.atomic",
1238 { select => { mfr => [qw/record value/] },
1243 subfield => $subfield,
1244 value => { '<' => lc($value) }
1248 { select=> { acp => [ 'id' ] },
1249 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1251 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1254 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1255 ((@$statuses) ? ( status => $statuses) : ()),
1256 ((@$copy_locations) ? ( location => $copy_locations) : ())
1263 { select=> { auri => [ 'id' ] },
1264 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1266 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1267 '+auri' => { active => 't' }
1274 order_by => { mfr => { value => 'desc' } },
1279 push @list, map { $_->{record} } reverse(@$before);
1283 my $after = $_storage->request(
1284 "open-ils.cstore.json_query.atomic",
1285 { select => { mfr => [qw/record value/] },
1290 subfield => $subfield,
1291 value => { '>=' => lc($value) }
1295 { select=> { acp => [ 'id' ] },
1296 from => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1298 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1301 ((@ou_ids) ? ( circ_lib => \@ou_ids) : ()),
1302 ((@$statuses) ? ( status => $statuses) : ()),
1303 ((@$copy_locations) ? ( location => $copy_locations) : ())
1310 { select=> { auri => [ 'id' ] },
1311 from => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1313 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1314 '+auri' => { active => 't' }
1321 order_by => { mfr => { value => 'asc' } },
1326 push @list, map { $_->{record} } @$after;
1331 __PACKAGE__->register_method(
1332 method => 'tag_sf_startwith',
1333 api_name => 'open-ils.supercat.tag.startwith',
1337 { desc => <<" DESC",
1338 Returns a list of the requested org-scoped record IDs held
1343 desc => 'The target MARC tag',
1345 { name => 'subfield',
1346 desc => 'The target MARC subfield',
1349 desc => 'The target string',
1351 { name => 'org_unit',
1352 desc => 'The org unit shortname (or "-" or undef for global) to browse',
1354 { name => 'page_size',
1355 desc => 'Count of call numbers to retrieve, default is 9',
1358 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1360 { name => 'statuses',
1361 desc => 'Array of statuses to filter copies by, optional and can be undef.',
1363 { name => 'locations',
1364 desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1368 { desc => 'Record IDs that have copies at the relevant org units',
1373 sub general_authority_startwith {
1376 return authority_tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1378 __PACKAGE__->register_method(
1379 method => 'general_authority_startwith',
1380 api_name => 'open-ils.supercat.authority.title.startwith',
1381 tag => ['130'], subfield => 'a',
1385 { desc => "Returns a list of the requested authority record IDs held",
1387 [ { name => 'value', desc => 'The target title', type => 'string' },
1388 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1389 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1390 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1393 __PACKAGE__->register_method(
1394 method => 'general_authority_startwith',
1395 api_name => 'open-ils.supercat.authority.author.startwith',
1396 tag => [qw/100 110 111/], subfield => 'a',
1400 { desc => "Returns a list of the requested authority record IDs held",
1402 [ { name => 'value', desc => 'The target author', type => 'string' },
1403 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1404 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1405 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1408 __PACKAGE__->register_method(
1409 method => 'general_authority_startwith',
1410 api_name => 'open-ils.supercat.authority.subject.startwith',
1411 tag => [qw/148 150 151 155/], subfield => 'a',
1415 { desc => "Returns a list of the requested authority record IDs held",
1417 [ { name => 'value', desc => 'The target subject', type => 'string' },
1418 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1419 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1420 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1423 __PACKAGE__->register_method(
1424 method => 'general_authority_startwith',
1425 api_name => 'open-ils.supercat.authority.topic.startwith',
1426 tag => ['150'], subfield => 'a',
1430 { desc => "Returns a list of the requested authority record IDs held",
1432 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1433 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1434 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1435 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1438 __PACKAGE__->register_method(
1439 method => 'general_authority_startwith',
1440 api_name => 'open-ils.supercat.authority.title.refs.startwith',
1441 tag => ['130'], subfield => 'a',
1445 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1447 [ { name => 'value', desc => 'The target title', type => 'string' },
1448 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1449 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1450 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1453 __PACKAGE__->register_method(
1454 method => 'general_authority_startwith',
1455 api_name => 'open-ils.supercat.authority.author.refs.startwith',
1456 tag => [qw/100 110 111/], subfield => 'a',
1460 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1462 [ { name => 'value', desc => 'The target author', type => 'string' },
1463 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1464 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1465 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1468 __PACKAGE__->register_method(
1469 method => 'general_authority_startwith',
1470 api_name => 'open-ils.supercat.authority.subject.refs.startwith',
1471 tag => [qw/148 150 151 155/], subfield => 'a',
1475 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1477 [ { name => 'value', desc => 'The target subject', type => 'string' },
1478 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1479 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1480 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1483 __PACKAGE__->register_method(
1484 method => 'general_authority_startwith',
1485 api_name => 'open-ils.supercat.authority.topic.refs.startwith',
1486 tag => ['150'], subfield => 'a',
1490 { desc => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1492 [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1493 { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1494 { name => 'page', desc => 'The page of records retrieved, calculated based on page_size. Can be positive, negative or 0.', type => 'number' }, ],
1495 'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1499 sub authority_tag_sf_startwith {
1504 my $subfield = shift;
1507 my $limit = shift || 10;
1508 my $page = shift || 0;
1510 # Match authority.full_rec normalization
1511 $value = naco_normalize($value, $subfield);
1513 my $ref_limit = $limit;
1514 my $offset = $limit * abs($page);
1515 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1518 # .refs variant includes 4xx and 5xx variants for see / see also
1519 foreach my $tagname (@$tag) {
1520 push(@ref_tags, $tagname);
1521 if ($self->api_name =~ /\.refs\./) {
1522 push(@ref_tags, '4' . substr($tagname, 1, 2));
1523 push(@ref_tags, '5' . substr($tagname, 1, 2));
1530 # Don't skip the first actual page of results in descending order
1531 $offset = $offset - $limit;
1533 my $before = $_storage->request(
1534 "open-ils.cstore.json_query.atomic",
1535 { select => { afr => [qw/record value/] },
1536 from => { 'afr', 'are' },
1538 '+afr' => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1539 '+are' => { deleted => 'f' }
1541 order_by => { afr => { value => 'desc' } },
1542 limit => $ref_limit,
1546 push @list, map { $_->{record} } reverse(@$before);
1550 my $after = $_storage->request(
1551 "open-ils.cstore.json_query.atomic",
1552 { select => { afr => [qw/record value/] },
1553 from => { 'afr', 'are' },
1555 '+afr' => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1556 '+are' => { deleted => 'f' }
1558 order_by => { afr => { value => 'asc' } },
1559 limit => $ref_limit,
1563 push @list, map { $_->{record} } @$after;
1566 # If we're not pulling in see/see also references, just return the raw list
1567 if ($self->api_name !~ /\.refs\./) {
1571 # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1574 foreach my $record (@list) {
1575 next if exists $seen{$record};
1576 push @retlist, int($record);
1582 __PACKAGE__->register_method(
1583 method => 'authority_tag_sf_startwith',
1584 api_name => 'open-ils.supercat.authority.tag.startwith',
1588 { desc => <<" DESC",
1589 Returns a list of the requested authority record IDs held
1594 desc => 'The target Authority MARC tag',
1596 { name => 'subfield',
1597 desc => 'The target Authority MARC subfield',
1600 desc => 'The target string',
1602 { name => 'page_size',
1603 desc => 'Count of call numbers to retrieve, default is 9',
1606 desc => 'The page of call numbers to retrieve, calculated based on page_size. Can be positive, negative or 0.',
1610 { desc => 'Authority Record IDs that are near the target string',
1616 sub holding_data_formats {
1619 namespace_uri => 'http://www.loc.gov/MARC21/slim',
1620 docs => 'http://www.loc.gov/marcxml/',
1621 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
1625 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acn.formats', api_level => 1 );
1626 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acp.formats', api_level => 1 );
1627 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.auri.formats', api_level => 1 );
1630 __PACKAGE__->register_method(
1631 method => 'retrieve_uri',
1632 api_name => 'open-ils.supercat.auri.marcxml.retrieve',
1636 { desc => <<" DESC",
1637 Returns a fleshed call number object
1642 desc => 'An OpenILS asset::uri id',
1646 { desc => 'fleshed uri',
1654 my $args = shift || {};
1656 return OpenILS::Application::SuperCat::unAPI
1657 ->new(OpenSRF::AppSession
1658 ->create( 'open-ils.cstore' )
1660 "open-ils.cstore.direct.asset.uri.retrieve",
1664 auri => [qw/call_number_maps/],
1665 auricnm => [qw/call_number/],
1666 acn => [qw/owning_lib record/],
1673 __PACKAGE__->register_method(
1674 method => 'retrieve_copy',
1675 api_name => 'open-ils.supercat.acp.marcxml.retrieve',
1679 { desc => <<" DESC",
1680 Returns a fleshed call number object
1685 desc => 'An OpenILS asset::copy id',
1689 { desc => 'fleshed copy',
1697 my $args = shift || {};
1699 return OpenILS::Application::SuperCat::unAPI
1700 ->new(OpenSRF::AppSession
1701 ->create( 'open-ils.cstore' )
1703 "open-ils.cstore.direct.asset.copy.retrieve",
1707 acn => [qw/owning_lib record/],
1708 acp => [qw/call_number location status circ_lib stat_cat_entries notes/],
1715 __PACKAGE__->register_method(
1716 method => 'retrieve_callnumber',
1717 api_name => 'open-ils.supercat.acn.marcxml.retrieve',
1722 { desc => <<" DESC",
1723 Returns a fleshed call number object
1728 desc => 'An OpenILS asset::call_number id',
1732 { desc => 'call number with copies',
1736 sub retrieve_callnumber {
1740 my $args = shift || {};
1742 return OpenILS::Application::SuperCat::unAPI
1743 ->new(OpenSRF::AppSession
1744 ->create( 'open-ils.cstore' )
1746 "open-ils.cstore.direct.asset.call_number.retrieve",
1750 acn => [qw/owning_lib record copies uri_maps/],
1751 auricnm => [qw/uri/],
1752 acp => [qw/location status circ_lib stat_cat_entries notes/],
1760 __PACKAGE__->register_method(
1761 method => 'basic_record_holdings',
1762 api_name => 'open-ils.supercat.record.basic_holdings.retrieve',
1767 { desc => <<" DESC",
1768 Returns a basic hash representation of the requested bibliographic record's holdings
1773 desc => 'An OpenILS biblio::record_entry id',
1777 { desc => 'Hash of bib record holdings hierarchy (call numbers and copies)',
1781 sub basic_record_holdings {
1787 # holdings hold an array of call numbers, which hold an array of copies
1788 # holdings => [ label: { library, [ copies: { barcode, location, status, circ_lib } ] } ]
1791 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1793 my $tree = $_storage->request(
1794 "open-ils.cstore.direct.biblio.record_entry.retrieve",
1798 bre => [qw/call_numbers/],
1799 acn => [qw/copies owning_lib/],
1800 acp => [qw/location status circ_lib/],
1805 my $o_search = { shortname => uc($ou) };
1806 if (!$ou || $ou eq '-') {
1807 $o_search = { parent_ou => undef };
1810 my $orgs = $_storage->request(
1811 "open-ils.cstore.direct.actor.org_unit.search",
1814 flesh_fields => { aou => [qw/children/] }
1818 my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1820 $logger->debug("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
1822 for my $cn (@{$tree->call_numbers}) {
1823 next unless ( $cn->deleted eq 'f' || $cn->deleted == 0 );
1826 for my $c (@{$cn->copies}) {
1827 next unless grep {$c->circ_lib->id == $_} @ou_ids;
1828 next unless ( $c->deleted eq 'f' || $c->deleted == 0 );
1834 $holdings{$cn->label}{'owning_lib'} = $cn->owning_lib->shortname;
1836 for my $cp (@{$cn->copies}) {
1838 next unless grep { $cp->circ_lib->id == $_ } @ou_ids;
1839 next unless ( $cp->deleted eq 'f' || $cp->deleted == 0 );
1841 push @{$holdings{$cn->label}{'copies'}}, {
1842 barcode => $cp->barcode,
1843 status => $cp->status->name,
1844 location => $cp->location->name,
1845 circlib => $cp->circ_lib->shortname
1854 #__PACKAGE__->register_method(
1855 # method => 'new_record_holdings',
1856 # api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
1861 # { desc => <<" DESC",
1862 #Returns the XML representation of the requested bibliographic record's holdings
1866 # { name => 'bibId',
1867 # desc => 'An OpenILS biblio::record_entry id',
1868 # type => 'number' },
1871 # { desc => 'Stream of bib record holdings hierarchy in XML',
1872 # type => 'string' }
1877 sub new_record_holdings {
1886 $paging = [-1,0] if (!$paging or !ref($paging) or @$paging == 0);
1887 my $limit = $$paging[0];
1888 my $offset = $$paging[1] || 0;
1890 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1891 my $_search = OpenSRF::AppSession->create( 'open-ils.search' );
1893 my $o_search = { shortname => uc($ou) };
1894 if (!$ou || $ou eq '-') {
1895 $o_search = { parent_ou => undef };
1898 my $one_org = $_storage->request(
1899 "open-ils.cstore.direct.actor.org_unit.search",
1903 my $count_req = $_search->request('open-ils.search.biblio.record.copy_count' => $one_org->id => $bib);
1904 my $staff_count_req = $_search->request('open-ils.search.biblio.record.copy_count.staff' => $one_org->id => $bib);
1906 my $orgs = $_storage->request(
1907 'open-ils.cstore.json_query.atomic',
1908 { from => [ 'actor.org_unit_descendants', defined($depth) ? ( $one_org->id, $depth ) : ( $one_org->id ) ] }
1912 my @ou_ids = map { $_->{id} } @$orgs;
1914 $logger->info("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
1916 my %subselect = ( '-or' => [
1917 { owning_lib => \@ou_ids },
1921 call_number => { '=' => {'+acn'=>'id'} },
1923 circ_lib => \@ou_ids
1929 if ($flesh and $flesh eq 'uris') {
1931 owning_lib => \@ou_ids,
1933 from => { auricnm => 'auri' },
1935 call_number => { '=' => {'+acn'=>'id'} },
1936 '+auri' => { active => 't' }
1943 my $cns = $_storage->request(
1944 "open-ils.cstore.direct.asset.call_number.search.atomic",
1951 acn => [qw/copies owning_lib uri_maps/],
1952 auricnm => [qw/uri/],
1953 acp => [qw/circ_lib location status stat_cat_entries notes/],
1954 asce => [qw/stat_cat/],
1956 ( $limit > -1 ? ( limit => $limit ) : () ),
1957 ( $offset ? ( offset => $offset ) : () ),
1958 order_by => { acn => { label_sortkey => {} } }
1962 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
1966 $client->respond("<holdings xmlns='http://open-ils.org/spec/holdings/v1'><counts>\n");
1968 my $copy_counts = $count_req->gather(1);
1969 my $staff_copy_counts = $staff_count_req->gather(1);
1971 for my $c (@$copy_counts) {
1972 $$c{transcendant} ||= 0;
1973 my $out = "<count type='public'";
1974 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
1975 $client->respond("$out/>\n")
1978 for my $c (@$staff_copy_counts) {
1979 $$c{transcendant} ||= 0;
1980 my $out = "<count type='staff'";
1981 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
1982 $client->respond("$out/>\n")
1985 $client->respond("</counts><volumes>\n");
1987 for my $cn (@$cns) {
1988 next unless (@{$cn->copies} > 0 or (ref($cn->uri_maps) and @{$cn->uri_maps}));
1990 # We don't want O:A:S:unAPI::acn to return the record, we've got that already
1991 # In the context of BibTemplate, copies aren't necessary because we pull those
1992 # in a separate call
1994 OpenILS::Application::SuperCat::unAPI::acn
1996 ->as_xml( {no_record => 1, no_copies => ($flesh ? 0 : 1)} )
2000 $client->respond("</volumes><subscriptions>\n");
2002 $logger->info("Searching for serial holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2004 %subselect = ( '-or' => [
2005 { owning_lib => \@ou_ids },
2008 where => { holding_lib => \@ou_ids }
2013 my $ssubs = $_storage->request(
2014 "open-ils.cstore.direct.serial.subscription.search.atomic",
2015 { record_entry => $bib,
2020 ssub => [qw/distributions issuances scaps owning_lib/],
2021 sdist => [qw/basic_summary supplement_summary index_summary streams holding_lib/],
2022 sstr => [qw/items/],
2023 sitem => [qw/notes unit/],
2024 sunit => [qw/notes location status circ_lib stat_cat_entries call_number/],
2025 acn => [qw/owning_lib/],
2027 ( $limit > -1 ? ( limit => $limit ) : () ),
2028 ( $offset ? ( offset => $offset ) : () ),
2040 date_expected => {},
2047 for my $ssub (@$ssubs) {
2048 next unless (@{$ssub->distributions} or @{$ssub->issuances} or @{$ssub->scaps});
2050 # We don't want O:A:S:unAPI::ssub to return the record, we've got that already
2051 # In the context of BibTemplate, copies aren't necessary because we pull those
2052 # in a separate call
2054 OpenILS::Application::SuperCat::unAPI::ssub
2056 ->as_xml( {no_record => 1, no_items => ($flesh ? 0 : 1)} )
2061 return "</subscriptions></holdings>\n";
2063 __PACKAGE__->register_method(
2064 method => 'new_record_holdings',
2065 api_name => 'open-ils.supercat.record.holdings_xml.retrieve',
2070 { desc => <<" DESC",
2071 Returns the XML representation of the requested bibliographic record's holdings
2076 desc => 'An OpenILS biblio::record_entry ID',
2078 { name => 'orgUnit',
2079 desc => 'An OpenILS actor::org_unit short name that limits the scope of returned holdings',
2082 desc => 'An OpenILS actor::org_unit_type depththat limits the scope of returned holdings',
2084 { name => 'hideCopies',
2085 desc => 'Flag that prevents the inclusion of copies in the returned holdings',
2086 type => 'boolean' },
2088 desc => 'Arry of limit and offset for holdings paging',
2092 { desc => 'Stream of bib record holdings hierarchy in XML',
2102 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2104 my $recs = $_storage->request(
2105 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2106 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2109 return undef unless (@$recs);
2111 return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
2113 __PACKAGE__->register_method(
2114 method => 'isbn_holdings',
2115 api_name => 'open-ils.supercat.isbn.holdings_xml.retrieve',
2119 { desc => <<" DESC",
2120 Returns the XML representation of the requested bibliographic record's holdings
2129 { desc => 'The bib record holdings hierarchy in XML',
2137 return '' unless $text;
2138 $text =~ s/&/&/gsom;
2139 $text =~ s/</</gsom;
2140 $text =~ s/>/>/gsom;
2141 $text =~ s/"/"/gsom;
2142 $text =~ s/'/'/gsom;
2146 sub recent_changes {
2149 my $when = shift || '1-01-01';
2152 my $type = 'biblio';
2155 if ($self->api_name =~ /authority/o) {
2156 $type = 'authority';
2160 my $axis = 'create_date';
2161 $axis = 'edit_date' if ($self->api_name =~ /edit/o);
2163 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2165 return $_storage->request(
2166 "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
2167 { $axis => { ">" => $when }, id => { '>' => 0 }, deleted => 'f', active => 't' },
2168 { order_by => { $hint => "$axis desc" }, limit => $limit }
2172 for my $t ( qw/biblio authority/ ) {
2173 for my $a ( qw/import edit/ ) {
2175 __PACKAGE__->register_method(
2176 method => 'recent_changes',
2177 api_name => "open-ils.supercat.$t.record.$a.recent",
2181 { desc => "Returns a list of recently ${a}ed $t records",
2185 desc => "Date to start looking for ${a}ed records",
2186 default => '1-01-01',
2190 desc => "Maximum count to retrieve",
2194 { desc => "An id list of $t records",
2202 sub retrieve_authority_marcxml {
2207 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2209 my $record = $_storage->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rid )->gather(1);
2210 return $U->entityize( $record->marc ) if ($record);
2214 __PACKAGE__->register_method(
2215 method => 'retrieve_authority_marcxml',
2216 api_name => 'open-ils.supercat.authority.marcxml.retrieve',
2220 { desc => <<" DESC",
2221 Returns the MARCXML representation of the requested authority record
2225 { name => 'authorityId',
2226 desc => 'An OpenILS authority::record_entry id',
2230 { desc => 'The authority record in MARCXML',
2235 sub retrieve_record_marcxml {
2240 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2242 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
2243 return $U->entityize( $record->marc ) if ($record);
2247 __PACKAGE__->register_method(
2248 method => 'retrieve_record_marcxml',
2249 api_name => 'open-ils.supercat.record.marcxml.retrieve',
2253 { desc => <<" DESC",
2254 Returns the MARCXML representation of the requested bibliographic record
2259 desc => 'An OpenILS biblio::record_entry id',
2263 { desc => 'The bib record in MARCXML',
2268 sub retrieve_isbn_marcxml {
2273 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2275 my $recs = $_storage->request(
2276 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2277 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2280 return undef unless (@$recs);
2282 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2283 return $U->entityize( $record->marc ) if ($record);
2287 __PACKAGE__->register_method(
2288 method => 'retrieve_isbn_marcxml',
2289 api_name => 'open-ils.supercat.isbn.marcxml.retrieve',
2293 { desc => <<" DESC",
2294 Returns the MARCXML representation of the requested ISBN
2299 desc => 'An ... um ... ISBN',
2303 { desc => 'The bib record in MARCXML',
2308 sub retrieve_record_transform {
2313 (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
2315 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2316 #$_storage->connect;
2318 my $record = $_storage->request(
2319 'open-ils.cstore.direct.biblio.record_entry.retrieve',
2323 return undef unless ($record);
2325 return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2328 sub retrieve_isbn_transform {
2333 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2335 my $recs = $_storage->request(
2336 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2337 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2340 return undef unless (@$recs);
2342 (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
2344 my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2346 return undef unless ($record);
2348 return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2351 sub retrieve_record_objects {
2356 my $type = 'biblio';
2358 if ($self->api_name =~ /authority/) {
2359 $type = 'authority';
2362 $ids = [$ids] unless (ref $ids);
2363 $ids = [grep {$_} @$ids];
2365 return [] unless (@$ids);
2367 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2368 return $_storage->request("open-ils.cstore.direct.$type.record_entry.search.atomic" => { id => [grep {$_} @$ids] })->gather(1);
2370 __PACKAGE__->register_method(
2371 method => 'retrieve_record_objects',
2372 api_name => 'open-ils.supercat.record.object.retrieve',
2376 { desc => <<" DESC",
2377 Returns the Fieldmapper object representation of the requested bibliographic records
2382 desc => 'OpenILS biblio::record_entry ids',
2386 { desc => 'The bib records',
2391 __PACKAGE__->register_method(
2392 method => 'retrieve_record_objects',
2393 api_name => 'open-ils.supercat.authority.object.retrieve',
2397 { desc => <<" DESC",
2398 Returns the Fieldmapper object representation of the requested authority records
2402 { name => 'authIds',
2403 desc => 'OpenILS authority::record_entry ids',
2407 { desc => 'The authority records',
2412 sub retrieve_isbn_object {
2417 return undef unless ($isbn);
2419 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2420 my $recs = $_storage->request(
2421 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2422 { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2425 return undef unless (@$recs);
2427 return $_storage->request(
2428 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
2429 { id => $recs->[0]->record }
2432 __PACKAGE__->register_method(
2433 method => 'retrieve_isbn_object',
2434 api_name => 'open-ils.supercat.isbn.object.retrieve',
2438 { desc => <<" DESC",
2439 Returns the Fieldmapper object representation of the requested bibliographic record
2448 { desc => 'The bib record',
2455 sub retrieve_metarecord_mods {
2460 my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
2462 # Get the metarecord in question
2465 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
2468 # Now get the map of all bib records for the metarecord
2471 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2472 {metarecord => $rid}
2475 $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
2477 # and retrieve the lead (master) record as MODS
2479 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2480 ->run($mr->master_record);
2481 my $master_mods = $_parser->parse_string($master)->documentElement;
2482 $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods" );
2483 $master_mods->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2485 # ... and a MODS clone to populate, with guts removed.
2486 my $mods = $_parser->parse_string($master)->documentElement;
2487 $mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # modsCollection element
2488 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2489 ($mods) = $mods->findnodes('//mods:mods');
2490 #$mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # mods element
2491 $mods->removeChildNodes;
2492 $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2494 # Add the metarecord ID as a (locally defined) info URI
2495 my $recordInfo = $mods
2497 ->createElement("recordInfo");
2499 my $recordIdentifier = $mods
2501 ->createElement("recordIdentifier");
2503 my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2508 $recordIdentifier->appendTextNode(
2509 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
2512 $recordInfo->appendChild($recordIdentifier);
2513 $mods->appendChild($recordInfo);
2515 # Grab the title, author and ISBN for the master record and populate the metarecord
2516 my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
2519 $title->setNamespace( "http://www.loc.gov/mods/", "mods" );
2520 $title->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2521 $title = $mods->ownerDocument->importNode($title);
2522 $mods->appendChild($title);
2525 my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
2527 $author->setNamespace( "http://www.loc.gov/mods/", "mods" );
2528 $author->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2529 $author = $mods->ownerDocument->importNode($author);
2530 $mods->appendChild($author);
2533 my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
2535 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2536 $isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2537 $isbn = $mods->ownerDocument->importNode($isbn);
2538 $mods->appendChild($isbn);
2541 # ... and loop over the constituent records
2542 for my $map ( @$recs ) {
2546 $self ->method_lookup('open-ils.supercat.record.mods.retrieve')
2547 ->run($map->source);
2549 my $part_mods = $_parser->parse_string($rec);
2550 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods" );
2551 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2552 ($part_mods) = $part_mods->findnodes('//mods:mods');
2554 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
2555 $node->setNamespace( "http://www.loc.gov/mods/", "mods" );
2556 $node->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2557 $node = $mods->ownerDocument->importNode($node);
2558 $mods->appendChild( $node );
2561 my $relatedItem = $mods
2563 ->createElement("relatedItem");
2565 $relatedItem->setAttribute( type => 'constituent' );
2567 my $identifier = $mods
2569 ->createElement("identifier");
2571 $identifier->setAttribute( type => 'uri' );
2573 my $subRecordInfo = $mods
2575 ->createElement("recordInfo");
2577 my $subRecordIdentifier = $mods
2579 ->createElement("recordIdentifier");
2581 my $subid = $map->source;
2582 $subRecordIdentifier->appendTextNode(
2583 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
2588 $subRecordInfo->appendChild($subRecordIdentifier);
2590 $relatedItem->appendChild( $subRecordInfo );
2592 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
2593 $tor->setNamespace( "http://www.loc.gov/mods/", "mods" );
2594 $tor->setNamespace( "http://www.loc.gov/mods/", undef, 1 ) if ($tor);
2595 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
2596 $relatedItem->appendChild($tor) if ($tor);
2598 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
2599 $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2600 $part_isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2601 $part_isbn = $mods->ownerDocument->importNode($part_isbn);
2602 $relatedItem->appendChild( $part_isbn );
2605 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
2609 $mods->appendChild( $relatedItem );
2613 $_storage->disconnect;
2615 return $U->entityize($mods->toString);
2618 __PACKAGE__->register_method(
2619 method => 'retrieve_metarecord_mods',
2620 api_name => 'open-ils.supercat.metarecord.mods.retrieve',
2624 { desc => <<" DESC",
2625 Returns the MODS representation of the requested metarecord
2629 { name => 'metarecordId',
2630 desc => 'An OpenILS metabib::metarecord id',
2634 { desc => 'The metarecord in MODS',
2639 sub list_metarecord_formats {
2642 { namespace_uri => 'http://www.loc.gov/mods/',
2643 docs => 'http://www.loc.gov/mods/',
2644 schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
2649 for my $type ( keys %metarecord_xslt ) {
2652 { namespace_uri => $metarecord_xslt{$type}{namespace_uri},
2653 docs => $metarecord_xslt{$type}{docs},
2654 schema_location => $metarecord_xslt{$type}{schema_location},
2661 __PACKAGE__->register_method(
2662 method => 'list_metarecord_formats',
2663 api_name => 'open-ils.supercat.metarecord.formats',
2667 { desc => <<" DESC",
2668 Returns the list of valid metarecord formats that supercat understands.
2671 { desc => 'The format list',
2677 sub list_authority_formats {
2680 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
2681 docs => 'http://www.loc.gov/marcxml/',
2682 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
2687 # for my $type ( keys %record_xslt ) {
2690 # { namespace_uri => $record_xslt{$type}{namespace_uri},
2691 # docs => $record_xslt{$type}{docs},
2692 # schema_location => $record_xslt{$type}{schema_location},
2699 __PACKAGE__->register_method(
2700 method => 'list_authority_formats',
2701 api_name => 'open-ils.supercat.authority.formats',
2705 { desc => <<" DESC",
2706 Returns the list of valid authority formats that supercat understands.
2709 { desc => 'The format list',
2714 sub list_record_formats {
2717 { namespace_uri => 'http://www.loc.gov/MARC21/slim',
2718 docs => 'http://www.loc.gov/marcxml/',
2719 schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
2724 for my $type ( keys %record_xslt ) {
2727 { namespace_uri => $record_xslt{$type}{namespace_uri},
2728 docs => $record_xslt{$type}{docs},
2729 schema_location => $record_xslt{$type}{schema_location},
2736 __PACKAGE__->register_method(
2737 method => 'list_record_formats',
2738 api_name => 'open-ils.supercat.record.formats',
2742 { desc => <<" DESC",
2743 Returns the list of valid record formats that supercat understands.
2746 { desc => 'The format list',
2750 __PACKAGE__->register_method(
2751 method => 'list_record_formats',
2752 api_name => 'open-ils.supercat.isbn.formats',
2756 { desc => <<" DESC",
2757 Returns the list of valid record formats that supercat understands.
2760 { desc => 'The format list',
2773 throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
2774 unless (length($isbn) >= 10);
2776 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2778 # Create a storage session, since we'll be making muliple requests.
2781 # Find the record that has that ISBN.
2782 my $bibrec = $_storage->request(
2783 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2784 { tag => '020', subfield => 'a', value => { like => lc($isbn).'%'} }
2787 # Go away if we don't have one.
2788 return {} unless (@$bibrec);
2790 # Find the metarecord for that bib record.
2791 my $mr = $_storage->request(
2792 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2793 {source => $bibrec->[0]->record}
2796 # Find the other records for that metarecord.
2797 my $records = $_storage->request(
2798 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2799 {metarecord => $mr->[0]->metarecord}
2802 # Just to be safe. There's currently no unique constraint on sources...
2803 my %unique_recs = map { ($_->source, 1) } @$records;
2804 my @rec_list = sort keys %unique_recs;
2806 # And now fetch the ISBNs for thos records.
2810 'open-ils.cstore.direct.metabib.full_rec.search',
2811 { tag => '020', subfield => 'a', record => $_ }
2812 )->gather(1) for (@rec_list);
2814 # We're done with the storage server session.
2815 $_storage->disconnect;
2817 # Return the oISBN data structure. This will be XMLized at a higher layer.
2819 { metarecord => $mr->[0]->metarecord,
2820 record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
2823 __PACKAGE__->register_method(
2825 api_name => 'open-ils.supercat.oisbn',
2829 { desc => <<" DESC",
2830 Returns the ISBN list for the metarecord of the requested isbn
2835 desc => 'An ISBN. Duh.',
2839 { desc => 'record to isbn map',
2844 sub return_bib_search_aliases {
2847 my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2849 my $cmsa = $_storage->request(
2850 'open-ils.cstore.direct.config.metabib_search_alias.search.atomic',
2851 { alias => { '!=' => undef } }
2855 if ($_->alias =~ /\./) {
2856 my ($qualifier, $name) = $_->alias =~ m/^(.+?)\.(.+)$/;
2857 $aliases{$qualifier}{$name}{'index'} = $_->alias;
2858 # We will add a 'title' property in a subsequent schema
2859 $aliases{$qualifier}{$name}{'title'} = $name;
2861 # au/kw/se/su/ti go into the default 'eg' qualifier
2862 $aliases{'eg'}{$_->alias}{'index'} = $_->alias;
2863 $aliases{'eg'}{$_->alias}{'title'} = $_->alias;
2870 __PACKAGE__->register_method(
2871 method => 'return_bib_search_aliases',
2872 api_name => 'open-ils.supercat.biblio.search_aliases',
2876 { desc => <<" DESC",
2877 Returns the set of qualified search aliases in the system
2881 { desc => 'Hash of qualified search aliases',
2887 package OpenILS::Application::SuperCat::unAPI;
2888 use base qw/OpenILS::Application::SuperCat/;
2891 die "dummy superclass, use a real class";
2897 return unless ($obj);
2899 $class = ref($class) || $class;
2901 if ($class eq __PACKAGE__) {
2902 return unless (ref($obj));
2903 $class .= '::' . $obj->json_hint;
2906 return bless { obj => $obj } => $class;
2911 return $self->{obj};
2914 package OpenILS::Application::SuperCat::unAPI::auri;
2915 use base qw/OpenILS::Application::SuperCat::unAPI/;
2921 my $xml = ' <uri xmlns="http://open-ils.org/spec/holdings/v1" ';
2922 $xml .= 'id="tag:open-ils.org:asset-uri/' . $self->obj->id . '" ';
2923 $xml .= 'use_restriction="' . $self->escape( $self->obj->use_restriction ) . '" ';
2924 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
2925 $xml .= 'href="' . $self->escape( $self->obj->href ) . '">';
2927 if (!$args->{no_volumes}) {
2928 if (ref($self->obj->call_number_maps) && @{ $self->obj->call_number_maps }) {
2929 $xml .= " <volumes>\n" . join(
2932 OpenILS::Application::SuperCat::unAPI
2933 ->new( $_->call_number )
2934 ->as_xml({ %$args, no_uris=>1, no_copies=>1 })
2935 } @{ $self->obj->call_number_maps }
2936 ) . " </volumes>\n";
2939 $xml .= " <volumes/>\n";
2943 $xml .= " </uri>\n";
2948 package OpenILS::Application::SuperCat::unAPI::acn;
2949 use base qw/OpenILS::Application::SuperCat::unAPI/;
2955 my $xml = ' <volume xmlns="http://open-ils.org/spec/holdings/v1" ';
2957 $xml .= 'id="tag:open-ils.org:asset-call_number/' . $self->obj->id . '" ';
2958 $xml .= 'lib="' . $self->escape( $self->obj->owning_lib->shortname ) . '" ';
2959 $xml .= 'opac_visible="' . $self->obj->owning_lib->opac_visible . '" ';
2960 $xml .= 'deleted="' . $self->obj->deleted . '" ';
2961 $xml .= 'label="' . $self->escape( $self->obj->label ) . '">';
2964 if (!$args->{no_copies}) {
2965 if (ref($self->obj->copies) && @{ $self->obj->copies }) {
2966 $xml .= " <copies>\n" . join(
2969 OpenILS::Application::SuperCat::unAPI
2971 ->as_xml({ %$args, no_volume=>1 })
2972 } @{ $self->obj->copies }
2976 $xml .= " <copies/>\n";
2980 if (!$args->{no_uris}) {
2981 if (ref($self->obj->uri_maps) && @{ $self->obj->uri_maps }) {
2982 $xml .= " <uris>\n" . join(
2985 OpenILS::Application::SuperCat::unAPI
2987 ->as_xml({ %$args, no_volumes=>1 })
2988 } @{ $self->obj->uri_maps }
2992 $xml .= " <uris/>\n";
2997 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
2998 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
2999 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3000 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3003 unless ($args->{no_record}) {
3004 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3006 my $r_doc = $parser->parse_string($self->obj->record->marc);
3007 $r_doc->documentElement->setAttribute( id => $rec_tag );
3008 $xml .= $U->entityize($r_doc->documentElement->toString);
3011 $xml .= " </volume>\n";
3016 package OpenILS::Application::SuperCat::unAPI::ssub;
3017 use base qw/OpenILS::Application::SuperCat::unAPI/;
3023 my $xml = ' <subscription xmlns="http://open-ils.org/spec/holdings/v1" ';
3025 $xml .= 'id="tag:open-ils.org:serial-subscription/' . $self->obj->id . '" ';
3026 $xml .= 'start="' . $self->escape( $self->obj->start_date ) . '" ';
3027 $xml .= 'end="' . $self->escape( $self->obj->end_date ) . '" ';
3028 $xml .= 'expected_date_offset="' . $self->escape( $self->obj->expected_date_offset ) . '">';
3031 if (!$args->{no_distributions}) {
3032 if (ref($self->obj->distributions) && @{ $self->obj->distributions }) {
3033 $xml .= " <distributions>\n" . join(
3036 OpenILS::Application::SuperCat::unAPI
3038 ->as_xml({ %$args, no_subscription=>1, no_issuance=>1 })
3039 } @{ $self->obj->distributions }
3040 ) . " </distributions>\n";
3043 $xml .= " <distributions/>\n";
3047 if (!$args->{no_captions_and_patterns}) {
3048 if (ref($self->obj->scaps) && @{ $self->obj->scaps }) {
3049 $xml .= " <captions_and_patterns>\n" . join(
3052 OpenILS::Application::SuperCat::unAPI
3054 ->as_xml({ %$args, no_subscription=>1 })
3055 } @{ $self->obj->scaps }
3056 ) . " </captions_and_patterns>\n";
3059 $xml .= " <captions_and_patterns/>\n";
3063 if (!$args->{no_issuances}) {
3064 if (ref($self->obj->issuances) && @{ $self->obj->issuances }) {
3065 $xml .= " <issuances>\n" . join(
3068 OpenILS::Application::SuperCat::unAPI
3070 ->as_xml({ %$args, no_subscription=>1, no_items=>1 })
3071 } @{ $self->obj->issuances }
3072 ) . " </issuances>\n";
3075 $xml .= " <issuances/>\n";
3080 $xml .= ' <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3081 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3082 $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3083 $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3086 unless ($args->{no_record}) {
3087 my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3089 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3090 $r_doc->documentElement->setAttribute( id => $rec_tag );
3091 $xml .= $U->entityize($r_doc->documentElement->toString);
3094 $xml .= " </subscription>\n";
3099 package OpenILS::Application::SuperCat::unAPI::ssum_base;
3100 use base qw/OpenILS::Application::SuperCat::unAPI/;
3106 (my $type = ref($self)) =~ s/^.+([^:]+)$/$1/;
3108 my $xml = " <serial_summary xmlns=\"http://open-ils.org/spec/holdings/v1\" type=\"$type\" ";
3110 $xml .= "id=\"tag:open-ils.org:serial-summary-$type/" . $self->obj->id . '" ';
3111 $xml .= 'generated_coverage="' . $self->escape( $self->obj->generated_coverage ) . '" ';
3112 $xml .= 'show_generated="' . $self->escape( $self->obj->show_generated ) . '" ';
3113 $xml .= 'textual_holdings="' . $self->escape( $self->obj->textual_holdings ) . '">';
3116 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_summaries=>1 }) if (!$args->{no_distribution});
3118 $xml .= " </serial_summary>\n";
3124 package OpenILS::Application::SuperCat::unAPI::sssum;
3125 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3127 package OpenILS::Application::SuperCat::unAPI::sbsum;
3128 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3130 package OpenILS::Application::SuperCat::unAPI::sisum;
3131 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3133 package OpenILS::Application::SuperCat::unAPI::sdist;
3134 use base qw/OpenILS::Application::SuperCat::unAPI/;
3140 my $xml = ' <distribution xmlns="http://open-ils.org/spec/holdings/v1" ';
3142 $xml .= 'id="tag:open-ils.org:serial-distribution/' . $self->obj->id . '" ';
3143 $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3144 $xml .= 'unit_label_prefix="' . $self->escape( $self->obj->unit_label_prefix ) . '" ';
3145 $xml .= 'unit_label_suffix="' . $self->escape( $self->obj->unit_label_suffix ) . '">';
3148 if (!$args->{no_distributions}) {
3149 if (ref($self->obj->streams) && @{ $self->obj->streams }) {
3150 $xml .= " <streams>\n" . join(
3153 OpenILS::Application::SuperCat::unAPI
3155 ->as_xml({ %$args, no_distribution=>1 })
3156 } @{ $self->obj->streams }
3157 ) . " </streams>\n";
3160 $xml .= " <streams/>\n";
3164 if (!$args->{no_summaries}) {
3165 $xml .= " <summaries>\n";
3169 OpenILS::Application::SuperCat::unAPI
3171 ->as_xml({ %$args, no_distribution=>1 }) : ""
3172 } ($self->obj->basic_summary, $self->obj->supplement_summary, $self->obj->index_summary)
3175 $xml .= " </summaries>\n";
3179 $xml .= ' <holding_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3180 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->holding_lib->id . '" ';
3181 $xml .= 'shortname="'.$self->escape( $self->obj->holding_lib->shortname ) .'" ';
3182 $xml .= 'name="'.$self->escape( $self->obj->holding_lib->name ) .'"/>';
3185 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_distributions=>1 }) if (!$args->{no_subscription});
3187 if (!$args->{no_record} && $self->obj->record_entry) {
3188 my $rec_tag = "tag:open-ils.org:serial-record_entry/".$self->obj->record_entry->id ;
3190 my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3191 $r_doc->documentElement->setAttribute( id => $rec_tag );
3192 $xml .= $U->entityize($r_doc->documentElement->toString);
3195 $xml .= " </distribution>\n";
3200 package OpenILS::Application::SuperCat::unAPI::sstr;
3201 use base qw/OpenILS::Application::SuperCat::unAPI/;
3207 my $xml = ' <stream xmlns="http://open-ils.org/spec/holdings/v1" ';
3209 $xml .= 'id="tag:open-ils.org:serial-stream/' . $self->obj->id . '" ';
3210 $xml .= 'routing_label="' . $self->escape( $self->obj->routing_label ) . '">';
3213 if (!$args->{no_items}) {
3214 if (ref($self->obj->items) && @{ $self->obj->items }) {
3215 $xml .= " <items>\n" . join(
3218 OpenILS::Application::SuperCat::unAPI
3220 ->as_xml({ %$args, no_stream=>1 })
3221 } @{ $self->obj->items }
3225 $xml .= " <items/>\n";
3229 #XXX routing_list_user's?
3231 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_streams=>1 }) if (!$args->{no_distribution});
3233 $xml .= " </stream>\n";
3238 package OpenILS::Application::SuperCat::unAPI::sitem;
3239 use base qw/OpenILS::Application::SuperCat::unAPI/;
3245 my $xml = ' <serial_item xmlns="http://open-ils.org/spec/holdings/v1" ';
3247 $xml .= 'id="tag:open-ils.org:serial-item/' . $self->obj->id . '" ';
3248 $xml .= 'date_expected="' . $self->escape( $self->obj->date_expected ) . '"';
3249 $xml .= ' date_received="' . $self->escape( $self->obj->date_received ) .'"'if ($self->obj->date_received);
3251 if ($args->{no_issuance}) {
3252 my $siss = ref($self->obj->issuance) ? $self->obj->issuance->id : $self->obj->issuance;
3253 $xml .= ' issuance="tag:open-ils.org:serial-issuance/' . $siss . '"';
3258 if (ref($self->obj->notes) && $self->obj->notes) {
3259 $xml .= " <notes>\n";
3260 for my $note ( @{$self->obj->notes} ) {
3261 next unless ( $note->pub eq 't' );
3262 $xml .= sprintf(' <note date="%s" title="%s">%s</note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3265 $xml .= " </notes>\n";
3267 $xml .= " <notes/>\n";
3270 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->issuance )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_issuance});
3271 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->stream )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_stream});
3272 $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});
3273 $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});
3275 $xml .= " </serial_item>\n";
3280 package OpenILS::Application::SuperCat::unAPI::sunit;
3281 use base qw/OpenILS::Application::SuperCat::unAPI/;
3287 my $xml = ' <serial_unit xmlns="http://open-ils.org/spec/holdings/v1" '.
3288 'id="tag:open-ils.org:serial-unit/' . $self->obj->id . '" ';
3290 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3291 create_date edit_date copy_number circulate deposit ref holdable deleted
3292 deposit_amount price barcode circ_modifier circ_as_type opac_visible cost
3293 status_changed_time floating mint_condition detailed_contents sort_key summary_contents
3298 $xml .= ' <status ident="' . $self->obj->status->id . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3299 $xml .= ' <location ident="' . $self->obj->location->id . '">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3300 $xml .= ' <circlib ident="' . $self->obj->circ_lib->id . '">' . $self->escape( $self->obj->circ_lib->name ) . "</circlib>\n";
3302 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3303 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3304 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3305 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'"/>';
3308 $xml .= " <copy_notes>\n";
3309 if (ref($self->obj->notes) && $self->obj->notes) {
3310 for my $note ( @{$self->obj->notes} ) {
3311 next unless ( $note->pub eq 't' );
3312 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3317 $xml .= " </copy_notes>\n";
3318 $xml .= " <statcats>\n";
3320 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3321 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3322 next unless ( $sce->stat_cat->opac_visible eq 't' );
3323 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3327 $xml .= " </statcats>\n";
3329 unless ($args->{no_volume}) {
3330 if (ref($self->obj->call_number)) {
3331 $xml .= OpenILS::Application::SuperCat::unAPI
3332 ->new( $self->obj->call_number )
3333 ->as_xml({ %$args, no_copies=>1 });
3335 $xml .= " <volume/>\n";
3339 $xml .= " </serial_unit>\n";
3344 package OpenILS::Application::SuperCat::unAPI::scap;
3345 use base qw/OpenILS::Application::SuperCat::unAPI/;
3351 my $xml = ' <caption_and_pattern xmlns="http://open-ils.org/spec/holdings/v1" '.
3352 'id="tag:open-ils.org:serial-caption_and_pattern/' . $self->obj->id . '" ';
3354 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3355 create_date type active pattern_code enum_1 enum_2 enum_3 enum_4
3356 enum_5 enum_6 chron_1 chron_2 chron_3 chron_4 chron_5 start_date end_date
3359 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_captions_and_patterns=>1 }) if (!$args->{no_subscription});
3360 $xml .= " </caption_and_pattern>\n";
3365 package OpenILS::Application::SuperCat::unAPI::siss;
3366 use base qw/OpenILS::Application::SuperCat::unAPI/;
3372 my $xml = ' <issuance xmlns="http://open-ils.org/spec/holdings/v1" '.
3373 'id="tag:open-ils.org:serial-issuance/' . $self->obj->id . '" ';
3375 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" '
3376 for (qw/create_date edit_date label date_published holding_code holding_type holding_link_id/);
3380 if (!$args->{no_items}) {
3381 if (ref($self->obj->items) && @{ $self->obj->items }) {
3382 $xml .= " <items>\n" . join(
3385 OpenILS::Application::SuperCat::unAPI
3387 ->as_xml({ %$args, no_stream=>1 })
3388 } @{ $self->obj->items }
3392 $xml .= " <items/>\n";
3396 $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_issuances=>1 }) if (!$args->{no_subscription});
3397 $xml .= " </issuance>\n";
3402 package OpenILS::Application::SuperCat::unAPI::acp;
3403 use base qw/OpenILS::Application::SuperCat::unAPI/;
3409 my $xml = ' <copy xmlns="http://open-ils.org/spec/holdings/v1" '.
3410 'id="tag:open-ils.org:asset-copy/' . $self->obj->id . '" ';
3412 $xml .= $_ . '="' . $self->escape( $self->obj->$_ ) . '" ' for (qw/
3413 create_date edit_date copy_number circulate deposit ref holdable deleted
3414 deposit_amount price barcode circ_modifier circ_as_type opac_visible
3419 $xml .= ' <status ident="' . $self->obj->status->id . '">' . $self->escape( $self->obj->status->name ) . "</status>\n";
3420 $xml .= ' <location ident="' . $self->obj->location->id . '" opac_visible="'.$self->obj->location->opac_visible.'">' . $self->escape( $self->obj->location->name ) . "</location>\n";
3421 $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";
3423 $xml .= ' <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3424 $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3425 $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3426 $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'" opac_visible="'.$self->obj->circ_lib->opac_visible.'"/>';
3429 $xml .= " <copy_notes>\n";
3430 if (ref($self->obj->notes) && $self->obj->notes) {
3431 for my $note ( @{$self->obj->notes} ) {
3432 next unless ( $note->pub eq 't' );
3433 $xml .= sprintf(' <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3438 $xml .= " </copy_notes>\n";
3439 $xml .= " <statcats>\n";
3441 if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3442 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3443 next unless ( $sce->stat_cat->opac_visible eq 't' );
3444 $xml .= sprintf(' <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3448 $xml .= " </statcats>\n";
3450 unless ($args->{no_volume}) {
3451 if (ref($self->obj->call_number)) {
3452 $xml .= OpenILS::Application::SuperCat::unAPI
3453 ->new( $self->obj->call_number )
3454 ->as_xml({ %$args, no_copies=>1 });
3456 $xml .= " <volume/>\n";
3460 $xml .= " </copy>\n";