Location: PHPKode > projects > Tv.2 CMS > tv2engine/tv2Xquery.class.php
<?php


/**
 * Branch id key
 * @constant int XDBQ_ID
 * @package tv2-engine
 */
define('XDBQ_ID',      0);
/**
 * Branch type key
 * @constant int XDBQ_TYPE
 * @package tv2-engine
 */
define('XDBQ_TYPE',    2);
/**
 * Branch weight key
 * @constant int XDBQ_WEIGHT
 * @package tv2-engine
 */
define('XDBQ_WEIGHT',  4);
/**
 * Branch parent id key
 * @constant int XDBQ_PARENT
 * @package tv2-engine
 */
define('XDBQ_PARENT',  5);
/**
 * Branch status key
 * @constant int XDBQ_STATUS
 * @package tv2-engine
 */
define('XDBQ_STATUS',  6);
/**
 * Branch partial result key
 * @constant int XDBQ_PARTIAL
 * @package tv2-engine
 */
define('XDBQ_PARTIAL', 7);
/**
 * Branch scope key
 * @constant int XDBQ_SCOPE
 * @package tv2-engine
 */
define('XDBQ_SCOPE',   8);
/**
 * Branch children key
 * @constant int XDBQ_CHILDREN
 * @package tv2-engine
 */
define('XDBQ_CHILDREN',9);

/**
 * Branch status: sleeping (partial result = all ids)
 * @constant int XDBQ_SLEEP
 * @package tv2-engine
 */
define('XDBQ_SLEEP',0);
/**
 * Branch status:  working (partial result changed)
 * @constant int XDBQ_WORK
 * @package tv2-engine
 */
define('XDBQ_WORK',1);

/**
 * Branch type: OR branch (plus)
 * @constant int XDBQ_QP
 * @package tv2-engine
 */
define('XDBQ_QP',0);
/**
 * Branch type: AND branch (multiply)
 * @constant int XDBQ_QM
 * @package tv2-engine
 */
define('XDBQ_QM',1);
/**
 * Branch type: NOT branch
 * @constant int XDBQ_QN
 * @package tv2-engine
 */
define('XDBQ_QN',2);
/**
 * Branch type: NULL branch (passes through partial result)
 * @constant int XDBQ_Q0
 * @package tv2-engine
 */
define('XDBQ_Q0',3);
/**
 * Branch type: query on system field
 * @constant int XDBQ_QS
 * @package tv2-engine
 */
define('XDBQ_QS',4);
/**
 * Branch type: query on index field
 * @constant int XDBQ_QI
 * @package tv2-engine
 */
define('XDBQ_QI',5);
/**
 * Branch type: query on unindexed field (full query)
 * @constant int XDBQ_QF
 * @package tv2-engine
 */
define('XDBQ_QF',6);

require_once(ENGINE_DIR.'ctlSets.flib.php');

/** 
 * Query parser and processor.
 *
 * @package tv2-engine
 * @author Emilis Dambauskas (hide@address.com)
 * @copyright 2002–2003 Emilis Dambauskas under {@link http://opensource.org/licenses/artistic-license.php Artistic license}
 * @version $Id: tv2Xquery.class.php,v 1.3 2003/07/12 00:52:15 lunaticlt Exp $ 
 * @class tv2XQuery 
 */
class tv2XQuery
{
	/**
	 * Reference to {@link tv2Xdb::$fat} array
	 * @attribute private array $fat
	 */
	var $fat;
	
	/**
	 * Query branch array
	 * @attribute private array $b
	 */
	var $b;
	
	/**
	 * Query result
	 * @attribute private array $qr
	 */
	var $qr;
	
	/**
	 * Array of all tv2Xdb document ids
	 * @attribute private array $iid
	 */
	var $iid;
	/**
	 * Reference to {@link tv2XQuery:$iid} array
	 * @attribute private array $liid
	 */
	var $liid;
	
	/**
	 * document ids indexed by document name
	 * @attribute private array $iname
	 */
	var $iname;
	/**
	 * document ids indexed by document path
	 * @attribute private array $ipath
	 */
	var $ipath;
	/**
	 * document ids indexed by document type
	 * @attribute private array $itype
	 */
	var $itype;
	/**
	 * document ids indexed by document ctime
	 * @attribute private array $ictime
	 */
	var $ictime;
	
	//var $iindex;
	
	/**
	 * {@link ctlIndexer} object
	 * @attribute private object xi
	 */
	var $xi;
	
	/**
	 * Experimental. document ids indexed by document version language
	 * @attribute private array $ilanguage
	 */
	var $ilanguage;
	/**
	 * Experimental. document ids indexed by document version context
	 * @attribute private array $icontext
	 */
	var $icontext;
	/**
	 * Experimental. document ids indexed by document version status
	 * @attribute private array $istatus
	 */
	var $istatus;
	/**
	 * Experimental. document ids indexed by document version time
	 * @attribute private array $ivtime
	 */
	var $ivtime;
	
	/**
	 * Constructor
	 */
	function tv2XQuery()
	{
		$this->ver = FALSE;
		$this->ver_context = NULL;
		$this->ver_filter = NULL;
	}
	
//----------------- MAPPABLE METHODS -----------------------------------------
	
	/**
	 * Error reporting
	 *
	 * @method private error
	 * @param string $msg - error message 
   * @return boolean always returns FALSE
   * @use $errors
   */
	function error($msg)
	{
		$GLOBALS['errors']->add(get_class($this).': '.$msg);
		return FALSE;
	}
	
//----------------- PRIVATE METHODS -----------------------------------------

//----------------- Wasp engine methods --------------------------------------
//*///------------------------------------------------------------------------

/**
 * Sends partial result to parent branch
 *
 * @method private propagateUp  
 * @param int $p parent branch id 
 * @param array $r partial result 
 * @param mixed $c child branch id  
 */  
function propagateUp($p, $r, $c)
{
	// update status & partial
	if ($this->b[$p][XDBQ_STATUS] == XDBQ_SLEEP)
	{
		$this->b[$p][XDBQ_PARTIAL] = $r;
		$this->b[$p][XDBQ_STATUS] = XDBQ_WORK;
	}
	else
	{
		if ($this->b[$p][XDBQ_TYPE] == XDBQ_QP)
				$this->b[$p][XDBQ_PARTIAL] = ctlSetUnion($this->b[$p][XDBQ_PARTIAL], $r);
		else if ($this->b[$p][XDBQ_TYPE] == XDBQ_QM)
				$this->b[$p][XDBQ_PARTIAL] = ctlSetIntersect($this->b[$p][XDBQ_PARTIAL], $r);
	}
	
	// update scope
	if ($this->b[$p][XDBQ_TYPE] == XDBQ_QP)
			$this->b[$p][XDBQ_SCOPE] = ctlSetSubtract($this->b[$p][XDBQ_SCOPE], $this->b[$p][XDBQ_PARTIAL]);
	else if ($this->b[$p][XDBQ_TYPE] == XDBQ_QM)
			$this->b[$p][XDBQ_SCOPE] = $this->b[$p][XDBQ_PARTIAL];
	
	// unset C if finished
	if (($this->b[$c][XDBQ_TYPE] != XDBQ_QP && $this->b[$c][XDBQ_TYPE] != XDBQ_QM) XOR !sizeof($this->b[$c][XDBQ_CHILDREN]))
	{
		foreach ($this->b[$p][XDBQ_CHILDREN] as $k => $v)
				if ($v == $c)
						unset($this->b[$p][XDBQ_CHILDREN][$k]);
		unset($this->b[$c]);
	}
	
	// if finished propagate up or return result
	if (!sizeof($this->b[$p][XDBQ_CHILDREN]))
			if ($this->b[$p][XDBQ_PARENT] !== NULL)
					$this->propagateUp($this->b[$p][XDBQ_PARENT], $this->b[$p][XDBQ_PARTIAL], $p);
			else
					$this->fillResult($this->b[$p][XDBQ_PARTIAL]);
	// else propagate down scope
	else
			foreach ($this->b[$p][XDBQ_CHILDREN] as $v)
					$this->propagateDown($v, $this->b[$p][XDBQ_SCOPE]);
}
//*///------------------------------------------------------------------------

/**
 * updates scope on child branch
 *
 * @method private propagateDown  
 * @param int $c child branch id 
 * @param array $s scope array  
 */  
function propagateDown($c, $s)
{
	if ($this->b[$c][XDBQ_TYPE] == XDBQ_QP)
	{
		$this->b[$c][XDBQ_PARTIAL] = ctlSetIntersect($this->b[$c][XDBQ_PARTIAL], $s);
		$this->b[$c][XDBQ_SCOPE] = ctlSetIntersect($this->b[$c][XDBQ_SCOPE], $s);
		foreach ($this->b[$c][XDBQ_CHILDREN] as $v)
				$this->propagateDown($v, $this->b[$c][XDBQ_SCOPE]);
	}
	else if ($this->b[$c][XDBQ_TYPE] == XDBQ_QM)
	{
		$this->b[$c][XDBQ_SCOPE] = $this->b[$c][XDBQ_PARTIAL] = ctlSetIntersect($this->b[$c][XDBQ_PARTIAL], $s);
		foreach ($this->b[$c][XDBQ_CHILDREN] as $v)
				$this->propagateDown($v, $this->b[$c][XDBQ_SCOPE]);
	}
	else
		$this->b[$c][XDBQ_SCOPE] = $s;
}

//*///------------------------------------------------------------------------

/**
 * Executes unknown branch
 *
 * @method private xQuery  
 * @param int $i branch id  
 */  
function xQuery($i)
{
	// count result
	/*if ($this->b[$i][XDBQ_TYPE] == XDBQ_QF)
			$this->_fQuery($i);
	else*/
	if ($this->b[$i][XDBQ_TYPE] == XDBQ_QI) // indexed query
			$this->iQuery($i);
	else if ($this->b[$i][XDBQ_TYPE] == XDBQ_QS) // system query
			$this->sQuery($i);
	else
	{
		if (!sizeof($this->b[$i][XDBQ_CHILDREN]))
		{
			$this->fillResult($this->b[$i][XDBQ_PARTIAL]);
			unset($this->b[$i]);
		}
		else
		{
			sort($this->b[$i][XDBQ_CHILDREN]);
			foreach ($this->b[$i][XDBQ_CHILDREN] as $c)
					$this->xQuery($c);
		}
	}
}

//*///------------------------------------------------------------------------

/**
 * Executes system query branch
 *
 * @method private sQuery  
 * @param int $i branch id  
 * @todo implement actions != nm
 */  
function sQuery($i)
{
	/*
	$branch[XDBQ_CHILDREN] array consists of:
		0: system field name
		1: action (one of =/!=/m/nm/>=/>/<\/</
		2: action argument
	
	this means query portion "name='myname'" translates into:
		array('name','=','myname')
	*/
	
	// link corresponding key to $k:
	$kn = 'i'.$this->b[$i][XDBQ_CHILDREN][0];
	$k = &$this->$kn;
	$this->b[$i][XDBQ_PARTIAL] = array();
	
	// fill partial according to action:
	switch ($this->b[$i][XDBQ_CHILDREN][1])
	{
		case '=':
			$this->b[$i][XDBQ_PARTIAL] = @$k[$this->b[$i][XDBQ_CHILDREN][2]];
		break;
		case 'm':
			$keys = array_values(preg_grep('/'.addCslashes($this->b[$i][XDBQ_CHILDREN][2], '/').'/', array_keys($k)));
			foreach ($keys as $key)
					$this->b[$i][XDBQ_PARTIAL] = array_merge($this->b[$i][XDBQ_PARTIAL], @$k[$key]);
		break;
		case '>=':
			$this->b[$i][XDBQ_PARTIAL] = @$k[$this->b[$i][XDBQ_CHILDREN][2]];
		case '>':
			$keys = array_keys($k);
			foreach ($keys as $key)
					if ($key > $this->b[$i][XDBQ_CHILDREN][2])
							$this->b[$i][XDBQ_PARTIAL] = array_merge($this->b[$i][XDBQ_PARTIAL], @$k[$key]);
		break;
		case '<=':
			$this->b[$i][XDBQ_PARTIAL] = @$k[$this->b[$i][XDBQ_CHILDREN][2]];
		case '<':
			$keys = array_keys($k);
			foreach ($keys as $key)
					if ($key < $this->b[$i][XDBQ_CHILDREN][2])
							$this->b[$i][XDBQ_PARTIAL] = array_merge($this->b[$i][XDBQ_PARTIAL], @$k[$key]);
		break;
		case '!=':
		case 'nm':
		/*case 'e': // system fields always exist
		case 'ne':*/
		default:
			$this->b[$i][XDBQ_PARTIAL] = array();
	}
	
	// send partial result to parent or return result
	if ($this->b[$i][XDBQ_PARENT] !== NULL)
			$this->propagateUp($this->b[$i][XDBQ_PARENT], $this->b[$i][XDBQ_PARTIAL], $i);
	else
	{
		$this->fillResult($this->b[$i][XDBQ_PARTIAL]);
		unset($this->b[$i]);
	}
}
//*///------------------------------------------------------------------------

/**
 * Executes indexed query branch
 *
 * @method private iQuery  
 * @param int $i branch id  
 * @todo check search operations nm,e,ne,>,>=,<,<=
 */  
function iQuery($i)
{
	if (!$this->loadXIndex())
			$this->b[$i][XDBQ_PARTIAL] = array();
	else
	{	
		// fill partial according to action:
		switch ($this->b[$i][XDBQ_CHILDREN][1])
		{
			case '=':
				$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByValue($this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2]);
			break;
			case '!=':
				$r1 = $this->xi->getByValue($this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2]);
				$this->b[$i][XDBQ_PARTIAL] = array_values(array_diff($this->b[$i][XDBQ_PARTIAL],$r1));
			break;
			case 'm':
				if (is_array($this->b[$i][XDBQ_CHILDREN][2]))
						$this->b[$i][XDBQ_CHILDREN][2] = '/'.$this->b[$i][XDBQ_CHILDREN][2][0].'/'.$this->b[$i][XDBQ_CHILDREN][2][1];
				else
						$this->b[$i][XDBQ_CHILDREN][2] = '/'.$this->b[$i][XDBQ_CHILDREN][2].'/';
				$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByPreg($this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2]);
			break;
			case 'nm':
				if (is_array($this->b[$i][XDBQ_CHILDREN][2]))
					$this->b[$i][XDBQ_CHILDREN][2] = '/'.$this->b[$i][XDBQ_CHILDREN][2][0].'/'.$this->b[$i][XDBQ_CHILDREN][2][1];
				else
					$this->b[$i][XDBQ_CHILDREN][2] = '/'.$this->b[$i][XDBQ_CHILDREN][2].'/';
				$r1 = $this->xi->getByPreg($this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2]);
				$this->b[$i][XDBQ_PARTIAL] = array_values(array_diff($this->b[$i][XDBQ_PARTIAL],$r1));
			break;
			case 'e':
				$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByIndex($this->b[$i][XDBQ_CHILDREN][0]);
			break;
			case 'ne':
				$r1 = $this->xi->getByIndex($this->b[$i][XDBQ_CHILDREN][0]);
				$this->b[$i][XDBQ_PARTIAL] = array_values(array_diff($this->b[$i][XDBQ_PARTIAL],$r1));
			break;
			case '>=':
				$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByRange($this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2]);
			break;
			case '>':
				$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByRange($this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2], 1);
			break;
			case '<=':
				$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByRange(NULL,NULL,$this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2]);
			break;
			case '<':
				$this->b[$i][XDBQ_PARTIAL] = $this->xi->getByRange(NULL,NULL,$this->b[$i][XDBQ_CHILDREN][0],$this->b[$i][XDBQ_CHILDREN][2],1);
			break;
			default:
				$this->b[$i][XDBQ_PARTIAL] = array();
		}
	}
	
	if ($this->b[$i][XDBQ_PARENT] !== NULL)
			$this->propagateUp($this->b[$i][XDBQ_PARENT], $this->b[$i][XDBQ_PARTIAL], $i);
	else
	{
		$this->fillResult($this->b[$i][XDBQ_PARTIAL]);
		unset($this->b[$i]);
	}
}
//*///------------------------------------------------------------------------

/**
 * Sorts branch array according to branch weights
 *
 * @method private sortB
 * @return array sorted branch key array  
 */  
function sortB()
{
	$bk = array_keys($this->b);
	$wk = array();
	$nb = array();
	
	foreach ($bk as $i)
			$wk[] = $this->b[$i][XDBQ_WEIGHT];
	array_multisort($wk, $bk);
	
	return $bk;
}
//*///------------------------------------------------------------------------

/**
 * Removes NULL branches from branch array
 *
 * @method private removeNull   
 */  
function removeNull()
{
	$bs = sizeof($this->b);
	for ($i=0;$i<$bs;$i++)
			if ($this->b[$i][XDBQ_TYPE] == XDBQ_Q0)
			{
				if ($this->b[$i][XDBQ_PARENT] !== NULL)
				{
					@$pk = @array_search($i, $this->b[$this->b[$i][XDBQ_PARENT]][XDBQ_CHILDREN]);
					@$this->b[$this->b[$i][XDBQ_PARENT]][XDBQ_CHILDREN][$pk] = $this->b[$i][XDBQ_CHILDREN][0];
				}
				@$this->b[$this->b[$i][XDBQ_CHILDREN][0]][XDBQ_PARENT] = $this->b[$i][XDBQ_PARENT];
				unset($this->b[$i]);
			}
}
//*///------------------------------------------------------------------------

/**
 * Inserts branch into branch array
 *
 * @method private addBNode
 * @return int new branch id 
 * @param int $t branch type 
 * @param int $p parent branch id 
 * @param optional array $c children array  
 */  
function addBNode($t, $p, $c = array())
{
	$id = sizeof($this->b);
	
	/* 
	count branch weight based on it's type:
	- system queries are fastest
	- indexed queries are slower
	- and opening every document (XDBQ_QF) would be very slow
	- all other branches are logic ones, so they do not count
	*/
	if ($t == XDBQ_QS)
			$w = 0.1;
	else if ($t == XDBQ_QI)
			$w = 0.25;
	else if ($t == XDBQ_QF)
			$w = 5;
	else
			$w = 0;
	
	// let's fill in the branch with values
	$this->b[$id] = array(
		XDBQ_ID => $id, // branch id - key in branch array $this->b
		XDBQ_TYPE => $t, // branch type
		XDBQ_WEIGHT => $w, // branch weight
		XDBQ_PARENT => $p, // parent branch id
		XDBQ_STATUS => XDBQ_SLEEP, // we're not working on this branch
		XDBQ_PARTIAL => array(), // partial result array
		XDBQ_SCOPE => &$this->liid, // default scope -- all document ids
		XDBQ_CHILDREN => $c); // children array. note: this has different meaning 
		                      // for query branches and logic branches
	
	// add this branch to it's parent and recount parent weight
	if ($p !== NULL)
	{
		$this->b[$p][XDBQ_CHILDREN][] = $id;
		
		if ($w)
		{
			if (!$this->b[$p][XDBQ_WEIGHT])
					$this->b[$p][XDBQ_WEIGHT] = $w;
			else
			{
				if ($this->b[$p][XDBQ_TYPE] == XDBQ_QM)
						$this->b[$p][XDBQ_WEIGHT] = $this->b[$p][XDBQ_WEIGHT] * $w;
				else if ($this->b[$p][XDBQ_TYPE] == XDBQ_QP)
						$this->b[$p][XDBQ_WEIGHT] = $this->b[$p][XDBQ_WEIGHT] + $w;
				else if ($w > $this->b[$p][XDBQ_WEIGHT])
						$this->b[$p][XDBQ_WEIGHT] = $w;
			}
		}
	}
	
	return $id;
}
//*///------------------------------------------------------------------------

/**
 * Adds system query branch
 *
 * @method private addBQS
 * @return int new branch id 
 * @param ref string $q query being parsed 
 * @param int $p parent branch id  
 */  
function addBQS(&$q, $p)
{
	// find out system field name:
	switch ($this->bgn($q, 4))
	{
		case 'ctim':
			$c[] = 'ctime';
			$len = 5;
		break;
		case 'lang':
			$c[] = 'language';
			$len = 8;
		break;
		case 'stat':
			$c[] = 'status';
			$len = 6;
		break;
		case 'cont':
			$c[] = 'context';
			$len = 7;
		break;
		default:
			$c[] = $this->bgn($q, 4); // this includes 'name', 'type', 'path' and so on
			$len = 4;
	}
	// cut off the field name from the query string:
	$q = ltrim(substr($q, $len));

	// find out operation:
	$c[] = $this->matchOP($q);
	
	// find out value
	$c[] = $this->matchVAL($q);
	
	// add a system query branch with filled children array:
	return $this->addBNode(XDBQ_QS, $p, $c);
}
//*///------------------------------------------------------------------------

/**
 * Add indexed query branch
 *
 * @method private addBQI
 * @return int new branch id 
 * @param ref string $q query being parsed 
 * @param int $p parent branch id  
 */  
function addBQI(&$q, $p)
{
	$q = substr($q, 6); // cut off 'index('
	$sp = strpos($q, ')'); // find next ')'
	$c[] = substr($q, 0, $sp); // cut out field name between index( and )
	$q = substr($q, $sp+1); // cut off remaining parenthesis
	
	// find out operation:
	$c[] = $this->matchOP($q);
	
	// find out value:
	$c[] = $this->matchVAL($q);
	
	// add an indexed query branch with filled children array
	return $this->addBNode(XDBQ_QI, $p, $c);
}
//*///------------------------------------------------------------------------

/**
 * Adds full query branch
 *
 * @method private addBQF
 * @return int new node id 
 * @param ref string query being parsed 
 * @param int $p parent node id  
 */  
function addBQF(&$q, $p)
{
	$q = substr($q, 5); // cut off 'full('
	$sp = strpos($q, ')'); // find next ')'
	$c[] = substr($q, 0, $sp); // cut out field name between full( and )
	$q = substr($q, $sp+1); // cut off remaining parenthesis
	
	// find out operation:
	$c[] = $this->matchOP($q);
	
	// find out value:
	$c[] = $this->matchVAL($q);
	
	// ad a full query branch with filled children array
	return $this->addBNode(XDBQ_QF, $p, $c);
}
//*///------------------------------------------------------------------------

/**
 * return up to $l symbols from the begining of the string lowercased
 *
 * @method private bgn
 * @return string portion of the string 
 * @param string $s input string 
 * @param int $l portion length  
 */  
function bgn($s, $l)
{
	return strtolower(substr($s, 0, $l));
}
//*///------------------------------------------------------------------------

/**
 * Takes operation from the query and returns it in internal format
 *
 * @method private matchOP
 * @return string internal operation representation 
 * @param ref string $q query string  
 */  
function matchOP(&$q)
{
	$q = ltrim($q);
	$r = '';
	if ($q{0} == '=') {$r = '='; $q=substr($q, 1);} // s+i-x-
	else if ($this->bgn($q, 2) == '!=' || $this->bgn($q, 2) == '<>') {$r = '!='; $q=substr($q, 2);} // s-i-x-
	else if ($this->bgn($q, 2) == '>=') {$r = '>='; $q=substr($q, 2);} // s+i-x-
	else if ($q{0} == '>') {$r = '>'; $q=substr($q, 1);} // s+i-x-
	else if ($this->bgn($q, 2) == '<=') {$r = '<='; $q=substr($q, 2);} // s+i-x-
	else if ($q{0} == '<') {$r = '<'; $q=substr($q, 1);} // s+i-x-
	else if ($this->bgn($q, 7) == 'matches') {$r = 'm'; $q=substr($q, 7);} // s+i-x-
	else if ($this->bgn($q, 8) == '!matches') {$r = 'nm'; $q=substr($q, 8);} // s-i-x-
	else if ($this->bgn($q, 6) == 'exists') {$r = 'e'; $q=substr($q, 6);} // s-i-x-
	else if ($this->bgn($q, 7) == '!exists') {$r = 'ne'; $q=substr($q, 7);} // s-i-x-
	else return $this->error('unidentified string in matchOP: '.$q.'.');
	$q = ltrim($q);

	return $r;
}
//*///------------------------------------------------------------------------

/**
 * Takes value from the query string
 *
 * @method private matchVAL
 * @return mixed string value or regexp: array('pattern', 'modifiers') 
 * @param ref string $q query string  
 */  
function matchVAL(&$q)
{
	$r = '';
	
	// if numeric value:
	if (is_numeric($q{0}))
	{
		while (is_numeric($q{0}))
		{
			$r .= $q{0};
			$q = substr($q, 1);
		}
	}
	// if string or regexp value:
	else if ($q{0} == '\'')
	{
		$ok = TRUE;
		while ($ok)
		{
			$q = substr($q, 1);
			if ($q{0} == '\'' && substr($r, -1) != '\\')
					if (@strstr('imsxeADSUXn', $q{1}))
					{
						$q = substr($q, 1);
						$sp1 = strpos($q, ' ');
						$sp2 = strpos($q, ')');
						if ($sp1 === FALSE)
						{
							if ($sp2 === FALSE)
							{
								$pm = $q;
								$q = '';
							}
							else
							{
								$pm = substr($q, 0, $sp2);
								$q = substr($q, $sp2);
							}
						}
						else if ($sp2 === FALSE)
						{
							$pm = substr($q, 0, $sp1);
							$q = substr($q, $sp1);
						}
						else
						{
							if ($sp2 < $sp1) 
									$sp1 = $sp2;
							$pm = substr($q, 0, $sp1);
							$q = substr($q, $sp1);
						}
						$ok = FALSE;
					}
					else
					{
						$q = substr($q, 1);
						$ok = FALSE;
					}
			else
					$r .= $q{0};
		}
	}
	else
			return $this->error('bad query in matchVAL: '.$q.'.'); //return_result('ERROR');
	
	if (isset($pm))
			return array($r, $pm);
	else
			return $r;
}
//*///------------------------------------------------------------------------

/**
 * Main query parsing loop
 *
 * @method private parseQuery
 * @return boolean FALSE on error, NULL otherwise 
 * @param string $q query string  
 */  
function parseQuery($q)
{
	$i = 0;
	// clear branch array
	$this->b = array();
	
	// first of all we ad a NULL branch:
	$this->addBNode(XDBQ_Q0, NULL);
	
	/*
	this loop imlpements query logic: where in the query string can you expect a
	logic operator, where parenthesis, where subquery and so on.
	
	portions of the query string are cut off from it's begining when branches
	are added to the query branch tree.
	
	$i is a branch counter, which increases with every new branch.
	
	NULL branches (XDBQ_Q0) are added in places where you can expect a nested 
	parenthesis.
	
	*/
	while (strlen($q)) // work until we fully parse query string
	{
		$q = ltrim($q);
		switch ($this->b[$i][XDBQ_TYPE])
		{
			case XDBQ_Q0:
			case XDBQ_QN:
				$i = $this->addBNode(XDBQ_Q0, $i);
			case XDBQ_QP:
				$i = $this->addBNode(XDBQ_Q0, $i);
			case XDBQ_QM:
				if ($q{0} == '(')
				{
					$i = $this->addBNode(XDBQ_Q0,$i);
					$q = substr($q, 1);
				}
				else if ($this->bgn($q, 4) == 'not(')
						$i = $this->addBNode(XDBQ_QN,$i);
				else if ($this->bgn($q, 4) == 'name' || $this->bgn($q, 4) == 'path' || $this->bgn($q, 4) == 'type' || $this->bgn($q, 5) == 'ctime' || $this->bgn($q, 8) == 'language' || $this->bgn($q, 7) == 'context' || $this->bgn($q, 6) == 'status')
						$i = $this->addBQS($q,$i);
				else if ($this->bgn($q, 6) == 'index(')
						$i = $this->addBQI($q,$i);
				else if ($this->bgn($q, 5) == 'full(')
						$i = $this->addBQF($q,$i);
				else
						return $this->error('unknown branch in parseQuery: '.$q.'.');
			break;
			case XDBQ_QS:
			case XDBQ_QI:
			case XDBQ_QF:
				$i = $this->b[$i][XDBQ_PARENT];
				if ($q{0} == ')')
				{
					$i = $this->b[$i][XDBQ_PARENT];
					$i = $this->b[$i][XDBQ_PARENT];
					$q = substr($q, 1);
				}
				else if ($this->bgn($q, 2) == 'or')
				{
					$i = $this->b[$i][XDBQ_PARENT];
					$this->b[$i][XDBQ_TYPE] = XDBQ_QP;
					$q = substr($q, 2);
				}
				else if ($this->bgn($q, 3) == 'and')
				{
					$this->b[$i][XDBQ_TYPE] = XDBQ_QM;
					$q = substr($q, 3);
				}
				else
						return $this->error('unknown branch in parseQuery: '.$q.'.');
			break;
			default:
				return $this->error('unknown active branch type in parseQuery: '.$this->_b[$i][XDBQ_TYPE].'.');
		}
	}
}
//*///------------------------------------------------------------------------

	/**
	 * Fills result array
	 *
	 * @method private fillResult  
	 * @param array $r result array  
	 */  
	function fillResult($r)
	{
		if (is_array($r))
				$this->qr = $r;
	}

//*///------------------------------------------------------------------------
//----------------- PUBLIC METHODS -----------------------------------------
	
	/**
	 * Fills system indexes with data, creates reference to all ids array
	 *
	 * @method public load
	 * @return boolean always TRUE
	 * @param ref array fat array from {@link tv2Xdb::$fat}  
	 */  
	function load(&$fat)
	{
		$this->fat = $fat;
		
		foreach ($this->fat as $id=>$ttfat)
		{
			$this->iname[$ttfat['name']][] = $id;
			$this->ipath[$ttfat['path']][] = $id;
			$this->itype[$ttfat['type']][] = $id;
			$this->ictime[@$ttfat['ctime']][] = $id;
		}
		
		$this->iid = array_keys($this->fat);
		$this->liid = &$this->iid;
		
		if ($this->ver)
		{
			$oids = array_keys($this->ver_index);
			$viid = array_values(array_diff($this->iid, $oids));
			$this->icontext[$this->ver_context] = $viid;
			$this->ilanguage[$this->ver['language']] = $viid;
			$this->istatus[$this->ver['status']] = $viid;
			$this->ivtime[0] = $viid;
			
			foreach ($this->ver_index as $id => $ttvi)
			{
				foreach ($ttvi as $tvi)
				{
					$this->ilanguage[$tvi['language']][] = $id;
					$this->icontext[$tvi['context']][] = $id;
					$this->istatus[$tvi['status']][] = $id;
					$this->ivtime[$tvi['vtime']][] = $id;
				}
			} // end of foreach
		} // end of versioning
		
		return TRUE;
	}
	
	/**
	 * Queries Xdb using SQL-like synthax
	 *
	 * @method public query
	 * @param string $q SQL like query 
	 * @param optional array $o order by: array('field', 'ASC'/'DESC') 
	 * @param optional int $s start from (default 0) 
	 * @param optional int $l limit result number to 
	 * @return array document ID array
	 */
	function query($q, $o = NULL, $s = 0, $l = NULL)
	{
		// clean up nodes (b for branches)
		$this->b = array();
		
		// clean up result
		$this->qr = array();
		
		// parse query
		$this->parseQuery($q);
		
		// remove NULL branches
		$this->removeNull();
		
		// generate results for all system (s) branches:
		$bk = array_keys($this->b);
		foreach($bk as $i)
			if (@$this->b[$i][XDBQ_TYPE] == XDBQ_QS)
					$this->sQuery($i);
		
		// if we have not result yet, sort other branches by their weights and generate results
		if (!sizeof($this->qr))
		{
			$bk = $this->sortB();
			foreach($bk as $i)
				if (isset($this->b[$i]))
						$this->xQuery($i);
		}
		
		// if result empty, return empty array
		if (!is_array($this->qr) || !sizeof($this->qr))
				return array();
		
		// ORDER results
		if (is_array($o) && sizeof($o))
		{
			if (@$o[1] == 'DESC')
					$order = SORT_DESC;
			else
					$order = SORT_ASC;
			
			if ($o[0] == 'name' || $o[0] == 'path' || $o[0] == 'type' || $o[0] == 'ctime')
					foreach ($this->qr as $id)
							$sa[] = $this->fat[$id][$o[0]];
			else if ($o[0] == 'ident')
					foreach ($this->qr as $id)
							$sa[] = $this->fat[$id]['path'].$this->fat[$id]['name'];
			else
					return $this->error('Ordering results by non-system fields not implemented yet.');
			
			array_multisort($sa, $order, $this->qr);
		}
		
		// LIMIT results
		if ($s)
				$this->qr = array_slice($this->qr, $s);
		if ($l !== NULL)
					$this->qr = array_slice($this->qr, 0, $l);
		
		// return result
		return $this->qr;

	}
	
	/**
	 * Loads {@link ctlIndexer} object into parameter $xi
	 *
	 * @method private loadXIndex
	 * @return boolean TRUE on success, FALSE on failure
	 * @use $config
	 */
	function loadXIndex()
	{
		if (isset($this->xi))
				return TRUE;
		
		global $config;
		
		include_once(ENGINE_DIR.'ctlIndexer.class.php');
		$this->xi = &new ctlIndexer();
		return $this->xi->load($config['xindex']);
	}
	
}

?>
Return current item: Tv.2 CMS