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) = ('','','');
332 my $body = "Content-type: application/xml; charset=utf-8\n\n";
334 if ($uri =~ m{^tag:[^:]+:([^\/]+)/([^/]+)(?:/(.+))$}o) {
338 $type = 'metarecord' if ($1 =~ /^m/o);
341 ->request("open-ils.supercat.$type.formats")
344 if ($type eq 'record' or $type eq 'isbn') {
345 $body .= <<" FORMATS";
347 <format name='opac' type='text/html'/>
348 <format name='html' type='text/html'/>
349 <format name='htmlholdings' type='text/html'/>
350 <format name='html-full' type='text/html'/>
351 <format name='htmlholdings-full' type='text/html'/>
352 <format name='marctxt' type='text/plain'/>
353 <format name='ris' type='text/plain'/>
355 } elsif ($type eq 'metarecord') {
356 $body .= <<" FORMATS";
358 <format name='opac' type='text/html'/>
363 my ($type) = keys %$h;
364 $body .= unapi_format($h, $type);
366 if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
367 $body .= unapi_format($h, "$type-full");
368 $body .= unapi_format($h, "$type-uris");
372 $body .= "</formats>\n";
376 ->request("open-ils.supercat.record.formats")
381 ->request("open-ils.supercat.metarecord.formats")
385 my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
386 $list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
388 $body .= <<" FORMATS";
390 <format name='opac' type='text/html'/>
391 <format name='html' type='text/html'/>
392 <format name='htmlholdings' type='text/html'/>
393 <format name='html-full' type='text/html'/>
394 <format name='htmlholdings-full' type='text/html'/>
395 <format name='marctxt' type='text/plain'/>
396 <format name='ris' type='text/plain'/>
401 my ($type) = keys %$h;
402 $body .= "\t" . unapi_format($h, $type);
404 if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
405 $body .= "\t" . unapi_format($h, "$type-full");
406 $body .= "\t" . unapi_format($h, "$type-uris");
410 $body .= "</formats>\n";
414 return Apache2::Const::OK;
417 if ($uri =~ m{^tag:[^:]+:([^\/]+)/([^/]+)(?:/(.+))?}o) {
421 $type = 'metarecord' if ($1 =~ /^metabib/o);
422 $type = 'isbn' if ($1 =~ /^isbn/o);
423 $type = 'call_number' if ($1 =~ /^call_number/o);
424 $type = 'acp' if ($1 =~ /^asset-copy/o);
425 $type = 'acn' if ($1 =~ /^asset-call_number/o);
426 $type = 'auri' if ($1 =~ /^asset-uri/o);
427 $command = 'retrieve';
428 $command = 'browse' if ($type eq 'call_number');
431 if (!$lib || $lib eq '-') {
432 $lib = $actor->request(
433 'open-ils.actor.org_unit_list.search' => parent_ou => undef
434 )->gather(1)->[0]->shortname;
437 my $lib_object = $actor->request(
438 'open-ils.actor.org_unit_list.search' => shortname => $lib
440 my $lib_id = $lib_object->id;
442 my $ou_types = $actor->request( 'open-ils.actor.org_types.retrieve' )->gather(1);
443 my $lib_depth = (grep { $_->id == $lib_object->ou_type } @$ou_types)[0]->depth;
445 if ($type eq 'call_number' and $command eq 'browse') {
446 print "Location: $root/browse/$base_format/call_number/$lib/$id\n\n";
450 if ($type eq 'isbn') {
451 my $rec = $supercat->request('open-ils.supercat.isbn.object.retrieve',$id)->gather(1);
453 print "Content-type: text/html; charset=utf-8\n\n";
454 $apache->custom_response( 404, <<" HTML");
457 <title>Type [$type] with id [$id] not found!</title>
461 <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
472 { (keys(%$_))[0] eq $base_format }
473 @{ $supercat->request("open-ils.supercat.$type.formats")->gather(1) }
475 { $_ eq $base_format }
476 qw/opac html htmlholdings marctxt ris/
478 print "Content-type: text/html; charset=utf-8\n\n";
479 $apache->custom_response( 406, <<" HTML");
482 <title>Invalid format [$format] for type [$type]!</title>
486 <center>Sorry, format $format is not valid for type $type.</center>
493 if ($format eq 'opac') {
494 print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id&l=$lib_id&d=$lib_depth\n\n"
495 if ($type eq 'metarecord');
496 print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id&l=$lib_id&d=$lib_depth\n\n"
497 if ($type eq 'record');
499 } elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format) && ($type ne 'acn' && $type ne 'acp' && $type ne 'auri')) {
500 my $feed = create_record_feed(
509 print "Content-type: text/html; charset=utf-8\n\n";
510 $apache->custom_response( 404, <<" HTML");
513 <title>Type [$type] with id [$id] not found!</title>
517 <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
525 $feed->creator($host);
527 $feed->link( unapi => $base) if ($flesh_feed);
529 print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
530 print $U->entityize($feed->toString) . "\n";
532 return Apache2::Const::OK;
535 my $req = $supercat->request("open-ils.supercat.$type.$format.$command",$id);
536 my $data = $req->gather(1);
538 if ($req->failed || !$data) {
539 print "Content-type: text/html; charset=utf-8\n\n";
540 $apache->custom_response( 404, <<" HTML");
543 <title>$type $id not found!</title>
547 <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
554 print "Content-type: application/xml; charset=utf-8\n\n$data";
556 return Apache2::Const::OK;
562 return Apache2::Const::DECLINED if (-e $apache->filename);
567 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
568 my $rel_name = $cgi->url(-relative=>1);
569 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
572 my $url = $cgi->url(-path_info=>$add_path);
573 my $root = (split 'supercat', $url)[0];
574 my $base = (split 'supercat', $url)[0] . 'supercat';
575 my $unapi = (split 'supercat', $url)[0] . 'unapi';
577 my $host = $cgi->virtual_host || $cgi->server_name;
579 my $path = $cgi->path_info;
580 my ($id,$type,$format,$command) = reverse split '/', $path;
581 my $flesh_feed = parse_feed_type($type);
582 (my $base_format = $format) =~ s/(-full|-uris)$//o;
584 my $skin = $cgi->param('skin') || 'default';
585 my $locale = $cgi->param('locale') || 'en-US';
587 if ( $path =~ m{^/formats(?:/([^\/]+))?$}o ) {
588 print "Content-type: application/xml; charset=utf-8\n";
591 ->request("open-ils.supercat.$1.formats")
599 <type>text/html</type>
602 if ($1 eq 'record' or $1 eq 'isbn') {
604 <name>htmlholdings</name>
605 <type>text/html</type>
609 <type>text/html</type>
612 <name>htmlholdings-full</name>
613 <type>text/html</type>
616 <name>html-full</name>
617 <type>text/html</type>
621 <type>text/plain</type>
625 <type>text/plain</type>
630 my ($type) = keys %$h;
631 print supercat_format($h, $type);
633 if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
634 print supercat_format($h, "$type-full");
635 print supercat_format($h, "$type-uris");
640 print "</formats>\n";
642 return Apache2::Const::OK;
646 ->request("open-ils.supercat.record.formats")
651 ->request("open-ils.supercat.metarecord.formats")
655 my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
656 $list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
661 <type>text/html</type>
664 <name>htmlholdings</name>
665 <type>text/html</type>
669 <type>text/html</type>
672 <name>htmlholdings-full</name>
673 <type>text/html</type>
676 <name>html-full</name>
677 <type>text/html</type>
681 <type>text/plain</type>
685 <type>text/plain</type>
689 my ($type) = keys %$h;
690 print supercat_format($h, $type);
692 if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
693 print supercat_format($h, "$type-full");
694 print supercat_format($h, "$type-uris");
699 print "</formats>\n";
702 return Apache2::Const::OK;
705 if ($format eq 'opac') {
706 print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id\n\n"
707 if ($type eq 'metarecord');
708 print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id\n\n"
709 if ($type eq 'record');
712 } elsif ($base_format eq 'marc21') {
716 my $bib = $supercat->request( "open-ils.supercat.record.object.retrieve", $id )->gather(1)->[0];
718 my $r = MARC::Record->new_from_xml( $bib->marc, 'UTF-8', 'USMARC' );
719 $r->delete_field( $_ ) for ($r->field(901));
724 a => $bib->tcn_value,
725 b => $bib->tcn_source,
730 print "Content-type: application/octet-stream\n\n";
736 print "Content-type: text/html; charset=utf-8\n\n";
737 $apache->custom_response( 404, <<" HTML");
744 <center>Couldn't fetch $id as MARC21.</center>
751 return Apache2::Const::OK;
753 } elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format)) {
754 my $feed = create_record_feed(
762 $feed->creator($host);
766 $feed->link( unapi => $base) if ($flesh_feed);
768 print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
769 print $U->entityize($feed->toString) . "\n";
771 return Apache2::Const::OK;
774 my $req = $supercat->request("open-ils.supercat.$type.$format.$command",$id);
778 print "Content-type: text/html; charset=utf-8\n\n";
779 $apache->custom_response( 404, <<" HTML");
782 <title>$type $id not found!</title>
786 <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
793 print "Content-type: application/xml; charset=utf-8\n\n";
794 print $U->entityize( $parser->parse_string( $req->gather(1) )->documentElement->toString );
796 return Apache2::Const::OK;
802 return Apache2::Const::DECLINED if (-e $apache->filename);
806 my $year = (gmtime())[5] + 1900;
807 my $host = $cgi->virtual_host || $cgi->server_name;
810 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
811 my $rel_name = $cgi->url(-relative=>1);
812 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
815 my $url = $cgi->url(-path_info=>$add_path);
816 my $root = (split 'feed', $url)[0] . '/';
817 my $base = (split 'bookbag', $url)[0] . '/bookbag';
818 my $unapi = (split 'feed', $url)[0] . '/unapi';
820 my $skin = $cgi->param('skin') || 'default';
821 my $locale = $cgi->param('locale') || 'en-US';
822 my $org = $cgi->param('searchOrg');
824 my $org_unit = get_ou($org);
825 my $scope = "l=" . $org_unit->[0]->id . "&";
827 $root =~ s{(?<!http:)//}{/}go;
828 $base =~ s{(?<!http:)//}{/}go;
829 $unapi =~ s{(?<!http:)//}{/}go;
831 my $path = $cgi->path_info;
832 #warn "URL breakdown: $url -> $root -> $base -> $path -> $unapi";
834 my ($id,$type) = reverse split '/', $path;
835 my $flesh_feed = parse_feed_type($type);
837 my $bucket = $actor->request("open-ils.actor.container.public.flesh", 'biblio', $id)->gather(1);
838 return Apache2::Const::NOT_FOUND unless($bucket);
840 my $bucket_tag = "tag:$host,$year:record_bucket/$id";
841 if ($type eq 'opac') {
842 print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
843 join('&', map { "rl=" . $_->target_biblio_record_entry } @{ $bucket->items }) .
848 my $feed = create_record_feed(
851 [ map { $_->target_biblio_record_entry } @{ $bucket->items } ],
853 $org_unit->[0]->shortname,
857 $feed->id($bucket_tag);
859 $feed->title("Items in Book Bag [".$bucket->name."]");
860 $feed->creator($host);
863 $feed->link(alternate => $base . "/rss2-full/$id" => 'application/rss+xml');
864 $feed->link(atom => $base . "/atom-full/$id" => 'application/atom+xml');
865 $feed->link(html => $base . "/html-full/$id" => 'text/html');
866 $feed->link(unapi => $unapi);
870 "http://$host/opac/$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
871 join('&', map { 'rl=' . $_->target_biblio_record_entry } @{$bucket->items} ),
876 print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
877 print $U->entityize($feed->toString) . "\n";
879 return Apache2::Const::OK;
884 return Apache2::Const::DECLINED if (-e $apache->filename);
888 my $year = (gmtime())[5] + 1900;
889 my $host = $cgi->virtual_host || $cgi->server_name;
892 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
893 my $rel_name = $cgi->url(-relative=>1);
894 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
897 my $url = $cgi->url(-path_info=>$add_path);
898 my $root = (split 'feed', $url)[0];
899 my $base = (split 'freshmeat', $url)[0] . '/freshmeat';
900 my $unapi = (split 'feed', $url)[0] . 'unapi';
902 my $skin = $cgi->param('skin') || 'default';
903 my $locale = $cgi->param('locale') || 'en-US';
904 my $org = $cgi->param('searchOrg');
906 my $org_unit = get_ou($org);
907 my $scope = "l=" . $org_unit->[0]->id . "&";
909 my $path = $cgi->path_info;
910 #warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
912 $path =~ s/^\/(?:feed\/)?freshmeat\///og;
914 my ($type,$rtype,$axis,$limit,$date) = split '/', $path;
915 my $flesh_feed = parse_feed_type($type);
919 my $list = $supercat->request("open-ils.supercat.$rtype.record.$axis.recent", $date, $limit)->gather(1);
921 #if ($type eq 'opac') {
922 # print "Location: $root/../../en-US/skin/default/xml/rresult.xml?rt=list&" .
923 # join('&', map { "rl=" . $_ } @$list) .
928 my $feed = create_record_feed( 'record', $type, $list, $unapi, $org_unit->[0]->shortname, $flesh_feed);
932 $feed->title("Up to $limit recent $rtype ${axis}s from $date forward");
934 $feed->title("$limit most recent $rtype ${axis}s");
937 $feed->creator($host);
940 $feed->link(alternate => $base . "/rss2-full/$rtype/$axis/$limit/$date" => 'application/rss+xml');
941 $feed->link(atom => $base . "/atom-full/$rtype/$axis/$limit/$date" => 'application/atom+xml');
942 $feed->link(html => $base . "/html-full/$rtype/$axis/$limit/$date" => 'text/html');
943 $feed->link(unapi => $unapi);
947 "http://$host/opac/$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
948 join('&', map { 'rl=' . $_} @$list ),
953 print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
954 print $U->entityize($feed->toString) . "\n";
956 return Apache2::Const::OK;
965 if ($version eq '1.0') {
967 Content-type: application/opensearchdescription+xml; charset=utf-8
969 <?xml version="1.0" encoding="UTF-8"?>
970 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearchdescription/1.0/">
971 <Url>$base/1.0/$lib/-/$class/?searchTerms={searchTerms}&startPage={startPage}&startIndex={startIndex}&count={count}</Url>
972 <Format>http://a9.com/-/spec/opensearchrss/1.0/</Format>
973 <ShortName>$lib</ShortName>
974 <LongName>Search $lib</LongName>
975 <Description>Search the $lib OPAC by $class.</Description>
976 <Tags>$lib book library</Tags>
977 <SampleSearch>harry+potter</SampleSearch>
978 <Developer>Mike Rylander for GPLS/PINES</Developer>
979 <Contact>feedback\@open-ils.org</Contact>
980 <SyndicationRight>open</SyndicationRight>
981 <AdultContent>false</AdultContent>
982 </OpenSearchDescription>
986 Content-type: application/opensearchdescription+xml; charset=utf-8
988 <?xml version="1.0" encoding="UTF-8"?>
989 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
990 <ShortName>$lib</ShortName>
991 <Description>Search the $lib OPAC by $class.</Description>
992 <Tags>$lib book library</Tags>
993 <Url type="application/rss+xml"
994 template="$base/1.1/$lib/rss2-full/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
995 <Url type="application/atom+xml"
996 template="$base/1.1/$lib/atom-full/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
997 <Url type="application/x-mods3+xml"
998 template="$base/1.1/$lib/mods3/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
999 <Url type="application/x-mods+xml"
1000 template="$base/1.1/$lib/mods/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
1001 <Url type="application/x-marcxml+xml"
1002 template="$base/1.1/$lib/marcxml/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
1003 <Url type="text/html"
1004 template="$base/1.1/$lib/html-full/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
1005 <LongName>Search $lib</LongName>
1006 <Query role="example" searchTerms="harry+potter" />
1007 <Developer>Mike Rylander for GPLS/PINES</Developer>
1008 <Contact>feedback\@open-ils.org</Contact>
1009 <SyndicationRight>open</SyndicationRight>
1010 <AdultContent>false</AdultContent>
1011 <Language>en-US</Language>
1012 <OutputEncoding>UTF-8</OutputEncoding>
1013 <InputEncoding>UTF-8</InputEncoding>
1014 </OpenSearchDescription>
1018 return Apache2::Const::OK;
1021 sub opensearch_feed {
1023 return Apache2::Const::DECLINED if (-e $apache->filename);
1026 my $year = (gmtime())[5] + 1900;
1028 my $host = $cgi->virtual_host || $cgi->server_name;
1031 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
1032 my $rel_name = $cgi->url(-relative=>1);
1033 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
1036 my $url = $cgi->url(-path_info=>$add_path);
1037 my $root = (split 'opensearch', $url)[0];
1038 my $base = (split 'opensearch', $url)[0] . 'opensearch';
1039 my $unapi = (split 'opensearch', $url)[0] . 'unapi';
1041 my $path = $cgi->path_info;
1042 #warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
1044 if ($path =~ m{^/?(1\.\d{1})/(?:([^/]+)/)?([^/]+)/osd.xml}o) {
1050 if (!$lib || $lib eq '-') {
1051 $lib = $actor->request(
1052 'open-ils.actor.org_unit_list.search' => parent_ou => undef
1053 )->gather(1)->[0]->shortname;
1056 if ($class eq '-') {
1060 return opensearch_osd($version, $lib, $class, $base);
1064 my $page = $cgi->param('startPage') || 1;
1065 my $offset = $cgi->param('startIndex') || 1;
1066 my $limit = $cgi->param('count') || 10;
1068 $page = 1 if ($page !~ /^\d+$/);
1069 $offset = 1 if ($offset !~ /^\d+$/);
1070 $limit = 10 if ($limit !~ /^\d+$/); $limit = 25 if ($limit > 25);
1073 $offset = ($page - 1) * $limit;
1078 my ($version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = ('','','','','','','','');
1079 (undef,$version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = split '/', $path;
1081 $lang = $cgi->param('searchLang') if $cgi->param('searchLang');
1082 $lang = '' if ($lang eq '*');
1084 $sort = $cgi->param('searchSort') if $cgi->param('searchSort');
1086 $sortdir = $cgi->param('searchSortDir') if $cgi->param('searchSortDir');
1089 $terms .= " " if ($terms && $cgi->param('searchTerms'));
1090 $terms .= $cgi->param('searchTerms') if $cgi->param('searchTerms');
1092 $class = $cgi->param('searchClass') if $cgi->param('searchClass');
1095 $type = $cgi->param('responseType') if $cgi->param('responseType');
1098 $org = $cgi->param('searchOrg') if $cgi->param('searchOrg');
1102 my $kwt = $cgi->param('kw');
1103 my $tit = $cgi->param('ti');
1104 my $aut = $cgi->param('au');
1105 my $sut = $cgi->param('su');
1106 my $set = $cgi->param('se');
1108 $terms .= " " if ($terms && $kwt);
1109 $terms .= "keyword: $kwt" if ($kwt);
1110 $terms .= " " if ($terms && $tit);
1111 $terms .= "title: $tit" if ($tit);
1112 $terms .= " " if ($terms && $aut);
1113 $terms .= "author: $aut" if ($aut);
1114 $terms .= " " if ($terms && $sut);
1115 $terms .= "subject: $sut" if ($sut);
1116 $terms .= " " if ($terms && $set);
1117 $terms .= "series: $set" if ($set);
1119 if ($version eq '1.0') {
1121 } elsif ($type eq '-') {
1124 my $flesh_feed = parse_feed_type($type);
1126 $terms = decode_utf8($terms);
1127 $lang = 'eng' if ($lang eq 'en-US');
1129 $log->debug("OpenSearch terms: $terms");
1131 my $org_unit = get_ou($org);
1133 # Apostrophes break search and get indexed as spaces anyway
1134 my $safe_terms = $terms;
1135 $safe_terms =~ s{'}{ }go;
1137 my $recs = $search->request(
1138 'open-ils.search.biblio.multiclass.query' => {
1139 org_unit => $org_unit->[0]->id,
1143 sort_dir => $sortdir,
1144 default_class => $class,
1145 ($lang ? ( 'language' => $lang ) : ()),
1146 } => $safe_terms => 1
1149 $log->debug("Hits for [$terms]: $recs->{count}");
1151 my $feed = create_record_feed(
1154 [ map { $_->[0] } @{$recs->{ids}} ],
1160 $log->debug("Feed created...");
1164 $feed->search($safe_terms);
1165 $feed->class($class);
1167 $feed->title("Search results for [$terms] at ".$org_unit->[0]->name);
1169 $feed->creator($host);
1172 $feed->_create_node(
1173 $feed->{item_xpath},
1174 'http://a9.com/-/spec/opensearch/1.1/',
1179 $feed->_create_node(
1180 $feed->{item_xpath},
1181 'http://a9.com/-/spec/opensearch/1.1/',
1186 $feed->_create_node(
1187 $feed->{item_xpath},
1188 'http://a9.com/-/spec/opensearch/1.1/',
1193 $log->debug("...basic feed data added...");
1197 $base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int($offset + $limit + 1) . "&count=" . $limit =>
1198 'application/opensearch+xml'
1199 ) if ($offset + $limit < $recs->{count});
1203 $base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int(($offset - $limit) + 1) . "&count=" . $limit =>
1204 'application/opensearch+xml'
1209 $base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1210 'application/opensearch+xml'
1215 $base . "/$version/$org/rss2-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1216 'application/rss+xml'
1221 $base . "/$version/$org/atom-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1222 'application/atom+xml'
1227 $base . "/$version/$org/html/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1233 $base . "/$version/$org/html-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1237 $feed->link( 'unapi-server' => $unapi);
1239 $log->debug("...feed links added...");
1243 # $root . "../$lang/skin/default/xml/rresult.xml?rt=list&" .
1244 # join('&', map { 'rl=' . $_->[0] } grep { ref $_ && defined $_->[0] } @{$recs->{ids}} ),
1248 #print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . entityize($feed->toString) . "\n";
1249 print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . $feed->toString . "\n";
1251 $log->debug("...and feed returned.");
1253 return Apache2::Const::OK;
1256 sub create_record_feed {
1259 my $records = shift;
1262 my $lib = uc(shift()) || '-';
1264 $flesh = 1 if (!defined($flesh));
1267 my $base = $cgi->url;
1268 my $host = $cgi->virtual_host || $cgi->server_name;
1270 my $year = (gmtime())[5] + 1900;
1272 my $flesh_feed = parse_feed_type($type);
1274 $type =~ s/(-full|-uris)$//o;
1276 my $feed = new OpenILS::WWW::SuperCat::Feed ($type);
1277 $feed->base($base) if ($flesh);
1278 $feed->unapi($unapi) if ($flesh);
1280 $type = 'atom' if ($type eq 'html');
1281 $type = 'marcxml' if (($type eq 'htmlholdings') || ($type eq 'marctxt') || ($type eq 'ris'));
1283 #$records = $supercat->request( "open-ils.supercat.record.object.retrieve", $records )->gather(1);
1286 for my $record (@$records) {
1287 next unless($record);
1289 #my $rec = $record->id;
1292 my $item_tag = "tag:$host,$year:biblio-record_entry/$rec/$lib";
1293 $item_tag = "tag:$host,$year:isbn/$rec/$lib" if ($search eq 'isbn');
1295 my $xml = $supercat->request(
1296 "open-ils.supercat.$search.$type.retrieve",
1301 my $node = $feed->add_item($xml);
1305 if ($lib && ($type eq 'marcxml' || $type eq 'atom') && $flesh) {
1306 my $r = $supercat->request( "open-ils.supercat.$search.holdings_xml.retrieve", $rec, $lib, ($flesh_feed eq "uris") ? 1 : 0 );
1307 while ( !$r->complete ) {
1308 $xml .= join('', map {$_->content} $r->recv);
1310 $xml .= join('', map {$_->content} $r->recv);
1311 $node->add_holdings($xml);
1314 $node->id($item_tag);
1315 #$node->update_ts(clense_ISO8601($record->edit_date));
1316 $node->link(alternate => $feed->unapi . "?id=$item_tag&format=htmlholdings-full" => 'text/html') if ($flesh);
1317 $node->link(opac => $feed->unapi . "?id=$item_tag&format=opac") if ($flesh);
1318 $node->link(unapi => $feed->unapi . "?id=$item_tag") if ($flesh);
1319 $node->link('unapi-id' => $item_tag) if ($flesh);
1327 return Apache2::Const::DECLINED if (-e $apache->filename);
1330 my $year = (gmtime())[5] + 1900;
1332 my $host = $cgi->virtual_host || $cgi->server_name;
1335 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
1336 my $rel_name = $cgi->url(-relative=>1);
1337 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
1340 my $url = $cgi->url(-path_info=>$add_path);
1341 my $root = (split 'browse', $url)[0];
1342 my $base = (split 'browse', $url)[0] . 'browse';
1343 my $unapi = (split 'browse', $url)[0] . 'unapi';
1345 my $path = $cgi->path_info;
1348 my ($format,$axis,$site,$string,$page,$page_size) = split '/', $path;
1349 #warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
1351 return item_age_browse($apache) if ($axis eq 'item-age'); # short-circut to the item-age sub
1353 $site ||= $cgi->param('searchOrg');
1354 $page ||= $cgi->param('startPage') || 0;
1355 $page_size ||= $cgi->param('count') || 9;
1357 $page = 0 if ($page !~ /^-?\d+$/);
1359 my $prev = join('/', $base,$format,$axis,$site,$string,$page - 1,$page_size);
1360 my $next = join('/', $base,$format,$axis,$site,$string,$page + 1,$page_size);
1362 unless ($string and $axis and grep { $axis eq $_ } keys %browse_types) {
1363 warn "something's wrong...";
1364 warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
1368 $string = decode_utf8($string);
1369 $string =~ s/\+/ /go;
1372 my $tree = $supercat->request(
1373 "open-ils.supercat.$axis.browse",
1380 (my $norm_format = $format) =~ s/(-full|-uris)$//o;
1382 my ($header,$content) = $browse_types{$axis}{$norm_format}->($tree,$prev,$next,$format,$unapi,$base,$site);
1383 print $header.$content;
1384 return Apache2::Const::OK;
1387 sub item_age_browse {
1389 return Apache2::Const::DECLINED if (-e $apache->filename);
1392 my $year = (gmtime())[5] + 1900;
1394 my $host = $cgi->virtual_host || $cgi->server_name;
1397 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
1398 my $rel_name = $cgi->url(-relative=>1);
1399 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
1402 my $url = $cgi->url(-path_info=>$add_path);
1403 my $root = (split 'browse', $url)[0];
1404 my $base = (split 'browse', $url)[0] . 'browse';
1405 my $unapi = (split 'browse', $url)[0] . 'unapi';
1407 my $path = $cgi->path_info;
1410 my ($format,$axis,$site,$page,$page_size) = split '/', $path;
1411 #warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
1413 unless ($axis eq 'item-age') {
1414 warn "something's wrong...";
1415 warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
1419 $site ||= $cgi->param('searchOrg') || '-';
1420 $page ||= $cgi->param('startPage') || 1;
1421 $page_size ||= $cgi->param('count') || 10;
1423 $page = 1 if ($page !~ /^-?\d+$/ || $page < 1);
1425 my $prev = join('/', $base,$format,$axis,$site,$page - 1,$page_size);
1426 my $next = join('/', $base,$format,$axis,$site,$page + 1,$page_size);
1428 my $recs = $supercat->request(
1429 "open-ils.supercat.new_book_list",
1435 (my $norm_format = $format) =~ s/(-full|-uris)$//o;
1437 my ($header,$content) = $browse_types{$axis}{$norm_format}->($recs,$prev,$next,$format,$unapi,$base,$site);
1438 print $header.$content;
1439 return Apache2::Const::OK;
1442 our %qualifier_map = (
1444 # Some EG qualifiers
1445 'eg.site' => 'site',
1446 'eg.sort' => 'sort',
1447 'eg.direction' => 'dir',
1448 'eg.available' => 'available',
1451 'eg.title' => 'title',
1452 'dc.title' => 'title',
1453 'bib.titleabbreviated' => 'title|abbreviated',
1454 'bib.titleuniform' => 'title|uniform',
1455 'bib.titletranslated' => 'title|translated',
1456 'bib.titlealternative' => 'title',
1457 'bib.titleseries' => 'series',
1458 'eg.series' => 'title',
1460 # Author/Name class:
1461 'eg.author' => 'author',
1462 'eg.name' => 'author',
1463 'creator' => 'author',
1464 'dc.creator' => 'author',
1465 'dc.contributer' => 'author',
1466 'dc.publisher' => 'keyword',
1467 'bib.name' => 'author',
1468 'bib.namepersonal' => 'author|personal',
1469 'bib.namepersonalfamily'=> 'author|personal',
1470 'bib.namepersonalgiven' => 'author|personal',
1471 'bib.namecorporate' => 'author|corporate',
1472 'bib.nameconference' => 'author|conference',
1475 'eg.subject' => 'subject',
1476 'dc.subject' => 'subject',
1477 'bib.subjectplace' => 'subject|geographic',
1478 'bib.subjecttitle' => 'keyword',
1479 'bib.subjectname' => 'subject|name',
1480 'bib.subjectoccupation' => 'keyword',
1483 'eg.keyword' => 'keyword',
1484 'srw.serverchoice' => 'keyword',
1487 'dc.identifier' => 'keyword',
1490 'bib.dateissued' => undef,
1491 'bib.datecreated' => undef,
1492 'bib.datevalid' => undef,
1493 'bib.datemodified' => undef,
1494 'bib.datecopyright' => undef,
1500 'dc.format' => undef,
1503 'bib.genre' => 'keyword',
1506 'bib.audience' => undef,
1509 'bib.originplace' => undef,
1512 'dc.language' => 'lang',
1515 'bib.edition' => 'keyword',
1518 'bib.volume' => 'keyword',
1519 'bib.issue' => 'keyword',
1520 'bib.startpage' => 'keyword',
1521 'bib.endpage' => 'keyword',
1524 'bib.issuance' => 'keyword',
1527 our %qualifier_ids = (
1528 eg => 'http://open-ils.org/spec/SRU/context-set/evergreen/v1',
1529 dc => 'info:srw/cql-context-set/1/dc-v1.1',
1530 bib => 'info:srw/cql-context-set/1/bib-v1.0',
1534 our %nested_qualifier_map = (
1536 site => ['site','Evergreen Site Code (shortname)'],
1537 sort => ['sort','Sort on relevance, title, author, pubdate, create_date or edit_date'],
1538 direction => ['dir','Sort direction (asc|desc)'],
1539 available => ['available','Filter to available (true|false)'],
1541 author => ['author'],
1543 subject => ['subject'],
1544 keyword => ['keyword'],
1545 series => ['series'],
1549 creator => ['author'],
1550 contributor => ['author'],
1551 publisher => ['keyword'],
1552 subject => ['subject'],
1553 identifier => ['keyword'],
1556 language => ['lang'],
1560 titleAbbreviated => ['title'],
1561 titleUniform => ['title'],
1562 titleTranslated => ['title'],
1563 titleAlternative => ['title'],
1564 titleSeries => ['series'],
1566 # Author/Name class:
1568 namePersonal => ['author'],
1569 namePersonalFamily => ['author'],
1570 namePersonalGiven => ['author'],
1571 nameCorporate => ['author'],
1572 nameConference => ['author'],
1575 subjectPlace => ['subject'],
1576 subjectTitle => ['keyword'],
1577 subjectName => ['subject|name'],
1578 subjectOccupation => ['keyword'],
1583 dateIssued => [undef],
1584 dateCreated => [undef],
1585 dateValid => [undef],
1586 dateModified => [undef],
1587 dateCopyright => [undef],
1590 genre => ['keyword'],
1593 audience => [undef],
1596 originPlace => [undef],
1599 edition => ['keyword'],
1602 volume => ['keyword'],
1603 issue => ['keyword'],
1604 startPage => ['keyword'],
1605 endPage => ['keyword'],
1608 issuance => ['keyword'],
1611 serverChoice => ['keyword'],
1616 my $base_explain = <<XML;
1618 id="evergreen-sru-explain-full"
1619 authoritative="true"
1620 xmlns:z="http://explain.z3950.org/dtd/2.0/"
1621 xmlns="http://explain.z3950.org/dtd/2.0/">
1622 <serverInfo transport="http" protocol="SRU" version="1.1">
1629 <title primary="true"/>
1630 <description primary="true"/>
1634 <set identifier="info:srw/cql-context-set/1/cql-v1.2" name="cql"/>
1639 identifier="info:srw/schema/1/marcxml-v1.1"
1640 location="http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
1644 <title>MARC21Slim (marcxml)</title>
1649 <default type="numberOfRecords">50</default>
1650 <default type="contextSet">eg</default>
1651 <default type="index">keyword</default>
1652 <default type="relation">all</default>
1653 <default type="sortSchema">marcxml</default>
1654 <default type="retrieveSchema">marcxml</default>
1655 <setting type="maximumRecords">50</setting>
1656 <supports type="relationModifier">relevant</supports>
1657 <supports type="relationModifier">stem</supports>
1658 <supports type="relationModifier">fuzzy</supports>
1659 <supports type="relationModifier">word</supports>
1670 my $req = SRU::Request->newFromCGI( $cgi );
1671 my $resp = SRU::Response->newFromRequest( $req );
1673 # Find the org_unit shortname, if passed as part of the URL
1674 # http://example.com/opac/extras/sru/SHORTNAME
1675 my $url = $cgi->path_info;
1676 my ($shortname, $holdings) = $url =~ m#/?([^/]*)(/holdings)?#;
1678 if ( $resp->type eq 'searchRetrieve' ) {
1680 # These terms are arriving to us double-encoded, so until we
1681 # figure out where in the CGI/SRU chain that's happening, we
1682 # have to # forcefully double-decode them a second time with
1683 # the outer decode('utf8', $string) call
1684 my $cql_query = decode('utf8', decode_utf8($req->query));
1685 my $search_string = decode('utf8', decode_utf8($req->cql->toEvergreen));
1687 # Ensure the search string overrides the default site
1688 if ($shortname and $search_string !~ m#site:#) {
1689 $search_string .= " site:$shortname";
1692 my $offset = $req->startRecord;
1693 $offset-- if ($offset);
1696 my $limit = $req->maximumRecords;
1699 $log->info("SRU search string [$cql_query] converted to [$search_string]\n");
1701 my $recs = $search->request(
1702 'open-ils.search.biblio.multiclass.query' => {offset => $offset, limit => $limit} => $search_string => 1
1705 my $bre = $supercat->request( 'open-ils.supercat.record.object.retrieve' => [ map { $_->[0] } @{$recs->{ids}} ] )->gather(1);
1707 foreach my $record (@$bre) {
1708 my $marcxml = $record->marc;
1709 # Make the beast conform to a VDX-supported format
1710 # See http://vdxipedia.oclc.org/index.php/Holdings_Parsing
1711 # Trying to implement LIBSOL_852_A format; so much for standards
1713 my $bib_holdings = $supercat->request('open-ils.supercat.record.basic_holdings.retrieve', $record->id, $shortname || '-')->gather(1);
1714 my $marc = MARC::Record->new_from_xml($marcxml, 'UTF8', 'XML');
1716 # Expects the record ID in the 001
1717 $marc->delete_field($_) for ($marc->field('001'));
1718 if (!$marc->field('001')) {
1719 $marc->insert_fields_ordered(
1720 MARC::Field->new( '001', $record->id )
1723 foreach my $cn (keys %$bib_holdings) {
1724 foreach my $cp (@{$bib_holdings->{$cn}->{'copies'}}) {
1725 $marc->insert_fields_ordered(
1728 a => $cp->{'location'},
1729 b => $bib_holdings->{$cn}->{'owning_lib'},
1731 d => $cp->{'circlib'},
1732 g => $cp->{'barcode'},
1733 n => $cp->{'status'},
1739 $marc->delete_field( $_ ) for ($marc->field(901));
1740 $marc->append_fields(
1743 a => $record->tcn_value,
1744 b => $record->tcn_source,
1749 $marcxml = $marc->as_xml_record();
1750 $marcxml =~ s/^<\?xml version="1.0" encoding="UTF-8"\?>//o;
1754 SRU::Response::Record->new(
1755 recordSchema => 'info:srw/schema/1/marcxml-v1.1',
1756 recordData => $marcxml,
1757 recordPosition => ++$offset
1762 $resp->numberOfRecords($recs->{count});
1764 } elsif ( $resp->type eq 'explain' ) {
1766 my $host = $cgi->virtual_host || $cgi->server_name;
1769 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
1770 my $rel_name = $cgi->url(-relative=>1);
1771 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
1773 my $base = $cgi->url(-base=>1);
1774 my $url = $cgi->url(-path_info=>$add_path);
1775 $url =~ s/^$base\///o;
1777 my $doc = $parser->parse_string($base_explain);
1778 my $e = $doc->documentElement;
1779 $e->findnodes('/z:explain/z:serverInfo/z:host')->shift->appendText( $host );
1780 $e->findnodes('/z:explain/z:serverInfo/z:port')->shift->appendText( $cgi->server_port );
1781 $e->findnodes('/z:explain/z:serverInfo/z:database')->shift->appendText( $url );
1783 for my $name ( keys %OpenILS::WWW::SuperCat::nested_qualifier_map ) {
1785 my $identifier = $OpenILS::WWW::SuperCat::qualifier_ids{ $name };
1787 next unless $identifier;
1789 my $set_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'set' );
1790 $set_node->setAttribute( identifier => $identifier );
1791 $set_node->setAttribute( name => $name );
1793 $e->findnodes('/z:explain/z:indexInfo')->shift->appendChild( $set_node );
1795 for my $index ( keys %{ $OpenILS::WWW::SuperCat::nested_qualifier_map{$name} } ) {
1796 my $desc = $OpenILS::WWW::SuperCat::nested_qualifier_map{$name}{$index}[1] || $index;
1798 my $name_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'name' );
1800 my $map_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'map' );
1801 $map_node->appendChild( $name_node );
1803 my $title_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'title' );
1805 my $index_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'index' );
1806 $index_node->appendChild( $title_node );
1807 $index_node->appendChild( $map_node );
1809 $index_node->setAttribute( id => $name . '.' . $index );
1810 $title_node->appendText( $desc );
1811 $name_node->setAttribute( set => $name );
1812 $name_node->appendText($index );
1814 $e->findnodes('/z:explain/z:indexInfo')->shift->appendChild( $index_node );
1818 $ex_doc = $e->toString;
1822 SRU::Response::Record->new(
1823 recordSchema => 'info:srw/cql-context-set/2/zeerex-1.1',
1824 recordData => $ex_doc
1829 print $cgi->header( -type => 'application/xml' );
1830 print $U->entityize($resp->asXML) . "\n";
1831 return Apache2::Const::OK;
1836 package CQL::BooleanNode;
1840 my $left = $self->left();
1841 my $right = $self->right();
1842 my $leftStr = $left->toEvergreen;
1843 my $rightStr = $right->toEvergreen();
1845 my $op = '||' if uc $self->op() eq 'OR';
1848 return "$leftStr $rightStr";
1851 package CQL::TermNode;
1855 my $qualifier = $self->getQualifier();
1856 my $term = $self->getTerm();
1857 my $relation = $self->getRelation();
1861 my ($qset, $qname) = split(/\./, $qualifier);
1863 $log->debug("SRU toEvergreen: $qset, $qname $OpenILS::WWW::SuperCat::nested_qualifier_map{$qset}{$qname}[0]\n");
1865 if ( exists($OpenILS::WWW::SuperCat::nested_qualifier_map{$qset}{$qname}) ) {
1866 $qualifier = $OpenILS::WWW::SuperCat::nested_qualifier_map{$qset}{$qname}[0] || 'kw';
1869 my @modifiers = $relation->getModifiers();
1871 my $base = $relation->getBase();
1872 if ( grep { $base eq $_ } qw/= scr exact all/ ) {
1875 foreach my $m ( @modifiers ) {
1876 if( grep { $m->[ 1 ] eq $_ } qw/cql.fuzzy cql.stem cql.relevant cql.word/ ) {
1882 $quote_it = 0 if ( $base eq 'all' );
1883 $term = maybeQuote($term) if $quote_it;
1886 croak( "Evergreen doesn't support the $base relations" );
1894 return "$qualifier:$term";
1898 =head2 get_ou($org_unit)
1900 Returns an aou object for a given actor.org_unit shortname or ID.
1905 my $org = shift || '-';
1909 $org_unit = $actor->request(
1910 'open-ils.actor.org_unit_list.search' => parent_ou => undef
1912 } elsif ($org !~ /^\d+$/o) {
1913 $org_unit = $actor->request(
1914 'open-ils.actor.org_unit_list.search' => shortname => uc($org)
1917 $org_unit = $actor->request(
1918 'open-ils.actor.org_unit_list.search' => id => $org
1927 # vim: noet:ts=4:sw=4