dedetag.class.php模板解析和属性解析,源码分析

模板解析:

 /**
     *  解析模板
     *
     * @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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354