在织梦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、如果是开源产品,在设计模板标签的时候一定要合理,否则在分析的时候可能给自己带来不必要的难度