<?php
/**
* @package domit-xmlparser
* @subpackage domit-xmlparser-main
* @copyright (C) 2004 John Heinstein. All rights reserved
* @license http://www.gnu.org/copyleft/lesser.html LGPL License
* @author John Heinstein <johnkarl@nbnet.nb.ca>
* @link http://www.engageinteractive.com/domit/ DOMIT! Home Page
* DOMIT! is Free Software
**/

if (!defined('DOMIT_INCLUDE_PATH')) {
	define('DOMIT_INCLUDE_PATH', (dirname(__FILE__) . "/"));
}

/** Separator for absolute path */
define('DOMIT_XPATH_SEPARATOR_ABSOLUTE', '/');
/** Separator for relative path */
define('DOMIT_XPATH_SEPARATOR_RELATIVE', '//');
/** OR separator for multiple patterns  */
define('DOMIT_XPATH_SEPARATOR_OR', '|');

/** Constant for an absolute path search (starting at the document root) */
define('DOMIT_XPATH_SEARCH_ABSOLUTE', 0);
/** Constant for a relative path search (starting at the level of the calling node) */
define('DOMIT_XPATH_SEARCH_RELATIVE', 1);
/** Constant for a variable path search (finds all matches, regardless of place in the hierarchy) */
define('DOMIT_XPATH_SEARCH_VARIABLE', 2);


/**
* DOMIT! XPath is an XPath parser.
*/
class DOMIT_XPath {
    /** @var Object The node from which the search is called */
	var $callingNode;
	/** @var Object The node that is the current parent of the search */
	var $searchType;
	/** @var array An array containing a series of path segments for which to search */
	var $arPathSegments = array();
	/** @var Object A DOMIT_NodeList of matching nodes */
	var $nodeList;
	/** @var string A temporary string container */
	var $charContainer;
	/** @var string The current character of the current pattern segment being parsed */
	var $currChar;
	/** @var string The current pattern segment being parsed */
	var $currentSegment;
	/** @var array A temporary node container for caching node references at the pattern level*/
	var $globalNodeContainer;
	/** @var array A temporary node container for caching node references at the pattern segment level */
	var $localNodeContainer;
	/** @var array Normalization table for XPath syntax */
	var $normalizationTable = array('child::' => '', 'self::' => '.',
							'attribute::' => '@', 'descendant::' => '*//',
							"\t" => ' ', "\x0B" => ' ');
	/** @var array A second-pass normalization table for XPath syntax */
	var $normalizationTable2 = array(' =' => '=', '= ' => '=', ' <' => '<',
							' >' => '>', '< ' => '<', '> ' => '>',
							' !' => '!', '( ' => '(',
							' )' => ')', ' ]' => ']', '] ' => ']',
							' [' => '[', '[ ' => '[', ' /' => '/',
							'/ ' => '/', '"' => "'");
	/** @var array A third-pass normalization table for XPath syntax */
	var $normalizationTable3 = array('position()=' => '',
							'/descendant-or-self::node()/' => "//", 'self::node()' => '.',
							'parent::node()' => '..');

	/**
	* Constructor - creates an empty DOMIT_NodeList to store matching nodes
	*/
	function DOMIT_XPath() {
		require_once(DOMIT_INCLUDE_PATH . 'xml_domit_nodemaps.php');
		$this->nodeList = new DOMIT_NodeList();
	} //DOMIT_XPath

	/**
	* Parses the supplied "path"-based pattern
	* @param Object The node from which the search is called
	* @param string The pattern
	* @return Object The NodeList containing matching nodes
	*/
	function &parsePattern(&$node, $pattern, $nodeIndex = 0) {
		$this->callingNode =& $node;
		$pattern = $this->normalize(trim($pattern));

		$this->splitPattern($pattern);

		$total = count($this->arPathSegments);

		//whole pattern level
		for ($i = 0; $i < $total; $i++) {
			$outerArray =& $this->arPathSegments[$i];
			$this->initSearch($outerArray);

			$outerTotal = count($outerArray);
			$isInitialMatchAttempt = true;

			//variable path segment level
			for ($j = 0; $j < $outerTotal; $j++) {
				$innerArray =& $outerArray[$j];
				$innerTotal = count($innerArray);

				if (!$isInitialMatchAttempt) {
					$this->searchType = DOMIT_XPATH_SEARCH_VARIABLE;
				}

				//pattern segment level
				for ($k = 0; $k < $innerTotal; $k++) {
					$currentPattern = $innerArray[$k];

					if (($k == 0) && ($currentPattern == null)) {
						if ($innerTotal == 1) {
							$isInitialMatchAttempt = false;
						}
						//else just skip current step and don't alter searchType
					}
					else {
						if (!$isInitialMatchAttempt && ($k > 0)) {
							$this->searchType = DOMIT_XPATH_SEARCH_RELATIVE;
						}

						$this->currentSegment = $currentPattern;
						$this->processPatternSegment();
						$isInitialMatchAttempt = false;
					}
				}
			}
		}

		if ($nodeIndex > 0) {
			if ($nodeIndex <= count($this->globalNodeContainer)) {
				return $this->globalNodeContainer[($nodeIndex - 1)];
			}
			else {
				$null = null;
				return $null;
			}
		}

		if (count($this->globalNodeContainer) != 0) {
			foreach ($this->globalNodeContainer as $key =>$value) {
				$currNode =& $this->globalNodeContainer[$key];
				$this->nodeList->appendNode($currNode);
			}
		}

		return $this->nodeList;
	} //parsePattern

	/**
	* Generates a new globalNodeContainer of matches
	*/
	function processPatternSegment() {
		$total = strlen($this->currentSegment);
		$this->charContainer = '';
		$this->localNodeContainer = array();

		for ($i = 0; $i < $total; $i++) {
//			$this->currChar = $this->currentSegment{$i};
			$this->currChar = substr($this->currentSegment, $i, 1);

			switch ($this->currChar) {
				case '@':
					$this->selectAttribute(substr($this->currentSegment, ($this->currChar + 1)));
					$this->updateNodeContainers();
					return;
					//break;

				case '*':
					if ($i == ($total - 1)) {
						$this->selectNamedChild('*');
					}
					else {
						$this->charContainer .= $this->currChar;
					}
					break;

				case '.':
					$this->charContainer .= $this->currChar;

					if ($i == ($total - 1)) {
						if ($this->charContainer == '..') {
							$this->selectParent();
						}
						else {
							return;
						}
					}
					break;

				case ')':
					$this->charContainer .= $this->currChar;
					$this->selectNodesByFunction();
					break;

				case '[':
					$this->parsePredicate($this->charContainer,
									substr($this->currentSegment, ($i + 1)));
					return;
					//break;

				default:
					$this->charContainer .= $this->currChar;

			}
		}

		if ($this->charContainer != '') {
			$this->selectNamedChild($this->charContainer);
		}

		$this->updateNodeContainers();
	} //processPatternSegment

	/**
	* Replaces the global node container with the local node container
	*/
	function updateNodeContainers() {
		$this->globalNodeContainer =& $this->localNodeContainer;
		unset($this->localNodeContainer);
	} //updateNodeContainers


	/**
	* Parses a predicate expression [...]
	* @param string The pattern segment containing the node expression
	* @param string The pattern segment containing the predicate expression
	*/
	function parsePredicate($nodeName, $patternSegment) {
		$arPredicates =& explode('][', $patternSegment);

		$total = count($arPredicates);

		$lastIndex = $total - 1;
		$arPredicates[$lastIndex] = substr($arPredicates[$lastIndex],
										0, (strlen($arPredicates[$lastIndex]) - 1));

		for ($i = 0; $i < $total; $i++) {
			$isRecursive = ($this->searchType == DOMIT_XPATH_SEARCH_VARIABLE) ? true : false;
			$currPredicate = $arPredicates[$i];

			if (is_numeric($currPredicate)) {
				if ($i == 0) {
					$this->filterByIndex($nodeName, intval($currPredicate), $isRecursive);
				}
				else {
					$this->refilterByIndex(intval($currPredicate));
				}
			}
			else {
				if ($i == 0) {
					$this->selectNamedChild($nodeName);
					$this->updateNodeContainers();
				}

				$phpExpression = $this->predicateToPHP($currPredicate);
				$this->filterByPHPExpression($phpExpression);
			}

			$this->updateNodeContainers();
		}

		$this->charContainer = '';
	} //parsePredicate

	/**
	* Converts the predicate into PHP evaluable code
	* @param string The predicate
	* @return string The converted PHP expression
	*/
	function predicateToPHP($predicate) {
		$phpExpression = $predicate;
		$currChar = '';
		$charContainer = '';
		$totalChars = strlen($predicate);

		for ($i = 0; $i < $totalChars; $i++) {
			$currChar = substr($predicate, $i, 1);

			switch ($currChar) {
				case '(':
				case ')':
				case ' ':
					if ($charContainer != '') {
						$convertedPredicate = $this->expressionToPHP($charContainer);
						$phpExpression = str_replace($charContainer, $convertedPredicate, $phpExpression);
						$charContainer = '';
					}
					break;

				default:
					$charContainer .= $currChar;

			}
		}

		if ($charContainer != '') {
			$convertedPredicate = $this->expressionToPHP($charContainer);
			$phpExpression = str_replace($charContainer, $convertedPredicate, $phpExpression);
		}

		return $phpExpression;
	} //predicateToPHP


	/**
	* Converts the predicate expression into a PHP expression
	* @param string The predicate expression
	* @return string The converted PHP expression
	*/
	function expressionToPHP($expression) {
		if ($expression == 'and') {
			$expression = '&&';
		}
		else if ($expression == 'or') {
			$expression = '||';
		}
		else if ($expression == 'not') {
			$expression = '!';
		}
		else {
			$expression = trim($expression);

			if (strpos($expression, '@') !== false) {
				if (strpos($expression, '>=') !== false) {
					$expression = str_replace('@', ('floatval($' . "contextNode->getAttribute('"), $expression);
					$expression = str_replace('>=', "')) >= floatval(", $expression);
					if (!is_numeric($expression)) $expression = str_replace('floatval', '', $expression);
					$expression .= ')';
				}
				else if (strpos($expression, '<=') !== false) {
					$expression = str_replace('@', ('floatval($' . "contextNode->getAttribute('"), $expression);
					$expression = str_replace('<=', "')) <= floatval(", $expression);
					if (!is_numeric($expression)) $expression = str_replace('floatval', '', $expression);
					$expression .= ')';
				}
				else if (strpos($expression, '!=') !== false) {
					$expression = str_replace('@', ('$' . "contextNode->getAttribute('"), $expression);
					$expression = str_replace('!=', "') != ", $expression);
				}
				else if (strpos($expression, '=') !== false) {
					$expression = str_replace('@', ('$' . "contextNode->getAttribute('"), $expression);
					$expression = str_replace('=', "') == ", $expression);
				}
				else if (strpos($expression, '>') !== false) {
					$expression = str_replace('>', "')) > floatval(", $expression); //reverse so > doesn't get replaced
					$expression = str_replace('@', ('floatval($' . "contextNode->getAttribute('"), $expression);
					if (!is_numeric($expression)) $expression = str_replace('floatval', '', $expression);
					$expression .= ')';
				}
				else if (strpos($expression, '<') !== false) {
					$expression = str_replace('@', ('floatval($' . "contextNode->getAttribute('"), $expression);
					$expression = str_replace('<', "')) < floatval(", $expression);
					if (!is_numeric($expression)) $expression = str_replace('floatval', '', $expression);
					$expression .= ')';
				}
				else {
					$expression = str_replace('@', ('$' . "contextNode->hasAttribute('"), $expression);
					$expression.= "')";
				}
			}
			else {
				if (strpos($expression, '>=') !== false) {
					$signPos = strpos($expression, '>=');
					$elementName = trim(substr($expression, 0, $signPos));
					$elementValue = trim(substr($expression, ($signPos + 2)));

					$expression = '$' . "this->hasNamedChildElementGreaterThanOrEqualToValue(" .
									'$' . "contextNode, '" . $elementName . "', " .
									$elementValue . ')';
				}
				else if (strpos($expression, '<=') !== false) {
					$signPos = strpos($expression, '>=');
					$elementName = trim(substr($expression, 0, $signPos));
					$elementValue = trim(substr($expression, ($signPos + 2)));

					$expression = '$' . "this->hasNamedChildElementLessThanOrEqualToValue(" .
									'$' . "contextNode, '" . $elementName . "', " .
									$elementValue . ')';
				}
				else if (strpos($expression, '!=') !== false) {
					$signPos = strpos($expression, '>=');
					$elementName = trim(substr($expression, 0, $signPos));
					$elementValue = trim(substr($expression, ($signPos + 2)));

					$expression = '$' . "this->hasNamedChildElementNotEqualToValue(" .
									'$' . "contextNode, '" . $elementName . "', " .
									$elementValue . ')';
				}
				else if (strpos($expression, '=') !== false) {
					$signPos = strpos($expression, '=');
					$elementName = trim(substr($expression, 0, $signPos));
					$elementValue = trim(substr($expression, ($signPos + 1)));

					$expression = '$' . "this->hasNamedChildElementEqualToValue(" .
									'$' . "contextNode, '" . $elementName . "', " .
									$elementValue . ')';

				}
				else if (strpos($expression, '>') !== false) {
					$signPos = strpos($expression, '=');
					$elementName = trim(substr($expression, 0, $signPos));
					$elementValue = trim(substr($expression, ($signPos + 1)));

					$expression = '$' . "this->hasNamedChildElementGreaterThanValue(" .
									'$' . "contextNode, '" . $elementName . "', " .
									$elementValue . ')';
				}
				else if (strpos($expression, '<') !== false) {
					$signPos = strpos($expression, '=');
					$elementName = trim(substr($expression, 0, $signPos));
					$elementValue = trim(substr($expression, ($signPos + 1)));

					$expression = '$' . "this->hasNamedChildElementLessThanValue(" .
									'$' . "contextNode, '" . $elementName . "', " .
									$elementValue . ')';
				}
				else {
					$expression = '$' . "this->hasNamedChildElement(" .
									'$' . "contextNode, '" . $expression . "')";
				}
			}
		}

		return $expression;
	} //expressionToPHP

	/**
	* Selects nodes that match the predicate expression
	* @param string The predicate expression, formatted as a PHP expression
	*/
	function filterByPHPExpression($expression) {
		if (count($this->globalNodeContainer) != 0) {
			foreach ($this->globalNodeContainer as $key =>$value) {
				$contextNode =& $this->globalNodeContainer[$key];

				if ($contextNode->nodeType == DOMIT_ELEMENT_NODE) {
					$evaluatedExpression = 'if (' . $expression . ") $" .
									'this->localNodeContainer[] =& $' . 'contextNode;';
					eval($evaluatedExpression);
				}
			}
		}
	} //filterByPHPExpression

	/**
	* Selects nodes with child elements that match the specified name
	* @param object The parent node of the child elements to match
	* @param string The tag name to match on
	* @return boolean True if a matching child element exists
	*/
	function hasNamedChildElement(&$parentNode, $nodeName) {
		$total = $parentNode->childCount;

		for ($i = 0; $i < $total; $i++) {
			$currNode =& $parentNode->childNodes[$i];

			if (($currNode->nodeType == DOMIT_ELEMENT_NODE) && ($currNode->nodeName == $nodeName)) {
				return true;
			}
		}

		return false;
	} //hasNamedChildElement

	/**
	* Selects nodes with child elements that match the specified name and text value
	* @param object The parent node of the child elements to match
	* @param string The tag name to match on
	* @param string The text string to match on
	* @return boolean True if a matching child element exists
	*/
	function hasNamedChildElementEqualToValue(&$parentNode, $nodeName, $nodeValue) {
		$total = $parentNode->childCount;

		for ($i = 0; $i < $total; $i++) {
			$currNode =& $parentNode->childNodes[$i];

			if (($currNode->nodeType == DOMIT_ELEMENT_NODE) &&
				($currNode->nodeName == $nodeName) && ($currNode->getText() == $nodeValue)) {
				return true;
			}
		}

		return false;
	} //hasNamedChildElementEqualToValue

	/**
	* Selects nodes with child elements that are greater than or equal to the specified name and value
	* @param object The parent node of the child elements to match
	* @param string The tag name to match on
	* @param string The text string to match on
	* @return boolean True if a matching child element exists
	*/
	function hasNamedChildElementGreaterThanOrEqualToValue(&$parentNode, $nodeName, $nodeValue) {
		$isNumeric = false;

		if (is_numeric($nodeValue)) {
			$isNumeric = true;
			$nodeValue = floatval($nodeValue);
		}

		$total = $parentNode->childCount;

		for ($i = 0; $i < $total; $i++) {
			$currNode =& $parentNode->childNodes[$i];

			if (($currNode->nodeType == DOMIT_ELEMENT_NODE) && ($currNode->nodeName == $nodeName)) {
				if ($isNumeric) {$compareVal = floatval($currNode->getText());}
				else {$compareVal = $currNode->getText();}
				if ($compareVal >= $nodeValue) return true;
			}
		}

		return false;
	} //hasNamedChildElementGreaterThanOrEqualToValue


	/**
	* Selects nodes with child elements that are less than or equal to the specified name and value
	* @param object The parent node of the child elements to match
	* @param string The tag name to match on
	* @param string The text string to match on
	* @return boolean True if a matching child element exists
	*/
	function hasNamedChildElementLessThanOrEqualToValue(&$parentNode, $nodeName, $nodeValue) {
		$isNumeric = false;

		if (is_numeric($nodeValue)) {
			$isNumeric = true;
			$nodeValue = floatval($nodeValue);
		}

		$total = $parentNode->childCount;

		for ($i = 0; $i < $total; $i++) {
			$currNode =& $parentNode->childNodes[$i];

			if (($currNode->nodeType == DOMIT_ELEMENT_NODE) && ($currNode->nodeName == $nodeName)) {
				if ($isNumeric) {$compareVal = floatval($currNode->getText());}
				else {$compareVal = $currNode->getText();}
				if ($compareVal <= $nodeValue) return true;
			}
		}

		return false;
	} //hasNamedChildElementLessThanOrEqualToValue

	/**
	* Selects nodes with child elements that are not equal to the specified name and value
	* @param object The parent node of the child elements to match
	* @param string The tag name to match on
	* @param string The text string to match on
	* @return boolean True if a matching child element exists
	*/
	function hasNamedChildElementNotEqualToValue(&$parentNode, $nodeName, $nodeValue) {
		$isNumeric = false;

		if (is_numeric($nodeValue)) {
			$isNumeric = true;
			$nodeValue = floatval($nodeValue);
		}

		$total = $parentNode->childCount;

		for ($i = 0; $i < $total; $i++) {
			$currNode =& $parentNode->childNodes[$i];

			if (($currNode->nodeType == DOMIT_ELEMENT_NODE) && ($currNode->nodeName == $nodeName)) {
				if ($isNumeric) {$compareVal = floatval($currNode->getText());}
				else {$compareVal = $currNode->getText();}
				if ($compareVal != $nodeValue) return true;
			}
		}

		return false;
	} //hasNamedChildElementNotEqualToValue

		/**
	* Selects nodes with child elements that are greater than the specified name and value
	* @param object The parent node of the child elements to match
	* @param string The tag name to match on
	* @param string The text string to match on
	* @return boolean True if a matching child element exists
	*/
	function hasNamedChildElementGreaterThanValue(&$parentNode, $nodeName, $nodeValue) {
		$isNumeric = false;

		if (is_numeric($nodeValue)) {
			$isNumeric = true;
			$nodeValue = floatval($nodeValue);
		}

		$total = $parentNode->childCount;

		for ($i = 0; $i < $total; $i++) {
			$currNode =& $parentNode->childNodes[$i];

			if (($currNode->nodeType == DOMIT_ELEMENT_NODE) && ($currNode->nodeName == $nodeName)) {
				if ($isNumeric) {$compareVal = floatval($currNode->getText());}
				else {$compareVal = $currNode->getText();}
				if ($compareVal > $nodeValue) return true;
			}
		}

		return false;
	} //hasNamedChildElementGreaterThanValue


	/**
	* Selects nodes with child elements that are less than the specified name and value
	* @param object The parent node of the child elements to match
	* @param string The tag name to match on
	* @param string The text string to match on
	* @return boolean True if a matching child element exists
	*/
	function hasNamedChildElementLessThanValue(&$parentNode, $nodeName, $nodeValue) {
		$isNumeric = false;

		if (is_numeric($nodeValue)) {
			$isNumeric = true;
			$nodeValue = floatval($nodeValue);
		}

		$total = $parentNode->childCount;

		for ($i = 0; $i < $total; $i++) {
			$currNode =& $parentNode->childNodes[$i];

			if (($currNode->nodeType == DOMIT_ELEMENT_NODE) && ($currNode->nodeName == $nodeName)) {
				if ($isNumeric) {$compareVal = floatval($currNode->getText());}
				else {$compareVal = $currNode->getText();}
				if ($compareVal < $nodeValue) return true;
			}
		}

		return false;
	} //hasNamedChildElementLessThanValue

	/**
	* Selects named elements of the specified index
	* @param string The pattern segment containing the node expression
	* @param int The index (base 1) of the matching node
	* @param boolean True if the selection is to be performed recursively
	*/
	function refilterByIndex($index) {
		if ($index > 1) {
			if (count($this->globalNodeContainer) != 0) {
				$counter = 0;
				$lastParentID = null;

				foreach ($this->globalNodeContainer as $key =>$value) {
					$currNode =& $this->globalNodeContainer[$key];

					if (($lastParentID != null) && ($currNode->parentNode->uid != $lastParentID)) {
						$counter = 0;
					}

					$counter++;

					if (($counter == $index) && ($currNode->parentNode->uid == $lastParentID)) {
						$this->localNodeContainer[] =& $currNode;
					}

					$lastParentID = $currNode->parentNode->uid;
				}
			}
		}
		else {
			$this->localNodeContainer =& $this->globalNodeContainer;
		}
	} //refilterByIndex


	/**
	* Selects named elements of the specified index
	* @param string The pattern segment containing the node expression
	* @param int The index (base 1) of the matching node
	* @param boolean True if the selection is to be performed recursively
	*/
	function filterByIndex($nodeName, $index, $deep) {
		if (count($this->globalNodeContainer) != 0) {
			foreach ($this->globalNodeContainer as $key =>$value) {
				$currNode =& $this->globalNodeContainer[$key];
				$this->_filterByIndex($currNode, $nodeName, $index, $deep);
			}
		}
	} //filterByIndex

	/**
	* Selects named elements of the specified index
	* @param object The context node
	* @param string The pattern segment containing the node expression
	* @param int The index (base 1) of the matching node
	* @param boolean True if the selection is to be performed recursively
	*/
	function _filterByIndex(&$contextNode, $nodeName, $index, $deep) {
		if (($contextNode->nodeType == DOMIT_ELEMENT_NODE) ||
			($contextNode->nodeType == DOMIT_DOCUMENT_NODE)) {
			$total = $contextNode->childCount;
			$nodeCounter = 0;

			for ($i = 0; $i < $total; $i++) {
				$currChildNode =& $contextNode->childNodes[$i];

				if ($currChildNode->nodeName == $nodeName) {
					$nodeCounter++;

					if ($nodeCounter == $index) {
						$this->localNodeContainer[] =& $currChildNode;
					}
				}

				if ($deep) {
					$this->_filterByIndex($currChildNode, $nodeName, $index, $deep);
				}
			}
		}
	} //_filterByIndex

	/**
	* Selects named elements with the specified named child
	* @param string The pattern segment containing the node expression
	* @param string The tag name of the matching child
	* @param boolean True if the selection is to be performed recursively
	*/
	function filterByChildName($nodeName, $childName, $deep) {
		if (count($this->globalNodeContainer) != 0) {
			foreach ($this->globalNodeContainer as $key =>$value) {
				$currNode =& $this->globalNodeContainer[$key];
				$this->_filterByChildName($currNode, $nodeName, $childName, $deep);
			}
		}
	} //filterByChildName

	/**
	* Selects named elements with the specified named child
	* @param object The context node
	* @param string The pattern segment containing the node expression
	* @param string The tag name of the matching child
	* @param boolean True if the selection is to be performed recursively
	*/
	function _filterByChildName(&$contextNode, $nodeName, $childName, $deep) {
		if (($contextNode->nodeType == DOMIT_ELEMENT_NODE) ||
			($contextNode->nodeType == DOMIT_DOCUMENT_NODE)) {
			$total = $contextNode->childCount;

			for ($i = 0; $i < $total; $i++) {
				$currChildNode =& $contextNode->childNodes[$i];

				if (($currChildNode->nodeName == $nodeName) &&
					($currChildNode->nodeType == DOMIT_ELEMENT_NODE)) {
					$total2 = $currChildNode->childCount;

					for ($j = 0; $j < $total2; $j++) {
						$currChildChildNode =& $currChildNode->childNodes[$j];

						if ($currChildChildNode->nodeName == $childName) {
							$this->localNodeContainer[] =& $currChildNode;
						}
					}
				}

				if ($deep) {
					$this->_filterByChildName($currChildNode, $nodeName, $childName, $deep);
				}
			}
		}
	} //_filterByChildName

	/**
	* Selects named attributes of the current context nodes
	* @param string The attribute name, or * to match all attributes
	*/
	function selectAttribute($attrName) {
		if (count($this->globalNodeContainer) != 0) {
			foreach ($this->globalNodeContainer as $key =>$value) {
				$currNode =& $this->globalNodeContainer[$key];

				$isRecursive = ($this->searchType == DOMIT_XPATH_SEARCH_VARIABLE) ? true : false;
				$this->_selectAttribute($currNode, $attrName, $isRecursive);
			}
		}

		$this->charContainer = '';
	} //selectAttribute

	/**
	* Selects all attributes of the context nodes
	* @param object The context node
	* @param string The attribute name, or * to match all attributes
	* @param boolean True if the selection is to be performed recursively
	*/
	function _selectAttribute(&$contextNode, $attrName, $deep) {
		if (($contextNode->nodeType == DOMIT_ELEMENT_NODE) ||
				($contextNode->nodeType == DOMIT_DOCUMENT_NODE)) {
			$total = $contextNode->childCount;

			for ($i = 0; $i < $total; $i++) {
				$currNode =& $contextNode->childNodes[$i];

				if ($currNode->nodeType == DOMIT_ELEMENT_NODE) {
					if ($attrName == '*') {
						$total2 = $currNode->attributes->getLength();

						for ($j = 0; $j < $total2; $j++) {
							$this->localNodeContainer[] =& $currNode->attributes->item($j);
						}
					}
					else {
						if ($currNode->hasAttribute($attrName)) {
							$this->localNodeContainer[] =& $currNode->getAttributeNode($attrName);
						}
					}
				}

				if ($deep) {
					$this->_selectAttribute($currNode, $attrName, $deep);
				}
			}
		}
	} //_selectAttribute


	/**
	* Selects all child nodes of the current context nodes
	* @param string The element name
	*/
	function selectNamedChild($tagName) {
		if (count($this->globalNodeContainer) != 0) {
			foreach ($this->globalNodeContainer as $key =>$value) {
				$currNode =& $this->globalNodeContainer[$key];

				$isRecursive = ($this->searchType == DOMIT_XPATH_SEARCH_VARIABLE) ? true : false;
				$this->_selectNamedChild($currNode, $tagName, $isRecursive);
			}
		}

		$this->charContainer = '';
	} //selectNamedChild

	/**
	* Selects all child nodes of the context node
	* @param object The context node
	* @param string The element name
	* @param boolean True if the selection is to be performed recursively
	*/
	function _selectNamedChild(&$contextNode, $tagName, $deep = false) {
		if (($contextNode->nodeType == DOMIT_ELEMENT_NODE) ||
			($contextNode->nodeType == DOMIT_DOCUMENT_NODE)) {
			$total = $contextNode->childCount;

			for ($i = 0; $i < $total; $i++) {
				$currChildNode =& $contextNode->childNodes[$i];

				if (($currChildNode->nodeType == DOMIT_ELEMENT_NODE) ||
					($currChildNode->nodeType == DOMIT_DOCUMENT_NODE)) {
					if (($tagName == '*') || ($tagName == $currChildNode->nodeName)) {
						$this->localNodeContainer[] =& $currChildNode;
					}

					if ($deep) {
						$this->_selectNamedChild($currChildNode, $tagName, $deep);
					}
				}
			}
		}
	} //_selectNamedChild

	/**
	* Selects parent node of the current context nodes
	*/
	function selectParent() {
		if (count($this->globalNodeContainer) != 0) {
			foreach ($this->globalNodeContainer as $key =>$value) {
				$currNode =& $this->globalNodeContainer[$key];

				$isRecursive = ($this->searchType == DOMIT_XPATH_SEARCH_VARIABLE) ? true : false;
				$this->_selectParent($currNode, $isRecursive);
			}
		}

		$this->charContainer = '';
	} //selectParent

	/**
	* Selects parent node of the current context nodes
	* @param object The context node
	* @param boolean True if the selection is to be performed recursively
	*/
	function _selectParent(&$contextNode, $deep = false) {
		if ($contextNode->nodeType == DOMIT_ELEMENT_NODE) {
			if ($contextNode->parentNode != null) {
				$this->localNodeContainer[] =& $contextNode->parentNode;
			}
		}

		if ($deep) {
			if (($contextNode->nodeType == DOMIT_ELEMENT_NODE) ||
				($contextNode->nodeType == DOMIT_DOCUMENT_NODE)) {
				$total = $contextNode->childCount;

				for ($i = 0; $i < $total; $i++) {
					$currNode =& $contextNode->childNodes[$i];

					if ($currNode->nodeType == DOMIT_ELEMENT_NODE) {
						$this->_selectParent($contextNode, $deep);
					}
				}
			}
		}
	} //_selectParent

	/**
	* Selects any nodes of the current context nodes which match the given function
	*/
	function selectNodesByFunction() {
		$doProcess = false;
		$targetNodeType = -1;

		switch (strtolower(trim($this->charContainer))) {
			case 'last()':
				if (count($this->globalNodeContainer) != 0) {
					foreach ($this->globalNodeContainer as $key =>$value) {
						$currNode =& $this->globalNodeContainer[$key];

						if ($currNode->nodeType == DOMIT_ELEMENT_NODE) {
							if ($currNode->lastChild != null) {
								$this->localNodeContainer[] =& $currNode->lastChild;
							}
						}
					}
				}
				break;

			case 'text()':
				$doProcess = true;
				$targetNodeType = DOMIT_TEXT_NODE;
				break;

			case 'comment()':
				$doProcess = true;
				$targetNodeType = DOMIT_COMMENT_NODE;
				break;

			case 'processing-instruction()':
				$doProcess = true;
				$targetNodeType = DOMIT_PROCESSING_INSTRUCTION_NODE;
				break;
		}

		if ($doProcess) {
			if (count($this->globalNodeContainer) != 0) {
				foreach ($this->globalNodeContainer as $key =>$value) {
					$currNode =& $this->globalNodeContainer[$key];

					if ($currNode->nodeType == DOMIT_ELEMENT_NODE) {
						$total = $currNode->childCount;

						for ($j = 0; $j < $total; $j++) {
							if ($currNode->childNodes[$j]->nodeType == $targetNodeType) {
								$this->localNodeContainer[] =& $currNode->childNodes[$j];
							}
						}
					}
				}
			}
		}

		$this->charContainer = '';
	} //selectNodesByFunction

	/**
	* Splits the supplied pattern into searchable segments
	* @param string The pattern
	*/
	function splitPattern($pattern) {
		//split multiple patterns if they exist (e.g. pattern1 | pattern2 | pattern3)
		$this->arPathSegments =& explode(DOMIT_XPATH_SEPARATOR_OR, $pattern);

		//split each pattern by relative path dividers (i.e., '//')
		$total = count($this->arPathSegments);

		for ($i = 0; $i < $total; $i++) {
			$this->arPathSegments[$i] =& explode(DOMIT_XPATH_SEPARATOR_RELATIVE, trim($this->arPathSegments[$i]));

			$currArray =& $this->arPathSegments[$i];
			$total2 = count($currArray);

			for ($j = 0; $j < $total2; $j++) {
				$currArray[$j] =& explode(DOMIT_XPATH_SEPARATOR_ABSOLUTE, $currArray[$j]);
			}
		}
	} //splitPattern

	/**
	* Converts long XPath syntax into abbreviated XPath syntax
	* @param string The pattern
	* @return string The normalized pattern
	*/
	function normalize($pattern) {
		$pattern = strtr($pattern, $this->normalizationTable);

		while (strpos($pattern, '  ') !== false) {
			$pattern = str_replace('  ', ' ', $pattern);
		}

		$pattern = strtr($pattern, $this->normalizationTable2);
		$pattern = strtr($pattern, $this->normalizationTable3);

		return $pattern;
	} //normalize

	/**
	* Initializes the contextNode and searchType
	* @param array The current array of path segments
	* @return int The index of the first array item to begin the search at
	*/
	function initSearch(&$currArPathSegments) {
		$this->globalNodeContainer = array();

		if (is_null($currArPathSegments[0])) {
			if (count($currArPathSegments) == 1) {
				//variable path
				$this->searchType = DOMIT_XPATH_SEARCH_VARIABLE;
				$this->globalNodeContainer[] =& $this->callingNode->ownerDocument;
			}
			else {
				//absolute path
				$this->searchType = DOMIT_XPATH_SEARCH_ABSOLUTE;
				$this->globalNodeContainer[] =& $this->callingNode->ownerDocument;
			}
		}
		else {
			//relative path
			$this->searchType = DOMIT_XPATH_SEARCH_RELATIVE;

			if ($this->callingNode->uid != $this->callingNode->ownerDocument->uid) {
				$this->globalNodeContainer[] =& $this->callingNode;
			}
			else {
				$this->globalNodeContainer[] =& $this->callingNode->ownerDocument;
			}
		}
	} //initSearch
} //DOMIT_XPath
?>