现在的位置: 首页 > 综合 > 正文

dedecms的模板解析与生成原理研究成果一:ParseTemplete方法的浅析

2014年02月22日 ⁄ 综合 ⁄ 共 4632字 ⁄ 字号 评论关闭

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

抱歉!评论已关闭.