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) {