%{
#
# Copyright (C) Motorola 2003 - All rights reserved
# Copyright (C) Crawford Currie 2004
#
=begin text
---++ class Search
Search operators work on the fields of a TWiki::Contrib::DBCacheContrib::Map. The fields are given by name, and the values by strings. Strings should always be surrounded by 'single-quotes'. String which are regular expressions use 'perl' re syntax (see =man perlre= for help)
*Warning* single and double quotes are not allowed in values!
The following operators are available:
| *Operator* | *LHS* | *RHS* | *Meaning* |
| = | field name | regular expression | Value exactly matches this regular expression. The expression must match the whole string. |
| != | field name | regular expression | Field is not this RE. Inverse of = |
| =~ | field name | regular expression | Value contains this regular expression i.e. the RE is found somewhere in the field value. |
| < | field name | integer (string containing an integer e.g '4') | Number is < |
| > | field name | integer | Number is > |
| >= | field name | integer | Number is >= |
| <= | field name | integer | Number is <= |
| ! | none | expr | Boolean NOT |
| AND | expr | expr | Boolean AND |
| OR | expr | expr | Boolean OR |
| () | N/A | N/A | Bracketed subexpression |
A search object implements the "matches" method as its general
contract with the rest of the world.
---+++ Example
Get a list of attachments that have a date earlier than 1st January 2000
$db = new TWiki::Contrib::DBCacheContrib::DBCache( $web ); # always done
$db->load();
my $search = new TWiki::Contrib::DBCacheContrib::Search("date EARLIER_THAN '1st January 2000'");
foreach my $topic ($db->getKeys()) {
my $attachments = $topic->get("attachments");
foreach my $val ($attachments->getValues()) {
if ($search->matches($val)) {
print $val->get("name") . "\n";
}
}
}
=cut
use TWiki::Plugins::SpreadSheetPlugin::Calc;
use TWiki::Func;
=begin text
---+++ =create($string)=
* =$string= - string containing an expression to parse
Construct a new search node by parsing the passed expression.
=cut
sub create {
my ( $class, $string, $left, $op, $right, $evalright ) = @_;
if ( defined( $string )) {
if ( $string =~ m/^\s*$/o ) {
return create Search( undef, undef, "TRUE", undef );
} else {
my $calc = new Search;
$calc->YYData->{INPUT} = $string;
return $calc->YYParse(yylex => \&Lexer);
}
} else {
my $this;
$this = bless( {}, $class );
$this->{right} = $right;
$this->{eval} = $evalright;
$this->{left} = $left;
$this->{op} = $op;
return $this;
}
}
%}
%left 'OR'
%left 'AND'
%right '!'
%left '=' '=~' '!=' '>=' '<=' '<' '>' 'EARLIER_THAN' 'LATER_THAN' 'WITHIN_DAYS' 'IS_DATE'
%%
input: #empty
| line { $_[1] }
;
line:
exp { ++$_[0]->YYData->{LINE}; $_[1] }
| error { ++$_[0]->YYData->{LINE}; undef }
;
prim: NUM
| TERM
| QUOT
;
TERM: PRIM { $_[1] }
| CALC { create Search( undef, undef, 'calc', $_[1], undef ) }
;
exp:
TERM '=' QUOT { create Search( undef, $_[1], $_[2], $_[3] ) }
| TERM '=~' QUOT { create Search( undef, $_[1], $_[2], $_[3] ) }
| TERM '!=' QUOT { create Search( undef, $_[1], $_[2], $_[3] ) }
| TERM '=' TERM { create Search( undef, $_[1], $_[2], $_[3], 1 ) }
| TERM '=~' TERM { create Search( undef, $_[1], $_[2], $_[3], 1 ) }
| TERM '!=' TERM { create Search( undef, $_[1], $_[2], $_[3], 1 ) }
| TERM '>=' QUOT { create Search( undef, $_[1], $_[2], $_[3] ) }
| TERM '<=' QUOT { create Search( undef, $_[1], $_[2], $_[3] ) }
| TERM '<' QUOT { create Search( undef, $_[1], $_[2], $_[3] ) }
| TERM '>' QUOT { create Search( undef, $_[1], $_[2], $_[3] ) }
| TERM 'EARLIER_THAN' QUOT { create Search( undef, $_[1], $_[2], $_[3] ) }
| TERM 'LATER_THAN' QUOT { create Search( undef, $_[1], $_[2], $_[3] ) }
| TERM 'WITHIN_DAYS' QUOT { create Search( undef, $_[1], $_[2], $_[3] ) }
| TERM 'IS_DATE' QUOT { create Search( undef, $_[1], $_[2], $_[3] ) }
| TERM '>=' TERM { create Search( undef, $_[1], $_[2], $_[3], 1 ) }
| TERM '<=' TERM { create Search( undef, $_[1], $_[2], $_[3], 1 ) }
| TERM '<' TERM { create Search( undef, $_[1], $_[2], $_[3], 1 ) }
| TERM '>' TERM { create Search( undef, $_[1], $_[2], $_[3], 1 ) }
| PRIM { create Search( undef, "text", "=~", $_[1] ) }
| CALC { create Search( undef, undef, 'calc', $_[1], undef ) }
| '!' exp { create Search( undef, undef, $_[1], $_[2], undef ) }
| MATCH { create Search( undef, undef, 'match', $_[1], undef ) }
| exp 'AND' exp { create Search( undef, $_[1], $_[2], $_[3] ) }
| exp 'OR' exp { create Search( undef, $_[1], $_[2], $_[3] ) }
| '(' exp ')' { $_[2] }
;
%%
sub Error {
my($parser)=shift;
push(@{$parser->YYData->{ERRLINES}}, $parser->YYData->{LINE});
}
sub Lexer {
my($parser)=shift;
exists($parser->YYData->{LINE})
or $parser->YYData->{LINE}=1;
$parser->YYData->{INPUT}
or return('',undef);
$parser->YYData->{INPUT}=~s/^[ \t]//;
for ($parser->YYData->{INPUT}) {
s/^([0-9]+(?:\.[0-9]+)?)// and return('NUM',$1);
s/^(\'.*?\')// and return('QUOT',substr($1,1,-1));
s/^(\=\~)// and return($1,$1);
s/^(\=)// and return($1,$1);
s/^(\!\=)// and return($1,$1);
s/^(\>\=)// and return($1,$1);
s/^(\>)// and return($1,$1);
s/^(\<\=)// and return($1,$1);
s/^(\<)// and return($1,$1);
s/^(\!)// and return($1,$1);
s/^(\()// and return($1,$1);
s/^(\))// and return($1,$1);
s/^(AND)// and return($1,$1);
s/^(OR)// and return($1,$1);
s/^\$CALC\(\"(.*?)\"\)// and return('CALC',$1);
s/^(\w+\[\?.*?\])// and return('MATCH',$1);
s/^(\w+(?:(?:\.\w+)|(?:\[[0-9]+\]))*)// and return('PRIM',$1);
}
}
=begin text
---+++ =matches($object)= -> boolean
* =$object= - object to test; must implement =get=
See if object matches the search. =$object= can actually be any object that provides
the method "get" that returns a value given a string key.
=cut
sub matches {
my ( $this, $map, $case ) = @_;
my $op = $this->{op};
return 1 if ( $op eq "TRUE" );
my $r = $this->{right};
return 0 unless ( defined( $r ));
if ($op eq "!") { return !( $r->matches( $map, $case )) };
if ($op eq "match") {
my $subfield = $map->get( $r );
if ( defined( $subfield )) {
if ( $subfield->isa( "TWiki::Contrib::DBCacheContrib::Array" ) && ($subfield->size() > 0) ) {
return $subfield;
} else { return 0; }
} else { return 0; }
}
if ($op eq "calc") {
return _evalCalc($r, $map);
}
my $l = $this->{left};
return 0 unless ( defined( $l ));
if ( $op eq "OR" ) {
my $tmp = $l->matches( $map, $case );
return $tmp if ($tmp || ! defined $tmp);
return $r->matches( $map, $case );
}
if ( $op eq "AND" ) {
my $tmp = $l->matches( $map, $case );
return $tmp if (! $tmp && defined $tmp);
return $r->matches( $map, $case );
}
return 0 unless ( defined( $map ));
my $val;
if ( ref($l) eq "Search" && $l->{op} eq "calc" ) {
$val = _evalCalc($l->{right}, $map);
} else {
$val = $map->get( $l );
# Compatibility: values have now moved down into the form for topics,
# but we need this just in case the search specifier is "old style"
unless ($val) {
$val = $map->get($map->get("form"))->get( $l );
}
return 0 unless ( defined( $val ));
}
$val = undef unless $val;
return $val unless ( $r );
if ( ref($r) eq "Search" && $r->{op} eq "calc" ) {
$r = _evalCalc($r->{right}, $map);
} else {
# expand variables
$r = $map->get( $r ) if $this->{eval};
}
if ( $case ) {
if ( $op eq "=" ) { return ( $val =~ m/^$r$/ ) };
if ( $op eq "!=" ) { return ( $val !~ m/^$r$/ ) };
if ( $op eq "=~" ) { return ( $val =~ m/$r/ ) };
} else {
if ( $op eq "=" ) { return ( $val =~ m/^$r$/i ) };
if ( $op eq "!=" ) { return ( $val !~ m/^$r$/i ) };
if ( $op eq "=~" ) { return ( $val =~ m/$r/i ) };
}
if ( $op eq ">" ) { return ( $val > $r ) };
if ( $op eq "<" ) { return ( $val < $r ) };
if ( $op eq ">=" ) { return ( $val >= $r ) };
if ( $op eq "<=" ) { return ( $val <= $r ) };
return 0;
}
sub _evalCalc {
my ($calc, $map) = @_;
# substitute for "$T(\s*xxx\s*)" the value of $map->get("xxx");
# then evaluate calc
$calc =~ s/\$T\(\s*(\w+(?:(?:\.\w+)|(?:\[[0-9]+\]))*)\s*\)/$map->get($1)/geos;
return TWiki::Plugins::SpreadSheetPlugin::Calc::doCalc($calc);
}
=begin text
---+++ =toString()= -> string
Generates a string representation of the object.
=cut
sub toString {
my $this = shift;
my $text = "";
if ( defined( $this->{left} )) {
if ( !ref($this->{left}) ) {
$text .= $this->{left};
} else {
$text .= "(" . $this->{left}->toString() . ")";
}
$text .= " ";
}
$text .= $this->{op} . " ";
if ( !ref($this->{right}) ) {
$text .= "'" . $this->{right} . "'";
} else {
$text .= "(" . $this->{right}->toString() . ")";
}
return $text;
}