diff --git a/Think/Template/Driver/File.php b/Think/Template/Driver/File.php new file mode 100644 index 00000000..62423d98 --- /dev/null +++ b/Think/Template/Driver/File.php @@ -0,0 +1,45 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +namespace Think\Template\Driver; +class File { + // 写入编译缓存 + public function write($cacheFile,$content){ + // 检测模板目录 + $dir = dirname($cacheFile); + if(!is_dir($dir)) + mkdir($dir,0755,true); + // 生成模板缓存文件 + if( false === file_put_contents($cacheFile,$content)) + E('_CACHE_WRITE_ERROR_:'.$cacheFile); + } + + // 读取编译编译 + public function read($cacheFile,$vars){ + // 模板阵列变量分解成为独立变量 + extract($vars, EXTR_OVERWRITE); + //载入模版缓存文件 + include $cacheFile; + } + + // 检查编译缓存是否有效 + public function check($template,$cacheFile,$cacheTime){ + if(!is_file($cacheFile)|| (is_file($template) && filemtime($template) > filemtime($cacheFile))) { + // 模板文件如果有更新则缓存需要更新 + return false; + }elseif ($cacheTime != 0 && time() > filemtime($cacheFile)+$cacheTime) { + // 缓存是否在有效期 + return false; + } + return true; + } +} \ No newline at end of file diff --git a/Think/Template/TagLib.php b/Think/Template/TagLib.php new file mode 100644 index 00000000..9050d01e --- /dev/null +++ b/Think/Template/TagLib.php @@ -0,0 +1,243 @@ + +// +---------------------------------------------------------------------- +// $Id$ +namespace Think\Template; +/** + * ThinkPHP标签库TagLib解析基类 + * @category Think + * @package Think + * @subpackage Template + * @author liu21st + */ +class TagLib { + + /** + * 标签库定义XML文件 + * @var string + * @access protected + */ + protected $xml = ''; + protected $tags = [];// 标签定义 + /** + * 标签库名称 + * @var string + * @access protected + */ + protected $tagLib =''; + + /** + * 标签库标签列表 + * @var string + * @access protected + */ + protected $tagList = []; + + /** + * 标签库分析数组 + * @var string + * @access protected + */ + protected $parse = []; + + /** + * 标签库是否有效 + * @var string + * @access protected + */ + protected $valid = false; + + /** + * 当前模板对象 + * @var object + * @access protected + */ + public $tpl; + + protected $comparison = [' nheq '=>' !== ',' heq '=>' === ',' neq '=>' != ',' eq '=>' == ',' egt '=>' >= ',' gt '=>' > ',' elt '=>' <= ',' lt '=>' < ']; + + /** + * TagLib标签属性分析 返回标签属性数组 + * @access public + * @param string $tagStr 标签内容 + * @return array + */ + public function parseXmlAttr($attr,$tag) { + if(''== trim($attr)) { + return []; + } + //XML解析安全过滤 + $attr = str_replace('&','___', $attr); + $xml = ''; + $xml = simplexml_load_string($xml); + if(!$xml) { + exit('_XML_TAG_ERROR_ : '.$attr); + } + $xml = (array)($xml->tag->attributes()); + $array = array_change_key_case($xml['@attributes']); + if($array) { + $tag = strtolower($tag); + if(isset($this->tags[$tag]['attr'])) { + $attrs = explode(',',$this->tags[$tag]['attr']); + if(isset($this->tags[strtolower($tag)]['must'])){ + $must = explode(',',$this->tags[$tag]['must']); + }else{ + $must = []; + } + foreach($attrs as $name) { + if( isset($array[$name])) { + $array[$name] = str_replace('___','&',$array[$name]); + }elseif(false !== array_search($name,$must)){ + exit('_PARAM_ERROR_:'.$name); + } + } + } + return $array; + } + } + + /** + * 解析条件表达式 + * @access public + * @param string $condition 表达式标签内容 + * @return array + */ + public function parseCondition($condition) { + $condition = str_ireplace(array_keys($this->comparison),array_values($this->comparison),$condition); + $condition = preg_replace('/\$(\w+):(\w+)\s/is','$\\1->\\2 ',$condition); + $condition = preg_replace('/\$(\w+)\.(\w+)\s/is','$\\1["\\2"] ',$condition); + + if(false !== strpos($condition, '$Think')) + $condition = preg_replace('/(\$Think.*?)\s/ies',"\$this->parseThinkVar('\\1');" , $condition); + return $condition; + } + + /** + * 自动识别构建变量 + * @access public + * @param string $name 变量描述 + * @return string + */ + public function autoBuildVar($name) { + if('Think.' == substr($name,0,6)){ + // 特殊变量 + return $this->parseThinkVar($name); + }elseif(strpos($name,'.')) { + $vars = explode('.',$name); + $var = array_shift($vars); + $name = '$'.$var; + foreach ($vars as $key=>$val){ + if(0===strpos($val,'$')) { + $name .= '["{'.$val.'}"]'; + }else{ + $name .= '["'.$val.'"]'; + } + } + }elseif(strpos($name,':')){ + // 额外的对象方式支持 + $name = '$'.str_replace(':','->',$name); + }elseif(!defined($name)) { + $name = '$'.$name; + } + return $name; + } + + /** + * 用于标签属性里面的特殊模板变量解析 + * 格式 以 Think. 打头的变量属于特殊模板变量 + * @access public + * @param string $varStr 变量字符串 + * @return string + */ + public function parseThinkVar($varStr){ + $vars = explode('.',$varStr); + $vars[1] = strtoupper(trim($vars[1])); + $parseStr = ''; + if(count($vars)>=3){ + $vars[2] = trim($vars[2]); + switch($vars[1]){ + case 'SERVER': $parseStr = '$_SERVER[\''.$vars[2].'\']';break; + case 'GET': $parseStr = '$_GET[\''.$vars[2].'\']';break; + case 'POST': $parseStr = '$_POST[\''.$vars[2].'\']';break; + case 'COOKIE': + if(isset($vars[3])) { + $parseStr = '$_COOKIE[\''.$vars[2].'\'][\''.$vars[3].'\']'; + }else{ + $parseStr = '$_COOKIE[\''.$vars[2].'\']'; + } + break; + case 'SESSION': + if(isset($vars[3])) { + $parseStr = '$_SESSION[\''.$vars[2].'\'][\''.$vars[3].'\']'; + }else{ + $parseStr = '$_SESSION[\''.$vars[2].'\']'; + } + break; + case 'ENV': $parseStr = '$_ENV[\''.$vars[2].'\']';break; + case 'REQUEST': $parseStr = '$_REQUEST[\''.$vars[2].'\']';break; + case 'CONST': $parseStr = strtoupper($vars[2]);break; + case 'LANG': + $parseStr = 'L("'.$vars[2].'")';break; + case 'CONFIG': + if(isset($vars[3])) { + $vars[2] .= '.'.$vars[3]; + } + $parseStr = 'C("'.$vars[2].'")';break; + } + }else if(count($vars)==2){ + switch($vars[1]){ + case 'NOW': $parseStr = "date('Y-m-d g:i a',time())";break; + case 'VERSION': $parseStr = 'THINK_VERSION';break; + default: if(defined($vars[1])) $parseStr = $vars[1]; + } + } + return $parseStr; + } + + /** + * 对模板变量使用函数 + * 格式 {$varname|function1|function2=arg1,arg2} + * @access protected + * @param string $name 变量名 + * @param array $varArray 函数列表 + * @return string + */ + protected function parseVarFunction($name,$varArray){ + //对变量使用函数 + $length = count($varArray); + for($i=0;$i<$length ;$i++ ){ + $args = explode('=',$varArray[$i],2); + //模板函数过滤 + $fun = strtolower(trim($args[0])); + switch($fun) { + case 'default': // 特殊模板函数 + $name = '('.$name.')?('.$name.'):'.$args[1]; + break; + default: // 通用模板函数 + if(isset($args[1])){ + if(strstr($args[1],'###')){ + $args[1] = str_replace('###',$name,$args[1]); + $name = "$fun($args[1])"; + }else{ + $name = "$fun($name,$args[1])"; + } + }else if(!empty($args[0])){ + $name = "$fun($name)"; + } + } + } + return $name; + } + + // 获取标签定义 + public function getTags(){ + return $this->tags; + } +} \ No newline at end of file diff --git a/Think/Template/TagLib/Cx.php b/Think/Template/TagLib/Cx.php new file mode 100644 index 00000000..5dc74803 --- /dev/null +++ b/Think/Template/TagLib/Cx.php @@ -0,0 +1,614 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Template\TagLib; +use Think\Template\TagLib; +/** + * CX标签库解析类 + * @category Think + * @package Think + * @subpackage Driver.Taglib + * @author liu21st + */ +class Cx extends TagLib { + + // 标签定义 + protected $tags = [ + // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次 + 'php' => [], + 'volist' => ['attr'=>'name,id,offset,length,key,mod','level'=>3,'alias'=>'iterate'], + 'foreach' => ['attr'=>'name,item,key','level'=>3], + 'if' => ['attr'=>'condition','level'=>2], + 'elseif' => ['attr'=>'condition','close'=>0], + 'else' => ['attr'=>'','close'=>0], + 'switch' => ['attr'=>'name','level'=>2], + 'case' => ['attr'=>'value,break'], + 'default' => ['attr'=>'','close'=>0], + 'compare' => ['attr'=>'name,value,type','level'=>3,'alias'=>'eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq'], + 'range' => ['attr'=>'name,value,type','level'=>3,'alias'=>'in,notin,between,notbetween'], + 'empty' => ['attr'=>'name','level'=>3], + 'notempty' => ['attr'=>'name','level'=>3], + 'present' => ['attr'=>'name','level'=>3], + 'notpresent'=> ['attr'=>'name','level'=>3], + 'defined' => ['attr'=>'name','level'=>3], + 'notdefined'=> ['attr'=>'name','level'=>3], + 'import' => ['attr'=>'file,href,type,value,basepath','close'=>0,'alias'=>'load,css,js'], + 'assign' => ['attr'=>'name,value','close'=>0], + 'define' => ['attr'=>'name,value','close'=>0], + 'for' => ['attr'=>'start,end,name,comparison,step', 'level'=>3], + ]; + + /** + * php标签解析 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _php($tag,$content) { + $parseStr = ''; + return $parseStr; + } + + /** + * volist标签解析 循环输出数据集 + * 格式: + * + * {user.username} + * {user.email} + * + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function _volist($tag,$content) { + $name = $tag['name']; + $id = $tag['id']; + $empty = isset($tag['empty'])?$tag['empty']:''; + $key = !empty($tag['key'])?$tag['key']:'i'; + $mod = isset($tag['mod'])?$tag['mod']:'2'; + // 允许使用函数设定数据集 {$vo.name} + $parseStr = 'autoBuildVar($name); + } + $parseStr .= 'if(is_array('.$name.')): $'.$key.' = 0;'; + if(isset($tag['length']) && '' !=$tag['length'] ) { + $parseStr .= ' $__LIST__ = array_slice('.$name.','.$tag['offset'].','.$tag['length'].',true);'; + }elseif(isset($tag['offset']) && '' !=$tag['offset']){ + $parseStr .= ' $__LIST__ = array_slice('.$name.','.$tag['offset'].',null,true);'; + }else{ + $parseStr .= ' $__LIST__ = '.$name.';'; + } + $parseStr .= 'if( count($__LIST__)==0 ) : echo "'.$empty.'" ;'; + $parseStr .= 'else: '; + $parseStr .= 'foreach($__LIST__ as $key=>$'.$id.'): '; + $parseStr .= '$mod = ($'.$key.' % '.$mod.' );'; + $parseStr .= '++$'.$key.';?>'; + $parseStr .= ($content); + $parseStr .= ''; + + if(!empty($parseStr)) { + return $parseStr; + } + return ; + } + + /** + * foreach标签解析 循环输出数据集 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function _foreach($tag,$content) { + $name = $tag['name']; + $item = $tag['item']; + $key = !empty($tag['key'])?$tag['key']:'key'; + $name = $this->autoBuildVar($name); + $parseStr = '$'.$item.'): ?>'; + $parseStr .= ($content); + $parseStr .= ''; + if(!empty($parseStr)) { + return $parseStr; + } + return ; + } + + /** + * if标签解析 + * 格式: + * + * + * + * + * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _if($tag,$content) { + $condition = $this->parseCondition($tag['condition']); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * else标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _elseif($tag,$content) { + $condition = $this->parseCondition($tag['condition']); + $parseStr = ''; + return $parseStr; + } + + /** + * else标签解析 + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function _else($tag) { + $parseStr = ''; + return $parseStr; + } + + /** + * switch标签解析 + * 格式: + * + * 1 + * 2 + * other + * + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _switch($tag,$content) { + $name = $tag['name']; + $varArray = explode('|',$name); + $name = array_shift($varArray); + $name = $this->autoBuildVar($name); + if(count($varArray)>0) + $name = $this->parseVarFunction($name,$varArray); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * case标签解析 需要配合switch才有效 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _case($tag,$content) { + $value = $tag['value']; + if('$' == substr($value,0,1)) { + $varArray = explode('|',$value); + $value = array_shift($varArray); + $value = $this->autoBuildVar(substr($value,1)); + if(count($varArray)>0) + $value = $this->parseVarFunction($value,$varArray); + $value = 'case '.$value.': '; + }elseif(strpos($value,'|')){ + $values = explode('|',$value); + $value = ''; + foreach ($values as $val){ + $value .= 'case "'.addslashes($val).'": '; + } + }else{ + $value = 'case "'.$value.'": '; + } + $parseStr = ''.$content; + $isBreak = isset($tag['break']) ? $tag['break'] : ''; + if('' ==$isBreak || $isBreak) { + $parseStr .= ''; + } + return $parseStr; + } + + /** + * default标签解析 需要配合switch才有效 + * 使用: ddfdf + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _default($tag) { + $parseStr = ''; + return $parseStr; + } + + /** + * compare标签解析 + * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq + * 格式: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _compare($tag,$content,$type='eq') { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type'])?$tag['type']:$type; + $type = $this->parseCondition(' '.$type.' '); + $varArray = explode('|',$name); + $name = array_shift($varArray); + $name = $this->autoBuildVar($name); + if(count($varArray)>0) + $name = $this->parseVarFunction($name,$varArray); + if('$' == substr($value,0,1)) { + $value = $this->autoBuildVar(substr($value,1)); + }else { + $value = '"'.$value.'"'; + } + $parseStr = ''.$content.''; + return $parseStr; + } + + public function _eq($tag,$content) { + return $this->_compare($tag,$content,'eq'); + } + + public function _equal($tag,$content) { + return $this->_compare($tag,$content,'eq'); + } + + public function _neq($tag,$content) { + return $this->_compare($tag,$content,'neq'); + } + + public function _notequal($tag,$content) { + return $this->_compare($tag,$content,'neq'); + } + + public function _gt($tag,$content) { + return $this->_compare($tag,$content,'gt'); + } + + public function _lt($tag,$content) { + return $this->_compare($tag,$content,'lt'); + } + + public function _egt($tag,$content) { + return $this->_compare($tag,$content,'egt'); + } + + public function _elt($tag,$content) { + return $this->_compare($tag,$content,'elt'); + } + + public function _heq($tag,$content) { + return $this->_compare($tag,$content,'heq'); + } + + public function _nheq($tag,$content) { + return $this->_compare($tag,$content,'nheq'); + } + + /** + * range标签解析 + * 如果某个变量存在于某个范围 则输出内容 type= in 表示在范围内 否则表示在范围外 + * 格式: content + * example: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @param string $type 比较类型 + * @return string + */ + public function _range($tag,$content,$type='in') { + $name = $tag['name']; + $value = $tag['value']; + $varArray = explode('|',$name); + $name = array_shift($varArray); + $name = $this->autoBuildVar($name); + if(count($varArray)>0) + $name = $this->parseVarFunction($name,$varArray); + + $type = isset($tag['type'])?$tag['type']:$type; + + if('$' == substr($value,0,1)) { + $value = $this->autoBuildVar(substr($value,1)); + $str = 'is_array('.$value.')?'.$value.':explode(\',\','.$value.')'; + }else{ + $value = '"'.$value.'"'; + $str = 'explode(\',\','.$value.')'; + } + if($type=='between') { + $parseStr = '= $_RANGE_VAR_[0] && '.$name.'<= $_RANGE_VAR_[1]):?>'.$content.''; + }elseif($type=='notbetween'){ + $parseStr = '$_RANGE_VAR_[1]):?>'.$content.''; + }else{ + $fun = ($type == 'in')? 'in_array' : '!in_array'; + $parseStr = ''.$content.''; + } + return $parseStr; + } + + // range标签的别名 用于in判断 + public function _in($tag,$content) { + return $this->_range($tag,$content,'in'); + } + + // range标签的别名 用于notin判断 + public function _notin($tag,$content) { + return $this->_range($tag,$content,'notin'); + } + + public function _between($tag,$content){ + return $this->_range($tag,$content,'between'); + } + + public function _notbetween($tag,$content){ + return $this->_range($tag,$content,'notbetween'); + } + + /** + * present标签解析 + * 如果某个变量已经设置 则输出内容 + * 格式: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _present($tag,$content) { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * notpresent标签解析 + * 如果某个变量没有设置,则输出内容 + * 格式: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _notpresent($tag,$content) { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * empty标签解析 + * 如果某个变量为empty 则输出内容 + * 格式: content + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _empty($tag,$content) { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = ''.$content.''; + return $parseStr; + } + + public function _notempty($tag,$content) { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * 判断是否已经定义了该常量 + * 已定义 + * @param $tag + * @param $content + * @return string + */ + public function _defined($tag,$content) { + $name = $tag['name']; + $parseStr = ''.$content.''; + return $parseStr; + } + + public function _notdefined($tag,$content) { + $name = $tag['name']; + $parseStr = ''.$content.''; + return $parseStr; + } + + /** + * import 标签解析 + * + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @param boolean $isFile 是否文件方式 + * @param string $type 类型 + * @return string + */ + public function _import($tag,$content,$isFile=false,$type='') { + $file = isset($tag['file'])?$tag['file']:$tag['href']; + $parseStr = ''; + $endStr = ''; + // 判断是否存在加载条件 允许使用函数判断(默认为isset) + if (isset($tag['value'])) { + $varArray = explode('|',$tag['value']); + $name = array_shift($varArray); + $name = $this->autoBuildVar($name); + if (!empty($varArray)) + $name = $this->parseVarFunction($name,$varArray); + else + $name = 'isset('.$name.')'; + $parseStr .= ''; + $endStr = ''; + } + if($isFile) { + // 根据文件名后缀自动识别 + $type = $type?$type:(!empty($tag['type'])?strtolower($tag['type']):null); + // 文件方式导入 + $array = explode(',',$file); + foreach ($array as $val){ + if (!$type || isset($reset)) { + $type = $reset = strtolower(substr(strrchr($val, '.'),1)); + } + switch($type) { + case 'js': + $parseStr .= ''; + break; + case 'css': + $parseStr .= ''; + break; + case 'php': + $parseStr .= ''; + break; + } + } + }else{ + // 命名空间导入模式 默认是js + $type = $type?$type:(!empty($tag['type'])?strtolower($tag['type']):'js'); + $basepath = !empty($tag['basepath'])?$tag['basepath']:__ROOT__.'/Public'; + // 命名空间方式导入外部文件 + $array = explode(',',$file); + foreach ($array as $val){ + list($val,$version) = explode('?',$val); + switch($type) { + case 'js': + $parseStr .= ''; + break; + case 'css': + $parseStr .= ''; + break; + case 'php': + $parseStr .= ''; + break; + } + } + } + return $parseStr.$endStr; + } + + // import别名 采用文件方式加载(要使用命名空间必须用import) 例如 + public function _load($tag,$content) { + return $this->_import($tag,$content,true); + } + + // import别名使用 导入css文件 + public function _css($tag,$content) { + return $this->_import($tag,$content,true,'css'); + } + + // import别名使用 导入js文件 + public function _js($tag,$content) { + return $this->_import($tag,$content,true,'js'); + } + + /** + * assign标签解析 + * 在模板中给某个变量赋值 支持变量赋值 + * 格式: + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _assign($tag,$content) { + $name = $this->autoBuildVar($tag['name']); + if('$'==substr($tag['value'],0,1)) { + $value = $this->autoBuildVar(substr($tag['value'],1)); + }else{ + $value = '\''.$tag['value']. '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * define标签解析 + * 在模板中定义常量 支持变量赋值 + * 格式: + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _define($tag,$content) { + $name = '\''.$tag['name']. '\''; + if('$'==substr($tag['value'],0,1)) { + $value = $this->autoBuildVar(substr($tag['value'],1)); + }else{ + $value = '\''.$tag['value']. '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * for标签解析 + * 格式: + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function _for($tag, $content){ + //设置默认值 + $start = 0; + $end = 0; + $step = 1; + $comparison = 'lt'; + $name = 'i'; + $rand = rand(); //添加随机数,防止嵌套变量冲突 + //获取属性 + foreach ($tag as $key => $value){ + $value = trim($value); + if(':'==substr($value,0,1)) + $value = substr($value,1); + elseif('$'==substr($value,0,1)) + $value = $this->autoBuildVar(substr($value,1)); + switch ($key){ + case 'start': + $start = $value; break; + case 'end' : + $end = $value; break; + case 'step': + $step = $value; break; + case 'comparison': + $comparison = $value; break; + case 'name': + $name = $value; break; + } + } + + $parseStr = 'parseCondition('$'.$name.' '.$comparison.' $__FOR_END_'.$rand.'__').';$'.$name.'+='.$step.'){ ?>'; + $parseStr .= $content; + $parseStr .= ''; + return $parseStr; + } + + } \ No newline at end of file