今天講的是dedecms最關鍵的東西,模板分析啦。也就是dedetag.class.php 里面的ParseTemplet方法 模板解析方法
先看看一個dedecms標簽,大家心里有個數:
{dede:arclist row=10 orderby=pubdate type='image.' imgwidth='143' imgheight='106'}
<li><a href="[field:arcurl/]">[field:image/]<span class="title">[field:title/]</span></a></li>
{/dede:arclist}
參考上面標簽我們就可以進一步分析啦。
這里假定,你已經了解了dedecms的標簽形式,標簽格式,和標簽種類。
下面我們展開分析
先看方法前面初始化一些最基本的變量:
1)標簽起始符號和結束符號。如:“{”和"}"
$TagStartWord = $this->TagStartWord;
$TagEndWord = $this->TagEndWord;
2)設置臨時變量,用于臨時存儲查找到的新標簽在模板中的起始位置和結束位置。
$sPos = 0; $ePos = 0;
3)設定完整標簽起始字符串和結束字符串。比如:“{dede:”這種形式
$FullTagStartWord =? $TagStartWord.$this->NameSpace.":";
$sTagEndWord =? $TagStartWord."/".$this->NameSpace.":";
$eTagEndWord = "/".$TagEndWord;
這里值得注意的是結束部分分兩種,一種是類似于{aa:ff /}單體結構標簽,一種是類似于{aa:fff}{/aa:fff}符合結構標簽
4)獲取標簽其實字符串({dede:)長度和整個模板的長度
$tsLen = strlen($FullTagStartWord);
$sourceLen=strlen($this->SourceString);
?
上面就是初始變量設置部分啦。
接下來是個小判斷,如果整個模板的長度不大于標簽起始字符串的長度加3,就退出。
if( $sourceLen <= ($tsLen + 3) ){
??? return;
}
為什么要加3(也就是模板長度最少應該是標簽起始字符串長度加4)呢?
我們看看我們能寫出的最短標簽:
{dede:a/}
冒號后面是可能出現的最短字符串,就是3個,所以這里如果小于3就連最起碼的一個標簽都無法完整,所以要做這個判斷,至于等于嘛,我個人認為是沒必要的。
好繼續往下看下面兩句:
$cAtt = new DedeAttributeParse();
$cAtt->charToLow = $this->CharToLow;
創建了一個DedeAttributeParse類,并設定了CharToLow屬性,這個類看名字應該是標簽屬性分析類,charToLow就是是否把字符串自動轉化為小寫。
接下來就是一個長長的for循環了,遍歷模板字符串的每個字符進行分析,提取模板中的標簽。
for($i=0; $i < $sourceLen; $i++)
下面我們就來看看這個for循環里面是怎么分析的吧
先定義一個臨時變量,存儲當前找到的標簽的名字
$tTagName = '';
下面是一個判斷,注釋寫得很清楚,但我們現在還看不懂,所以先知道有這么個判斷就行啦
//如果不進行此判斷,將無法識別相連的兩個標記
if($i-1 >= 0){
??? $ss = $i-1;
}else{
??? $ss = 0;
}
設定了一個變量$ss,后面留意一下就是了。
下面就是查找標簽了
$sPos = strpos($this->SourceString,$FullTagStartWord,$ss);
$isTag = $sPos;
找到在模板字符串中從$ss指定的位置開始,第一個類似“{dede:”這種標簽頭的位置,并把$isTag變量設置為strpos的返回值,這是個偷懶的寫法,應該明確指出查到標簽了,就是true,而不是任意字符。
我們看到這里用到了$ss,作用是設定查找的起始位置。
我們繼續往下看吧
下面一個if語句好像是對第一個字符開始就是標簽的情況下的一種補充?
搞不懂了,本來就能找到的,加這句什么意思呢?多余哦,這個肯定有更好方法的。不多說這句了。
在下來的if就是如果沒找到標簽就不循環了,不解釋。
再下來,一個子循環
for($j=($sPos+$tsLen);$j<($sPos+$tsLen+$this->TagMaxLen);$j++)
$tsLen我們之前說了,是標簽頭(類似{dede:)長度
那這個for的解釋就是遍歷從標簽頭的下一個字符開始到標簽最大長度位置結束這中間的所有字符,看來是要找標簽名字啦
再看看for循環里面,很簡單的幾句,就是找出標簽的名字,如何找出來的呢?
if($j>($sourceLen-1)){break;}else if( ereg("[/ \t\r\n]",$this->SourceString[$j]) || $this->SourceString[$j] == $this->TagEndWord ){break;}else{$tTagName .= $this->SourceString[$j];}
這個for里面的if語句,兩種情況下名字結束,一種是字符位置到模板的字后一個位置,另一種是發現了空格、斷行、tab符、/等或找到了標簽結束符(如:"}")
通過這個for循環,標簽的名字就弄出來了,保存在變量$tTagName中。
下面是一個極其長的if語句啦,判斷$tTagName變量是否為空,如果是空則跳出循環(標簽出錯了嘛),不過跳出前還設置$i,有什么用?看不懂。
接下來重點就是找到標簽名字的情況啦。
先是設置幾個變量
$i = $sPos+$tsLen;
$endPos = -1;
$fullTagEndWordThis = $sTagEndWord.$tTagName.$TagEndWord;
把循環模板字符串的指針$i跳到標簽名字開始的地方。然后設置變量$endPos 為-1,組合出一種標簽結束符({/dede:xxx})
?
接下來是查找三個位置:$eTagEndWord(/})、$FullTagStartWord({dede:)、$fullTagEndWordThis({/dede:xxx})
$e1 = strpos($this->SourceString,$eTagEndWord, $i);
$e2 = strpos($this->SourceString,$FullTagStartWord, $i);
$e3 = strpos($this->SourceString,$fullTagEndWordThis,$i);
$e1就是在標簽名字找到后第一個"/}"出現的位置,$e2就是第一個“{dede:”出現的位置,$e3就是第一個{/dede:xxx}出現的位置。這里注意,獲取$e3值的時候,$fullTagEndWordThis是以當前找到的標簽為名字的結束字符串。
?
在下面幾句是統一$e1 $e2 $e3的值,使這三個變量如果找到要找的標簽字符串就保存位置,找不到就保存-1
$e1 = trim($e1); $e2 = trim($e2); $e3 = trim($e3);
$e1 = ($e1=='' ? '-1' : $e1);
$e2 = ($e2=='' ? '-1' : $e2);
$e3 = ($e3=='' ? '-1' : $e3);
?
接下來就要根據這三個值進行一些處理啦。處理什么呢?我們先看看這段代碼吧:
//not found '{/tag:'if($e3==-1) {$endPos = $e1;$elen = $endPos + strlen($eTagEndWord);}//not found '/}'else if($e1==-1) {$endPos = $e3;$elen = $endPos + strlen($fullTagEndWordThis);}//found '/}' and found '{/dede:'else{//if '/}' more near '{dede:'、'{/dede:' , end tag is '/}', else is '{/dede:'if($e1 < $e2 && $e1 < $e3 ){$endPos = $e1;$elen = $endPos + strlen($eTagEndWord);}else{$endPos = $e3;$elen = $endPos + strlen($fullTagEndWordThis);}}
我們知道,dedecms標簽結束有兩種方式,一種是(/})這種方式,還有一種是({/dede:xxx}),除此之外沒有他選,如果沒有這兩種結束,只能說明一個問題,模板內的標簽不完整。這個if語句做了一個假設,就是兩種標簽結束方式一定是有一種存在的。
?
if的第一個分支,假設$e3為-1,也就是(/})這種方式存在,所以設置了標簽結束符位置變量$endPos為變量$e1的值,而此時,標簽最終結束位置就知道了,是$endPos加上(/})的長度。
if語句的第二個分支和第一個類似,只是假定找到了({/dede:xxx})。
if語句的else部分,是假定兩個都找到了(有這種可能嗎?),那么就要進一步分析啦,如果(/})這種結束符出現的位置比下個標簽起始位置靠前,而且還比$e3的結束符({/dede:xxx})位置靠前,說明當前找到的(/})就是當前標簽的結束符;否則一定是({/dede:xxx})這種啦。
上面通過$e1 $e2 $e3的變量設置和一個if語句,最終是要得到兩個變量:$endPos和$elen,當前標簽結束符開始的位置和結束位置。
下面又是一個if語句,很簡單,通過endPos是否為-1判斷當前標簽是否正確結束。如果沒有正確結束則打印一段文字,然后就退出循環。這塊設計的是否可以再好點呢,比如把這塊出錯的標簽替換為一個錯誤信息,或在做模板分析前,統一檢查語法正確性,以保證更快速分析模板。
再繼續往下看,又是設置了兩個變量。
$i = $elen;
$ePos = $endPos;
由于找到當前循環要找的標簽,所以,設置主循環for的循環變量$i到下個標簽的起始位置。
設置當前標簽的結束符起始位置$ePos。
?
當前標簽的開始位置和結束位置都確定了,接下來就可以分析標簽的屬性了,我們繼續。
$attStr = '';
$innerText = '';
$startInner = 0;
三個變量,我們了解到,標簽內部有兩種東西,一種是屬性字符串,還有一種是內容字符串。$startInner 變量指示內容字符串是否開始(奇怪為什么不用布爾值呢)。
下面一個for循環開始提取這些字符串,從標簽名稱后面到結束符開始之前的部分。
for($j=($sPos+$tsLen);$j < $ePos;$j++)
?
看看循環里面是怎么提取屬性字符串和內容字符串的。
if($startInner==0 && ($this->SourceString[$j]==$TagEndWord && $this->SourceString[$j-1]!="\\") ){$startInner=1;continue;}if($startInner==0){$attStr .= $this->SourceString[$j];}else{$innerText .= $this->SourceString[$j];}
嗯,用了兩個if語句,第一個語句是用來判斷內容字符串是否開始的。第二個if語句根據內容字符串開始指示符判斷,分別讀取內容字符串和屬性字符串。
個人認為,通過特殊標識符截字更快一些。
這里面還有個問題就是,是否內容字符串開始是如何判斷的呢?
我們看看第一個if
if($startInner==0 && ($this->SourceString[$j]==$TagEndWord && $this->SourceString[$j-1]!="\\") )
$startInner==0這句就是做個過濾,當讀取內容字符串的時候就不會再走這個if了,關鍵是&&后面括號里面的內容。
如果當前字符為標簽結束符$TagEndWord(})而且結束符的前一個字符不是反斜杠的時候,就是屬性部分結束了,如果是反斜杠說明是一些模板內容之類的了。
?
通過上面的for循環我們就提取出了當前標簽的屬性和內容,接下來就開始分析屬性和內容啦
$cAtt->SetSource($attStr);if($cAtt->cAttributes->GetTagName()!=''){$this->Count++;$CDTag = new DedeTag();$CDTag->TagName = $cAtt->cAttributes->GetTagName();$CDTag->StartPos = $sPos;$CDTag->EndPos = $i;$CDTag->CAttribute = $cAtt->cAttributes;$CDTag->IsReplace = FALSE;$CDTag->TagID = $this->Count;$CDTag->InnerText = $innerText;$this->CTags[$this->Count] = $CDTag;}
通過屬性分析類來進行分析啦,然后創建DedeTag標簽類實例(就是創建一個標簽對象),然后把當前標簽的屬性都放進這個標簽對象。
包括標簽名稱、起始位置、結束位置、屬性數組、內部字符串等。
然后,把這個新的標簽對象放到DedeTagParse類的CTags數組中。
?
這樣一個標簽就分析完了,也結束了一次最外層的for循環。原來每循環一次只能分析出一個標簽,有多少個標簽就 有可能循環多少次。
?
整個模板分析結束后,如果允許緩存再調用SaveCache方法,把當前模板的標簽信息保存到緩存文件或者叫中間信息文件。
ps:其實整個模板解析方法也只是把模板標簽的信息記錄了下來放到新建的dedetag對象中,然后保存到當前DedeParse類的CTags數組中(類似于上一節我們從緩存文件分析的過程)
分析到這里,大家可能有個疑問?
織夢系統是怎么通過標簽獲取到數據的呢?這就是我們下部分需要分析的,其實細心的朋友應該已經知道,肯定是我們之前在視圖類里面的MakeOneTag這個方法里面
模板分析就講完啦,這樣該有的信息就都有了,我們又可以回到LoadTemplate方法繼續啦。 闞榮華博客:www.kanronghua.com 歡迎學習和交流