1 package OpenILS::WWW::AutoSuggest;
7 use Apache2::Const -compile => qw(
8 OK HTTP_NOT_ACCEPTABLE HTTP_INTERNAL_SERVER_ERROR :log
12 use CGI qw(:all -utf8);
14 use OpenSRF::Utils::JSON;
15 use OpenILS::Utils::CStoreEditor qw/:funcs/;
17 # BEGIN package globals
19 # We'll probably never need this fanciness for autosuggest, but
20 # you can add handlers for different requested content-types here, and
21 # you can weight them to control what matches requests for things like
24 my $_output_handler_dispatch = {
25 "application/xml" => {
29 $r->content_type("application/xml; charset=utf-8");
30 print suggestions_to_xml($data);
31 return Apache2::Const::OK;
34 "application/json" => {
38 $r->content_type("application/json; charset=utf-8");
39 print suggestions_to_json($data);
40 return Apache2::Const::OK;
45 my @_output_handler_types = sort {
46 $_output_handler_dispatch->{$a}->{prio} <=>
47 $_output_handler_dispatch->{$b}->{prio}
48 } keys %$_output_handler_dispatch;
52 # The third argument to our stored procedure, metabib.suggest_browse_entries(),
53 # is passed through directly to ts_headline() as the 'options' arugment.
54 sub prepare_headline_opts {
55 my ($css_prefix, $highlight_min, $highlight_max, $short_word_length) = @_;
57 $css_prefix =~ s/[^\w]//g;
60 qq{StartSel="<span class='$css_prefix'>"},
64 push @parts, "MinWords=$highlight_min" if $highlight_min > 0;
65 push @parts, "MaxWords=$highlight_max" if $highlight_max > 0;
66 push @parts, "ShortWord=$short_word_length" if defined $short_word_length;
68 return join(", ", @parts);
71 # Get raw autosuggest data (rows returned from a stored procedure) from the DB.
75 my $search_class = shift;
77 my $css_prefix = shift || 'oils_AS';
78 my $highlight_min = int(shift || 0);
79 my $highlight_max = int(shift || 0);
80 my $short_word_length = shift;
82 my $normalization = int(shift || 14); # 14 is not totally arbitrary.
83 # See http://www.postgresql.org/docs/9.0/static/textsearch-controls.html#TEXTSEARCH-RANKING
85 my $limit = int(shift || 10);
87 $limit = 10 unless $limit > 0;
89 my $headline_opts = prepare_headline_opts(
90 $css_prefix, $highlight_min, $highlight_max,
91 defined $short_word_length ? int($short_word_length) : undef
94 return $editor->json_query({
96 "metabib.suggest_browse_entries",
107 sub suggestions_to_xml {
108 my ($suggestions) = @_;
110 my $dom = new XML::LibXML::Document("1.0", "UTF-8");
111 my $as = $dom->createElement("as");
112 $dom->setDocumentElement($as);
114 foreach (@$suggestions) {
115 my $val = $dom->createElement("val");
116 $val->setAttribute("term", $_->{value});
117 $val->setAttribute("field", $_->{field});
118 $val->appendText($_->{match});
122 # XML::LibXML::Document::toString() returns an encoded byte string, which
123 # is why we don't need to binmode STDOUT, ':utf8'.
124 return $dom->toString();
127 sub suggestions_to_json {
128 my ($suggestions) = @_;
130 return OpenSRF::Utils::JSON->perl2JSON({
133 +{ term => $_->{value}, field => $_->{field},
134 match => $_->{match} }
140 # Given data and the Apache request object, this sub picks a sub from a
141 # dispatch table based on the list of content-type encodings that the client
142 # has indicated it will accept, and calls that sub, which will deliver
143 # a response of appropriately encoded data.
147 foreach my $media_range (split /,/, $r->headers_in->{Accept}) {
148 $media_range =~ s/;.+$//; # keep type, subtype. lose parameters.
151 Text::Glob::match_glob($media_range, $_)
152 } @_output_handler_types;
155 return $_output_handler_dispatch->{$match}{code}->($r, $data);
159 return Apache2::Const::HTTP_NOT_ACCEPTABLE;
166 my $editor = new_editor;
167 my $suggestions = get_suggestions(
169 map { scalar($cgi->param($_)) } qw(
182 if (not $suggestions) {
184 "get_suggestions() failed: " . $editor->die_event->{textcode}
186 return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
191 return output_handler($r, $suggestions);