Location: PHPKode > projects > Open Power Template > lib/Opt/Xml/Scannable.php
<?php
/*
 *  OPEN POWER LIBS <http://www.invenzzia.org>
 *
 * This file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE. It is also available through
 * WWW at this URL: <http://www.invenzzia.org/license/new-bsd>
 *
 * Copyright (c) Invenzzia Group <http://www.invenzzia.org>
 * and other contributors. See website for details.
 *
 */

	/**
	 * The abstract class that represents the XML nodes which can contain
	 * another nodes. It uses the DOM-like API to manage the nodes.
	 *
	 * @abstract
	 */
	class Opt_Xml_Scannable extends Opt_Xml_Node implements Iterator
	{
		protected $_subnodes;
		protected $_i;
		protected $_size;
		protected $_copy;
		
		private $_prototypes;

		/**
		 * Creates the scannable node.
		 */
		public function __construct()
		{
			parent::__construct();
		} // end __construct();
		
		/*
		 * Public DOM-like API
		 */

		/**
		 * Appends a new child to the end of the children list. The method
		 * is DOM-compatible.
		 *
		 * @param Opt_Xml_Node $child The child to be appended.
		 */
		public function appendChild(Opt_Xml_Node $child)
		{
			// Test if the node can be a child of this and initialize an
			// empty array if needed.
			$this->_testNode($child);
			if(!is_array($this->_subnodes))
			{
				$this->_subnodes = array();
			}
			$child->setParent($this);
			$this->_subnodes[] = $child;
		} // end appendChild();

		/**
		 * Inserts the new node after the node identified by the '$refnode'. The
		 * reference node can be identified either by the number or by the object.
		 * If the reference node is empty, the new node is appended to the children
		 * list, if the last argument allows for that.
		 *
		 * @param Opt_Xml_Node $newnode The new node.
		 * @param Integer|Opt_Xml_Node $refnode The reference node.
		 * @param Boolean $appendOnError Do we like to append the node, if $refnode does not exist?
		 */
		public function insertBefore(Opt_Xml_Node $newnode, $refnode = null, $appendOnError = true)
		{
			// Test if the node can be a child of this and initialize an
			// empty array if needed.
			$this->_testNode($newnode);
			if(!is_array($this->_subnodes))
			{
				$this->_subnodes = array();
			}
			$newnode->setParent($this);
			if(is_null($refnode))
			{
				$this->_appendChild($newnode, $appendOnError);
			}
			if(is_object($refnode))
			{
				$i = 0;
				$cnt = sizeof($this->_subnodes);
				while($cnt > 0)
				{
					if(isset($this->_subnodes[$i]))
					{
						$cnt--;
						if($this->_subnodes[$i] === $refnode)
						{
							$this->_subnodes = $this->_arrayCreateHole($this->_subnodes, $i);
						//	Opt_Array_Push($this->_subnodes, $i, $cnt);
							$this->_subnodes[$i] = $newnode;
							break;
						}
					}
					$i++;
				}
			}
			elseif(is_integer($refnode))
			{
				end($this->_subnodes);
				if($refnode <= key($this->_subnodes) && $refnode >= 0)
				{
					$this->_subnodes = $this->_arrayCreateHole($this->_subnodes, $refnode);
				//	Opt_Array_Push($this->_subnodes, $refnode);
					$this->_subnodes[$refnode] = $newnode;
				}
				else
				{
					$this->_appendChild($newnode, $appendOnError);
				}
			}
		} // end insertBefore();

		/**
		 * Removes the child identified either by the number or the object.
		 *
		 * @param Integer|Opt_Xml_Node $node The node to be removed.
		 * @return Boolean
		 */
		public function removeChild($node)
		{
			if(!is_array($this->_subnodes))
			{
				return false;
			}
			if(is_object($node))
			{
				$cnt = sizeof($this->_subnodes);
				$found = 0;
				for($i = 0; $i < $cnt; $i++)
				{
					if(isset($this->_subnodes[$i]))
					{
						if($this->_subnodes[$i] === $node)
						{
							$node->setParent(null);
							unset($this->_subnodes[$i]);
							$found++;
						}
					}
				}
				$this->_subnodes = $this->_arrayReduceHoles($this->_subnodes);
				return $found > 0;
			}
			elseif(is_integer($node) && isset($this->_subnodes[$node]))
			{
				$this->_subnodes[$node]->setParent(null);
			//	$this->_subnodes[$node]->dispose();
				unset($this->_subnodes[$node]);
				$this->_subnodes = $this->_arrayReduceHoles($this->_subnodes);
				return true;
			}
			return false;
		} // end removeChild();

		/**
		 * Removes all the children. The memory after the children is not freed.
		 */
		public function removeChildren()
		{
			if(is_array($this->_subnodes))
			{
				foreach($this->_subnodes as $subnode)
				{
					if(is_object($subnode))
					{
						$subnode->setParent(null);
					}
				}
			}
			unset($this->_subnodes);
			$this->_subnodes = null;
		} // end removeChildren();

		/**
		 * Moves all the children of another node to the current node.
		 *
		 * @param Opt_Xml_Node $node Another node.
		 */
		public function moveChildren(Opt_Xml_Scannable $node)
		{
			// If there are already some nodes, we have to free the memory first.
			if(is_array($this->_subnodes))
			{
				foreach($this->_subnodes as $item)
				{
					if(is_object($item))
					{
						$item->dispose();
					}
				}
			}
			// Move the nodes by copying the internal data structures.
			$this->_subnodes = $node->_subnodes;
			$this->_i = $node->_i;
			$this->_size = $node->_size;
			$this->_copy = $node->_copy;
			// Reset the children list in the $node.
			$node->_subnodes = null;
			$node->_i = null;
			$node->_size = null;
			$node->_copy = null;
			// Create a connection between this node and the new children.
			if(is_array($this->_subnodes))
			{
				foreach($this->_subnodes as $subnode)
				{
					if(is_object($subnode))
					{
						// Test the node in order to check whether
						// we have moved them to the correct node.
						$this->_testNode($subnode);
						$subnode->setParent($this);
					}
				}
			}
		} // end moveChildren();

		/**
		 * Replaces the child with the new node. The reference node can be
		 * identified either by the number or by the object.
		 *
		 * @param Opt_Xml_Node $newnode The new node.
		 * @param Integer|Opt_Xml_Node $refnode The old node.
		 * @param Boolean $dispose Dispose the old node?
		 * @return Boolean
		 */
		public function replaceChild(Opt_Xml_Node $newnode, $refnode, $dispose = false)
		{
			$this->_testNode($newnode);
			if(!is_array($this->_subnodes))
			{
				return false;
			}
			$newnode->setParent($this);
			if(is_object($refnode))
			{
				$i = 0;
				$cnt = sizeof($this->_subnodes);
				while($cnt > 0)
				{
					if(isset($this->_subnodes[$i]))
					{
						// SEE: note about comparing" in bringToEnd()
						if($refnode === $this->_subnodes[$i])
						{
							$dispose and $this->_subnodes[$i]->dispose();
							$this->_subnodes[$i] = $newnode;
							return true;
						}
					}
					$i++;
				}
			}
			elseif(is_integer($refnode) && isset($this->_subnodes[$refnode]))
			{
				$dispose and $this->_subnodes[$refnode]->dispose();
				$this->_subnodes[$refnode] = $newnode;
				return true;
			}
			return false;
		} // end replaceChild();

		/**
		 * Returns true, if the current node contains any children.
		 *
		 * @return Boolean
		 */
		public function hasChildren()
		{
			if(!is_array($this->_subnodes))
			{
				return false;
			}
			return sizeof($this->_subnodes) > 0;
		} // end hasChildren();

		/**
		 * Returns the number of the children.
		 *
		 * @return Integer
		 */
		public function countChildren()
		{
			if(!is_array($this->_subnodes))
			{
				return 0;
			}
			return sizeof($this->_subnodes);
		} // end countChildren();

		/**
		 * Returns the last child of the node.
		 *
		 * @return Opt_Xml_Node
		 */
		public function getLastChild()
		{
			if(!is_array($this->_subnodes))
			{
				return NULL;
			}
			if(sizeof($this->_subnodes) > 0)
			{
				return end($this->_subnodes);
			}
			return NULL;
		} // end getLastChild();

		/**
		 * Returns the array containing all the children.
		 *
		 * @return Array
		 */
		public function getChildren()
		{
			$cnt = sizeof($this->_subnodes);
			$result = array();
			for($i = 0; $i < $cnt; $i++)
			{
				if(is_object($this->_subnodes[$i]))
				{
					$result[] = $this->_subnodes[$i];
				}			
			}
			return $result;
		} // end getChildren();

		/**
		 * Returns all the descendants of the current node.
		 *
		 * @return Array The list of descendants.
		 */
		public function getElements()
		{
			$queue = array();
			foreach($this->_subnodes as $item)
			{
				if(is_object($item))
				{
					$queue[] = $item;
				}
			}
			$result = array();
			while(sizeof($queue) > 0)
			{
				$current = array_shift($queue);
				if($current instanceof Opt_Xml_Node)
				{
					$result[] = $current;
				}
				if($current instanceof Opt_Xml_Scannable)
				{
					foreach($current as $subnode)
					{
						$queue[] = $subnode;
					}
				}
			}
			return $result;
		} // end getElements();

		/**
		 * Returns all the children or descendants with the specified name.
		 *
		 * @param String $name The tag name (without the namespace)
		 * @param Boolean $recursive Scan the descendants recursively?
		 * @return Array
		 */
		public function getElementsByTagName($name, $recursive = true)
		{
			if($recursive)
			{
				return $this->_getElementsByTagName($name, '*');
			}
			
			if(!is_array($this->_subnodes))
			{
				return array();
			}
			$result = array();
			foreach($this->_subnodes as $subnode)
			{
				if(!$subnode instanceof Opt_Xml_Element)
				{
					continue;
				}
				
				if($subnode->getName() == $name || $name == '*')
				{
					$result[] = $subnode;
				}
			}
			return $result;			
		} // end getElementsByTagName();

		/**
		 * Returns all the children or descendants with the specified name
		 * and namespace.
		 *
		 * @param String $namespace The tag namespace
		 * @param String $name The tag name
		 * @param Boolean $recursive Scan the descendants recursively?
		 * @return Array
		 */
		public function getElementsByTagNameNS($namespace, $name, $recursive = true)
		{
			if($recursive)
			{
				return $this->_getElementsByTagName($name, $namespace);
			}

			if(!is_array($this->_subnodes))
			{
				return array();
			}
			$result = array();
			foreach($this->_subnodes as $subnode)
			{
				if(!$subnode instanceof Opt_Xml_Element)
				{
					continue;
				}

				if(($subnode->getName() == $name || $name == '*') && ($subnode->getNamespace() == $namespace || $namespace == '*') )
				{
					$result[] = $subnode;
				}
			}
			return $result;
		} // end getElementsByTagNameNS();

		/**
		 * Returns all the descendants with the specified name and namespace.
		 * Contrary to getElementsByTagNameNS(), the method does not go into
		 * the matching descendants.
		 *
		 * @param String $ns The namespace name
		 * @param String $name The tag name
		 * @return Array
		 */
		public function getElementsExt($ns, $name)
		{
			if(!is_array($this->_subnodes))
			{
				return array();
			}
			// Use the queue to avoid recusive calls in this place.
			$queue = new SplQueue;
			foreach($this->_subnodes as $item)
			{
				if(is_object($item))
				{
					$queue->enqueue($item);
				}
			}
			$result = array();
			while($queue->count() > 0)
			{
				$current = $queue->dequeue();
				if($current instanceof Opt_Xml_Element)
				{
					if(is_null($ns))
					{
						if($current->getName() == $name || $name == '*')
						{
							$result[] = $current;
							continue;	// Do not visit the children of the matching node.
						}
					}
					else
					{
						if(($current->getName() == $name || $name == '*') && ($current->getNamespace() == $ns || $ns == '*') )
						{
							$result[] = $current;
							continue;	// Do not visit the children of the matching node.
						}
					}
				}
				if($current instanceof Opt_Xml_Scannable)
				{
					foreach($current as $subnode)
					{
						$queue->enqueue($subnode);
					}
				}
			}
			return $result;
		} // end getElementsExt();

		/**
		 * Sorts the children with the order specified in the associative
		 * array. The array must contain the pairs 'tag name' => order. Moreover,
		 * it must contain the '*' element representing the new location of
		 * the rest of the nodes.
		 *
		 * @param Array $prototypes The required order.
		 */
		public function sort(Array $prototypes)
		{
			$this->_prototypes = $prototypes;
			if(!isset($prototypes['*']))
			{
				throw new Opt_APINoWildcard_Exception;
			}
			// To create a stable sort.
			$i = 0;
			foreach($this->_subnodes as &$node)
			{
				if($node instanceof Opt_Xml_Buffer)
				{
					$node->set('_ssort', $i++);
				}				
			}
			// Sort!
			usort($this->_subnodes, array($this, 'sortCmp'));
			unset($this->_prototypes);
		} // end sort();

		/**
		 * Moves the specified node to the end of the children list.
		 *
		 * @param Integer|Opt_Xml_Node $node The node to be moved.
		 * @return Boolean
		 */
		public function bringToEnd($node)
		{
			if(!is_array($this->_subnodes))
			{
				return false;			
			}
			if(is_object($node))
			{
				$i = 0;
				$cnt = sizeof($this->_subnodes);
				while($cnt > 0)
				{
					if(isset($this->_subnodes[$i]))
					{
						// Information: NEVER compare two nodes using "=="!
						if($this->_subnodes[$i] === $node)
						{
							$this->_subnodes[] = $node;
							unset($this->_subnodes[$i]);
							return true;
						}
					}
					$i++;
				}
			}
			else
			{
				if(isset($this->_subnodes[$node]))
				{
					$this->_subnodes[] = $this->_subnodes[$node];
					unset($this->_subnodes[$node]);
					return true;
				}
			}
			return false;
		} // end bringToEnd();

		/**
		 * Returns the internal subnode array.
		 *
		 * @internal
		 * @return Array
		 */
		public function getSubnodeArray()
		{
			return $this->_subnodes;
		} // end getSubnodeArray();
	/*
		public function clearNode()
		{
			$queue = new SplQueue;
			foreach($this->_subnodes as $subitem)
			{
				$queue->enqueue($subitem);
			}
			$this->_subnodes = NULL;
			$buffer = array();
			while($queue->count() > 0)
			{
				$item = $queue->dequeue();
				
				foreach($item as $subitem)
				{
					$queue->enqueue($subitem);
					$buffer[] = $subitem;
				}
				$item->_subnodes = NULL;
			}
			unset($buffer);
		} // end clearNode();
	*/
		protected function _cloneHandler()
		{
			/* null */
		} // end _cloneHandler();

		/**
		 * The cloning helper that clones also all the descendants. The cloning algorithm
		 * does not use true recursion, so that it can be safely used even with very deep
		 * trees.
		 *
		 * @internal
		 */
		final public function __clone()
		{
			if($this->get('__nrc') === true)
			{
				// In this state, we do not clone the subnodes, because some else node takes
				// care of it
				$this->set('__nrc', NULL);
				$this->_subnodes = NULL;
				$this->_prototypes = NULL;
				$this->_i = NULL;
				$this->_size = 0;
				$this->_copy = NULL;
				$this->_cloneHandler();
			}
			else
			{
				if(!is_array($this->_subnodes))
				{
					$this->_subnodes = NULL;
					return;
				}
				// The main instance of cloning, it makes copies of all the subnodes.
				$queue = new SplQueue;
				foreach($this->_subnodes as $subitem)
				{
					if(is_object($subitem))
					{
						$queue->enqueue(array($subitem, $this));
					}
				}
				$this->_subnodes = NULL;
				$this->_prototypes = NULL;
				$this->_i = NULL;
				$this->_size = 0;
				$this->_copy = NULL;
				while($queue->count() > 0)
				{
					$pair = $queue->dequeue();
					$pair[0]->set('__nrc', true);
					$pair[1]->appendChild($clone = clone $pair[0]);
					
					// Only Opt_Xml_Scannable instances must go deeper.
					if($pair[0] instanceof Opt_Xml_Scannable)
					{
						foreach($pair[0] as $subitem)
						{
							if(is_object($subitem))
							{
								$queue->enqueue(array($subitem, $clone));
							}
						}
					}		
				}
			}
		} // end __clone();

		/**
		 * Removes the connections between all the descendants so that they can
		 * be safely collected by the PHP garbage collector. Remember to use this
		 * method before you free the last reference to the root node.
		 */
		public function dispose()
		{
			// The main instance of cloning, it makes copies of all the subnodes.
			$queue = new SplQueue;
			if(is_array($this->_subnodes))
			{
				foreach($this->_subnodes as $subitem)
				{
					if(is_object($subitem))
					{
						$queue->enqueue($subitem);
					}
				}
			}
			while($queue->count() > 0)
			{
				$item = $queue->dequeue();
				if($item instanceof Opt_Xml_Scannable)
				{
					foreach($item as $subitem)
					{
						if(is_object($subitem))
						{
							$queue->enqueue($subitem);
						}
					}
				}
				$item->_dispose();
			}
			$this->_dispose();
		} // end dispose();

		/**
		 * Extra dispose function.
		 *
		 * @internal
		 */
		protected function _dispose()
		{
			parent::_dispose();
			$this->_subnodes = NULL;
			$this->_prototypes = NULL;
			$this->_i = NULL;
			$this->_size = 0;
			$this->_copy = NULL;
		} // end _dispose();
		/*
		 * Iterator interface implementation
		 */
		
		public function rewind()
		{
			if(!is_array($this->_subnodes))
			{
				$this->_subnodes = array();
			}
			ksort($this->_subnodes);
			$this->_i = 0;
			end($this->_subnodes);
			$this->_size = key($this->_subnodes);
			
			while(!isset($this->_subnodes[$this->_i]) && $this->_i <= $this->_size)
			{
				$this->_i++;
			}
			
			$this->_copy = $this->_subnodes;
		} // end rewind();
		
		public function valid()
		{
			if($this->_i <= $this->_size)
			{
				return true;
			}
			$this->_copy = null;
			return false;
		} // end valid();
		
		public function current()
		{
			return $this->_copy[$this->_i];
		} // end current();
		
		public function next()
		{
			do
			{
				$this->_i++;
			}
			while(!isset($this->_copy[$this->_i]) && $this->_i <= $this->_size);
		} // end next();
		
		public function key()
		{
			return $this->_i;
		} // end key();
		
		/*
		 * Internal methods
		 */
		
		final public function sortCmp($node1, $node2)
		{
			$name1 = (string)$node1;
			$name2 = (string)$node2;
			if(!isset($this->_prototypes[$name1]))
			{
				$name1 = '*';
			}
			if(!isset($this->_prototypes[$name2]))
			{
				$name2 = '*';
			}
			if($this->_prototypes[$name1] == $this->_prototypes[$name2])
			{
				if($node1->get('_ssort') < $node2->get('_ssort'))
				{
					return -1;
				}
				return 1;
			}
			elseif($this->_prototypes[$name1] < $this->_prototypes[$name2])
			{
				return -1;
			}
			return +1;
		} // end sortCmp();
		
		final protected function _getElementsByTagName($name, $ns)
		{
			if(!is_array($this->_subnodes))
			{
				return array();
			}
			// Use the queue to avoid recusive calls in this place.
			$queue = new SplQueue;
			foreach($this->_subnodes as $item)
			{
				if(is_object($item))
				{
					$queue->enqueue($item);
				}
			}
			$result = array();
			while($queue->count() > 0)
			{
				$current = $queue->dequeue();
				if($current instanceof Opt_Xml_Element)
				{
					if(is_null($ns))
					{
						if($current->getName() == $name || $name == '*')
						{
							$result[] = $current;
						}
					}
					else
					{
						
						if(($current->getName() == $name || $name == '*') && ($current->getNamespace() == $ns || $ns == '*') )
						{
							$result[] = $current;
						}
					}
				}
				if($current instanceof Opt_Xml_Scannable)
				{
					foreach($current as $subnode)
					{
						$queue->enqueue($subnode);
					}
				}
			}
			return $result;
		} // end _getElementsByTagName();
		
		final protected function _appendChild(Opt_Xml_Node $child, $appendOnError)
		{
			if($appendOnError)
			{
				if(!is_array($this->_subnodes))
				{
					return false;
				}
				$this->_subnodes[] = $child;
			}
			else
			{
				throw new Opt_APIInvalidBorders_Exception;
			}
		} // end _appendChild();
		
		protected function _testNode(Opt_Xml_Node $node)
		{
			// This method tests, whether the specified node can be a child
			// of the current note. By overwriting this method, we can change
			// this behavior.
			return true;
		} // end _testNode();

		private function _arrayReduceHoles($array)
		{
			$newArray = array();
			foreach($array as $value)
			{
				$newArray[] = $value;
			}
			return $newArray;
		} // end _arrayReduceHoles();

		private function _arrayCreateHole($array, $where)
		{
			$newArray = array();
			$i = 0;
			foreach($array as $value)
			{
				if($i == $where)
				{
					$newArray[$i] = null;
					$i++;
				}
				$newArray[$i] = $value;
				$i++;
			}
			return $newArray;
		} // end _arrayCreateHole();
	} // end Opt_Xml_Scannable;
Return current item: Open Power Template