在织梦include/dedetemplate.class.php里面有一个名为ParseTemplete的方法,对模板中的全局以及循环标签(以{dede:开头)进行分析。
/**
* 解析模板
*
* @access public
* @return void
*/
function ParseTemplate()
{
if($this->makeLoop > 5)
{
return ;
}
$this->count = -1;
$this->cTags = array();
$this->isParse = TRUE;
$sPos = 0;
$ePos = 0;
$tagStartWord = $this->tagStartWord;//标签开始字符串
$fullTagEndWord = $this->fullTagEndWord;//循环标签结束字符串
$sTagEndWord = $this->sTagEndWord;
$tagEndWord = $this->tagEndWord;
$startWordLen = strlen($tagStartWord);//获取标签开始字符串的长度
$sourceLen = strlen($this->sourceString);//获取模板内容的长度
if( $sourceLen <= ($startWordLen + 3) )//如果模板内容的长度还没有模板开始字符串长度+3,直接返回
{
return;
}
$cAtt = new TagAttributeParse();
$cAtt->CharToLow = TRUE;
//遍历模板字符串,请取标记及其属性信息
$t = 0;
$preTag = '';
$tswLen = strlen($tagStartWord);//获取标签开始字符串的长度
for($i=0; $i<$sourceLen; $i++)//开始循环源代码
{
$ttagName = '';
//如果不进行此判断,将无法识别相连的两个标记
if($i-1>=0)
{//如果循环位实际上超过1位,则从前一位取,因为$i的下次起始位置是由本次标签循环长度决定的
$ss = $i-1;
}
else
{
$ss = 0;
}
$tagPos = strpos($this->sourceString,$tagStartWord,$ss);//获取当前开始标记最近的位置
//判断后面是否还有模板标记,防止陷入死循环
if($tagPos==0 && ($sourceLen-$i < $tswLen
|| substr($this->sourceString,$i,$tswLen)!=$tagStartWord ))
{//如果后续的字符串不是标签,则跳出
$tagPos = -1;
break;
}
//获取TAGNAME基本信息
for($j = $tagPos+$startWordLen; $j < $tagPos+$startWordLen+$this->tagMaxLen; $j++)
{//当前标签开始标记位置 + 开始标签长度,循环读取字符直到自定义的tagMaxLen的长度(实际应用中可能更短,但不影响性能),如果遇到HTML标签结束符、换行、空格,则结束一次处理
//遇到空格、换行符、回车符、段落符、点均表示TAGNAME获取完毕,我在实际测试中,用' '==$this->sourceString{$j} || "\r"==$this->sourceString{$j}更快
if(preg_match("/[ >\/\r\n\t\}\.]/", $this->sourceString[$j]))
{
break;
}
else
{
$ttagName .= $this->sourceString[$j];
}
}
if($ttagName!='')
{//如果处理到标签了,就开始继续获取标签结束部分
$i = $tagPos + $startWordLen;//将$i的值前进遇到标签+开始标签长度的位置
$endPos = -1;
//但是可能存在这种情况 {dede:xxx}{dede:global.cfg_cmsurl/}{/dede:xxx}就还要判断是否存在其它起始标签
//可能存在的情况:
// 1、如果后面都没有 {/dede 了,就表示只可能存在 /} 这种结束符,不是 /} 就是 {/dede:
// 2、反之如果不存在/},那就只可能是{/dede
// 3、如果都存在,好吧,就看后面新的起始标签{dede:的位置
// 1):如果 /} 出现在 {dede: 和 {dede: 的前面,不用说,它就是结束符了,因为表示{dede:xxx/}成立
// 2):反之表示上述结果不成立,那只可能是{/dede:了,因为不是 /} 就是 {/dede:
// 4、标签结束位置,就是strpos获取到的结束标签起始位置 + 结束标签的长度
// 5、如果标签没有结束标签或者标签不和规范怎么办?那只能在具体分析的时候判断了
$fullTagEndWordThis = $fullTagEndWord.$ttagName.$tagEndWord;//{/dede: xxxx } 拼接出结束符
$e1 = strpos($this->sourceString, $sTagEndWord, $i);//是否存在 /} 位置
$e2 = strpos($this->sourceString, $tagStartWord, $i); //是否存在 {dede:
$e3 = strpos($this->sourceString, $fullTagEndWordThis, $i); //是否存在 {/dede
$e1 = trim($e1); $e2 = trim($e2); $e3 = trim($e3);
$e1 = ($e1=='' ? '-1' : $e1);
$e2 = ($e2=='' ? '-1' : $e2);
$e3 = ($e3=='' ? '-1' : $e3);
if($e3==-1)
{
//不存在'{/tag:标记'
$endPos = $e1;
$elen = $endPos + strlen($sTagEndWord);
}
else if($e1==-1)
{
//不存在 '/}'
$endPos = $e3;
$elen = $endPos + strlen($fullTagEndWordThis);
}
//同时存在 '/}' 和 '{/tag:标记'
else
{
//如果 '/}' 比 '{tag:'、'{/tag:标记' 都要靠近,则认为结束标志是 '/}',否则结束标志为 '{/tag:标记'
if($e1 < $e2 && $e1 < $e3 )
{
$endPos = $e1;
$elen = $endPos + strlen($sTagEndWord);
}
else
{
$endPos = $e3;
$elen = $endPos + strlen($fullTagEndWordThis);
}
}
//如果找不到结束标记,则认为这个标记存在错误
if($endPos==-1)
{
echo "Tpl Character postion $tagPos, '$ttagName' Error!<br />\r\n";
break;
}
$i = $elen;
//分析所找到的标记位置等信息,分为{dede:xxx}{/dede:xxx}以及{dede:global.cfg_cmsurl/}两种
//从标签开始位置到标签结束标记开始位置(注意 $endpos 和 $elen 的区别),循环分析
// 如果首先遇到了},两种情况,一种是{dede:global.cfg_cmsurl/}分析完毕,一种是{dede:xxx loop=10}的属性部分获取完毕,
//但是肯定的是,可以获取到的cfg_cmsurl/ loop=10均属于标签属性部分attStr,就是需要进行分析的部分
//剩下的就是循环标签的内容部分了,{dede:global.cfg_cmsurl/}这种就是空
$attStr = '';
$innerText = '';
$startInner = 0;
for($j = $tagPos+$startWordLen; $j < $endPos; $j++)
{
if($startInner==0)
{
if($this->sourceString[$j]==$tagEndWord)
{
$startInner=1; continue;
}
else
{
$attStr .= $this->sourceString[$j];
}
}
else
{
$innerText .= $this->sourceString[$j];
}
}
$ttagName = strtolower($ttagName);
//if、php标记,把整个属性串视为属性
if(preg_match("/^if[0-9]{0,}$/", $ttagName))
{
$cAtt->cAttributes = new TagAttribute();
$cAtt->cAttributes->count = 2;
$cAtt->cAttributes->items['tagname'] = $ttagName;
$cAtt->cAttributes->items['condition'] = preg_replace("/^if[0-9]{0,}[\r\n\t ]/", "", $attStr);
$innerText = preg_replace("/\{else\}/i", '<'."?php\r\n}\r\nelse{\r\n".'?'.'>', $innerText);
}
else if($ttagName=='php')
{
$cAtt->cAttributes = new TagAttribute();
$cAtt->cAttributes->count = 2;
$cAtt->cAttributes->items['tagname'] = $ttagName;
$cAtt->cAttributes->items['code'] = '<'."?php\r\n".trim(preg_replace("/^php[0-9]{0,}[\r\n\t ]/",
"",$attStr))."\r\n?".'>';
}
else
{
//普通标记,解释属性
$cAtt->SetSource($attStr);
}
//最后就是装箱集中给相应的taglib目录里面的标签处理类去处理
$this->count++;
$cTag = new Tag();
$cTag->tagName = $ttagName;
$cTag->startPos = $tagPos;
$cTag->endPos = $i;
$cTag->cAtt = $cAtt->cAttributes;
$cTag->isCompiler = FALSE;
$cTag->tagID = $this->count;
$cTag->innerText = $innerText;
$this->cTags[$this->count] = $cTag;
}
else
{
$i = $tagPos+$startWordLen;
break;
}
}//结束遍历模板字符串
//如果要编译,则开始编译(默认就是要编译的,编译好下次就不用解析标签了)
if( $this->count > -1 && $this->isCompiler )
{
$this->CompilerAll();
}
}
总结流程:
1、分析整个模板,逐字符分析
2、找到模板开始与结束标签后,要将下次循环的起始变量$i及时跳跃,节约时间
总结几点:
1、用for循环逐字符处理可能比preg_macth处理速度要快
2、写产品,要考虑到标签错误造成的各种可能
3、如果是开源产品,在设计模板标签的时候一定要合理,否则在分析的时候可能给自己带来不必要的难度