模板引擎其实就是将一个带有自定义标签的字符串,通过相应的规则解析,返回php可以解析的字符串,这其中正则的运用是必不可少的,所以要有一定的正则基础。
总体思想,引入按规则写好的模板,传递给标签解析类(_HtmlTag)进行解析,再把解析好的字符串传递给php进行解析渲染输出
首先定义了一个_HtmlTag类:

class _HtmlTag{
protected $taglist = "if|elseif|else|loop|for|while|=|:=|:e|:html|:";
protected $string;
protected $tpldir;
function __construct($dir, $tpl) {
$this->findReg = "/\\{[ ]*($this->findReg)(\\s*[\\{\\}]+\\s*)?\\}/i";
$this->tpldir = $dir;
$this->tpl =$tpl;
}
}
  • 先解析下render方法
    在render方法中对传入的字符串$s,进行相应的替换渲染,用到了extends方法,import方法,''开头的各种过滤方法,后面会一一进行详解.

    function render($s) {
    $s = str_replace(['<--{', '}-->'], ['{','}'], $s);
    if (strpos($s, "{extends") !== false) {
    $s = $this->_extends($s);
    }
    $s = preg_replace('/[\\n\\r]+/s', "\\n", $s);
    $s = preg_replace('/\\r+/s', "", $s);
    $s = preg_replace('/\\t+/s', "\\t", $s); $this->string = str_replace(["\\{", "\\}"], ["\\HKH1", "\\HKH2"], $s);
    preg_match_all("/\\{[ ]*(import)(\\s*[^\\{\\}]+\\s*)?\\}/i", $s, $d);
    foreach($d[0] as $k => $v) {
    call_user_func_array(array($this, "_import"), array($v, $d[1][$k], $d[2][$k]));
    }
    static $m = array("=" => "_defaultEcho", ":=" =>" _xssEcho", ":e" => "_htmlencodeEcho", ":" => "_echo", ":html" => "_htmldecodeEcho");
    preg_match_all($this->findReg, $this->string, $d);
    foreach ($d[0] as $k => $v) {
    if (isset($m[$d[1][$k]])) {
    $medth = $m[$d[1][$k]];
    } else {
    $medth = '_' . $d[1][$k];
    }
    call_user_func_array(array($this, $medth), array($v, $d[1][$k], $d[2][$k]));
    }
    }
  • _extends方法

    使用 个方法是为了判断是否有继承相应的模板文件,在子文件中用{extends 模板名},一般只要填写文件名,如index,系统会自动去构造完整的文件路径,一般在上一级目录,这个在realTpl()方法时会有详解
    function _extends($tpl) {
    preg_match("/\\{\\s*extends\\s+([^\\{\\}]+?)\\s*\\}/i", $tpl, $d);
    list($search, $arg) = $d;
    if (stripos($arg, ".") !==0 ) {
    $arg = '../' . $arg;
    }
    $file = $this->tpl->me->realTpl($arg . ".htm");
    $basetpl = file_get_contents($file);
    preg_match_all("/\\{block\\s+([^\\{\\}]+?)\\s*\\}(.+)\\{\\/block\\}/s", $tpl, $ds);
    foreach($ds[1] as $n => $name) {
    $basetpl = preg_replace("/\\{block\\s+name={$name}\\s*\\}/", $ds[2][$n], $basetpl);
    }
    $basetpl = preg_replace("/\\{block\\s+name=.+?\\}/", ' ', $basetpl);
    return $basetpl;
    }
  • _import 方法

    _import 方法是用来对{import 文件名}标签进行解析,默认也是上层目录,会把该文件的内容解析到当前标签位置,同时支持使用:函数名|参数1, 参数2,...的方式进行字符串的回调.这中的display方法后面会有详解
    function _import ($search, $tag, $arg) {
    $arg = trim($arg);
    if (stripos($arg, ".") !== 0) {
    $arg = '../' . $arg;
    }
    $file = $this->tpl->me->realTpl($arg . ".htm");
    if (file_exists($file)) {
    $this->string = str_replace($search, file_get_contents($file), $this->string);
    return;
    } else {
    if (stripos($file, "$") !== false) {
    $this->string = str_replace($search, '<? include $this->tpl->display("' . $arg . '"); ?>', $this->string);
    return;
    }
    if (stripos($arg, '|') !== false) {
    list($func, $tmp) = explode("|", trim($arg));
    $kw = explode(', ', $tmp);
    } else {
    $func = trim($arg);
    $kw = array();
    }
    if (function_exists($func) {
    $tpl_str = call_user_func_array($func, $kw);
    $this->string = str_replace($search, $tpl_str, $this->string);
    return;
    } else {
    $this->string = str_replace($search, $arg, $this->string);
    return;
    }
    system_error($this->tpldir . trim($arg) . ".htm不存在");
    }
    }
  • 现在来看一个标签函数_loop,将标签{loop $data $k $v}或{loop $data $v}替换,实现模板循环,其中的parseAttr方法接下来将详解

    function _loop($search, $tag, $arg) {
    list($attr, $arg) = $this->parseAttr($arg);
    $d = preg_split("/\\s+/", trim($arg));
    if (count($d) == 3) {
    $data = $d[0];
    $k = $d[1];
    $v = $d[2];
    $s = "<? \\n if(!empty($data)) {\\n \\t foreach(" . $data . " as {$k} => {$v}){";
    } elseif (count($d) == 2) {
    $data = $d[0];
    $v = $d[1];
    $s = "<? \\n if(!empty($data)){\\n \\t
    foreach(". $datsa . " as {$v}) {";
    } if (isset($attr['counter'])) {
    $s = "\\n \\t\\t<? if (!isset({$attr['counter']})) { {$attr['counter']} = 0;} ?> \\n \\t\\t" . $s . "\\n \\t\\t{$attr['counter']}++; \\n ?>;
    } else {
    $s .= "?>";
    }
    $this->string = str_replace($search, $s, $this->string);
    $this->string = str_replace("{/loop}", "<? \\t}\\n}?>", $this->string);
    }
  • parseAttr方法

    该方法用来解析标签 的参数,返回的第一个参数是值形式字符串,第二个是键值数组,如{loop $data $k $v}返回$data $k $v 组成 的字符串和一个空数组,{if $k = $v}则返回空字符串和和数组[$k => $v];
    function parseAttr($s) {
    $reg = '/([a-zA-Z0-9_])+\\s*=\\s*([a-zA-Z0-9_\\$]+)/i';
    $arg = preg_replace($reg, '', $s);
    preg_match_all($reg, $s, $d);
    $arr = array();
    foreach($d[1] as $key => $value) {
    $arr[trim($value)] = trim($d[2][$key]);
    }
    return array($arr, $arg);
    }
  • _if 方法

    将{if}{/if}标签解析成原生 的php,简单明了,不多解释
    function _if ($search, $tag, $arg) {
    $replace[0] = "<? if($arg){?>";
    $replace[1] = "<? }?>";
    $this->string = str_replace(array($search, "{/$tag}"), $replace, $this->string);
    }
  • _else方法
    将{else}解析成原生的php
    function _else($search, $tag, $arg) {
    $this->string = str_replace("{else}", "\\n" . '<? } else {?>', $this->string);
    }
  • _elseif方法
    将{elseif}解析成原生的php
    function _elseif($search, $tag, $arg) {
    $this->string = str_replace($search, "<? }elseif($arg){?>", $this->string);
    }

      

  • _for 方法
    function _for($search, $tag, $arg) {
    $s = trim(trim(preg_replace('/\\s+/', ' ; ', $arg)), ';');
    $this->string = str_replace($search, "<? for($s){?>", $this->string);
    $this->string = str_replace("{/for}", "<? \\t\\n}?>", $this->string);
    }

      

  • _while方法
    function _while($search, $tag, $arg) {
    $this->string = str_replace($search , "<? while($aeg){ ?>", $this->string);
    $this->string = str_replace("{/while}", "<? \\t\\n} ?>", $this->string);
    }

      

  • _end方法
    通过这个方法对其$this->string进行修改可以对变量进行如下标签定义:
    {$val}{$arr[$key]}{$arr['key']}{:val()}{$arr.key}{$arr.$key}

    function _end() {
    //{$val}标签解析
    $this->string = preg_replace('/\\{(\\$\\w+.*)\\}/is', "<?=\\\\1;?>", $this->string);
    //{:$val()}标签解析
    $this->string = preg_replace('/\\{\\:\\s*(\\$?\\w+.*?)\\}/is', "<? =\\\\1;?>", $this->string);
    //将$val[arg]解析成$val['arg']
    $this->string = preg_replace('/\\$([_a-z]+\\w*)\\[([_a-z]+\\w*)\\]/is', "$\\\\1['\\\\2']", $this->string);
    //支持点号访问数组。如array['key']可以用array.key访问
    $this->string = preg_replace('/\\$([_a-z]+\\w*)\\.(\\$[_a-z]+\\w*)/is', "$\\\\1[\\\\2]", $this->string);
    $this->string = preg_replace('/\\$([_a-z]+\\w*)\\.([_a-z]+\\w*)/is', "$\\\\1['\\\\2']", $this->string);
    $this->string = preg_replace(array("/<\?/", "/<?\php\s*=/"), array("<?php ", "<?php echo "), $this->string);
    }

      

  • _result方法,做最后处理,返回字符串,_end方法在上面已经解释过了

    function result() {
    $this->_end();
    $this->_baseParse($this->stirng);
    return str_replace(array("\\HKH1", "\\HKH2"), array("\\{", "\\}"), $this->string);
    }

      

    下面再来介绍一下几个过滤的方法

  • _htmlencodeEcho方法

    可以用此种形式对变量进行htmlspecialchars式的过滤spspecialchars函数后面会有介绍{:e $val}

    function _htmlencodeEcho($search, $tag, $arg) {
    if (stripos($arg, '$')) {
    $this->string = str_replace($search, "<? echo sphtmlspecialchars(!empty($arg)?$arg:''))?>", $this->string);
    } else {
    $this->string = str_replace($search, "<? echo sphtmlspecialchars($arg)?>", $this->string);
    }
    }

      

  • _htmldecodeEcho方法
    _htmldecodeEcho($search, $tag, $arg) {
    if (stripos($arg, '$')) {
    $this->string = str_replace($search, "<? echo htmlspecialchars_decode(!empty($arg)?$arg:'')?>", $this->string);
    } else {
    $this->string = str_replace($search, "<? echo htmlspecialchars_decode($arg)?>", $this->string);
    }
    }

      

  • _defaultEcho方法
    function _defaultEcho($search, $tag, $arg) {
    if (stripos($arg, ',') == false) {
    $arg . = ", ' ' ";
    list($v, $default) = explode(",", $arg);
    $this->string = str_replace($search, "<? echo empty($v)?$default:$v; ?>", $this->string);
    }
    }

      

  • _xssEcho方法

    _xssEcho($search, $tag, $arg) {
    $this->string = str_replace($search, "<? echo xssRemove($arg); ?>", $this->string);
    }

      

    _echo方法

    function _echo($search, $tag, $arg){
    $this->string = str_replace($search, "<? echo $arg;?>", $this->string);
    }

      

  • _baseParse方法,这个对css和js的引入进行处理

    function _baseParse($s) {
    if (strpos($s, "{loadCss") !== false) {
    $s = $this->_loadCss($s);
    }
    if (strpos($s, "{loadJs") !== false) {
    $s = $this->_loadJs($s);
    }
    return $s;
    }

      

  • _loadJs方法
    function _loadJs($tpl) {
    preg_match_all('/\{loadJs\s+([^\{\}]+?)\s*\}/i', $tpl, $match);
    include "jsmin.php";
    foreach($match[1] as $k => $js) {
    $jsstr = "";
    $cdn = "";
    $if (stripos($js, ',') !== false) {
    $filename = "";
    $md5 = "";
    $js_content = "";
    foreach(explode(',', $js) as $j) {
    $filename .= basename($j, '.js') . ',';
    $md5 .= self::fileMd5($j);
    if (stripos($j, '.min.') == false) {
    $js_content .= JSMin::minify(file_get_contents(WEB_ROOT . $j));
    } else {
    $js_content .= file_get_contents(WEB_ROOT . $j);
    }
    $js_content .= ';'
    }
    $md5 = md5($md5);
    $filename = dirname($j) . '/' .trim($filename, ',') . '.js';
    self::parseJs($js_content, $filename);
    $jsstr = "<script src=\"{$cdn}{$filename}?v={$md5}\"></script>";
    } else {
    $md5 = self::fileMd5($js);
    if (stripos($js, ".min.") === false) {
    $js = self::pareJs(JSMin::minify(file_get_contents(WEB_ROOT . $js)), dirname($js). '/' .basename($js, '.js') . '.min.js');
    }
    $jsstr = "<script src=\"{$cdn}{$js}?v={$mdt}\"></script>";
    }
    $tpl = str_replace($match[0][$k], $jsstr, $tpl);
    }
    return $tpl;
    }

      

    _loadCss方法

    function _loadCss($tpl) {
    preg_match_all('/\{loadCss\s+([^\{\}]+)\s*\}/i' , $tpl , $match);
    $cdn = "";
    foreach($match[1] as $k => $css) {
    $cssstr = "";
    if (stripos($css, ',') !== false) {
    $filename = "";
    $md5 = "";
    $css_content = "";
    foreach($explode(',', $css) as $css) {
    $filename .= basename($css, '.css') . ',';
    $md5 = self::fileMd5($css);
    $css_content .= file_get_contents(WEB_ROOT .$css);
    }
    $md5 = md5($md5);
    $filename = dirname($css) . '/' .trim($filename, ',') . '.css';
    self::parseCss($css_content, $filename);
    $cssstr = "<link rel=\"stylesheet\" href=\"{$cdn}{$filename}?v={md5}\">";
    } else {
    $md5 = self::fileMd5($css);
    if (stripos($css, ".min.") == false) {
    $css = self::parseCss(file_get_contents(WEB_ROOT . $css), dirname($css) . '/' .basename($css, '.css') . '.min.css');
    }
    $cssstr .= "<link rel=\"stylesheet\" href=\"{$cdn}{$css}?v={$md5}\">";
    }
    $tpl = str_replace($match[0][$k], $cssstr, $tpl);
    }
    return $tpl;
    }

      

  • 上面两个函数都用到了各自的parseCss,和parseJs,让我们来看下
    static function parseCss($css_content, $filename) {
    $css_content = preg_replace("/[\r\n\t]/", '', $css_content);
    $css_content = preg_replace("/ +/", ' ', $css_content);
    sp_file_pu_contents(WEB_ROOT . $filename, $css_content);
    return $filename;
    }
    static function parseJs($js, $filename) {
    sp_file_put_content(WEB_ROOT . $filename, $js);
    return $filename;
    }

      


上面的_html类和他的方法对传入的字符串进行了标签到原生 Php的替换,可以传入相应的模板,也可以在其中自定义自己的过滤方法。

下面我们再来看一个template类:

定义这个类来对模板进行管理,这里会用到上面的_html类,基本定义如下,接下来会介绍其中的一些方法

class template {
private $tpl_cache_dir;
public $ext = ".htm";
public $default_tpl_dir = false;
public $me; function _construct($tpl_cache_dir, $me) {
$this->sets($tpl_cache_dir);
$this->me = $me;
}
function sets($tpl_cache_dir) {
$this->tpl_cache_dir = $tpl_cache_dir;
if (SP_DEBUG == 1) {
spmkdir($this->tpl_cache_dir);
}
}
function default_template($dir) {
$this->default_tpl_dir = $dir;
}
}

  

  • getString方法

    在之前 开启ob_start(),然后获取这之间的ob数据,试用于框架中

    function getString() {
    $s = ob_get_contents();
    ob_clean();
    return $s;
    }

      

  • display方法

    function display($tpl, $return = 0, $script = SCRIPT) {
    $tpl .= $this->ext;
    $tpl_name = $this->tpl_cache_dir . $script . '_' . str_replace(['/', '\\\\'], '_', $tpl);
    if (SP_DEBUG){
    if (SP_DEBUG == 1 || (SP_DEBUG == 2 && !file_exists($tpl_name))) {
    $this->compiles($tpl_name, $tpl);
    }
    }
    return $tpl_name;
    }

      

  • compiles方法, 在这里使用了sp_file_put_contentsb函数会在后面介绍
    function compiles($cache_name, $tpl) {
    if (!file_exists($tpl)) {
    if (file_exists($this->default_tpl_dir . $tpl)) {
    $html = file_get_contents($this->default_tpl_dir . $tpl);
    } else {
    spmkdir(dirname($tpl), '777');
    file_get_contents($tpl, 这个是自动生成模板');
    }
    } else {
    $html = file_get_contents($tpl);
    }
    $tag = new _HtmlTag(realpath(dirname($tpl) . '/../'), $this);
    $tag->render($html);
    $html = preg_replace_callback('//s', array(&$this, "make_magic_func"), $tag->result());
    $html = preg_replace('/[\\n\\r]{1,}/s', "\\n", $html);
    @unlink($cache_name);
    sp_file_put_contents($cache_name, $thml);
    }

      

  • make_magic_func方法
    function make_magic_func($d) {
    preg_match_all('/(\\w+)=\\s*(\\'|\\")([^\\'"]+)?\\2/m', $d[2], $dd);
    $aa = array();
    foreach($dd[1] as $i => $k) {
    $vs = preg_replace('/([^\\-]*[<>]{1,1})\\s*(\\S+)?\\s*/', '\\1\\'\\2\\' ', $dd[3][$i]);
    $vs = preg_replace('/=\\s*(\\S+)?\\s*/', '=\\'\\1\\'', $dd[3][$i]);
    if (stripos($d[3][$i], '$') !== false) {
    $vs = preg_replace('/\\$([^\\']+)\\s*/', '{$\\1}', $vs);
    }
    }
    }

    注意

  • 两个类的相应方法都 定义好了,这其中用到的函数sp_file_pu_contents($file, $data)

    function sp_file_put_contents($file, $data) {
    $dir = dirname($file);
    if (is_writeable($dir)) {
    return file_put_contents($file, $data);
    } else {
    throw new writeFail(sprintf("写入文件%s,失败,目录不可写", $file), 503);
    }
    }

    js压缩类: jsmin.class.php
    链接:http://pan.baidu.com/s/1kUBx0X5 密码:y5jb
    将这两个 类定义在一个文件中,使用进进行相应的配置

    define("WEB_ROOT", dirname(__FILE__) . '/');

   使用

  • 文件准备

    base.htm,main.htm,test.js,test1.js,test.css,test1.css,indx.php,im.htm
  • main.htm:

    {import ./im}
    {block name=head}
    {block name=body}
    {block name=foot}
  • base.htm
    {extends ./main}
    {block head}
    {loadCss test.css,test1.css}
    {loop $data $k $v}
    <p>
    我是头部
    </p>
    {/loop}
    {if $bb == 'aa'}
    <p>
    我是if 测试
    </p>
    {elseif $bb = 'bb'}
    <p>
    我是elesif测试
    </p>
    {else}
    <p>
    我是eles测试
    </p>
    {/if}
    {for $i=0 $i<10 $i++}
    <p>
    我是for循环
    </p>
    {/for}
    {while $con >5}
    <? $con--;?>
    <p>
    我是while循环
    </p>
    {/while}
    {/block}
    {block foot}
    <p>
    我是尾部
    </p>
    {loadJs test1.js,test.js}
    {/block}
  • im.htm
    我是import进来的
  • index.php
<?php
define('WEB_ROOT',dirname(__FILE__) . '/');
function sp_file_put_contents($file, $data) {
$dir = dirname($file);
if (is_writeable($dir)) {
return file_put_contents($file, $data);
} else {
throw new writeFail(sprintf("写入文件%s,失败,目录不可写", $file), 503);
}
}
class _HtmlTag{
protected $taglist = "if|elseif|else|loop|for|while|=|:=|:e|:html|:";
protected $string;
protected $tpldir;
public $findReg;
function __construct($dir, $tpl) {
$this->findReg = "/\{[ ]*($this->taglist)(\s*[^\{\}]+\s*)?\}/i";
$this->tpldir = $dir;
$this->tpl =$tpl;
}
function render($s) {
$s = str_replace(['<--{', '}-->'], ['{','}'], $s);
if (strpos($s, "{extends") !== false) {
$s = $this->_extends($s);
}
$s = preg_replace('/[\n\r]+/s', "\n", $s);
$s = preg_replace('/\r+/s', "", $s);
$s = preg_replace('/\t+/s', "\t", $s);
$this->string = str_replace(["\{", "\}"], ["\HKH1", "\HKH2"], $s);
preg_match_all("/\{[ ]*(import)(\s*[^\{\}]+\s*)?\}/i", $s, $d);
foreach($d[0] as $k => $v) {
call_user_func_array(array($this, "_import"), array($v, $d[1][$k], $d[2][$k]));
}
static $m = array("=" => "_defaultEcho", ":=" =>" _xssEcho", ":e" => "_htmlencodeEcho", ":" => "_echo", ":html" => "_htmldecodeEcho");
preg_match_all($this->findReg, $this->string, $d);
foreach ($d[0] as $k => $v) {
if (isset($m[$d[1][$k]])) {
$medth = $m[$d[1][$k]];
} else {
$medth = '_' . $d[1][$k];
}
call_user_func_array(array($this, $medth), array($v, $d[1][$k], $d[2][$k]));
}
}
function _extends($tpl) {
preg_match("/\{\s*extends\s+([^\{\}]+?)\s*\}/i", $tpl, $d);
list($search, $arg) = $d;
if (stripos($arg, ".") !==0 ) {
$arg = '../' . $arg;
}
//$file = $this->tpl->me->realTpl($arg . ".htm");
$file = $arg . ".htm";
$basetpl = file_get_contents($file);
preg_match_all("/\{block\s+([^\{\}]+?)\s*\}(.+?)\{\/block\}/s", $tpl, $ds);
foreach($ds[1] as $n => $name) {
$basetpl = preg_replace("/\{block\s+name={$name}\s*\}/", $ds[2][$n], $basetpl);
}
$basetpl = preg_replace("/\{block\s+name=.+?\}/", ' ', $basetpl); return $basetpl;
}
function _import ($search, $tag, $arg) { $arg = trim($arg);
if (stripos($arg, ".") !== 0) {
$arg = '../' . $arg;
}
//$file = $this->tpl->me->realTpl($arg . ".htm");
$file = $arg . ".htm";
if (file_exists($file)) {
$this->string = str_replace($search, file_get_contents($file), $this->string);
return;
} else {
if (stripos($file, "$") !== false) {
$this->string = str_replace($search, '<? include $this->tpl->display("' . $arg . '"); ?>', $this->string);
return;
}
if (stripos($arg, '|') !== false) {
list($func, $tmp) = explode("|", trim($arg));
$kw = explode(', ', $tmp);
} else {
$func = trim($arg);
$kw = array();
}
if (function_exists($func)) {
$tpl_str = call_user_func_array($func, $kw);
$this->string = str_replace($search, $tpl_str, $this->string);
return;
} else {
$this->string = str_replace($search, $arg, $this->string);
return;
}
system_error($this->tpldir . trim($arg) . ".htm不存在");
}
} function mytest(){
return $this->string;
} function _baseParse($s) {
if (strpos($s, "{loadCss") !== false) {
$s = $this->_loadCss($s);
}
if (strpos($s, "{loadJs") !== false) {
$s = $this->_loadJs($s);
}
return $s;
} function fileMd5($f) {
if (stripos($f, "http://") !== false) {
return md5($f);
}
return md5_file(WEB_ROOT . $f);
} function _loadCss($tpl) {
preg_match_all('/\{loadCss\s+([^\{\}]+)\s*\}/i' , $tpl , $match);
$cdn = "";
foreach($match[1] as $k => $css) {
$cssstr = "";
if (stripos($css, ',') !== false) {
$filename = "";
$md5 = "";
$css_content = "";
foreach(explode(',', $css) as $css) {
$filename .= basename($css, '.css') . ',';
$md5 = self::fileMd5($css);
$css_content .= file_get_contents(WEB_ROOT .$css);
}
$md5 = md5($md5);
$filename = dirname($css) . '/' . trim($filename, ',') . '.css';
self::parseCss($css_content, $filename);
$cssstr = "<link rel=\"stylesheet\" href=\"{$cdn}{$filename}?v={$md5}\">";
} else {
$md5 = self::fileMd5($css);
if (stripos($css, ".min.") == false) {
$css = self::parseCss(file_get_contents(WEB_ROOT . $css), dirname($css) . '/' .basename($css, '.css') . '.min.css');
}
$cssstr .= "<link rel=\"stylesheet\" href=\"{$cdn}{$css}?v={$md5}\">";
}
$tpl = str_replace($match[0][$k], $cssstr, $tpl);
}
return $tpl;
} function _loadJs($tpl) { preg_match_all('/\{loadJs\s+([^\{\}]+?)\s*\}/i', $tpl, $match);
//var_dump($match);die;
include "jsmin.class.php";
foreach($match[1] as $k => $js) {
$jsstr = "";
$cdn = "";
if (stripos($js, ',') !== false) {
$filename = "";
$md5 = "";
$js_content = "";
foreach(explode(',', $js) as $j) {
$filename .= basename($j, '.js') . ',';
$md5 .= self::fileMd5($j);
if (stripos($j, '.min.') === false) {
$js_content .= JSMin::minify(file_get_contents(WEB_ROOT . $j));
} else {
$js_content .= file_get_contents(WEB_ROOT . $j);
}
$js_content .= ';';
}
$md5 = md5($md5); $filename = dirname($js) . '/' .trim($filename, ',') . '.js';
self::parseJs($js_content, $filename);
$jsstr = "<script src=\"{$cdn}{$filename}?v={$md5}\"></script>";
} else {
$md5 = self::fileMd5($js);
if (stripos($js, ".min.") === false) {
$js = self::pareJs(JSMin::minify(file_get_contents(WEB_ROOT . $js)), dirname($js). '/' .basename($js, '.js') . '.min.js');
}
$jsstr = "<script src=\"{$cdn}{$js}?v={$mdt}\"></script>";
}
$tpl = str_replace($match[0][$k], $jsstr, $tpl);
}
return $tpl;
} static function parseCss($css_content, $filenme) { $css_content = preg_replace("/[\r\n\t]/", '', $css_content);
$css_content = preg_replace("/ +/", ' ', $css_content);
// echo WEB_ROOT . $filenme; sp_file_put_contents(WEB_ROOT . $filenme, $css_content); return $filenme;
} static function parseJs($js, $filenme) { sp_file_put_contents(WEB_ROOT . $filenme, $js); return $filenme; } function _loop($search, $tag, $arg) {
list($attr, $arg) = $this->parseAttr($arg);
$d = preg_split("/\s+/", trim($arg));
if (count($d) == 3) {
$data = $d[0];
$k = $d[1];
$v = $d[2];
$s = "<? \n if(!empty($data)) {\n \t foreach(" . $data . " as {$k} => {$v}){";
} elseif (count($d) == 2) {
$data = $d[0];
$v = $d[1];
$s = "<? \n if(!empty($data)){\n \t foreach(". $datsa . " as {$v}) {";
} if (isset($attr['counter'])) {
$s = "\n\t\t <? if (!isset({$attr['counter']})) { {$attr['counter']} = 0;} ?> \n \t\t" . $s . "\n \t\t{$attr['counter']}++; \n ?>";
} else {
$s .= "?>";
}
$this->string = str_replace($search, $s, $this->string);
$this->string = str_replace("{/loop}", "<? \t}\n}?>", $this->string);
}
function parseAttr($s) {
$reg = '/([a-zA-Z0-9_])+\s*=\s*([a-zA-Z0-9_\$]+)/i';
$arg = preg_replace($reg, '', $s);
preg_match_all($reg, $s, $d);
$arr = array();
foreach($d[1] as $key => $value) {
$arr[trim($value)] = trim($d[2][$key]);
}
return array($arr, $arg);
}
function _if ($search, $tag, $arg) {
$replace[0] = "<? if($arg){?>";
$replace[1] = "<? }?>";
$this->string = str_replace(array($search, "{/$tag}"), $replace, $this->string);
}
function _else($search, $tag, $arg) {
$this->string = str_replace("{else}", "\n" . '<? } else {?>', $this->string);
}
function _elseif($search, $tag, $arg) {
$this->string = str_replace($search, "<? }elseif($arg){?>", $this->string);
} function _for($search, $tag, $arg) {
$s = trim(trim(preg_replace('/\s+/', ' ; ', $arg)), ';');
$this->string = str_replace($search, "<? for($s){?>", $this->string);
$this->string = str_replace("{/for}", "<? \t\n}?>", $this->string);
}
function _while($search, $tag, $arg) {
$this->string = str_replace($search , "<? while($arg){ ?>", $this->string);
$this->string = str_replace("{/while}", "<? \t\n} ?>", $this->string);
}
function _end() {
//{$val}标签解析
$this->string = preg_replace('/\\{(\\$\\w+.*)\\}/is', "<?=\\\\1;?>", $this->string);
//{:$val()}标签解析
$this->string = preg_replace('/\\{\\:\\s*(\\$?\\w+.*?)\\}/is', "<? =\\\\1;?>", $this->string);
//将$val[arg]解析成$val['arg']
$this->string = preg_replace('/\\$([_a-z]+\\w*)\\[([_a-z]+\\w*)\\]/is', "$\\\\1['\\\\2']", $this->string);
//支持点号访问数组。如array['key']可以用array.key访问
$this->string = preg_replace('/\\$([_a-z]+\\w*)\\.(\\$[_a-z]+\\w*)/is', "$\\\\1[\\\\2]", $this->string);
$this->string = preg_replace('/\\$([_a-z]+\\w*)\\.([_a-z]+\\w*)/is', "$\\\\1['\\\\2']", $this->string);
$this->sring = preg_replace(array("/<\?/", "/<\?php\s*=/"), array("<?php ", "<?php echo "), $this->string);
}
function result() {
$this->_end();
$this->string = $this->_baseParse($this->string);
return str_replace(array("\\HKH1", "\\HKH2"), array("\\{", "\\}"), $this->string);
}
}
$data = ['aa','bb','cc'];
$bb = 'aa';
$con = 11;
$tag = new _HtmlTag('./index', '');
$str = file_get_contents('base.htm');
$tag->render($str);
$html = $tag->result();
var_dump($html);
echo '</br>----------------------------</br>';
sp_file_put_contents("./index.html", $html);
include "index.html";
  • 运行index.php查看结果

     

    总结
    
    我们来总结一下这个类的使用过程,先实例话template类,调用template::display()方法,其中会调用template::compiles()方法,这个方法会实例化_HhtmlTag类,调用_HtmlTag::render方法,在这个方法里,对传入的带字符串进行一个结构的完整化,包括调用_HtmlTag::_extends,_HtmlTag::_import,_HtmlTag::_loop,_HtmlTag::_if,_HtmlTag::elseif,_HtmlTag::else,_HtmlTag::_for,_HtmlTag::while接着调用_HtmlTag::result方法,里面调用_HtmlTag::_end方法对变量进行php原生化和_HtmlTag::_baseParse()引入css和js,最后返回解析好的完整模板字符串。

php模板引擎的原理与简单实例的更多相关文章

  1. Vue.js双向绑定的实现原理和模板引擎实现原理(##########################################)

    Vue.js双向绑定的实现原理 解析 神奇的 Object.defineProperty 这个方法了不起啊..vue.js和avalon.js 都是通过它实现双向绑定的..而且Object.obser ...

  2. PHP的模板引擎smarty原理浅谈

    mvc是开发中的一个伟大的思想,使得开发代码有了更加清晰的层次,让代码分为了三层各施其职.无论是对代码的编写以及后期的阅读和维护,都提供了很大的便利. 我们在php开发中,视图层view是不允许有ph ...

  3. PHP的模板引擎smarty原理是什么(整理)

    PHP的模板引擎smarty原理是什么(整理) 一.总结 一句话总结:其实所有的模板引擎的工作原理是差不多的,无非就是在php程序里面用正则匹配将模板里面的标签替换为php代码从而将两者混合为一个ph ...

  4. 高性能JavaScript模板引擎实现原理详解

    这篇文章主要介绍了JavaScript模板引擎实现原理详解,本文着重讲解artTemplate模板的实现原理,它采用预编译方式让性能有了质的飞跃,是其它知名模板引擎的25.32 倍,需要的朋友可以参考 ...

  5. Win Socket编程原理及简单实例

    [转]http://www.cnblogs.com/tornadomeet/archive/2012/04/11/2442140.html 使用Linux Socket做了小型的分布式,如Linux ...

  6. 浅谈dedecms模板引擎工作原理及其自定义标签

    浅谈dedecms模板引擎工作原理: 理解织梦模板引擎有什么意思? 可以更好地自定义标签.更多在于了解织梦系统,理解模板引擎是理解织梦工作原理的第一步. 理解织梦会使我们写PHP代码是更顺手,同时能学 ...

  7. JavaScript 模板引擎实现原理解析

    1.入门实例 首先我们来看一个简单模板: <script type="template" id="template"> <h2> < ...

  8. (转)浅谈dedecms模板引擎工作原理及自定义标签

    理解织梦模板引擎有什么意义?一方面可以更好地自定义标签.更多在于了解织梦系统,理解模板引擎是理解织梦工作原理的第一步.理解织梦会使我们写php代码时更顺手,同时能学习一些php代码的组织方式. 这似乎 ...

  9. JS模板引擎handlebars.js的简单使用

    handlebars.js的使用 首先我们要明白handlebars.js是干什么用的,我们知道使用jsp很麻烦, 于是我们开始使用freemarker等模板引擎,但这个只是针对服务器端来解 析的,那 ...

随机推荐

  1. Kettle系列文章二(安装配置Kettle+SqlServer+简单的输入输出作业)

    一.下载 Kettle下载地址:https://community.hitachivantara.com/docs/DOC-1009855 下拉到DownLoad,点击红框中的链接进行下载.. 二.解 ...

  2. final 、finalize和finally的区别

    2019-04-1217:29:40 (1)final用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承.内部类要访问局部变量,局部变量必须定义成final类型,比如一段代码 (2) ...

  3. python模块之contexlib

    一.上下文管理器 with是python实现上下文管理器的核心关键词.它能够在代码执行前和执行后做一些额外的事情. 最常见的代码恐怕就是文件操作了. with open("file" ...

  4. HttpContext.Current.Request.RawUrl是什么意思?

    原始 URL 定义为 URL 中域信息之后的部分.在 URL 字符串 http://www.contoso.com/articles/recent.aspx 中,原始 URL 为/articles/r ...

  5. Spring课程 Spring入门篇 6-1 Spring AOP API的PointCut、advice的概念及应用

    本节主要是模拟spring aop 的过程. 实现spring aop的过程 这一节老师虽然说是以后在工作中不常用这些api,实际上了解还是有好处的, 我们可以从中模拟一下spring aop的过程. ...

  6. mysql的with rollup

    无意间发现了mysql的with rollup函数(用在group by 函数后面) 测试 1. SELECT NAME,DATE,score FROM stu 结果是 2. SELECT NAME, ...

  7. C 堆内存管理

    在Win32 程序中每个进程都占有4GB的虚拟地址空间,这4G的地址空间内部又被分为代码段,全局变量段堆段和栈段,栈内存由函数使用,用来存储函数内部的局部变量,而堆是由程序员自己申请与释放的,系统在管 ...

  8. 廖雪峰JavaScript练习题3

    请尝试写一个验证Email地址的正则表达式.版本一应该可以验证出类似的Email: 正则表达式: <!DOCTYPE html> <html> <head> < ...

  9. safari兼容时间格式

    前提: 使用iview的DatePicker组件,保存时间后台接收时间戳 问题: safari中不支持2018-02-13这种格式转为时间戳会显示NaN 解决: new Date('2018/02/1 ...

  10. 大规模WEB服务技术

    CPU负载的扩展很简单,增加相同结构的服务器,通过负载均衡来分散. I/O负载的扩展很困难.要考虑局部性.