织梦解析模板函数ParseTemplate()

admin2019-12-301203

函数名:ParseTemplate()

作用:解析模板中的标签,并把标记以及标记属性保存到对象数组里面

所在文件:dedetemplets.class.php

我们知道了设定模板文件函数已经把模板基本的东西处理好了,接下来上场的函数就是要对处理好后的模板进行解析了,解析的目的就是把模板中的标记全部取出来,然后,把这个标记和标记的属性一起存在到数组中,以备后面编译函数进行编译使用。

虽然,这样说起来简单,但是,实现起来是非常复杂的,这跟我们自己定义模板引擎那个相比,复杂程度是一个天上,一个地下,但是,二者目的完全一样,最终都是为了用php替换模板中的标签。

代码分析开始:

要对这个有二百行左右的代码分析完不是一件容易的事情,在分析前,我们先把里面几个在本函数中常用的变量弄明白,这些变量是:

  $tagStartWord =  '{dede:';
  $fullTagEndWord =  '{/dede:';
  $sTagEndWord = '/}';
  $tagEndWord = '}';

实际上是定义的标签开始与结束符号,因为,织梦模板里查找的就是这些标签符号,只有找到这些开始与结束位置了,才能找到标签里面的标记及其属性。

为了分析方便,这里我们数字标注要分析的代码,但是,并不是每一个数字只代表一行,也许一个数字代表多行,这要看分析代码的需要而定。

1. 初始化基本变量,以备下面使用,代码如下:

        if($this->makeLoop > 5)
        {
            return ;
        }
        $this->count = -1;
        $this->cTags = array();
        $this->isParse = TRUE;
        $sPos = 0;
        $ePos = 0;

这段我们就不用介绍了,都是一些初始化数据。

2.初始化标记开始与结束标记,代码如下:

标记的开始符号,即"{dede:"

$tagStartWord =  $this->tagStartWord;

块标记的结束符号,即{/dede:

$fullTagEndWord =  $this->fullTagEndWord;

无底层模板的单独标记结束符号,即 "/}"

$sTagEndWord = $this->sTagEndWord;

块标记结束符号,即 "}"

$tagEndWord = $this->tagEndWord;

计算开始标记符“{dede:”的长度

$startWordLen = strlen($tagStartWord);

计算模板字符串长度

$sourceLen = strlen($this->sourceString);

3. 判断模板字符串长度是不是正常的,若长度小于{dede:+3,退出程序,为什么小于这个长度就退出,因为,一个织梦标签最小的长度为{dede:xx /},若都比这个还小,只能说明这个模板里面的肯定没有完整的标签,所以,就要退出。判断代码如下所示:

        if( $sourceLen <= ($startWordLen + 3) )
        {
            return;
        }

4. 实例化属性解析器,以便在取出标记后,对其属性进行解析,代码如下所示:

        $cAtt = new TagAttributeParse();
        $cAtt->CharToLow = TRUE;

5.遍历模板字符串,请取标记及其属性信息,代码一个for($i=0; $i<$sourceLen; $i++)遍历。这是这个函数的核心部分,也是这个函数代码最多的一段,所以,我们把这一段也分成几部分来分析,要不一口气分析完不便于理解,并且,分析起来也麻烦。

a) 遍历的条件:$i<$sourceLen ,就是遍历从模板字符串里面第一个位置开始,一直到结束,这个比较好理解。

b) 为查找多个模板标签,设置初始化变量$i,代码如下所示:

if($i-1>=0)
            {
                $ss = $i-1;
            }
            else
            {
                $ss = 0;
            }

为什么要设置这个?因为,一个模板中有多个标签,假设有二个相关的标签{dede:field.cid/}{dede:field.channelid/},若没有这个$i,好么我们通过$tagPos = strpos($this->sourceString,$tagStartWord),那我们得到的是第一个开始的标记符号{dede:还是第二个?无法确定,当有了这个$i后,就可以区别开来二个相连的标签了。

代码为:$tagPos = strpos($this->sourceString,$tagStartWord,$ss),这样通过$ss变量,而且可以肯定的是,每遍历完一个标记后,一下次标记开始遍历后,这个$i变会变大,$ss也会变大,这样就不会重复查找前一个标签了。

c) 判断一下标记若是查找完了,就要退出遍历,代码如下所示:

            if($tagPos==0 && ($sourceLen-$i < $tswLen|| substr($this->sourceString,$i,$tswLen)!=$tagStartWord ))
            {
                $tagPos = -1;
                break;
            }

这个判断里面的条件可以理解起来不好理解,特别是后二个,第一个$tagPos若为0说明在模板开始就找到了标记开始符,这个好理解,因为,$tagPos的值就是查找{dede:出现的位置,如果这个为0了,说明{dede:在模板中一开始就是标签,这个要与下面二处条件中任意一个同时成立表示到结尾了或没有织梦标签了。

若模板中的字符串长度减去$i小于{dede:的长度,这说明已经水可能有标记了,为什么? 因为,假设模板字符串有100个字符,现在遍历了10次,现在肯定这个判断不成立,但是,如果模板字符串假设为15,遍历了10次后,也就是$i=10,这个时二者之差为5,这时肯定比{dede:的长度要小,试想一个都比标记开始符号都小的模板,不可能遍历出一个完整的标签的。此种情况表示,模板字符串已经没有标签了。

第三个条件是从$i位置查找{dede:不等于{dede:也说明到尾了。这一个判断其实理解起来不好理解。

d) 获取TAG基本信息,代码如下:

              for($j = $tagPos+$startWordLen; $j < $tagPos+$startWordLen+$this->tagMaxLen; $j++)
            {    
                //
                if(preg_match("/[ >\/\r\n\t\}\.]/", $this->sourceString[$j]))
                {
                    break;
                }
                else
                {
                    $ttagName .= $this->sourceString[$j];
                }
            }

这个for遍历的,假设以标签{dede:global.postionname/}为例子,$j表示的是从冒号“:”开始遍历,标记global,若在冒号后的字符串中找到了与符号: > \r\n\t / } . 相匹配的话,说明,这个标记已经结束了,若没有遇到这些结束符号,就把当前遍历的一个符号$this->sourceString[$j]=l,存在到$ttagNmae里面,这样一直遍历,直接把整个global遍历完,这时就遇到了点".",说明标记结束,这时,$ttagNmae=glboal了。

其中,遍历的条件$j < $tagPos+$startWordLen+$this->tagMaxLen表示$j从“{dede:”符号开始遍历,最大值不会超过$this->tagMaxLen=64,也就是$j不会超过64个字符,一般没有这么长的标记,要64个字符,织梦里面是没有,其它系统既使也做成根织梦一样的系统,一般也不会有这样的长度的标记。

在这个里面还有一个知识点:不仅数组可以有下标,字符串也可以用下标,这个跟数组一样都是用中括号,例如字符串$this->sourceString[3],表示下标为3字符,以global为遍历的例子,$this->sourceString[3]值为b。

e) 若在模板字符串中存在标记,则取出标记并设置标记的属性。

初始化:       $i = $tagPos + $startWordLen 这里的$i值就是从最外面$i(注意这二个$i虽然,名字一样,但是,实际上它们是完全不同的)位置开始加上标记开始字符的长度也就是{dede:冒号后面开始位置值。

标记符号位置初始化,代码如下:

                $fullTagEndWordThis = $fullTagEndWord.$ttagName.$tagEndWord;

                $e1 = strpos($this->sourceString, $sTagEndWord, $i);
                $e2 = strpos($this->sourceString, $tagStartWord, $i);
                $e3 = strpos($this->sourceString, $fullTagEndWordThis, $i);

                $e1 = trim($e1); $e2 = trim($e2); $e3 = trim($e3);
                $e1 = ($e1=='' ? '-1' : $e1);
                $e2 = ($e2=='' ? '-1' : $e2);
                $e3 = ($e3=='' ? '-1' : $e3);

$fullTagEndWordThis值就是:假设模板中取出的当前标记是global,那么这个变量就是:{/dede:global}

$e1表示:当前标记结束符号“/}”的位置值

$e2表示:当前标记开始符号“{dede:}”位置值

$e3表示:当前标记完整结束符号“{/dede:global}”的位置值

$e1,$e2,$e3的值都是数字型整数。

f) 判断标记符号的几种情况,代码如下:

                if($e3==-1)
                {
                    $endPos = $e1;
 
                    $elen = $endPos + strlen($sTagEndWord);
                }
                else if($e1==-1)
                {
                    $endPos = $e3;
                    $elen = $endPos + strlen($fullTagEndWordThis);
                }

若$e3为-1,说明,结束符号“{/dede:global}”不存在,把符号“/}”的位置值给$endPos,把$endPos 加上"/}"长度,即类似标签{dede:     /}到}结束后的位置长度值赋给$elen。

若$e1 为-1,说明,结束符号“/}”不存在,则把{/dede:global}标签到后面括号}的位置值,赋给$elen。

紧接着上面的代码,若“{/dede:global}”和“/}”都存在,则要比较这二个符号哪个在前面哪个在后面,若/}比{dede:和{/dede:标记都要靠近,则认为结束标志是 '/}',否则结束标志为 '{/dede:标记'。这个else后面的代码就是做的这件事,代码如下:

                else
                {
                    if($e1 < $e2 &&  $e1 < $e3 )
                    {
                        $endPos = $e1;

                        $elen = $endPos + strlen($sTagEndWord);
                    }
                    else
                    {
                        $endPos = $e3;
                        $elen = $endPos + strlen($fullTagEndWordThis);
                    }
                }

g) 分析所找到的标记位置等信息,代码如下所示:

              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];
                    }
                }

这个条件里的$j是从{dede: 符号后面的冒号开始查找,$j的值小于冒号到结束符号“/}”或“{/dede:xxx}”结束之间的值的长度。

若当前为$j时,$this->sourceString[$j]值不等于右括号,那么,就把找到的$this->sourceString[$j]值保存到$attr里面,以此类推,直到找到了右括号},则说明标签结束了。这时$attr保存的是标记属性。这时把$startInner设

置为1,这里要注意右括号指的是{dede:xxx }或{dede:xxx /}这里面的右括号。然后,跳回到for进行遍历,这次遍历就不再进入到if($startInner==0)这个条件里面了,因为,标记的属性已经取完了,也就是$startInner值为1了。

image.png

网友评论