Added boolean search tab to advanced search, translates boolean key words into the...
authorFredrick Parks <fparks@catalystitservices.com>
Wed, 1 May 2013 17:00:19 +0000 (10:00 -0700)
committerKathy Lussier <klussier@masslnc.org>
Fri, 2 Dec 2016 14:50:41 +0000 (09:50 -0500)
Signed-off-by: Fredrick Parks <fparks@catalystitservices.com>
Signed-off-by: Kathy Lussier <klussier@masslnc.org>
Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Search.pm
Open-ILS/src/templates/opac/advanced.tt2
Open-ILS/src/templates/opac/css/style.css.tt2
Open-ILS/src/templates/opac/parts/advanced/boolean.tt2 [new file with mode: 0644]
Open-ILS/src/templates/opac/parts/config.tt2
Open-ILS/src/templates/opac/parts/header.tt2
Open-ILS/src/templates/opac/parts/searchbar.tt2
Open-ILS/web/opac/locale/en-US/opac.dtd

index 7ee9cfa..1c71876 100644 (file)
@@ -6,6 +6,7 @@ use OpenILS::Utils::CStoreEditor qw/:funcs/;
 use OpenILS::Utils::Fieldmapper;
 use OpenILS::Application::AppUtils;
 use OpenSRF::Utils::JSON;
+use OpenSRF::Utils::Cache;
 use Data::Dumper;
 $Data::Dumper::Indent = 0;
 my $U = 'OpenILS::Application::AppUtils';
@@ -14,7 +15,7 @@ sub _prepare_biblio_search_basics {
     my ($cgi) = @_;
 
     return $cgi->param('query') unless $cgi->param('qtype');
-
+    $logger->info(Dumper($cgi->param('query')));
     my %parts;
     my @part_names = qw/qtype contains query bool/;
     $parts{$_} = [ $cgi->param($_) ] for (@part_names);
@@ -31,7 +32,6 @@ sub _prepare_biblio_search_basics {
             $qtype = 'title';
             $jtitle = 1;
         }
-
         # This stuff probably will need refined or rethought to better handle
         # the weird things Real Users will surely type in.
         $contains = "" unless defined $contains; # silence warning
@@ -66,13 +66,89 @@ sub _prepare_biblio_search_basics {
     return $full_query;
 }
 
-sub _prepare_biblio_search {
+########################################################################
+# Replaces boolean search keywords [and, or, not] with the appropriate
+# tokens [&&, ||, -], and returns the modified query.
+# Keywords can be localised, localization files are located in
+# templates/opac/location
+########################################################################
+sub _prepare_biblio_search_boolean {
     my ($cgi, $ctx) = @_;
+    my $cache = OpenSRF::Utils::Cache->new;
+    my $i18n;
+    my $location = $ctx->{locale};
+    #Reformat location abreveation [en_us -> en-US]
+    $location =~ s/(\w\w)_(\w\w)/$1\-\U$2\E/g;
+    
+    #Check cache for localization valuse for the current locale
+    $i18n = $cache->get_cache("boolsearchlocale.${location}");
+    #unless there is a cached set of values load from disk and add to cache
+    unless (defined($i18n)){
+    #Else if the isn't: load values from disk and cache values
+        $i18n = {};
+        my $conf = OpenSRF::Utils::SettingsClient->new();
+        my $dir = $conf->config_value("dirs", "script");
+        my @foundFiles = glob("$dir/web/opac/locale/${location}/opac.dtd");
+        my $string_bundle = shift @foundFiles;
+        { #if file can not be opened we will just break out of this block
+            #open locale file or break out of the block
+            open(I18NFH, '<', $string_bundle) or last;
+            local $/ = undef;
+            my $file = <I18NFH>;
+            close(I18NFH);
+            
+            #Regex for the keywords and, or, not
+            ( $i18n->{"AND"} ) = $file =~ /opac\.boolean\.keyword\.and "(.+)"/;
+            ( $i18n->{"OR"} ) = $file =~ /opac\.boolean\.keyword\.or "(.+)"/;
+            ( $i18n->{"NOT"} ) = $file =~ /opac\.boolean\.keyword\.not "(.+)"/;
+        }
+        #if AND keyword was not in opac.dtd or file couldn't be opened default to english
+        if(!defined($i18n->{"AND"}))
+        {
+            $logger->info("[ERROR] Boolean Search: AND keyword not found in opac.dtd, defaulting to english");
+            $i18n->{"AND"} = "and";
+        }
+        #if OR keyword was not in opac.dtd or file couldn't be opened default to english
+        if(!defined($i18n->{"OR"}))
+        {
+            $logger->info("[ERROR] Boolean Search: OR keyword not found in opac.dtd, defaulting to english");
+            $i18n->{"OR"} = "or";
+        }
+        #if NOT keyword was not in opac.dtd or file couldn't be opened default to english
+        if(!defined($i18n->{"NOT"}))
+        {
+            $logger->info("[ERROR] Boolean Search: NOT keyword not found in opac.dtd, defaulting to english");
+            $i18n->{"NOT"} = "not";
+        }
+        #add localization keywords to cache
+        $cache->put_cache("boolsearchlocale.${location}", $i18n);
+    }
+    
+    my $and = $i18n->{"AND"};
+    my $or = $i18n->{"OR"};
+    my $not = $i18n->{"NOT"};
+    #replace keywords in the submited query with the boolean tokens 
+    my $queryStatement = $cgi->param('boolean_query');
+    #RegEx selects the l18n keyword in the query and substatutes in '&&'
+    $queryStatement =~ s/((?<=" )(\b$and\b)|(\b$and\b)((?= ")|(?=(\w|\s)+((\b($and|$or|$not)\b)(?![^"]+(\w"))))|(?=[^"]+$)))/\&\&/gi;
+    #RegEx selects the l18n keyword in the query and substatutes in '||'
+    $queryStatement =~ s/((?<=" )(\b$or\b)|(\b$or\b)((?= ")|(?=(\w|\s)+((\b($and|$or|$not})\b)(?![^"]+(\w"))))|(?=[^"]+$)))/\|\|/gi;
+    #RegEx selects the l18n keyword and all trailing white space in the query and substatutes in '-'
+    $queryStatement =~ s/((?<=" )(\b$not\s+)|(\b$not\s+)((?= ")|(?=(\w|\s)+((\b($and|$or|$not)\b)(?![^"]+(\w"))))|(?=[^"]+$)))/\-/gi;
+    $ctx->{query} = $queryStatement;
+    return $queryStatement;
+}
 
-    my $query = _prepare_biblio_search_basics($cgi) || '';
+sub _prepare_biblio_search {
+    my ($cgi, $ctx) = @_;
 
+    my $query;
+    if ($cgi->param('boolean')) {
+        $query = _prepare_biblio_search_boolean($cgi,$ctx);
+    }else {
+        $query = _prepare_biblio_search_basics($cgi) || '';
+    }
     $query .= ' ' . $ctx->{global_search_filter} if $ctx->{global_search_filter};
-
     foreach ($cgi->param('modifier')) {
         # The unless bit is to avoid stacking modifiers.
         $query = ('#' . $_ . ' ' . $query) unless 
@@ -390,7 +466,6 @@ sub load_rresults {
     }
 
     my ($query, $site, $depth) = _prepare_biblio_search($cgi, $ctx);
-
     $self->get_staff_search_settings;
 
     if (!$find_last and $ctx->{staff_saved_search_size}) {
index de86006..c90635d 100644 (file)
                 <a href="[% mkurl('', {pane => 'expert'}) %]"
                     [% IF pane == 'expert' %]class="on" [% END %]
                     id="expert_search">[% l('Expert Search') %]</a>
+                [% IF search.boolean_enabled == "true" %]
+                               <a href="[% mkurl('', {pane => 'boolean'}) %]"
+                    [% IF pane == 'boolean' %]class="on" [% END %]
+                    id="boolean_search">[% l('Boolean Search') %]</a>
+                [% END %]
             </div>
         </div>
     </div>
@@ -40,6 +45,8 @@
             [% INCLUDE "opac/parts/advanced/numeric.tt2" %]
             [% ELSIF pane == 'expert' %]
             [% INCLUDE "opac/parts/advanced/expert.tt2" %]
+                       [% ELSIF pane == 'boolean' %]
+            [% INCLUDE "opac/parts/advanced/boolean.tt2" %]
             [% END %]
             </div>
             <div class="common-full-pad"></div>        
index 8a3c4f7..59a92b5 100644 (file)
@@ -338,7 +338,7 @@ for now until a better color is picked - if needed.
     padding-left: 10px !important;
 }
 
-#adv_search.on, #num_search.on, #expert_search.on {
+#adv_search.on, #num_search.on, #expert_search.on, #boolean_search.on {
     color: [% css_colors.accent_darker %];
     background: [% css_colors.background %];
     text-decoration: none;
diff --git a/Open-ILS/src/templates/opac/parts/advanced/boolean.tt2 b/Open-ILS/src/templates/opac/parts/advanced/boolean.tt2
new file mode 100644 (file)
index 0000000..3b4e36c
--- /dev/null
@@ -0,0 +1,79 @@
+[% query = CGI.param('boolean_query'); %]
+<form action="[% ctx.opac_root %]/results" method="GET">
+       <div style="width:100%;" class="header_middle">[% l("Free-Form Boolean Search") %]</div>
+       <input class="hidden" name="boolean" class="hidden" value=true/>
+       <input type="text" name="boolean_query" size="50" value="[% query | html %]" autofocus />
+    <div style="width:100%;" class="header_middle">[% l('Search Filters') %]</div>
+       <table cellpadding='10' cellspacing='0' border='0'>
+       [%
+               in_row = 0;
+               FOR adv_chunk IN search.bool_config;
+                       NEXT IF adv_chunk.adv_hide;
+                       IF in_row == 0;
+                               in_row = 1; %]
+                               <tr>
+       [%
+                       END; %]
+                               <td valign='top'>
+                                       <strong>[% adv_chunk.adv_label %]</strong><br />
+       [%
+                       IF adv_chunk.adv_special;
+                               SWITCH adv_chunk.adv_special;
+                                       CASE "lib_selector";
+                                               PROCESS "opac/parts/org_selector.tt2";
+                                                       INCLUDE build_org_selector show_loc_groups=1; %]
+                                                               <div style="position:relative;top:7px;">
+                                                                       <input type='checkbox' name="modifier"
+                                                                               value="available"[% CGI.param('modifier').grep('available').size ? ' checked="checked"' : '' %]
+                                                                               id='opac.result.limit2avail' />
+                                                                       <label style="position:relative;top:-2px;"
+                                                                               for='opac.result.limit2avail'>
+                                                                               [% l("Limit to Available") %]</label>
+                                                               </div>
+       [%
+                                       CASE "pub_year"; %]
+                                                               <select name='pubdate' onchange='
+                                                                       if(this.selectedIndex == 3)
+                                                                               unHideMe($("adv_global_pub_date_2_span"));
+                                                                       else
+                                                                               hideMe($("adv_global_pub_date_2_span"));'>
+                                                                               [%  FOR opt IN [
+                                                                                               {"code" => "is", "label" => l("Is")},
+                                                                                               {"code" => "before", "label" => l("Before")},
+                                                                                               {"code" => "after", "label" => l("After")},
+                                                                                               {"code" => "between", "label" => l("Between")} ] %]
+                                                                                       <option value="[% opt.code %]"[% CGI.param('pubdate') == opt.code ? ' selected="selected"' : '' %]>[% opt.label | html %]</option>
+                                                                               [%  END %]
+                                                               </select>    
+                                                               <div style='margin-top:5px;'>
+                                                                       <input name='date1' type='text' size='4' maxlength='4' value="[% CGI.param('date1') | html %]" />
+                                                                       <span id='adv_global_pub_date_2_span' class='[% CGI.param("pubdate") == "between" ? "" : "hide_me" %]'>
+                                                                          [% l("and") %] <input name='date2' type='text' size='4' maxlength='4' value="[% CGI.param('date2') | html %]" />
+                                                                       </span>
+                                                               </div>
+       [%
+                                       CASE "sort_selector";
+                                               INCLUDE "opac/parts/filtersort.tt2"
+                                                       value=CGI.param('sort') class='results_header_sel';
+                               END;
+                       ELSIF adv_chunk.adv_attr;
+                               INCLUDE "opac/parts/coded_value_selector.tt2"
+                                       attr=adv_chunk.adv_attr multiple="multiple" size="4";
+                       END; %]
+                               </td>
+       [%
+                       IF adv_chunk.adv_break;
+                               in_row = 0; %]
+                               </tr>
+       [%
+                       END;
+               END; %]
+       </table>
+    <span>
+        <input id='search-submit-go' type="submit" value="[% l('Search') %]" alt="[% l('Search') %]" class="opac-button"
+                onclick='setTimeout(function(){$("search-submit-spinner").className=""; $("search-submit-go").className="hidden"}, 2000)'/>
+            <img id='search-submit-spinner' src='/opac/images/progressbar_green.gif' style='height:16px;width:16px;' class='hidden' alt=''/>
+          </span>
+    <a href="[% mkurl(ctx.opac_root _ '/advanced', {$loc_name => loc_value, pane => 'boolean'}, 1) %]"
+        class="pointer opac-button">[% l('Clear Form') %]</a>
+</form>
\ No newline at end of file
index 58d2c09..5d6e5ff 100644 (file)
@@ -145,6 +145,32 @@ search.adv_config = [
 #search.default_adv_select_height = 4;
 
 ##############################################################################
+# Define the boolean search limiters and labels.
+# adv_label is the (translated) label for the limiter
+# adv_attr is an array of possible limiters, the first one that has any
+#   values will be used
+# adv_break will end the current row. If specified with a label/attr it
+#   will do so *after* that limiter.
+# adv_special will drop in a special entry:
+#   sort_selector will put the sort results selector
+#   lib_selector will put the search library box (with limit to available)
+#   pub_year will put the publication year box
+
+search.bool_config = [
+    {adv_label => l("Item Type"), adv_attr => ["mattype", "item_type"]},
+    {adv_label => l("Language"),  adv_attr => "item_lang"},
+    {adv_label => l("Sort Results"), adv_special => "sort_selector", adv_break => 1},
+    {adv_label => l("Search Library"), adv_special => "lib_selector"},
+    {adv_label => l("Publication Year"), adv_special => "pub_year"},
+];
+
+##############################################################################
+# Define if the boolean search tab is enabled on the
+# advanced search page
+
+search.boolean_enabled = "true";
+
+##############################################################################
 # For each search box the default "query type" value can be specified here
 # This is the actual backend value, not the label
 # Also note that including more than the row count entries won't add rows
index 8b60ba9..00dc983 100644 (file)
@@ -11,6 +11,7 @@
 
     is_advanced = CGI.param("_adv").size;
     is_special = CGI.param("_special").size;
+    is_boolean = CGI.param("boolean").size;
 
     # Check if we want to show the detail record view.  Doing this
     # here because we don't want to repeat logic in multiple other
index a48c4a0..b48ccd4 100644 (file)
             <label id="search_box_label" for="search_box">[% l('Search: ') %]
             <input type="text" id="search_box" name="query" aria-label="[%
                     l('Enter search query:');
-                %]" value="[% is_advanced ? ctx.naive_query_scrub(ctx.processed_search_query) : CGI.param('query') | html %]"
+                %]" value="[% IF is_boolean; -%]
+                    [%- ctx.query | html; -%]
+                [%- END; -%]
+                [%- is_advanced ? ctx.naive_query_scrub(ctx.processed_search_query) : CGI.param('query') | html %]"
                 [%- IF use_autosuggest.enabled == "t" %]
                 dojoType="openils.widget.AutoSuggest" type_selector="'qtype'"
                 submitter="this.textbox.form.submit();"
         %]</a> ]
     </div>
     [% END %]
+    [% IF (is_boolean) %]
+    <div class="opac-auto-102">
+        [ <a href="[% mkurl(ctx.opac_root _ '/advanced', {$loc_name => loc_value, pane => 'boolean'}) %]">[%
+            l('Refine My Original Search')
+        %]</a> ]
+    </div>
+    [% END %]
     <!--
     <div id="breadcrumb">
         <a href="[% ctx.opac_root %]/home">[% l('Catalog Home') %]</a> &gt;
index cf90339..69c6649 100644 (file)
@@ -332,6 +332,13 @@ Please see a librarian to renew your account.">
 <!ENTITY opac.advanced.copy_loc_filter "Shelving Location">
 
 <!-- ==========================================================
+    Boolean Search Tab
+    ======================================================= -->
+<!ENTITY opac.boolean.keyword.and "and">
+<!ENTITY opac.boolean.keyword.or "or">
+<!ENTITY opac.boolean.keyword.not "not">
+
+<!-- ==========================================================
      MARC expert search
      ========================================================== -->
 <!ENTITY search.marc "MARC Expert Search">