[Bio] / FigWebServices / SearchSkeleton.cgi Repository:
ViewVC logotype

View of /FigWebServices/SearchSkeleton.cgi

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.11 - (download) (annotate)
Fri Nov 10 22:02:40 2006 UTC (13 years, 5 months ago) by parrello
Branch: MAIN
Changes since 1.10: +6 -2 lines
Changed the handling of the advanced class list so that it is no longer necessary to maintain a list of search helpers.
Added support for an ADVANCED button.

#!/usr/bin/perl -w

BEGIN {
    # Print the HTML header.
    print "CONTENT-TYPE: text/html\n\n";
}

use strict;
use Tracer;
use CGI;
use Sprout;
use SearchHelper;
use POSIX qw(ceil);
use File::stat;

# Add USE statements for new search helpers below.
use SHFidSearch;
use SHBlastSearch;
use SHSigGenes;
use SHWordSearch;
use SHPropSearch;
use SHDrugSearch;

=head1 NMPDR Search Skeleton

This script executes a search and displays the results. If, on entry,
it sees a session ID, then it will assume search results have been
cached and the cached results are to be displayed. Otherwise, it
will perform the search, cache the results, and display the first
page. The search itself is performed by an object that subclasses
B<SearchHelper>. To allow for additional search types, you need
merely implement a new subclass of B<SearchHelper> and add it
to the C<use> list below. By convention, all search helper
subclasses begin with the letters C<SH>. This is not consistent
with normal PERL practice, but it fits better into the way we
do builds.

=head2 Session Data

The following parameters are expected from the CGI query object.
Additional parameters may be required by whichever B<SearchHelper>
subclass is selected. By convention, the parameters required by
the subclasses will be lower-case and the parameters used by this
script are capital-case. Note that some parameters are only required
by old sessions, that is, sessions which are established with
existing search result cache files.

=over 4

=item Trace

Trace level and list of trace modules to turn on, space-delimited.

=item NoForm

If specified, then no search form will be generated.

=item SessionID

Unique session ID for this user. This is used to generate the name of the user's
cache file in the temporary directory. The actual filename will be
C<tmp_>I<SessionID>C<.cache>.

=item Page (old only)

Number of the current page to display.

=item PageSize

Number of items per page.

=item ResultCount (old only)

Total number of search result lines.

=item Class

Name of the B<SearchHelper> subclass for this type of search. The name does not include
the C<SH> prefix. So, to specify a B<SHFidSearch> type of
search, you would specify a class of C<FidSearch>. If this parameter is omitted,
then all of the advanced search forms will be displayed.

=item ShowURL

If specified, then a URL for repeating the search will be shown as a hyperlink on the
results page.

=item ShowAliases

If specified, then hyperlinked aliases will be shown for each feature.

=back

=head2 The Cache File

The cache file is a tab-delimited file. The first line of the file contains the
column names and the remaining lines contain the data for each result item.

The column contents may contain HTML tags, including hyperlinks and buttons. For best
results, all links should be relative.

Some columns will consist of a doubled percent sign followed by a name, an equal sign,
and some text. This tells the display code to call the B<RunTimeColumns> method of
the B<SearchHelper> object to compute the column value. This facility is designed for
columns that require a lot of time to calculate, so we don't want to calculate them
until we absolutely have to display them.

It is presumed that the cache file is small, containing no more than a few thousand
lines of data. If this is not the case, an entirely different strategy will be
needed for displaying search results.

If the cache file is empty or has only a single line, a stock "No Search Results"
message will be displayed.

=cut


my ($cgi, $varHash) = ScriptSetup();
eval {
    # Get the search class.
    my $class = $cgi->param("Class");
    # Check for advanced mode.
    if ($cgi->param("Alternate")) {
        $class = $FIG_Config::advanced_class;
    }
    if (! $class) {
        Trace("Producing index of search tools.") if T(3);
        # No class specified, so we simply generate an index of the
        # searches. First, make sure the template knows there are no search results.
        $varHash->{result_count} = 0;
        Trace("Building URL.") if T(3);
        # Get a copy of our URL. Note we include the query fields so that any
        # tracing parameters are preserved.
        my $selfURL = $cgi->url(-relative => 1, -query => 1);
        # Append a question mark or semicolon to the URL, depending on whether or not
        # there's already a question mark present.
        $selfURL .= ($selfURL =~ /\?/ ? ';' : '?');
        # Loop through the search classes building a table of contents.
        my @contents = ();
        for my $className (SearchHelper::AdvancedClassList()) {
            Trace("Processing $className") if T(3);
            my $shelp = GetHelper($cgi, $className);
            push @contents, "<a href=\"${selfURL}Class=$className\">$className</a>: " . $shelp->Description();
        }
        # Create the table of contents.
        Trace("Building index.") if T(3);
        my $index = $cgi->h3("Index of Search Tools") .
                    $cgi->ul($cgi->li(\@contents));
        # Store it as the results.
        $varHash->{results} = $index;
        # Tell the template we don't have a class.
        $varHash->{class} = "";
        Trace("Index built.") if T(3);
    } else {
        Trace("Class $class detected.") if T(3);
        # Here we have a class, so we're working with a single type of search.
        my $shelp = GetHelper($cgi, $class);
        # Tell the template what the class is.
        $varHash->{class} = $class;
        # Insure we have a page size.
        if (! $cgi->param("PageSize")) {
            $cgi->param(-name => 'PageSize', -value => $FIG_Config::results_per_page);
        }
        # Display the form, if desired.
        my $formShown = ! $cgi->param("NoForm");
        if ($formShown) {
            Trace("Displaying form.") if T(3);
            $varHash->{form} = $shelp->Form();
        }
        # Declare the result count variable.
        my $result_count = 0;
        # Now there are three different directions we can go. If a
        # "Search" button has been pressed, then we need to perform a
        # search. If this is a new session and the button has not
        # been pressed, we do nothing. If this is an old session
        # and the button has not been pressed, we display results. Note
        # that we allow for regular buttons (Search) or image buttons
        # (Search.x).
        if (!$cgi->param("Search") && !$cgi->param("Search.x")) {
            # No button, so check for results. If this is a new session, we do
            # nothing. The form is displayed and nothing else need be done.
            # Otherwise, we go into display-results mode.
            Trace("No search requested.") if T(3);
            if (! $shelp->IsNew()) {
                $varHash->{results} = DisplayResults($shelp, $cgi);
            }
        } else {
            # Here we have a button press, so we need to find stuff and
            # then display it..
            Trace("Performing the search.") if T(3);
            # Clear the result area.
            $varHash->{results} = "";
            # Now we process the showURL thing. If showURL is checked, then
            # we will display a hyperlink to this search. Note that we only
            # do it if the form was shown. If the form was not shown, the
            # code for computing the URL won't work.
            if ($cgi->param('ShowURL') && $formShown) {
                my $searchURL = $shelp->ComputeSearchURL();
                $varHash->{results} .= $cgi->p("<a href=\"$searchURL\">Right-click to save a URL for this search</a>");
            }
            # Perform the search.
            $result_count = $shelp->Find();
            # Check to see what kind of results we got.
            if (! defined($result_count)) {
                # Here an error occurred, so we display the error message.
                $varHash->{results} .= $cgi->h3("ERROR: " . $shelp->Message());
                $result_count = 0;
            } elsif ($result_count == 0) {
                # Here nothing matched.
                $varHash->{results} .= $cgi->h3("No match found.");
            } else {
                # Here we have results. Save the result count and set up to display
                # the first page of results.
                $cgi->param(-name => "ResultCount", -value => $result_count);
                $cgi->param(-name => "Page", -value => 1);
                # Start with a message about how many matches we found.
                my $countText = ($result_count == 1 ? "One match" : "$result_count matches");
                $varHash->{results} .= $cgi->h3("$countText found.");
                # Append the page display.
                $varHash->{results} .= DisplayResults($shelp, $cgi);
            }
        }
        # Save the result count so that the results helper text appears if it
        # is needed. This text is in the template, but it's protected by a TMPL_IF
        # on "result_count".
        $varHash->{result_count} = $result_count;
        # If there are no results and the form was shown, add the help text. We are
        # assuming that if the user got the search to work, he doesn't need help.
        # In addition, if the form was not shown, a description of how to use it
        # makes no sense.
        if (! $result_count && $formShown) {
            $varHash->{helptext} = $shelp->GetHelpText();
        }
    }
};
if ($@) {
    my $errorMessage = $@;
    Trace("Script Error: $errorMessage") if T(0);
    $varHash->{results} = $cgi->h3("Script Error: $errorMessage");
}
ScriptFinish("SproutSearch_tmpl.php", $varHash);

=head3 DisplayResults

C<< my $htmlText = DisplayResults($shelp, $cgi); >>

Display the results of a search. A page of results will be displayed, along with links to get to
other pages. The HTML for the results display is returned.

=over 4

=item shelp

Search helper object representing the search. The column headers and search rows will be
stored in the session file attached to it.

=item cgi

CGI query object for the current session. This includes the page number, size, and result
counts.

=item RETURN

Returns the HTML text for displaying the current page of search results.

=back

=cut

sub DisplayResults {
    # Get the parameters.
    my ($shelp, $cgi) = @_;
    # Declare the return variable.
    my $retVal;
    # Extract the result parameters.
    my ($pageSize, $pageNum, $resultCount) = ($cgi->param('PageSize'),
                                              $cgi->param('Page'),
                                              $cgi->param('ResultCount'));
    # Only proceed if there are actual results.
    if ($resultCount <= 0) {
        $retVal = $cgi->h3("No matches found.");
    } else {
        # Check the state of the session file.
        my $fileName = $shelp->GetCacheFileName();
        if (! -e $fileName) {
            $retVal = $cgi->h3("Search session has expired. Please resubmit your query.");
        } else {
            # The file is here, so we can open it.
            my $sessionH = Open(undef, "<$fileName");
            if (T(3)) {
                my $fileData = stat($sessionH);
                Trace($fileData->size . " bytes in $fileName.");
            }
            # Read the column headers.
            my @colHdrs = Tracer::GetLine($sessionH);
            # Compute the page navigation string.
            my $pageNavigator = PageNavigator($cgi);
            # Now we need to find our page. The line number we compute will be
            # zero-based. We'll read from the session file until it drops to zero.
            # This may push us past end-of-file, but it won't cause an exception, and
            # it's something that should only happen very rarely in any case.
            my $linesToSkip = ($pageNum - 1) * $pageSize;
            Trace("Skipping $linesToSkip lines in session file $fileName.") if T(3);
            for (my $lines = $linesToSkip; $lines > 0; $lines--) {
                Tracer::GetLine($sessionH);
            }
            # The session file is now positioned at the beginning of our line.
            # We build the table rows one line at a time until we run out of data
            # or exceed the page size.
            my @tableRows = ();
            my $linesLeft = $pageSize;
            Trace("$linesLeft lines to read from session file.") if T(3);
            while ($linesLeft-- > 0) {
                Trace("Reading line from session file.") if T(3);
                my @cols = Tracer::GetLine($sessionH);
                if (! @cols) {
                    Trace("End of file read.") if T(3);
                    $linesLeft = 0;
                } else {
                    Trace("Line has " . scalar(@cols) . " columns. $linesLeft lines left.") if T(3);
                    # Check the columns for run-time generation.
                    my @actual = map { substr($_,0,2) eq "%%" ? $shelp->GetRunTimeValue($_) : $_ } @cols;
                    # Put the actual data into the table list.
                    push @tableRows, \@actual;
                }
            }
            # Now we're ready. We do a page navigator, a spacer, the table, a spacer,
            # and another page navigator.
            $retVal = join("\n", $pageNavigator,
                                 $cgi->p("&nbsp;"),
                                 $cgi->h3("Search Results Page $pageNum"),
                                 PageBuilder::MakeFancyTable($cgi, \@colHdrs, \@tableRows),
                                 $cgi->p("&nbsp;"),
                                 $pageNavigator,
                                 "");
        }
    } 
    # Return the result.
    return $retVal;
}

=head3 PageNavigator

C<< my $htmlText = PageNavigator($cgi); >>

Return a page navigation string for the specified query.

=over 4

=item cgi

CGI query object for the current session. The key values are described in the
introduction to this document.

=back

=cut

sub PageNavigator {
    # Get the parameters.
    my ($cgi) = @_;
    # Extract the result parameters.
    my ($pageSize, $pageNum, $resultCount) = ($cgi->param('PageSize'),
                                              $cgi->param('Page'),
                                              $cgi->param('ResultCount'));
    # Declare the return variable.
    my $retVal = "";
    # Compute the number of the last page.
    my $lastPage = ceil($resultCount / $pageSize);
    # Only proceed if there's more than one page.
    if ($lastPage > 1) {
        # Create a URL without a page number. All the other URLs will be generated
        # from this one by appending the new page number.
        my $url = StatusURL($cgi, Page => undef);
        # Now compute the start and end pages for the display. We display ten pages,
        # with the current one more or less centered.
        my $startPage = $pageNum - 4;
        if ($startPage < 1) { $startPage = 1; }
        my $endPage = $startPage + 9;
        if ($endPage > $lastPage) { $endPage = $lastPage; }
        # Create a list of URL/page-number combinations.
        my @pageThings = ();
        for (my $linkPage = $startPage; $linkPage <= $endPage; $linkPage++) {
            # Check for the current page. It gets a page number with no link.
            if ($linkPage == $pageNum) {
                push @pageThings, $linkPage;
            } else {
                # This is not the current page, so it gets the full treatment.
                push @pageThings, PageThing($cgi, $linkPage, $linkPage, $url);
            }
        }
        # Now add some jump links at the end.
        my @forePointers = ();
        if ($endPage < $lastPage) {
            for (my $pg = $endPage + 5; $pg < $lastPage; $pg += 15) {
                push @forePointers, PageThing($cgi, $pg, $pg, $url);
            }
            push @forePointers, PageThing($cgi, ">>", $lastPage, $url);
        }
        # Finally, add some jump links at the front.
        my @backPointers = ();
        if ($startPage > 1) {
            for (my $pg = $startPage - 5; $pg > 1; $pg -= 15) {
                unshift @backPointers, PageThing($cgi, $pg, $pg, $url);
            }
            unshift @backPointers, PageThing($cgi, "<<", 1, $url);
        }
        # Put it all together.
        my $middle = join(" ", @pageThings);
        $retVal = join " ... ", @backPointers, $middle, @forePointers;
    }
    # Return the result.
    return $retVal;
}

=head3 PageThing

C<< my $htmlText = PageThing($cgi, $pageLabel, $pageNumber, $url); >>

Create an entry for the page navigator. The entry consists of a label that
is hyperlinked to the specified page number of the search results.

=over 4

=item cgi

CGI object, used to access the CGI HTML-building methods.

=item pageLabel

Text to be hyperlinked. This is usually the page number, but sometimes it will be
arrows.

=item pageNumber

Number of the page to be presented when the link is followed.

=item url

Base URL for viewing a page.

=item RETURN

Returns HTML for the specified label, hyperlinked to the desired page.

=back

=cut

sub PageThing {
    # Get the parameters.
    my ($cgi, $pageLabel, $pageNumber, $url) = @_;
    # Compute the full URL.
    my $purl = "$url&Page=$pageNumber";
    # Form it into a hyperlink.
    my $retVal = "<a href=\"$purl\" title=\"Results page $pageNumber\">$pageLabel</a>";
    # Return the result.
    return $retVal;
}

=head3 StatusURL

C<< my $queryUrl = StatusURL($cgi, %overrides); >>

Create a URL for the current script containing status information for the search in progress.
The values in the incoming CGI object will be used for all parameters except the ones
specified as overrides. So, for example

    StatusURL($cgi, PageNum => 3)

would specify a page number of 3, but all the other parameters will be taken as is from
the CGI object. The complete list of session variables is given in the L</Session Data>
section.

=over 4

=item cgi

CGI query object containing the session variables.

=item overrides

A hash mapping key names to override values. These are used to override values in the
I<$cgi> parameter.

=item RETURN

Returns a relative URL for the current page with GET-style values for all the session
variables.

=back

=cut

sub StatusURL {
    # Get the parameters.
    my ($cgi, %overrides) = @_;
    # Create a hash of the session variables we want to keep.
    my %varHash;
    for my $varKey (qw(SessionID Trace NoForm ResultCount Page PageSize Class SPROUT)) {
        # Check for an override.
        if (exists $overrides{$varKey}) {
            my $override = $overrides{$varKey};
            # Use the override if it is not null or undefined.
            if (defined($override) && $override ne "") {
                $varHash{$varKey} = $override;
            }
        } else {
            # Check for a CGI value.
            my $normal = $cgi->param($varKey);
            # Use it if it exists.
            if (defined($normal)) {
                $varHash{$varKey} = $normal;
            }
        }
    }
    # Compute the full URL.
    my $retVal = Tracer::GenerateURL($cgi->url(-relative => 1), %varHash);
    # Return the result.
    return $retVal;
}

=head3 GetHelper

C<< my $shelp = GetHelper($className); >>

Return a helper object with the given class name. If no such class exists, an
error will be thrown.

=over 4

=item cgi

Active CGI query object.

=item className

Class name for the search helper object, without the preceding C<SH>. This is
identical to what the script expects for the C<Class> parameter.

=item RETURN

Returns a search helper object for the specified class.

=back

=cut

sub GetHelper {
    # Get the parameters.
    my ($cgi, $className) = @_;
    # Try to create the search helper.
    my $retVal = eval("SH$className->new(\$cgi)");
    if (! defined $retVal) {
        Confess("Could not find a search handler of type $className.");
    }
    # Return the result.
    return $retVal;
}

1;

MCS Webmaster
ViewVC Help
Powered by ViewVC 1.0.3