在下面的代碼中,命名有規(guī)律。$tpl是沒有后綴名的,$tplfile是后綴為htm的模板文件,$objfile是后綴為php的緩存文件
UCH里使用模板的流程是:
在php代碼中獲取動態(tài)數(shù)據(jù),然后include_once template($tpl)
template函數(shù)解析模板文件$tplfile,返回緩存文件$objfile。
template函數(shù)中調(diào)用parse_template函數(shù)解析$tplfile,
$tplfile里有UCH定義的一套語法,在parse_template函數(shù)里可以看到,有<!-- template name--> <!—block/name-->等。這些語法在parse_template中都會被替換成對應(yīng)的函數(shù),如readtemplate blocktags等,這些函數(shù)也都位于function_template.php中。
這里有點點糾結(jié)的就是,模板文件中可能還通過<!-- template name-->包含了其他的模板文件,所以在parse_template中要兩次調(diào)用preg_replace,第一次讀模板文件$tplfile所包含的子模板文件name,第二次是讀子模板文件name中再包含的孫子模板文件。UCH中至多有3層模板包含關(guān)系,即父親->兒子->孫子,所以不需要第三次調(diào)用preg_replace來讀取孫子模板文件可能包含的重孫模板文件了。
template函數(shù)在function_common.php中定義
function template($name) {
global $_SCONFIG, $_SGLOBAL;
if(strexists($name,'/')) {
$tpl = $name;//$name是完整目錄的情況
} else {
$tpl = "template/$_SCONFIG[template]/$name";
/*
$name只是一個文件名的情況,$tpl類似template/default/cp_blog或者template/blue/cp_blog
默認的模板風(fēng)格是default,但是如果用戶選擇了其他風(fēng)格,$_SCONFIG[template]就會變成blue之類的其他值
在首頁的右下角可以選擇模板風(fēng)格
*/
}
$objfile = S_ROOT.'./data/tpl_cache/'.str_replace('/','_',$tpl).'.php';
/*
緩存文件名,$objfile類似data/tpl_cache/template_default_cp_blog.php或者data/tpl_cache/template_blue_cp_blog.php
*/
if(!file_exists($objfile)) {
include_once(S_ROOT.'./source/function_template.php');
parse_template($tpl);
//如果緩存文件不存在,則對模板文件進行解析
}
return $objfile;
}
parse_template函數(shù)在function_template.php中定義
function parse_template($tpl) {
global $_SGLOBAL, $_SC, $_SCONFIG;
//包含模板
$_SGLOBAL['sub_tpls'] = array($tpl);
$tplfile = S_ROOT.'./'.$tpl.'.htm';
/*
$tplfile類似template/default/cp_blog.htm或者template/blue/cp_blog.htm
*/
$objfile = S_ROOT.'./data/tpl_cache/'.str_replace('/','_',$tpl).'.php';
/*
$objfile類似data/tpl_cache/template_default_cp_blog.php或者data/tpl_cache/template_blue_cp_blog.php
*/
//read
if(!file_exists($tplfile)) {
$tplfile = str_replace('/'.$_SCONFIG['template'].'/', '/default/', $tplfile);
//如果非默認模板風(fēng)格的某個模板文件不存在,那么就改用default風(fēng)格的該模板文件
}
$template = sreadfile($tplfile);
//讀入模板文件內(nèi)容
if(empty($template)) {
exit("Template file : $tplfile Not found or have no access!");
}
//模板
$template = preg_replace("/\<\!\-\-\{template\s+([a-z0-9_\/]+)\}\-\-\>/ie", "readtemplate('\\1')", $template);
/*
這就是定義UCH的模板語法了,模板頁中的<!--{template name}-->
被替換成readtemplate(name),readtemplate函數(shù)也在function_template.php中定義。name就是([a-zA-Z0-9_\/]+)
為什么多了A-Z呢,因為"/\<\!\-\-\{template\s+([a-z0-9_\/]+)\}\-\-\>/ie"最后的i選項表示不區(qū)分大小寫的正則匹配
python里的正則表達式分組,似乎就是用\1來表示第一組,這里用了\\1
\\1為什么又要用單引號裹起來呢,這是因為readtemplate函數(shù)的參數(shù)要是一個字符串
*/
//處理子頁面中的代碼
$template = preg_replace("/\<\!\-\-\{template\s+([a-z0-9_\/]+)\}\-\-\>/ie", "readtemplate('\\1')", $template);
//解析模塊調(diào)用
$template = preg_replace("/\<\!\-\-\{block\/(.+?)\}\-\-\>/ie", "blocktags('\\1')", $template);
/*
<!--{block/name}-->被替換成blocktags(name)
name就是(.+?) .匹配除換行符外的任意字符,+表示出現(xiàn)一次或多次
?表示懶惰匹配,不然后面的\} \- \>都會被.+匹配掉
*/
//解析廣告
$template = preg_replace("/\<\!\-\-\{ad\/(.+?)\}\-\-\>/ie", "adtags('\\1')", $template);
/*
<!--{ad/name}-->被替換成<!--adtags(name)-->
如space_doing.htm里有一個<!--{ad/header}-->被替換成了<!--AD_TAG_1-->
*/
//時間處理
$template = preg_replace("/\<\!\-\-\{date\((.+?)\)\}\-\-\>/ie", "datetags('\\1')", $template);
/*
<!--{date(name)}-->被替換成datetags(name)
如space_doing.htm里有一個
<!--{date('m-d H:i',$basevalue[dateline],1)}-->被替換成了<!--DATE_TAG_7-->
*/
//頭像處理
$template = preg_replace("/\<\!\-\-\{avatar\((.+?)\)\}\-\-\>/ie", "avatartags('\\1')", $template);
/*
<!--{avatar(name)}-->被替換成avatartags(name)
如space_doing.htm里有一個<!--{avatar($_SGLOBAL[supe_uid],small)}-->被替換成了<!--AVATAR_TAG_10-->
*/
//PHP代碼
$template = preg_replace("/\<\!\-\-\{eval\s+(.+?)\s*\}\-\-\>/ies", "evaltags('\\1')", $template);
/*
<!--{eval php_expression}-->被替換成evaltags(php_expression)
php_expression就是(.+?) 而且這里的.匹配包括換行符在內(nèi)的一切字符 這是由/ies中的s選項確定的
如space_doing.htm里有一個<!--{eval echo formhash();}-->被替換成了<!--EVAL_TAG_16-->
*/
//開始處理
//變量
$var_regexp = "((\\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)(\[[a-zA-Z0-9_\-\.\"\'\[\]\$\x7f-\xff]+\])*)";
/*
上面用紅色顯示的\是轉(zhuǎn)義符,沒用紅色顯示的\就還是反斜杠
但是一開始為什么要用三個\,我搞不明白。試驗了一下,\\\$匹配的就是$,而改成\$卻沒能匹配模板文件里的變量名。這個我還沒搞明白。
$var_regexp匹配變量名
[a-zA-Z_\x7f-\xff] 變量名以字母或下劃線或漢字開頭
\x7f-\xff這個讓人非常困惑,到底是想匹配什么呢?難道是想匹配擴展ASCII碼嗎?0x7f-0xff確實是擴展ASCII碼表的范圍,但顯然沒人會用這些控制字符去當(dāng)變量名。
我這個UCH是UTF-8版本的,從UCS-2(就是現(xiàn)在通用的Unicode)到UTF-8的編碼方式如下:
UCS-2編碼(16進制) | UTF-8 字節(jié)流(二進制) |
0000 - 007F | 0xxxxxxx |
0080 - 07FF | 110xxxxx 10xxxxxx |
0800 - FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
前面的0000-007F是兼容ASCII碼的
漢字的unicode編碼在0080-FFFF里,轉(zhuǎn)換成UTF-8后的字節(jié)是
110xxxxx或10xxxxxx或1110xxxx,在0x01111111-0x11111111范圍內(nèi),也即0x7f-0xff。
更精確地,這個匹配變量名開頭的部分可以寫成[a-zA-Z_\xc0-\xef],因為漢字的開始字節(jié)只可能是110xxxxx或1110xxxx,范圍就是0xc0-0xdf和0xe0-0xef
[a-zA-Z0-9_\x7f-\xff]* 比變量名的開頭字節(jié)的允許取值范圍多了數(shù)字,和C語言是一樣的,變量名不能以數(shù)字開頭
更精確地,這里可以寫成[a-zA-Z0-9_\x80-\xef]* 因為這里漢字的字節(jié)可能是110xxxxx或1110xxxx或10xxxxxx,比0xc0-0xef多了10xxxxxx,即0x80-0xbf,合起來就是0x80-0xef
(\[[a-zA-Z0-9_\-\.\"\'\[\]\$\x7f-\xff]+\])*
變量可能是數(shù)組形式的,如$name1[$name2],\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*匹配了$name1,后面的[$name2]怎么匹配呢?我一開始以為是\[\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]\],因為[]里面也應(yīng)該是一個變量名。
但是變量可能是嵌套數(shù)組形式的,如$name1[$name2[$name3[$name4]]],此時用\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]來一個個匹配變量名明顯不可行。所以只是用[]把可能的字符都框起來,希望程序員不要寫太扯淡的變量名了。
*/
$template = preg_replace("/\<\!\-\-\{(.+?)\}\-\-\>/s", "{\\1}", $template);
/*
形如<!--{name}-->的字符串被替換成{name}
前面 解析廣告,時間處理等產(chǎn)生的<!--EVAL_TAG_15-->等都不匹配,仍然保留
name就是(.+?) 這里的.匹配包括換行符在內(nèi)的所有字符(有/s選項) ?表示懶惰匹配
*/
$template = preg_replace("/([\n\r]+)\t+/s", "\\1", $template);
/*
去掉換行回車后的制表符
beyondcompare中的對比效果如下
*/
$template = preg_replace("/(\\\$[a-zA-Z0-9_\[\]\'\"\$\x7f-\xff]+)\.([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/s", "\\1['\\2']", $template);
/*
形如$name1.name2替換成$name1[name2],不要搞成了$name1['name2'],'\\2'和"\\1"都是正則表達式的后向引用。可是我在模板文件中還沒找到例子
*/
$template = preg_replace("/\{(\\\$[a-zA-Z0-9_\[\]\'\"\$\.\x7f-\xff]+)\}/s", "<?=\\1?>", $template);
/*
形如{$name1}替換成<?=$name1?>
如space_doing.htm里有{$_SN[$space[uid]]}被換成了
<?=$_SN[$space[uid]]?>
*/
$template = preg_replace("/$var_regexp/es", "addquote('<?=\\1?>')", $template);
/*
將沒有被{}包裹的變量名替換成addquote('<?=\\1?>')
addquote的作用是將類似[name]轉(zhuǎn)換成[‘name’]
如space_doing.htm里有{if $space}被替換成了{if <?=$space?>}
但是這樣也有副作用,比如space_doing.htm里有{$_SN[$space[uid]]},在上一條語句中被替換成了<?=$_SN[$space[uid]]?>,在這又被替換成了<?=<?=$_SN[$space['uid']]?>?>
*/
$template = preg_replace("/\<\?\=\<\?\=$var_regexp\?\>\?\>/es", "addquote('<?=\\1?>')", $template);
/*
消除上一條語句的副作用,將<?=<?=$_SN[$space['uid']]?>?>換成<?=$_SN[$space['uid']]?>
*/
//邏輯
$template = preg_replace("/\{elseif\s+(.+?)\}/ies", "stripvtags('<?php } elseif(\\1) { ?>','')", $template);
/*
{elseif expression1} 替換成stripvtags('<?php } elseif(expression1) { ?>','')
stripvtags的作用是將類似<?=$name?>替換成$name
*/
$template = preg_replace("/\{else\}/is", "<?php } else { ?>", $template);
/*
{else} 替換成<?php } else { ?>
*/
//循環(huán)
for($i = 0; $i < 5; $i++) {
$template = preg_replace("/\{loop\s+(\S+)\s+(\S+)\}(.+?)\{\/loop\}/ies", "stripvtags('<?php if(is_array(\\1)) { foreach(\\1 as \\2) { ?>','\\3<?php } } ?>')", $template);
/*
解析loop
將類似{loop array1 key1} expression1 {/loop}替換成
stripvtags(
<’?php if(is_array(array1)) {foreach (array1 as key1){?>’,
‘expression1<?php } } ?>’
)
如space_doing.htm里將
<title>{if <?=$_TPL['titles']?>}{loop <?=$_TPL['titles']?> <?=$value?>}{if <?=$value?>}<?=$value?> - {/if}{/loop}{/if}{if <?=$space?>}<?=$_SN[$space['uid']]?> - {/if}<?=$_SCONFIG['sitename']?>
替換成了
<title>{if <?=$_TPL['titles']?>}<?php if(is_array($_TPL['titles'])) { foreach($_TPL['titles'] as $value) { ?>{if <?=$value?>}<?=$value?> - {/if}<?php } } ?>{/if}{if <?=$space?>}<?=$_SN[$space['uid']]?> - {/if}<?=$_SCONFIG['sitename']?>
*/
$template = preg_replace("/\{loop\s+(\S+)\s+(\S+)\s+(\S+)\}(.+?)\{\/loop\}/ies", "stripvtags('<?php if(is_array(\\1)) { foreach(\\1 as \\2 => \\3) { ?>','\\4<?php } } ?>')", $template);
/*
解析loop
將類似{loop array1 key1 value1} expression1 {/loop}替換成
stripvtags(
<’?php if(is_array(array1)) {foreach (array1 as key1=>value1){?>’,
‘expression1<?php } } ?>’
)
如space_doing.htm里將
{loop <?=$moodlist?> <?=$key?> <?=$value?>}
<li>
<div class="avatar48"><a href="space.php?uid=<?=$value['uid']?>&do=doing"><img src="<!--AVATAR_TAG_14-->" alt="<?=$_SN[$value['uid']]?>" /></a></div>
<p><a href="space.php?uid=<?=$value['uid']?>" title="<?=$_SN[$value['uid']]?>"><?=$_SN[$value['uid']]?></a></p>
<p class="time"><!--DATE_TAG_9--></p>
</li>
{/loop}
替換成了
<?php if(is_array($moodlist)) { foreach($moodlist as $key => $value) { ?>
<li>
<div class="avatar48"><a href="space.php?uid=<?=$value['uid']?>&do=doing"><img src="<!--AVATAR_TAG_14-->" alt="<?=$_SN[$value['uid']]?>" /></a></div>
<p><a href="space.php?uid=<?=$value['uid']?>" title="<?=$_SN[$value['uid']]?>"><?=$_SN[$value['uid']]?></a></p>
<p class="time"><!--DATE_TAG_9--></p>
</li>
<?php } } ?>
*/
$template = preg_replace("/\{if\s+(.+?)\}(.+?)\{\/if\}/ies", "stripvtags('<?php if(\\1) { ?>','\\2<?php } ?>')", $template);
/*
解析if
將類似
{if name1} expression1 {/if}
替換成
stripvtags(
<’?php if(name1) { ?>’,
‘expression1<?php } ?>’
)
如space_doing.htm里將
{if empty(<?=$_SGLOBAL['inajax']?>)}
替換成
<?php if(empty($_SGLOBAL['inajax'])) { ?>
*/
}/*for循環(huán)結(jié)束*/
/*
為什么要有for循環(huán)呢?因為可能存在循環(huán)語句嵌套的情況。
比如space_doing.htm里有這么一段代碼
{loop <?=$list?> <?=$basevalue?>}
………
………
{loop <?=$clist[$basevalue['doid']]?> <?=$value?>}
………
{/loop}
………
{/loop}
$i=0時,
$template = preg_replace("/\{loop\s+(\S+)\s+(\S+)\}(.+?)\{\/loop\}/ies", "stripvtags('<?php if(is_array(\\1)) { foreach(\\1 as \\2) { ?>','\\3<?php } } ?>')", $template);
只替換掉了外層的loop,內(nèi)層的loop被(.+?)所匹配
?表示懶惰匹配,否則后面的{/loop}都會被(.+)所匹配
但是既然是懶惰匹配,為什么(.+?)不是匹配到內(nèi)層的{/loop}就停止呢?
因為正則表達式有另一條規(guī)則,比懶惰/貪婪規(guī)則的優(yōu)先級更高:最先開始的匹配擁有最高的優(yōu)先權(quán)——The match that begins earliest wins。
$i=1時,內(nèi)層的loop也將被替換掉
從這個for循環(huán)我們也可以看出,UCH的模板中最多允許5層嵌套循環(huán),當(dāng)然這已經(jīng)足夠了
*/
//常量
$template = preg_replace("/\{([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/s", "<?=\\1?>", $template);
/*
比變量名少了一開始的$,也不考慮數(shù)組形式的常量
*/
//替換
if(!empty($_SGLOBAL['block_search'])) {
$template = str_replace($_SGLOBAL['block_search'], $_SGLOBAL['block_replace'], $template);
}
//換行
$template = preg_replace("/ \?\>[\n\r]*\<\? /s", " ", $template);
/*
?>
<?
前一段php代碼的結(jié)尾和下一段php代碼的開始都被以空格代替了
*/
//附加處理
$template = "<?php if(!defined('IN_UCHOME')) exit('Access Denied');?><?php subtplcheck('".implode('|', $_SGLOBAL['sub_tpls'])."', '$_SGLOBAL[timestamp]', '$tpl');?>$template<?php ob_out();?>";
//write
if(!swritefile($objfile, $template)) {
exit("File: $objfile can not be write!");
}
/*
寫入緩存文件
所以在template函數(shù)最后,無論$objfile一開始是否存在,最后都是返回$objfile
*/
}
function readtemplate($name) {
global $_SGLOBAL, $_SCONFIG;
$tpl = strexists($name,'/')?$name:"template/$_SCONFIG[template]/$name";
$tplfile = S_ROOT.'./'.$tpl.'.htm';
$_SGLOBAL['sub_tpls'][] = $tpl;
if(!file_exists($tplfile)) {
$tplfile = str_replace('/'.$_SCONFIG['template'].'/', '/default/', $tplfile);
}
/*
如果$_SCONFIG['template']風(fēng)格的模板文件不存在,則改用default風(fēng)格的模板文件
*/
$content = sreadfile($tplfile);
//讀入模板文件的內(nèi)容
return $content;
}
function addquote($var) {
return str_replace("\\\"", "\"", preg_replace("/\[([a-zA-Z0-9_\-\.\x7f-\xff]+)\]/s", "['\\1']", $var));
/*
這個preg_replace感覺沒什么用啊,[name1]替換成[name1]?
str_replace將\\\"替換成\"
*/
}
function blocktags($parameter) {
global $_SGLOBAL;
$_SGLOBAL['i']++;
$search = "<!--BLOCK_TAG_{$_SGLOBAL['i']}-->";
/*
按廣告出現(xiàn)的次序,依次將{block/name}替換成BLOCK_TAG_1 BLOCK_TAG_2等等
*/
$_SGLOBAL['block_search'][$_SGLOBAL['i']] = $search;
$_SGLOBAL['block_replace'][$_SGLOBAL['i']] = "<?php block(\"$parameter\"); ?>";
return $search;
}
function adtags($pagetype) {
global $_SGLOBAL;
$_SGLOBAL['i']++;
$search = "<!--AD_TAG_{$_SGLOBAL['i']}-->";
/*
按廣告出現(xiàn)的次序,依次將{ad/name}替換成AD_TAG_3 AD_TAG_4等等
這個編號是跟著前面的BLOCK_TAG來的
*/
$_SGLOBAL['block_search'][$_SGLOBAL['i']] = $search;
$_SGLOBAL['block_replace'][$_SGLOBAL['i']] = "<?php adshow('$pagetype'); ?>";
return $search;
}
function datetags($parameter) {
global $_SGLOBAL;
$_SGLOBAL['i']++;
$search = "<!--DATE_TAG_{$_SGLOBAL['i']}-->";
/*
按date模板方法出現(xiàn)的次序,依次將{date(name)}替換成DATE_TAG_7 DATE_TAG_8等等
注意這里的編號是跟著前面的AD_TAG來的
*/
$_SGLOBAL['block_search'][$_SGLOBAL['i']] = $search;
$_SGLOBAL['block_replace'][$_SGLOBAL['i']] = "<?php echo sgmdate($parameter); ?>";
return $search;
}
function avatartags($parameter) {
global $_SGLOBAL;
$_SGLOBAL['i']++;
$search = "<!--AVATAR_TAG_{$_SGLOBAL['i']}-->";
/*
按avatar模板方法出現(xiàn)的次序,依次將{avatar(name)}替換成AVATAR_TAG_9 AVATAR_TAG_10等等
注意這里的編號是跟著前面的DATE_TAG來的
*/
$_SGLOBAL['block_search'][$_SGLOBAL['i']] = $search;
$_SGLOBAL['block_replace'][$_SGLOBAL['i']] = "<?php echo avatar($parameter); ?>";
return $search;
}
function evaltags($php) {
global $_SGLOBAL;
$_SGLOBAL['i']++;
$search = "<!--EVAL_TAG_{$_SGLOBAL['i']}-->";
/*
按eval模板方法出現(xiàn)的次序,依次將{eval php_expression}替換成EVAL_TAG_16 EVAL_TAG_17等等
注意這里的編號是跟著前面的AVATAR_TAG來的
*/
$_SGLOBAL['block_search'][$_SGLOBAL['i']] = $search;
$_SGLOBAL['block_replace'][$_SGLOBAL['i']] = "<?php ".stripvtags($php)." ?>";
return $search;
}
function addquote($var) {
return str_replace("\\\"", "\"", preg_replace("/\[([a-zA-Z0-9_\-\.\x7f-\xff]+)\]/s", "['\\1']", $var));
/*
preg_replace將類似[name]轉(zhuǎn)換成[‘name’]
str_replace將\\\轉(zhuǎn)換成\ 不過我還沒找到例子
*/
}
function stripvtags($expr, $statement='') {
$expr = str_replace("\\\"", "\"", preg_replace("/\<\?\=(\\\$.+?)\?\>/s", "\\1", $expr));
/*
preg_replace將<?=$name?>替換成$name
*/
$statement = str_replace("\\\"", "\"", $statement);
return $expr.$statement;
}