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'>";
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'>";
73 $content .= "<owning_lib xmlns='http://open-ils.org/spec/actors/v1' id='$ou_tag' name='$ou_name'/>";
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>";
82 $content .= '</volumes>';
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';
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 my $feed = create_record_feed( 'record', $real_format, $record_list, $unapi, $site, $real_format =~ /-full$/o ? 1 : 0 );
166 $feed->root( "$base/../" );
168 $feed->link( next => $next => $feed->type );
169 $feed->link( previous => $prev => $feed->type );
172 "Content-type: ". $feed->type ."; charset=utf-8\n\n",
184 return Apache2::Const::DECLINED if (-e $apache->filename);
186 (my $isbn = $apache->path_info) =~ s{^.*?([^/]+)$}{$1}o;
189 ->request("open-ils.supercat.oisbn", $isbn)
192 print "Content-type: application/xml; charset=utf-8\n\n";
193 print "<?xml version='1.0' encoding='UTF-8' ?>\n";
195 unless (exists $$list{metarecord}) {
197 return Apache2::Const::OK;
200 print "<idlist metarecord='$$list{metarecord}'>\n";
202 for ( keys %{ $$list{record_list} } ) {
203 (my $o = $$list{record_list}{$_}) =~s/^(\S+).*?$/$1/o;
204 print " <isbn record='$_'>$o</isbn>\n"
209 return Apache2::Const::OK;
215 return Apache2::Const::DECLINED if (-e $apache->filename);
220 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
221 my $rel_name = $cgi->url(-relative=>1);
222 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
225 my $url = $cgi->url(-path_info=>$add_path);
226 my $root = (split 'unapi', $url)[0];
227 my $base = (split 'unapi', $url)[0] . 'unapi';
230 my $uri = $cgi->param('id') || '';
231 my $host = $cgi->virtual_host || $cgi->server_name;
233 my $skin = $cgi->param('skin') || 'default';
234 my $locale = $cgi->param('locale') || 'en-US';
236 my $format = $cgi->param('format');
237 my $flesh_feed = ($format =~ /-full$/o) ? 1 : 0;
238 (my $base_format = $format) =~ s/-full$//o;
239 my ($id,$type,$command,$lib) = ('','','');
242 my $body = "Content-type: application/xml; charset=utf-8\n\n";
244 if ($uri =~ m{^tag:[^:]+:([^\/]+)/([^/]+)(?:/(.+))$}o) {
248 $type = 'metarecord' if ($1 =~ /^m/o);
251 ->request("open-ils.supercat.$type.formats")
254 if ($type eq 'record' or $type eq 'isbn') {
255 $body .= <<" FORMATS";
257 <format name='opac' type='text/html'/>
258 <format name='html' type='text/html'/>
259 <format name='htmlholdings' type='text/html'/>
260 <format name='html-full' type='text/html'/>
261 <format name='htmlholdings-full' type='text/html'/>
262 <format name='marctxt' type='text/plain'/>
264 } elsif ($type eq 'metarecord') {
265 $body .= <<" FORMATS";
267 <format name='opac' type='text/html'/>
272 my ($type) = keys %$h;
273 $body .= "\t<format name='$type' type='application/xml'";
275 for my $part ( qw/namespace_uri docs schema_location/ ) {
276 $body .= " $part='$$h{$type}{$part}'"
277 if ($$h{$type}{$part});
282 if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
283 $body .= "\t<format name='$type-full' type='application/xml'";
285 for my $part ( qw/namespace_uri docs schema_location/ ) {
286 $body .= " $part='$$h{$type}{$part}'"
287 if ($$h{$type}{$part});
294 $body .= "</formats>\n";
298 ->request("open-ils.supercat.record.formats")
303 ->request("open-ils.supercat.metarecord.formats")
307 my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
308 $list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
310 $body .= <<" FORMATS";
312 <format name='opac' type='text/html'/>
313 <format name='html' type='text/html'/>
314 <format name='htmlholdings' type='text/html'/>
315 <format name='html-full' type='text/html'/>
316 <format name='htmlholdings-full' type='text/html'/>
317 <format name='marctxt' type='text/plain'/>
322 my ($type) = keys %$h;
323 $body .= "\t<format name='$type' type='application/xml'";
325 for my $part ( qw/namespace_uri docs schema_location/ ) {
326 $body .= " $part='$$h{$type}{$part}'"
327 if ($$h{$type}{$part});
332 if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
333 $body .= "\t<format name='$type-full' type='application/xml'";
335 for my $part ( qw/namespace_uri docs schema_location/ ) {
336 $body .= " $part='$$h{$type}{$part}'"
337 if ($$h{$type}{$part});
344 $body .= "</formats>\n";
348 return Apache2::Const::OK;
351 if ($uri =~ m{^tag:[^:]+:([^\/]+)/([^/]+)(?:/(.+))?}o) {
355 $type = 'metarecord' if ($1 =~ /^metabib/o);
356 $type = 'isbn' if ($1 =~ /^isbn/o);
357 $type = 'call_number' if ($1 =~ /^call_number/o);
358 $type = 'acp' if ($1 =~ /^asset-copy/o);
359 $type = 'acn' if ($1 =~ /^asset-call_number/o);
360 $type = 'auri' if ($1 =~ /^asset-uri/o);
361 $command = 'retrieve';
362 $command = 'browse' if ($type eq 'call_number');
365 if (!$lib || $lib eq '-') {
366 $lib = $actor->request(
367 'open-ils.actor.org_unit_list.search' => parent_ou => undef
368 )->gather(1)->[0]->shortname;
371 my $lib_object = $actor->request(
372 'open-ils.actor.org_unit_list.search' => shortname => $lib
374 my $lib_id = $lib_object->id;
376 my $ou_types = $actor->request( 'open-ils.actor.org_types.retrieve' )->gather(1);
377 my $lib_depth = (grep { $_->id == $lib_object->ou_type } @$ou_types)[0]->depth;
379 if ($type eq 'call_number' and $command eq 'browse') {
380 print "Location: $root/browse/$base_format/call_number/$lib/$id\n\n";
384 if ($type eq 'isbn') {
385 my $rec = $supercat->request('open-ils.supercat.isbn.object.retrieve',$id)->gather(1);
387 print "Content-type: text/html; charset=utf-8\n\n";
388 $apache->custom_response( 404, <<" HTML");
391 <title>Type [$type] with id [$id] not found!</title>
395 <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
406 { (keys(%$_))[0] eq $base_format }
407 @{ $supercat->request("open-ils.supercat.$type.formats")->gather(1) }
409 { $_ eq $base_format }
410 qw/opac html htmlholdings marctxt/
412 print "Content-type: text/html; charset=utf-8\n\n";
413 $apache->custom_response( 406, <<" HTML");
416 <title>Invalid format [$format] for type [$type]!</title>
420 <center>Sorry, format $format is not valid for type $type.</center>
427 if ($format eq 'opac') {
428 print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id&l=$lib_id&d=$lib_depth\n\n"
429 if ($type eq 'metarecord');
430 print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id&l=$lib_id&d=$lib_depth\n\n"
431 if ($type eq 'record');
433 } elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format) && ($type ne 'acn' && $type ne 'acp' && $type ne 'auri')) {
434 my $feed = create_record_feed(
443 print "Content-type: text/html; charset=utf-8\n\n";
444 $apache->custom_response( 404, <<" HTML");
447 <title>Type [$type] with id [$id] not found!</title>
451 <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
459 $feed->creator($host);
461 $feed->link( unapi => $base) if ($flesh_feed);
463 print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
464 print $U->entityize($feed->toString) . "\n";
466 return Apache2::Const::OK;
469 my $req = $supercat->request("open-ils.supercat.$type.$format.$command",$id);
470 my $data = $req->gather(1);
472 if ($req->failed || !$data) {
473 print "Content-type: text/html; charset=utf-8\n\n";
474 $apache->custom_response( 404, <<" HTML");
477 <title>$type $id not found!</title>
481 <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
488 print "Content-type: application/xml; charset=utf-8\n\n$data";
490 return Apache2::Const::OK;
496 return Apache2::Const::DECLINED if (-e $apache->filename);
501 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
502 my $rel_name = $cgi->url(-relative=>1);
503 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
506 my $url = $cgi->url(-path_info=>$add_path);
507 my $root = (split 'supercat', $url)[0];
508 my $base = (split 'supercat', $url)[0] . 'supercat';
509 my $unapi = (split 'supercat', $url)[0] . 'unapi';
511 my $host = $cgi->virtual_host || $cgi->server_name;
513 my $path = $cgi->path_info;
514 my ($id,$type,$format,$command) = reverse split '/', $path;
515 my $flesh_feed = ($type =~ /-full$/o) ? 1 : 0;
516 (my $base_format = $format) =~ s/-full$//o;
518 my $skin = $cgi->param('skin') || 'default';
519 my $locale = $cgi->param('locale') || 'en-US';
521 if ( $path =~ m{^/formats(?:/([^\/]+))?$}o ) {
522 print "Content-type: application/xml; charset=utf-8\n";
525 ->request("open-ils.supercat.$1.formats")
533 <type>text/html</type>
536 if ($1 eq 'record' or $1 eq 'isbn') {
538 <name>htmlholdings</name>
539 <type>text/html</type>
543 <type>text/html</type>
546 <name>htmlholdings-full</name>
547 <type>text/html</type>
550 <name>html-full</name>
551 <type>text/html</type>
555 <type>text/plain</type>
560 my ($type) = keys %$h;
561 print "<format><name>$type</name><type>application/xml</type>";
563 for my $part ( qw/namespace_uri docs schema_location/ ) {
564 print "<$part>$$h{$type}{$part}</$part>"
565 if ($$h{$type}{$part});
570 if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
571 print "<format><name>$type-full</name><type>application/xml</type>";
573 for my $part ( qw/namespace_uri docs schema_location/ ) {
574 print "<$part>$$h{$type}{$part}</$part>"
575 if ($$h{$type}{$part});
583 print "</formats>\n";
585 return Apache2::Const::OK;
589 ->request("open-ils.supercat.record.formats")
594 ->request("open-ils.supercat.metarecord.formats")
598 my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
599 $list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
604 <type>text/html</type>
607 <name>htmlholdings</name>
608 <type>text/html</type>
612 <type>text/html</type>
615 <name>htmlholdings-full</name>
616 <type>text/html</type>
619 <name>html-full</name>
620 <type>text/html</type>
624 <type>text/plain</type>
628 my ($type) = keys %$h;
629 print "<format><name>$type</name><type>application/xml</type>";
631 for my $part ( qw/namespace_uri docs schema_location/ ) {
632 print "<$part>$$h{$type}{$part}</$part>"
633 if ($$h{$type}{$part});
638 if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
639 print "<format><name>$type-full</name><type>application/xml</type>";
641 for my $part ( qw/namespace_uri docs schema_location/ ) {
642 print "<$part>$$h{$type}{$part}</$part>"
643 if ($$h{$type}{$part});
651 print "</formats>\n";
654 return Apache2::Const::OK;
657 if ($format eq 'opac') {
658 print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id\n\n"
659 if ($type eq 'metarecord');
660 print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id\n\n"
661 if ($type eq 'record');
664 } elsif ($base_format eq 'marc21') {
668 my $bib = $supercat->request( "open-ils.supercat.record.object.retrieve", $id )->gather(1)->[0];
670 my $r = MARC::Record->new_from_xml( $bib->marc, 'UTF-8', 'USMARC' );
671 $r->delete_field( $_ ) for ($r->field(901));
676 a => $bib->tcn_value,
677 b => $bib->tcn_source,
682 print "Content-type: application/octet-stream\n\n";
688 print "Content-type: text/html; charset=utf-8\n\n";
689 $apache->custom_response( 404, <<" HTML");
696 <center>Couldn't fetch $id as MARC21.</center>
703 return Apache2::Const::OK;
705 } elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format)) {
706 my $feed = create_record_feed(
714 $feed->creator($host);
718 $feed->link( unapi => $base) if ($flesh_feed);
720 print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
721 print $U->entityize($feed->toString) . "\n";
723 return Apache2::Const::OK;
726 my $req = $supercat->request("open-ils.supercat.$type.$format.$command",$id);
730 print "Content-type: text/html; charset=utf-8\n\n";
731 $apache->custom_response( 404, <<" HTML");
734 <title>$type $id not found!</title>
738 <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
745 print "Content-type: application/xml; charset=utf-8\n\n";
746 print $U->entityize( $parser->parse_string( $req->gather(1) )->documentElement->toString );
748 return Apache2::Const::OK;
754 return Apache2::Const::DECLINED if (-e $apache->filename);
758 my $year = (gmtime())[5] + 1900;
759 my $host = $cgi->virtual_host || $cgi->server_name;
762 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
763 my $rel_name = $cgi->url(-relative=>1);
764 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
767 my $url = $cgi->url(-path_info=>$add_path);
768 my $root = (split 'feed', $url)[0] . '/';
769 my $base = (split 'bookbag', $url)[0] . '/bookbag';
770 my $unapi = (split 'feed', $url)[0] . '/unapi';
772 my $skin = $cgi->param('skin') || 'default';
773 my $locale = $cgi->param('locale') || 'en-US';
775 $root =~ s{(?<!http:)//}{/}go;
776 $base =~ s{(?<!http:)//}{/}go;
777 $unapi =~ s{(?<!http:)//}{/}go;
779 my $path = $cgi->path_info;
780 #warn "URL breakdown: $url -> $root -> $base -> $path -> $unapi";
782 my ($id,$type) = reverse split '/', $path;
783 my $flesh_feed = ($type =~ /-full$/o) ? 1 : 0;
785 my $bucket = $actor->request("open-ils.actor.container.public.flesh", 'biblio', $id)->gather(1);
786 return Apache2::Const::NOT_FOUND unless($bucket);
788 my $bucket_tag = "tag:$host,$year:record_bucket/$id";
789 if ($type eq 'opac') {
790 print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?rt=list&" .
791 join('&', map { "rl=" . $_->target_biblio_record_entry } @{ $bucket->items }) .
796 my $feed = create_record_feed(
799 [ map { $_->target_biblio_record_entry } @{ $bucket->items } ],
805 $feed->id($bucket_tag);
807 $feed->title("Items in Book Bag [".$bucket->name."]");
808 $feed->creator($host);
811 $feed->link(alternate => $base . "/rss2-full/$id" => 'application/rss+xml');
812 $feed->link(atom => $base . "/atom-full/$id" => 'application/atom+xml');
813 $feed->link(html => $base . "/html-full/$id" => 'text/html');
814 $feed->link(unapi => $unapi);
818 $host . "/opac/$locale/skin/$skin/xml/rresult.xml?rt=list&" .
819 join('&', map { 'rl=' . $_->target_biblio_record_entry } @{$bucket->items} ),
824 print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
825 print $U->entityize($feed->toString) . "\n";
827 return Apache2::Const::OK;
832 return Apache2::Const::DECLINED if (-e $apache->filename);
836 my $year = (gmtime())[5] + 1900;
837 my $host = $cgi->virtual_host || $cgi->server_name;
840 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
841 my $rel_name = $cgi->url(-relative=>1);
842 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
845 my $url = $cgi->url(-path_info=>$add_path);
846 my $root = (split 'feed', $url)[0];
847 my $base = (split 'freshmeat', $url)[0] . '/freshmeat';
848 my $unapi = (split 'feed', $url)[0] . 'unapi';
850 my $skin = $cgi->param('skin') || 'default';
851 my $locale = $cgi->param('locale') || 'en-US';
853 my $path = $cgi->path_info;
854 #warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
856 $path =~ s/^\/(?:feed\/)?freshmeat\///og;
858 my ($type,$rtype,$axis,$limit,$date) = split '/', $path;
859 my $flesh_feed = ($type =~ /-full$/o) ? 1 : 0;
862 my $list = $supercat->request("open-ils.supercat.$rtype.record.$axis.recent", $date, $limit)->gather(1);
864 #if ($type eq 'opac') {
865 # print "Location: $root/../../en-US/skin/default/xml/rresult.xml?rt=list&" .
866 # join('&', map { "rl=" . $_ } @$list) .
871 my $feed = create_record_feed( 'record', $type, $list, $unapi, undef, $flesh_feed);
875 $feed->title("Up to $limit recent $rtype ${axis}s from $date forward");
877 $feed->title("$limit most recent $rtype ${axis}s");
880 $feed->creator($host);
883 $feed->link(alternate => $base . "/rss2-full/$rtype/$axis/$limit/$date" => 'application/rss+xml');
884 $feed->link(atom => $base . "/atom-full/$rtype/$axis/$limit/$date" => 'application/atom+xml');
885 $feed->link(html => $base . "/html-full/$rtype/$axis/$limit/$date" => 'text/html');
886 $feed->link(unapi => $unapi);
890 $host . "/opac/$locale/skin/$skin/xml/rresult.xml?rt=list&" .
891 join('&', map { 'rl=' . $_} @$list ),
896 print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
897 print $U->entityize($feed->toString) . "\n";
899 return Apache2::Const::OK;
908 if ($version eq '1.0') {
910 Content-type: application/opensearchdescription+xml; charset=utf-8
912 <?xml version="1.0" encoding="UTF-8"?>
913 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearchdescription/1.0/">
914 <Url>$base/1.0/$lib/-/$class/?searchTerms={searchTerms}&startPage={startPage}&startIndex={startIndex}&count={count}</Url>
915 <Format>http://a9.com/-/spec/opensearchrss/1.0/</Format>
916 <ShortName>$lib</ShortName>
917 <LongName>Search $lib</LongName>
918 <Description>Search the $lib OPAC by $class.</Description>
919 <Tags>$lib book library</Tags>
920 <SampleSearch>harry+potter</SampleSearch>
921 <Developer>Mike Rylander for GPLS/PINES</Developer>
922 <Contact>feedback\@open-ils.org</Contact>
923 <SyndicationRight>open</SyndicationRight>
924 <AdultContent>false</AdultContent>
925 </OpenSearchDescription>
929 Content-type: application/opensearchdescription+xml; charset=utf-8
931 <?xml version="1.0" encoding="UTF-8"?>
932 <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
933 <ShortName>$lib</ShortName>
934 <Description>Search the $lib OPAC by $class.</Description>
935 <Tags>$lib book library</Tags>
936 <Url type="application/rss+xml"
937 template="$base/1.1/$lib/rss2-full/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
938 <Url type="application/atom+xml"
939 template="$base/1.1/$lib/atom-full/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
940 <Url type="application/x-mods3+xml"
941 template="$base/1.1/$lib/mods3/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
942 <Url type="application/x-mods+xml"
943 template="$base/1.1/$lib/mods/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
944 <Url type="application/x-marcxml+xml"
945 template="$base/1.1/$lib/marcxml/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
946 <Url type="text/html"
947 template="$base/1.1/$lib/html-full/$class/?searchTerms={searchTerms}&startPage={startPage?}&startIndex={startIndex?}&count={count?}&searchLang={language?}"/>
948 <LongName>Search $lib</LongName>
949 <Query role="example" searchTerms="harry+potter" />
950 <Developer>Mike Rylander for GPLS/PINES</Developer>
951 <Contact>feedback\@open-ils.org</Contact>
952 <SyndicationRight>open</SyndicationRight>
953 <AdultContent>false</AdultContent>
954 <Language>en-US</Language>
955 <OutputEncoding>UTF-8</OutputEncoding>
956 <InputEncoding>UTF-8</InputEncoding>
957 </OpenSearchDescription>
961 return Apache2::Const::OK;
964 sub opensearch_feed {
966 return Apache2::Const::DECLINED if (-e $apache->filename);
969 my $year = (gmtime())[5] + 1900;
971 my $host = $cgi->virtual_host || $cgi->server_name;
974 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
975 my $rel_name = $cgi->url(-relative=>1);
976 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
979 my $url = $cgi->url(-path_info=>$add_path);
980 my $root = (split 'opensearch', $url)[0];
981 my $base = (split 'opensearch', $url)[0] . 'opensearch';
982 my $unapi = (split 'opensearch', $url)[0] . 'unapi';
984 my $path = $cgi->path_info;
985 #warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
987 if ($path =~ m{^/?(1\.\d{1})/(?:([^/]+)/)?([^/]+)/osd.xml}o) {
993 if (!$lib || $lib eq '-') {
994 $lib = $actor->request(
995 'open-ils.actor.org_unit_list.search' => parent_ou => undef
996 )->gather(1)->[0]->shortname;
1003 return opensearch_osd($version, $lib, $class, $base);
1007 my $page = $cgi->param('startPage') || 1;
1008 my $offset = $cgi->param('startIndex') || 1;
1009 my $limit = $cgi->param('count') || 10;
1011 $page = 1 if ($page !~ /^\d+$/);
1012 $offset = 1 if ($offset !~ /^\d+$/);
1013 $limit = 10 if ($limit !~ /^\d+$/); $limit = 25 if ($limit > 25);
1016 $offset = ($page - 1) * $limit;
1021 my ($version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = ('','','','','','','','');
1022 (undef,$version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = split '/', $path;
1024 $lang = $cgi->param('searchLang') if $cgi->param('searchLang');
1025 $lang = '' if ($lang eq '*');
1027 $sort = $cgi->param('searchSort') if $cgi->param('searchSort');
1029 $sortdir = $cgi->param('searchSortDir') if $cgi->param('searchSortDir');
1032 $terms .= " " if ($terms && $cgi->param('searchTerms'));
1033 $terms .= $cgi->param('searchTerms') if $cgi->param('searchTerms');
1035 $class = $cgi->param('searchClass') if $cgi->param('searchClass');
1038 $type = $cgi->param('responseType') if $cgi->param('responseType');
1041 $org = $cgi->param('searchOrg') if $cgi->param('searchOrg');
1045 my $kwt = $cgi->param('kw');
1046 my $tit = $cgi->param('ti');
1047 my $aut = $cgi->param('au');
1048 my $sut = $cgi->param('su');
1049 my $set = $cgi->param('se');
1051 $terms .= " " if ($terms && $kwt);
1052 $terms .= "keyword: $kwt" if ($kwt);
1053 $terms .= " " if ($terms && $tit);
1054 $terms .= "title: $tit" if ($tit);
1055 $terms .= " " if ($terms && $aut);
1056 $terms .= "author: $aut" if ($aut);
1057 $terms .= " " if ($terms && $sut);
1058 $terms .= "subject: $sut" if ($sut);
1059 $terms .= " " if ($terms && $set);
1060 $terms .= "series: $set" if ($set);
1062 if ($version eq '1.0') {
1064 } elsif ($type eq '-') {
1067 my $flesh_feed = ($type =~ /-full$/o) ? 1 : 0;
1069 $terms = decode_utf8($terms);
1070 $lang = 'eng' if ($lang eq 'en-US');
1072 $log->debug("OpenSearch terms: $terms");
1076 $org_unit = $actor->request(
1077 'open-ils.actor.org_unit_list.search' => parent_ou => undef
1079 } elsif ($org !~ /^\d+$/o) {
1080 $org_unit = $actor->request(
1081 'open-ils.actor.org_unit_list.search' => shortname => uc($org)
1084 $org_unit = $actor->request(
1085 'open-ils.actor.org_unit_list.search' => id => $org
1089 # Apostrophes break search and get indexed as spaces anyway
1090 my $safe_terms = $terms;
1091 $safe_terms =~ s{'}{ }go;
1093 my $recs = $search->request(
1094 'open-ils.search.biblio.multiclass.query' => {
1095 org_unit => $org_unit->[0]->id,
1099 sort_dir => $sortdir,
1100 default_class => $class,
1101 ($lang ? ( 'language' => $lang ) : ()),
1102 } => $safe_terms => 1
1105 $log->debug("Hits for [$terms]: $recs->{count}");
1107 my $feed = create_record_feed(
1110 [ map { $_->[0] } @{$recs->{ids}} ],
1116 $log->debug("Feed created...");
1120 $feed->search($safe_terms);
1121 $feed->class($class);
1123 $feed->title("Search results for [$terms] at ".$org_unit->[0]->name);
1125 $feed->creator($host);
1128 $feed->_create_node(
1129 $feed->{item_xpath},
1130 'http://a9.com/-/spec/opensearch/1.1/',
1135 $feed->_create_node(
1136 $feed->{item_xpath},
1137 'http://a9.com/-/spec/opensearch/1.1/',
1142 $feed->_create_node(
1143 $feed->{item_xpath},
1144 'http://a9.com/-/spec/opensearch/1.1/',
1149 $log->debug("...basic feed data added...");
1153 $base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int($offset + $limit + 1) . "&count=" . $limit =>
1154 'application/opensearch+xml'
1155 ) if ($offset + $limit < $recs->{count});
1159 $base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int(($offset - $limit) + 1) . "&count=" . $limit =>
1160 'application/opensearch+xml'
1165 $base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1166 'application/opensearch+xml'
1171 $base . "/$version/$org/rss2-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1172 'application/rss+xml'
1177 $base . "/$version/$org/atom-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1178 'application/atom+xml'
1183 $base . "/$version/$org/html/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1189 $base . "/$version/$org/html-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
1193 $feed->link( 'unapi-server' => $unapi);
1195 $log->debug("...feed links added...");
1199 # $root . "../$lang/skin/default/xml/rresult.xml?rt=list&" .
1200 # join('&', map { 'rl=' . $_->[0] } grep { ref $_ && defined $_->[0] } @{$recs->{ids}} ),
1204 #print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . entityize($feed->toString) . "\n";
1205 print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . $feed->toString . "\n";
1207 $log->debug("...and feed returned.");
1209 return Apache2::Const::OK;
1212 sub create_record_feed {
1215 my $records = shift;
1218 my $lib = uc(shift()) || '-';
1220 $flesh = 1 if (!defined($flesh));
1223 my $base = $cgi->url;
1224 my $host = $cgi->virtual_host || $cgi->server_name;
1226 my $year = (gmtime())[5] + 1900;
1228 my $flesh_feed = ($type =~ s/-full$//o) ? 1 : 0;
1230 my $feed = new OpenILS::WWW::SuperCat::Feed ($type);
1231 $feed->base($base) if ($flesh);
1232 $feed->unapi($unapi) if ($flesh);
1234 $type = 'atom' if ($type eq 'html');
1235 $type = 'marcxml' if (($type eq 'htmlholdings') || ($type eq 'marctxt'));
1237 #$records = $supercat->request( "open-ils.supercat.record.object.retrieve", $records )->gather(1);
1240 for my $record (@$records) {
1241 next unless($record);
1243 #my $rec = $record->id;
1246 my $item_tag = "tag:$host,$year:biblio-record_entry/$rec/$lib";
1247 $item_tag = "tag:$host,$year:isbn/$rec/$lib" if ($search eq 'isbn');
1249 my $xml = $supercat->request(
1250 "open-ils.supercat.$search.$type.retrieve",
1255 my $node = $feed->add_item($xml);
1259 if ($lib && ($type eq 'marcxml' || $type eq 'atom') && $flesh) {
1260 my $r = $supercat->request( "open-ils.supercat.$search.holdings_xml.retrieve", $rec, $lib );
1261 while ( !$r->complete ) {
1262 $xml .= join('', map {$_->content} $r->recv);
1264 $xml .= join('', map {$_->content} $r->recv);
1265 $node->add_holdings($xml);
1268 $node->id($item_tag);
1269 #$node->update_ts(clense_ISO8601($record->edit_date));
1270 $node->link(alternate => $feed->unapi . "?id=$item_tag&format=htmlholdings-full" => 'text/html') if ($flesh);
1271 $node->link(opac => $feed->unapi . "?id=$item_tag&format=opac") if ($flesh);
1272 $node->link(unapi => $feed->unapi . "?id=$item_tag") if ($flesh);
1273 $node->link('unapi-id' => $item_tag) if ($flesh);
1281 return Apache2::Const::DECLINED if (-e $apache->filename);
1284 my $year = (gmtime())[5] + 1900;
1286 my $host = $cgi->virtual_host || $cgi->server_name;
1289 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
1290 my $rel_name = $cgi->url(-relative=>1);
1291 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
1294 my $url = $cgi->url(-path_info=>$add_path);
1295 my $root = (split 'browse', $url)[0];
1296 my $base = (split 'browse', $url)[0] . 'browse';
1297 my $unapi = (split 'browse', $url)[0] . 'unapi';
1299 my $path = $cgi->path_info;
1302 my ($format,$axis,$site,$string,$page,$page_size) = split '/', $path;
1303 #warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
1305 return item_age_browse($apache) if ($axis eq 'item-age'); # short-circut to the item-age sub
1307 $site ||= $cgi->param('searchOrg');
1308 $page ||= $cgi->param('startPage') || 0;
1309 $page_size ||= $cgi->param('count') || 9;
1311 $page = 0 if ($page !~ /^-?\d+$/);
1313 my $prev = join('/', $base,$format,$axis,$site,$string,$page - 1,$page_size);
1314 my $next = join('/', $base,$format,$axis,$site,$string,$page + 1,$page_size);
1316 unless ($string and $axis and grep { $axis eq $_ } keys %browse_types) {
1317 warn "something's wrong...";
1318 warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
1322 $string = decode_utf8($string);
1323 $string =~ s/\+/ /go;
1326 my $tree = $supercat->request(
1327 "open-ils.supercat.$axis.browse",
1334 (my $norm_format = $format) =~ s/-full$//o;
1336 my ($header,$content) = $browse_types{$axis}{$norm_format}->($tree,$prev,$next,$format,$unapi,$base,$site);
1337 print $header.$content;
1338 return Apache2::Const::OK;
1341 sub item_age_browse {
1343 return Apache2::Const::DECLINED if (-e $apache->filename);
1346 my $year = (gmtime())[5] + 1900;
1348 my $host = $cgi->virtual_host || $cgi->server_name;
1351 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
1352 my $rel_name = $cgi->url(-relative=>1);
1353 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
1356 my $url = $cgi->url(-path_info=>$add_path);
1357 my $root = (split 'browse', $url)[0];
1358 my $base = (split 'browse', $url)[0] . 'browse';
1359 my $unapi = (split 'browse', $url)[0] . 'unapi';
1361 my $path = $cgi->path_info;
1364 my ($format,$axis,$site,$page,$page_size) = split '/', $path;
1365 #warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
1367 unless ($axis eq 'item-age') {
1368 warn "something's wrong...";
1369 warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
1373 $site ||= $cgi->param('searchOrg') || '-';
1374 $page ||= $cgi->param('startPage') || 1;
1375 $page_size ||= $cgi->param('count') || 10;
1377 $page = 1 if ($page !~ /^-?\d+$/ || $page < 1);
1379 my $prev = join('/', $base,$format,$axis,$site,$page - 1,$page_size);
1380 my $next = join('/', $base,$format,$axis,$site,$page + 1,$page_size);
1382 my $recs = $supercat->request(
1383 "open-ils.supercat.new_book_list",
1389 (my $norm_format = $format) =~ s/-full$//o;
1391 my ($header,$content) = $browse_types{$axis}{$norm_format}->($recs,$prev,$next,$format,$unapi,$base,$site);
1392 print $header.$content;
1393 return Apache2::Const::OK;
1396 our %qualifier_map = (
1398 # Some EG qualifiers
1399 'eg.site' => 'site',
1400 'eg.sort' => 'sort',
1401 'eg.direction' => 'dir',
1402 'eg.available' => 'available',
1405 'eg.title' => 'title',
1406 'dc.title' => 'title',
1407 'bib.titleabbreviated' => 'title|abbreviated',
1408 'bib.titleuniform' => 'title|uniform',
1409 'bib.titletranslated' => 'title|translated',
1410 'bib.titlealternative' => 'title',
1411 'bib.titleseries' => 'series',
1412 'eg.series' => 'title',
1414 # Author/Name class:
1415 'eg.author' => 'author',
1416 'eg.name' => 'author',
1417 'creator' => 'author',
1418 'dc.creator' => 'author',
1419 'dc.contributer' => 'author',
1420 'dc.publisher' => 'keyword',
1421 'bib.name' => 'author',
1422 'bib.namepersonal' => 'author|personal',
1423 'bib.namepersonalfamily'=> 'author|personal',
1424 'bib.namepersonalgiven' => 'author|personal',
1425 'bib.namecorporate' => 'author|corporate',
1426 'bib.nameconference' => 'author|conference',
1429 'eg.subject' => 'subject',
1430 'dc.subject' => 'subject',
1431 'bib.subjectplace' => 'subject|geographic',
1432 'bib.subjecttitle' => 'keyword',
1433 'bib.subjectname' => 'subject|name',
1434 'bib.subjectoccupation' => 'keyword',
1437 'eg.keyword' => 'keyword',
1438 'srw.serverchoice' => 'keyword',
1441 'dc.identifier' => 'keyword',
1444 'bib.dateissued' => undef,
1445 'bib.datecreated' => undef,
1446 'bib.datevalid' => undef,
1447 'bib.datemodified' => undef,
1448 'bib.datecopyright' => undef,
1454 'dc.format' => undef,
1457 'bib.genre' => 'keyword',
1460 'bib.audience' => undef,
1463 'bib.originplace' => undef,
1466 'dc.language' => 'lang',
1469 'bib.edition' => 'keyword',
1472 'bib.volume' => 'keyword',
1473 'bib.issue' => 'keyword',
1474 'bib.startpage' => 'keyword',
1475 'bib.endpage' => 'keyword',
1478 'bib.issuance' => 'keyword',
1481 our %qualifier_ids = (
1482 eg => 'http://open-ils.org/spec/SRU/context-set/evergreen/v1',
1483 dc => 'info:srw/cql-context-set/1/dc-v1.1',
1484 bib => 'info:srw/cql-context-set/1/bib-v1.0',
1488 our %nested_qualifier_map = (
1490 site => ['site','Evergreen Site Code (shortname)'],
1491 sort => ['sort','Sort on relevance, title, author, pubdate, create_date or edit_date'],
1492 direction => ['dir','Sort direction (asc|desc)'],
1493 available => ['available','Filter to available (true|false)'],
1495 author => ['author'],
1497 subject => ['subject'],
1498 keyword => ['keyword'],
1499 series => ['series'],
1503 creator => ['author'],
1504 contributor => ['author'],
1505 publisher => ['keyword'],
1506 subject => ['subject'],
1507 identifier => ['keyword'],
1510 language => ['lang'],
1514 titleAbbreviated => ['title'],
1515 titleUniform => ['title'],
1516 titleTranslated => ['title'],
1517 titleAlternative => ['title'],
1518 titleSeries => ['series'],
1520 # Author/Name class:
1522 namePersonal => ['author'],
1523 namePersonalFamily => ['author'],
1524 namePersonalGiven => ['author'],
1525 nameCorporate => ['author'],
1526 nameConference => ['author'],
1529 subjectPlace => ['subject'],
1530 subjectTitle => ['keyword'],
1531 subjectName => ['subject|name'],
1532 subjectOccupation => ['keyword'],
1537 dateIssued => [undef],
1538 dateCreated => [undef],
1539 dateValid => [undef],
1540 dateModified => [undef],
1541 dateCopyright => [undef],
1544 genre => ['keyword'],
1547 audience => [undef],
1550 originPlace => [undef],
1553 edition => ['keyword'],
1556 volume => ['keyword'],
1557 issue => ['keyword'],
1558 startPage => ['keyword'],
1559 endPage => ['keyword'],
1562 issuance => ['keyword'],
1565 serverChoice => ['keyword'],
1570 my $base_explain = <<XML;
1572 id="evergreen-sru-explain-full"
1573 authoritative="true"
1574 xmlns:z="http://explain.z3950.org/dtd/2.0/"
1575 xmlns="http://explain.z3950.org/dtd/2.0/">
1576 <serverInfo transport="http" protocol="SRU" version="1.1">
1583 <title primary="true"/>
1584 <description primary="true"/>
1588 <set identifier="info:srw/cql-context-set/1/cql-v1.2" name="cql"/>
1593 identifier="info:srw/schema/1/marcxml-v1.1"
1594 location="http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
1598 <title>MARC21Slim (marcxml)</title>
1603 <default type="numberOfRecords">50</default>
1604 <default type="contextSet">eg</default>
1605 <default type="index">keyword</default>
1606 <default type="relation">all</default>
1607 <default type="sortSchema">marcxml</default>
1608 <default type="retrieveSchema">marcxml</default>
1609 <setting type="maximumRecords">50</setting>
1610 <supports type="relationModifier">relevant</supports>
1611 <supports type="relationModifier">stem</supports>
1612 <supports type="relationModifier">fuzzy</supports>
1613 <supports type="relationModifier">word</supports>
1624 my $req = SRU::Request->newFromCGI( $cgi );
1625 my $resp = SRU::Response->newFromRequest( $req );
1627 # Find the org_unit shortname, if passed as part of the URL
1628 # http://example.com/opac/extras/sru/SHORTNAME
1629 my $url = $cgi->path_info;
1630 my ($shortname, $holdings) = $url =~ m#/?([^/]*)(/holdings)?#;
1632 if ( $resp->type eq 'searchRetrieve' ) {
1633 my $cql_query = decode_utf8($req->query);
1634 my $search_string = decode_utf8($req->cql->toEvergreen);
1636 # Ensure the search string overrides the default site
1637 if ($shortname and $search_string !~ m#site:#) {
1638 $search_string .= " site:$shortname";
1641 my $offset = $req->startRecord;
1642 $offset-- if ($offset);
1645 my $limit = $req->maximumRecords;
1648 $log->info("SRU search string [$cql_query] converted to [$search_string]\n");
1650 my $recs = $search->request(
1651 'open-ils.search.biblio.multiclass.query' => {offset => $offset, limit => $limit} => $search_string => 1
1654 my $bre = $supercat->request( 'open-ils.supercat.record.object.retrieve' => [ map { $_->[0] } @{$recs->{ids}} ] )->gather(1);
1656 foreach my $record (@$bre) {
1657 my $marcxml = $record->marc;
1658 # Make the beast conform to a VDX-supported format
1659 # See http://vdxipedia.oclc.org/index.php/Holdings_Parsing
1660 # Trying to implement LIBSOL_852_A format; so much for standards
1662 my $bib_holdings = $supercat->request('open-ils.supercat.record.basic_holdings.retrieve', $record->id, $shortname || '-')->gather(1);
1663 my $marc = MARC::Record->new_from_xml($marcxml, 'UTF8', 'XML');
1665 # Expects the record ID in the 001
1666 $marc->delete_field($_) for ($marc->field('001'));
1667 if (!$marc->field('001')) {
1668 $marc->insert_fields_ordered(
1669 MARC::Field->new( '001', $record->id )
1672 foreach my $cn (keys %$bib_holdings) {
1673 foreach my $cp (@{$bib_holdings->{$cn}->{'copies'}}) {
1674 $marc->insert_fields_ordered(
1677 a => $cp->{'location'},
1678 b => $bib_holdings->{$cn}->{'owning_lib'},
1680 d => $cp->{'circlib'},
1681 g => $cp->{'barcode'},
1682 n => $cp->{'status'},
1688 $marc->delete_field( $_ ) for ($marc->field(901));
1689 $marc->append_fields(
1692 a => $record->tcn_value,
1693 b => $record->tcn_source,
1698 $marcxml = $marc->as_xml_record();
1699 $marcxml =~ s/^<\?xml version="1.0" encoding="UTF-8"\?>//o;
1703 SRU::Response::Record->new(
1704 recordSchema => 'info:srw/schema/1/marcxml-v1.1',
1705 recordData => $marcxml,
1706 recordPosition => ++$offset
1711 $resp->numberOfRecords($recs->{count});
1713 } elsif ( $resp->type eq 'explain' ) {
1715 my $host = $cgi->virtual_host || $cgi->server_name;
1718 if ( $cgi->server_software !~ m|^Apache/2.2| ) {
1719 my $rel_name = $cgi->url(-relative=>1);
1720 $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
1722 my $base = $cgi->url(-base=>1);
1723 my $url = $cgi->url(-path_info=>$add_path);
1724 $url =~ s/^$base\///o;
1726 my $doc = $parser->parse_string($base_explain);
1727 my $e = $doc->documentElement;
1728 $e->findnodes('/z:explain/z:serverInfo/z:host')->shift->appendText( $host );
1729 $e->findnodes('/z:explain/z:serverInfo/z:port')->shift->appendText( $cgi->server_port );
1730 $e->findnodes('/z:explain/z:serverInfo/z:database')->shift->appendText( $url );
1732 for my $name ( keys %OpenILS::WWW::SuperCat::nested_qualifier_map ) {
1734 my $identifier = $OpenILS::WWW::SuperCat::qualifier_ids{ $name };
1736 next unless $identifier;
1738 my $set_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'set' );
1739 $set_node->setAttribute( identifier => $identifier );
1740 $set_node->setAttribute( name => $name );
1742 $e->findnodes('/z:explain/z:indexInfo')->shift->appendChild( $set_node );
1744 for my $index ( keys %{ $OpenILS::WWW::SuperCat::nested_qualifier_map{$name} } ) {
1745 my $desc = $OpenILS::WWW::SuperCat::nested_qualifier_map{$name}{$index}[1] || $index;
1747 my $name_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'name' );
1749 my $map_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'map' );
1750 $map_node->appendChild( $name_node );
1752 my $title_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'title' );
1754 my $index_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'index' );
1755 $index_node->appendChild( $title_node );
1756 $index_node->appendChild( $map_node );
1758 $index_node->setAttribute( id => $name . '.' . $index );
1759 $title_node->appendText( $desc );
1760 $name_node->setAttribute( set => $name );
1761 $name_node->appendText($index );
1763 $e->findnodes('/z:explain/z:indexInfo')->shift->appendChild( $index_node );
1767 $ex_doc = $e->toString;
1771 SRU::Response::Record->new(
1772 recordSchema => 'info:srw/cql-context-set/2/zeerex-1.1',
1773 recordData => $ex_doc
1778 print $cgi->header( -type => 'application/xml' );
1779 print $U->entityize($resp->asXML) . "\n";
1780 return Apache2::Const::OK;
1785 package CQL::BooleanNode;
1789 my $left = $self->left();
1790 my $right = $self->right();
1791 my $leftStr = $left->toEvergreen;
1792 my $rightStr = $right->toEvergreen();
1794 my $op = '||' if uc $self->op() eq 'OR';
1797 return "$leftStr $rightStr";
1800 package CQL::TermNode;
1804 my $qualifier = $self->getQualifier();
1805 my $term = $self->getTerm();
1806 my $relation = $self->getRelation();
1810 my ($qset, $qname) = split(/\./, $qualifier);
1812 $log->debug("SRU toEvergreen: $qset, $qname $OpenILS::WWW::SuperCat::nested_qualifier_map{$qset}{$qname}[0]\n");
1814 if ( exists($OpenILS::WWW::SuperCat::nested_qualifier_map{$qset}{$qname}) ) {
1815 $qualifier = $OpenILS::WWW::SuperCat::nested_qualifier_map{$qset}{$qname}[0] || 'kw';
1818 my @modifiers = $relation->getModifiers();
1820 my $base = $relation->getBase();
1821 if ( grep { $base eq $_ } qw/= scr exact all/ ) {
1824 foreach my $m ( @modifiers ) {
1825 if( grep { $m->[ 1 ] eq $_ } qw/cql.fuzzy cql.stem cql.relevant cql.word/ ) {
1831 $quote_it = 0 if ( $base eq 'all' );
1832 $term = maybeQuote($term) if $quote_it;
1835 croak( "Evergreen doesn't support the $base relations" );
1843 return "$qualifier:$term";
1849 # vim: noet:ts=4:sw=4