diff --git a/demo/base.html b/demo/base.html new file mode 100644 index 0000000..d8efd58 --- /dev/null +++ b/demo/base.html @@ -0,0 +1,14 @@ + + + + + <block name="title">TITLE</block> + + + + + 默认内容,没有定义就不会覆盖我 + + + + \ No newline at end of file diff --git a/demo/footer.html b/demo/footer.html new file mode 100644 index 0000000..dd38f0e --- /dev/null +++ b/demo/footer.html @@ -0,0 +1 @@ +include footer \ No newline at end of file diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..439f66b --- /dev/null +++ b/demo/index.html @@ -0,0 +1,24 @@ + + +Document + + + + {$vo} + + 将上面的内容放入raw标签: + + + {$vo} + + + + {:time()} + + 扩展的标签: + + + + + FOOTER + \ No newline at end of file diff --git a/script.php b/script.php new file mode 100644 index 0000000..a3b74d3 --- /dev/null +++ b/script.php @@ -0,0 +1,26 @@ +compile($content); + +echo "\r\n\r\n第一次结果\r\n\r\n"; +print_r($result); + +// 开始处理用户定义标签库 +$extend = new \template\Extend(); +$result = $extend->parseCustomizeTags($result); +// 用户定义标签库处理结束 + +// 最后还原raw标签 +$result = $template->returnRaw($result); + +echo "\r\n\r\n第二次结果\r\n\r\n"; +print_r($result); diff --git a/template/Engine.php b/template/Engine.php new file mode 100644 index 0000000..8d0e4ea --- /dev/null +++ b/template/Engine.php @@ -0,0 +1,427 @@ +'; + + /** + * @var array 标签定义 + */ + protected $tags = []; + + /** + * @var null 模板配置 + */ + protected $config = null; + + /** + * @var null 模板路径 + */ + private $viewDir = null; + + /** + * @var array 默认标签定义 + */ + private $defaultTags = [ + 'if' => ['attr' => 'condition', 'close' => 1], + 'else' => ['attr' => 'condition', 'close' => 0], + 'volist' => ['attr' => 'name,id,key', 'close' => 1], + 'assign' => ['attr' => 'name,value', 'close' => 0], + 'switch' => ['attr' => 'name', 'close' => 1], + 'case' => ['attr' => 'value', 'close' => 1], + 'default' => ['attr' => '', 'close' => 1] + ]; + + /** + * 构造方法 + * Engine constructor. + * @throws \Exception + */ + public function __construct() + { + // 仅作示例,请在外部传入模板目录 + $this->viewDir = './demo/'; + } + + /** + * 处理模板继承 + */ + private function parseExtend($tmpl) + { + $pattern = '#' . $this->left . 'extend +file=[\'"](.*?)[\'"] +/' . $this->right . '#'; + preg_match($pattern, $tmpl, $matches); + if (!empty($matches)) { + $blockPattern = '#' . $this->left . 'block +name=[\'"](.*?)[\'"]' . $this->right; + $blockPattern .= '([\s\S]*?)' . $this->left . '\/block' . $this->right . '#'; + // 获得被继承的模板内容 + $file = $this->viewDir . $matches[1] . '.html'; + $extendFileContent = null; + if (file_exists($file)) { + $extendFileContent = file_get_contents($file); + } + // 处理继承中的include标签 + $tempContent = $this->parseInclude($extendFileContent); + $extendFileContent = $tempContent !== false ? $tempContent : $extendFileContent; + // 被继承模板中的块 + preg_match_all($blockPattern, $extendFileContent, $extendResult); + // 继承模板中的块 + preg_match_all($blockPattern, $tmpl, $templateResult); + // 组合搜索的块数组 + $search = []; + $defaultContent = []; + for ($i = 0; $i < count($extendResult[0]); $i++) { + $search[$extendResult[1][$i]] = $extendResult[0][$i]; + $defaultContent[$extendResult[1][$i]] = $extendResult[2][$i]; + } + // 组合替换的块数组 + $replace = []; + for ($j = 0; $j < count($templateResult[0]); $j++) { + $replace[$templateResult[1][$j]] = $templateResult[2][$j]; + } + // 块是否在继承模板中存在 + $searchArray = []; + $replaceArray = []; + foreach ($search as $key => $value) { + $searchArray[] = $value; + if (isset($replace[$key])) { + $replaceArray[] = $replace[$key]; + } else { + $replaceArray[] = $defaultContent[$key]; + } + } + $tmpl = str_replace($searchArray, $replaceArray, $extendFileContent); + } + return $tmpl; + } + + /** + * 处理include标签 + * @return bool + */ + private function parseInclude($tmpl) + { + $pattern = '#' . $this->left . 'include +file=[\'"](.*?)[\'"] +/' . $this->right . '#'; + $tmpl = preg_replace_callback($pattern, function ($result) { + $str = null; + $file = $this->viewDir . $result[1] . '.html'; + if (file_exists($file)) { + $str = file_get_contents($file); + } + return $str; + }, $tmpl); + // 处理多层include + if ($this->hasInclude($tmpl)) { + $tmpl = $this->parseInclude($tmpl); + } + return $tmpl; + } + + /** + * 检测是否含有include + * @param $tmpl + * @return bool + */ + private function hasInclude($tmpl) + { + $pattern = '#' . $this->left . 'include +file=[\'"](.*?)[\'"] +/' . $this->right . '#'; + preg_match($pattern, $tmpl, $matches); + return !empty($matches); + } + + /** + * 分析参数以及函数输出 + */ + private function parseVars($tmpl) + { + preg_match_all('#{(.*?)}#', $tmpl, $matches); + $search = []; + $replace = []; + for ($i = 0; $i < count($matches[0]); $i++) { + $start = substr($matches[1][$i], 0, 1); + if ($start == '$') { + $search[] = $matches[0][$i]; + $replace[] = ''; + } elseif ($start == ':') { + $search[] = $matches[0][$i]; + $replace[] = ''; + } + } + $tmpl = str_replace($search, $replace, $tmpl); + return $tmpl; + } + + /** + * 处理用户自定义标签 + * @param $tmpl + * @return null|string|string[] + */ + public function parseCustomizeTags($tmpl) + { + return $this->parseTags($tmpl, $this->tags); + } + + /** + * 处理默认的标签 + * @param $tmpl + * @return null|string|string[] + */ + private function parseDefaultTags($tmpl) + { + return $this->parseTags($tmpl, $this->defaultTags); + } + + /** + * 进行标签处理 + * @param $tmpl + * @param $tags + * @return null|string|string[] + */ + private function parseTags($tmpl, $tags) + { + foreach ($tags as $name => $item) { + $attr = ($item['attr']) ? ' +(.*?)' : null; + $pattern = '#' . $this->left . $name . $attr . ($item['close'] ? $this->right : '\/' . $this->right . '#'); + if ($item['close']) { + $pattern .= '([\s\S]*?)' . $this->left . '\/' . $name . $this->right . '#'; + } + preg_match_all($pattern, $tmpl, $matches); + for ($i = 0; $i < count($matches[0]); $i++) { + $attrPattern = '#(.*?)=[\'"](.*?)[\'"]#'; + preg_match_all($attrPattern, $matches[1][$i], $result); + $tag = []; + if (!empty($result)) { + foreach ($result[1] as $key => $value) { + $tag[trim($value, ' ')] = $result[2][$key]; + } + } + if ($item['close']) { + $tagContent = isset($matches[2][$i]) ? $matches[2][$i] : (($attr) ? null : $matches[1][$i]); + $content = $this->{'_' . $name . '_start'}($tag, $tagContent); + if ($item['close']) { + $content .= $this->{'_' . $name . '_end'}($tag, $tagContent); + } + } else { + $content = $this->{'_' . $name . '_start'}($tag); + } + $tmpl = str_replace($matches[0][$i], $content, $tmpl); + } + } + return preg_replace('#\?>([\r|\n|\s]*?)<\?php#', '', $tmpl); + } + + /** + * 处理raw标签 + * @param $tmpl + * @return null|string|string[] + */ + private function parseRaw($tmpl) + { + $pattern = '#' . $this->left . 'raw' . $this->right . '([\s\S]*?)'; + $pattern .= $this->left . '\/raw' . $this->right . '#'; + $tmpl = preg_replace_callback($pattern, function ($matches) { + return str_replace([ + $this->left, $this->right, + '{', '}' + ], [ + '', + '{raw!--', '--raw}' + ], $matches[1]); + }, $tmpl); + return $tmpl; + } + + /** + * 还原raw + * @param $tmpl + * @return null|string|string[] + */ + public function returnRaw($tmpl) + { + $pattern = '#[{|<]raw!--([\s\S]*?)--raw[>|}]#'; + $tmpl = preg_replace_callback($pattern, function ($matches) { + $left = substr($matches[0], 0, 1); + $right = substr($matches[0], -1); + return $left . $matches[1] . $right; + }, $tmpl); + return $tmpl; + } + + /** + * if标签 + * @param $tag + * @param $content + * @return string + */ + private function _if_start($tag, $content) + { + return '' . $content; + } + + /** + * if标签结束 + * @return string + */ + private function _if_end() + { + return ''; + } + + /** + * else标签(支持条件) + * @param $tag + * @return string + */ + private function _else_start($tag) + { + if (isset($tag['condition'])) { + $parse = ''; + } else { + $parse = ''; + } + return $parse; + } + + /** + * volist标签 + * @param $tag + * @param $content + * @return string + */ + private function _volist_start($tag, $content) + { + if (substr($tag['name'], 0, 1) == ':') { + $name = substr($tag['name'], 1); + } else { + $name = '$' . $tag['name']; + } + $key = (empty($tag['key'])) ? null : '$' . $tag['key'] . ' = 0;'; + $parse = '' . $content; + return $parse; + } + + /** + * volist标签结束 + * @return string + */ + private function _volist_end() + { + return ''; + } + + /** + * assign标签 + * @param $tag + * @return string + */ + private function _assign_start($tag) + { + return ''; + } + + /** + * switch标签 + * @param $tag + * @param $content + * @return string + */ + private function _switch_start($tag, $content) + { + $parse = '' . $content; + return $parse; + } + + /** + * switch标签结束 + * @return string + */ + private function _switch_end() + { + $parse = ''; + return $parse; + } + + /** + * case标签 + * @param $tag + * @param $content + * @return string + */ + private function _case_start($tag, $content) + { + if (is_numeric($tag['value'])) { + $caseVar = $tag['value']; + } else { + $caseVar = '\'' . $tag['value'] . '\''; + } + $parse = '' . $content; + return $parse; + } + + /** + * case标签结束 + * @return string + */ + private function _case_end() + { + $parse = ''; + return $parse; + } + + /** + * default标签 + * @param $tag + * @param $content + * @return string + */ + private function _default_start($tag, $content) + { + $parse = '' . $content; + return $parse; + } + + /** + * default标签结束 + * @return string + */ + private function _default_end() + { + $parse = ''; + return $parse; + } + + /** + * 获取编译后的内容 + * @param $tmpl + * @return bool|mixed|null|string|string[] + */ + public function compile($tmpl) + { + $tmpl = $this->parseRaw($tmpl); + // 处理模板继承标签 + $tmpl = $this->parseExtend($tmpl); + // 处理include标签 + $tmpl = $this->parseInclude($tmpl); + // 处理变量以及函数 + $tmpl = $this->parseVars($tmpl); + // 处理定义的标签 + $tmpl = $this->parseDefaultTags($tmpl); + return $tmpl; + } + +} diff --git a/template/Extend.php b/template/Extend.php new file mode 100644 index 0000000..77e272f --- /dev/null +++ b/template/Extend.php @@ -0,0 +1,16 @@ + ['attr' => 'what', 'close' => 0] + ]; + + protected function _say_start($tag) + { + return ''; + } + +}