1 package OpenILS::WWW::SuperCat;
2 use strict; use warnings;
5 use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND :log);
6 use APR::Const -compile => qw(:error SUCCESS);
7 use Apache2::RequestRec ();
8 use Apache2::RequestIO ();
9 use Apache2::RequestUtil;
15 use OpenSRF::EX qw(:try);
16 use OpenSRF::Utils qw/:datetime/;
17 use OpenSRF::Utils::Cache;
19 use OpenSRF::AppSession;
24 use Unicode::Normalize;
25 use OpenILS::Utils::Fieldmapper;
26 use OpenILS::WWW::SuperCat::Feed;
27 use OpenSRF::Utils::Logger qw/$logger/;
28 use OpenILS::Application::AppUtils;
33 my $log = 'OpenSRF::Utils::Logger';
34 my $U = 'OpenILS::Application::AppUtils';
36 # set the bootstrap config when this module is loaded
37 my ($bootstrap, $supercat, $actor, $parser, $search, $xslt, $cn_browse_xslt, %browse_types);
39 $browse_types{call_number}{xml} = sub {
42 my $year = (gmtime())[5] + 1900;
45 $content .= "<volumes xmlns='http://open-ils.org/spec/holdings/v1'>\n";
48 (my $cn_class = $cn->class_name) =~ s/::/-/gso;
49 $cn_class =~ s/Fieldmapper-//gso;
51 my $cn_tag = "tag:open-ils.org,$year:$cn_class/".$cn->id;
52 my $cn_lib = $cn->owning_lib->shortname;
53 my $cn_label = $cn->label;
55 $cn_label =~ s/\n//gos;
56 $cn_label =~ s/'/'/go;
58 (my $ou_class = $cn->owning_lib->class_name) =~ s/::/-/gso;
59 $ou_class =~ s/Fieldmapper-//gso;
61 my $ou_tag = "tag:open-ils.org,$year:$ou_class/".$cn->owning_lib->id;
62 my $ou_name = $cn->owning_lib->name;
64 $ou_name =~ s/\n//gos;
65 $ou_name =~ s/'/'/go;
67 (my $rec_class = $cn->record->class_name) =~ s/::/-/gso;
68 $rec_class =~ s/Fieldmapper-//gso;
70 my $rec_tag = "tag:open-ils.org,$year:$rec_class/".$cn->record->id.'/'.$cn->owning_lib->shortname;
72 $content .= "<volume id='$cn_tag' lib='$cn_lib' label='$cn_label'>\n";
73 $content .= "<owning_lib xmlns='http://open-ils.org/spec/actors/v1' id='$ou_tag' name='$ou_name'/>\n";
75 my $r_doc = $parser->parse_string($cn->record->marc);
76 $r_doc->documentElement->setAttribute( id => $rec_tag );
77 $content .= $U->entityize($r_doc->documentElement->toString);
79 $content .= "</volume>\n";
82 $content .= "</volumes>\n";
83 return ("Content-type: application/xml\n\n",$content);
87 $browse_types{call_number}{html} = sub {
92 if (!$cn_browse_xslt) {
93 $cn_browse_xslt = $parser->parse_file(
94 OpenSRF::Utils::SettingsClient
96 ->config_value( dirs => 'xsl' ).
99 $cn_browse_xslt = $xslt->parse_stylesheet( $cn_browse_xslt );
102 my (undef,$xml) = $browse_types{call_number}{xml}->($tree);
105 "Content-type: text/html\n\n",
107 $cn_browse_xslt->transform(
108 $parser->parse_string( $xml ),
123 OpenSRF::System->bootstrap_client( config_file => $bootstrap );
125 my $idl = OpenSRF::Utils::SettingsClient->new->config_value("IDL");
126 Fieldmapper->import(IDL => $idl);
128 $supercat = OpenSRF::AppSession->create('open-ils.supercat');
129 $actor = OpenSRF::AppSession->create('open-ils.actor');
130 $search = OpenSRF::AppSession->create('open-ils.search');
131 $parser = new XML::LibXML;
132 $xslt = new XML::LibXSLT;
134 $cn_browse_xslt = $parser->parse_file(
135 OpenSRF::Utils::SettingsClient
137 ->config_value( dirs => 'xsl' ).
141 $cn_browse_xslt = $xslt->parse_stylesheet( $cn_browse_xslt );
144 ->request("open-ils.supercat.record.formats")
147 $list = [ map { (keys %$_)[0] } @$list ];
148 push @$list, 'htmlholdings','html', 'marctxt', 'ris';
150 for my $browse_axis ( qw/title author subject topic series item-age/ ) {
151 for my $record_browse_format ( @$list ) {
153 my $__f = $record_browse_format;
154 my $__a = $browse_axis;
156 $browse_types{$__a}{$__f} = sub {
157 my $record_list = shift;
160 my $real_format = shift || $__f;
165 $log->info("Creating record feed with params [$real_format, $record_list, $unapi, $site]");
166 my $feed = create_record_feed( 'record', $real_format, $record_list, $unapi, $site, $real_format =~ /(-full|-uris)$/o ? 1 : 0 );
167 $feed->root( "$base/../" );
169 $feed->link( next => $next => $feed->type );
170 $feed->link( previous => $prev => $feed->type );
173 "Content-type: ". $feed->type ."; charset=utf-8\n\n",
182 =head2 parse_feed_type($type)
184 Determines whether and how a given feed type needs to be "fleshed out"
185 with holdings information.
187 The feed type could end with the string "-full", in which case we want
188 to return call numbers, copies, and URIS.
190 Or the feed type could be "-uris", in which case we want to return
191 call numbers and URIS.
193 Otherwise, we won't return any holdings.
197 sub parse_feed_type {
200 if ($type =~ /-full$/o) {
204 if ($type =~ /-uris$/o) {
208 # Otherwise, we'll return just the facts, ma'am
212 =head2 supercat_format($format_hashref, $format_type)
214 Given a reference to a hash containing the namespace_uri,
215 docs, and schema location attributes for a set of formats,
216 generate the XML description required by the supercat service.
218 We derive the base type from the format type so that we do not
219 have to populate the hash with redundant information.
223 sub supercat_format {
227 (my $base_type = $type) =~ s/(-full|-uris)$//o;
229 my $format = "<format><name>$type</name><type>application/xml</type>";
231 for my $part ( qw/namespace_uri docs schema_location/ ) {
232 $format .= "<$part>$$h{$base_type}{$part}</$part>"
233 if ($$h{$base_type}{$part});
236 $format .= '</format>';
241 =head2 unapi_format($format_hashref, $format_type)
243 Given a reference to a hash containing the namespace_uri,
244 docs, and schema location attributes for a set of formats,
245 generate the XML description required by the supercat service.
247 We derive the base type from the format type so that we do not
248 have to populate the hash with redundant information.
256 (my $base_type = $type) =~ s/(-full|-uris)$//o;
258 my $format = "<format name='$type' type='application/xml'";
260 for my $part ( qw/namespace_uri docs schema_location/ ) {
261 $format .= " $part='$$h{$base_type}{$part}'"
262 if ($$h{$base_type}{$part});
274 return Apache2::Const::DECLINED if (-e $apache->filename);
276 (my $isbn = $apache->path_info) =~ s{^.*?([^/]+)$}{$1}o;
279 ->request("open-ils.supercat.oisbn", $isbn)
282 print "Content-type: application/xml; charset=utf-8\n\n";
283 print "<?xml version='1.0' encoding='UTF-8' ?>\n";
285 unless (exists $$list{metarecord}) {
287 return Apache2::Const::OK;
290 print "<idlist metarecord='$$list{metarecord}'>\n";
292 for ( keys %{ $$list{record_list} } ) {
293 (my $o = $$list{record_list}{$_}) =~s/^(\S+).*?$/$1/o;
294 print " <isbn record='$_'>$o</isbn>\n"
299 return Apache2::Const::OK;
305 return Apache2::Const::DECLINED if (-e $apache->filename);
310 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
311 my $rel_name = $cgi->url(-relative=>1);
312 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
315 my $url = $cgi->url(-path_info=>$add_path);
316 my $root = (split 'unapi', $url)[0];
317 my $base = (split 'unapi', $url)[0] . 'unapi';
320 my $uri = $cgi->param('id') || '';
321 my $host = $cgi->virtual_host || $cgi->server_name;
323 my $skin = $cgi->param('skin') || 'default';
324 my $locale = $cgi->param('locale') || 'en-US';
326 my $format = $cgi->param('format');
327 my $flesh_feed = parse_feed_type($format);
328 (my $base_format = $format) =~ s/(-full|-uris)$//o;
329 my ($id,$type,$command,$lib,$paging) = ('','','');
332 my $body = "Content-type: application/xml; charset=utf-8\n\n";
334 if ($uri =~ m{^tag:[^:]+:([^\/]+)/(\d+)(?:\[([0-9,]+)\])?(?:/(.+))?}o) {
339 $type = 'metarecord' if ($1 =~ /^m/o);
342 ->request("open-ils.supercat.$type.formats")
345 if ($type eq 'record' or $type eq 'isbn') {
346 $body .= <<" FORMATS";
348 <format name='opac' type='text/html'/>
349 <format name='html' type='text/html'/>
350 <format name='htmlholdings' type='text/html'/>
351 <format name='holdings_xml' type='application/xml'/>
352 <format name='holdings_xml-full' type='application/xml'/>
353 <format name='html-full' type='text/html'/>
354 <format name='htmlholdings-full' type='text/html'/>
355 <format name='marctxt' type='text/plain'/>
356 <format name='ris' type='text/plain'/>
358 } elsif ($type eq 'metarecord') {
359 $body .= <<" FORMATS";
361 <format name='opac' type='text/html'/>
366 my ($type) = keys %$h;
367 $body .= unapi_format($h, $type);
369 if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
370 $body .= unapi_format($h, "$type-full");
371 $body .= unapi_format($h, "$type-uris");
375 $body .= "</formats>\n";
379 ->request("open-ils.supercat.record.formats")
384 ->request("open-ils.supercat.metarecord.formats")
388 my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
389 $list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
391 $body .= <<" FORMATS";
393 <format name='opac' type='text/html'/>
394 <format name='html' type='text/html'/>
395 <format name='htmlholdings' type='text/html'/>
396 <format name='holdings_xml' type='application/xml'/>
397 <format name='holdings_xml-full' type='application/xml'/>
398 <format name='html-full' type='text/html'/>
399 <format name='htmlholdings-full' type='text/html'/>
400 <format name='marctxt' type='text/plain'/>
401 <format name='ris' type='text/plain'/>
406 my ($type) = keys %$h;
407 $body .= "\t" . unapi_format($h, $type);
409 if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
410 $body .= "\t" . unapi_format($h, "$type-full");
411 $body .= "\t" . unapi_format($h, "$type-uris");
415 $body .= "</formats>\n";
419 return Apache2::Const::OK;
422 if ($uri =~ m{^tag:[^:]+:([^\/]+)/(\d+)(?:\[([0-9,]+)\])?(?:/(.+))?}o) {
427 $type = 'metarecord' if ($1 =~ /^metabib/o);
428 $type = 'isbn' if ($1 =~ /^isbn/o);
429 $type = 'call_number' if ($1 =~ /^call_number/o);
430 $type = 'acp' if ($1 =~ /^asset-copy/o);
431 $type = 'acn' if ($1 =~ /^asset-call_number/o);
432 $type = 'auri' if ($1 =~ /^asset-uri/o);
433 $command = 'retrieve';
434 $command = 'browse' if ($type eq 'call_number');
438 $paging = [split ',', $paging];
443 if (!$lib || $lib eq '-') {
444 $lib = $actor->request(
445 'open-ils.actor.org_unit_list.search' => parent_ou => undef
446 )->gather(1)->[0]->shortname;
449 my $lib_object = $actor->request(
450 'open-ils.actor.org_unit_list.search' => shortname => $lib
452 my $lib_id = $lib_object->id;
454 my $ou_types = $actor->request( 'open-ils.actor.org_types.retrieve' )->gather(1);
455 my $lib_depth = (grep { $_->id == $lib_object->ou_type } @$ou_types)[0]->depth;
457 if ($type eq 'call_number' and $command eq 'browse') {
458 print "Location: $root/browse/$base_format/call_number/$lib/$id\n\n";
462 if ($type eq 'isbn') {
463 my $rec = $supercat->request('open-ils.supercat.isbn.object.retrieve',$id)->gather(1);
465 print "Content-type: text/html; charset=utf-8\n\n";
466 $apache->custom_response( 404, <<" HTML");
469 <title>Type [$type] with id [$id] not found!</title>
473 <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
484 { (keys(%$_))[0] eq $base_format }
485 @{ $supercat->request("open-ils.supercat.$type.formats")->gather(1) }
487 { $_ eq $base_format }
488 qw/opac html htmlholdings marctxt ris holdings_xml/
490 print "Content-type: text/html; charset=utf-8\n\n";
491 $apache->custom_response( 406, <<" HTML");
494 <title>Invalid format [$format] for type [$type]!</title>
498 <center>Sorry, format $format is not valid for type $type.</center>
505 if ($format eq 'opac') {
506 print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id&l=$lib_id&d=$lib_depth\n\n"
507 if ($type eq 'metarecord');
508 print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id&l=$lib_id&d=$lib_depth\n\n"
509 if ($type eq 'record');
511 } elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format) && ($type ne 'acn' && $type ne 'acp' && $type ne 'auri')) {
512 my $feed = create_record_feed(
522 print "Content-type: text/html; charset=utf-8\n\n";
523 $apache->custom_response( 404, <<" HTML");
526 <title>Type [$type] with id [$id] not found!</title>
530 <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
538 $feed->creator($host);
540 $feed->link( unapi => $base) if ($flesh_feed);
542 print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
543 print $U->entityize($feed->toString) . "\n";
545 return Apache2::Const::OK;
548 my $method = "open-ils.supercat.$type.$base_format.$command";
550 push @params, $lib, $flesh_feed, $paging if ($base_format eq 'holdings_xml');
552 my $req = $supercat->request($method,@params);
553 my $data = $req->gather();
555 if ($req->failed || !$data) {
556 print "Content-type: text/html; charset=utf-8\n\n";
557 $apache->custom_response( 404, <<" HTML");
560 <title>$type $id not found!</title>
564 <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
571 print "Content-type: application/xml; charset=utf-8\n\n$data";
573 if ($base_format eq 'holdings_xml') {
574 while (my $c = $req->recv) {
579 return Apache2::Const::OK;
585 return Apache2::Const::DECLINED if (-e $apache->filename);
590 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
591 my $rel_name = $cgi->url(-relative=>1);
592 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
595 my $url = $cgi->url(-path_info=>$add_path);
596 my $root = (split 'supercat', $url)[0];
597 my $base = (split 'supercat', $url)[0] . 'supercat';
598 my $unapi = (split 'supercat', $url)[0] . 'unapi';
600 my $host = $cgi->virtual_host || $cgi->server_name;
602 my $path = $cgi->path_info;
603 my ($id,$type,$format,$command) = reverse split '/', $path;
604 my $flesh_feed = parse_feed_type($type);
605 (my $base_format = $format) =~ s/(-full|-uris)$//o;
607 my $skin = $cgi->param('skin') || 'default';
608 my $locale = $cgi->param('locale') || 'en-US';
610 if ( $path =~ m{^/formats(?:/([^\/]+))?$}o ) {
611 print "Content-type: application/xml; charset=utf-8\n";
614 ->request("open-ils.supercat.$1.formats")
622 <type>text/html</type>
625 if ($1 eq 'record' or $1 eq 'isbn') {
627 <name>htmlholdings</name>
628 <type>text/html</type>
632 <type>text/html</type>
635 <name>htmlholdings-full</name>
636 <type>text/html</type>
639 <name>html-full</name>
640 <type>text/html</type>
644 <type>text/plain</type>
648 <type>text/plain</type>
653 my ($type) = keys %$h;
654 print supercat_format($h, $type);
656 if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
657 print supercat_format($h, "$type-full");
658 print supercat_format($h, "$type-uris");
663 print "</formats>\n";
665 return Apache2::Const::OK;
669 ->request("open-ils.supercat.record.formats")
674 ->request("open-ils.supercat.metarecord.formats")
678 my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
679 $list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
684 <type>text/html</type>
687 <name>htmlholdings</name>
688 <type>text/html</type>
692 <type>text/html</type>
695 <name>htmlholdings-full</name>
696 <type>text/html</type>
699 <name>html-full</name>
700 <type>text/html</type>
704 <type>text/plain</type>
708 <type>text/plain</type>
712 my ($type) = keys %$h;
713 print supercat_format($h, $type);
715 if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
716 print supercat_format($h, "$type-full");
717 print supercat_format($h, "$type-uris");
722 print "</formats>\n";
725 return Apache2::Const::OK;
728 if ($format eq 'opac') {
729 print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id\n\n"
730 if ($type eq 'metarecord');
731 print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id\n\n"
732 if ($type eq 'record');
735 } elsif ($base_format eq 'marc21') {
739 my $bib = $supercat->request( "open-ils.supercat.record.object.retrieve", $id )->gather(1)->[0];
741 my $r = MARC::Record->new_from_xml( $bib->marc, 'UTF-8', 'USMARC' );
742 $r->delete_field( $_ ) for ($r->field(901));
747 a => $bib->tcn_value,
748 b => $bib->tcn_source,
753 print "Content-type: application/octet-stream\n\n";
759 print "Content-type: text/html; charset=utf-8\n\n";
760 $apache->custom_response( 404, <<" HTML");
767 <center>Couldn't fetch $id as MARC21.</center>
774 return Apache2::Const::OK;
776 } elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format)) {
777 my $feed = create_record_feed(
785 $feed->creator($host);
789 $feed->link( unapi => $base) if ($flesh_feed);
791 print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
792 print $U->entityize($feed->toString) . "\n";
794 return Apache2::Const::OK;
797 my $req = $supercat->request("open-ils.supercat.$type.$format.$command",$id);
801 print "Content-type: text/html; charset=utf-8\n\n";
802 $apache->custom_response( 404, <<" HTML");
805 <title>$type $id not found!</title>
809 <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
816 print "Content-type: application/xml; charset=utf-8\n\n";
817 print $U->entityize( $parser->parse_string( $req->gather(1) )->documentElement->toString );
819 return Apache2::Const::OK;
825 return Apache2::Const::DECLINED if (-e $apache->filename);
829 my $year = (gmtime())[5] + 1900;
830 my $host = $cgi->virtual_host || $cgi->server_name;
833 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
834 my $rel_name = $cgi->url(-relative=>1);
835 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
838 my $url = $cgi->url(-path_info=>$add_path);
839 my $root = (split 'feed', $url)[0] . '/';
840 my $base = (split 'bookbag', $url)[0] . '/bookbag';
841 my $unapi = (split 'feed', $url)[0] . '/unapi';
843 my $skin = $cgi->param('skin') || 'default';
844 my $locale = $cgi->param('locale') || 'en-US';
845 my $org = $cgi->param('searchOrg');
847 my $org_unit = get_ou($org);
848 my $scope = "l=" . $org_unit->[0]->id . "&";
850 $root =~ s{(?<!http:)//}{/}go;
851 $base =~ s{(?<!http:)//}{/}go;
852 $unapi =~ s{(?<!http:)//}{/}go;
854 my $path = $cgi->path_info;
855 #warn "URL breakdown: $url -> $root -> $base -> $path -> $unapi";
857 my ($id,$type) = reverse split '/', $path;
858 my $flesh_feed = parse_feed_type($type);
860 my $bucket = $actor->request("open-ils.actor.container.public.flesh", 'biblio', $id)->gather(1);
861 return Apache2::Const::NOT_FOUND unless($bucket);
863 my $bucket_tag = "tag:$host,$year:record_bucket/$id";
864 if ($type eq 'opac') {
865 print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
866 join('&', map { "rl=" . $_->target_biblio_record_entry } @{ $bucket->items }) .
871 my $feed = create_record_feed(
874 [ map { $_->target_biblio_record_entry } @{ $bucket->items } ],
876 $org_unit->[0]->shortname,
880 $feed->id($bucket_tag);
882 $feed->title("Items in Book Bag [".$bucket->name."]");
883 $feed->creator($host);
886 $feed->link(alternate => $base . "/rss2-full/$id" => 'application/rss+xml');
887 $feed->link(atom => $base . "/atom-full/$id" => 'application/atom+xml');
888 $feed->link(html => $base . "/html-full/$id" => 'text/html');
889 $feed->link(unapi => $unapi);
893 "http://$host/opac/$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
894 join('&', map { 'rl=' . $_->target_biblio_record_entry } @{$bucket->items} ),
899 print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
900 print $U->entityize($feed->toString) . "\n";
902 return Apache2::Const::OK;
907 return Apache2::Const::DECLINED if (-e $apache->filename);
911 my $year = (gmtime())[5] + 1900;
912 my $host = $cgi->virtual_host || $cgi->server_name;
915 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
916 my $rel_name = $cgi->url(-relative=>1);
917 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
920 my $url = $cgi->url(-path_info=>$add_path);
921 my $root = (split 'feed', $url)[0];
922 my $base = (split 'freshmeat', $url)[0] . '/freshmeat';
923 my $unapi = (split 'feed', $url)[0] . 'unapi';
925 my $skin = $cgi->param('skin') || 'default';
926 my $locale = $cgi->param('locale') || 'en-US';
927 my $org = $cgi->param('searchOrg');
929 my $org_unit = get_ou($org);
930 my $scope = "l=" . $org_unit->[0]->id . "&";
932 my $path = $cgi->path_info;
933 #warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
935 $path =~ s/^\/(?:feed\/)?freshmeat\///og;
937 my ($type,$rtype,$axis,$limit,$date) = split '/', $path;
938 my $flesh_feed = parse_feed_type($type);
942 my $list = $supercat->request("open-ils.supercat.$rtype.record.$axis.recent", $date, $limit)->gather(1);
944 #if ($type eq 'opac') {
945 # print "Location: $root/../../en-US/skin/default/xml/rresult.xml?rt=list&" .
946 # join('&', map { "rl=" . $_ } @$list) .
951 my $feed = create_record_feed( 'record', $type, $list, $unapi, $org_unit->[0]->shortname, $flesh_feed);
955 $feed->title("Up to $limit recent $rtype ${axis}s from $date forward");
957 $feed->title("$limit most recent $rtype ${axis}s");
960 $feed->creator($host);
963 $feed->link(alternate => $base . "/rss2-full/$rtype/$axis/$limit/$date" => 'application/rss+xml');
964 $feed->link(atom => $base . "/atom-full/$rtype/$axis/$limit/$date" => 'application/atom+xml');
965 $feed->link(html => $base . "/html-full/$rtype/$axis/$limit/$date" => 'text/html');
966 $feed->link(unapi => $unapi);
970 "http://$host/opac/$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
971 join('&', map { 'rl=' . $_} @$list ),
976 print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
977 print $U->entityize($feed->toString) . "\n";
979 return Apache2::Const::OK;
988 if ($version eq '1.0') {
990 Content-type: application/opensearchdescription+xml; charset=utf-8
992 <?xml version="1.0" encoding="UTF-8"?>
993 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearchdescription/1.0/">
994 <Url>$base/1.0/$lib/-/$class/?searchTerms={searchTerms}&startPage={startPage}&startIndex={startIndex}&count={count}</Url>
995 <Format>http://a9.com/-/spec/opensearchrss/1.0/</Format>
996 <ShortName>$lib</ShortName>
997 <LongName>Search $lib</LongName>
998 <Description>Search the $lib OPAC by $class.</Description>
999 <Tags>$lib book library</Tags>
1000 <SampleSearch>harry+potter</SampleSearch>
1001 <Developer>Mike Rylander for GPLS/PINES</Developer>
1002 <Contact>feedback\@open-ils.org</Contact>
1003 <SyndicationRight>open</SyndicationRight>
1004 <AdultContent>false</AdultContent>
1005 </OpenSearchDescription>
1009 Content-type: application/opensearchdescription+xml; charset=utf-8
1011 <?xml version="1.0" encoding="UTF-8"?>
1012 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
1013 <ShortName>$lib</ShortName>
1014 <Description>Search the $lib OPAC by $class.</Description>
1015 <Tags>$lib book library</Tags>
1016 <Url type="application/rss+xml"
1017 template="$base/1.1/$lib/rss2-full/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
1018 <Url type="application/atom+xml"
1019 template="$base/1.1/$lib/atom-full/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
1020 <Url type="application/x-mods3+xml"
1021 template="$base/1.1/$lib/mods3/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
1022 <Url type="application/x-mods+xml"
1023 template="$base/1.1/$lib/mods/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
1024 <Url type="application/x-marcxml+xml"
1025 template="$base/1.1/$lib/marcxml/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
1026 <Url type="text/html"
1027 template="$base/1.1/$lib/html-full/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
1028 <LongName>Search $lib</LongName>
1029 <Query role="example" searchTerms="harry+potter" />
1030 <Developer>Mike Rylander for GPLS/PINES</Developer>
1031 <Contact>feedback\@open-ils.org</Contact>
1032 <SyndicationRight>open</SyndicationRight>
1033 <AdultContent>false</AdultContent>
1034 <Language>en-US</Language>
1035 <OutputEncoding>UTF-8</OutputEncoding>
1036 <InputEncoding>UTF-8</InputEncoding>
1037 </OpenSearchDescription>
1041 return Apache2::Const::OK;
1044 sub opensearch_feed {
1046 return Apache2::Const::DECLINED if (-e $apache->filename);
1049 my $year = (gmtime())[5] + 1900;
1051 my $host = $cgi->virtual_host || $cgi->server_name;
1054 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
1055 my $rel_name = $cgi->url(-relative=>1);
1056 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
1059 my $url = $cgi->url(-path_info=>$add_path);
1060 my $root = (split 'opensearch', $url)[0];
1061 my $base = (split 'opensearch', $url)[0] . 'opensearch';
1062 my $unapi = (split 'opensearch', $url)[0] . 'unapi';
1064 my $path = $cgi->path_info;
1065 #warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
1067 if ($path =~ m{^/?(1\.\d{1})/(?:([^/]+)/)?([^/]+)/osd.xml}o) {
1073 if (!$lib || $lib eq '-') {
1074 $lib = $actor->request(
1075 'open-ils.actor.org_unit_list.search' => parent_ou => undef
1076 )->gather(1)->[0]->shortname;
1079 if ($class eq '-') {
1083 return opensearch_osd($version, $lib, $class, $base);
1087 my $page = $cgi->param('startPage') || 1;
1088 my $offset = $cgi->param('startIndex') || 1;
1089 my $limit = $cgi->param('count') || 10;
1091 $page = 1 if ($page !~ /^\d+$/);
1092 $offset = 1 if ($offset !~ /^\d+$/);
1093 $limit = 10 if ($limit !~ /^\d+$/); $limit = 25 if ($limit > 25);
1096 $offset = ($page - 1) * $limit;
1101 my ($version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = ('','','','','','','','');
1102 (undef,$version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = split '/', $path;
1104 $lang = $cgi->param('searchLang') if $cgi->param('searchLang');
1105 $lang = '' if ($lang eq '*');
1107 $sort = $cgi->param('searchSort') if $cgi->param('searchSort');
1109 $sortdir = $cgi->param('searchSortDir') if $cgi->param('searchSortDir');
1112 $terms .= " " if ($terms && $cgi->param('searchTerms'));
1113 $terms .= $cgi->param('searchTerms') if $cgi->param('searchTerms');
1115 $class = $cgi->param('searchClass') if $cgi->param('searchClass');
1118 $type = $cgi->param('responseType') if $cgi->param('responseType');
1121 $org = $cgi->param('searchOrg') if $cgi->param('searchOrg');
1125 my $kwt = $cgi->param('kw');
1126 my $tit = $cgi->param('ti');
1127 my $aut = $cgi->param('au');
1128 my $sut = $cgi->param('su');
1129 my $set = $cgi->param('se');
1131 $terms .= " " if ($terms && $kwt);
1132 $terms .= "keyword: $kwt" if ($kwt);
1133 $terms .= " " if ($terms && $tit);
1134 $terms .= "title: $tit" if ($tit);
1135 $terms .= " " if ($terms && $aut);
1136 $terms .= "author: $aut" if ($aut);
1137 $terms .= " " if ($terms && $sut);
1138 $terms .= "subject: $sut" if ($sut);
1139 $terms .= " " if ($terms && $set);
1140 $terms .= "series: $set" if ($set);
1142 if ($version eq '1.0') {
1144 } elsif ($type eq '-') {
1147 my $flesh_feed = parse_feed_type($type);
1149 $terms = decode_utf8($terms);
1150 $lang = 'eng' if ($lang eq 'en-US');
1152 $log->debug("OpenSearch terms: $terms");
1154 my $org_unit = get_ou($org);
1156 # Apostrophes break search and get indexed as spaces anyway
1157 my $safe_terms = $terms;
1158 $safe_terms =~ s{'}{ }go;
1160 my $recs = $search->request(
1161 'open-ils.search.biblio.multiclass.query' => {
1162 org_unit => $org_unit->[0]->id,
1166 sort_dir => $sortdir,
1167 default_class => $class,
1168 ($lang ? ( 'language' => $lang ) : ()),
1169 } => $safe_terms => 1
1172 $log->debug("Hits for [$terms]: $recs->{count}");
1174 my $feed = create_record_feed(
1177 [ map { $_->[0] } @{$recs->{ids}} ],
1183 $log->debug("Feed created...");
1187 $feed->search($safe_terms);
1188 $feed->class($class);
1190 $feed->title("Search results for [$terms] at ".$org_unit->[0]->name);
1192 $feed->creator($host);
1195 $feed->_create_node(
1196 $feed->{item_xpath},
1197 'http://a9.com/-/spec/opensearch/1.1/',
1202 $feed->_create_node(
1203 $feed->{item_xpath},
1204 'http://a9.com/-/spec/opensearch/1.1/',
1209 $feed->_create_node(
1210 $feed->{item_xpath},
1211 'http://a9.com/-/spec/opensearch/1.1/',
1216 $log->debug("...basic feed data added...");
1220 $base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int($offset + $limit + 1) . "&count=" . $limit =>
1221 'application/opensearch+xml'
1222 ) if ($offset + $limit < $recs->{count});
1226 $base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int(($offset - $limit) + 1) . "&count=" . $limit =>
1227 'application/opensearch+xml'
1232 $base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1233 'application/opensearch+xml'
1238 $base . "/$version/$org/rss2-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1239 'application/rss+xml'
1244 $base . "/$version/$org/atom-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1245 'application/atom+xml'
1250 $base . "/$version/$org/html/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1256 $base . "/$version/$org/html-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1260 $feed->link( 'unapi-server' => $unapi);
1262 $log->debug("...feed links added...");
1266 # $root . "../$lang/skin/default/xml/rresult.xml?rt=list&" .
1267 # join('&', map { 'rl=' . $_->[0] } grep { ref $_ && defined $_->[0] } @{$recs->{ids}} ),
1271 #print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . entityize($feed->toString) . "\n";
1272 print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . $feed->toString . "\n";
1274 $log->debug("...and feed returned.");
1276 return Apache2::Const::OK;
1279 sub create_record_feed {
1282 my $records = shift;
1285 my $lib = uc(shift()) || '-';
1287 $flesh = 1 if (!defined($flesh));
1292 my $base = $cgi->url;
1293 my $host = $cgi->virtual_host || $cgi->server_name;
1295 my $year = (gmtime())[5] + 1900;
1297 my $flesh_feed = parse_feed_type($type);
1299 $type =~ s/(-full|-uris)$//o;
1301 my $feed = new OpenILS::WWW::SuperCat::Feed ($type);
1302 $feed->base($base) if ($flesh);
1303 $feed->unapi($unapi) if ($flesh);
1305 $type = 'atom' if ($type eq 'html');
1306 $type = 'marcxml' if (($type eq 'htmlholdings') || ($type eq 'marctxt') || ($type eq 'ris'));
1308 #$records = $supercat->request( "open-ils.supercat.record.object.retrieve", $records )->gather(1);
1311 for my $record (@$records) {
1312 next unless($record);
1314 #my $rec = $record->id;
1317 my $item_tag = "tag:$host,$year:biblio-record_entry/$rec/$lib";
1318 $item_tag = "tag:$host,$year:isbn/$rec/$lib" if ($search eq 'isbn');
1320 my $xml = $supercat->request(
1321 "open-ils.supercat.$search.$type.retrieve",
1326 my $node = $feed->add_item($xml);
1330 if ($lib && ($type eq 'marcxml' || $type eq 'atom') && $flesh) {
1331 my $r = $supercat->request( "open-ils.supercat.$search.holdings_xml.retrieve", $rec, $lib, $flesh_feed, $paging );
1332 while ( !$r->complete ) {
1333 $xml .= join('', map {$_->content} $r->recv);
1335 $xml .= join('', map {$_->content} $r->recv);
1336 $node->add_holdings($xml);
1339 $node->id($item_tag);
1340 #$node->update_ts(cleanse_ISO8601($record->edit_date));
1341 $node->link(alternate => $feed->unapi . "?id=$item_tag&format=htmlholdings-full" => 'text/html') if ($flesh);
1342 $node->link(opac => $feed->unapi . "?id=$item_tag&format=opac") if ($flesh);
1343 $node->link(unapi => $feed->unapi . "?id=$item_tag") if ($flesh);
1344 $node->link('unapi-id' => $item_tag) if ($flesh);
1352 return Apache2::Const::DECLINED if (-e $apache->filename);
1355 my $year = (gmtime())[5] + 1900;
1357 my $host = $cgi->virtual_host || $cgi->server_name;
1360 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
1361 my $rel_name = $cgi->url(-relative=>1);
1362 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
1365 my $url = $cgi->url(-path_info=>$add_path);
1366 my $root = (split 'browse', $url)[0];
1367 my $base = (split 'browse', $url)[0] . 'browse';
1368 my $unapi = (split 'browse', $url)[0] . 'unapi';
1370 my $path = $cgi->path_info;
1373 my ($format,$axis,$site,$string,$page,$page_size) = split '/', $path;
1374 #warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
1376 return item_age_browse($apache) if ($axis eq 'item-age'); # short-circut to the item-age sub
1378 $site ||= $cgi->param('searchOrg');
1379 $page ||= $cgi->param('startPage') || 0;
1380 $page_size ||= $cgi->param('count') || 9;
1382 $page = 0 if ($page !~ /^-?\d+$/);
1384 my $prev = join('/', $base,$format,$axis,$site,$string,$page - 1,$page_size);
1385 my $next = join('/', $base,$format,$axis,$site,$string,$page + 1,$page_size);
1387 unless ($string and $axis and grep { $axis eq $_ } keys %browse_types) {
1388 warn "something's wrong...";
1389 warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
1393 $string = decode_utf8($string);
1394 $string =~ s/\+/ /go;
1397 my $tree = $supercat->request(
1398 "open-ils.supercat.$axis.browse",
1405 (my $norm_format = $format) =~ s/(-full|-uris)$//o;
1407 my ($header,$content) = $browse_types{$axis}{$norm_format}->($tree,$prev,$next,$format,$unapi,$base,$site);
1408 print $header.$content;
1409 return Apache2::Const::OK;
1412 sub item_age_browse {
1414 return Apache2::Const::DECLINED if (-e $apache->filename);
1417 my $year = (gmtime())[5] + 1900;
1419 my $host = $cgi->virtual_host || $cgi->server_name;
1422 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
1423 my $rel_name = $cgi->url(-relative=>1);
1424 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
1427 my $url = $cgi->url(-path_info=>$add_path);
1428 my $root = (split 'browse', $url)[0];
1429 my $base = (split 'browse', $url)[0] . 'browse';
1430 my $unapi = (split 'browse', $url)[0] . 'unapi';
1432 my $path = $cgi->path_info;
1435 my ($format,$axis,$site,$page,$page_size) = split '/', $path;
1436 #warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
1438 unless ($axis eq 'item-age') {
1439 warn "something's wrong...";
1440 warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
1444 $site ||= $cgi->param('searchOrg') || '-';
1445 $page ||= $cgi->param('startPage') || 1;
1446 $page_size ||= $cgi->param('count') || 10;
1448 $page = 1 if ($page !~ /^-?\d+$/ || $page < 1);
1450 my $prev = join('/', $base,$format,$axis,$site,$page - 1,$page_size);
1451 my $next = join('/', $base,$format,$axis,$site,$page + 1,$page_size);
1453 my $recs = $supercat->request(
1454 "open-ils.supercat.new_book_list",
1460 (my $norm_format = $format) =~ s/(-full|-uris)$//o;
1462 my ($header,$content) = $browse_types{$axis}{$norm_format}->($recs,$prev,$next,$format,$unapi,$base,$site);
1463 print $header.$content;
1464 return Apache2::Const::OK;
1467 our %qualifier_map = (
1469 # Some EG qualifiers
1470 'eg.site' => 'site',
1471 'eg.sort' => 'sort',
1472 'eg.direction' => 'dir',
1473 'eg.available' => 'available',
1476 'eg.title' => 'title',
1477 'dc.title' => 'title',
1478 'bib.titleabbreviated' => 'title|abbreviated',
1479 'bib.titleuniform' => 'title|uniform',
1480 'bib.titletranslated' => 'title|translated',
1481 'bib.titlealternative' => 'title',
1482 'bib.titleseries' => 'series',
1483 'eg.series' => 'title',
1485 # Author/Name class:
1486 'eg.author' => 'author',
1487 'eg.name' => 'author',
1488 'creator' => 'author',
1489 'dc.creator' => 'author',
1490 'dc.contributer' => 'author',
1491 'dc.publisher' => 'keyword',
1492 'bib.name' => 'author',
1493 'bib.namepersonal' => 'author|personal',
1494 'bib.namepersonalfamily'=> 'author|personal',
1495 'bib.namepersonalgiven' => 'author|personal',
1496 'bib.namecorporate' => 'author|corporate',
1497 'bib.nameconference' => 'author|conference',
1500 'eg.subject' => 'subject',
1501 'dc.subject' => 'subject',
1502 'bib.subjectplace' => 'subject|geographic',
1503 'bib.subjecttitle' => 'keyword',
1504 'bib.subjectname' => 'subject|name',
1505 'bib.subjectoccupation' => 'keyword',
1508 'eg.keyword' => 'keyword',
1509 'srw.serverchoice' => 'keyword',
1512 'dc.identifier' => 'keyword',
1515 'bib.dateissued' => undef,
1516 'bib.datecreated' => undef,
1517 'bib.datevalid' => undef,
1518 'bib.datemodified' => undef,
1519 'bib.datecopyright' => undef,
1525 'dc.format' => undef,
1528 'bib.genre' => 'keyword',
1531 'bib.audience' => undef,
1534 'bib.originplace' => undef,
1537 'dc.language' => 'lang',
1540 'bib.edition' => 'keyword',
1543 'bib.volume' => 'keyword',
1544 'bib.issue' => 'keyword',
1545 'bib.startpage' => 'keyword',
1546 'bib.endpage' => 'keyword',
1549 'bib.issuance' => 'keyword',
1552 our %qualifier_ids = (
1553 eg => 'http://open-ils.org/spec/SRU/context-set/evergreen/v1',
1554 dc => 'info:srw/cql-context-set/1/dc-v1.1',
1555 bib => 'info:srw/cql-context-set/1/bib-v1.0',
1559 our %nested_qualifier_map = (
1561 site => ['site','Evergreen Site Code (shortname)'],
1562 sort => ['sort','Sort on relevance, title, author, pubdate, create_date or edit_date'],
1563 direction => ['dir','Sort direction (asc|desc)'],
1564 available => ['available','Filter to available (true|false)'],
1566 author => ['author'],
1568 subject => ['subject'],
1569 keyword => ['keyword'],
1570 series => ['series'],
1574 creator => ['author'],
1575 contributor => ['author'],
1576 publisher => ['keyword'],
1577 subject => ['subject'],
1578 identifier => ['keyword'],
1581 language => ['lang'],
1585 titleAbbreviated => ['title'],
1586 titleUniform => ['title'],
1587 titleTranslated => ['title'],
1588 titleAlternative => ['title'],
1589 titleSeries => ['series'],
1591 # Author/Name class:
1593 namePersonal => ['author'],
1594 namePersonalFamily => ['author'],
1595 namePersonalGiven => ['author'],
1596 nameCorporate => ['author'],
1597 nameConference => ['author'],
1600 subjectPlace => ['subject'],
1601 subjectTitle => ['keyword'],
1602 subjectName => ['subject|name'],
1603 subjectOccupation => ['keyword'],
1608 dateIssued => [undef],
1609 dateCreated => [undef],
1610 dateValid => [undef],
1611 dateModified => [undef],
1612 dateCopyright => [undef],
1615 genre => ['keyword'],
1618 audience => [undef],
1621 originPlace => [undef],
1624 edition => ['keyword'],
1627 volume => ['keyword'],
1628 issue => ['keyword'],
1629 startPage => ['keyword'],
1630 endPage => ['keyword'],
1633 issuance => ['keyword'],
1636 serverChoice => ['keyword'],
1641 my $base_explain = <<XML;
1643 id="evergreen-sru-explain-full"
1644 authoritative="true"
1645 xmlns:z="http://explain.z3950.org/dtd/2.0/"
1646 xmlns="http://explain.z3950.org/dtd/2.0/">
1647 <serverInfo transport="http" protocol="SRU" version="1.1">
1654 <title primary="true"/>
1655 <description primary="true"/>
1659 <set identifier="info:srw/cql-context-set/1/cql-v1.2" name="cql"/>
1664 identifier="info:srw/schema/1/marcxml-v1.1"
1665 location="http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
1669 <title>MARC21Slim (marcxml)</title>
1674 <default type="numberOfRecords">50</default>
1675 <default type="contextSet">eg</default>
1676 <default type="index">keyword</default>
1677 <default type="relation">all</default>
1678 <default type="sortSchema">marcxml</default>
1679 <default type="retrieveSchema">marcxml</default>
1680 <setting type="maximumRecords">50</setting>
1681 <supports type="relationModifier">relevant</supports>
1682 <supports type="relationModifier">stem</supports>
1683 <supports type="relationModifier">fuzzy</supports>
1684 <supports type="relationModifier">word</supports>
1695 my $req = SRU::Request->newFromCGI( $cgi );
1696 my $resp = SRU::Response->newFromRequest( $req );
1698 # Find the org_unit shortname, if passed as part of the URL
1699 # http://example.com/opac/extras/sru/SHORTNAME
1700 my $url = $cgi->path_info;
1701 my ($shortname, $holdings) = $url =~ m#/?([^/]*)(/holdings)?#;
1703 if ( $resp->type eq 'searchRetrieve' ) {
1705 # Older versions of Debian packages returned terms to us double-encoded,
1706 # so we had to forcefully double-decode them a second time with
1707 # an outer decode('utf8', $string) call; this seems to be resolved with
1708 # Debian Lenny packages sometime between 2009-07-27 and 2010-02-15
1709 my $cql_query = decode_utf8($req->query);
1710 my $search_string = decode_utf8($req->cql->toEvergreen);
1712 # Ensure the search string overrides the default site
1713 if ($shortname and $search_string !~ m#site:#) {
1714 $search_string .= " site:$shortname";
1717 my $offset = $req->startRecord;
1718 $offset-- if ($offset);
1721 my $limit = $req->maximumRecords;
1724 $log->info("SRU search string [$cql_query] converted to [$search_string]\n");
1726 my $recs = $search->request(
1727 'open-ils.search.biblio.multiclass.query' => {offset => $offset, limit => $limit} => $search_string => 1
1730 my $bre = $supercat->request( 'open-ils.supercat.record.object.retrieve' => [ map { $_->[0] } @{$recs->{ids}} ] )->gather(1);
1732 foreach my $record (@$bre) {
1733 my $marcxml = $record->marc;
1734 # Make the beast conform to a VDX-supported format
1735 # See http://vdxipedia.oclc.org/index.php/Holdings_Parsing
1736 # Trying to implement LIBSOL_852_A format; so much for standards
1738 my $bib_holdings = $supercat->request('open-ils.supercat.record.basic_holdings.retrieve', $record->id, $shortname || '-')->gather(1);
1739 my $marc = MARC::Record->new_from_xml($marcxml, 'UTF8', 'XML');
1741 # Expects the record ID in the 001
1742 $marc->delete_field($_) for ($marc->field('001'));
1743 if (!$marc->field('001')) {
1744 $marc->insert_fields_ordered(
1745 MARC::Field->new( '001', $record->id )
1748 $marc->delete_field($_) for ($marc->field('852')); # remove any legacy 852s
1749 foreach my $cn (keys %$bib_holdings) {
1750 foreach my $cp (@{$bib_holdings->{$cn}->{'copies'}}) {
1751 $marc->insert_fields_ordered(
1754 a => $cp->{'location'},
1755 b => $bib_holdings->{$cn}->{'owning_lib'},
1757 d => $cp->{'circlib'},
1758 g => $cp->{'barcode'},
1759 n => $cp->{'status'},
1765 $marc->delete_field( $_ ) for ($marc->field(901));
1766 $marc->append_fields(
1769 a => $record->tcn_value,
1770 b => $record->tcn_source,
1775 $marcxml = $marc->as_xml_record();
1776 $marcxml =~ s/^<\?xml version="1.0" encoding="UTF-8"\?>//o;
1780 SRU::Response::Record->new(
1781 recordSchema => 'info:srw/schema/1/marcxml-v1.1',
1782 recordData => $marcxml,
1783 recordPosition => ++$offset
1788 $resp->numberOfRecords($recs->{count});
1790 } elsif ( $resp->type eq 'explain' ) {
1792 my $host = $cgi->virtual_host || $cgi->server_name;
1795 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
1796 my $rel_name = $cgi->url(-relative=>1);
1797 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
1799 my $base = $cgi->url(-base=>1);
1800 my $url = $cgi->url(-path_info=>$add_path);
1801 $url =~ s/^$base\///o;
1803 my $doc = $parser->parse_string($base_explain);
1804 my $e = $doc->documentElement;
1805 $e->findnodes('/z:explain/z:serverInfo/z:host')->shift->appendText( $host );
1806 $e->findnodes('/z:explain/z:serverInfo/z:port')->shift->appendText( $cgi->server_port );
1807 $e->findnodes('/z:explain/z:serverInfo/z:database')->shift->appendText( $url );
1809 for my $name ( keys %OpenILS::WWW::SuperCat::nested_qualifier_map ) {
1811 my $identifier = $OpenILS::WWW::SuperCat::qualifier_ids{ $name };
1813 next unless $identifier;
1815 my $set_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'set' );
1816 $set_node->setAttribute( identifier => $identifier );
1817 $set_node->setAttribute( name => $name );
1819 $e->findnodes('/z:explain/z:indexInfo')->shift->appendChild( $set_node );
1821 for my $index ( keys %{ $OpenILS::WWW::SuperCat::nested_qualifier_map{$name} } ) {
1822 my $desc = $OpenILS::WWW::SuperCat::nested_qualifier_map{$name}{$index}[1] || $index;
1824 my $name_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'name' );
1826 my $map_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'map' );
1827 $map_node->appendChild( $name_node );
1829 my $title_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'title' );
1831 my $index_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'index' );
1832 $index_node->appendChild( $title_node );
1833 $index_node->appendChild( $map_node );
1835 $index_node->setAttribute( id => $name . '.' . $index );
1836 $title_node->appendText( $desc );
1837 $name_node->setAttribute( set => $name );
1838 $name_node->appendText($index );
1840 $e->findnodes('/z:explain/z:indexInfo')->shift->appendChild( $index_node );
1844 $ex_doc = $e->toString;
1848 SRU::Response::Record->new(
1849 recordSchema => 'info:srw/cql-context-set/2/zeerex-1.1',
1850 recordData => $ex_doc
1855 print $cgi->header( -type => 'application/xml' );
1856 print $U->entityize($resp->asXML) . "\n";
1857 return Apache2::Const::OK;
1862 package CQL::BooleanNode;
1866 my $left = $self->left();
1867 my $right = $self->right();
1868 my $leftStr = $left->toEvergreen;
1869 my $rightStr = $right->toEvergreen();
1871 my $op = '||' if uc $self->op() eq 'OR';
1874 return "$leftStr $rightStr";
1877 package CQL::TermNode;
1881 my $qualifier = $self->getQualifier();
1882 my $term = $self->getTerm();
1883 my $relation = $self->getRelation();
1887 my ($qset, $qname) = split(/\./, $qualifier);
1889 $log->debug("SRU toEvergreen: $qset, $qname $OpenILS::WWW::SuperCat::nested_qualifier_map{$qset}{$qname}[0]\n");
1891 if ( exists($OpenILS::WWW::SuperCat::nested_qualifier_map{$qset}{$qname}) ) {
1892 $qualifier = $OpenILS::WWW::SuperCat::nested_qualifier_map{$qset}{$qname}[0] || 'kw';
1895 my @modifiers = $relation->getModifiers();
1897 my $base = $relation->getBase();
1898 if ( grep { $base eq $_ } qw/= scr exact all/ ) {
1901 foreach my $m ( @modifiers ) {
1902 if( grep { $m->[ 1 ] eq $_ } qw/cql.fuzzy cql.stem cql.relevant cql.word/ ) {
1908 $quote_it = 0 if ( $base eq 'all' );
1909 $term = maybeQuote($term) if $quote_it;
1912 croak( "Evergreen doesn't support the $base relations" );
1920 return "$qualifier:$term";
1924 =head2 get_ou($org_unit)
1926 Returns an aou object for a given actor.org_unit shortname or ID.
1931 my $org = shift || '-';
1935 $org_unit = $actor->request(
1936 'open-ils.actor.org_unit_list.search' => parent_ou => undef
1938 } elsif ($org !~ /^\d+$/o) {
1939 $org_unit = $actor->request(
1940 'open-ils.actor.org_unit_list.search' => shortname => uc($org)
1943 $org_unit = $actor->request(
1944 'open-ils.actor.org_unit_list.search' => id => $org
1953 # vim: noet:ts=4:sw=4