<?PHP
/**
 * Searchquerystring Parser
 *
 * Parses a search query into it's tokens kann detect + - AND OR NOT Operators
 * and Phrases.
 *
 * @package PHP Competition System
 * @author  Eric GALLENE <okrome@free.fr>
 * @link	http://www.phpcompet.com
 * @version $Id: queryparser.php,v 1.00 2006/10/22 18:15:00
 */


/**
 * Querystringparser
 *
 * Parses a querystring into a datastructure
 *
 * @param   string  $query    Querystring
 * @param   string  &$errors  Stringreference to write errors back
 * @return  array             parsed querytokens
 */
function queryparser($query, &$errors)
{
    $query	= trim($query);

    $ops    = array();
    $struct = array();
    $tokens	= tokenizer($query);

    // look through tokens
    while ($current = array_shift($tokens))
    {
        if (preg_match('/^(AND|OR|NOT)$/i', $current)) 
        {  
            // token is operator
            $ops[] = strtoupper($current);
        }
        else
        {                                      
            // token is searchword
            if (!count($ops)) 
            {
                // empty operator counts as AND
                $ops[] = 'AND';
            }
            
            // clean invalid operators
            $cleanops = cleanoperators($ops, $errors);

            // check wildcards
            $wild = '';
            if (substr($current,0,1) == '*') $wild .= 'l';
            if (substr($current, -1) == '*') $wild .= 'r';

            $current    = str_replace('*', '', $current);
            $struct[]   = array('ops'      => $cleanops,
                                'token'    => $current,
                                'wildcard' => $wild);
            $ops = array();
        }
    }
    return $struct;
}

/**
 * Querystring tokenizer
 *
 * Parse string into array of tokens. 
 * Honors literal expressions enclosed by "",
 * converts +/- to AND and NOT
 *
 * @param   string    Querystring
 * @return  array     All tokens of the Strings
 */
function tokenizer($qstring)
{
    // replace +/- with AND and NOT
    $qstring = ' '.$qstring; // for following regexps
    $qstring = preg_replace('/(\s)-(\S)/',  '\1NOT \2', $qstring);
    $qstring = preg_replace('/(\s)\+(\S)/', '\1AND \2', $qstring);
    $qstring = trim($qstring);

    $tokens  = array();
    $current = '';
    $sep     = '\s';

    for ($i=0; $i < strlen($qstring); $i++)
    {
        $char = $qstring{$i};

        // match current separator?
        if (preg_match("/$sep/", $char))
        {
            $current = trim($current);
            if (!empty($current) AND ((str_replace('*', '', $current)) != '')) 
            {
                // add non-empty token
                $tokens[] = $current;
            }
            $current    = '';
            $sep		= '\s';
        }

        // begin literal expression?
        elseif ($char == '"') 
        {
            $sep = '"';
        }
        
        // normal token character
        else 
        {
            $current .= $char;
        }
    }

    // add remaining token
    $current = trim($current);
    if (!empty($current) AND ((str_replace('*','',$current))!='')) 
    {
        $tokens[] = $current;
    }

    return $tokens;
}

/**
 * Operator cleaning
 *
 * removes illogical operator combinations...
 *
 * @param   array   Operators
 * @param   string  Stringreference to write errors back
 * @return  string  cleaned Operators
 */
function cleanoperators($ops, &$errors)
{
    $newops = array();

    // make unique
    $ops = array_unique($ops);
    
    // sort
    if (in_array('AND', $ops))  $newops[] = 'AND';
    if (in_array('OR', $ops))   $newops[] = 'OR';
    if (in_array('NOT', $ops))  $newops[] = 'NOT';

    // join
    $opstr = join(' ', $newops);

    // clean unnormal conditions
    if (strstr($opstr, 'AND OR')) 
    {
        $errors	.="Logic operator 'AND OR' isn't compatible with 'OR' operator.\n";
        $opstr	 = str_replace('AND OR', 'OR', $opstr);
    }
    if (strstr($opstr, 'OR NOT')) 
    {
        $errors	.="Logic operator 'OR NOT' isn't compatible with 'AND' operator.\n";
        $opstr	 = str_replace('OR NOT', 'AND', $opstr);
    }
    if ($opstr == 'NOT') 
    {
        $opstr = 'AND NOT';
    }

    return $opstr;
}

?>
