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 # Given a string such as a user might type into a search box, prepare
53 # it for to_tsquery(). See
54 # http://www.postgresql.org/docs/9.0/static/textsearch-controls.html
55 sub prepare_for_tsquery {
58 $str =~ s/[^\w\s]/ /ig;
59 $str .= ":*" unless $str =~ /\s$/;
61 return join(" & ", grep(length, split(/\s+/, $str)));
64 # The third argument to our stored procedure, metabib.suggest_browse_entries(),
65 # is passed through directly to ts_headline() as the 'options' arugment.
66 sub prepare_headline_opts {
67 my ($css_prefix, $highlight_min, $highlight_max, $short_word_length) = @_;
69 $css_prefix =~ s/[^\w]//g;
72 qq{StartSel="<span class='$css_prefix'>"},
76 push @parts, "MinWords=$highlight_min" if $highlight_min > 0;
77 push @parts, "MaxWords=$highlight_max" if $highlight_max > 0;
78 push @parts, "ShortWord=$short_word_length" if defined $short_word_length;
80 return join(", ", @parts);
83 # Get raw autosuggest data (rows returned from a stored procedure) from the DB.
87 my $search_class = shift;
89 my $css_prefix = shift || 'oils_AS';
90 my $highlight_min = int(shift || 0);
91 my $highlight_max = int(shift || 0);
92 my $short_word_length = shift;
94 my $normalization = int(shift || 14); # 14 is not totally arbitrary.
95 # See http://www.postgresql.org/docs/9.0/static/textsearch-controls.html#TEXTSEARCH-RANKING
97 my $limit = int(shift || 10);
99 $limit = 10 unless $limit > 0;
101 my $headline_opts = prepare_headline_opts(
102 $css_prefix, $highlight_min, $highlight_max,
103 defined $short_word_length ? int($short_word_length) : undef
106 return $editor->json_query({
108 "metabib.suggest_browse_entries",
109 prepare_for_tsquery($query),
119 sub suggestions_to_xml {
120 my ($suggestions) = @_;
122 my $dom = new XML::LibXML::Document("1.0", "UTF-8");
123 my $as = $dom->createElement("as");
124 $dom->setDocumentElement($as);
126 foreach (@$suggestions) {
127 my $val = $dom->createElement("val");
128 $val->setAttribute("term", $_->{value});
129 $val->setAttribute("field", $_->{field});
130 $val->appendText($_->{match});
134 # XML::LibXML::Document::toString() returns an encoded byte string, which
135 # is why we don't need to binmode STDOUT, ':utf8'.
136 return $dom->toString();
139 sub suggestions_to_json {
140 my ($suggestions) = @_;
142 return OpenSRF::Utils::JSON->perl2JSON({
145 +{ term => $_->{value}, field => $_->{field},
146 match => $_->{match} }
152 # Given data and the Apache request object, this sub picks a sub from a
153 # dispatch table based on the list of content-type encodings that the client
154 # has indicated it will accept, and calls that sub, which will deliver
155 # a response of appropriately encoded data.
159 foreach my $media_range (split /,/, $r->headers_in->{Accept}) {
160 $media_range =~ s/;.+$//; # keep type, subtype. lose parameters.
163 Text::Glob::match_glob($media_range, $_)
164 } @_output_handler_types;
167 return $_output_handler_dispatch->{$match}{code}->($r, $data);
171 return Apache2::Const::HTTP_NOT_ACCEPTABLE;
178 my $editor = new_editor;
179 my $suggestions = get_suggestions(
181 map { scalar($cgi->param($_)) } qw(
194 if (not $suggestions) {
196 "get_suggestions() failed: " . $editor->die_event->{textcode}
198 return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
203 return output_handler($r, $suggestions);