仿Smarty替换模板标签时遇到的问题
最近正在做一个微型的仿TP框架,当然以鄙人之技术只能略仿表层,于是遇到的问题层出不穷。今天做到View层替换模板部分,本以为一下子搞掂的事,果不其然又是败下阵来。
好了,来重点。
模板文件 test1.tpl
{foreach from=$phone key=k item=v name=phones} <tr> <td>{$smarty.foreach.phones.iteration}</td> <td>{$v.brand}</td> <td>{$v.ver}</td> <td>{$v.conf}</td><td>{$v.price|test:20}</td> <td>{$v.date}</td></tr> {/foreach}
而Smarty将以上代码替换为:
<?php $_from = $_smarty_tpl->tpl_vars['phone']->value; if (!is_array($_from) && !is_object($_from)) { settype($_from, 'array'); } $__foreach_phones_1_saved = isset($_smarty_tpl->tpl_vars['__smarty_foreach_phones']) ? $_smarty_tpl->tpl_vars['__smarty_foreach_phones'] : false; $__foreach_phones_1_saved_item = isset($_smarty_tpl->tpl_vars['v']) ? $_smarty_tpl->tpl_vars['v'] : false; $__foreach_phones_1_saved_key = isset($_smarty_tpl->tpl_vars['k']) ? $_smarty_tpl->tpl_vars['k'] : false; $_smarty_tpl->tpl_vars['v'] = new Smarty_Variable(); $_smarty_tpl->tpl_vars['__smarty_foreach_phones'] = new Smarty_Variable(array('iteration' => 0)); $_smarty_tpl->tpl_vars['k'] = new Smarty_Variable(); $_smarty_tpl->tpl_vars['v']->_loop = false; foreach ($_from as $_smarty_tpl->tpl_vars['k']->value => $_smarty_tpl->tpl_vars['v']->value) { $_smarty_tpl->tpl_vars['v']->_loop = true; $_smarty_tpl->tpl_vars['__smarty_foreach_phones']->value['iteration']++; $__foreach_phones_1_saved_local_item = $_smarty_tpl->tpl_vars['v']; ?> <tr> <td><?php echo (isset($_smarty_tpl->tpl_vars['__smarty_foreach_phones']->value['iteration']) ? $_smarty_tpl->tpl_vars['__smarty_foreach_phones']->value['iteration'] : null);?></td> <td><?php echo $_smarty_tpl->tpl_vars['v']->value['brand'];?></td> <td><?php echo $_smarty_tpl->tpl_vars['v']->value['ver'];?></td> <td><?php echo $_smarty_tpl->tpl_vars['v']->value['conf'];?></td> <td><?php echo smarty_modifier_test($_smarty_tpl->tpl_vars['v']->value['price'],20);?></td> <td><?php echo $_smarty_tpl->tpl_vars['v']->value['date'];?></td> <?php $_smarty_tpl->tpl_vars['vv'] = $__foreach_v_2_saved_local_item; } if ($__foreach_v_2_saved_item) { $_smarty_tpl->tpl_vars['vv'] = $__foreach_v_2_saved_item; } if ($__foreach_v_2_saved_key) { $_smarty_tpl->tpl_vars['vk'] = $__foreach_v_2_saved_key; } ?> </tr>
这替换的代码也怪吓人的,其实分析也就三个部分:
好了,问题来了,由于foreach标签的特殊性,{/foreach}结束标签替换后第三部分不是单单的?>php脚本结束标签,而是有第一部分声明而来的变量代码,因此直接替换标签不可行。于是想到用正则解决。
搞了一个下午,一直有个困扰问题,在很多情况下都不可能是单单一个foreach,其内部有可能有嵌套不止一个foreach循环,以我目前认知水平,用正则直接匹配替换显然不可行。
于是想到先用正则匹配出所有的{foreach}{/foreach},【1】匹配替换过程中将匹配的标签加入到一个临时数组中,以该数组下标和一个MD5值组成的混合值替换模板中该标签所在的位置作标识占位。【2】排列好临时数组中的foreach闭合标签后,【3】将排列好的foreach内数值进行匹配替换成上图代码的样子,【4】再根据该标签所在的下标替换回编译文件中的标识占位即可。
【1】这里用到PHP一个强大的(我认为)正则替换函数preg_replace_callback《执行一个正则表达式搜索并且使用一个回调进行替换》
//模拟Smarty内部处理模板替换
$l_delimiter='{'; $r_delimiter='}'; $_foreach=md5('foreach'); $_foreach_count=0; $_foreach_arr=array(); $reg_foreach="/\\$l_delimiter\s*".'\/?\s*\bforeach\b[^\\'.$r_delimiter.']*\\'.$r_delimiter.'/i'; $content=preg_replace_callback($reg_foreach,'_replace_foreach', $content); function _replace_foreach($match) { global $_foreach_arr,$_foreach,$_foreach_count; $_foreach_arr[]=$match[0]; $rt=$_foreach.$_foreach_count.'_'; $foreach_count++; return $rt; } echo '<pre>'; print_r($foreach_arr); echo '</pre>';
原模板foreach部分代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 {foreach from=$phone key=k item=v name=phones} 2 <tr> 3 <td>{$smarty.foreach.phones.iteration}</td> 4 <td>{$v.brand}</td> 5 <td>{$v.ver}</td> 6 <td>{$v.conf}</td> 7 {foreach from=v key=vk item=vv name=v}//这是用于测试标签嵌套,实际不可能如此 8 <td>{$v.price|test:20}</td> 9 <td>{$v.date}</td> 10 {/foreach}//这是用于测试标签嵌套,实际不可能如此 11 </tr> 12 {/foreach} 13 14 {foreach $phone as $v} 15 <tr> 16 <td>{$smarty.foreach.phones.iteration}</td> 17 <td>{$v.brand}</td> 18 <td>{$v.ver}</td> 19 <td>{$v.conf}</td> 20 <td>{$v.price|test:20}</td> 21 <td>{$v.date}</td> 22 </tr> 23 {/foreach} 24 25 {foreach $phone as $k=>$v name=phonename} 26 <tr> 27 <td>{$smarty.foreach.phones.iteration}</td> 28 <td>{$v.brand}</td> 29 <td>{$v.ver}</td> 30 <td>{$v.conf}</td> 31 <td>{$v.price|test:20}</td> 32 <td>{$v.date}</td> 33 </tr> 34 {/foreach}
模板替换后的编译文件(只看替换foreach的部分)代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 1dfae1db802b03f8a8debd7a9a1cb34b0_ 2 <tr> 3 <td><?php echo $_tpl_data["_smarty_sys_reserved_value"]["foreach"]["phones"]["iteration"]; ?></td> 4 <td><?php echo $_tpl_data["v"]["brand"]; ?></td> 5 <td><?php echo $_tpl_data["v"]["ver"]; ?></td> 6 <td><?php echo $_tpl_data["v"]["conf"]; ?></td> 7 1dfae1db802b03f8a8debd7a9a1cb34b1_ 8 <td><?php echo test($_tpl_data["v"]["price"],20); ?></td> 9 <td><?php echo $_tpl_data["v"]["date"]; ?></td> 10 1dfae1db802b03f8a8debd7a9a1cb34b2_ 11 </tr> 12 1dfae1db802b03f8a8debd7a9a1cb34b3_ 13 14 1dfae1db802b03f8a8debd7a9a1cb34b4_ 15 <tr> 16 <td><?php echo $_tpl_data["_smarty_sys_reserved_value"]["foreach"]["phones"]["iteration"]; ?></td> 17 <td><?php echo $_tpl_data["v"]["brand"]; ?></td> 18 <td><?php echo $_tpl_data["v"]["ver"]; ?></td> 19 <td><?php echo $_tpl_data["v"]["conf"]; ?></td> 20 <td><?php echo test($_tpl_data["v"]["price"],20); ?></td> 21 <td><?php echo $_tpl_data["v"]["date"]; ?></td> 22 </tr> 23 1dfae1db802b03f8a8debd7a9a1cb34b5_ 24 25 1dfae1db802b03f8a8debd7a9a1cb34b6_ 26 <tr> 27 <td><?php echo $_tpl_data["_smarty_sys_reserved_value"]["foreach"]["phones"]["iteration"]; ?></td> 28 <td><?php echo $_tpl_data["v"]["brand"]; ?></td> 29 <td><?php echo $_tpl_data["v"]["ver"]; ?></td> 30 <td><?php echo $_tpl_data["v"]["conf"]; ?></td> 31 <td><?php echo test($_tpl_data["v"]["price"],20); ?></td> 32 <td><?php echo $_tpl_data["v"]["date"]; ?></td> 33 </tr> 34 1dfae1db802b03f8a8debd7a9a1cb34b7_
替换后可见原本foreach标签所在的位置已经被一串md5码+数字+下划线替代。
打印显示:
Array
(
[0] => {foreach from=$phone key=k item=v name=phones}
[1] => {foreach from=v key=vk item=vv name=v}
[2] => {/foreach}
[3] => {/foreach}
[4] => {foreach $phone as $v}
[5] => {/foreach}
[6] => {foreach $phone as $k=>$v name=phonename}
[7] => {/foreach}
)
拿到了存储foreach的临时数组$foreach_arr,即可以【2】排列数组中的foreach闭合标签。
刚开始想到用递归,孰知递归及算法乃鄙人之短板,折腾了一晚,终究还是笨方法,使用foreach
以上面数组排列,正确应该是0->3;1->2;4->5;6->7;。思考良久,有一个思路。一个数组存头标签$a_start,每当遇到非{/foreach}的标签,则加入到$a_start数组中,一旦遇到{/foreach}标签,立刻将$a_start最后一个数据取出,加入到$a_result数组中。
具体实现如下:
1 function foreach_sort($arr) 2 { 3 $a_start=array(); 4 $a_result=array(); 5 for($i=0;$i<count($arr);$i++) 6 { 7 preg_match('/\{\s*\/\bforeach\b\s*\}/i',$arr[$i],$match); 8 if(!$match)//如果是开头 9 { 10 $a_start[]=$i; 11 } 12 else 13 { 14 $a_result[]=array_pop($a_start).'->'.$i; 15 } 16 } 17 return $a_result; 18 } 19 20 echo '<pre>'; 21 print_r(foreach_sort($foreach_arr)); 22 echo '</pre>';
打印显示:
Array
(
[0] => 1->2
[1] => 0->3
[2] => 4->5
[3] => 6->7
)
排列OK。
【3】将排列好的$foreach_arr内数值进行匹配替换。
未完,明天继续...