From e216563b098625dd37bbf27e7f0a4966380ed6b1 Mon Sep 17 00:00:00 2001 From: yin Date: Thu, 24 Dec 2015 00:41:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E4=BA=86=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E6=96=B9=E5=BC=8F=EF=BC=8C=E4=B8=8D=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E9=80=92=E5=BD=92=E8=BF=9B=E7=94=A8=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=EF=BC=8C=E6=95=88=E7=8E=87=E6=8F=90=E5=8D=87=EF=BC=8C=E4=B9=9F?= =?UTF-8?q?=E4=B8=8D=E7=94=A8=E5=86=8D=E9=99=90=E5=88=B6=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E5=B5=8C=E5=A5=97=E5=B1=82=E6=95=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 兼容原来所有的标签功能和用法,已 对正则进行了优化,标签库和内置的普通标签可以使用一样的边界符,比如都用"{}",只要不重名不会相互干扰,这样这些标签就可以和html标签区分开。 2. 模板支持多级继承。C继承B,而B又继承了A,C中的block会覆盖B和A中的同名block。 3. include标签支持多层嵌套,可以传变量。如: include file="Public/nav" selected="{$id}" 在Public/nav模板用[selected]得到的是[$id}被解析后的值,而在3.2版中这样的写法是不能正确得到{$id}的值的。 4. 增强了.语法的应用范围 {$user.name.$group.name} 解析后是 {:substr($varname.aa, $varname.bb)} 解析后是 .语法在各个标签中都可以使用,$a.b.c这样的形式都能正确解析成$a['b']['c'] 5. 增加了一些新的语法 {$varname.aa ?? 'xxx'} 表示如果有设置$varname则输出$varname,否则输出'xxx'。 解析后的代码为: {$varname?='xxx'} 表示$varname为真时才输出xxx。 解析后的代码为: {$varname ?: 'no'} 表示如果$varname为真则输出$varname,否则输出no。解析后的代码为: {$a==$b ? 'yes' : 'no'} 前面的表达式为真输出yes,否则输出no, 条件可以是==、===、!=、!==、>=、<= 6. 对if标签及foreach也加了一些更简洁的用法 {if condition="表达式"} {if (表达式)} {if 表达式} 这三种写法结果是一样的 {foreach $list as $v} 解析后是最简洁的,只一个foreach语句 {foreach name="list" item="v“} 这僦是原来的写法,解析后foreach外层会多一if判断,item换成id也可以 {foreach name="list" id="v" key="key" index="i" mod="2" offset="2" length="5"} volist上的功能,foreach都有,只是volist默认会带上一些参数,而foreach需要指定这些参数才会生效 --- library/think/template.php | 923 +++++++++++++++------------ library/think/template/taglib.php | 407 +++++++----- library/think/template/taglib/cx.php | 248 +++---- 3 files changed, 923 insertions(+), 655 deletions(-) diff --git a/library/think/template.php b/library/think/template.php index b07f915c..e1f8d9c8 100644 --- a/library/think/template.php +++ b/library/think/template.php @@ -46,7 +46,6 @@ class Template ]; private $literal = []; - private $block = []; protected $storage = null; /** @@ -72,6 +71,7 @@ class Template * 字符串替换 避免正则混淆 * @access private * @param string $str + * @return string */ private function stripPreg($str) { @@ -86,6 +86,7 @@ class Template * @access public * @param mixed $name * @param mixed $value + * @return viod */ public function assign($name, $value = '') { @@ -107,14 +108,28 @@ class Template $this->config[$name] = $value; } + /** + * 模板引擎配置项 + * @access public + * @param array $config + * @return void + */ public function config($config) { - $this->config = array_merge($this->config, $config); + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } } + /** + * 模板变量获取 + * @access public + * @param string $name 变量名 + * @return string|false + */ public function get($name) { - return $this->data[$name]; + return isset($this->data[$name]) ? $this->data[$name] : false; } /** @@ -194,13 +209,19 @@ class Template return $this->storage->check($template, $cacheFile, $this->config['cache_time']); } + /** + * 检查编译缓存是否存在 + * @access public + * @param string $cacheId 缓存的id + * @return boolen + */ public function isCache($cacheId) { if ($cacheId && $this->config['display_cache']) { // 缓存页面输出 return Cache::get($cacheId) ? true : false; } - return null; + return false; } /** @@ -210,14 +231,10 @@ class Template * @param string $cacheFile 缓存文件名 * @return void */ - private function compiler($content, $cacheFile) + private function compiler(&$content, $cacheFile) { // 模板解析 - $content = $this->parse($content); - // 还原被替换的Literal标签 - $content = preg_replace_callback('//is', function ($matches) { - return $this->restoreLiteral($matches[1]); - }, $content); + $this->parse($content); // 添加安全代码 $content = '' . $content; if ($this->config['strip_space']) { @@ -227,7 +244,7 @@ class Template $content = preg_replace($find, $replace, $content); } // 优化生成的php代码 - $content = str_replace('?>\s*<\?php\s?/is', '', $content); // 模板过滤输出 $replace = $this->config['tpl_replace_string']; $content = str_replace(array_keys($replace), array_values($replace), $content); @@ -241,24 +258,24 @@ class Template * 支持普通标签和TagLib解析 支持自定义标签库 * @access public * @param string $content 要解析的模板内容 - * @return string + * @return viod */ - public function parse($content) + public function parse(&$content) { // 内容为空不解析 if (empty($content)) { - return ''; + return; } - $begin = $this->config['taglib_begin']; - $end = $this->config['taglib_end']; + // 解析继承 + $this->parseExtend($content); + // 解析布局 + $this->parseLayout($content); // 检查include语法 - $content = $this->parseInclude($content); + $this->parseInclude($content); // 检查PHP语法 - $content = $this->parsePhp($content); - // 首先替换literal标签内容 - $content = preg_replace_callback('/' . $begin . 'literal' . $end . '(.*?)' . $begin . '\/literal' . $end . '/is', function ($matches) { - return $this->parseLiteral($matches[1]); - }, $content); + $this->parsePhp($content); + // 替换literal标签内容 + $this->parseLiteral($content); // 获取需要引入的标签库列表 // 标签库只需要定义一次,允许引入多个一次 @@ -286,15 +303,21 @@ class Template foreach ($tagLibs as $tag) { $this->parseTagLib($tag, $content, true); } - // 解析普通模板标签 {tagName} - $content = preg_replace_callback('/(' . $this->config['tpl_begin'] . ')([^\d\s' . $this->config['tpl_begin'] . $this->config['tpl_end'] . '].+?)(' . $this->config['tpl_end'] . ')/is', function ($matches) { - return $this->parseTag($matches[2], $matches[0]); - }, $content); - return $content; + // 解析普通模板标签 {$tagName} + $this->parseTag($content); + + // 还原被替换的Literal标签 + $this->parseLiteral($content, true); + return; } - // 检查PHP语法 - private function parsePhp($content) + /** + * 检查PHP语法 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parsePhp(&$content) { if (ini_get('short_open_tag')) { // 开启短标签的情况要将config['tpl_deny_php'] && false !== strpos($content, 'parseExtend($content); - // 解析布局 - $content = $this->parseLayout($content); - // 读取模板中的include标签 - $find = preg_match_all('/' . $this->config['taglib_begin'] . 'include\s(.+?)\s*?\/' . $this->config['taglib_end'] . '/is', $content, $matches); - if ($find) { - for ($i = 0; $i < $find; $i++) { - $include = $matches[1][$i]; - $array = $this->parseXmlAttrs($include); - $file = $array['file']; - unset($array['file']); - $content = str_replace($matches[0][$i], $this->parseIncludeItem($file, $array), $content); - } - } - return $content; - } - - // 解析模板中的布局标签 - private function parseLayout($content) - { - // 读取模板中的布局标签 - $find = preg_match('/' . $this->config['taglib_begin'] . 'layout\s(.+?)\s*?\/' . $this->config['taglib_end'] . '/is', $content, $matches); - if ($find) { - //替换Layout标签 - $content = str_replace($matches[0], '', $content); - //解析Layout标签 - $array = $this->parseXmlAttrs($matches[1]); - // 读取布局模板 - $layoutFile = (defined('THEME_PATH') && substr_count($array['name'], '/') < 2 ? THEME_PATH : $this->config['tpl_path']) . $array['name'] . $this->config['tpl_suffix']; - $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item']; - // 替换布局的主体内容 - $content = str_replace($replace, $content, file_get_contents($layoutFile)); - } else { - $content = str_replace('{__NOLAYOUT__}', '', $content); - } - return $content; - } - - // 解析模板中的extend标签 - private function parseExtend($content) - { - $begin = $this->config['taglib_begin']; - $end = $this->config['taglib_end']; - // 读取模板中的继承标签 - $find = preg_match('/' . $begin . 'extend\s(.+?)\s*?\/' . $end . '/is', $content, $matches); - if ($find) { - //替换extend标签 - $content = str_replace($matches[0], '', $content); - // 记录页面中的block标签 - preg_replace_callback('/' . $begin . 'block\sname=(.+?)\s*?' . $end . '(.*?)' . $begin . '\/block' . $end . '/is', function ($matches) { - return $this->parseBlock($matches[1], $matches[2]); - }, $content); - // 读取继承模板 - $array = $this->parseXmlAttrs($matches[1]); - $content = $this->parseTemplateName($array['name']); - // 替换block标签 - $content = preg_replace_callback('/' . $begin . 'block\sname=(.+?)\s*?' . $end . '(.*?)' . $begin . '\/block' . $end . '/is', function ($matches) { - return $this->replaceBlock($matches[1], $matches[2]); - }, $content); - } else { - $content = preg_replace_callback('/' . $begin . 'block\sname=(.+?)\s*?' . $end . '(.*?)' . $begin . '\/block' . $end . '/is', function ($matches) { - return stripslashes($matches[2]); - }, $content); - } - return $content; + return; } /** - * 分析XML属性 + * 解析模板中的布局标签 * @access private - * @param string $attrs XML属性字符串 - * @return array + * @param string $content 要解析的模板内容 + * @return void */ - private function parseXmlAttrs($attrs) + private function parseLayout(&$content) { - $xml = ''; - $xml = simplexml_load_string($xml); - if (!$xml) { - throw new Exception('template tag define error', 11601); + // 读取模板中的布局标签 + if (preg_match($this->getRegex('layout'), $content, $matches)) { + // 替换Layout标签 + $content = str_replace($matches[0], '', $content); + // 解析Layout标签 + $array = $this->parseAttr($matches[0]); + //if (!C('LAYOUT_ON') || C('LAYOUT_NAME') != $array['name']) { + // 读取布局模板 + $layoutFile = (defined('THEME_PATH') && substr_count($array['name'], '/') < 2 ? THEME_PATH : $this->config['tpl_path']) . $array['name'] . $this->config['tpl_suffix']; + if (is_file($layoutFile)) { + $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item']; + // 替换布局的主体内容 + $content = str_replace($replace, $content, file_get_contents($layoutFile)); + } + //} + } else { + $content = str_replace('{__NOLAYOUT__}', '', $content); } - $xml = (array) ($xml->tag->attributes()); - $array = array_change_key_case($xml['@attributes']); - return $array; + return; + } + + /** + * 解析模板中的include标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseInclude(&$content) + { + $regex = $this->getRegex('include'); + $funReplace = function ($template) use (&$funReplace, &$regex, &$content) { + if (preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $array = $this->parseAttr($match[0]); + $file = $array['file']; + unset($array['file']); + // 分析模板文件名并读取内容 + $parseStr = $this->parseTemplateName($file); + // 替换变量 + foreach ($array as $k => $v) { + $parseStr = str_replace('[' . $k . ']', $v, $parseStr); + } + // 再次对包含文件进行模板分析 + $funReplace($parseStr); + $content = str_replace($match[0], $parseStr, $content); + } + unset($matches); + } + }; + // 替换模板中的include标签 + $funReplace($content); + return; + } + + /** + * 解析模板中的extend标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseExtend(&$content) + { + $regex = $this->getRegex('extend'); + $array = $blocks = $extBlocks = []; + $extend = ''; + $fun = function ($template) use (&$fun, &$regex, &$array, &$extend, &$blocks, &$extBlocks) { + if (preg_match($regex, $template, $matches)) { + if (!isset($array[$matches['name']])) { + $array[$matches['name']] = 1; + // 读取继承模板 + $extend = $this->parseTemplateName($matches['name']); + // 递归检查继承 + $fun($extend); + // 取得block标签内容 + $blocks = array_merge($blocks, $this->parseBlock($template)); + return; + } + } else { + // 取得顶层模板block标签内容 + $extBlocks = $this->parseBlock($template); + if (empty($extend)) { + // 无extend标签但有block标签的情况 + $extend = $template; + } + } + }; + + $fun($content); + if (!empty($extend)) { + if ($extBlocks) { + foreach ($extBlocks as $name => $v) { + $replace = isset($blocks[$name]) ? $blocks[$name]['content'] : $v['content']; + $extend = str_replace($v['begin']['tag'] . $v['content'] . $v['end']['tag'], $replace, $extend); + } + } + $content = $extend; + } + return; } /** * 替换页面中的literal标签 * @access private - * @param string $content 模板内容 - * @return string + * @param string $content 模板内容 + * @param boolean $restore 是否为还原 + * @return void */ - private function parseLiteral($content) + private function parseLiteral(&$content, $restore = false) { - if (trim($content) == '') { - return ''; + $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal'); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + if (!$restore) { + // 替换literal标签 + foreach ($matches as $i => $match) { + $this->literal[$i] = substr($match[0], strlen($match[1]), -strlen($match[2])); + $content = str_replace($match[0], "", $content); + } + } else { + // 还原literal标签 + foreach ($matches as $i => $match) { + $content = str_replace($match[0], $this->literal[$i], $content); + } + // 销毁literal记录 + unset($this->literal); + } + unset($matches); } - $content = stripslashes($content); - $i = count($this->literal); - $parseStr = ""; - $this->literal[$i] = $content; - return $parseStr; + return; } /** - * 还原被替换的literal标签 + * 获取模板中的block标签 * @access private - * @param string $tag literal标签序号 - * @return string + * @param string $content 模板内容 + * @return array */ - private function restoreLiteral($tag) + private function parseBlock(&$content) { - // 还原literal标签 - $parseStr = $this->literal[$tag]; - // 销毁literal记录 - unset($this->literal[$tag]); - return $parseStr; - } - - /** - * 记录当前页面中的block标签 - * @access private - * @param string $name block名称 - * @param string $content 模板内容 - * @return string - */ - private function parseBlock($name, $content) - { - $this->block[$name] = $content; - return ''; - } - - /** - * 替换继承模板中的block标签 - * @access private - * @param string $name block名称 - * @param string $content 模板内容 - * @return string - */ - private function replaceBlock($name, $content) - { - // 替换block标签 没有重新定义则使用原来的 - $replace = isset($this->block[$name]) ? $this->block[$name] : $content; - return stripslashes($replace); + $regex = $this->getRegex('block'); + $array = []; + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = []; + foreach ($matches as $match) { + if (empty($match['name'][0])) { + if (!empty($right)) { + $begin = array_pop($right); + $end = ['offset' => $match[0][1], 'tag' => $match[0][0]]; + $start = $begin['offset'] + strlen($begin['tag']); + $len = $end['offset'] - $start; + $array[$begin['name']] = [ + 'begin' => $begin, + 'content' => substr($content, $start, $len), + 'end' => $end, + ]; + } else { + continue; + } + } else { + $right[] = [ + 'name' => $match[2][0], + 'offset' => $match[0][1], + 'tag' => $match[0][0], + ]; + } + } + unset($right, $matches); + } + return $array; } /** * 搜索模板页面中包含的TagLib库 * 并返回列表 * @access private - * @param string $content 模板内容 - * @return array + * @param string $content 模板内容 + * @return array|null */ private function getIncludeTagLib(&$content) { - //搜索是否有TagLib标签 - $find = preg_match('/' . $this->config['taglib_begin'] . 'taglib\s(.+?)(\s*?)\/' . $this->config['taglib_end'] . '\W/is', $content, $matches); - if ($find) { - //替换TagLib标签 + // 搜索是否有TagLib标签 + if (preg_match($this->getRegex('taglib'), $content, $matches)) { + // 替换TagLib标签 $content = str_replace($matches[0], '', $content); - //解析TagLib标签 - $array = $this->parseXmlAttrs($matches[1]); - return explode(',', $array['name']); + return explode(',', $matches['name']); } - return []; + return null; } /** * TagLib库解析 - * @access private - * @param string $tagLib 要解析的标签库 - * @param string $content 要解析的模板内容 - * @param boolen $hide 是否隐藏标签库前缀 + * @access public + * @param string $tagLib 要解析的标签库 + * @param string $content 要解析的模板内容 + * @param boolean $hide 是否隐藏标签库前缀 * @return void */ - protected function parseTagLib($tagLib, &$content, $hide = false) + public function parseTagLib($tagLib, &$content, $hide = false) { - $begin = $this->config['taglib_begin']; - $end = $this->config['taglib_end']; - $className = '\\think\\template\\taglib\\' . strtolower($tagLib); - $tLib = new $className($this); - //$that = $this; - foreach ($tLib->getTags() as $name => $val) { - $tags = [$name]; - if (isset($val['alias'])) { - // 别名设置 - $tags = explode(',', $val['alias']); - $tags[] = $name; - } - $level = isset($val['level']) ? $val['level'] : 1; - $closeTag = isset($val['close']) ? $val['close'] : true; - foreach ($tags as $tag) { - $parseTag = !$hide ? $tagLib . ':' . $tag : $tag; // 实际要解析的标签名称 - if (!method_exists($tLib, '_' . $tag)) { - // 别名可以无需定义解析方法 - $tag = $name; - } - $n1 = empty($val['attr']) ? '(\s*?)' : '\s([^' . $end . ']*)'; - if (!$closeTag) { - $patterns = '/' . $begin . $parseTag . $n1 . '\/(\s*?)' . $end . '/is'; - $content = preg_replace_callback($patterns, function ($matches) use ($tLib, $tagLib, $tag) { - return $this->parseXmlTag($tLib, $tagLib, $tag, $matches[1], $matches[2]); - }, $content); - } else { - $patterns = '/' . $begin . $parseTag . $n1 . $end . '(.*?)' . $begin . '\/' . $parseTag . '(\s*?)' . $end . '/is'; - for ($i = 0; $i < $level; $i++) { - $content = preg_replace_callback($patterns, function ($matches) use ($tLib, $tagLib, $tag) { - return $this->parseXmlTag($tLib, $tagLib, $tag, $matches[1], $matches[2]); - }, $content); - } - } - } + $tagLib = strtolower($tagLib); + if (strpos($tagLib, '\\')) { + // 支持指定标签库的命名空间 + $className = $tagLib; + $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1); + } else { + $className = '\\think\\template\\taglib\\' . strtolower($tagLib); } + $tLib = new $className($this); + $tLib->parseTag($content, $hide ? '' : $tagLib); + return; } /** - * 解析标签库的标签 - * 需要调用对应的标签库文件解析类 - * @access private - * @param object $tLib 模板引擎实例 - * @param string $tagLib 标签库名称 - * @param string $tag 标签名 - * @param string $attr 标签属性 - * @param string $content 标签内容 - * @return string + * 分析标签属性 + * @access public + * @param string $str 属性字符串 + * @param string $name 不为空时返回指定的属性名 + * @return array */ - private function parseXmlTag($tLib, $tagLib, $tag, $attr, $content) + public function parseAttr($str, $name = null) { - $attr = stripslashes($attr); - $content = stripslashes($content); - if (ini_get('magic_quotes_sybase')) { - $attr = str_replace('\"', '\'', $attr); + $regex = '/\s+(?>(?\w+)\s*)=(?>\s*)([\"\'])(?(?:(?!\\2).)*)\\2/is'; + $array = []; + if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $array[$match['name']] = $match['value']; + } + unset($matches); + } + if (!empty($name) && isset($array[$name])) { + return $array[$name]; + } else { + return $array; } - $parse = '_' . $tag; - $content = trim($content); - $tags = $tLib->parseXmlAttr($attr, $tag); - return $tLib->$parse($tags, $content); } /** * 模板标签解析 * 格式: {TagName:args [|content] } * @access private - * @param string $tagStr 标签内容 - * @param string $content 原始内容 - * @return string + * @param string $content 要解析的模板内容 + * @return void */ - private function parseTag($tagStr, $content) + private function parseTag(&$content) { - $tagStr = stripslashes($tagStr); + $regex = $this->getRegex('tag'); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $str = stripslashes($match[1]); + $flag = substr($str, 0, 1); + switch ($flag) { + case '$': // 解析模板变量 格式 {$varName} + $this->parseVar($str); + // 是否带有?号 + if (false !== $pos = strpos($str, '?')) { + $array = preg_split('/([!=]={1,2}|(?<]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE); + $name = trim($array[0]); + $this->parseVarFunction($name); - //还原非模板标签 - if (!preg_match('/^[\s|\d]/is', $tagStr)) { - $flag = substr($tagStr, 0, 1); - $flag2 = substr($tagStr, 1, 1); - $name = substr($tagStr, 1); - if ('$' == $flag && '.' != $flag2 && '(' != $flag2) { - //解析模板变量 格式 {$varName} - return $this->parseVar($name); - } elseif ('-' == $flag || '+' == $flag) { - // 输出计算 - return ''; - } elseif (':' == $flag) { - // 输出某个函数的结果 - return ''; - } elseif ('~' == $flag) { - // 执行某个函数 - return ''; - } elseif (substr($tagStr, 0, 2) == '//' || (substr($tagStr, 0, 2) == '/*' && substr($tagStr, -2) == '*/')) { - //注释标签 - return ''; + $str = trim(substr($str, $pos + 1)); + $first = substr($str, 0, 1); + if (isset($array[1])) { + // XXX: 加入这句原本是为解决变量末声明的问题,但$name中是多个条件时会解析错误,故注释掉 + /*if (strpos($name, '[')) { + $name = 'isset(' . $name . ') && ' . $name; + }*/ + $name .= $array[1] . trim($array[2]); + if ('=' == $first) { + // {$varname?='xxx'} $varname为真时才输出xxx + $str = ''; + } else { + $str = ''; + } + } else { + switch ($first) { + case '?': + // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx + $str = ''; + break; + case '=': + // {$varname?='xxx'} $varname为真时才输出xxx + $str = ''; + break; + case ':': + // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx + $str = ''; + break; + default: + if (strpos($str, ':')) { + // {$varname ? 'a' : 'b'} $varname为真时输出a,否则输出b + $str = ''; + } else { + $str = ''; + } + } + } + } else { + $this->parseVarFunction($str); + $str = ''; + } + break; + case ':': // 输出某个函数的结果 + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; + break; + case '~': // 执行某个函数 + $str = substr($str, 1); + $str = ''; + break; + case '-': + case '+': // 输出计算 + $str = ''; + break; + case '/': // 注释标签 + $flag2 = substr($str, 1, 1); + if ($flag2 == '/' || ($flag2 == '*' && substr(rtrim($str), -2) == '*/')) { + $str = ''; + } + break; + default: + // 未识别的标签直接返回 + $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end']; + break; + } + $content = str_replace($match[0], $str, $content); } + unset($matches); } - // 非法标签直接返回 - return $content; + return; } /** * 模板变量解析,支持使用函数 * 格式: {$varname|function1|function2=arg1,arg2} - * @access private - * @param string $varStr 变量数据 - * @return string + * @access public + * @param string $varStr 变量数据 + * @return void */ - private function parseVar($varStr) + public function parseVar(&$varStr) { - $varStr = trim($varStr); - static $_varParseList = []; - //如果已经解析过该变量字串,则直接返回变量值 - if (isset($_varParseList[$varStr])) { - return $_varParseList[$varStr]; - } - $parseStr = ''; - if (!empty($varStr)) { - $varArray = explode('|', $varStr); - //取得变量名称 - $var = array_shift($varArray); - if ('Think.' == substr($var, 0, 6)) { - // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 - $name = $this->parseThinkVar($var); - } elseif (false !== strpos($var, '.')) { - //支持 {$var.property} - $vars = explode('.', $var); - $var = array_shift($vars); - $name = '$' . $var; - if (count($vars) > 1) { - foreach ($vars as $key => $val) { - $name .= '["' . $val . '"]'; - } + $varStr = trim($varStr); + if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) { + static $_varParseList = []; + while ($matches[0]) { + $match = array_pop($matches[0]); + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varParseList[$match[0]])) { + $parseStr = $_varParseList[$match[0]]; } else { - // 一维自动识别对象和数组 - $name = 'is_array($' . $var . ')?$' . $var . '["' . $vars[0] . '"]:$' . $var . '->' . $vars[0]; + if (strpos($match[0], '.')) { + $vars = explode('.', $match[0]); + $first = array_shift($vars); + if ($first == '$Think') { + // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 + $parseStr = $this->parseThinkVar($vars); + } else { + // 一维自动识别对象和数组 + $parseStr = 'is_array(' . $first . ')?' . $first . '[\'' . implode('\'][\'', $vars) . '\']:' . $first . '->' . implode('->', $vars); + } + } else { + $parseStr = str_replace(':', '->', $match[0]); + } + $_varParseList[$match[0]] = $parseStr; } - } elseif (false !== strpos($var, '[')) { - //支持 {$var['key']} 方式输出数组 - $name = "$" . $var; - } elseif (false !== strpos($var, ':') && false === strpos($var, '(') && false === strpos($var, '::') && false === strpos($var, '?')) { - //支持 {$var:property} 方式输出对象的属性 - $vars = explode(':', $var); - $var = str_replace(':', '->', $var); - $name = "$" . $var; - } else { - $name = "$$var"; + $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0])); } - //对变量使用函数 - if (count($varArray) > 0) { - $name = $this->parseVarFunction($name, $varArray); - } - $parseStr = ''; + unset($matches); } - $_varParseList[$varStr] = $parseStr; - return $parseStr; + return; } /** - * 对模板变量使用函数 + * 对模板中使用了函数的变量进行解析 * 格式 {$varname|function1|function2=arg1,arg2} - * @access private - * @param string $name 变量名 - * @param array $varArray 函数列表 - * @return string + * @access public + * @param string $varStr 变量字符串 + * @return void */ - private function parseVarFunction($name, $varArray) + public function parseVarFunction(&$varStr) { - //对变量使用函数 - $length = count($varArray); - //取得模板禁止使用函数列表 - $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']); - 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 (!in_array($fun, $template_deny_funs)) { - 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)"; - } - } - } + if (false == strpos($varStr, '|')) { + return; } - return $name; + static $_varFunctionList = []; + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varFunctionList[$varStr])) { + $varStr = $_varFunctionList[$varStr]; + } else { + $varArray = explode('|', $varStr); + // 取得变量名称 + $name = array_shift($varArray); + // 对变量使用函数 + $length = count($varArray); + // 取得模板禁止使用函数列表 + $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']); + for ($i = 0; $i < $length; $i++) { + $args = explode('=', $varArray[$i], 2); + // 模板函数过滤 + $fun = trim($args[0]); + switch ($fun) { + case 'default': // 特殊模板函数 + $varStr = '(isset(' . $name . ') && (' . $name . ' !== \'\'))?(' . $name . '):' . $args[1]; + break; + default: // 通用模板函数 + if (!in_array($fun, $template_deny_funs)) { + if (isset($args[1])) { + if (strstr($args[1], '###')) { + $args[1] = str_replace('###', $name, $args[1]); + $name = "$fun($args[1])"; + } else { + $varStr = "$fun($name,$args[1])"; + } + } else { + if (!empty($args[0])) { + $name = "$fun($name)"; + } + } + } + } + } + $varStr = $name; + } + return; } /** * 特殊模板变量解析 * 格式 以 $Think. 打头的变量属于特殊模板变量 - * @access private - * @param string $varStr 变量字符串 + * @access public + * @param array $vars 变量数组 * @return string */ - private function parseThinkVar($varStr) + public function parseThinkVar(&$vars) { - $vars = explode('.', $varStr); - $vars[1] = strtoupper(trim($vars[1])); + $vars[0] = strtoupper(trim($vars[0])); $parseStr = ''; - if (count($vars) >= 3) { - $vars[2] = trim($vars[2]); - switch ($vars[1]) { + if (count($vars) >= 2) { + $vars[1] = trim($vars[1]); + switch ($vars[0]) { case 'SERVER': - $parseStr = '$_SERVER[\'' . strtoupper($vars[2]) . '\']'; + $parseStr = '$_SERVER[\'' . strtoupper($vars[1]) . '\']'; break; case 'GET': - $parseStr = '$_GET[\'' . $vars[2] . '\']'; + $parseStr = '$_GET[\'' . $vars[1] . '\']'; break; case 'POST': - $parseStr = '$_POST[\'' . $vars[2] . '\']'; + $parseStr = '$_POST[\'' . $vars[1] . '\']'; break; case 'COOKIE': - if (isset($vars[3])) { - $parseStr = '$_COOKIE[\'' . $vars[2] . '\'][\'' . $vars[3] . '\']'; + if (isset($vars[2])) { + $parseStr = '$_COOKIE[\'' . $vars[1] . '\'][\'' . $vars[2] . '\']'; } else { - $parseStr = '\\think\\cookie::get(\'' . $vars[2] . '\')'; + $parseStr = '\\think\\cookie::get(\'' . $vars[1] . '\')'; } break; case 'SESSION': - if (isset($vars[3])) { - $parseStr = '$_SESSION[\'' . $vars[2] . '\'][\'' . $vars[3] . '\']'; + if (isset($vars[2])) { + $parseStr = '$_SESSION[\'' . $vars[1] . '\'][\'' . $vars[2] . '\']'; } else { - $parseStr = '\\think\\session::get(\'' . $vars[2] . '\')'; + $parseStr = '\\think\\session::get(\'' . $vars[1] . '\')'; } break; case 'ENV': - $parseStr = '$_ENV[\'' . strtoupper($vars[2]) . '\']'; + $parseStr = '$_ENV[\'' . strtoupper($vars[1]) . '\']'; break; case 'REQUEST': - $parseStr = '$_REQUEST[\'' . $vars[2] . '\']'; + $parseStr = '$_REQUEST[\'' . $vars[1] . '\']'; break; case 'CONST': - $parseStr = strtoupper($vars[2]); + $parseStr = strtoupper($vars[1]); break; case 'LANG': - $parseStr = '\\think\\lang::get("' . $vars[2] . '")'; + $parseStr = '\\think\\lang::get(\'' . $vars[1] . '\')'; break; case 'CONFIG': - if (isset($vars[3])) { - $vars[2] .= '.' . $vars[3]; + if (isset($vars[2])) { + $vars[1] .= '.' . $vars[2]; } - $parseStr = '\\think\\config::get("' . $vars[2] . '")'; + $parseStr = '\\think\\config::get(\'' . $vars[1] . '\')'; break; default: 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; - case 'LDELIM': - $parseStr = $this->config['tpl_begin']; - break; - case 'RDELIM': - $parseStr = $this->config['tpl_end']; - break; - default: - if (defined($vars[1])) { - $parseStr = $vars[1]; - } + } else { + if (count($vars) == 1) { + switch ($vars[0]) { + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'VERSION': + $parseStr = 'THINK_VERSION'; + break; + case 'LDELIM': + $parseStr = $this->config['tpl_begin']; + break; + case 'RDELIM': + $parseStr = $this->config['tpl_end']; + break; + default: + if (defined($vars[0])) { + $parseStr = $vars[0]; + } + } } } return $parseStr; } - /** - * 加载公共模板并缓存 和当前模板在同一路径,否则使用相对路径 - * @access private - * @param string $tmplPublicName 公共模板文件名 - * @param array $vars 要传递的变量列表 - * @return string - */ - private function parseIncludeItem($tmplPublicName, $vars = []) - { - // 分析模板文件名并读取内容 - $parseStr = $this->parseTemplateName($tmplPublicName); - // 替换变量 - foreach ($vars as $key => $val) { - if (strpos($parseStr, '[' . $key . ']')) { - $parseStr = str_replace('[' . $key . ']', $val, $parseStr); - } - } - // 再次对包含文件进行模板分析 - return $this->parseInclude($parseStr); - } - /** * 分析加载的模板文件并读取内容 支持多个模板文件读取 * @access private - * @param string $tmplPublicName 模板文件名 + * @param string $templateName 模板文件名 * @return string */ private function parseTemplateName($templateName) { - if (substr($templateName, 0, 1) == '$') { + if ('$' == substr($templateName, 0, 1)) { //支持加载变量文件名 $templateName = $this->get(substr($templateName, 1)); } $array = explode(',', $templateName); $parseStr = ''; foreach ($array as $templateName) { + if (empty($templateName)) { + continue; + } $template = $this->parseTemplateFile($templateName); // 获取模板文件内容 $parseStr .= file_get_contents($template); @@ -809,6 +871,12 @@ class Template return $parseStr; } + /** + * 解析模板文件名 + * @access private + * @param string $template 文件名 + * @return string + */ private function parseTemplateFile($template) { if (false === strpos($template, '.')) { @@ -817,4 +885,65 @@ class Template return $template; } } + + /** + * 按标签生成正则 + * @access private + * @param string $tagName 标签名 + * @return string + */ + private function getRegex($tagName) + { + $begin = $this->config['taglib_begin']; + $end = $this->config['taglib_end']; + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + $regex = ''; + switch ($tagName) { + case 'block': + if ($single) { + $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?[\w\/\:@,]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; + } else { + $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?[\w\/\:@,]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end; + } + break; + case 'literal': + if ($single) { + $regex = '(' . $begin . $tagName . '\b(?>[^' . $end . ']*)' . $end . ')'; + $regex .= '(?:(?>[^' . $begin . ']*)(?>(?!' . $begin . '(?>' . $tagName . '\b[^' . $end . ']*|\/' . $tagName . ')' . $end . ')' . $begin . '[^' . $begin . ']*)*)'; + $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; + } else { + $regex = '(' . $begin . $tagName . '\b(?>(?:(?!' . $end . ').)*)' . $end . ')'; + $regex .= '(?:(?>(?:(?!' . $begin . ').)*)(?>(?!' . $begin . '(?>' . $tagName . '\b(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end . ')' . $begin . '(?>(?:(?!' . $begin . ').)*))*)'; + $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; + } + break; + case 'restoreliteral': + $regex = ''; + break; + case 'include': + $name = 'file'; + case 'taglib': + case 'layout': + case 'extend': + if (empty($name)) { + $name = 'name'; + } + if ($single) { + $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?[\w\/\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; + } else { + $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?[\w\/\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; + } + break; + case 'tag': + $begin = $this->config['tpl_begin']; + $end = $this->config['tpl_end']; + if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) { + $regex = $begin . '((?:[\$\:\-\+][a-wA-w_][\w\.\:\[\(\*\/\-\+\%_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; + } else { + $regex = $begin . '((?:[\$\:\-\+][a-wA-w_][\w\.\:\[\(\*\/\-\+\%_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end; + } + break; + } + return '/' . $regex . '/is'; + } } diff --git a/library/think/template/taglib.php b/library/think/template/taglib.php index 2117e002..3054bcda 100644 --- a/library/think/template/taglib.php +++ b/library/think/template/taglib.php @@ -67,18 +67,163 @@ class TagLib protected $comparison = [' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < ']; + /** + * 架构函数 + * @access public + * @param class $template 模板引擎对象 + */ public function __construct($template) { $this->tpl = $template; } + /** + * 按签标库替换页面中的标签 + * @access public + * @param string $content 模板内容 + * @param string $lib 标签库名 + * @return void + */ + public function parseTag(&$content, $lib = '') + { + $lib = strtolower($lib); + $tags = []; + foreach ($this->tags as $name => $val) { + $close = !isset($val['close']) || $val['close'] ? 1 : 0; + $_key = $lib ? $lib . ':' . $name : $name; + $tags[$close][$_key] = $name; + if (isset($val['alias'])) { + // 别名设置 + foreach (explode(',', $val['alias']) as $v) { + $_key = $lib ? $lib . ':' . $v : $v; + $tags[$close][$_key] = $name; + } + } + } + + // 闭合标签 + if (!empty($tags[1])) { + $nodes = []; + $regex = $this->getRegex(array_keys($tags[1]), 1); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = []; + foreach ($matches as $match) { + if ($match[1][0] == '') { + $name = $match[2][0]; + // 如果有没闭合的标签头则取出最后一个 + if (!empty($right[$name])) { + // $match[0][1]为标签结束符在模板中的位置 + $nodes[$match[0][1]] = [ + 'name' => $name, + 'begin' => array_pop($right[$name]), // 标签开始符 + 'end' => $match[0] // 标签结束符 + ]; + } else { + continue; + } + } else { + // 标签头压入栈 + $right[$match[1][0]][] = $match[0]; + } + } + unset($right, $matches); + // 按标签在模板中的位置从后向前排序 + krsort($nodes); + } + + $break = ''; + if ($nodes) { + $beginArray = []; + // 标签替换 从后向前 + foreach ($nodes as $pos => $node) { + // 对应的标签名 + $name = $tags[1][$node['name']]; + // 解析标签属性 + $attrs = $this->parseAttr($node['begin'][0], $name); + $method = '_' . $name; + // 读取标签库中对应的标签内容 replace[0]用来替换标签头,replace[1]用来替换标签尾 + $replace = explode($break, $this->$method($attrs, $break, $node['name'])); + if (count($replace) > 1) { + while ($beginArray) { + $begin = end($beginArray); + // 判断当前标签尾的位置是否在栈中最后一个标签头的后面,是则为子标签 + if ($node['end'][1] > $begin['pos']) { + break; + } else { + // 不为子标签时,取出栈中最后一个标签头 + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + // 替换标签尾部 + $content = substr_replace($content, $replace[1], $node['end'][1], strlen($node['end'][0])); + // 把标签头压入栈 + $beginArray[] = ['pos' => $node['begin'][1], 'len' => strlen($node['begin'][0]), 'str' => $replace[0]]; + } + } + while ($beginArray) { + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + } + // 自闭合标签 + if (!empty($tags[0])) { + $regex = $this->getRegex(array_keys($tags[0]), 0); + $self = &$this; + $content = preg_replace_callback($regex, function ($matches) use (&$tags, &$self) { + $name = $tags[0][$matches[1]]; + // 解析标签属性 + $attrs = $self->parseAttr($matches[0], $name); + $method = '_' . $name; + return $self->$method($attrs, '', $matches[1]); + }, $content); + } + return; + } + + /** + * 按标签生成正则 + * @access private + * @param array|string $tags 标签名 + * @param boolean $close 是否为闭合标签 + * @return string + */ + private function getRegex($tags, $close) + { + $begin = $this->tpl->config['taglib_begin']; + $end = $this->tpl->config['taglib_end']; + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + if (is_array($tags)) { + $tagName = implode('|', $tags); + } else { + $tagName = $tags; + } + if ($single) { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>[^' . $end . ']*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>[^' . $end . ']*)' . $end; + } + } else { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)' . $end; + } + } + return '/' . $regex . '/is'; + } + /** * TagLib标签属性分析 返回标签属性数组 - * @access public - * - * @param $attr - * @param $tag - * + * @access public + * @param string $attr 标签属性字符串 + * @param string $tag 标签名 * @return array * @throws Exception * @internal param string $tagStr 标签内容 @@ -90,53 +235,116 @@ class TagLib } //XML解析安全过滤 $attr = str_replace('&', '___', $attr); - $xml = ''; - $xml = simplexml_load_string($xml); + if (substr($attr, 0, 1) == '<' && substr($attr, -1, 1) == '>') { + $xml = '' . $attr . ''; + } else { + $xml = ''; + } + $xml = simplexml_load_string($xml); if (!$xml) { throw new Exception('_XML_TAG_ERROR_ : ' . $attr); } - $xml = (array) ($xml->tag->attributes()); - $array = array_change_key_case($xml['@attributes']); - if (!is_array($array)) { - return []; - } - - $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']); + $xml = (array)($xml->tag->attributes()); + if (isset($xml['@attributes']) && $result = array_change_key_case($xml['@attributes'])) { + $tag = strtolower($tag); + if (!isset($this->tags[$tag])) { + // 检测是否存在别名定义 + foreach ($this->tags as $key => $val) { + if (isset($val['alias']) && in_array($tag, explode(',', $val['alias']))) { + $item = $val; + break; + } + } } else { - $must = []; + $item = $this->tags[$tag]; } - foreach ($attrs as $name) { - if (isset($array[$name])) { - $array[$name] = str_replace('___', '&', $array[$name]); - } elseif (false !== array_search($name, $must)) { - throw new Exception('_PARAM_ERROR_:' . $name); + if (!empty($item['attr'])) { + if (isset($item['must'])) { + $must = explode(',', $item['must']); + } else { + $must = []; + } + $attrs = explode(',', $item['attr']); + foreach ($attrs as $name) { + if (isset($result[$name])) { + $result[$name] = str_replace('___', '&', $result[$name]); + } elseif (false !== array_search($name, $must)) { + throw new Exception('_PARAM_ERROR_:' . $name); + } } } + return $result; + } else { + return []; } + } - return $array; + /** + * 分析标签属性 正则方式 + * @access public + * @param string $str 标签属性字符串 + * @param string $tag 标签名 + * @return array + */ + public function parseAttr($str, $tag) + { + if (ini_get('magic_quotes_sybase')) { + $str = str_replace('\"', '\'', $str); + } + $regex = '/\s+(?>(?\w+)\s*)=(?>\s*)([\"\'])(?(?:(?!\\2).)*)\\2/is'; + $result = []; + if (preg_match_all($regex, $str, $matches)) { + foreach ($matches['name'] as $key => $val) { + $result[$val] = $matches['value'][$key]; + } + $tag = strtolower($tag); + if (!isset($this->tags[$tag])) { + // 检测是否存在别名定义 + foreach ($this->tags as $key => $val) { + if (isset($val['alias']) && in_array($tag, explode(',', $val['alias']))) { + $item = $val; + break; + } + } + } else { + $item = $this->tags[$tag]; + } + if (!empty($item['must'])) { + $must = explode(',', $item['must']); + foreach ($must as $name) { + if (!isset($result[$name])) { + throw new Exception('_PARAM_ERROR_:' . $name); + } + } + } + } else { + // 允许直接使用表达式的标签 + if (!empty($this->tags[$tag]['expression'])) { + static $_taglibs; + if (!isset($_taglibs[$tag])) { + $_taglibs[$tag][0] = strlen($this->tpl->config['taglib_begin'] . $tag); + $_taglibs[$tag][1] = strlen($this->tpl->config['taglib_end']); + } + $str = substr($str, $_taglibs[$tag][0], -$_taglibs[$tag][1]); + $result['expression'] = trim($str); + } elseif (empty($this->tags[$tag]) || !empty($this->tags[$tag]['attr'])) { + throw new Exception('_XML_TAG_ERROR_:' . $tag); + } + } + return $result; } /** * 解析条件表达式 * @access public - * @param string $condition 表达式标签内容 - * @return array + * @param string $condition 表达式标签内容 + * @return string */ 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); - } - + $this->tpl->parseVar($condition); + $this->tpl->parseVarFunction($condition); // XXX: 此句能解析表达式中用|分隔的函数,但表达式中如果有|、||这样的逻辑运算就产生了歧异 return $condition; } @@ -146,133 +354,30 @@ class TagLib * @param string $name 变量描述 * @return string */ - public function autoBuildVar($name) + 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 . '"]'; - } + $flag = substr($name, 0, 1); + if (':' == $flag) { + // 以:开头为函数调用,解析前去掉: + $name = substr($name, 1); + } elseif ('$' != $flag && preg_match('/[a-zA-Z_]/', $flag)) { // XXX: 这句的写法可能还需要改进 + // 常量不需要解析 + if (defined($name)) { + return $name; } - } elseif (strpos($name, ':')) { - // 额外的对象方式支持 - $name = '$' . str_replace(':', '->', $name); - } elseif (!defined($name)) { + // 不以$开头并且也不是常量,自动补上$前缀 $name = '$' . $name; } + $this->tpl->parseVar($name); + $this->tpl->parseVarFunction($name); return $name; } /** - * 用于标签属性里面的特殊模板变量解析 - * 格式 以 Think. 打头的变量属于特殊模板变量 + * 获取标签列表 * @access public - * @param string $varStr 变量字符串 - * @return string + * @return array */ - 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 = '\\think\\cookie::get(\'' . $vars[2] . '\')'; - } - break; - case 'SESSION': - if (isset($vars[3])) { - $parseStr = '$_SESSION[\'' . $vars[2] . '\'][\'' . $vars[3] . '\']'; - } else { - $parseStr = '\\think\\session::get(\'' . $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 = '\\think\\Lang::get("' . $vars[2] . '")'; - break; - case 'CONFIG': - if (isset($vars[3])) { - $vars[2] .= '.' . $vars[3]; - } - $parseStr = '\\think\\config::get("' . $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() { diff --git a/library/think/template/taglib/cx.php b/library/think/template/taglib/cx.php index 5d97f77d..81ac2d26 100644 --- a/library/think/template/taglib/cx.php +++ b/library/think/template/taglib/cx.php @@ -2,7 +2,7 @@ // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK IT ] // +---------------------------------------------------------------------- -// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved. +// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved. // +---------------------------------------------------------------------- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) // +---------------------------------------------------------------------- @@ -26,34 +26,34 @@ 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], + 'php' => ['attr' => ''], + 'volist' => ['attr' => 'name,id,offset,length,key,mod', 'alias' => 'iterate'], + 'foreach' => ['attr' => 'name,id,item,key,offset,length,mod', 'expression' => true], + 'if' => ['attr' => 'condition', 'expression' => true], 'elseif' => ['attr' => 'condition', 'close' => 0], 'else' => ['attr' => '', 'close' => 0], - 'switch' => ['attr' => 'name', 'level' => 2], - 'case' => ['attr' => 'value,break', 'level' => 2], + 'switch' => ['attr' => 'name'], + '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], + 'compare' => ['attr' => 'name,value,type', 'alias' => 'eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq'], + 'range' => ['attr' => 'name,value,type', 'alias' => 'in,notin,between,notbetween'], + 'empty' => ['attr' => 'name'], + 'notempty' => ['attr' => 'name'], + 'present' => ['attr' => 'name'], + 'notpresent' => ['attr' => 'name'], + 'defined' => ['attr' => 'name'], + 'notdefined' => ['attr' => 'name'], '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], + 'for' => ['attr' => 'start,end,name,comparison,step'], ]; /** * php标签解析 * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param string $content 标签内容 * @return string */ public function _php($tag, $content) @@ -71,7 +71,7 @@ class Cx extends Taglib * * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param string $content 标签内容 * @return string|void */ public function _volist($tag, $content) @@ -83,8 +83,10 @@ class Cx extends Taglib $mod = isset($tag['mod']) ? $tag['mod'] : '2'; // 允许使用函数设定数据集 {$vo.name} $parseStr = 'autoBuildVar($name); + $parseStr .= '$_result=' . $name . ';'; $name = '$_result'; } else { $name = $this->autoBuildVar($name); @@ -102,7 +104,7 @@ class Cx extends Taglib $parseStr .= 'foreach($__LIST__ as $key=>$' . $id . '): '; $parseStr .= '$mod = ($' . $key . ' % ' . $mod . ' );'; $parseStr .= '++$' . $key . ';?>'; - $parseStr .= ($content); + $parseStr .= $content; $parseStr .= ''; if (!empty($parseStr)) { @@ -115,18 +117,70 @@ class Cx extends Taglib * foreach标签解析 循环输出数据集 * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @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); + // 直接使用表达式 + if (!empty($tag['expression'])) { + $expression = ltrim(rtrim($tag['expression'], ')'), '('); + $expression = $this->autoBuildVar($expression); + $parseStr = ''; + $parseStr .= $content; + $parseStr .= ''; + return $parseStr; + } + $name = $tag['name']; + $key = !empty($tag['key']) ? $tag['key'] : 'key'; + $item = !empty($tag['id']) ? $tag['id'] : $tag['item']; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + + $parseStr = 'autoBuildVar($name); + $parseStr .= $var . '=' . $name . '; '; + $name = $var; + } else { + $name = $this->autoBuildVar($name); + } + $parseStr .= 'if(is_array(' . $name . ')): '; + // 设置了输出数组长度 + if ($offset != 0 || $length != 'null') { + if (!isset($var)) { + $var = '$_' . uniqid(); + } + $parseStr .= $var . ' = array_slice(' . $name . ',' . $offset . ',' . $length . ',true); '; + } else { + $var = &$name; + } + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + $parseStr .= '$' . $index . '=0; '; + } + $parseStr .= 'foreach(' . $var . ' as $' . $key . '=>$' . $item . '): '; + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + if (isset($tag['mod'])) { + $mod = (int)$tag['mod']; + $parseStr .= '$mod = ($' . $index . ' % ' . $mod . '); '; + } + $parseStr .= '++$' . $index . '; '; + } + $parseStr .= '?>'; + // 循环体中的内容 + $parseStr .= $content; $parseStr .= ''; + // 设置了数组为空时的显示内容 + if (isset($tag['empty'])) { + $parseStr .= ''; + } + if (!empty($parseStr)) { return $parseStr; } @@ -143,12 +197,13 @@ class Cx extends Taglib * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param string $content 标签内容 * @return string */ public function _if($tag, $content) { - $condition = $this->parseCondition($tag['condition']); + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); $parseStr = '' . $content . ''; return $parseStr; } @@ -158,12 +213,13 @@ class Cx extends Taglib * 格式:见if标签 * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param string $content 标签内容 * @return string */ public function _elseif($tag, $content) { - $condition = $this->parseCondition($tag['condition']); + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); $parseStr = ''; return $parseStr; } @@ -190,19 +246,13 @@ class Cx extends Taglib * * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @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; } @@ -211,20 +261,15 @@ class Cx extends Taglib * case标签解析 需要配合switch才有效 * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @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); - } - + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); $value = 'case ' . $value . ': '; } elseif (strpos($value, '|')) { $values = explode('|', $value); @@ -248,7 +293,7 @@ class Cx extends Taglib * 使用: ddfdf * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param string $content 标签内容 * @return string */ public function _default($tag) @@ -263,27 +308,22 @@ class Cx extends Taglib * 格式: content * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @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)); + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : $type; + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); } else { - $value = '"' . $value . '"'; + $value = '\'' . $value . '\''; } + $type = $this->parseCondition(' ' . $type . ' '); $parseStr = '' . $content . ''; return $parseStr; } @@ -345,25 +385,20 @@ class Cx extends Taglib * example: content * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 - * @param string $type 比较类型 + * @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); - } + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : $type; - $type = isset($tag['type']) ? $tag['type'] : $type; - - if ('$' == substr($value, 0, 1)) { - $value = $this->autoBuildVar(substr($value, 1)); + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); $str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')'; } else { $value = '"' . $value . '"'; @@ -408,7 +443,7 @@ class Cx extends Taglib * 格式: content * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param string $content 标签内容 * @return string */ public function _present($tag, $content) @@ -425,7 +460,7 @@ class Cx extends Taglib * 格式: content * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param string $content 标签内容 * @return string */ public function _notpresent($tag, $content) @@ -442,7 +477,7 @@ class Cx extends Taglib * 格式: content * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param string $content 标签内容 * @return string */ public function _empty($tag, $content) @@ -464,8 +499,8 @@ class Cx extends Taglib /** * 判断是否已经定义了该常量 * 已定义 - * @param $tag - * @param $content + * @param array $tag + * @param string $content * @return string */ public function _defined($tag, $content) @@ -487,9 +522,9 @@ class Cx extends Taglib * * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 - * @param boolean $isFile 是否文件方式 - * @param string $type 类型 + * @param string $content 标签内容 + * @param boolean $isFile 是否文件方式 + * @param string $type 类型 * @return string */ public function _import($tag, $content, $isFile = false, $type = '') @@ -499,15 +534,9 @@ class Cx extends Taglib $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 . ')'; - } - + $name = $tag['value']; + $name = $this->autoBuildVar($name); + $name = 'isset(' . $name . ')'; $parseStr .= ''; $endStr = ''; } @@ -539,7 +568,11 @@ class Cx extends Taglib // 命名空间方式导入外部文件 $array = explode(',', $file); foreach ($array as $val) { - list($val, $version) = explode('?', $val); + if (strpos($val, '?')) { + list($val, $version) = explode('?', $val); + } else { + $version = ''; + } switch ($type) { case 'js': $parseStr .= ''; @@ -580,14 +613,15 @@ class Cx extends Taglib * 格式: * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @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)); + $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); } else { $value = '\'' . $tag['value'] . '\''; } @@ -601,14 +635,15 @@ class Cx extends Taglib * 格式: * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @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)); + $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); } else { $value = '\'' . $tag['value'] . '\''; } @@ -621,7 +656,7 @@ class Cx extends Taglib * 格式: * @access public * @param array $tag 标签属性 - * @param string $content 标签内容 + * @param string $content 标签内容 * @return string */ public function _for($tag, $content) @@ -636,10 +671,9 @@ class Cx extends Taglib //获取属性 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)); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); } switch ($key) {