
interchange-cvs at icdevgroup
May 15, 2008, 2:42 PM
Post #1 of 1
(341 views)
Permalink
|
|
interchange - racke modified 3 files
|
|
User: racke Date: 2008-05-15 21:42:40 GMT Modified: . MANIFEST Modified: lib/Vend Swish.pm Removed: lib/Vend Swish2.pm Log: replace old Swish implementation with new one Revision Changes Path 2.227 interchange/MANIFEST rev 2.227, prev_rev 2.226 Index: MANIFEST =================================================================== RCS file: /var/cvs/interchange/MANIFEST,v retrieving revision 2.226 retrieving revision 2.227 diff -u -r2.226 -r2.227 --- MANIFEST 7 May 2008 05:12:04 -0000 2.226 +++ MANIFEST 15 May 2008 21:42:40 -0000 2.227 @@ -1130,7 +1130,6 @@ lib/Vend/SQL_Parser.pm lib/Vend/Subs.pm lib/Vend/Swish.pm -lib/Vend/Swish2.pm lib/Vend/Table/Common.pm lib/Vend/Table/DB_File.pm lib/Vend/Table/DBI.pm 1.12 interchange/lib/Vend/Swish.pm rev 1.12, prev_rev 1.11 Index: Swish.pm =================================================================== RCS file: /var/cvs/interchange/lib/Vend/Swish.pm,v retrieving revision 1.11 retrieving revision 1.12 diff -u -r1.11 -r1.12 --- Swish.pm 9 Aug 2007 13:40:54 -0000 1.11 +++ Swish.pm 15 May 2008 21:42:40 -0000 1.12 @@ -1,10 +1,10 @@ -# Vend::Swish - Search indexes with Swish-e +# Vend::Swish2 - Search indexes with Swish-e's new SWISH::API # -# $Id: Swish.pm,v 1.11 2007-08-09 13:40:54 pajamian Exp $ +# $Id: Swish.pm,v 1.12 2008-05-15 21:42:40 racke Exp $ # -# Adapted from Vend::Glimpse +# Adapted from Vend::Swish by Brian Miller <brian[at]endpoint.com> # -# Copyright (C) 2002-2007 Interchange Development Group +# Copyright (C) 2005-2007 Interchange Development Group # Copyright (C) 2002 Mike Heins <mikeh[at]perusion.net> # # This program is free software; you can redistribute it and/or modify @@ -22,334 +22,417 @@ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, # MA 02110-1301 USA. -package Vend::Swish; +package Vend::Swish2; require Vend::Search; @ISA = qw(Vend::Search); -$VERSION = substr(q$Revision: 1.11 $, 10); +$VERSION = substr(q$Revision: 1.12 $, 10); use strict; +use SWISH::API; + +BEGIN { + eval { + require SWISH::ParseQuery; + require SWISH::PhraseHighlight; + $Vend::Swish2::Highlighting = 1; + }; +} + +# singleton to hold initialization object, +# search objects are then retrieved through it +# this should improve performance through caching +my $_swish = {}; +my $_swish_highlighters = {}; + +my %Default = ( + matches => 0, + mv_head_skip => 0, + mv_index_delim => "\t", + mv_record_delim => "\n", + mv_matchlimit => 50, + mv_max_matches => 2000, + mv_min_string => 1, +); + +my %fmap = ( code => 'swishreccount', + score => 'swishrank', + url => 'swishdocpath', + title => 'swishtitle', + filesize => 'swishdocsize', + mod_date => 'swishlastmodified', + description => 'swishdescription', + dbfile => 'swishdbfile', + ); +my %highlight_settings = ( show_words => 8, + occurrences => 5, + max_words => 100, + highlight_on => '<span class="highlight">', + highlight_off => '</span>', + ); + sub array { - my ($s, $opt) = @_; - $s->{mv_list_only} = 1; # makes perform_search only return results array - return Vend::Scan::perform_search($opt, undef, $s); + my ($s, $opt) = @_; + $s->{mv_list_only} = 1; # makes perform_search only return results array + return Vend::Scan::perform_search($opt, undef, $s); } sub hash { - my ($s, $opt) = @_; - $s->{mv_return_reference} = 'HASH'; - $s->{mv_list_only} = 1; # makes perform_search only return results array - return Vend::Scan::perform_search($opt, undef, $s); + my ($s, $opt) = @_; + $s->{mv_return_reference} = 'HASH'; + $s->{mv_list_only} = 1; # makes perform_search only return results array + return Vend::Scan::perform_search($opt, undef, $s); } sub list { - my ($s, $opt) = @_; - $s->{mv_return_reference} = 'LIST'; - $s->{mv_list_only} = 1; # makes perform_search only return results array - return Vend::Scan::perform_search($opt, undef, $s); + my ($s, $opt) = @_; + $s->{mv_return_reference} = 'LIST'; + $s->{mv_list_only} = 1; # makes perform_search only return results array + return Vend::Scan::perform_search($opt, undef, $s); } -my %Default = ( - matches => 0, - mv_head_skip => 0, - mv_index_delim => "\t", - mv_record_delim => "\n", - mv_matchlimit => 50, - mv_max_matches => 2000, - mv_min_string => 4, -); - - sub init { - my ($s, $options) = @_; + my ($s, $options) = @_; -#::logDebug("initting Swish search, Swish=" . Vend::Util::uneval($Vend::Cfg->{Swish})); - $Vend::Cfg->{Swish} ||= {}; - @{$s}{keys %Default} = (values %Default); - $s->{mv_base_directory} = undef, - $s->{mv_begin_string} = []; - $s->{mv_all_chars} = [1]; - $s->{mv_case} = []; - $s->{mv_column_op} = []; - $s->{mv_negate} = []; - $s->{mv_numeric} = []; - $s->{mv_orsearch} = []; - $s->{mv_searchspec} = []; - $s->{mv_search_group} = []; - $s->{mv_search_field} = []; - $s->{mv_search_file} = []; - push @{$s->{mv_search_file}}, $Vend::Cfg->{Swish}{index} - if $Vend::Cfg->{Swish}{index}; - $s->{mv_searchspec} = []; - $s->{mv_sort_option} = []; - $s->{mv_substring_match} = []; - $s->{mv_field_names} = [qw/code score url title filesize mod_date/]; - $s->{mv_return_fields} = [qw/code score url title filesize mod_date/]; - $s->{swish_cmd} = $Vend::Cfg->{Swish}{command} || '/usr/local/bin/swish-e'; -#::logDebug("initting Swish search, swish command=$s->{swish_cmd}"); + #::logDebug("initing Swish search, Swish=" . Vend::Util::uneval($Vend::Cfg->{Swish2})); + $Vend::Cfg->{Swish2} ||= {}; - for(keys %$options) { - $s->{$_} = $options->{$_}; - } + @{$s}{keys %Default} = (values %Default); - return; + $s->{mv_base_directory} = $Vend::Cfg->{VendRoot}, + $s->{mv_begin_string} = []; + $s->{mv_all_chars} = [1]; + $s->{mv_case} = []; + $s->{mv_column_op} = []; + $s->{mv_negate} = []; + $s->{mv_numeric} = []; + $s->{mv_orsearch} = []; + $s->{mv_searchspec} = []; + $s->{mv_search_group} = []; + $s->{mv_search_field} = []; + $s->{mv_search_file} = []; + push @{$s->{mv_search_file}}, $Vend::Cfg->{Swish2}{index} + if $Vend::Cfg->{Swish2}{index}; + $s->{mv_searchspec} = []; + $s->{mv_sort_option} = []; + $s->{mv_substring_match} = []; + $s->{mv_field_names} = [qw/code score url title filesize mod_date/]; + $s->{mv_return_fields} = [qw/code score url title filesize mod_date/]; + + for (keys %$options) { + $s->{$_} = $options->{$_}; + } + + # can create the base Swish object once and run + # multiple queries off of it + my @searchfiles = @{$s->{mv_search_file}}; + for (@searchfiles) { + $_ = Vend::Util::catfile($s->{mv_base_directory}, $_) + unless Vend::Util::file_name_is_absolute($_); + } + my $from_index = join ' ', @searchfiles; + $s->{'swish_index'} = $from_index; + + unless ($_swish->{$from_index}) { + $_swish->{$from_index} = new SWISH::API ( $from_index ); + if ($_swish->{$from_index}->Error) { + die "Can't create swish engine on searchfile(s) @searchfiles: " . $_swish->{$from_index}->ErrorString . "\n"; + } + } + + if ($Vend::Cfg->{Swish2}{highlight_context}) { + push @{ $s->{mv_field_names} }, 'context'; + push @{ $s->{mv_return_fields} }, 'context'; + $fmap{'context'} = 'swishdescription'; + + foreach my $index (@{ $s->{'mv_search_file'} }) { + my $swish = $_swish->{$from_index}; + my %headers = map { lc $_ => ($swish->HeaderValue( $index, $_ ) || '') } $swish->HeaderNames; + + $_swish_highlighters->{$index} = new SWISH::PhraseHighlight ( \%highlight_settings, \%headers, { swish => $swish } ); + } + } + + return; } sub new { - my ($class, %options) = @_; - my $s = new Vend::Search; - bless $s, $class; - $s->init(\%options); - return $s; + my ($class, %options) = @_; + my $s = new Vend::Search; + bless $s, $class; + $s->init(\%options); + return $s; } sub search { + my ($s, %options) = @_; - my($s,%options) = @_; - - my(@out); - my($limit_sub,$return_sub,$delayed_return); - my($dict_limit,$f,$key,$val); - my($searchfile, @searchfiles); - my(@specs); - my(@pats); - - # map Swish-e auto properties to field names - my %fmap = qw/ - code swishreccount - description swishdescription - dbfile swishdbfile - score swishrank - url swishdocpath - title swishtitle - filesize swishdocsize - mod_date swishlastmodified - /; - while (($key,$val) = each %options) { - $s->{$key} = $val; + while (my ($key,$val) = each %options) { + $s->{$key} = $val; + } + $s->{mv_return_delim} = $s->{mv_index_delim} + unless defined $s->{mv_return_delim}; + + my @specs = @{$s->{mv_searchspec}}; + my @pats = $s->spec_check(@specs); + + $s->save_specs(); + + my $search_string = join ' ', @pats; + if (length $search_string < $s->{mv_min_string}) { + my $msg = ::errmsg( + "Swish search string less than minimum %s characters: %s", + $s->{mv_min_string}, + $search_string, + ); + return $s->search_error($msg); + } + + my $engine = $_swish->{ $s->{'swish_index'} }; + + # check properties first + my @indexes = $engine->IndexNames(); + my $index_num = @indexes; + my (%prop_avail, @plist, $prop); + + for my $index (@indexes) { + @plist = $engine->PropertyList($index); + for $prop (@plist) { + push (@{$prop_avail{$prop->Name()}}, $index); + } } - @searchfiles = @{$s->{mv_search_file}}; - - for(@searchfiles) { - $_ = Vend::Util::catfile($s->{mv_base_directory}, $_) - unless Vend::Util::file_name_is_absolute($_); + my @sf = @{ $s->{mv_search_field} }; + + for my $search_field (@{ $s->{mv_search_field} }) { + if (exists $prop_avail{$search_field}) { + $search_string = join (' or ', map {"$search_field=$_"} @pats); + } } + + for (@{ $s->{'mv_field_names'} }) { + unless (exists $fmap{$_}) { + $fmap{$_} = $_; + } + + $prop = $fmap{$_}; + + unless (exists $prop_avail{$prop}) { + return $s->search_error("Unknown property '$prop'"); + } -#::logDebug("gsearch: self=" . ::Vend::Util::uneval_it({%$s})); - $s->{mv_return_delim} = $s->{mv_index_delim} - unless defined $s->{mv_return_delim}; - - unless ($s->{swish_cmd} && -x $s->{swish_cmd}) { - return $s->search_error("Invalid swish command $s->{swish_cmd}"); + unless (@{$prop_avail{$prop}} == $index_num) { + return $s->search_error("Property '$prop' is missing from some index files"); + } } + + # + # bug alert (make your own klaxon sound here) + # -------------------------------------------. + # uncommenting the following line causes the [on-match] and + # [no-match] blocks to reverse their meaning. This has something + # to do with the resulting "matches" count, but I haven't looked + # into it properly + # + # $search_string = $s->build_search(\@pats); - @specs = @{$s->{mv_searchspec}}; - - @pats = $s->spec_check(@specs); - - my @f; - - for(@{$s->{mv_field_names}}) { - my $name = $fmap{$_} || $_; - $name = "<$name>"; - push @f, $name; - } + #::logDebug("Swish search string is $search_string within " . join(', ', @sf)); - my $fmt_string = join $s->{mv_return_delim}, @f; + my $results = $engine->Query( $search_string ); + if ($engine->Error) { + $s->{matches} = -1; + return $s->search_error("Can't run swish query: " . $engine->ErrorString); + } + + # no matches, can return now + unless ($results->Hits) { + $s->{matches} = 0; + return; + } + + my @out; + my $date_format = $Vend::Cfg->{Swish2}->{date_format} || '%Y-%m-%d %H:%M:%S'; - $fmt_string .= $s->{mv_record_delim} eq "\n" ? '\n' : $s->{mv_record_delim}; + while (my $result = $results->NextResult) { + my $out_ref = []; + foreach my $field (@{ $s->{'mv_field_names'} }) { + my $text = $result->Property( $fmap{$field} ); + if ($field =~ /context/) { + if ($Vend::Cfg->{'Swish2'}{'highlight_context'} and defined $text and $text ne '') { + my $index = $result->Property('swishdbfile'); + + my $parsed_query = parse_query( join ' ', $results->ParsedWords( $index ) ); + #::logDebug("parsed query: " . Vend::Util::uneval($parsed_query)); + + $_swish_highlighters->{$index}->highlight( \$text, $parsed_query->{'swishdefault'}, undef, $result ); + } + push @$out_ref, $text; + } + elsif ($field eq 'mod_date' && $text) { + push @$out_ref, POSIX::strftime($date_format, localtime($text)); + } + else { + push @$out_ref, $text; + } + } + + push @out, $out_ref; + } + + { + my $field_names = join "\t", @{$s->{mv_field_names}}; + $field_names =~ s/^\s+//; + my @laundry = (qw/mv_search_field mv_range_look mv_return_fields/); + $s->hash_fields( + [ split /\Q$s->{mv_index_delim}/, $field_names ], + @laundry, + ); + } + + if ($s->{mv_unique}) { + my %seen; + @out = grep ! $seen{$_->[0]}++, @out; + } + + if ($s->{mv_sort_field} and @{$s->{mv_sort_field}}) { + $s->hash_fields( $s->{mv_field_names}, qw/mv_sort_field/ ); + @out = $s->sort_search_return(\@out); + } + + $s->{matches} = @out; + + if ($s->{matches} > $s->{mv_matchlimit} and $s->{mv_matchlimit} > 0) { + $s->save_more(\@out) + or ::logError("Error saving matches: $!"); + + if ($s->{mv_first_match}) { + splice @out, 0, $s->{mv_first_match}; + $s->{mv_next_pointer} = $s->{mv_first_match} + $s->{mv_matchlimit}; + $s->{mv_next_pointer} = 0 + if $s->{mv_next_pointer} > $s->{matches}; + } + $#out = $s->{mv_matchlimit} - 1; + } + + if (! $s->{mv_return_reference}) { + $s->{mv_results} = \@out; + #::logDebug("returning search: " . Vend::Util::uneval($s)); + return $s; + } + elsif ($s->{mv_return_reference} eq 'LIST') { + my $col = @{ $s->{mv_return_fields} }; + @out = map { join $s->{mv_return_delim}, @$_ } @out; + $s->{mv_results} = join $s->{mv_record_delim}, @out; + } + else { + my $col = @{ $s->{mv_return_fields} }; + + my @names = @{ $s->{mv_field_names} }; + $names[0] eq '0' and $names[0] = 'code'; + + my %hash; + for (@out) { + my @col = split /$s->{mv_return_delim}/, $_, $col; + + $hash{ $col[0] } = {}; + @{ $hash{$col[0]} } {@names} = @col; + } + $s->{mv_results} = \%hash; + } - return undef if $s->{matches} == -1; + #::logDebug("returning search: " . Vend::Util::uneval($s)); + return $s; +} - # Build swish line - my @cmd; - push @cmd, $s->{swish_cmd}; - push @cmd, qq{-x '$fmt_string'}; - push @cmd, "-c $s->{mv_base_directory}" - if $s->{mv_base_directory}; +sub build_search { + my ($s, $pats) = @_; + my ($search_string); - if (@searchfiles) { - push @cmd, "-f " . join(" ", @searchfiles); - } + my ($field_count, @ops, @group, @sf); - push @cmd, "-m $s->{mv_max_matches}" if $s->{mv_max_matches}; + $field_count = @{$s->{mv_searchspec}}; + @ops = $s->map_ops($field_count); + @group = @{$s->{mv_search_group}}; + @sf = @{$s->{mv_search_field}}; + my @su = @{$s->{mv_substring_match}}; - local($/) = $s->{mv_record_delim} || "\n"; - - $s->save_specs(); + my (@specs_by_group, @joiner); - my $spec = join ' ', @pats; - - $spec =~ s/[^-\w()"\s\*]+//g - and $CGI::values{debug} - and ::logError("Removed unsafe characters from search string"); - - if(length($spec) < $s->{mv_min_string}) { - my $msg = ::errmsg( - "Swish search string less than minimum %s characters: %s", - $s->{mv_min_string}, - $spec, - ); - return $s->search_error($msg); - } - - push @cmd, qq{-w $spec}; - - if(length($spec) < $s->{mv_min_string}) { - my $msg = ::errmsg (<<EOF, $s->{mv_min_string}, $spec); -Search strings must be at least %s characters. -You had '%s' as the operative characters of your search strings. -EOF - return $s->search_error($msg); - } - - my $cmd = join ' ', @cmd; - - my $cwd = `pwd`; - chomp($cwd); -#::logDebug("Swish command '$cmd' cwd=$cwd"); - - open(SEARCH, "$cmd |") - or ::logError( "Couldn't fork swish search '$cmd': $!"), next; - #$s->adjust_delimiter(\*SEARCH) if $s->{mv_delimiter_auto}; - my $line; - my $field_names; - -#::logDebug("search after getting fields: self=" . ::uneval({%$s})); - my $prospect; - - my $f = sub { 1 }; - - eval { - ($limit_sub, $prospect) = $s->get_limit($f, 1); - }; - - $@ and return $s->search_error("Limit subroutine creation: $@"); - - $f = $prospect if $prospect; - - eval {($return_sub, $delayed_return) = $s->get_return(undef, 1)}; - - $return_sub = sub { return [ split $s->{mv_index_delim}, shift(@_) ] }; - - $@ and return $s->search_error("Return subroutine creation: $@"); - - my $field_names = join "\t", @{$s->{mv_field_names}}; - $field_names =~ s/^\s+//; - my @laundry = (qw/mv_search_field mv_range_look mv_return_fields/); - $s->hash_fields( - [ split /\Q$s->{mv_index_delim}/, $field_names ], - @laundry, - ); - undef $field_names; - - if($limit_sub) { - while(<SEARCH>) { -#::logDebug("swish line, limit_sub: $_"); - next if /^#/; - last if $_ eq ".\n"; - $limit_sub->($_); - push @out, $return_sub->($_); - } - } - else { - while(<SEARCH>) { -#::logDebug("swish line: $_"); - next if /^#/; - last if $_ eq ".\n"; - push @out, $return_sub->($_); + for (my $i = 0; $i < $field_count; $i++) { + + # validate $group first + if (@sf) { + push (@{$specs_by_group[$group[$i]]}, ["$sf[$i] = $pats->[$i]", $s->{mv_orsearch}->[$i]]); + } else { +# if ($su[$i]) { +# push (@{$specs_by_group[$group[$i]]}, ["*$pats->[$i]", $s->{mv_orsearch}->[$i]]); +# } else { + push (@{$specs_by_group[$group[$i]]}, [$pats->[$i], $s->{mv_orsearch}->[$i]]); +# } } + # record joiner, last one prevails + $joiner[$group[$i]] = $s->{mv_orsearch}->[$i] ? 'OR' : 'AND'; } + + my @gall; - if(scalar(@out) == 1 and $out[0][0] =~ s/^err:\s*(.*?)\s*$//) { - # presumably search error signaled by Swish - @out = (); - - # don't consider missing matches as search error - unless ($1 eq 'no results') { - $s->{matches} = -1; - return $s->search_error($1); + for (my $i = 0; $i < @specs_by_group; $i++) { + my $gsp = $specs_by_group[$i]; + my @gout; + + for (my $j = 0; $j < @$gsp; $j++) { + push (@gout, $gsp->[$j][0]); + if ($gsp->[$j][1]) { + push (@gout, 'OR'); + } else { + push (@gout, 'AND'); + } } - } - - $s->{matches} = scalar(@out); -#::logDebug("gsearch before delayed return: self=" . ::Vend::Util::uneval_it({%$s})); - if($s->{mv_sort_field} and @{$s->{mv_sort_field}}) { - $s->hash_fields($s->{mv_field_names}, qw/mv_sort_field/); - @out = $s->sort_search_return(\@out); - } -#::logDebug("after delayed return: self=" . ::Vend::Util::uneval_it({%$s})); + # remove last operator + pop (@gout); - if($s->{mv_unique}) { - my %seen; - @out = grep ! $seen{$_->[0]}++, @out; - $s->{matches} = scalar(@out); + $gall[$i] = join (' ', @gout); } - if ($s->{matches} > $s->{mv_matchlimit} and $s->{mv_matchlimit} > 0) { - $s->save_more(\@out) - or ::logError("Error saving matches: $!"); - if ($s->{mv_first_match}) { - splice(@out,0,$s->{mv_first_match}); - $s->{mv_next_pointer} = $s->{mv_first_match} + $s->{mv_matchlimit}; - $s->{mv_next_pointer} = 0 - if $s->{mv_next_pointer} > $s->{matches}; + if (@gall > 1) { + my $i; + for ($i = 0; $i < @gall - 1; $i++) { + $search_string .= "($gall[$i]) $joiner[$i] "; } - $#out = $s->{mv_matchlimit} - 1; + $search_string .= $gall[$i]; + } else { + $search_string = $gall[0]; } - if(! $s->{mv_return_reference}) { - $s->{mv_results} = \@out; -#::logDebug("returning search: " . Vend::Util::uneval($s)); - return $s; - } - elsif($s->{mv_return_reference} eq 'LIST') { - my $col = scalar @{$s->{mv_return_fields}}; - @out = map { join $s->{mv_return_delim}, @$_ } @out; - $s->{mv_results} = join $s->{mv_record_delim}, @out; - } - else { - my $col = scalar @{$s->{mv_return_fields}}; - my @col; - my @names; - @names = @{$s->{mv_field_names}}; - $names[0] eq '0' and $names[0] = 'code'; - my %hash; - my $key; - for (@out) { - @col = split /$s->{mv_return_delim}/, $_, $col; - $hash{$col[0]} = {}; - @{ $hash{$col[0]} } {@names} = @col; - } - $s->{mv_results} = \%hash; - } -#::logDebug("returning search: " . Vend::Util::uneval($s)); - return $s; + ::logError ("Search string is: $search_string"); + return $search_string; } # Unfortunate hack need for Safe searches -*create_search_and = \&Vend::Search::create_search_and; -*create_search_or = \&Vend::Search::create_search_or; -*dump_options = \&Vend::Search::dump_options; *escape = \&Vend::Search::escape; -*get_limit = \&Vend::Search::get_limit; -*get_return = \&Vend::Search::get_return; +*spec_check = \&Vend::Search::spec_check; *get_scalar = \&Vend::Search::get_scalar; -*hash_fields = \&Vend::Search::hash_fields; -*map_ops = \&Vend::Search::map_ops; *more_matches = \&Vend::Search::more_matches; +*get_return = \&Vend::Search::get_return; +*map_ops = \&Vend::Search::map_ops; +*get_limit = \&Vend::Search::get_limit; +*saved_params = \&Vend::Search::saved_params; *range_check = \&Vend::Search::range_check; -*restore_specs = \&Vend::Search::restore_specs; +*create_search_and = \&Vend::Search::create_search_and; +*create_search_or = \&Vend::Search::create_search_or; *save_context = \&Vend::Search::save_context; +*dump_options = \&Vend::Search::dump_options; *save_more = \&Vend::Search::save_more; +*sort_search_return = \&Vend::Search::sort_search_return; +*get_scalar = \&Vend::Search::get_scalar; +*hash_fields = \&Vend::Search::hash_fields; *save_specs = \&Vend::Search::save_specs; -*saved_params = \&Vend::Search::saved_params; +*restore_specs = \&Vend::Search::restore_specs; +*splice_specs = \&Vend::Search::splice_specs; *search_error = \&Vend::Search::search_error; +*save_more = \&Vend::Search::save_more; *sort_search_return = \&Vend::Search::sort_search_return; -*spec_check = \&Vend::Search::spec_check; -*splice_specs = \&Vend::Search::splice_specs; 1; __END__ _______________________________________________ interchange-cvs mailing list interchange-cvs[at]icdevgroup.org http://www.icdevgroup.org/mailman/listinfo/interchange-cvs
|