--- loncom/interface/lonsearchcat.pm 2001/03/14 16:57:04 1.10 +++ loncom/interface/lonsearchcat.pm 2001/03/21 03:37:43 1.58 @@ -2,7 +2,41 @@ # Search Catalog # # 03/08/2001 Scott Harrison +# Scott Harrison: 03/12/2001, 03/13/2001, 03/14/2001, 03/15/2001, 03/19/2001 +# Scott Harrison: 03/20/2001 # +# Functions +# +# handler(server reference) : interacts with the Apache server layer +# (for /adm/searchcat URLs) +# simpletextfield(name,value) : returns HTML formatted string for simple text +# field +# simplecheckbox(name,value) : returns HTML formatted string for simple +# checkbox +# searchphrasefield(title,name,value) : returns HTML formatted string for +# a search expression phrase field +# dateboxes(name, defaultmonth, defaultday, defaultyear) : returns HTML +# formatted string +# for a calendar date +# selectbox(title,name,value,%HASH=options) : returns HTML formatted string for +# a selection box field +# advancedsearch(server reference, environment reference) : perform a complex +# multi-field logical query +# filled(field) : determines whether a given field has been filled +# basicsearch(server reference, environment reference) : perform a simple +# single-field logical query +# output_blank_field_error(server reference) : outputs a message saying that +# more fields need to be filled in +# output_results(output mode, +# server reference, +# environment reference, +# reply list reference) : outputs results from search +# build_SQL_query(field name, logic) : builds a SQL query string from a +# logical expression with AND/OR keywords +# recursive_SQL_query_build(field name, reverse notation expression) : +# builds a SQL query string from a reverse notation expression +# logical expression with AND/OR keywords + package Apache::lonsearchcat; use strict; @@ -10,12 +44,30 @@ use Apache::Constants qw(:common); use Apache::lonnet(); use Apache::File(); use CGI qw(:standard); +use Text::Query; my %language; my $scrout; my %metadatafields; my %cprtag; my %mimetag; +my $closebutton; +my $basicviewselect=< + + + + + +END +my $advancedviewselect=< + + + + + +END sub handler { my $r = shift; @@ -41,6 +93,10 @@ sub handler { END + $closebutton=< +END + # ------------------------------------------------ First, check out environment $metadatafields{'owner'}=$ENV{'user.name'}.'@'.$ENV{'user.domain'}; @@ -51,7 +107,7 @@ END { my $fh=Apache::File->new($r->dir_config('lonTabDir').'/language.tab'); map { - $_=~/(\w+)\s+([\w\s\-]+)/; + $_=~/(\w+)\s+([\w\s\-]+)/; chomp; $language{$1}=$2; } <$fh>; } @@ -61,7 +117,7 @@ END { my $fh=Apache::File->new($r->dir_config('lonIncludes').'/copyright.tab'); map { - $_=~/(\w+)\s+([\w\s\-]+)/; + $_=~/(\w+)\s+([\w\s\-]+)/; chomp; $cprtag{$1}=$2; } <$fh>; } @@ -71,36 +127,51 @@ END { my $fh=Apache::File->new($r->dir_config('lonTabDir').'/filetypes.tab'); map { - $_=~/(\w+)\s+(\w+)\s+([\w\s\-]+)/; + $_=~/(\w+)\s+(\w+)\s+([\w\s\-]+)/; chomp; $mimetag{$1}=".$1 $3"; } <$fh>; } if ($ENV{'form.basicsubmit'} eq 'SEARCH') { - return &basicsearch($r,$ENV{'form.basicexp'}); + return &basicsearch($r,\%ENV); + } + elsif ($ENV{'form.advancedsubmit'} eq 'SEARCH') { + return &advancedsearch($r,\%ENV); } $scrout=''; # building a part of screen output $scrout.=&searchphrasefield('Limit by title','title', - ''); + $ENV{'form.title'}); $scrout.=&searchphrasefield('Limit by author','author', - ''); + $ENV{'form.author'}); $scrout.=&searchphrasefield('Limit by subject','subject', - ''); + $ENV{'form.subject'}); + + $scrout.=&searchphrasefield('Limit by keywords','keywords', + $ENV{'form.keywords'}); + + $scrout.=&searchphrasefield('Limit by URL','url', + $ENV{'form.url'}); + + $scrout.=&searchphrasefield('Limit by version','version', + $ENV{'form.version'}); $scrout.=&searchphrasefield('Limit by notes','notes', - ''); + $ENV{'form.notes'}); $scrout.=&searchphrasefield('Limit by abstract','abstract', - ''); + $ENV{'form.abstract'}); + $ENV{'form.mime'}='notxxx' unless length($ENV{'form.mime'}); $scrout.=&selectbox('Limit by MIME type','mime', - 'notxxx',%mimetag); + $ENV{'form.mime'},%mimetag); + + $ENV{'form.language'}='any' unless length($ENV{'form.language'}); $scrout.=&selectbox('Limit by language','language', - 'any',%language); + $ENV{'form.language'},%language); # ------------------------------------------------ Compute date selection boxes @@ -111,11 +182,19 @@ END
between: CREATIONDATESTART - $scrout.=&dateboxes('creationdatestart',1,1,1976); + $scrout.=&dateboxes('creationdatestart',1,1,1976, + $ENV{'form.creationdatestart_month'}, + $ENV{'form.creationdatestart_day'}, + $ENV{'form.creationdatestart_year'}, + ); $scrout.=<
between: LASTREVISIONDATESTART - $scrout.=&dateboxes('lastrevisiondatestart',1,1,1976); + $scrout.=&dateboxes('lastrevisiondatestart',1,1,1976, + $ENV{'form.lastrevisiondatestart_month'}, + $ENV{'form.lastrevisiondatestart_day'}, + $ENV{'form.lastrevisiondatestart_year'}, + ); $scrout.=< +LIMIT BY OTHER METADATA FIELDS: + +For author-specific metadata, enter in an expression in the form of +key=value separated by operators such as AND or OR.
+Example: grandmother=75 OR grandfather=85 +
+CUSTOMMETADATA +$scrout.=&simpletextfield('custommetadata',$ENV{'form.custommetadata'}); +$scrout.=' initial users of this system do not need to worry about this option'; # ---------------------------------------------------------------- Print screen $r->print(< - - -Title only +ENDDOCUMENT + $r->print(&simpletextfield('basicexp',$ENV{'form.basicexp'})); + $r->print(' '); + $r->print(&simplecheckbox('titleonly',$ENV{'form.titleonly'})); + $r->print('Title only '); + $r->print(&simplecheckbox('allversions',$ENV{'form.allversions'})); + $r->print(<Search historic archives
- +$closebutton +$basicviewselect


Advanced Search

@@ -170,7 +278,8 @@ $scrout

- +$closebutton +$advancedviewselect

@@ -181,10 +290,16 @@ ENDDOCUMENT # --------------------------------------------------------- Various form fields -sub textfield { - my ($title,$name,$value)=@_; - return "\n

$title:
". - ''; +sub simpletextfield { + my ($name,$value)=@_; + return ''; +} + +sub simplecheckbox { + my ($name,$value)=@_; + my $checked=''; + $checked="CHECKED" if $value eq 'on'; + return ''; } sub searchphrasefield { @@ -200,137 +315,147 @@ END } sub dateboxes { - my ($name,$defaultmonth,$defaultday,$defaultyear)=@_; - return< - - - - - - - - - - + + + + + + + + + + +END + $month=~s/(\"$currentmonth\")/$1 SELECTED/ if length($currentmonth); + my $day=< - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +END + $day=~s/(\"$currentday\")/$1 SELECTED/ if length($currentday); + my $year=< - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + END + $year=~s/(\"$currentyear\")/$1 SELECTED/ if length($currentyear); + return "$month$day$year"; } sub selectbox { @@ -346,75 +471,91 @@ sub selectbox { return $selout.''; } -# --------------------------------------------------- Performing a basic search -sub basicsearch { - my ($r,$expression)=@_; +# ----------------------------------------------- Performing an advanced search +sub advancedsearch { + my ($r,$envhash)=@_; + my %ENV=%{$envhash}; + + my $fillflag=0; + for my $field ('title','author','subject','keywords','url','version', + 'notes','abstract','mime','language','owner', + 'custommetadata') { + if (&filled($ENV{"form.$field"})) { + $fillflag++; + } + } - my $query=$expression; - $query="select * from metadata where concat(title,\" \",author) like '\%$expression\%'"; - my $reply=&Apache::lonnet::reply("querysend:$query",'msul3'); - - my @results; - - my $replyfile=''; - $reply=~/^([\.\w]+)$/; # must do since 'use strict' checks for tainting - $replyfile=$r->dir_config('lonDaemons').'/tmp/'.$1; - $reply=~/(.*?)\_/; - my $hostname=$1; + unless ($fillflag) { + &output_blank_field_error($r); + return OK; + } - { - while (1) { - last if -e $replyfile; - sleep 1; + my $query=''; + + my @queries; + # Go through logical expression AND/OR/NOT phrase fields. + + foreach my $field ('title','author','subject','notes','abstract','url', + 'keywords','version','owner') { + if ($ENV{'form.'.$field}) { + push @queries,&build_SQL_query($field,$ENV{'form.'.$field}); } - # QUESTION: how should I handle this error condition.. - # I'm sure there is syntax elsewhere I can use.. - my $fh=Apache::File->new($replyfile) or - ($r->print('file cannot be opened') and return OK); - @results=<$fh>; } + if ($ENV{'form.language'} and $ENV{'form.language'} ne 'any') { + push @queries,"language like $ENV{'form.language'}"; + } + if ($ENV{'form.mime'} and $ENV{'form.mime'} ne 'any') { + push @queries,"mime like $ENV{'form.mime'}"; + } + if ($ENV{'form.copyright'} and $ENV{'form.copyright'} ne 'any') { + push @queries,"copyright like $ENV{'form.copyright'}"; + } + if (@queries) { + $query=join(" AND ",@queries); + $query="select * from metadata where $query"; + my $reply=&Apache::lonnet::metadata_query($query); + &output_results('Advanced',$r,$envhash,$query,$reply); + } + else { + &output_results('Advanced',$r,$envhash,$query); + } + return OK; +} - my $compiledresult=''; - - foreach my $result (@results) { - my ($title,$author,$subject,$url,$keywords,$version, - $notes,$abstract,$mime,$lang, - $creationdate,$lastrevisiondate,$owner,$copyright - )=map {&Apache::lonnet::unescape($_)} (split(/\,/,$result)); - my $shortabstract=$abstract; - $shortabstract=substr($abstract,0,200) if length($abstract)>200; - $compiledresult.=< -END - $compiledresult.=< - -
-END - $compiledresult.=<URL: $url
-Title: $title
-Author(s): $author
-Subject: $subject
-Keyword(s): $keywords
-Notes: $notes
-Abstract: $shortabstract
-MIME Type: $mimetag{$mime}
-Language: $language{$lang}
-Creation Date: $creationdate
-Last Revision Date: $lastrevisiondate
-Publisher/Owner: $owner
-Copyright/Distribution: $copyright
-Repository Location: $hostname -

-END +# ---------------------------------------------------- see if a field is filled +sub filled { + my ($field)=@_; + if ($field=~/\S/) { + return 1; + } + else { + return 0; + } } - unless ($compiledresult) { - $compiledresult="There were no results that matched your query"; +# --------------------------------------------------- Performing a basic search +sub basicsearch { + my ($r,$envhash)=@_; + my %ENV=%{$envhash}; + + unless (&filled($ENV{'form.basicexp'})) { + &output_blank_field_error($r); + return OK; } + my $query=''; + my $concatarg=join('," ",', + ('title', 'author', 'subject', 'notes', 'abstract')); + + $query="select * from metadata where concat($concatarg) like '\%$ENV{'form.basicexp'}\%'"; + my $reply=&Apache::lonnet::metadata_query($query); + &output_results('Basic',$r,$envhash,$query,$reply); + return OK; +} + +# ---------------- Message to output when there are not enough fields filled in +sub output_blank_field_error { + my ($r)=@_; # make query information persistent to allow for subsequent revision my $persistent=''; map { @@ -433,7 +574,126 @@ END The LearningOnline Network with CAPA BEGINNING - $r->print(<print(< + + +

Search Catalog

+
+$persistent + +$closebutton +
+

Helpful Message

+

+Incorrect search query due to blank entry fields. +You need to fill in the relevant +fields on the search page in order for a query to be +processed. +

+ + +RESULTS +} + +# ----------------------------- format and output results based on a reply list +sub output_results { + my ($mode,$r,$envhash,$query,@replylist)=@_; + my %ENV=%{$envhash}; + my $compiledresult=''; + + foreach my $reply (@replylist) { + + my @results; + + my $replyfile=''; + $reply=~/^([\.\w]+)$/; # must do since 'use strict' checks for tainting + $replyfile=$r->dir_config('lonDaemons').'/tmp/'.$1; + $reply=~/(.*?)\_/; + my $hostname=$1; + sleep 3; # temporary fix, need to check for completion and status + { + while (1) { + last if -e $replyfile; + sleep 1; + } + # QUESTION: how should I handle this error condition.. + # I'm sure there is syntax elsewhere I can use.. + my $fh=Apache::File->new($replyfile) or + ($r->print('file cannot be opened') and return OK); + @results=<$fh>; + } + + foreach my $result (@results) { + my @fields=map + {&Apache::lonnet::unescape($_)} + (split(/\,/,$result)); + my ($title,$author,$subject,$url,$keywords,$version, + $notes,$abstract,$mime,$lang, + $creationdate,$lastrevisiondate,$owner,$copyright)=@fields; + my $shortabstract=$abstract; + $shortabstract=substr($abstract,0,200) if length($abstract)>200; + $fields[7]=$shortabstract; + $compiledresult.=< +END + $compiledresult.=< + +
+END + my $httphost=$ENV{'HTTP_HOST'}; + + my $viewselect; + if ($mode eq 'Basic') { + $viewselect=$ENV{'form.basicviewselect'}; + } + elsif ($mode eq 'Advanced') { + $viewselect=$ENV{'form.advancedviewselect'}; + } + + if ($viewselect eq 'Detailed Citation View') { + $compiledresult.=&detailed_citation_view(@fields, + $hostname,$httphost); + } + elsif ($viewselect eq 'Summary View') { + $compiledresult.=&summary_view(@fields,$hostname,$httphost); + } + elsif ($viewselect eq 'Fielded Format') { + $compiledresult.=&fielded_format_view(@fields,$hostname, + $httphost); + } + elsif ($viewselect eq 'XML/SGML') { + $compiledresult.=&xml_sgml_view(@fields,$hostname,$httphost); + } + + } + + unless ($compiledresult) { + $compiledresult="There were no results that matched your query"; + } + + # make query information persistent to allow for subsequent revision + my $persistent=''; + map { + if (/^form\./ && !/submit/) { + my $name=$_; + my $key=$name; + $name=~s/^form\.//; + $persistent.=< +END + } + } (keys %ENV); + + $r->print(< + +The LearningOnline Network with CAPA +BEGINNING + $r->print(< function select_data(title,url) { changeTitle(title); @@ -451,7 +711,7 @@ BEGINNING } SCRIPT - $r->print(<print(< @@ -459,19 +719,179 @@ SCRIPT +$closebutton $persistent

Search Query

+RESULTS + if ($mode eq 'Basic') { + $r->print(< -Basic search: $expression +Basic search: $ENV{'form.basicexp'}

+RESULTS + } + elsif ($mode eq 'Advanced') { + $r->print(< +Advanced search +$query +

+RESULTS + } + $r->print(<Search Results $compiledresult RESULTS + } +} - return OK; +# ------------------------------------------------------------- build_SQL_query +sub build_SQL_query { + my ($field_name,$logic_statement)=@_; + my $q=new Text::Query('abc', + -parse => 'Text::Query::ParseAdvanced', + -build => 'Text::Query::Build'); + $q->prepare($logic_statement); + my $matchexp=${$q}{'matchexp'}; chomp $matchexp; + my $sql_query=&recursive_SQL_query_build($field_name,$matchexp); + return $sql_query; +} + +# - Recursively parse a reverse notation expression into a SQL query expression +sub recursive_SQL_query_build { + my ($dkey,$pattern)=@_; + my @matches=($pattern=~/(\[[^\]|\[]*\])/g); + return $pattern unless @matches; + foreach my $match (@matches) { + $match=~/\[ (\w+)\s(.*) \]/; + my ($key,$value)=($1,$2); + my $replacement=''; + if ($key eq 'literal') { + $replacement="($dkey like \"\%$value\%\")"; + } + elsif ($key eq 'and') { + $value=~/(.*[\"|\)]) ([|\(|\^].*)/; + $replacement="($1 AND $2)"; + } + elsif ($key eq 'or') { + $value=~/(.*[\"|\)]) ([|\(|\^].*)/; + $replacement="($1 OR $2)"; + } + substr($pattern, + index($pattern,$match), + length($match), + $replacement + ); + } + &recursive_SQL_query_build($dkey,$pattern); +} + +# ------------------------------------------------------ Detailed Citation View +sub detailed_citation_view { + my ($title,$author,$subject,$url,$keywords,$version, + $notes,$shortabstract,$mime,$lang, + $creationdate,$lastrevisiondate,$owner,$copyright, + $hostname,$httphost)=@_; + my $result=<$owner, last revised $lastrevisiondate +

$title

+

$author

+

+

+Subject: $subject
+Keyword(s): $keywords
+Notes: $notes
+MIME Type: $mimetag{$mime}
+Language: $language{$lang}
+Copyright/Distribution: $cprtag{$copyright}
+$shortabstract +

+END + return $result; +} + +# ---------------------------------------------------------------- Summary View +sub summary_view { + my ($title,$author,$subject,$url,$keywords,$version, + $notes,$shortabstract,$mime,$lang, + $creationdate,$lastrevisiondate,$owner,$copyright, + $hostname,$httphost)=@_; + my $result=<$author
+$title
+$owner -- $lastrevisiondate
+$cprtag{$copyright}
+

+END + return $result; +} + +# -------------------------------------------------------------- Fielded Format +sub fielded_format_view { + my ($title,$author,$subject,$url,$keywords,$version, + $notes,$shortabstract,$mime,$lang, + $creationdate,$lastrevisiondate,$owner,$copyright, + $hostname,$httphost)=@_; + my $result=<URL: $url +
+Title: $title
+Author(s): $author
+Subject: $subject
+Keyword(s): $keywords
+Notes: $notes
+MIME Type: $mimetag{$mime}
+Language: $language{$lang}
+Creation Date: $creationdate
+Last Revision Date: $lastrevisiondate
+Publisher/Owner: $owner
+Copyright/Distribution: $cprtag{$copyright}
+Repository Location: $hostname
+Abstract: $shortabstract
+

+END + return $result; +} + +# -------------------------------------------------------------------- XML/SGML +sub xml_sgml_view { + my ($title,$author,$subject,$url,$keywords,$version, + $notes,$shortabstract,$mime,$lang, + $creationdate,$lastrevisiondate,$owner,$copyright, + $hostname,$httphost)=@_; + my $result=< +<LonCapaResource> +<url>$url</url> +<title>$title</title> +<author>$author</author> +<subject>$subject</subject> +<keywords>$keywords</keywords> +<notes>$notes</notes> +<mimeInfo> +<mime>$mime</mime> +<mimetag>$mimetag{$mime}</mimetag> +</mimeInfo> +<languageInfo> +<language>$lang</language> +<languagetag>$language{$lang}</languagetag> +</languageInfo> +<creationdate>$creationdate</creationdate> +<lastrevisiondate>$lastrevisiondate</lastrevisiondate> +<owner>$owner</owner> +<copyrightInfo> +<copyright>$copyright</copyright> +<copyrighttag>$cprtag{$copyright}</copyrighttag> +</copyrightInfo> +<repositoryLocation>$hostname</repositoryLocation> +<shortabstract>$shortabstract</shortabstract> +</LonCapaResource> + +END + return $result; } 1;