Location: PHPKode > projects > Open Power Template > lib/Opt/Compiler/Class.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.
 *
 */

	class Opt_Compiler_Class
	{
		// Opcodes
		const OP_VARIABLE = 1;
		const OP_LANGUAGE_VAR = 2;
		const OP_STRING = 4;
		const OP_NUMBER = 8;
		const OP_ARRAY = 16;
		const OP_OBJECT = 32;
		const OP_IDENTIFIER = 64;
		const OP_OPERATOR = 128;
		const OP_POST_OPERATOR = 256;
		const OP_PRE_OPERATOR = 512;
		const OP_ASSIGN = 1024;
		const OP_NULL = 2048;
		const OP_SQ_BRACKET = 4096;
		const OP_SQ_BRACKET_E = 8192;
		const OP_FUNCTION = 16384;
		const OP_METHOD = 32768;
		const OP_BRACKET = 65536;
		const OP_CLASS = 131072;
		const OP_CALL = 262144;
		const OP_FIELD = 524288;
		const OP_EXPRESSION = 1048576;
		const OP_OBJMAN = 2097152;
		const OP_BRACKET_E = 4194304;
		const OP_TU = 8388608;
		const OP_CURLY_BRACKET = 16777216;
		
		const ESCAPE_ON = true;
		const ESCAPE_OFF = false;
		const ESCAPE_BOTH = 2;
	
	
		// Current compilation
		protected $_template = NULL;
		protected $_attr = array();
		protected $_stack = NULL;
		protected $_node = NULL;
		
		static protected $_recursionDetector = NULL;
		
		// Compiler info
		protected $_tags = array();
		protected $_attributes = array();
		protected $_conversions = array();
		protected $_processors = array();
		protected $_dependencies = array();
		
		// OPT parser info
		protected $_tpl;
		protected $_instructions;
		protected $_namespaces;
		protected $_functions;
		protected $_classes;
		protected $_blocks;
		protected $_components;
		protected $_tf;
		protected $_entities;
		protected $_formnatInfo;
		protected $_formats = array();
		protected $_formatObj = array();
		protected $_inheritance;
		
		// Regular expressions
		private $_rCDataExpression = '/(\<\!\[CDATA\[|\]\]\>)/msi';
		private $_rCommentExpression = '/(\<\!\-\-|\-\-\>)/si';
		private $_rCommentSplitExpression = '/(\<\!\-\-(.*?)\-\-\>)/si';
		private $_rOpeningChar = '[a-zA-Z\:\_]';
		private $_rNameChar = '[a-zA-Z0-9\:\.\_\-]';
		private $_rNameExpression;
		private $_rXmlTagExpression;
		private $_rTagExpandExpression;
		private $_rQuirksTagExpression = '';
		private $_rExpressionTag = '/(\{([^\}]*)\})/msi';
		private $_rAttributeTokens = '/(?:[^\=\"\'\s]+|\=|\"|\'|\s)/x';
		private $_rPrologTokens = '/(?:[^\=\"\'\s]+|\=|\'|\"|\s)/x';
		private $_rModifiers = 'si';
		private $_rXmlHeader = '/(\<\?xml.+\?\>)/msi';
		private $_rProlog = '/\<\?xml(.+)\?\>|/msi';
		private $_rEncodingName = '/[A-Za-z]([A-Za-z0-9.\_]|\-)*/si';
		
		private $_rBacktickString = '`[^`\\\\]*(?:\\\\.[^`\\\\]*)*`';
		private $_rSingleQuoteString = '\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'';
		private $_rHexadecimalNumber = '\-?0[xX][0-9a-fA-F]+';
		private $_rDecimalNumber = '[0-9]+\.?[0-9]*';
		private $_rLanguageVar = '\$[a-zA-Z0-9\_]+@[a-zA-Z0-9\_]+';
		private $_rVariable = '(\$|@)[a-zA-Z0-9\_\.]*';
		private $_rOperators = '\-\>|!==|===|==|!=|\=\>|<>|<<|>>|<=|>=|\&\&|\|\||\(|\)|,|\!|\^|=|\&|\~|<|>|\||\%|\+\+|\-\-|\+|\-|\*|\/|\[|\]|\.|\:\:|\{|\}|\'|\"|';
		private $_rIdentifier = '[a-zA-Z\_]{1}[a-zA-Z0-9\_\.]*';
		private $_rLanguageVarExtract = '\$([a-zA-Z0-9\_]+)@([a-zA-Z0-9\_]+)';

		// Help fields
		private $_charset = null;
		private $_translationConversion = null;
		private $_initialMemory = null;
		private $_comments = 0;
		private $_standalone = false;
		private $_dynamicBlocks = null;
		
		static private $_templates = array();

		/**
		 * Creates a new instance of the template compiler. The compiler can
		 * be created, using the settings from the main OPT class or another
		 * compiler.
		 *
		 * @param Opt_Class|Opt_Compiler_Class $tpl The initial object.
		 */
		public function __construct($tpl)
		{
			if($tpl instanceof Opt_Class)
			{
				$this->_tpl = $tpl;
				$this->_namespaces = $tpl->_getList('_namespaces');
				$this->_classes = $tpl->_getList('_classes');
				$this->_functions = $tpl->_getList('_functions');
				$this->_components = $tpl->_getList('_components');
				$this->_blocks = $tpl->_getList('_blocks');
				$this->_phpFunctions = $tpl->_getList('_phpFunctions');
				$this->_formats = $tpl->_getList('_formats');
				$this->_tf = $tpl->_getList('_tf');
				$this->_entities = $tpl->_getList('_entities');
				$this->_charset = strtoupper($tpl->charset);
				
				// Create the processors and call their configuration method in the constructors.
				$instructions = $tpl->_getList('_instructions');
				foreach($instructions as $instructionClass)
				{
					$obj = new $instructionClass($this, $tpl);
					$this->_processors[$obj->getName()] = $obj;
					
					// Add the tags and attributes registered by this processor.
					foreach($obj->getInstructions() as $item)
					{
						$this->_instructions[$item] = $obj;
					}
					foreach($obj->getAttributes() as $item)
					{
						$this->_attributes[$item] = $obj;
					}
				}
			}
			elseif($tpl instanceof Opt_Compiler_Class)
			{
				// Simply import the data structures from that compiler.
				$this->_tpl = $tpl->_tpl;
				$this->_namespaces = $tpl->_namespaces;
				$this->_classes = $tpl->_classes;
				$this->_functions = $tpl->_functions;
				$this->_components = $tpl->_components;
				$this->_blocks = $tpl->_blocks;
				$this->_inheritance = $tpl->_inheritance;
				$this->_formatInfo = $tpl->_formatInfo;
				$this->_formats = $tpl->_formats;
				$this->_tf = $tpl->_tf;
				$this->_processor = $tpl->_processors;
				$this->_instructions = $tpl->_instructions;
				$this->_attributes = $tpl->_attributes;
				$this->_charset = $tpl->_charset;
				$this->_entities = $tpl->_entities;
				$tpl = $this->_tpl;
			}
			
			if($tpl->unicodeNames)
			{
				// Register unicode name regular expressions
				$this->_rOpeningChar = '[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Nl}\_\:]';
				$this->_rNameChar = '[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Nl}\p{Mc}\p{Me}\p{Mn}\p{Nd}\_\:\.\-]';
				$this->_rModifiers = 'msiu';
			}
			
			// Register the rest of the expressions
			$this->_rNameExpression = '/('.$this->_rOpeningChar.'?'.$this->_rNameChar.'*)/'.$this->_rModifiers;
			$this->_rXmlTagExpression = '/(\<((\/)?('.$this->_rOpeningChar.'?'.$this->_rNameChar.'*)( [^\<\>]*)?(\/)?)\>)/'.$this->_rModifiers;
			$this->_rTagExpandExpression = '/^(\/)?('.$this->_rOpeningChar.'?'.$this->_rNameChar.'*)( [^\<\>]*)?(\/)?$/'.$this->_rModifiers;
			
			
			$this->_rQuirksTagExpression = '/(\<((\/)?(('.implode('|', $this->_namespaces).')\:'.$this->_rNameChar.'*)( [^\<\>]+)?(\/)?)\>)/'.$this->_rModifiers;
			// We've just thrown the performance away by loading the compiler, so this won't make things worse
			// but the user may be happy :). However, don't show this message, if we are in the performance mode.
			if(!is_writable($tpl->compileDir) && $tpl->_compileMode != Opt_Class::CM_PERFORMANCE)
			{
				throw new Opt_FilesystemAccess_Exception('compilation', 'writeable');
			}

			// If the debug console is active, preload the XML tree classes.
			// Without it, the debug console would show crazy things about the memory usage.
			if($this->_tpl->debugConsole && !class_exists('Opt_Xml_Root'))
			{
				Opl_Loader::load('Opt_Xml_Root');
				Opl_Loader::load('Opt_Xml_Text');
				Opl_Loader::load('Opt_Xml_Cdata');
				Opl_Loader::load('Opt_Xml_Element');
				Opl_Loader::load('Opt_Xml_Attribute');
				Opl_Loader::load('Opt_Xml_Expression');
				Opl_Loader::load('Opt_Xml_Prolog');
				Opl_Loader::load('Opt_Xml_Dtd');
				Opl_Loader::load('Opt_Format_Array');
			}
		} // end __construct();

		/**
		 * Allows to clone the original compiler, creating new instruction
		 * processors for the new instance.
		 */
		public function __clone()
		{
			$this->_processors = array();
			$this->_tags = array();
			$this->_attributes = array();
			$this->_conversions = array();
			$instructions = $this->_tpl->_getList('_instructions');
			$cnt = sizeof($instructions);
			for($i = 0; $i < $cnt; $i++)
			{
				$obj = new $instructions[$i]($this, $tpl);	
				$this->_processors[$obj->getName()] = $obj;
			}
		} // end __clone();
	
		/*
		 * General purpose tools and utilities
		 */

		/**
		 * Returns the currently processed template file name.
		 *
		 * @static
		 * @return String The currently processed template name
		 */
		static public function getCurrentTemplate()
		{
			return end(self::$_templates);
		} // end getCurrentTemplate();

		/**
		 * Cleans the compiler state after the template compilation.
		 * It is necessary in the exception processing - if the exception
		 * is thrown in the middle of the compilation, the compiler becomes
		 * useless, because it is locked. The compilation algorithm automatically
		 * filters the exceptions, cleans the compiler state and throws the captured
		 * exceptions again, to the script.
		 *
		 * @static
		 */
		static public function cleanCompiler()
		{
			self::$_recursionDetector = null;
			self::$_templates = array();
		} // end cleanCompiler();

		/**
		 * Returns the value of the compiler state variable or
		 * NULL if the variable is not set.
		 *
		 * @param String $name Compiler variable name
		 * @return Mixed The compiler variable value.
		 */
		public function get($name)
		{
			if(!isset($this->_attr[$name]))
			{
				return NULL;
			}
			return $this->_attr[$name];
		} // end get();

		/**
		 * Creates or modifies the compiler state variable.
		 *
		 * @param String $name The name
		 * @param Mixed $value The value
		 */
		public function set($name, $value)
		{
			$this->_attr[$name] = $value;
		} // end set();

		/**
		 * Adds the escaping formula to the specified expression using the current escaping
		 * rules:
		 *
		 * 1. The $status variable.
		 * 2. The current template settings.
		 * 3. The OPT settings.
		 *
		 * @param String $expression The PHP expression to be escaped.
		 * @param Boolean $status The status of escaping for this expression or NULL, if not set.
		 * @return String The expression with the escaping formula added, if necessary.
		 */
		public function escape($expression, $status = null)
		{
			// OPT Configuration
			$escape = $this->_tpl->escape;
			
			// Template configuration
			if(!is_null($this->get('escaping')))
			{
				$escape = ($this->get('escaping') == true ? true : false);
			}
			
			// Expression settings
			if(!is_null($status))
			{
				$escape = ($status == true ? true : false);
			}
			
			if($escape)
			{
				// The user may define a custom escaping function
				if($this->isFunction('escape'))
				{
					if(strpos($this->_functions['escape'], '#', 0) !== false)
					{
						throw new Opt_InvalidArgumentFormat_Exception('escape', 'escape');
					}
					return $this->_functions['escape'].'('.$expression.')';
				}
				return 'htmlspecialchars('.$expression.')';
			}
			return $expression;
		} // end escape();

		/**
		 * Returns the format object for the specified variable.
		 *
		 * @param String $variable The variable identifier.
		 * @param Boolean $restore optional Whether to load a previously created format object (false) or to create a new one.
		 * @return Opt_Compiler_Format The format object.
		 */
		public function getFormat($variable, $restore = false)
		{
			$hc = $this->_tpl->defaultFormat;
			if(isset($this->_formatInfo[$variable]))
			{
				$hc = $this->_formatInfo[$variable];
			}
			if($restore && isset($this->_formatObj[$hc]))
			{
				return $this->_formatObj[$hc];
			}
			
			$top = $this->createFormat($variable, $hc);
			if($restore)
			{
				$this->_formatObj[$hc] = $top;
			}
			return $top;
		} // end getFormat();

		/**
		 * Creates a format object for the specified description string.
		 *
		 * @param String $variable The variable name (for debug purposes)
		 * @param String $hc The description string.
		 * @return Opt_Compiler_Format The newly created format object.
		 */
		public function createFormat($variable, $hc)
		{
			// Decorate the objects, if necessary
			$expanded = explode('/', $hc);
			$obj = null;
			foreach($expanded as $class)
			{
				if(!isset($this->_formats[$class]))
				{
					throw new Opt_FormatNotFound_Exception($variable, $class);
				}
				$hcName = $this->_formats[$class];
				if(!is_null($obj))
				{
					$obj->decorate($obj2 = new $hcName($this->_tpl, $this));
					$obj = $obj2;
				}
				else
				{
					$top = $obj = new $hcName($this->_tpl, $this, $hc);
				}
			}
			return $top;
		} // end createFormat();

		/**
		 * Allows to export the list of variables and their data formats to
		 * the template compiler.
		 *
		 * @param Array $list An associative array of pairs "variable => format description"
		 */
		public function setFormatList(Array $list)
		{
			$this->_formatInfo = $list;
		} // end setFormatList();

		/**
		 * Converts the specified item into another string using one of the
		 * registered patterns. If the pattern is not found, the method returns
		 * the original item unmodified.
		 *
		 * @param String $item The item to be converted.
		 * @return String
		 */
		public function convert($item)
		{
			// the converter allows to convert one name into another and keep it, if there is no
			// conversion pattern. Used in connection with sections + snippets.
			if(isset($this->_conversions[$item]))
			{
				return $this->_conversions[$item];
			}
			return $item;
		} // end convert();

		/**
		 * Creates a new conversion pattern. The string $from will be converted
		 * into $to.
		 *
		 * @param String $from The original string
		 * @param String $to The new string
		 */
		public function setConversion($from, $to)
		{
			$this->_conversions[$from] = $to;
		} // end setConversion();

		/**
		 * Removes the conversion pattern from the compiler memory.
		 *
		 * @param String $from The original string.
		 * @return Boolean
		 */
		public function unsetConversion($from)
		{
			if(isset($this->_conversions[$from]))
			{
				unset($this->_conversions[$from]);
				return true;
			}
			return false;
		} // end unsetConversion();

		/**
		 * Registers the dynamic inheritance rules for the templates. The
		 * array taken as a parameter must be an associative array of pairs
		 * 'extending' => 'extended' file names.
		 *
		 * @param Array $inheritance The list of inheritance rules.
		 */
		public function setInheritance(Array $inheritance)
		{
			$this->_inheritance = $inheritance;
		} // end setInheritance();

		/**
		 * Parses the entities in the specified text.
		 *
		 * @param String $text The original text
		 * @return String
		 */
		public function parseEntities($text)
		{
			return preg_replace_callback('/\&(([a-zA-Z\_\:]{1}[a-zA-Z0-9\_\:\-\.]*)|(\#((x[a-fA-F0-9]+)|([0-9]+))))\;/', array($this, '_decodeEntity'), $text);
		//	return htmlspecialchars_decode(str_replace(array_keys($this->_entities), array_values($this->_entities), $text));
		} // end parseEntities();

		/**
		 * Replaces only OPT-specific entities &lb; and &rb; to the corresponding
		 * characters.
		 *
		 * @param String $text Input text
		 * @return String output text
		 */
		public function parseShortEntities($text)
		{
			return str_replace(array('&lb;', '&rb;'), array('{', '}'), $text);
		} // end parseShortEntities();

		/**
		 * Replaces the XML special characters back to entities with smart ommiting of &
		 * that already creates an entity.
		 *
		 * @param String $text Input text.
		 * @return String Output text.
		 */
		public function parseSpecialChars($text)
		{
			return htmlspecialchars($text);
			return preg_replace_callback('/(\&\#?[a-zA-Z0-9]*\;)|\<|\>|\"|\&/', array($this, '_entitize'), $text);
		} // end parseSpecialChars();

		/**
		 * Returns 'true', if the argument is a valid identifier. An identifier
		 * must begin with a letter or underscore, and later, the numbers are also
		 * allowed.
		 *
		 * @param String $id The tested string
		 * @return Boolean
		 */
		public function isIdentifier($id)
		{
			return preg_match($this->_rEncodingName, $id);
		} // end isIdentifier();

		/**
		 * Checks whether the specified tag name is registered as an instruction.
		 * Returns its processor in case of success or NULL.
		 *
		 * @param String $tag The tag name (with the namespace)
		 * @return Opt_Compiler_Processor|NULL The processor that registered this tag.
		 */
		public function isInstruction($tag)
		{
			if(isset($this->_instructions[$tag]))
			{
				return $this->_instructions[$tag];
			}
			return NULL;
		} // end isInstruction();

		/**
		 * Returns true, if the argument is the name of an OPT attribute.
		 *
		 * @param String $tag The attribute name
		 * @return Boolean
		 */
		public function isOptAttribute($tag)
		{
			if(isset($this->_attributes[$tag]))
			{
				return $this->_attributes[$tag];
			}
			return NULL;
		} // end isOptAttribute();

		/**
		 * Returns true, if the argument is the OPT function name.
		 *
		 * @param String $name The function name
		 * @return Boolean
		 */
		public function isFunction($name)
		{
			if(isset($this->_functions[$name]))
			{
				return $this->_functions[$name];
			}
			return NULL;
		} // end isFunction();

		/**
		 * Returns true, if the argument is the name of the class
		 * accepted by OPT.
		 *
		 * @param String $id The class name.
		 * @return Boolean
		 */
		public function isClass($id)
		{
			if(isset($this->_classes[$id]))
			{
				return $this->_classes[$id];
			}
			return NULL;
		} // end isClass();

		/**
		 * Returns true, if the argument is the name of the namespace
		 * processed by OPT.
		 *
		 * @param String $ns The namespace name
		 * @return Boolean
		 */
		public function isNamespace($ns)
		{
			return in_array($ns, $this->_namespaces);
		} // end isNamespace();

		/**
		 * Returns true, if the argument is the name of the component tag.
		 * @param String $component The component tag name
		 * @return Boolean
		 */
		public function isComponent($component)
		{
			return isset($this->_components[$component]);
		} // end isComponent();

		/**
		 * Returns true, if the argument is the name of the block tag.
		 * @param String $block The block tag name.
		 * @return Boolean
		 */
		public function isBlock($block)
		{
			return isset($this->_blocks[$block]);
		} // end isComponent();

		/**
		 * Returns true, if the argument is the processor name.
		 *
		 * @param String $name The instruction processor name
		 * @return Boolean
		 */
		public function isProcessor($name)
		{
			if(!isset($this->_processors[$name]))
			{
				return NULL;
			}
			return $this->_processors[$name];
		} // end isProcessor();

		/**
		 * Returns the processor object with the specified name. If
		 * the processor does not exist, it generates an exception.
		 *
		 * @param String $name The processor name
		 * @return Opt_Compiler_Processor
		 */
		public function processor($name)
		{
			if(!isset($this->_processors[$name]))
			{
				throw new Opt_ObjectNotExists_Exception('processor', $name);
			}
			return $this->_processors[$name];
		} // end processor();

		/**
		 * Returns the component class name assigned to the specified
		 * XML tag. If the component class is not registered, it throws
		 * an exception.
		 *
		 * @param String $name The component XML tag name.
		 * @return Opt_Component_Interface
		 */
		public function component($name)
		{
			if(!isset($this->_components[$name]))
			{
				throw new Opt_ObjectNotExists_Exception('component', $name);
			}
			return $this->_components[$name];
		} // end component();

		/**
		 * Returns the block class name assigned to the specified
		 * XML tag. If the block class is not registered, it throws
		 * an exception.
		 *
		 * @param String $name The block XML tag name.
		 * @return Opt_Block_Interface
		 */
		public function block($name)
		{
			if(!isset($this->_blocks[$name]))
			{
				throw new Opt_ObjectNotExists_Exception('block', $name);
			}
			return $this->_blocks[$name];
		} // end block();

		/**
		 * Returns the template name that is inherited by the template '$name'
		 *
		 * @param String $name The "current" template file name
		 * @return String
		 */
		public function inherits($name)
		{
			if(isset($this->_inheritance[$name]))
			{
				return $this->_inheritance[$name];
			}
			return NULL;
		} // end inherits();

		/**
		 * Adds the template file name to the dependency list of the currently
		 * compiled file, so that it could be checked for modifications during
		 * the execution.
		 *
		 * @param String $template The template file name.
		 */
		public function addDependantTemplate($template)
		{
			if(in_array($template, $this->_dependencies))
			{
				$exception = new Opt_InheritanceRecursion_Exception($template);
				$exception->setData($this->_dependencies);
				throw $exception;
			}
			
			$this->_dependencies[] = $template;
		} // end addDependantTemplate();

		/**
		 * Imports the dependencies from another compiler object and adds them
		 * to the actual dependency list.
		 *
		 * @param Opt_Compiler_Class $compiler Another compiler object.
		 */
		public function importDependencies(Opt_Compiler_Class $compiler)
		{
			$this->_dependencies = array_merge($this->_dependencies, $compiler->_dependencies);
		} // end importDependencies();

		/*
		 * Internal tools and utilities
		 */

		/**
		 * Compiles the attribute part of the opening tag and extracts the tag
		 * attributes to an array. Moreover, it performs the entity conversion
		 * to the corresponding characters.
		 *
		 * @internal
		 * @param String $attrList The attribute list string
		 * @param string $tagName The tag name for debug purposes
		 * @return Array The list of attributes with the values.
		 */
		protected function _compileAttributes($attrList, $tagName = '')
		{
			// Tokenize the list
			preg_match_all($this->_rAttributeTokens, $attrList, $match, PREG_SET_ORDER);
			
			$size = sizeof($match);
			$result = array();
			for($i = 0; $i < $size; $i++)
			{
				/**
				 * The algorithm scans the tokens on the list and determines, where
				 * the beginning and the end of the attribute is. We do not use the
				 * regular expressions, because they are not able to capture the
				 * invalid content between the expressions.
				 *
				 * The sub-loops can modify the iteration variable to skip the found
				 * elements, white characters etc. This means that the main loop
				 * does only a few iteration number, equal approximately the number
				 * of attributes.
				 */
				if(!ctype_space($match[$i][0]))
				{
					
					if(!preg_match($this->_rNameExpression, $match[$i][0]))
					{
						return false;
					}
					
					$vret = false;
					$name = $match[$i][0];

					if(substr_count($name, ':') > 1)
					{
						throw new Opt_InvalidNamespace_Exception($name);
					}

					$value = null;
					for($i++; ctype_space($match[$i][0]) && $i < $size; $i++){}
					
					if($match[$i][0] != '=')
					{
						if($this->_tpl->htmlAttributes)
						{
							$result[$name] = $name;
							continue;
						}
						else
						{
							return false;
						}
					}
					// Look for the attribute value start
					for($i++; ctype_space($match[$i][0]) && $i < $size; $i++){}
				
					if($match[$i][0] != '"' && $match[$i][0] != '\'')
					{
						return false;
					}

					// Save the delimiter, because we will use it to make the error checking
					$delimiter = $match[$i][0];

					$value = '';
					for($i++; $i < $size; $i++)
					{
						if($match[$i][0] == $delimiter)
						{
							break;
						}
						$value .= $match[$i][0];
					}
					if(!isset($match[$i][0]))
					{
						return false;
					}
					if($match[$i][0] != $delimiter)
					{
						return false;
					}
					// We return the decoded attribute values, because they are
					// stored without the entities.
					if(isset($result[$name]))
					{
						throw new Opt_XmlDuplicatedAttribute_Exception($name, $tagName);
					}
					$result[$name] = htmlspecialchars_decode($value);
				}
			}
			return $result;
		} // end _compileAttributes();

		/**
		 * Parses the XML prolog and returns its attributes as an array. The parsing
		 * algorith is the same, as in _compileAttributes().
		 *
		 * @internal
		 * @param String $prolog The prolog string.
		 * @return Array
		 */
		protected function _compileProlog($prolog)
		{
			// Tokenize the list
			preg_match_all($this->_rPrologTokens, $prolog, $match, PREG_SET_ORDER);
				
			$size = sizeof($match);
			$result = array();
			for($i = 0; $i < $size; $i++)
			{
				if(!ctype_space($match[$i][0]))
				{
					// Traverse through a single attribute
					if(!preg_match($this->_rNameExpression, $match[$i][0]))
					{
						throw new Opt_XmlInvalidProlog_Exception('invalid attribute format');
					}
						
					$vret = false;
					$name = $match[$i][0];
					$value = null;
					for($i++; $i < $size && ctype_space($match[$i][0]); $i++){}
						
					if($i >= $size || $match[$i][0] != '=')
					{
						throw new Opt_XmlInvalidProlog_Exception('invalid attribute format');
					}
					for($i++; ctype_space($match[$i][0]) && $i < $size; $i++){}
					
					if($match[$i][0] != '"' && $match[$i][0] != '\'')
					{
						throw new Opt_XmlInvalidProlog_Exception('invalid attribute format');
					}
					$opening = $match[$i][0];
					$value = '';
					for($i++; $i < $size; $i++)
					{
						if($match[$i][0] == $opening)
						{
							break;
						}
						$value .= $match[$i][0];
					}
					if(!isset($match[$i][0]) || $match[$i][0] != $opening)
					{
						throw new Opt_XmlInvalidProlog_Exception('invalid attribute format');
					}
					// If we are here, the attribute is correct. No shit on the way detected.
					$result[$name] = $value;
				}
			}
			$returnedResult = $result;
			// Check, whether the arguments are correct.
			if(isset($result['version']))
			{
				// There is no other version so far, so report a warning. For 99,9% this is a mistake.
				if($result['version'] != '1.0')
				{
					$this->_tpl->debugConsole and Opt_Support::warning('OPT', 'XML prolog warning: strange XML version: '.$result['version']);
				}
				unset($result['version']);
			}
			if(isset($result['encoding']))
			{
				if(!preg_match($this->_rEncodingName, $result['encoding']))
				{
					throw new Opt_XmlInvalidProlog_Exception('invalid encoding name format');
				}
				// The encoding should match the value we mentioned in the OPT configuration and sent to the browser.
				$result['encoding'] = strtolower($result['encoding']);
				$charset = is_null($this->_tpl->charset) ? null : strtolower($this->_tpl->charset);
				if($result['encoding'] != $charset && !is_null($charset))
				{
					$this->_tpl->debugConsole and Opt_Support::warning('OPT', 'XML prolog warning: the declared encoding: "'.$result['encoding'].'" differs from setContentType() setting: "'.$charset.'"');
				}
				unset($result['encoding']);
			}
			else
			{
				$this->_tpl->debugConsole and Opt_Support::warning('XML prolog warning: no encoding information. Remember your content must be pure UTF-8 or UTF-16 then.');
			}
			if(isset($result['standalone']))
			{
				if($result['standalone'] != 'yes' && $result['standalone'] != 'no')
				{
					throw new Opt_XmlInvalidProlog_Exception('invalid value for "standalone" attribute: "'.$result['standalone'].'"; expected: "yes", "no".');
				}
				unset($result['standalone']);
			}
			if(sizeof($result) > 0)
			{
				throw new Opt_XmlInvalidProlog_Exception('invalid attributes in prolog.');
			}
			return $returnedResult;
		} // end _compileProlog();

		/**
		 * Adds the PHP code with dependencies to the code buffers in the tree
		 * root node.
		 *
		 * @internal
		 * @param Opt_Xml_Node $tree The tree root node.
		 */
		protected function _addDependencies($tree)
		{
			// OK, there is really some info to include!
			$list = '';
			foreach($this->_dependencies as $a)
			{
				$list .= '\''.$a.'\',';
			}
			
			$tree->addBefore(Opt_Xml_Buffer::TAG_BEFORE, 'if(!$this->_massPreprocess($compileTime, array('.$list.'))){ ');
			$tree->addAfter(Opt_Xml_Buffer::TAG_AFTER, ' }else{ $compileTime = $this->_compile($this->_template); require(__FILE__); } ');
		} // end _addDependencies();

		/**
		 * Compiles the current text block between two XML tags, creating a
		 * complete Opt_Xml_Text node. It looks for the expressions in the
		 * curly brackets, extracts them and packs as separate nodes.
		 *
		 * Moreover, it replaces the entities with the corresponding characters.
		 *
		 * @internal
		 * @param Opt_Xml_Node $current The current XML node.
		 * @param String $text The text block between two tags.
		 * @param Boolean $noExpressions=false If true, do not look for the expressions.
		 * @return Opt_Xml_Node The current XML node.
		 */
		protected function _treeTextCompile($current, $text, $noExpressions = false)
		{
			// Yes, we parse entities, but the text itself should not contain
			// any special characters.
			if(strcspn($text, '<>') != strlen($text))
			{
				throw new Opt_XmlInvalidCharacter_Exception(htmlspecialchars($text));
			}

			if($noExpressions)
			{
				$current = $this->_treeTextAppend($current, $this->parseEntities($text));
			}
			
			preg_match_all($this->_rExpressionTag, $text, $result, PREG_SET_ORDER);
			
			$resultSize = sizeof($result);
			$offset = 0;
			for($i = 0; $i < $resultSize; $i++)
			{
				$id = strpos($text, $result[$i][0], $offset);
				if($id > $offset)
				{
					$current = $this->_treeTextAppend($current, $this->parseEntities(substr($text, $offset, $id - $offset)));						
				}
				$offset = $id + strlen($result[$i][0]);
				
				$current = $this->_treeTextAppend($current, new Opt_Xml_Expression($this->parseEntities($result[$i][2])));
			}
			
			$i--;
			// Now the remaining content of the file
			if(strlen($text) > $offset)
			{
				$current = $this->_treeTextAppend($current, $this->parseEntities(substr($text, $offset, strlen($text) - $offset)));
			}
			return $current;
		} // end _treeTextCompile();

		/**
		 * An utility method that simplifies inserting the text to the XML
		 * tree. Depending on the last child type, it can create a new text
		 * node or add the text to the existing one.
		 *
		 * @internal
		 * @param Opt_Xml_Node $current The currently built XML node.
		 * @param String|Opt_Xml_Node $text The text or the expression node.
		 * @return Opt_Xml_Node The current XML node.
		 */
		protected function _treeTextAppend($current, $text)
		{
			$last = $current->getLastChild();
			if(!is_object($last) || !($last instanceof Opt_Xml_Text))
			{
				if(!is_object($text))
				{
					$node = new Opt_Xml_Text($text);
				}
				else
				{
					$node = new Opt_Xml_Text();
					$node->appendChild($text);
				}
				$current->appendChild($node);
			}
			else
			{
				if(!is_object($text))
				{
					$last->appendData($text);
				}
				else
				{
					$last->appendChild($text);
				}
			}
			return $current;
		} // end _treeTextAppend();

		/**
		 * A helper method for building the XML tree. It appends the
		 * node to the current node and returns the new node that should
		 * become the new current node.
		 *
		 * @internal
		 * @param Opt_Xml_Node $current The current node.
		 * @param Opt_Xml_Node $node The newly created node.
		 * @param Boolean $goInto Whether we visit the new node.
		 * @return Opt_Xml_Node
		 */
		protected function _treeNodeAppend($current, $node, $goInto)
		{
			$current->appendChild($node);
			if($goInto)
			{
				return $node;
			}
			return $current;
		} // end _treeNodeAppend();

		/**
		 * A helper method for building the XML tree. It jumps out of the
		 * current node to the parent and switches to it.
		 *
		 * @internal
		 * @param Opt_Xml_Node $current The current node.
		 * @return Opt_Xml_Node
		 */
		protected function _treeJumpOut($current)
		{
			$parent = $current->getParent();
			
			if(!is_null($parent))
			{
				return $parent;
			}
			return $current;
		} // end _treeJumpOut();

		/**
		 * Looks for special OPT attributes in the element attribute list and
		 * processes them. Returns the list of nodes that need to be postprocessed.
		 *
		 * @internal
		 * @param Opt_Xml_Element $node The scanned element.
		 * @param Boolean $specialNs Do we recognize "parse" and "str" namespaces?
		 * @return Array
		 */
		protected function _processXml(Opt_Xml_Element $node, $specialNs = true)
		{
			if(!$node->hasAttributes())
			{
				return array();
			}
			$attributes = $node->getAttributes();
			$pp = array();

			// Look for special OPT attributes
			foreach($attributes as $attr)
			{
				if($this->isNamespace($attr->getNamespace()))
				{
					$xml = $attr->getXmlName();
					// Check the namespace we found
					switch($attr->getNamespace())
					{
						case 'parse':
							if($specialNs)
							{
								$result = $this->compileExpression((string)$attr, false, Opt_Compiler_Class::ESCAPE_BOTH);
								$attr->addAfter(Opt_Xml_Buffer::ATTRIBUTE_VALUE, ' echo '.$result[0].'; ');
								$attr->setNamespace(null);
							}
							break;
						case 'str':
							if($specialNs)
							{
								$attr->setNamespace(null);
							}
							break;
						default:
							if(isset($this->_attributes[$xml]))
							{
								$this->_attributes[$xml]->processAttribute($node, $attr);
								if($attr->get('postprocess'))
								{
									$pp[] = array($this->_attributes[$xml], $attr);
								}
							}
							$node->removeAttribute($xml);
					}
				}
			}
			return $pp;
		} // end _processXml();

		/**
		 * Runs the postprocessors for the specified attributes.
		 *
		 * @internal
		 * @param Opt_Xml_Node $node The scanned node.
		 * @param Array $list The list of XML attribute processors that need to be postprocessed.
		 */
		protected function _postprocessXml(Opt_Xml_Node $node, Array $list)
		{
			$cnt = sizeof($list);
			for($i = 0; $i < $cnt; $i++)
			{
				$list[$i][0]->postprocessAttribute($node, $list[$i][1]);
			}
		} // end _postprocessXml();

		/**
		 * An utility method for the stage 2 and 3 of the compilation. It is
		 * used to create a non-recursive depth-first search algorithm. The
		 * current queue is sent to a stack, and the new queue if initialized,
		 * if $item contains children.
		 *
		 * @internal
		 * @param SplStack $stack The processing stack.
		 * @param SplQueue $queue The processing queue.
		 * @param Opt_Xml_Scannable $item The item, where to import the nodes from.
		 * @param Boolean $pp The postprocess flag.
		 * @return SplQueue The new queue (or the old one, if none has been created).
		 */
		protected function _pushQueue($stack, $queue, $item, $pp)
		{		
			if($item->hasChildren())
			{
				$stack->push(array($item, $queue, $pp));
				$pp = NULL;
				$queue = new SplQueue;
				foreach($item as $child)
				{
					$queue->enqueue($child);
				}
			}
			
			return $queue;
		} // end _pushQueue();

		/**
		 * Does the postprocessing in the second stage of compilation.
		 *
		 * @internal
		 * @param Opt_Xml_Node|Null $item The postprocessed node.
		 * @param Array $pp The list of postprocessed attributes.
		 */
		protected function _doPostprocess($item, $pp)
		{
			// Postprocess code for the compilation stage 2
			// Packed into a method, because it is used twice.
			if(is_null($item))
			{
				return;
			}
			if(sizeof($pp) > 0)
			{
				$this->_postprocessXml($item, $pp);
			}
			if($item->get('postprocess'))
			{
				if(!is_null($processor = $this->isInstruction($item->getXmlName())))
				{
					$processor->postprocessNode($item);
				}
				elseif($this->isComponent($item->getXmlName()))
				{
					$processor = $this->processor('component');
					$processor->postprocessComponent($item);
				}
				elseif($this->isBlock($item->getXmlName()))
				{
					$processor = $this->processor('block');
					$processor->postprocessBlock($item);
				}
				else
				{
					throw new Opt_UnknownProcessor_Exception($item->getXmlName());
				}
			}
		} // end _doPostprocess();

		/**
		 * Does the post-linking for the third stage of the compilation and returns
		 * the linked code.
		 *
		 * @internal
		 * @param Opt_Xml_Node $item The linked item.
		 * @return String
		 */
		protected function _doPostlinking($item)
		{
			// Post code
			if(is_null($item))
			{
				return '';
			}

			// This prevents from displaying </> if the HTML node was hidden.
			if($item->get('hidden') !== false)
			{
				return '';
			}
			if($item->get('_skip_postlinking') == true)
			{
				return '';
			}

			$output = '';
			switch($item->getType())
			{
				case 'Opt_Xml_Text':
					$output .= $item->buildCode(Opt_Xml_Buffer::TAG_AFTER);
					break;
				case 'Opt_Xml_Element':
					if($this->isNamespace($item->getNamespace()))
					{
						if($item->get('single'))
						{
							$output .= $item->buildCode(Opt_Xml_Buffer::TAG_SINGLE_AFTER, Opt_Xml_Buffer::TAG_AFTER);
						}
						else
						{
							$output .= $item->buildCode(Opt_Xml_Buffer::TAG_CONTENT_AFTER, Opt_Xml_Buffer::TAG_CLOSING_BEFORE,
								Opt_Xml_Buffer::TAG_CLOSING_AFTER, Opt_Xml_Buffer::TAG_AFTER);
						}
					}
					else
					{
						$output .= $item->buildCode(Opt_Xml_Buffer::TAG_CONTENT_AFTER, Opt_Xml_Buffer::TAG_CLOSING_BEFORE).'</'.$item->get('_name').'>'.$item->buildCode(Opt_Xml_Buffer::TAG_CLOSING_AFTER, Opt_Xml_Buffer::TAG_AFTER);
						$item->set('_name', NULL);
					}
					break;
				case 'Opt_Xml_Root':
					$output .= $item->buildCode(Opt_Xml_Buffer::TAG_AFTER);
					break;
			}
			$this->_closeComments($item, $output);
			return $output;
		} // end _doPostlinking();

		/**
		 * Closes the XML comment for the commented item.
		 *
		 * @internal
		 * @param Opt_Xml_Node $item The commented item.
		 * @param String &$output The reference to the output buffer.
		 */
		protected function _closeComments($item, &$output)
		{
			if($item->get('commented'))
			{
				$this->_comments--;
				if($this->_comments == 0)
				{
					// According to the XML grammar, the construct "--->" is not allowed.
					if(strlen($output) > 0 && $output[strlen($output)-1] == '-')
					{
						throw new Opt_XmlComment_Exception('--->');
					}

					$output .= '-->';
				}
			}
		} // end _closeComments();

		/**
		 * Links the element attributes into a valid XML code and returns
		 * the output code.
		 *
		 * @internal
		 * @param Opt_Xml_Element $subitem The XML element.
		 * @return String
		 */
		protected function _linkAttributes($subitem)
		{
			// Links the attributes into the PHP code
			if($subitem->hasAttributes() || $subitem->bufferSize(Opt_Xml_Buffer::TAG_BEGINNING_ATTRIBUTES) > 0 || $subitem->bufferSize(Opt_Xml_Buffer::TAG_ENDING_ATTRIBUTES) > 0)
			{
			
				$code = $subitem->buildCode(Opt_Xml_Buffer::TAG_ATTRIBUTES_BEFORE, Opt_Xml_Buffer::TAG_BEGINNING_ATTRIBUTES);
				$attrList = $subitem->getAttributes();
				// Link attributes into a string
				foreach($attrList as $attribute)
				{
					$s = $attribute->bufferSize(Opt_Xml_Buffer::ATTRIBUTE_NAME);
					switch($s)
					{
						case 0:
							$code .= $attribute->buildCode(Opt_Xml_Buffer::ATTRIBUTE_BEGIN).' '.$attribute->getXmlName();
							break;
						case 1:
							$code .= ($attribute->bufferSize(Opt_Xml_Buffer::ATTRIBUTE_BEGIN) == 0 ? ' ' : '').$attribute->buildCode(Opt_Xml_Buffer::ATTRIBUTE_BEGIN, ' ', Opt_Xml_Buffer::ATTRIBUTE_NAME);
							break;
						default:
							throw new Opt_CompilerCodeBufferConflict_Exception(1, 'ATTRIBUTE_NAME', $subitem->getXmlName());
					}

					if($attribute->bufferSize(Opt_Xml_Buffer::ATTRIBUTE_VALUE) == 0)
					{
						// Static value
						if(!($this->_tpl->htmlAttributes && $attribute->getValue() == $attribute->getName()))
						{
							$code .= '="'.htmlspecialchars($attribute->getValue()).'"';
						}
					}
					else
					{
						$code .= '="'.$attribute->buildCode(Opt_Xml_Buffer::ATTRIBUTE_VALUE).'"';
					}
					$code .= $attribute->buildCode(Opt_Xml_Buffer::ATTRIBUTE_END);
				}
				return $code.$subitem->buildCode(Opt_Xml_Buffer::TAG_ENDING_ATTRIBUTES, Opt_Xml_Buffer::TAG_ATTRIBUTES_AFTER);				
			}
			return '';
		} // end _linkAttributes();
		
		/*
		 * Main compilation methods
		 */

		/**
		 * The compilation launcher. It executes the proper compilation steps
		 * according to the inheritance rules etc.
		 *
		 * @param String $code The source code to be compiled.
		 * @param String $filename The source template filename.
		 * @param String $compiledFilename The output template filename.
		 * @param Int $mode The compilation mode.
		 */
		public function compile($code, $filename, $compiledFilename, $mode)
		{
			try
			{
				// We cannot compile two templates at the same time
				if(!is_null($this->_template))
				{
					throw new Opt_CompilerLocked_Exception($filename, $this->_template);
				}

				// Detecting recursive inclusion
				if(is_null(self::$_recursionDetector))
				{
					self::$_recursionDetector = array(0 => $filename);
					$weFree = true;
				}
				else
				{
					if(in_array($filename, self::$_recursionDetector))
					{
						$exception = new Opt_CompilerRecursion_Exception($filename);
						$exception->setData(self::$_recursionDetector);
						throw $exception;
					}
					self::$_recursionDetector[] = $filename;
					$weFree = false;
				}
				// Cleaning up the processors
				foreach($this->_processors as $proc)
				{
					$proc->reset();
				}
				// Initializing the template launcher
				$this->set('template', $this->_template = $filename);
				$this->set('mode', $mode);
				$this->set('currentTemplate', $this->_template);
				array_push(self::$_templates, $filename);
			//	$this->_stack = new SplStack;
				$i = 0;
				$extend = $filename;

				$memory = 0;

				// The inheritance loop
				do
				{
					// Stage 1 - code compilation
					if($this->_tpl->debugConsole)
					{
						$initial = memory_get_usage();
						$tree = $this->_stage1($code, $extend, $mode);
						// Stage 2 - PHP tree processing
				//		$this->_stack = array();
						$this->_stage2($tree);
						$this->set('escape', NULL);
				//		unset($this->_stack);
						$memory += (memory_get_usage() - $initial);
						unset($code);
					}
					else
					{
						$tree = $this->_stage1($code, $extend, $mode);
						unset($code);
						// Stage 2 - PHP tree processing
				//		$this->_stack = array();
						$this->_stage2($tree);
						$this->set('escape', NULL);
				//		unset($this->_stack);
					}


					// if the template extends something, load it and also process
					if(isset($extend) && $extend != $filename)
					{
						$this->addDependantTemplate($extend);
					}

					if(!is_null($snippet = $tree->get('snippet')))
					{
						$tree->dispose();
						unset($tree);

						// Change the specified snippet into a root node.
						$tree = new Opt_Xml_Root;
						$attribute = new Opt_Xml_Attribute('opt:use', $snippet);
						$this->processor('snippet')->processAttribute($tree, $attribute);
						$this->processor('snippet')->postprocessAttribute($tree, $attribute);

						$this->_stage2($tree, true);
						break;
					}
					if(!is_null($extend = $tree->get('extend')))
					{
						$tree->dispose();
						unset($tree);

						$this->set('currentTemplate', $extend);
						array_pop(self::$_templates);
						array_push(self::$_templates, $extend);
						$code = $this->_tpl->_getSource($extend);
					}
					$i++;
				}
				while(!is_null($extend));
				// There are some dependant templates. We must add a suitable PHP code
				// to the output.

				if(sizeof($this->_dependencies) > 0)
				{
					$this->_addDependencies($tree);
				}

				if($this->_tpl->debugConsole)
				{
					Opt_Support::addCompiledTemplate($this->_template, $memory);
				}

				// Stage 3 - linking the last tree
				if(!is_null($compiledFilename))
				{
					$output = '';
					$this->_dynamicBlocks = array();

					$this->_stage3($output, $tree);
					$tree->dispose();
					unset($tree);

					$output = str_replace('?><'.'?php', '', $output);

					// Build the directories, if needed.
					if(($pos = strrpos($compiledFilename, '/')) !== false)
					{
						$path = $this->_tpl->compileDir.substr($compiledFilename, 0, $pos);
						if(!is_dir($path))
						{
							mkdir($path, 0750, true);
						}
					}

					// Save the file
					if(sizeof($this->_dynamicBlocks) > 0)
					{
						file_put_contents($this->_tpl->compileDir.$compiledFilename.'.dyn', serialize($this->_dynamicBlocks));
					}
					file_put_contents($this->_tpl->compileDir.$compiledFilename, $output);
				}
				else
				{
					$tree->dispose();
				}
				array_pop(self::$_templates);
				$this->_inheritance = array();
				if($weFree)
				{
					// Do the cleanup.
					$this->_dependencies = array();
					self::$_recursionDetector = NULL;
					foreach($this->_processors as $processor)
					{
						$processor->reset();
					}
				}
				$this->_template = NULL;

				// Run the new garbage collector, if it is available.
			/*	if(version_compare(PHP_VERSION, '5.3.0', '>='))
				{
					gc_collect_cycles();
				}*/
			}
			catch(Exception $e)
			{
				// Free the memory
				if(isset($tree))
				{
					$tree->dispose();
				}
				// Clean the compiler state in case of exception
				$this->_template = NULL;
				$this->_dependencies = array();
				self::$_recursionDetector = NULL;
				foreach($this->_processors as $processor)
				{
					$processor->reset();
				}
				// Run the new garbage collector, if it is available.
			/*	if(version_compare(PHP_VERSION, '5.3.0', '>='))
				{					
					gc_collect_cycles();
				}*/
				// And throw it forward.
				throw $e;
			}
		} // end compile();

		/**
		 * Compilation - stage 1 - parsing the input template and
		 * building an XML tree.
		 *
		 * @internal
		 * @param String &$code The code to be parsed
		 * @param String $filename Currently unused.
		 * @param Int $mode The compilation mode.
		 * @return Opt_Xml_Root The root node of the new tree.
		 */
		protected function _stage1(&$code, $filename, $mode)
		{
			$current = $tree = new Opt_Xml_Root;
			$codeSize = strlen($code);
			$encoding = $this->_tpl->charset;
			
			// First we have to find the prolog and DTD. Then we will be able to parse tags.			
			if($mode != Opt_Class::QUIRKS_MODE)
			{
				// Find and parse XML prolog
				$endProlog = 0;
				$endDoctype = 0;
				if(substr($code, 0, 5) == '<?xml')
				{
					$endProlog = strpos($code, '?>', 5);

					if($endProlog === false)
					{
						throw new Opt_XmlInvalidProlog_Exception('prolog ending is missing');
					}
					$values = $this->_compileProlog(substr($code, 5, $endProlog - 5));
					$endProlog += 2;
					if(!$this->_tpl->prologRequired)
					{
						// The prolog must be displayed
						$tree->setProlog(new Opt_Xml_Prolog($values));
					}
				}
				// Skip white spaces
				for($i = $endProlog; $i < $codeSize; $i++)
				{
					if($code[$i] != ' ' && $code[$i] != '	' && $code[$i] != "\r" && $code[$i] != "\n")
					{
						break;
					}
				}
				// Try to find doctype at the new position.
				$possibleDoctype = substr($code, $i, 9);
				
				if($possibleDoctype == '<!doctype' || $possibleDoctype == '<!DOCTYPE')
				{
					// OK, we've found it, now determine the doctype end.
					$bracketCounter = 0;
					$doctypeStart = $i;
					for($i += 9; $i < $codeSize; $i++)
					{
						if($code[$i] == '<')
						{
							$bracketCounter++;
						}
						else if($code[$i] == '>')
						{
							if($bracketCounter == 0)
							{
								$endDoctype = $i;
								break;
							}
							$bracketCounter--;
						}
					}
					if($endDoctype == 0)
					{
						throw new Opt_XmlInvalidDoctype_Exception('doctype ending is missing');
					}

					if(!$this->_tpl->prologRequired)
					{
						$tree->setDtd(new Opt_Xml_Dtd(substr($code, $doctypeStart, $i - $doctypeStart + 1)));
					}
					$endDoctype++;
				}
				else
				{
					$endDoctype = $endProlog;
				}
				// OK, now skip that part.
				$code = substr($code, $endDoctype, $codeSize);
				// In the quirks mode, some results from the regular expression parser are
				// moved by one position, so we must add some dynamics here.
				$attributeCell = 5;
				$endingSlashCell = 6;
				$tagExpression = $this->_rXmlTagExpression;
			}
			else
			{
				$tagExpression = $this->_rQuirksTagExpression;
				$attributeCell = 6;
				$endingSlashCell = 7;
			}

			// Split through the general groups (cdata-content)
			$groups = preg_split($this->_rCDataExpression, $code, 0, PREG_SPLIT_DELIM_CAPTURE);
			$groupCnt = sizeof($groups);
			$groupState = 0;
			Opt_Xml_Cdata::$mode = $mode;
			for($k = 0; $k < $groupCnt; $k++)
			{
				// Process CDATA
				if($groupState == 0 && $groups[$k] == '<![CDATA[')
				{
					$cdata = new Opt_Xml_Cdata('');
					$cdata->set('cdata', true);
					$groupState = 1;
					continue;
				}
				if($groupState == 1)
				{
					if($groups[$k] == ']]>')
					{
						$current = $this->_treeTextAppend($current, $cdata);
						$groupState = 0;
					}
					else
					{
						$cdata->appendData($groups[$k]);
					}
					continue;
				}
				$subgroups = preg_split($this->_rCommentExpression, $groups[$k], 0, PREG_SPLIT_DELIM_CAPTURE);
				$subgroupCnt = sizeof($subgroups);
				$subgroupState = 0;
				for($i = 0; $i < $subgroupCnt; $i++)
				{
					// Process comments
					if($subgroupState == 0 && $subgroups[$i] == '<!--')
					{
						$commentNode = new Opt_Xml_Comment();
						$subgroupState = 1;
						continue;
					}
					if($subgroupState == 1)
					{
						if($subgroups[$i] == '-->')
						{
							$current->appendChild($commentNode);
							$subgroupState = 0;
						}
						else
						{
							$commentNode->appendData($subgroups[$i]);
						}
						continue;
					}
					elseif($subgroups[$i] == '-->')
					{
						throw new Opt_XmlInvalidCharacter_Exception('--&gt;');
					}
					// Find XML tags
					preg_match_all($tagExpression, $subgroups[$i], $result, PREG_SET_ORDER);
					/*
					 * Output field description for $result array:
					 *  0 - original content
					 *  1 - tag content (without delimiters)
					 *  2 - /, if enclosing tag
					 *  3 - name
					 *  4 - arguments (5 in quirks mode)
					 *  5 - /, if enclosing tag without subcontent (6 in quirks mode)
					 */
					
					$resultSize = sizeof($result);
					$offset = 0;
					for($j = 0; $j < $resultSize; $j++)
					{
						// Copy the remaining text to the text node
						$id = strpos($subgroups[$i], $result[$j][0], $offset);
						if($id > $offset)
						{
							$current = $this->_treeTextCompile($current, substr($subgroups[$i], $offset, $id - $offset));
						}
						$offset = $id + strlen($result[$j][0]);
						if(!isset($result[$j][$endingSlashCell]))
						{
							$result[$j][$endingSlashCell] = '';
						}					
						// Process the argument list
						$attributes = array();
						if(!empty($result[$j][$attributeCell]))
						{
							// Just for sure...
							$result[$j][$attributeCell] = trim($result[$j][$attributeCell]);
							$oldLength = strlen($result[$j][$attributeCell]);
							$result[$j][$attributeCell] = rtrim($result[$j][$attributeCell], '/');
							if(strlen($result[$j][$attributeCell]) != $oldLength)
							{
								$result[$j][$endingSlashCell] = '/';
							}
							$attributes = $this->_compileAttributes($result[$j][$attributeCell], $result[$j][4]);
							if(!is_array($attributes))
							{
								throw new Opt_XmlInvalidAttribute_Exception($result[$j][0]);
							}
						}
						// Recognize the tag type
						if(substr_count($result[$j][4], ':') > 1)
						{
							throw new Opt_InvalidNamespace_Exception($result[$j][4]);
						}
						if($result[$j][3] != '/')
						{
							// Opening tag
							$node = new Opt_Xml_Element($result[$j][4]);
							$node->set('single', $result[$j][$endingSlashCell] == '/');
							foreach($attributes as $name => $value)
							{
								$node->addAttribute($anode = new Opt_Xml_Attribute($name, $value));
							}
							$current = $this->_treeNodeAppend($current, $node, $result[$j][$endingSlashCell] != '/');
						}
						elseif($result[$j][3] == '/')
						{
							if(sizeof($attributes) > 0)
							{
								throw new Opt_XmlInvalidTagStructure_Exception($result[$j][0]);
							}
							if($current instanceof Opt_Xml_Element)
							{
								if($current->getXmlName() != $result[$j][4])
								{
									throw new Opt_XmlInvalidOrder_Exception($result[$j][4], $current->getXmlName());
								}
							}
							else
							{
								throw new Opt_XmlInvalidOrder_Exception($result[$j][4], 'NULL');	
							}
							$current = $this->_treeJumpOut($current);
						}
						else
						{
							throw new Opt_XmlInvalidTagStructure_Exception($result[$j][0]);
						}
					}
					if(strlen($subgroups[$i]) > $offset)
					{
						$current = $this->_treeTextCompile($current, substr($subgroups[$i], $offset, strlen($subgroups[$i]) - $offset));
					}
				}
			}
			// Testing if everything was closed.
			if($current !== $tree)
			{
				// Error handling - determine the name of the unclosed tag.
				while(! $current instanceof Opt_Xml_Element)
				{
					$current = $current->getParent();
				}

				throw new Opt_UnclosedTag_Exception($current->getXmlName());
			}

			// Testing the single root node.
			if($mode == Opt_Class::XML_MODE && $this->_tpl->singleRootNode)
			{
				// TODO: The current code does not check the contents of Opt_Text_Nodes and other root elements
				// that may contain invalid and valid XML syntax at the same time.
				// For now, this code is frozen, we'll think a bit about it in the future. Maybe nobody
				// will notice this :)
				$elementFound = false;
				foreach($tree as $item)
				{
					if($item instanceof Opt_Xml_Element)
					{
						if($elementFound)
						{
							// Oops, there is already another root node!
							throw new Opt_XmlRootElement_Exception($item->getXmlName());
						}
						$elementFound = true;
					}
				}
			}
			return $tree;
		} // end _stage1();

		/**
		 * Compilation - stage 2. Traversing through the tree and doing something
		 * with the tree and the nodes. The method is recursion-safe.
		 *
		 * @internal
		 * @param Opt_Xml_Node $node The initial node.
		 */
		protected function _stage2(Opt_Xml_Node $node)
		{
			$queue = new SplQueue;
			$stack = new SplStack;
			$queue->enqueue($node);
			
			while(true)
			{
				$item = NULL;
				if($queue->count() > 0)
				{
					$item = $queue->dequeue();
				}
				$pp = array();

				// We set the "hidden" state unless it is set.
				
				try
				{
					if(is_null($item))
					{
						throw new Opl_Goto_Exception;
					}
					
					$stateSet = false;
					if(is_null($item->get('hidden')))
					{ 
						$item->set('hidden', true);
						$stateSet = true;
					}
	
					// Proper processing
					switch($item->getType())
					{
						case 'Opt_Xml_Cdata':
							$stateSet and $item->set('hidden', false);
							break;
						case 'Opt_Xml_Text':
							$stateSet and $item->set('hidden', false);
							if($item->hasChildren())
							{
								$stack->push(array($item, $queue, $pp));
								$pp = NULL;
								$queue = new SplQueue;
								foreach($item as $child)
								{
									$queue->enqueue($child);
								}
								continue 2;
							}
							break;
						case 'Opt_Xml_Element':
							if($this->isNamespace($item->getNamespace()))
							{
								$name = $item->getXmlName();
								$pp = $this->_processXml($item, false);

								// Look for the processor
								if(!is_null($processor = $this->isInstruction($name)))
								{
									$processor->processNode($item);
								}
								elseif($this->isComponent($name))
								{
									$processor = $this->processor('component');
									$processor->processComponent($item);
								}
								elseif($this->isBlock($name))
								{
									$processor = $this->processor('block');
									$processor->processBlock($item);
								}
								
								if(is_object($processor))
								{									
									$stateSet and $item->set('hidden', false);
									$result = $processor->getQueue();
									if(!is_null($result))
									{
										$stack->push(array($item, $queue, $pp));
										$queue = $result;
										continue 2;
									}
								}
								elseif($item->get('processAll'))
								{
									$stateSet and $item->set('hidden', false);
									$stack->push(array($item, $queue, $pp));
									$pp = NULL;
									$queue = new SplQueue;
									foreach($item as $child)
									{
										$queue->enqueue($child);
									}
									continue 2;
								}
								unset($processor);
							}
							else
							{
								$pp = $this->_processXml($item, true);
								$stateSet and $item->set('hidden', false);
								if($item->hasChildren())
								{
									$stack->push(array($item, $queue, $pp));
									$pp = NULL;
									$queue = new SplQueue;
									foreach($item as $child)
									{
										$queue->enqueue($child);
									}
									continue 2;
								}
							}
							break;
						case 'Opt_Xml_Expression':
							$stateSet and $item->set('hidden', false);
							// Empty expressions will be caught by the try... catch.
							try
							{
								$result = $this->compileExpression((string)$item, true);
								if(!$result[1])
								{
									$item->addAfter(Opt_Xml_Buffer::TAG_BEFORE, 'echo '.$result[0].'; ');
								}
								else
								{
									$item->addAfter(Opt_Xml_Buffer::TAG_BEFORE, $result[0].';');
								}
							}
							catch(Opt_EmptyExpression_Exception $e){}
							break;
						case 'Opt_Xml_Root':
							$stateSet and $item->set('hidden', false);
							$queue = $this->_pushQueue($stack, $queue, $item, array());
							break;
						case 'Opt_Xml_Comment':
							if($this->_tpl->printComments)
							{
								$stateSet and $item->set('hidden', false);
							}
							break;
					}
				
				}
				catch(Opl_Goto_Exception $e){}
				$this->_doPostprocess($item, $pp);
				if($queue->count() == 0)
				{
					unset($queue);
					if($stack->count() == 0)
					{
						break;
					}
					list($item, $queue, $pp) = $stack->pop();
					$this->_doPostprocess($item, $pp);
				}
			}
		} // end _stage2();

		/**
		 * Links the tree into a valid XML file with embedded PHP commands
		 * from the tag buffers. The method is recursion-free.
		 *
		 * @internal
		 * @param String &$output Where to store the output.
		 * @param Opt_Xml_Node $node The initial node.
		 */
		protected function _stage3(&$output, Opt_Xml_Node $node)
		{
			// To avoid the recursion, we need a queue and a stack.
			$queue = new SplQueue;
			$stack = new SplStack;
			$queue->enqueue($node);
			
			// Reset the output
			$realOutput = &$output;
			$output = '';
			$wasElement = false;

			// We will be processing the tags in a "infinite" loop
			// In fact, the stop condition can be found within the loop.
			while(true)
			{
				// Obtain the new item to process from the queue.
				$item = NULL;
				if($queue->count() > 0)
				{
					$item = $queue->dequeue();
				}

				// Note that this code uses Opl_Goto_Exception to simulate
				// "goto" instruction.
				try
				{
					// Should we display this node?
					if(is_null($item) || $item->get('hidden') !== false)
					{
						throw new Opl_Goto_Exception;	// Goto postprocess;
					}
					// If the tag has the "commented" flag, we comment it
					// and its content.
					if($item->get('commented'))
					{
						$this->_comments++;
						if($this->_comments == 1)
						{
							$output .= '<!--';
						}
					}
					// If the block is dynamic, then replace the output with some other variable.
					if($item->get('dynamic'))
					{
						$i = sizeof($this->_dynamicBlocks);
						$this->_dynamicBlocks[$i] = '';
						$output = &$this->_dynamicBlocks[$i];
					}

					/* Note that some of the node types must execute some code both before
					 * processing their children and after them. This method processes only
					 * the code executed BEFORE the children are parsed. The latter situation
					 * is implemented in the _doPostlinking() method.
					 */
					$doPostlinking = false;
					switch($item->getType())
					{
						case 'Opt_Xml_Cdata':
							if($item->get('cdata'))
							{
								$output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE).'<![CDATA['.$item.']]>'.$item->buildCode(Opt_Xml_Buffer::TAG_AFTER);
								break;
							}
							$output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE);

							// We strip the white spaces at the linking level.
							if($this->_tpl->stripWhitespaces)
							{
								// The CDATA composed of white characters only is reduced to a single space.
								if(ctype_space((string)$item))
								{
									if($wasElement)
									{
										$output .= ' ';
									}
								}
								else
								{
									// In the opposite case reduce all the groups of the white characters
									// to single spaces in the text.
									if($item->get('noEntitize') === true)
									{
										$output .= preg_replace('/\s\s+/', ' ', (string)$item);
									}
									else
									{
										$output .= $this->parseSpecialChars(preg_replace('/(\s){1,}/', ' ', (string)$item));
									}
								}
							}
							else
							{
								$output .= ($item->get('noEntitize') ? (string)$item : $this->parseSpecialChars($item));
							}
							
							$output .= $item->buildCode(Opt_Xml_Buffer::TAG_AFTER);
							$this->_closeComments($item, $output);
							break;
						case 'Opt_Xml_Text':
							$output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE);
							$queue = $this->_pushQueue($stack, $queue, $item, NULL);
							// Next part in the post-process section
							break;
						case 'Opt_Xml_Element':
							if($this->isNamespace($item->getNamespace()))
							{
								// This code handles the XML elements that represent the
								// OPT instructions. They have shorter code, because
								// we do not need to display their tags.
								if(!$item->hasChildren() && $item->get('single'))
								{
									$output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE, Opt_Xml_Buffer::TAG_SINGLE_BEFORE);
									$doPostlinking = true;
								}
								elseif($item->hasChildren())
								{
									$output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE, Opt_Xml_Buffer::TAG_OPENING_BEFORE,
										Opt_Xml_Buffer::TAG_OPENING_AFTER, Opt_Xml_Buffer::TAG_CONTENT_BEFORE);
									
									$queue = $this->_pushQueue($stack, $queue, $item, NULL);
									// Next part in the post-process section
								}
								else
								{
									$output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE, Opt_Xml_Buffer::TAG_OPENING_BEFORE,
										Opt_Xml_Buffer::TAG_OPENING_AFTER, Opt_Xml_Buffer::TAG_CONTENT_BEFORE);
									$doPostlinking = true;
								}
							}
							else
							{
								$wasElement = true;
								$output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE, Opt_Xml_Buffer::TAG_OPENING_BEFORE);
								if($item->bufferSize(Opt_Xml_Buffer::TAG_NAME) == 0)
								{
									$name = $item->getXmlName();
								}
								elseif($item->bufferSize(Opt_Xml_Buffer::TAG_NAME) == 1)
								{
									$name = $item->buildCode(Opt_Xml_Buffer::TAG_NAME);
								}
								else
								{
									throw new Opt_CompilerCodeBufferConflict_Exception(1, 'TAG_NAME', $item->getXmlName());
								}
								if(!$item->hasChildren() && $item->bufferSize(Opt_Xml_Buffer::TAG_CONTENT) == 0 && $item->get('single'))
								{
									$output .= '<'.$name.$this->_linkAttributes($item).' />'.$item->buildCode(Opt_Xml_Buffer::TAG_SINGLE_AFTER,Opt_Xml_Buffer::TAG_AFTER);
									$item = null;
								}
								else
								{
									$output .= '<'.$name.$this->_linkAttributes($item).'>'.$item->buildCode(Opt_Xml_Buffer::TAG_OPENING_AFTER);
									$item->set('_name', $name);
									if($item->bufferSize(Opt_Xml_Buffer::TAG_CONTENT) > 0)
									{
										$output .= $item->buildCode(Opt_Xml_Buffer::TAG_CONTENT_BEFORE, Opt_Xml_Buffer::TAG_CONTENT, Opt_Xml_Buffer::TAG_CONTENT_AFTER);
									}
									elseif($item->hasChildren())
									{
										$output .= $item->buildCode(Opt_Xml_Buffer::TAG_CONTENT_BEFORE);
										$queue = $this->_pushQueue($stack, $queue, $item, NULL);
										// Next part in the post-process section
										break;
									}
									else
									{
										// The postlinking is already done here, so skip this part
										// in the linker
										$item->set('_skip_postlinking', true);
										$output .= $item->buildCode(Opt_Xml_Buffer::TAG_CLOSING_BEFORE).'</'.$name.'>'.$item->buildCode(Opt_Xml_Buffer::TAG_CLOSING_AFTER, Opt_Xml_Buffer::TAG_AFTER);
									}
								}
							}
							break;
						case 'Opt_Xml_Expression':
							$output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE);
							$this->_closeComments($item, $output);
							break;
						case 'Opt_Xml_Root':
							$output .= $item->buildCode(Opt_Xml_Buffer::TAG_BEFORE);

							// Display the prolog and DTD, if it was set in the node.
							// Such construct ensures us that they will appear in the
							// valid place in the output document.
							if($item->hasProlog())
							{
								$output .= str_replace('<?xml', '<<?php echo \'?\'; ?>xml', $item->getProlog()->getProlog())."\r\n";
							}
							if($item->hasDtd())
							{
								$output .= $item->getDtd()->getDoctype()."\r\n";
							}

							// And go to their children.
							$queue = $this->_pushQueue($stack, $queue, $item, NULL);
							break;
						case 'Opt_Xml_Comment':
							// The comment tags are added automatically.
							$output .= (string)$item;
							$this->_closeComments($item, $output);
							break;
					}
				}
				catch(Opl_Goto_Exception $goto){}	// postprocess:
				if($doPostlinking)
				{
					$output .= $this->_doPostlinking($item);
					if(is_object($item) && $item->get('dynamic'))
					{
						$realOutput .= $output;
						$output = &$realOutput;
					}
				}

				if($queue->count() == 0)
				{
					if($stack->count() == 0)
					{
						break;
					}
					/**
					 * !!!!!!!!!!!!THIS IS WRONG!!!!!!!!!!!!!!!!
					 *
					 * Some items get this called only if they are
					 * the last in the queue.
					 */
					if(!$doPostlinking)
					{
						$output .= $this->_doPostlinking($item);
						if(is_object($item) && $item->get('dynamic'))
						{
							$realOutput .= $output;
							$output = &$realOutput;
						}
					}
					
					list($item, $queue, $pp) = $stack->pop();
					$output .= $this->_doPostlinking($item);
					if($item->get('dynamic'))
					{
						$realOutput .= $output;
						$output = &$realOutput;
					}
				}
			}

			if($this->_tpl->stripWhitespaces)
			{
				$output = rtrim($output);
			}
		} // end _stage3();
		
		/*
		 * Expression compiler
		 */

		/**
		 * Compiles the template expression to the PHP code and checks the syntax
		 * errors. The method is recursion-free.
		 *
		 * @param String $expr The expression
		 * @param Boolean $allowAssignment=false True, if the assignments are allowed.
		 * @param Int $escape=self::ESCAPE_ON The HTML escaping policy for this expression.
		 * @return Array An array consisting of four elements: the compiled expression,
		 *   the assignment status and the variable status (if the expression is in fact
		 *   a single variable). If the escaping is controlled by the template or the
		 *   script, the fourth element contains also an unescaped PHP expression.
		 */
		public function compileExpression($expr, $allowAssignment = false, $escape = self::ESCAPE_ON)
		{
			// The expression modifier must not be tokenized, so we
			// capture it before doing anything with the expression.
			$modifier = '';
			if(preg_match('/^([^\'])\:[^\:]/', $expr, $found))
			{
				$modifier = $found[1];

				if($modifier != 'e' && $modifier != 'u')
				{
					throw new Opt_InvalidExpressionModifier_Exception($modifier, $expr);
				}

				$expr = substr($expr, 2, strlen($expr) - 2);
			}

			// cat $expr > /dev/oracle > $result > happy programmer :)
			preg_match_all('/(?:'.
		   			$this->_rSingleQuoteString.'|'.
		   			$this->_rBacktickString.'|'.
					$this->_rHexadecimalNumber.'|'.
					$this->_rDecimalNumber.'|'.
					$this->_rLanguageVar.'|'.
					$this->_rVariable.'|'.
					$this->_rOperators.'|'.
					$this->_rIdentifier.')/x', $expr, $match);
					
			// Skip the whitespaces and create the translation units
			$cnt = sizeof($match[0]);
			$stack = new SplStack;
			$tu = array(0 => array());
			$tuid = 0;
			$maxTuid = 0;
			$prev = '';
			$chr = chr(18);
			$assignments = array();

			/* The translation units allow to avoid recursive compilation of the
			 * expression. Each sub-expression within parentheses and that is a
			 * function call parameter, becomes a separate translation unit. The
			 * loop below scans the array of tokens, looking for translation
			 * unit separators and builds suitable arrays of tokens for each
			 * TU.
			 */
			for($i = 0; $i < $cnt; $i++)
			{
				if(ctype_space($match[0][$i]) || $match[0][$i] == '')
				{
					continue;
				}
				switch($match[0][$i])
				{
					case ',':
						if($prev == '(' || $prev == ',')
						{
							throw new Opt_Expression_Exception('OP_COMMA', $match[0][$i], $expr);
						}
						$tuid = $stack->pop();
						if(in_array($tuid, $assignments))
						{
							$tuid = $stack->pop();
						}
					case '[':
					case '(':
					case 'is':
					case '=':
						if($match[0][$i] == '=' || $match[0][$i] == 'is')
						{
							$assignments[] = $tuid;
						}
						$tu[$tuid][] = $match[0][$i];
						++$maxTuid;
						$tu[$tuid][] = $chr.$maxTuid;	// A fake token that marks the translation unit which goes here.
						$stack->push($tuid);
						$tuid = $maxTuid;
						$tu[$tuid] = array();
						break;
					case ']':
					case ')':
						// If we have a situation like (), we can remove the TU we've just created,
						// because it's empty and will confuse the expression compiler later.
						if($prev == '(')
						{
							unset($tu[$tuid]);
							--$maxTuid;
						}
						if($stack->count() > 0)
						{
							$tuid = $stack->pop();
							if(in_array($tuid, $assignments))
							{
								$tuid = $stack->pop();
							}
						}
						if($prev == '(')
						{
							array_pop($tu[$tuid]);
						}
						if($prev == ',')
						{
							throw new Opt_Expression_Exception('OP_BRACKET', $match[0][$i], $expr);
						}
						$tu[$tuid][] = $match[0][$i];
						break;
					default:
						$tu[$tuid][] = $match[0][$i];
				}
				$prev = $match[0][$i];
			}
			if(sizeof($tu[0]) == 0)
			{
				throw new Opt_EmptyExpression_Exception();
			}
			/*
			 * Now we have an array of translation units and their tokens and
			 * we can process it linearly, thus avoiding recursive calls.
			 */
			foreach($tu as $id => &$tuItem)
			{
				$tuItem = $this->_compileExpression($expr, $allowAssignment, $tuItem, $id);
			}			
			$assign = $tu[0][1];
			$variable = $tu[0][2];
			
			/*
			 * Finally, we have to link all the subexpressions into an output
			 * expression. We use SPL stack to achieve this, because we need
			 * to store the current subexpression status while finding a new one.
			 */
			$tuid = 0;
			$i = -1;
			$cnt = sizeof($tu[0][0]);
			$stack = new SplStack;
			$prev = null;
			$expression = '';
			
			while(true)
			{
				$i++;
				$token = &$tu[$tuid][0][$i];
				
				// If we've found a translation unit, we must stop for a while the current one
				// and link the new.
				if(strlen($token) > 0 && $token[0] == $chr)
				{
					$wasAssignment = in_array($tuid, $assignments);
					$stack->push(Array($tuid, $i, $cnt));
					$tuid = (int)ltrim($token, $chr);
					$i = -1;
					$cnt = sizeof($tu[$tuid][0]);
					if($cnt == 0 && $wasAssignment)
					{
						throw new Opt_Expression_Exception('OP_NULL', '', $expr);
					}
					continue;				
				}
				else
				{
					$expression .= $token;
				}
			
				if($i >= $cnt)
				{
					if($stack->count() == 0)
					{
						break;
					}
					// OK, current TU is ready. Check, whether there are unfinished upper-level TUs
					// on the stack
					unset($tu[$tuid]);
					list($tuid, $i, $cnt) = $stack->pop();
				}
				$prev = $token;
			}
			
			/*
			 * Now it's time to apply the escaping policy to this expression. We check
			 * the expression for the "e:" and "u:" modifiers and redirect the task to
			 * the escape() method.
			 */
			$result = $expression;
			if($escape != self::ESCAPE_OFF && !$assign)
			{
				if($modifier != '')
				{
					$result = $this->escape($result, $modifier == 'e');
				}
				else
				{
					$result = $this->escape($result);
				}
			}
			// Pack everything
			if($escape != self::ESCAPE_BOTH)
			{
				return array(0 => $result, $assign, $variable, NULL);
			}
			else
			{
				return array(0 => $result, $assign, $variable, $expression);
			}
		} // end compileExpression();

		/**
		 * Compiles a single translation unit in the expression.
		 *
		 * @internal
		 * @param String &$expr A reference to the compiled expressions for debug purposes.
		 * @param Boolean $allowAssignment True, if the assignments are allowed in this unit.
		 * @param Array &$tokens A reference to the array of tokens for this translation unit.
		 * @param String $tu The number of the current translation unit.
		 * @return Array An array build of three items: the compiled expression, the assignment status
		 *    and the variable status (whether the expression is in fact a single variable).
		 */
		protected function _compileExpression(&$expr, $allowAssignment, Array &$tokens, $tu)
		{
			/* The method processes a single translation unit (TU). For example, in the expression
			 *		$a is ($b + $c) * $d
			 * we have the following translation units:
			 * 1. $a is #TU2 * $d
			 * 2. $b + $c
			 * 
			 * They are compiled separately and automatically, so you do not have to do this on
			 * your own. This has been done to remove the recursion from the source code, and moreover
			 * it allows, for example, to manage the argument order in the functions.
			 */

			// Operator mappings
			$wordOperators = array(
				'eq' => '==',
				'eqt' => '===',
				'ne' => '!=',
				'net' => '!==',
				'neq' => '!=',
				'neqt' => '!==',
				'lt' => '<',
				'le' => '<=',
				'lte' => '<=',
				'gt' => '>',
				'ge' => '>=',
				'gte' => '>=',
				'and' => '&&',
				'or' => '||',
				'xor' => 'xor',
				'not' => '!',
				'mod' => '%',
				'div' => '/',
				'add' => '+',
				'sub' => '-',
				'mul' => '*',
				'shl' => '<<',
				'shr' => '>>'
			);
			
			// Previous token information
			$previous = array(
				'token' => null,
				'source' => null,
				'result' => null
			);
			// Some standard "next token sets"
			$valueSet = self::OP_VARIABLE | self::OP_LANGUAGE_VAR | self::OP_STRING | self::OP_NUMBER |
				self::OP_IDENTIFIER | self::OP_PRE_OPERATOR | self::OP_OBJMAN | self::OP_BRACKET;
			$operatorSet = self::OP_OPERATOR | self::OP_POST_OPERATOR | self::OP_NULL;
			// Initial state
			$state = array(
				'next' => $valueSet | self::OP_NULL,	// What token must occur next.
				'step' => 0,		// This flag helps processing brackets by saving some extra token information.
				'func' => 0,		// The function call type: 0 - OPT function (with "$this" as the first argument); 1 - ordinary function
				'oper' => false,	// The assignment flag. The value must be assigned to a variable, so on the left side there must not be any operator (false).
				'clone' => 0,		// We've already used "clone"
				'preop' => false,	// Prefix operators ++ and -- found. This flag is cancelled by any other operator.
				'rev' => NULL,		// Changing the argument order options
				'assign_func' => false,	// Informing the bracket parser that the first argument must be a language block, which must be processed separately.
				'tu'	=> 0,		// What has opened a translation unit? The field contains the token type.
				'variable' => NULL,	// To detect if the expression is a single variable or not.
				'function' => NULL	// Function name for the argument checker errors
			);
			$chr = chr(18);		// Which ASCII code marks the translation unit
			$result = array();	// Here we put the compilation result
			$void = false;		// This is a fake variable for a recursive call, as a last argument (reference)
			$assign = false;
			$to = sizeof($tokens);

			// Loop through the token list.
			for($i = 0; $i < $to; $i++)
			{
				// Some initializing stuff.
				$token = &$tokens[$i];
				$parsefunc = false;
				$current = array(
					'token' => null,		// Symbolic token type. Look at the file header to find the token definitions.
					'source' => $token,		// Original form of the token is also remembered.
					'result' => null,		// Here we have to put the result PHP code generated from the token.
				);
				// Find out, what it is and process it.
				switch($token)
				{
					case '[':
						// This code checks, whether the token is properly used. We have to assign it to one of the token groups.
						if(!($state['next'] & self::OP_SQ_BRACKET))
						{
							throw new Opt_Expression_Exception('OP_SQ_BRACKET', $token, $expr);
						}
						$result[] = '[';
						$state['tu'] = self::OP_SQ_BRACKET_E;
						$state['next'] = self::OP_TU;
						$state['step'] = self::OP_VARIABLE;
						continue;
					case ']':
						if(!($state['next'] & self::OP_SQ_BRACKET_E))
						{
							throw new Opt_Expression_Exception('OP_SQ_BRACKET_E', $token, $expr);
						}
						$current['token'] = $state['step'];
						$current['result'] = ']';
						$state['step'] = 0;
						// This is the way we mark, what tokens can occur next.
						$state['next'] = self::OP_OPERATOR | self::OP_NULL | self::OP_SQ_BRACKET;
						if($state['clone'] == 1)
						{
							$state['next'] = self::OP_NULL | self::OP_SQ_BRACKET;
						}
						break;
					// These tokens are invalid and must produce an error
					case '\'':
					case '"':
					case '{':
					case '}':
						throw new Opt_Expression_Exception('OP_CURLY_BRACKET', $token, $expr);
						break;
					// Text operators.
					case 'add':
					case 'sub':
					case 'mul':
					case 'div':
					case 'mod':
					case 'shl':
					case 'shr':
					case 'eq':
					case 'neq':
					case 'eqt':
					case 'neqt':
					case 'ne':
					case 'net':
					case 'lt':
					case 'le':
					case 'lte':
					case 'gt':
					case 'gte':
					case 'ge':
						// These guys can be also method names, if in proper context
						if($previous['token'] == self::OP_CALL)
						{
							$this->_compileIdentifier($token, $previous['token'], $previous['result'], 
								isset($tokens[$i+1]) ? $tokens[$i+1] : null, $operatorSet, $expr, $current, $state);
							break;
						}
					case 'and':
					case 'or':
					case 'xor':
						$this->_testPreOperators($previous['token'], $state['preop'], $token, $expr);

						// And these three ones - only strings.		
						if($state['next'] & self::OP_STRING)
						{
							$current['result'] = '\''.$token.'\'';
							$current['token'] = self::OP_STRING;
							$state['next'] = $operatorSet | self::OP_SQ_BRACKET_E;
						}
						else
						{
							if(!($state['next'] & self::OP_OPERATOR))
							{
								throw new Opt_Expression_Exception('OP_OPERATOR', $token, $expr);
							}
							$current['result'] = $wordOperators[$token];
							$current['token'] = self::OP_OPERATOR;
							$state['next'] = $valueSet;
							$state['preop'] = false;
						}
						$state['variable'] = false;
						break;
					case 'not':
						if(!($state['next'] & self::OP_PRE_OPERATOR))
						{
							throw new Opt_Expression_Exception('OP_PRE_OPERATOR', $token, $expr);
						}
						$current['token'] = self::OP_PRE_OPERATOR;
						$current['result'] = $wordOperators[$token];
						$state['next'] = $valueSet;
						$state['variable'] = false;					
						break;
					case 'new':
					case 'clone':
						// These operators are active only if the directive advancedOOP is true.
						if(!$this->_tpl->advancedOOP)
						{
							throw new Opt_ExpressionOptionDisabled_Exception($token, 'security reasons');
						}
						if(!($state['next'] & self::OP_OBJMAN))
						{
							throw new Opt_Expression_Exception('OP_OBJMAN', $token, $expr);
						}
						$current['result'] = $token.' ';
						$current['token'] = self::OP_OBJMAN;
						$state['next'] = ($token == 'new' ? self::OP_IDENTIFIER : self::OP_BLOCK);
						$state['clone'] = 1;
						$state['variable'] = false;
						break;
					case 'is':
						if($state['next'] & self::OP_STRING)
						{
							$current['result'] = '\''.$token.'\'';
							$state['next'] = $operatorSet | self::OP_SQ_BRACKET_E | self::OP_TU;
							break;
						}
					case '=':
						if(!$allowAssignment)
						{
							throw new Opt_ExpressionOptionDisabled_Exception('Assignments', 'compiler requirements');
						}
						// We have to assign the data to the variable or object field.
						if(($previous['token'] == self::OP_VARIABLE || $previous['token'] == self::OP_FIELD) && !$state['oper'] && $previous['token'] != self::OP_LANGUAGE_VAR)
						{
							$current['result'] = '';
							$current['token'] = self::OP_ASSIGN;
							$state['variable'] = false;
							$state['next'] = self::OP_TU;
							$state['tu'] = self::OP_NULL;
							$assign = true;
						}
						else
						{
							throw new Opt_Expression_Exception('OP_ASSIGN', $token, $expr);
						}
						break;
					case '!==':
					case '==':
					case '===':
					case '!=':
					case '+':
					case '*':
					case '/':
					case '%':						
						if(!($state['next'] & self::OP_OPERATOR))
						{
							throw new Opt_Expression_Exception('OP_OPERATOR', $token, $expr);
						}
						$this->_testPreOperators($previous['token'], $state['preop'], $token, $expr);
						
						$current['result'] = $token;
						$state['next'] = $valueSet;
						$state['oper'] = true;
						$state['preop'] = false;
						$state['variable'] = false;
						break;
					case '-':
						if($state['next'] & self::OP_OPERATOR)
						{
							$this->_testPreOperators($previous['token'], $state['preop'], $token, $expr);
							
							$current['result'] = $token;
							$state['oper'] = true;
							$state['next'] = $valueSet;
							$state['preop'] = false;
						}
						elseif($state['next'] & self::OP_NUMBER | self::OP_VARIABLE | self::OP_IDENTIFIER)
						{
							$current['result'] = $token;
							$state['next'] = self::OP_NUMBER | self::OP_VARIABLE | self::OP_IDENTIFIER;
						}
						else
						{
							throw new Opt_Expression_Exception('OP_OPERATOR', $token, $expr);
						}
						$state['variable'] = false;
						break;
					case '~':
						if(!($state['next'] & self::OP_OPERATOR))
						{
							throw new Opt_Expression_Exception('OP_OPERATOR', $token, $expr);
						}
						$current['result'] = '.';
						$state['next'] = $valueSet;
						$state['oper'] = true;
						$state['preop'] = false;
						$state['variable'] = false;
						break;
					case '++':
					case '--':
						$current['token'] = self::OP_PRE_OPERATOR;
						if(!($state['next'] & self::OP_PRE_OPERATOR))
						{
							$current['token'] = self::OP_POST_OPERATOR;
							if(!($state['next'] & self::OP_POST_OPERATOR))
							{							
								throw new Opt_Expression_Exception('OP_POST_OPERATOR', $token, $expr);
							}
							else
							{
								$state['next'] = self::OP_OPERATOR | self::OP_NULL;
							}
						}
						else
						{
							$state['next'] = self::OP_VARIABLE | self::OP_LANGUAGE_VAR | self::OP_NUMBER;
							$state['preop'] = true;
						}
						$state['oper'] = true;
						$state['variable'] = false;
						$current['result'] = $token;
						break;
					case '!':
						if(!($state['next'] & self::OP_PRE_OPERATOR))
						{
							throw new Opt_Expression_Exception('OP_PRE_OPERATOR', $token, $expr);
						}
						$current['result'] = $token;
						$current['token'] = self::OP_PRE_OPERATOR;
						$state['variable'] = false;
						break;
					case 'null':
					case 'false':
					case 'true':
						// These special values are treated as numbers by the compiler.
						if(!($state['next'] & self::OP_NUMBER))
						{
							throw new Opt_Expression_Exception('OP_NUMBER', $token, $expr);
						}
						$current['token'] = self::OP_NUMBER;
						$current['result'] = $token;
						$state['next'] = $operatorSet;
						break;
					case '.':
						throw new Opt_Expression_Exception('.', $token, $expr);
						break;
					case '::':
						if(!($state['next'] & self::OP_CALL))
						{
							throw new Opt_Expression_Exception('OP_CALL', $token, $expr);
						}
						if(!$this->_tpl->basicOOP)
						{
							throw new Opt_NotSupported_Exception('object-oriented programming', 'disabled');
						}
						// OPT decides from the context, whether "::" means a static
						// or dynamic call.
						if($previous['token'] == self::OP_CLASS)
						{
							$current['result'] = '::';
							$state['call'] = 0;
						}
						else
						{
							$current['result'] = '->';
						}
						$current['token'] = self::OP_CALL;
						$state['next'] = self::OP_IDENTIFIER;
						break;
					case '(':
						// Check, if the parenhesis begins a function/method argument list
						if($previous['token'] == self::OP_METHOD || $previous['token'] == self::OP_FUNCTION || $previous['token'] == self::OP_CLASS)
						{
							// Yes, this is a function call, so we need to find its arguments.
							$args = array();
							for($j = $i + 1; $j < $to && $tokens[$j] != ')'; $j++)
							{								
								if($tokens[$j][0] == $chr)
								{
									$args[] = $tokens[$j];
								}
								elseif($tokens[$j] != ',')
								{
									throw new Opt_Expression_Exception('OP_UNKNOWN', $tokens[$j], $expr);	
								}
							}
							$argNum = sizeof($args);
							
							// Optionally, change the argument order
							if(!is_null($state['rev']))
							{
								$this->_reverseArgs($args, $state['rev'], $state['function']);
								$state['rev'] = null;
								$argNum = sizeof($args);
							}

							// Put the parenthesis to the compiled token list.
							$result[] = '(';

							// If we have a call of the assign() function, we need to store the
							// number of the translation unit in the _translationConversion field.
							// This will allow the language variable compiler to notice that here
							// we should have a language call that must be treated in a bit different
							// way.
							if($argNum > 0 && $state['assign_func'])
							{
								$this->_translationConversion = (int)trim($args[0], $chr);
							}
							// Build the argument list.
							for($k = 0; $k < $argNum; $k++)
							{
								$result[] = $args[$k];
								if($k < $argNum - 1)
								{
									$result[] = ',';
								}
							}
							$i = $j-1;
							$state['next'] = self::OP_BRACKET_E;
							$state['step'] = $previous['token'];
							continue;
						}
						else
						{
							if(!($state['next'] & self::OP_BRACKET))
							{
								throw new Opt_Expression_Exception('OP_BRACKET', $token, $expr);
							}
							$result[] = '(';
							$state['tu'] = self::OP_BRACKET_E;
							$state['next'] = self::OP_TU;
							$state['step'] = self::OP_VARIABLE;
						}
						break;
					case ')':
						if($state['step'] == 0)
						{
							throw new Opt_Expression_Exception('OP_BRACKET', $token, $expr);
						}
						else
						{
							if(!($state['next'] & self::OP_BRACKET_E))
							{
								throw new Opt_Expression_Exception('OP_BRACKET_E', $token, $expr);
							}
							$current['token'] = $state['step'];
							$current['result'] = ')';
							$state['step'] = 0;
							$state['next'] = self::OP_OPERATOR | self::OP_NULL | self::OP_CALL;
							if($state['clone'] == 1)
							{
								$state['next'] = self::OP_NULL | self::OP_CALL;
							}
						}
						break;
					default:	
						if($token[0] == $chr)
						{
							// We've found another translation unit.
							if(!($state['next'] & self::OP_TU))
							{
								throw new Opt_Expression_Exception('OP_TU', 'Translation unit #'.ltrim($token, $chr), $expr);
							}
							if($previous['token'] != self::OP_ASSIGN)
							{
								$result[] = $token;
							}
							$state['next'] = $state['tu'];
						}
						elseif(preg_match('/^'.$this->_rVariable.'$/', $token))
						{
							// Variable call.
							if(!($state['next'] & self::OP_VARIABLE))
							{
								throw new Opt_Expression_Exception('OP_VARIABLE', $token, $expr);
							}
							// We do the first character test manually, because
							// in regular expression the parser would receive too much rubbish.
							if(!ctype_alpha($token[1]) && $token[1] != '_')
							{
								throw new Opt_Expression_Exception('OP_VARIABLE', $token, $expr);
							}
							// Moreover, we need to know the future (assignments)
							$assignment = null;
							if(isset($tokens[$i+1]) && ($tokens[$i+1] == '=' || $tokens[$i+1] == 'is'))
							{
								$assignment = $tokens[$i+2];
							}

							$out = $this->_compileVariable($token, $assignment);
							if(is_array($out))
							{
								foreach($out as $t)
								{
									$result[] = $t;
								}
								$current['result'] = '';
								$current['token'] = self::OP_VARIABLE;
							}
							else
							{
								$current['result'] = $out;
								$current['token'] = self::OP_VARIABLE;
							}
							if(is_null($state['variable']))
							{
								$state['variable'] = true;
							}
							// Hmmm... and what is the purpose of this IF? Seriously, I forgot.
							// So better do not touch it; it must have been very important.
							if($state['clone'] == 1)
							{
								$state['next'] = self::OP_SQ_BRACKET | self::OP_CALL | self::OP_NULL;
							}
							else
							{
								$state['next'] = $operatorSet | self::OP_SQ_BRACKET | self::OP_CALL;
							}
						}
						elseif(preg_match('/^'.$this->_rLanguageVarExtract.'$/', $token, $found))
						{
							// Extracting the language var.
							if(!($state['next'] & self::OP_LANGUAGE_VAR))
							{
								throw new Opt_Expression_Exception('OP_LANGUAGE_VAR', $token, $expr);
							}
							$current['result'] = $this->_compileLanguageVar($found[1], $found[2], $tu);
							$current['token'] = self::OP_LANGUAGE_VAR;
							$state['next'] = $operatorSet;
						}
						elseif(preg_match('/^'.$this->_rDecimalNumber.'$/', $token))
						{
							// Handling the decimal numbers.
							if(!($state['next'] & self::OP_NUMBER))
							{
								throw new Opt_Expression_Exception('OP_NUMBER', $token, $expr);
							}
							$current['result'] = $token;
							$state['next'] = $operatorSet | self::OP_SQ_BRACKET_E;
						}
						elseif(preg_match('/^'.$this->_rHexadecimalNumber.'$/', $token))
						{
							// Hexadecimal, too.
							if(!($state['next'] & self::OP_NUMBER))
							{
								throw new Opt_Expression_Exception('OP_NUMBER', $token, $expr);
							}
							$current['result'] = $token;
							$state['next'] = $operatorSet | self::OP_SQ_BRACKET_E;
						}
						elseif(preg_match('/^'.$this->_rSingleQuoteString.'$/', $token))
						{
							if(!($state['next'] & self::OP_STRING))
							{
								throw new Opt_Expression_Exception('OP_STRING', $token, $expr);
							}
							$current['result'] = $this->_compileString($token);
							$state['next'] = $operatorSet | self::OP_SQ_BRACKET_E;
						}
						elseif(preg_match('/^'.$this->_rBacktickString.'$/', $token))
						{
							if(!($state['next'] & self::OP_STRING))
							{
								throw new Opt_Expression_Exception('OP_STRING', $token, $expr);
							}
							$current['result'] = $this->_compileString($token);
							$state['next'] = $operatorSet | self::OP_SQ_BRACKET_E;
						}
						elseif(preg_match('/^'.$this->_rIdentifier.'$/', $token))
						{
							$this->_compileIdentifier($token, $previous['token'], $previous['result'], 
								isset($tokens[$i+1]) ? $tokens[$i+1] : null, $operatorSet, $expr, $current, $state);
						}
				}
				$previous = $current;
				if($current['result'] != '')
				{
					$result[] = $current['result'];
				}
			}
			// Finally, test if the pre- operators have been used properly.
			$this->_testPreOperators($previous['token'], $state['preop'], $token, $expr);

			// And if we are allowed to finish here...
			if(!($state['next'] & self::OP_NULL))
			{
				throw new Opt_Expression_Exception('OP_NULL', $token, $expr);
			}
			// TODO: For variable detection: check also class/object fields!
			return array($result, $assign, $state['variable']);
		} // end _compileExpression();

		/**
		 * An utility function that allows to test the preincrementation
		 * operators, if they are used in the right place. In case of
		 * problems, it generates an exception.
		 *
		 * @internal
		 * @param Int $previous The previous token type.
		 * @param Boolean $state The state of the "preop" expression parser flag.
		 * @param String $token The current token provided for debug purposes.
		 * @param String &$expr The reference to the parsed expression for debug purposes.
		 */
		protected function _testPreOperators($previous, $state, &$token, &$expr)
		{
			if(($previous == self::OP_METHOD || $previous == self::OP_FUNCTION || $previous == self::OP_EXPRESSION) && $state)
			{
				// Invalid use of prefix operators!
				throw new Opt_Expression_Exception('OP_PRE_OPERATOR', $token, $expr);
			}
		} // end _testPreOperators();

		/**
		 * Compiles the template variable into the PHP code. It can be
		 * generated in two contexts: read and save. The method supports
		 * all the special variables, local template variables and
		 * chooses the correct data formats. Moreover, it provides a
		 * build-in support for sections.
		 *
		 * @internal
		 * @param String $name Variable call
		 * @param String $newValue Null or the new value to assign
		 * @return String The output PHP code.
		 */
		protected function _compileVariable($name, $saveContext = null)
		{
			$value = substr($name, 1, strlen($name) - 1);
			$result = '';
			if(strpos($value, '.') !== FALSE)
			{
				$ns = explode('.', $value);
			}
			else
			{
				$ns = array(0 => $value);
			}
			
			if($name[0] == '@')
			{
				// The instruction may wish to handle this variable somehow differently.
				if(($to = $this->convert('##var_'.$ns[0])) == '##var_'.$ns[0])
				{
					$result = 'self::$_vars';	// Standard handler
				}
				else
				{
					$result = $to;		// Programmer-defined handler
					unset($ns[0]);		// We assume that the variable name is already included into the handler.
				}
				
				// Link the rest of the array call.
				foreach($ns as $item)
				{
					if(ctype_digit($item))
					{
						$result .= '['.$item.']';
					}
					else
					{
						$result .= '[\''.$item.'\']';
					}
				}
				if($saveContext !== null)
				{
					return array($result.'=', $saveContext);
				}
				return $result;
			}
			else
			{
				/*
				 * This is the variable scanner that parses things like "$var.foo.bar.joe".
				 * Each segment of the name can be parsed in different format, depending on
				 * the programmer settings. Moreover, it recognizes the special calls, like "opt"/"system"
				 * or section element calls.
				 */
			
				$path = '';
				$previous = null;
				$code = '';
				$count = sizeof($ns);
				$state = array(
					'access' => $this->_tpl->variableAccess,
					'section' => null,
					'first' => false	
				);

				// Check the first element for special keywords.
				switch($ns[0])
				{
					case 'opt':
					case 'sys':
					case 'system':
						if($saveContext !== null)
						{
							throw new Opt_AssignNotSupported_Exception($name);
						}
						return $this->_compileSys($ns);
					case 'this':
						$state['access'] = Opt_Class::ACCESS_LOCAL;
						unset($ns[0]);
						break;
					case 'global':
						$state['access'] = Opt_Class::ACCESS_GLOBAL;
						unset($ns[0]);
						break;
				}
				// Scan the rest of the name
				$final = sizeof($ns) - 1;
				foreach($ns as $id => $item)
				{
					$previous = $path;
					if($path == '')
					{
						// Parsing the first element. First, check the conversions.
						if(($to = $this->convert('##simplevar_'.$item)) != '##simplevar_'.$item)
						{
							$item = $to;
						}
						$path = $item;
						$state['first'] = true;
					}
					else
					{
						// Parsing one of the later elements
						$path .= '.'.$item;
						$state['first'] = false;
					}
					
					// Processing sections
					if(!is_null($this->isProcessor('section')))
					{
						if(is_null($state['section']))
						{
							// Check if any section with the specified name exists.
							$proc = $this->processor('section');
							$sectionName = $this->convert($item);
							if(!is_null($section = $proc->getSection($sectionName)))
							{
								$path = $sectionName;
								$state['section'] = $section;

								if($id == $count - 1)
								{
									// This is the last name element.
									if($saveContext !== null)
									{
										if(!$section['format']->property('section:itemAssign'))
										{
											throw new Opt_AssignNotSupported_Exception($name);
										}
										$format->assign('value', $saveContext);
										return $section['format']->get('section:itemAssign');
									}

									return $section['format']->get('section:item');
								}
								continue;
							}
						}
						else
						{
							// The section has been found, we need to process the item.
							$state['section']['format']->assign('item', $item);

							if($saveContext !== null && $id == $final)
							{
								if(!$state['section']['format']->property('section:variableAssign'))
								{
									throw new Opt_AssignNotSupported_Exception($name);
								}
								$state['section']['format']->assign('value', $saveContext);
								$code = $state['section']['format']->get('section:variableAssign');
							}
							else
							{
								$code = $state['section']['format']->get('section:variable');
							}
							$state['section'] = null;
							continue;
						}
					}
					
					// Now, the normal variables
					if($state['first'])
					{
						if($state['access'] == Opt_Class::ACCESS_GLOBAL)
						{
							$format = $this->getFormat('global.'.$path, true);
						}
						else
						{
							$format = $this->getFormat($path, true);
						}
						if(!$format->supports('variable'))
						{
							throw new Opt_FormatNotSupported_Exception($format->getName(), 'variable');
						}

						$format->assign('access', $state['access']);
						if($format->property('variable:captureAll'))
						{
							// With this property, the data format may capture
							// the whole namespace and process it in a more complex
							// way
							$format->assign('items', $ns);
							if($saveContext !== null)
							{
								if(!$format->property('variable:assign'))
								{
									throw new Opt_AssignNotSupported_Exception($name);
								}
								$format->assign('value', $saveContext);
								$code = $format->get('variable:captureAssign');
							}
							else
							{								
								$code = $format->get('variable:capture');
							}
							break;
						}
						else
						{
							// An ordinary call - the format captures only
							// the first item, the others are processed
							// by different "item" formats.
							$format->assign('item', $item);
							if($final == $id && $saveContext !== null)
							{
								if(!$format->property('variable:assign'))
								{
									throw new Opt_AssignNotSupported_Exception($name);
								}								
								$format->assign('value', $saveContext);
								$code = $format->get('variable:assign');
							}
							else
							{
								$code = $format->get('variable:main');
							}
						}
					}
					else
					{
						// The subitems are processed with the upper-item format
						if($state['access'] == Opt_Class::ACCESS_GLOBAL)
						{
							$format = $this->getFormat('global.'.$previous, true);
						}
						else
						{
							$format = $this->getFormat($previous, true);
						}
						if(!$format->supports('item'))
						{
							throw new Opt_FormatNotSupported_Exception($format->getName(), 'item');
						}
						if($final == $id && $saveContext !== null)
						{
							if(!$format->property('item:assign'))
							{
								throw new Opt_AssignNotSupported_Exception($name);
							}
							$format->assign('item', $item);
							$format->assign('value', $saveContext);
							$code .= $format->get('item:assign');
						}
						else
						{
							$format->assign('item', $item);
							$code .= $format->get('item:item');
						}
					}
				}
				if($saveContext !== null)
				{
					$out = explode($saveContext, $code);
					if(sizeof($out) != 2)
					{
						return $code;
					}
					return array(0 => $out[0], $saveContext, $out[1]);
				}
				return $code;
			}
		} // end _compileVariable();

		/**
		 * Compiles the call to the language variable into the PHP code.
		 *
		 * @param String $group Group name
		 * @param String $id Message identifier name within a group
		 * @param String $tu The ID of the current translation unit for handling the assign() function properly.
		 * @return String The output PHP code.
		 */
		protected function _compileLanguageVar($group, $id, $tu)
		{
			if(is_null($this->_tf))
			{
				throw new Opl_NoTranslationInterface_Exception('OPT template compiler');
			}
			if($tu === $this->_translationConversion)
			{
				$this->_translationConversion = null;
				return '\''.$group.'\',\''.$id.'\'';
			}
			return '$this->_tf->_(\''.$group.'\',\''.$id.'\')';
		} // end _compileLanguageVar();

		/**
		 * Compiles the special $sys variable to PHP code.
		 *
		 * @param Array $ns The $sys call splitted into array.
		 * @return String The output PHP code.
		 */
		protected function _compileSys(Array $ns)
		{
			switch($ns[1])
			{
				case 'version':
					return '\''.Opt_Class::VERSION.'\'';
				case 'const':
					return 'constant(\''.$ns[2].'\')';				
				default:
					if(!is_null($this->isProcessor($ns[1])))
					{
						return $this->processor($ns[1])->processSystemVar($ns);
					}
					
					throw new Opt_SysVariableUnknown_Exception('$'.implode('.', $ns));
			}
		} // end _compileSys();

		/**
		 * Compiles the string call in the expression to a suitable PHP source code.
		 *
		 * @internal
		 * @param String $str The "string" string (with the delimiting characters)
		 * @return String The output PHP code.
		 */
		protected function _compileString($str)
		{
			// TODO: Fix
			// COMMENT: Fix what?
			switch($str[0])
			{
				case '\'':
					return $str;
				case '`':
					if(is_null($this->_tpl->backticks))
					{
						throw new Opt_NotSupported_Exception('backticks', 'not configured');
					}
					elseif(is_string($this->_tpl->backticks))
					{
						// A redirect to a function		
						return $this->_tpl->backticks.'(\''.str_replace('\'', '\\\'', stripslashes(substr($str, 1, strlen($str) - 2))).'\')';
					}
					elseif(is_array($this->_tpl->backticks) && is_object($this->_tpl->backticks[0]))
					{
						// A redirect to an object method
			
						return '$this->_tpl->backticks[0]->'.$this->_tpl->backticks[1].'(\''.str_replace('\'', '\\\'', stripslashes(substr($str, 1, strlen($str) - 2))).'\')';
					}
					else
					{
						throw new Opt_InvalidCallback_Exception('backticks');
					}
				default:
					return '\''.$str.'\'';
			}
		} // end _compileString();

		/**
		 * Compiles the specified identifier encountered in the expression
		 * to the PHP code.
		 *
		 * @internal
		 * @param String $token The encountered token.
		 * @param Int $previous Previous token
		 * @param String $pt Used for OOP parsing to determine whether we have a static call.
		 * @param String $next The next token in the list
		 * @param Int $operatorSet The flag of allowed opcodes at this position.
		 * @param String &$expr The current expression (for debug purposes)
		 * @param Array &$current Reference to the current token information
		 * @param Array &$state Reference to the parser state flags.
		 */
		protected function _compileIdentifier($token, $previous, $pt, $next, $operatorSet, &$expr, &$current, &$state)
		{
			if($previous == self::OP_OBJMAN)
			{
				// Class constructor call
				if(isset($this->_classes[$token]) && $this->_tpl->basicOOP)
				{
					$current['result'] = $this->_classes[$token];
					$current['token'] = self::OP_CLASS;
					$state['next'] = self::OP_BRACKET | self::OP_NULL;
					if($next == '(')
					{
						$state['func'] = 1;
					}
				}
				else
				{
					throw new Opt_ItemNotAllowed_Exception('Class', $token);
				}
			}
			elseif($next == '(')
			{
				// Function/method call
				if($previous == self::OP_CALL)
				{
					$current['result'] = $token;
					$current['token'] = self::OP_METHOD;
					$state['next'] = self::OP_BRACKET;
					$state['func'] = 1;
				}
				elseif(isset($this->_functions[$token]))
				{
					$name = $this->_functions[$token];
					if($name[0] == '#')
					{
						$pos = strpos($name, '#', 1);
						if($pos === false)
						{
							throw new Opt_InvalidArgumentFormat_Exception($name, $token);
						}
						$state['rev'] = substr($name, 1, $pos - 1);
						$name = substr($name, $pos+1, strlen($name));
					}
					$current['result'] = $name;
					$current['token'] = self::OP_FUNCTION;
					$state['next'] = self::OP_BRACKET;
					$state['function'] = $token;
				}
				elseif($token == 'assign')
				{
					$current['result'] = '$this->_tf->assign';
					$current['token'] = self::OP_FUNCTION;
					$state['next'] = self::OP_BRACKET;
					$state['assign_func'] = true;
					$state['function'] = $token;
				}
				else
				{
					throw new Opt_ItemNotAllowed_Exception('Function', $token);
				}
			}
			elseif($previous == self::OP_CALL)
			{
				// Class/object field call, check whether static or not.
				$current['result'] = ($pt == '::' ? '$'.$token : $token);
				$current['token'] = self::OP_FIELD;
				$state['next'] = $operatorSet | self::OP_SQ_BRACKET | self::OP_CALL;
				if($state['clone'] == 1)
				{
					$state['next'] = self::OP_SQ_BRACKET | self::OP_CALL | self::OP_NULL;
				}
			}
			elseif($next == '::')
			{
				// Static class call
				if(isset($this->_classes[$token]))
				{
					$current['result'] = $this->_classes[$token];
					$current['token'] = self::OP_CLASS;
					$state['next'] = self::OP_CALL;
				}
				else
				{
					throw new Opt_ItemNotAllowed_Exception('Class', $token);
				}
			}
			else
			{
				// An ending string.
				if(!($state['next'] & self::OP_STRING))
				{
					throw new Opt_Expression_Exception('OP_STRING', $token, $expr);
				}
				$state['next'] = self::OP_NULL;
				$current['token'] = self::OP_STRING;
				$current['result'] = '\''.$token.'\'';
			}
		} // end _compileIdentifier();

		/**
		 * Processes the argument order change functionality for function
		 * parsing in expressions.
		 *
		 * @internal
		 * @param Array &$args Reference to a list of function arguments.
		 * @param String $format The new order format code.
		 * @param String $function The function name provided for debugging purposes.
		 */
		protected function _reverseArgs(&$args, $format, $function)
		{
			$codes = explode(',', $format);
			$newArgs = array();
			$i = 0;
			foreach($codes as $code)
			{
				$data = explode(':', $code);
				if(!isset($args[$i]))
				{
					if(!isset($data[1]))
					{
						throw new Opt_FunctionArgument_Exception($i, $function);
					}
					$newArgs[(int)$data[0]-1] = $data[1];
				}
				else
				{
					$newArgs[(int)$data[0]-1] = $args[$i];
				}
				$i++;
			}
			$args = $newArgs;
		} // end _reverseArgs();

		/**
		 * Smart special character replacement that leaves entities
		 * unmodified. Used by parseSpecialChars().
		 *
		 * @internal
		 * @param Array $text Matching string
		 * @return String Modified text
		 */
		protected function _entitize($text)
		{
			switch($text[0])
			{
				case '&':	return '&amp;';
				case '>':	return '&gt;';
				case '<':	return '&lt;';
				case '"':	return '&quot;';
				default:	return $text[0];
			}
		} // end _entitize();

		/**
		 * Smart entity replacement that makes use of
		 *
		 * @internal
		 * @param Array $text Matching string
		 * @return String Modified text
		 */
		protected function _decodeEntity($text)
		{
			switch($text[1])
			{
				case 'amp':	return '&';
				case 'quot':	return '"';
				case 'lt':	return '<';
				case 'gt':	return '>';
				case 'apos': return "'";
				default:
					
					if(isset($this->_entities[$text[1]]))
					{
						return $this->_entities[$text[1]];
					}
					if($text[1][0] == '#')
					{
						return html_entity_decode($text[0], ENT_COMPAT, $this->_tpl->charset);
					}
					elseif($this->_tpl->htmlEntities && $text[0] != ($result = html_entity_decode($text[0], ENT_COMPAT, $this->_tpl->charset)))
					{
						return $result;
					}
					throw new Opt_UnknownEntity_Exception(htmlspecialchars($text[0]));
			}
		} // end _entitize();
	} // end Opt_Compiler_Class;
Return current item: Open Power Template