1. 程式人生 > >dedecms講解-dedetag.class.php模板解析和屬性解析,原始碼分析

dedecms講解-dedetag.class.php模板解析和屬性解析,原始碼分析

其他相對簡單,這2個稍微複雜:

模板解析:

    /**
     *  解析模板
     *
     * @access    public
     * @return    string
     */
    function ParseTemplet()
    {
        $TagStartWord = $this->TagStartWord;    // 標籤開始定界符,{
        $TagEndWord = $this->TagEndWord;        // 標籤結束定界符,}
        $sPos = 0; $ePos = 0;
        $FullTagStartWord =  $TagStartWord.$this->NameSpace.":";    // 標籤完整開始定界符,{dede:
        $sTagEndWord =  $TagStartWord."/".$this->NameSpace.":";     // 塊狀標籤結束符,{/dede:
        $eTagEndWord = "/".$TagEndWord;                             // 單標籤結束符,/}
        $tsLen = strlen($FullTagStartWord);     // 標籤完整的開始定界符長度
        $sourceLen=strlen($this->SourceString); // 模板字串長度
        
        if( $sourceLen <= ($tsLen + 3) )    // 模板字串長度至少比 '{dede:' 長度>3,因為:{dede:a/} - 這種是最短的了,不過好像可以=3啊
        {
            return;
        }
        $cAtt = new DedeAttributeParse();
        $cAtt->charToLow = $this->CharToLow;

        //遍歷模板字串,請取標記及其屬性資訊
        for($i=0; $i < $sourceLen; $i++)
        {
            $tTagName = '';

            //如果不進行此判斷,將無法識別相連的兩個標記
            if($i-1 >= 0)
            {
                $ss = $i-1;
            }
            else
            {
                $ss = 0;
            }
            $sPos = strpos($this->SourceString,$FullTagStartWord,$ss);  // 模板字串中查詢開始標記
            $isTag = $sPos;     // 是否能找到開始標記

            /*
                使用strpos(),當在第一個字元查詢到指定字元,返回的是 0,其實已經找到了,但是$isTag = 0。但是下方也使用的是 '==='(恆等) 來判斷,沒有必需再多這一步啊
             */
            if($i==0)
            {
                $headerTag = substr($this->SourceString,0,strlen($FullTagStartWord));
                if($headerTag==$FullTagStartWord)
                {
                    $isTag=TRUE; $sPos=0;
                }
            }
            if($isTag===FALSE)  // 未檢視到開始標記,無標籤,解析完畢
            {
                break;
            }
            //判斷是否已經到倒數第三個字元(可能性機率極小,取消此邏輯)
            /*
            if($sPos > ($sourceLen-$tsLen-3) )
            {
                break;
            }
            */

            /*
                功能:"標籤名"
             */
            // 從開始標記後一個字元開始查詢,最大長度不能超過規定的 TagMaxLen-標籤允許的最大長度(單標籤|塊標籤的開始塊)
            for($j=($sPos+$tsLen);$j<($sPos+$tsLen+$this->TagMaxLen);$j++)
            {
                if($j>($sourceLen-1))   // 已經查詢到模板字串的最後一位,跳出迴圈
                {
                    break;
                }

                // 匹配到 '/'、'空白' || 標籤結束定界符'}',跳出迴圈
                else if( preg_match("/[\/ \t\r\n]/", $this->SourceString[$j]) || $this->SourceString[$j] == $this->TagEndWord )
                {
                    break;
                }

                // 其他字元,都賦值給 $tTagName,得到 '標籤名'
                else
                {
                    $tTagName .= $this->SourceString[$j];
                }
            }

            /*
                匹配標籤的結束位置 
             */
            // 匹配到標籤名,接著下面邏輯
            if($tTagName!='')
            {
                $i = $sPos+$tsLen;
                $endPos = -1;
                $fullTagEndWordThis = $sTagEndWord.$tTagName.$TagEndWord;   // 得到此標籤的 '塊標籤結束定界符',"{/dede:channel}"
                
                $e1 = strpos($this->SourceString,$eTagEndWord, $i);         // 查詢單標籤結束定界符的首次出現位置,'/}' 
                $e2 = strpos($this->SourceString,$FullTagStartWord, $i);    // 查詢標籤開始定界符的首次出現位置,'{dede:'
                $e3 = strpos($this->SourceString,$fullTagEndWordThis,$i);   // 查詢此標籤的 '塊標籤結束定界符'的首次出現位置,"{/dede:channel}"
                
                //$eTagEndWord = /} $FullTagStartWord = {tag: $fullTagEndWordThis = {/tag:xxx] - 作者也給出了形式
                
                // 未找到對應的,設定為-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:'

                /*
                    針對3種情況的分析
                 */

                // 1.剩餘模板字串中未找到 '塊標籤結束符',說明只能是 '單標籤',不可能出現巢狀標籤
                if($e3==-1) 
                {
                    $endPos = $e1;  // 得到 '單標籤' 結束位置
                    $elen = $endPos + strlen($eTagEndWord);     // 得到結束長度
                }

                //not found '/}'
                // 2.剩餘模板字串中未找到 '單標籤結束符',說明只能是 '塊標籤',此種情況可能出現標籤巢狀,但是也不允許,同名標籤的巢狀。直接獲取到整個標籤即可,不用考慮內部巢狀問題
                else if($e1==-1) 
                {
                    $endPos = $e3;  // 得到 '塊標籤' 結束位置
                    $elen = $endPos + strlen($fullTagEndWordThis);  // 得到結束長度
                }

                //found '/}' and found '{/dede:'
                // 3.剩餘模板字串中 '塊標籤結束符' 和 '單標籤結束符' 都匹配到,再分下面2種情況:
                else
                {
                    //if '/}' more near '{dede:'、'{/dede:' , end tag is '/}', else is '{/dede:'
                    // 1>'/}'比'{/dede:channel}'近,同時'/}'比'{dede:'近,就說明該標籤是 '單標籤'。(保證了'/}'不是新的標籤的結束符)
                    if($e1 < $e2 &&  $e1 < $e3 )
                    {
                        $endPos = $e1;
                        $elen = $endPos + strlen($eTagEndWord);
                    }
                    // 2>其他情況,說明是 '塊標籤'
                    else
                    {
                        $endPos = $e3;
                        $elen = $endPos + strlen($fullTagEndWordThis);
                    }
                }

                //not found end tag , error
                if($endPos==-1)
                {
                    echo "Tag Character postion $sPos, '$tTagName' Error!<br />\r\n";
                    break;
                }
                $i = $elen;     // 一個標籤匹配完畢,另$i指標指向標籤結束的後一位
                $ePos = $endPos;


                //分析所找到的標記位置等資訊
                $attStr = '';
                $innerText = '';
                $startInner = 0;
                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];  // 獲取 '塊標籤' 的內部字串
                    }
                }
                //echo "<xmp>$attStr</xmp>\r\n";

                // 傳入屬性字串,解析屬性字串
                $cAtt->SetSource($attStr);
                if($cAtt->cAttributes->GetTagName()!='')
                {
                    $this->Count++;     // 標籤個數+1

                    // 例項化DedeTag標籤描述物件,設定一些必要的值
                    $CDTag = new DedeTag();
                    $CDTag->TagName = $cAtt->cAttributes->GetTagName();
                    $CDTag->StartPos = $sPos;
                    $CDTag->EndPos = $i;
                    $CDTag->CAttribute = $cAtt->cAttributes;
                    $CDTag->IsReplace = FALSE;  // 此時的替換為false,只是解析出來標籤,並未執行標籤,得通過Assign()。
                    $CDTag->TagID = $this->Count;
                    $CDTag->InnerText = $innerText;

                    // 最終,全部解析的標籤,記錄到 CTags 陣列中
                    $this->CTags[$this->Count] = $CDTag;
                }
            }
            else
            {
                $i = $sPos+$tsLen;
                break;
            }
        }//結束遍歷模板字串

        // 設定了快取,可將解析後的 CTags 陣列,寫入到快取檔案中的 $z 陣列
        if($this->IsCache)
        {
            $this->SaveCache();
        }
    }

屬性解析:

    function ParseAttribute()
    {
        $d = '';
        $tmpatt = '';
        $tmpvalue = '';
        $startdd = -1;
        $ddtag = '';
        $hasAttribute=FALSE;
        $strLen = strlen($this->sourceString);

        // 獲得Tag的名稱,解析到 cAtt->GetAtt('tagname') 中
        for($i=0; $i<$strLen; $i++)
        {
            if($this->sourceString[$i]==' ')    // 發現存在空格,表示有屬性,例如:{dede:channel row="2"}
            {
                $this->cAttributes->Count++;    // count從-1變為0,開始計算屬性個數
                $tmpvalues = explode('.', $tmpvalue);   // 支援 '.' 分隔到name。例如:{dede:field.name ...}
                $this->cAttributes->Items['tagname'] = ($this->charToLow ? strtolower($tmpvalues[0]) : $tmpvalues[0]);  // tagname屬性,新增到items陣列中
                if(isset($tmpvalues[1]) && $tmpvalues[1]!='')   // 如果有name屬性,也新增到items陣列中(name並未計入 count 計數)
                {
                    $this->cAttributes->Items['name'] = $tmpvalues[1];
                }
                $tmpvalue = '';
                $hasAttribute = TRUE;   // 標籤有屬性
                break;
            }
            else
            {
                $tmpvalue .= $this->sourceString[$i];
            }
        }

        //不存在屬性列表的情況
        if(!$hasAttribute)
        {
            /*
                不存在屬性,則僅解析出 'tagname' 和 'name'(如果有name的話),count+1(name不作為count計數)
             */
            $this->cAttributes->Count++;
            $tmpvalues = explode('.', $tmpvalue);
            $this->cAttributes->Items['tagname'] = ($this->charToLow ? strtolower($tmpvalues[0]) : $tmpvalues[0]);
            if(isset($tmpvalues[1]) && $tmpvalues[1]!='')
            {
                $this->cAttributes->Items['name'] = $tmpvalues[1];
            }
            return ;
        }
        $tmpvalue = '';

        //如果字串含有屬性值,遍歷源字串,並獲得各屬性
        for($i; $i<$strLen; $i++)
        {
            $d = $this->sourceString[$i];
            //查詢屬性名稱
            if($startdd==-1)        // 第一步,準備獲取 '屬性名'
            {
                if($d != '=')
                {
                    $tmpatt .= $d;
                }
                else
                {
                    /* 匹配到 '=',得到 '屬性名' */
                    if($this->charToLow)
                    {
                        $tmpatt = strtolower(trim($tmpatt));
                    }
                    else
                    {
                        $tmpatt = trim($tmpatt);
                    }
                    $startdd=0;     
                }
            }

            //查詢屬性的限定標誌
            else if($startdd==0)    // 進入第二步,判斷屬性值的標誌符號
            {
                switch($d)
                {
                    case ' ':       // '=' 後字串是 ' ',繼續下個字元
                        break;
                    case '"':
                        $ddtag = '"';   // =" 格式,表示屬性以 "" 包圍
                        $startdd = 1;
                        break;
                    case '\'':
                        $ddtag = '\'';  // =' 格式,表示屬性以 '' 包圍
                        $startdd = 1;
                        break;
                    default:
                        $tmpvalue .= $d;    // 支援屬性值不使用 ' 或 "
                        $ddtag = ' ';       // 此情況下,屬性的結束符就是 ' ',發現空格,表示屬性值就結束了(屬性值中不能出現空格)
                        $startdd = 1;
                        break;
                }
            }
            else if($startdd==1)    // 進入第三步,獲取屬性值
            {

                // 一旦第二步中的 '屬性結束符',說明屬性結束(這裡得注意個細節:結束符的前一位,不允許是 '\' 轉譯字元,轉譯字元說明i "當前結束符並非真正結束符")
                if($d==$ddtag && ( isset($this->sourceString[$i-1]) && $this->sourceString[$i-1]!="\\") )
                {
                    $this->cAttributes->Count++;    // count+1
                    $this->cAttributes->Items[$tmpatt] = trim($tmpvalue);   // 屬性新增到items陣列

                    // 重置幾個變數,開始下一個屬性的匹配!
                    $tmpatt = '';
                    $tmpvalue = '';
                    $startdd = -1;
                }
                else
                {
                    $tmpvalue .= $d;
                }
            }
        }//for

        /*
            這裡也得注意下:
                最後一個屬性的處理:當 {dede:channel row=2},此時,並未發現以 ' '結尾,上面的最後一步未匹配到,所以得在這裡做一次處理!
                也支援了:{dede:channel row="2},這種錯誤寫法!
         */
        //最後一個屬性的給值
        if($tmpatt != '')
        {
            $this->cAttributes->Count++;
            $this->cAttributes->Items[$tmpatt] = trim($tmpvalue);
        }
        //print_r($this->cAttributes->Items);
    }// end func