+---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- // | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved. // +---------------------------------------------------------------------- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) // +---------------------------------------------------------------------- // | Author: liu21st // +---------------------------------------------------------------------- namespace think\template; use think\Exception; /** * ThinkPHP标签库TagLib解析基类 * @category Think * @package Think * @subpackage Template * @author liu21st */ class TagLib { /** * 标签库定义XML文件 * @var string * @access protected */ protected $xml = ''; protected $tags = []; // 标签定义 /** * 标签库名称 * @var string * @access protected */ protected $tagLib = ''; /** * 标签库标签列表 * @var array * @access protected */ protected $tagList = []; /** * 标签库分析数组 * @var array * @access protected */ protected $parse = []; /** * 标签库是否有效 * @var bool * @access protected */ protected $valid = false; /** * 当前模板对象 * @var object * @access protected */ protected $tpl; protected $comparison = [' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < ']; /** * 架构函数 * @access public * @param \stdClass $template 模板引擎对象 */ public function __construct($template) { $this->tpl = $template; } /** * 按签标库替换页面中的标签 * @access public * @param string $content 模板内容 * @param string $lib 标签库名 * @return void */ public function parseTag(&$content, $lib = '') { $tags = []; $lib = $lib ? strtolower($lib) . ':' : ''; foreach ($this->tags as $name => $val) { $close = !isset($val['close']) || $val['close'] ? 1 : 0; $tags[$close][$lib . $name] = $name; if (isset($val['alias'])) { // 别名设置 $array = (array) $val['alias']; foreach (explode(',', $array[0]) as $v) { $tags[$close][$lib . $v] = $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 = strtolower($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 { // 标签头压入栈 $right[strtolower($match[1][0])][] = $match[0]; } } unset($right, $matches); // 按标签在模板中的位置从后向前排序 krsort($nodes); } $break = ''; if ($nodes) { $beginArray = []; // 标签替换 从后向前 foreach ($nodes as $pos => $node) { // 对应的标签名 $name = $tags[1][$node['name']]; $alias = $lib . $name != $node['name'] ? ($lib ? strstr($node['name'], $lib) : $node['name']) : ''; // 解析标签属性 $attrs = $this->parseAttr($node['begin'][0], $name, $alias); $method = 'tag' . $name; // 读取标签库中对应的标签内容 replace[0]用来替换标签头,replace[1]用来替换标签尾 $replace = explode($break, $this->$method($attrs, $break)); 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); $content = preg_replace_callback($regex, function ($matches) use (&$tags, &$lib) { // 对应的标签名 $name = $tags[0][strtolower($matches[1])]; $alias = $lib . $name != $matches[1] ? ($lib ? strstr($matches[1], $lib) : $matches[1]) : ''; // 解析标签属性 $attrs = $this->parseAttr($matches[0], $name, $alias); $method = 'tag' . $name; return $this->$method($attrs, ''); }, $content); } return; } /** * 按标签生成正则 * @access public * @param array|string $tags 标签名 * @param boolean $close 是否为闭合标签 * @return string */ public 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; $tagName = is_array($tags) ? implode('|', $tags) : $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'; } /** * 分析标签属性 正则方式 * @access public * @param string $str 标签属性字符串 * @param string $name 标签名 * @param string $alias 别名 * @return array */ public function parseAttr($str, $name, $alias = '') { $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is';