Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php/*** TTemplateManager and TTemplate class file** @author Qiang Xue <qiang.xue@gmail.com>* @link http://www.pradosoft.com/* @copyright Copyright © 2005-2008 PradoSoft* @license http://www.pradosoft.com/license/* @version $Id: TTemplateManager.php 2567 2008-11-12 16:53:52Z carlgmathisen $* @package System.Web.UI*//*** Includes TOutputCache class file*/Prado::using('System.Web.UI.WebControls.TOutputCache');/*** TTemplateManager class** TTemplateManager manages the loading and parsing of control templates.** There are two ways of loading a template, either by the associated template* control class name, or the template file name.* The former is via calling {@link getTemplateByClassName}, which tries to* locate the corresponding template file under the directory containing* the class file. The name of the template file is the class name with* the extension '.tpl'. To load a template from a template file path,* call {@link getTemplateByFileName}.** By default, TTemplateManager is registered with {@link TPageService} as the* template manager module that can be accessed via {@link TPageService::getTemplateManager()}.** @author Qiang Xue <qiang.xue@gmail.com>* @version $Id: TTemplateManager.php 2567 2008-11-12 16:53:52Z carlgmathisen $* @package System.Web.UI* @since 3.0*/class TTemplateManager extends TModule{/*** Template file extension*/const TEMPLATE_FILE_EXT='.tpl';/*** Prefix of the cache variable name for storing parsed templates*/const TEMPLATE_CACHE_PREFIX='prado:template:';/*** Initializes the module.* This method is required by IModule and is invoked by application.* It starts output buffer if it is enabled.* @param TXmlElement module configuration*/public function init($config){$this->getService()->setTemplateManager($this);}/*** Loads the template corresponding to the specified class name.* @return ITemplate template for the class name, null if template doesn't exist.*/public function getTemplateByClassName($className){$class=new ReflectionClass($className);$tplFile=dirname($class->getFileName()).DIRECTORY_SEPARATOR.$className.self::TEMPLATE_FILE_EXT;return $this->getTemplateByFileName($tplFile);}/*** Loads the template from the specified file.* @return ITemplate template parsed from the specified file, null if the file doesn't exist.*/public function getTemplateByFileName($fileName){if(($fileName=$this->getLocalizedTemplate($fileName))!==null){Prado::trace("Loading template $fileName",'System.Web.UI.TTemplateManager');if(($cache=$this->getApplication()->getCache())===null)return new TTemplate(file_get_contents($fileName),dirname($fileName),$fileName);else{$array=$cache->get(self::TEMPLATE_CACHE_PREFIX.$fileName);if(is_array($array)){list($template,$timestamps)=$array;if($this->getApplication()->getMode()===TApplicationMode::Performance)return $template;$cacheValid=true;foreach($timestamps as $tplFile=>$timestamp){if(!is_file($tplFile) || filemtime($tplFile)>$timestamp){$cacheValid=false;break;}}if($cacheValid)return $template;}$template=new TTemplate(file_get_contents($fileName),dirname($fileName),$fileName);$includedFiles=$template->getIncludedFiles();$timestamps=array();$timestamps[$fileName]=filemtime($fileName);foreach($includedFiles as $includedFile)$timestamps[$includedFile]=filemtime($includedFile);$cache->set(self::TEMPLATE_CACHE_PREFIX.$fileName,array($template,$timestamps));return $template;}}elsereturn null;}/*** Finds a localized template file.* @param string template file.* @return string|null a localized template file if found, null otherwise.*/protected function getLocalizedTemplate($filename){if(($app=$this->getApplication()->getGlobalization(false))===null)return is_file($filename)?$filename:null;foreach($app->getLocalizedResource($filename) as $file){if(($file=realpath($file))!==false && is_file($file))return $file;}return null;}}/*** TTemplate implements PRADO template parsing logic.* A TTemplate object represents a parsed PRADO control template.* It can instantiate the template as child controls of a specified control.* The template format is like HTML, with the following special tags introduced,* - component tags: a component tag represents the configuration of a component.* The tag name is in the format of com:ComponentType, where ComponentType is the component* class name. Component tags must be well-formed. Attributes of the component tag* are treated as either property initial values, event handler attachment, or regular* tag attributes.* - property tags: property tags are used to set large block of attribute values.* The property tag name is in the format of <prop:AttributeName> where AttributeName* can be a property name, an event name or a regular tag attribute name.* - group subproperty tags: subproperties of a common property can be configured using* <prop:MainProperty SubProperty1="Value1" SubProperty2="Value2" .../>* - directive: directive specifies the property values for the template owner.* It is in the format of <%@ property name-value pairs %>;* - expressions: They are in the formate of <%= PHP expression %> and <%% PHP statements %>* - comments: There are two kinds of comments, regular HTML comments and special template comments.* The former is in the format of <!-- comments -->, which will be treated as text strings.* The latter is in the format of <!-- comments --!>, which will be stripped out.** Tags other than the above are not required to be well-formed.** A TTemplate object represents a parsed PRADO template. To instantiate the template* for a particular control, call {@link instantiateIn($control)}, which* will create and intialize all components specified in the template and* set their parent as $control.** @author Qiang Xue <qiang.xue@gmail.com>* @version $Id: TTemplateManager.php 2567 2008-11-12 16:53:52Z carlgmathisen $* @package System.Web.UI* @since 3.0*/class TTemplate extends TApplicationComponent implements ITemplate{/*** '<!--.*?--!>' - template comments* '<!--.*?-->' - HTML comments* '<\/?com:([\w\.]+)((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?"|\s*[\w\.]+\s*=\s*<%.*?%>)*)\s*\/?>' - component tags* '<\/?prop:([\w\.]+)\s*>' - property tags* '<%@\s*((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?")*)\s*%>' - directives* '<%[%#~\/\\$=\\[](.*?)%>' - expressions* '<prop:([\w\.]+)((?:\s*[\w\.]+=\'.*?\'|\s*[\w\.]+=".*?"|\s*[\w\.]+=<%.*?%>)*)\s*\/>' - group subproperty tags*/const REGEX_RULES='/<!--.*?--!>|<!---.*?--->|<\/?com:([\w\.]+)((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?"|\s*[\w\.]+\s*=\s*<%.*?%>)*)\s*\/?>|<\/?prop:([\w\.]+)\s*>|<%@\s*((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?")*)\s*%>|<%[%#~\/\\$=\\[](.*?)%>|<prop:([\w\.]+)((?:\s*[\w\.]+\s*=\s*\'.*?\'|\s*[\w\.]+\s*=\s*".*?"|\s*[\w\.]+\s*=\s*<%.*?%>)*)\s*\/>/msS';/*** Different configurations of component property/event/attribute*/const CONFIG_DATABIND=0;const CONFIG_EXPRESSION=1;const CONFIG_ASSET=2;const CONFIG_PARAMETER=3;const CONFIG_LOCALIZATION=4;const CONFIG_TEMPLATE=5;/*** @var array list of component tags and strings*/private $_tpl=array();/*** @var array list of directive settings*/private $_directive=array();/*** @var string context path*/private $_contextPath;/*** @var string template file path (if available)*/private $_tplFile=null;/*** @var integer the line number that parsing starts from (internal use)*/private $_startingLine=0;/*** @var string template content to be parsed*/private $_content;/*** @var boolean whether this template is a source template*/private $_sourceTemplate=true;/*** @var string hash code of the template*/private $_hashCode='';private $_tplControl=null;private $_includedFiles=array();private $_includeAtLine=array();private $_includeLines=array();/*** Constructor.* The template will be parsed after construction.* @param string the template string* @param string the template context directory* @param string the template file, null if no file* @param integer the line number that parsing starts from (internal use)* @param boolean whether this template is a source template, i.e., this template is loaded from* some external storage rather than from within another template.*/public function __construct($template,$contextPath,$tplFile=null,$startingLine=0,$sourceTemplate=true){$this->_sourceTemplate=$sourceTemplate;$this->_contextPath=$contextPath;$this->_tplFile=$tplFile;$this->_startingLine=$startingLine;$this->_content=$template;$this->_hashCode=md5($template);$this->parse($template);$this->_content=null; // reset to save memory}/*** @return string template file path if available, null otherwise.*/public function getTemplateFile(){return $this->_tplFile;}/*** @return boolean whether this template is a source template, i.e., this template is loaded from* some external storage rather than from within another template.*/public function getIsSourceTemplate(){return $this->_sourceTemplate;}/*** @return string context directory path*/public function getContextPath(){return $this->_contextPath;}/*** @return array name-value pairs declared in the directive*/public function getDirective(){return $this->_directive;}/*** @return string hash code that can be used to identify the template*/public function getHashCode(){return $this->_hashCode;}/*** @return array the parsed template*/public function &getItems(){return $this->_tpl;}/*** Instantiates the template.* Content in the template will be instantiated as components and text strings* and passed to the specified parent control.* @param TControl the control who owns the template* @param TControl the control who will become the root parent of the controls on the template. If null, it uses the template control.*/public function instantiateIn($tplControl,$parentControl=null){$this->_tplControl=$tplControl;if($parentControl===null)$parentControl=$tplControl;if(($page=$tplControl->getPage())===null)$page=$this->getService()->getRequestedPage();$controls=array();$directChildren=array();foreach($this->_tpl as $key=>$object){if($object[0]===-1)$parent=$parentControl;else if(isset($controls[$object[0]]))$parent=$controls[$object[0]];elsecontinue;if(isset($object[2])) // component{$component=Prado::createComponent($object[1]);$properties=&$object[2];if($component instanceof TControl){if($component instanceof TOutputCache)$component->setCacheKeyPrefix($this->_hashCode.$key);$component->setTemplateControl($tplControl);if(isset($properties['id'])){if(is_array($properties['id']))$properties['id']=$component->evaluateExpression($properties['id'][1]);$tplControl->registerObject($properties['id'],$component);}if(isset($properties['skinid'])){if(is_array($properties['skinid']))$component->setSkinID($component->evaluateExpression($properties['skinid'][1]));else$component->setSkinID($properties['skinid']);unset($properties['skinid']);}$component->trackViewState(false);$component->applyStyleSheetSkin($page);foreach($properties as $name=>$value)$this->configureControl($component,$name,$value);$component->trackViewState(true);if($parent===$parentControl)$directChildren[]=$component;else$component->createdOnTemplate($parent);if($component->getAllowChildControls())$controls[$key]=$component;}else if($component instanceof TComponent){$controls[$key]=$component;if(isset($properties['id'])){if(is_array($properties['id']))$properties['id']=$component->evaluateExpression($properties['id'][1]);$tplControl->registerObject($properties['id'],$component);if(!$component->hasProperty('id'))unset($properties['id']);}foreach($properties as $name=>$value)$this->configureComponent($component,$name,$value);if($parent===$parentControl)$directChildren[]=$component;else$component->createdOnTemplate($parent);}}else{if($object[1] instanceof TCompositeLiteral){// need to clone a new object because the one in template is reused$o=clone $object[1];$o->setContainer($tplControl);if($parent===$parentControl)$directChildren[]=$o;else$parent->addParsedObject($o);}else{if($parent===$parentControl)$directChildren[]=$object[1];else$parent->addParsedObject($object[1]);}}}// delay setting parent till now because the parent may cause// the child to do lifecycle catchup which may cause problem// if the child needs its own child controls.foreach($directChildren as $control){if($control instanceof TComponent)$control->createdOnTemplate($parentControl);else$parentControl->addParsedObject($control);}}/*** Configures a property/event of a control.* @param TControl control to be configured* @param string property name* @param mixed property initial value*/protected function configureControl($control,$name,$value){if(strncasecmp($name,'on',2)===0) // is an event$this->configureEvent($control,$name,$value,$control);else if(($pos=strrpos($name,'.'))===false) // is a simple property or custom attribute$this->configureProperty($control,$name,$value);else // is a subproperty$this->configureSubProperty($control,$name,$value);}/*** Configures a property of a non-control component.* @param TComponent component to be configured* @param string property name* @param mixed property initial value*/protected function configureComponent($component,$name,$value){if(strpos($name,'.')===false) // is a simple property or custom attribute$this->configureProperty($component,$name,$value);else // is a subproperty$this->configureSubProperty($component,$name,$value);}/*** Configures an event for a control.* @param TControl control to be configured* @param string event name* @param string event handler* @param TControl context control*/protected function configureEvent($control,$name,$value,$contextControl){if(strpos($value,'.')===false)$control->attachEventHandler($name,array($contextControl,'TemplateControl.'.$value));else$control->attachEventHandler($name,array($contextControl,$value));}/*** Configures a simple property for a component.* @param TComponent component to be configured* @param string property name* @param mixed property initial value*/protected function configureProperty($component,$name,$value){if(is_array($value)){switch($value[0]){case self::CONFIG_DATABIND:$component->bindProperty($name,$value[1]);break;case self::CONFIG_EXPRESSION:if($component instanceof TControl)$component->autoBindProperty($name,$value[1]);else{$setter='set'.$name;$component->$setter($this->_tplControl->evaluateExpression($value[1]));}break;case self::CONFIG_TEMPLATE:$setter='set'.$name;$component->$setter($value[1]);break;case self::CONFIG_ASSET: // asset URL$setter='set'.$name;$url=$this->publishFilePath($this->_contextPath.DIRECTORY_SEPARATOR.$value[1]);$component->$setter($url);break;case self::CONFIG_PARAMETER: // application parameter$setter='set'.$name;$component->$setter($this->getApplication()->getParameters()->itemAt($value[1]));break;case self::CONFIG_LOCALIZATION:$setter='set'.$name;$component->$setter(Prado::localize($value[1]));break;default: // an error if reaching herethrow new TConfigurationException('template_tag_unexpected',$name,$value[1]);break;}}else{$setter='set'.$name;$component->$setter($value);}}/*** Configures a subproperty for a component.* @param TComponent component to be configured* @param string subproperty name* @param mixed subproperty initial value*/protected function configureSubProperty($component,$name,$value){if(is_array($value)){switch($value[0]){case self::CONFIG_DATABIND: // databinding$component->bindProperty($name,$value[1]);break;case self::CONFIG_EXPRESSION: // expressionif($component instanceof TControl)$component->autoBindProperty($name,$value[1]);else$component->setSubProperty($name,$this->_tplControl->evaluateExpression($value[1]));break;case self::CONFIG_TEMPLATE:$component->setSubProperty($name,$value[1]);break;case self::CONFIG_ASSET: // asset URL$url=$this->publishFilePath($this->_contextPath.DIRECTORY_SEPARATOR.$value[1]);$component->setSubProperty($name,$url);break;case self::CONFIG_PARAMETER: // application parameter$component->setSubProperty($name,$this->getApplication()->getParameters()->itemAt($value[1]));break;case self::CONFIG_LOCALIZATION:$component->setSubProperty($name,Prado::localize($value[1]));break;default: // an error if reaching herethrow new TConfigurationException('template_tag_unexpected',$name,$value[1]);break;}}else$component->setSubProperty($name,$value);}/*** Parses a template string.** This template parser recognizes five types of data:* regular string, well-formed component tags, well-formed property tags, directives, and expressions.** The parsing result is returned as an array. Each array element can be of three types:* - a string, 0: container index; 1: string content;* - a component tag, 0: container index; 1: component type; 2: attributes (name=>value pairs)* If a directive is found in the template, it will be parsed and can be* retrieved via {@link getDirective}, which returns an array consisting of* name-value pairs in the directive.** Note, attribute names are treated as case-insensitive and will be turned into lower cases.* Component and directive types are case-sensitive.* Container index is the index to the array element that stores the container object.* If an object has no container, its container index is -1.** @param string the template string* @throws TConfigurationException if a parsing error is encountered*/protected function parse($input){$input=$this->preprocess($input);$tpl=&$this->_tpl;$n=preg_match_all(self::REGEX_RULES,$input,$matches,PREG_SET_ORDER|PREG_OFFSET_CAPTURE);$expectPropEnd=false;$textStart=0;$stack=array();$container=-1;$matchEnd=0;$c=0;$this->_directive=null;try{for($i=0;$i<$n;++$i){$match=&$matches[$i];$str=$match[0][0];$matchStart=$match[0][1];$matchEnd=$matchStart+strlen($str)-1;if(strpos($str,'<com:')===0) // opening component tag{if($expectPropEnd)continue;if($matchStart>$textStart)$tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));$textStart=$matchEnd+1;$type=$match[1][0];$attributes=$this->parseAttributes($match[2][0],$match[2][1]);$this->validateAttributes($type,$attributes);$tpl[$c++]=array($container,$type,$attributes);if($str[strlen($str)-2]!=='/') // open tag{array_push($stack,$type);$container=$c-1;}}else if(strpos($str,'</com:')===0) // closing component tag{if($expectPropEnd)continue;if($matchStart>$textStart)$tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));$textStart=$matchEnd+1;$type=$match[1][0];if(empty($stack))throw new TConfigurationException('template_closingtag_unexpected',"</com:$type>");$name=array_pop($stack);if($name!==$type){$tag=$name[0]==='@' ? '</prop:'.substr($name,1).'>' : "</com:$name>";throw new TConfigurationException('template_closingtag_expected',$tag);}$container=$tpl[$container][0];}else if(strpos($str,'<%@')===0) // directive{if($expectPropEnd)continue;if($matchStart>$textStart)$tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));$textStart=$matchEnd+1;if(isset($tpl[0]) || $this->_directive!==null)throw new TConfigurationException('template_directive_nonunique');$this->_directive=$this->parseAttributes($match[4][0],$match[4][1]);}else if(strpos($str,'<%')===0) // expression{if($expectPropEnd)continue;if($matchStart>$textStart)$tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));$textStart=$matchEnd+1;$literal=trim($match[5][0]);if($str[2]==='=') // expression$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_EXPRESSION,$literal));else if($str[2]==='%') // statements$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_STATEMENTS,$literal));else if($str[2]==='#')$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_DATABINDING,$literal));else if($str[2]==='$')$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_EXPRESSION,"\$this->getApplication()->getParameters()->itemAt('$literal')"));else if($str[2]==='~')$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_EXPRESSION,"\$this->publishFilePath('$this->_contextPath/$literal')"));else if($str[2]==='/')$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_EXPRESSION,"dirname(\$this->getApplication()->getRequest()->getApplicationUrl()).'/$literal'"));else if($str[2]==='['){$literal=strtr(trim(substr($literal,0,strlen($literal)-1)),array("'"=>"\'","\\"=>"\\\\"));$tpl[$c++]=array($container,array(TCompositeLiteral::TYPE_EXPRESSION,"Prado::localize('$literal')"));}}else if(strpos($str,'<prop:')===0) // opening property{if(strrpos($str,'/>')===strlen($str)-2) //subproperties{if($expectPropEnd)continue;if($matchStart>$textStart)$tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));$textStart=$matchEnd+1;$prop=strtolower($match[6][0]);$attrs=$this->parseAttributes($match[7][0],$match[7][1]);$attributes=array();foreach($attrs as $name=>$value)$attributes[$prop.'.'.$name]=$value;$type=$tpl[$container][1];$this->validateAttributes($type,$attributes);foreach($attributes as $name=>$value){if(isset($tpl[$container][2][$name]))throw new TConfigurationException('template_property_duplicated',$name);$tpl[$container][2][$name]=$value;}}else // regular property{$prop=strtolower($match[3][0]);array_push($stack,'@'.$prop);if(!$expectPropEnd){if($matchStart>$textStart)$tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));$textStart=$matchEnd+1;$expectPropEnd=true;}}}else if(strpos($str,'</prop:')===0) // closing property{$prop=strtolower($match[3][0]);if(empty($stack))throw new TConfigurationException('template_closingtag_unexpected',"</prop:$prop>");$name=array_pop($stack);if($name!=='@'.$prop){$tag=$name[0]==='@' ? '</prop:'.substr($name,1).'>' : "</com:$name>";throw new TConfigurationException('template_closingtag_expected',$tag);}if(($last=count($stack))<1 || $stack[$last-1][0]!=='@'){if($matchStart>$textStart){$value=substr($input,$textStart,$matchStart-$textStart);if(substr($prop,-8,8)==='template')$value=$this->parseTemplateProperty($value,$textStart);else$value=$this->parseAttribute($value);if($container>=0){$type=$tpl[$container][1];$this->validateAttributes($type,array($prop=>$value));if(isset($tpl[$container][2][$prop]))throw new TConfigurationException('template_property_duplicated',$prop);$tpl[$container][2][$prop]=$value;}else // a property for the template control$this->_directive[$prop]=$value;$textStart=$matchEnd+1;}$expectPropEnd=false;}}else if(strpos($str,'<!--')===0) // comments{if($expectPropEnd)throw new TConfigurationException('template_comments_forbidden');if($matchStart>$textStart)$tpl[$c++]=array($container,substr($input,$textStart,$matchStart-$textStart));$textStart=$matchEnd+1;}elsethrow new TConfigurationException('template_matching_unexpected',$match);}if(!empty($stack)){$name=array_pop($stack);$tag=$name[0]==='@' ? '</prop:'.substr($name,1).'>' : "</com:$name>";throw new TConfigurationException('template_closingtag_expected',$tag);}if($textStart<strlen($input))$tpl[$c++]=array($container,substr($input,$textStart));}catch(Exception $e){if(($e instanceof TException) && ($e instanceof TTemplateException))throw $e;if($matchEnd===0)$line=$this->_startingLine+1;else$line=$this->_startingLine+count(explode("\n",substr($input,0,$matchEnd+1)));$this->handleException($e,$line,$input);}if($this->_directive===null)$this->_directive=array();// optimization by merging consecutive strings, expressions, statements and bindings$objects=array();$parent=null;$merged=array();foreach($tpl as $id=>$object){if(isset($object[2]) || $object[0]!==$parent){if($parent!==null){if(count($merged[1])===1 && is_string($merged[1][0]))$objects[$id-1]=array($merged[0],$merged[1][0]);else$objects[$id-1]=array($merged[0],new TCompositeLiteral($merged[1]));}if(isset($object[2])){$parent=null;$objects[$id]=$object;}else{$parent=$object[0];$merged=array($parent,array($object[1]));}}else$merged[1][]=$object[1];}if($parent!==null){if(count($merged[1])===1 && is_string($merged[1][0]))$objects[$id]=array($merged[0],$merged[1][0]);else$objects[$id]=array($merged[0],new TCompositeLiteral($merged[1]));}$tpl=$objects;return $objects;}/*** Parses the attributes of a tag from a string.* @param string the string to be parsed.* @return array attribute values indexed by names.*/protected function parseAttributes($str,$offset){if($str==='')return array();$pattern='/([\w\.]+)\s*=\s*(\'.*?\'|".*?"|<%.*?%>)/msS';$attributes=array();$n=preg_match_all($pattern,$str,$matches,PREG_SET_ORDER|PREG_OFFSET_CAPTURE);for($i=0;$i<$n;++$i){$match=&$matches[$i];$name=strtolower($match[1][0]);if(isset($attributes[$name]))throw new TConfigurationException('template_property_duplicated',$name);$value=$match[2][0];if(substr($name,-8,8)==='template'){if($value[0]==='\'' || $value[0]==='"')$attributes[$name]=$this->parseTemplateProperty(substr($value,1,strlen($value)-2),$match[2][1]+1);else$attributes[$name]=$this->parseTemplateProperty($value,$match[2][1]);}else{if($value[0]==='\'' || $value[0]==='"')$attributes[$name]=$this->parseAttribute(substr($value,1,strlen($value)-2));else$attributes[$name]=$this->parseAttribute($value);}}return $attributes;}protected function parseTemplateProperty($content,$offset){$line=$this->_startingLine+count(explode("\n",substr($this->_content,0,$offset)))-1;return array(self::CONFIG_TEMPLATE,new TTemplate($content,$this->_contextPath,$this->_tplFile,$line,false));}/*** Parses a single attribute.* @param string the string to be parsed.* @return array attribute initialization*/protected function parseAttribute($value){if(($n=preg_match_all('/<%[#=].*?%>/msS',$value,$matches,PREG_OFFSET_CAPTURE))>0){$isDataBind=false;$textStart=0;$expr='';for($i=0;$i<$n;++$i){$match=$matches[0][$i];$token=$match[0];$offset=$match[1];$length=strlen($token);if($token[2]==='#')$isDataBind=true;if($offset>$textStart)$expr.=".'".strtr(substr($value,$textStart,$offset-$textStart),array("'"=>"\\'","\\"=>"\\\\"))."'";$expr.='.('.substr($token,3,$length-5).')';$textStart=$offset+$length;}$length=strlen($value);if($length>$textStart)$expr.=".'".strtr(substr($value,$textStart,$length-$textStart),array("'"=>"\\'","\\"=>"\\\\"))."'";if($isDataBind)return array(self::CONFIG_DATABIND,ltrim($expr,'.'));elsereturn array(self::CONFIG_EXPRESSION,ltrim($expr,'.'));}else if(preg_match('/\\s*(<%~.*?%>|<%\\$.*?%>|<%\\[.*?\\]%>)\\s*/msS',$value,$matches) && $matches[0]===$value){$value=$matches[1];if($value[2]==='~') // a URLreturn array(self::CONFIG_ASSET,trim(substr($value,3,strlen($value)-5)));else if($value[2]==='[')return array(self::CONFIG_LOCALIZATION,trim(substr($value,3,strlen($value)-6)));else if($value[2]==='$')return array(self::CONFIG_PARAMETER,trim(substr($value,3,strlen($value)-5)));}elsereturn $value;}protected function validateAttributes($type,$attributes){Prado::using($type);if(($pos=strrpos($type,'.'))!==false)$className=substr($type,$pos+1);else$className=$type;$class=new TReflectionClass($className);if(is_subclass_of($className,'TControl') || $className==='TControl'){foreach($attributes as $name=>$att){if(($pos=strpos($name,'.'))!==false){// a subproperty, so the first segment must be readable$subname=substr($name,0,$pos);if(!$class->hasMethod('get'.$subname))throw new TConfigurationException('template_property_unknown',$type,$subname);}else if(strncasecmp($name,'on',2)===0){// an eventif(!$class->hasMethod($name))throw new TConfigurationException('template_event_unknown',$type,$name);else if(!is_string($att))throw new TConfigurationException('template_eventhandler_invalid',$type,$name);}else{// a simple propertyif(!$class->hasMethod('set'.$name)){if($class->hasMethod('get'.$name))throw new TConfigurationException('template_property_readonly',$type,$name);elsethrow new TConfigurationException('template_property_unknown',$type,$name);}else if(is_array($att) && $att[0]!==self::CONFIG_EXPRESSION){if(strcasecmp($name,'id')===0)throw new TConfigurationException('template_controlid_invalid',$type);else if(strcasecmp($name,'skinid')===0)throw new TConfigurationException('template_controlskinid_invalid',$type);}}}}else if(is_subclass_of($className,'TComponent') || $className==='TComponent'){foreach($attributes as $name=>$att){if(is_array($att) && ($att[0]===self::CONFIG_DATABIND))throw new TConfigurationException('template_databind_forbidden',$type,$name);if(($pos=strpos($name,'.'))!==false){// a subproperty, so the first segment must be readable$subname=substr($name,0,$pos);if(!$class->hasMethod('get'.$subname))throw new TConfigurationException('template_property_unknown',$type,$subname);}else if(strncasecmp($name,'on',2)===0)throw new TConfigurationException('template_event_forbidden',$type,$name);else{// id is still alowed for TComponent, even if id property doesn't existif(strcasecmp($name,'id')!==0 && !$class->hasMethod('set'.$name)){if($class->hasMethod('get'.$name))throw new TConfigurationException('template_property_readonly',$type,$name);elsethrow new TConfigurationException('template_property_unknown',$type,$name);}}}}elsethrow new TConfigurationException('template_component_required',$type);}/*** @return array list of included external template files*/public function getIncludedFiles(){return $this->_includedFiles;}/*** Handles template parsing exception.* This method rethrows the exception caught during template parsing.* It adjusts the error location by giving out correct error line number and source file.* @param Exception template exception* @param int line number* @param string template string if no source file is used*/protected function handleException($e,$line,$input=null){$srcFile=$this->_tplFile;if(($n=count($this->_includedFiles))>0) // need to adjust error row number and file name{for($i=$n-1;$i>=0;--$i){if($this->_includeAtLine[$i]<=$line){if($line<$this->_includeAtLine[$i]+$this->_includeLines[$i]){$line=$line-$this->_includeAtLine[$i]+1;$srcFile=$this->_includedFiles[$i];break;}else$line=$line-$this->_includeLines[$i]+1;}}}$exception=new TTemplateException('template_format_invalid',$e->getMessage());$exception->setLineNumber($line);if(!empty($srcFile))$exception->setTemplateFile($srcFile);else$exception->setTemplateSource($input);throw $exception;}/*** Preprocesses the template string by including external templates* @param string template string* @return string expanded template string*/protected function preprocess($input){if($n=preg_match_all('/<%include(.*?)%>/',$input,$matches,PREG_SET_ORDER|PREG_OFFSET_CAPTURE)){for($i=0;$i<$n;++$i){$filePath=Prado::getPathOfNamespace(trim($matches[$i][1][0]),TTemplateManager::TEMPLATE_FILE_EXT);if($filePath!==null && is_file($filePath))$this->_includedFiles[]=$filePath;else{$errorLine=count(explode("\n",substr($input,0,$matches[$i][0][1]+1)));$this->handleException(new TConfigurationException('template_include_invalid',trim($matches[$i][1][0])),$errorLine,$input);}}$base=0;for($i=0;$i<$n;++$i){$ext=file_get_contents($this->_includedFiles[$i]);$length=strlen($matches[$i][0][0]);$offset=$base+$matches[$i][0][1];$this->_includeAtLine[$i]=count(explode("\n",substr($input,0,$offset)));$this->_includeLines[$i]=count(explode("\n",$ext));$input=substr_replace($input,$ext,$offset,$length);$base+=strlen($ext)-$length;}}return $input;}}