<?

# class to turn boolean queries into sql queries
# Copyright (c) Adriane Boyd

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

# Use:

# $btosql = new BooleanToSQL($querystring);
# $varnames = array("title", "notes");
# $sqlquery = $btosql->getSQL($varnames, "or", "regexp", "[[:<:]]", "[[:>:]]");

# getSQL arguments: array of column names, string to join sets of column 
#  expressions, relation for expression (like, regexp, etc.), string to put
#  before each word, string to put after each word (e.g. "%", "[[:<:]]")

# result: ($varname regexp '[[:<:]]$wordfromquerystring[[:>:]]') or $varname ...

require_once("Token.php");

class 
BooleanToSQL
{
    var 
$query;
    var 
$ops;    # valid operators other than parens, not case-sensitive
    
var $tarray;    # infix token array
    
var $ptarray;    # postfix token array

    # constructor sets up query and valid operators
    
function BooleanToSQL($bquery)
    {
        
$this->query $bquery;
        
$this->ops = array("and""or""not");
    }

    function 
setQuery($bquery)
    {
        
$this->query $bquery;
    }

    function 
getQuery()
    {
        return 
$this->query;
    }

    function 
getCount()
    {
        if(!
$this->getTokens())
        {
            return 
0;
        }

        
$count 0;
        for(
$i 0$i count($this->tarray); $i++)
        {
            if(
$this->tarray[$i]->isOperator() == 0)
            {
                
$count++;
            }
        }
        return 
$count;
    }

    
# tokenize the query
    
function getTokens()
    {
        
$str $this->query;
        
$t 0;
        while(
$str != "")
        {
            
$str trim($str);

            
# take the first character in the remaining query
            
$c substr($str01);

            
# if a quote character, match anything up to the next
            # matching quote
            
if($c == "\"" || $c == "'")
            {
                
preg_match("/^$c([^$c]*)$c/"$str$matches);
                
$val preg_quote($matches[1]);
                
$str preg_replace("/^$c$val$c/"""$str);
                
$token = new Token($matches[1]);
            }

            
# if a parenthesis, create a token for it
            
else if($c == "(" || $c == ")")
            {
                
$str substr($str1);
                
$token = new Token($c1);
            }

            
# otherwise match any non-special characters to find
            # the next word or word operator
            
else
            {
                
preg_match("/^([^\s\(\)\"\']+)/"$str$matches);
                
$val preg_quote($matches[1]);
                
$str preg_replace("/^$val/"""$str);
                
$token = new Token($matches[1]);

                
# if the token is one of the operators, set the
                # operator flag for that token
                
for($j 0$j count($this->ops); $j++)
                {
                    if(
strtolower($val) == $this->ops[$j])
                    {
                        if(
$val == "not")
                        {
                            
$val "and not";
                        }
                        
$token->setValue(strtolower($val));
                        
$token->setOperator();
                        break;
                    }
                }
            }
            
$this->tarray[$t] = $token;
            
$t++;

            
# if the query is too long or something else has gone
            # awry, break out of the loop
            
if($t 100)
            {
                return 
false;
            }
        }
        return 
true;
    }

    
# given an array of variable names (table columns), a string to join
    # each variable set with, the relation operator, and the strings to
    # place before and after each query token in the string: 
    # '$pre$word$post'
    
function getSQL($varnames$join$rel$pre$post)
    {
        if(!
$this->postfix())
        {
            return 
0;
        }
        if(
count($varnames) < || count($this->ptarray) < 1)
        {
            return 
0;
        }

        
$wclause $this->createWhere($varnames[0], $this->ptarray$rel$pre$post);
        for(
$i 1$i count($varnames); $i++)
        {
            
$wclause .= "$join " .$this->createWhere($varnames[$i], $this->ptarray$rel$pre$post);
        }
        return 
$wclause;
    }

    
# print a token array for debugging purposes
    
function printTokens($array)
    {
        for(
$i 0$i count($array); $i++)
        {
            print 
"$i: " $array[$i]->isOperator() . " " $array[$i]->getValue() . "<br>\n";
        }
    }

    
# create the section of the where clause for column $varname given
    # an array of keywords, the relation operator, and pre and post
    # strings which surround the word in the expression
    
function createWhere($varname$array$rel$pre$post)
    {
        
$tmpstack = array();

        if(
count($array) == 0)
        {
            return 
0;
        }

        while(
count($array) > 0)
        {
            if(
$array[0]->isOperator() == 0)
            {
                
$val $array[0]->getEValue($rel);
                
$tok = new Token("( $varname $rel '$pre$val$post') ");
                
array_push($tmpstack$tok);
            }
            else
            {
                
$word2 array_pop($tmpstack);
                
$word1 array_pop($tmpstack);
                
$op $array[0]->getValue();
                
$tok = new Token(" (" $word1->getValue() . " $op " $word2->getValue() . ") ");
                
array_push($tmpstack$tok);
            }
            
array_shift($array);
        }
        return 
$tmpstack[0]->getValue();
    }

    function 
escapeVal($val$rel)
    {
        if(
preg_match("/regexp|rlike/"$rel))
        {
            
$val preg_quote($val);
        }
        
$val preg_replace("/\*/""%"$val);
        
$val addslashes($val);
        if(
preg_match("/regexp|rlike/"$rel))
        {
            
$val preg_replace("/\\\\\\\\%/"".*"$val);
        }
        return 
$val;
    }

    
# create a postfix token array from the infix token array, returning
    # 1 if successful and 0 if there are any problems
    
function postfix()
    {
        
$this->getTokens();
        
$tmpstack = array();
        
$prevWord 0;        # 1 if the previous token wasn't an op
        
$tarray $this->tarray;
        
$this->ptarray = array();

        
# check for balanced parentheses in the input
        
$openparen 0$closeparen 0;
        for(
$i 0$i count($tarray); $i++)
        {
            if(
$tarray[$i]->getValue() == "(")
            {
                
$openparen++;
            }
            else if(
$tarray[$i]->getValue() == ")")
            {
                
$closeparen++;
            }
        }
        if(
$openparen != $closeparen)
        {
            return 
0;
        }

        while(
count($tarray) > 0)
        {
            
# if not an operator, push on the temp stack
            
if($tarray[0]->isOperator() == 0)
            {
                
array_push($tmpstackarray_shift($tarray));
                if(
$prevWord == 1)
                {
                    return 
0;
                }
                else
                {
                    
$prevWord 1;
                }
            }
            
# if ), pop the temp stack to output until ( is found
            
else if($tarray[0]->getValue() == ")")
            {
                
$prevWord 0;
                
$tcount count($tmpstack);
                while(
$tcount && $tmpstack[$tcount 1]->getValue() != "(")
                {
                    
array_push($this->ptarrayarray_pop($tmpstack));
                    
$tcount count($tmpstack);
                }
                
array_shift($tarray);
                
array_pop($tmpstack);
            }
            
# if (, push on temp stack
            
else if($tarray[0]->getValue() == "(")
            {
                
$prevWord 0;
                
array_push($tmpstackarray_shift($tarray));
            }
            
# otherwise, pop the stack until another operator is
            # found and push the operator from the input on the 
            # stack
            
else
            {
                
$prevWord 0;
                
$tcount count($tmpstack);
                while(
$tcount && $tmpstack[$tcount 1]->isOperator() == 0)
                {
                    
array_push($this->ptarrayarray_pop($tmpstack));
                    
$tcount count($tmpstack);
                }
                
array_push($tmpstackarray_shift($tarray));
            }
        }

        
# pop all remaining terms from temp stack to output
        
while(count($tmpstack) > 0)
        {
            
array_push($this->ptarrayarray_pop($tmpstack));

        } 

        
# check to make sure that there is one more word than operator
        
$numwords $numops 0;
        for(
$i 0$i count($this->ptarray); $i++)
        {
            if(
$this->ptarray[$i]->isOperator())
            {
                
$numops++;
            }
            else
            {
                
$numwords++;
            }
        }
        if(
$numops != ($numwords 1))
        {
            return 
0;
        }
        return 
1;
    }



}

?>