<?php   if(!defined('DEDEINC')) exit("Request Error!");
/**
 * 模板引擎文件
 *
 * @version        $Id: dedetemplate.class.php 3 15:44 2010年7月6日Z tianya $
 * @package        DedeCMS.Libraries
 * @copyright      Copyright (c) 2007 - 2020, DesDev, Inc.
 * @license        http://help.dedecms.com/usersguide/license.html
 * @link           http://www.dedecms.com
 */

/**
 *  这个函数用于定义任意名称的块使用的接口
 *  返回值应是一个二维数组
 *  块调用对应的文件为 include/taglib/plus_blockname.php
 *  ----------------------------------------------------------------
 *  由于标记一般存在默认属性，在编写块函数时，应该在块函数中进行给属性赋省缺值处理，如：
 *  $attlist = "titlelen=30,catalogid=0,modelid=0,flag=,addon=,row=8,ids=,orderby=id,orderway=desc,limit=,subday=0";
 *  给属性赋省缺值
 *  FillAtts($atts,$attlist);
 *  处理属性中使用的系统变量 var、global、field 类型(不支持多维数组)
 *  FillFields($atts,$fields,$refObj);
 *
 * @access    public
 * @param     array  $atts  属性
 * @param     object  $refObj  所属对象
 * @param     array  $fields  字段
 * @return    string
 */
function MakePublicTag($atts=array(),$refObj='',$fields=array())
{
    $atts['tagname'] = preg_replace("/[0-9]{1,}$/", "", $atts['tagname']);
    $plusfile = DEDEINC.'/tpllib/plus_'.$atts['tagname'].'.php';
    if(!file_exists($plusfile))
    {
        if(isset($atts['rstype']) && $atts['rstype']=='string')
        {
            return '';
        }
        else
        {
            return array();
        }
    }
    else
    {
        include_once($plusfile);
        $func = 'plus_'.$atts['tagname'];
        return $func($atts, $refObj, $fields);
    }
}

/**
 *  设定属性的默认值
 *
 * @access    public
 * @param     array    $atts  属性
 * @param     array    $attlist  属性列表
 * @return    void
 */
function FillAtts(&$atts, $attlist)
{
    $attlists = explode(',', $attlist);
    foreach($attlists as $att)
    {
        list($k, $v)=explode('=', $att);
        if(!isset($atts[$k]))
        {
            $atts[$k] = $v;
        }
    }
}

/**
 *  把上级的fields传递给atts
 *
 * @access    public
 * @param     array  $atts  属性
 * @param     object  $refObj  所属对象
 * @param     array  $fields  字段
 * @return    string
 */
function FillFields(&$atts, &$refObj, &$fields)
{
    global $_vars;
    foreach($atts as $k=>$v)
    {
        if(preg_match('/^field\./i',$v))
        {
            $key = preg_replace('/^field\./i', '', $v);
            if( isset($fields[$key]) )
            {
                $atts[$k] = $fields[$key];
            }
        }
        else if(preg_match('/^var\./i', $v))
        {
            $key = preg_replace('/^var\./i', '', $v);
            if( isset($_vars[$key]) )
            {
                $atts[$k] = $_vars[$key];
            }
        }
        else if(preg_match('/^global\./i', $v))
        {
            $key = preg_replace('/^global\./i', '', $v);
            if( isset($GLOBALS[$key]) )
            {
                $atts[$k] = $GLOBALS[$key];
            }
        }
    }
}

/**
 * class Tag 标记的数据结构描述
 * function C__Tag();
 *
 * @package          Tag
 * @subpackage       DedeCMS.Libraries
 * @link             http://www.dedecms.com
 */
class Tag
{
    var $isCompiler=FALSE;   //标记是否已被替代，供解析器使用
    var $tagName="";         //标记名称
    var $innerText="";       //标记之间的文本
    var $startPos=0;         //标记起始位置
    var $endPos=0;           //标记结束位置
    var $cAtt="";            //标记属性描述,即是class TagAttribute
    var $tagValue="";        //标记的值
    var $tagID = 0;

    /**
     *  获取标记的名称和值
     *
     * @access    public
     * @return    string
     */
    function GetName()
    {
        return strtolower($this->tagName);
    }

    function GetValue()
    {
        return $this->tagValue;
    }

    function IsAtt($str)
    {
        return $this->cAtt->IsAttribute($str);
    }

    function GetAtt($str)
    {
        return $this->cAtt->GetAtt($str);
    }

    /**
     *  获取底层模板
     *
     * @return    string
     */
    function GetinnerText()
    {
        return $this->innerText;
    }
}

/**
 * 模板解析器
 * function C__DedeTemplate
 *
 * @package          DedeTemplate
 * @subpackage       DedeCMS.Libraries
 * @link             http://www.dedecms.com
 */
class DedeTemplate
{
    var $tagMaxLen = 64;
    var $charToLow = TRUE;
    var $isCache = TRUE;
    var $isParse = FALSE;
    var $isCompiler = TRUE;
    var $templateDir = '';
    var $tempMkTime = 0;
    var $cacheFile = '';
    var $configFile = '';
    var $buildFile = '';
    var $refDir = '';
    var $cacheDir = '';
    var $templateFile = '';
    var $sourceString = '';
    var $cTags = array();

    //var $definedVars = array();
    var $count = -1;
    var $loopNum = 0;
    var $refObj = '';
    var $makeLoop = 0;
    var $tagStartWord =  '{dede:';
    var $fullTagEndWord =  '{/dede:';
    var $sTagEndWord = '/}';
    var $tagEndWord = '}';
    var $tpCfgs = array();


    /**
     *  析构函数
     *
     * @access    public
     * @param     string    $templatedir  模板目录
     * @param     string    $refDir  所属目录
     * @return    void
     */
    function __construct($templatedir='',$refDir='')
    {
        //$definedVars[] = 'var';
        //缓存目录
        if($templatedir=='')
        {
            $this->templateDir = DEDEROOT.'/templates';
        }
        else
        {
            $this->templateDir = $templatedir;
        }

        //模板include目录
        if($refDir=='')
        {
            if(isset($GLOBALS['cfg_df_style']))
            {
                $this->refDir = $this->templateDir.'/'.$GLOBALS['cfg_df_style'].'/';
            }
            else
            {
                $this->refDir = $this->templateDir;
            }
        }
        $this->cacheDir = DEDEROOT.$GLOBALS['cfg_tplcache_dir'];
    }

    //构造函数,兼容PHP4
    function DedeTemplate($templatedir='',$refDir='')
    {
        $this->__construct($templatedir,$refDir);
    }

    /**
     *  设定本类自身实例的类引用和使用本类的类实例(如果在类中使用本模板引擎，后一参数一般为$this)
     *
     * @access    public
     * @param     object    $refObj   实例对象
     * @return    string
     */
    function SetObject(&$refObj)
    {
        $this->refObj = $refObj;
    }

    /**
     *  设定Var的键值对
     *
     * @access    public
     * @param     string  $k  键
     * @param     string  $v  值
     * @return    string
     */
    function SetVar($k, $v)
    {
        $GLOBALS['_vars'][$k] = $v;
    }

    /**
     *  设定Var的键值对
     *
     * @access    public
     * @param     string  $k  键
     * @param     string  $v  值
     * @return    string
     */
    function Assign($k, $v)
    {
        $GLOBALS['_vars'][$k] = $v;
    }

    /**
     *  设定数组
     *
     * @access    public
     * @param     string  $k  键
     * @param     string  $v  值
     * @return    string
     */
    function SetArray($k, $v)
    {
        $GLOBALS[$k] = $v;
    }

    /**
     *  设置标记风格
     *
     * @access    public
     * @param     string   $ts  标签开始标记
     * @param     string   $ftend  标签结束标记
     * @param     string   $stend  标签尾部结束标记
     * @param     string   $tend  结束标记
     * @return    void
     */
    function SetTagStyle($ts='{dede:',$ftend='{/dede:',$stend='/}',$tend='}')
    {
        $this->tagStartWord =  $ts;
        $this->fullTagEndWord =  $ftend;
        $this->sTagEndWord = $stend;
        $this->tagEndWord = $tend;
    }

    /**
     *  获得模板设定的config值
     *
     * @access    public
     * @param     string   $k  键名
     * @return    string
     */
    function GetConfig($k)
    {
        return (isset($this->tpCfgs[$k]) ? $this->tpCfgs[$k] : '');
    }

    /**
     *  设定模板文件
     *
     * @access    public
     * @param     string  $tmpfile  模板文件
     * @return    void
     */
    function LoadTemplate($tmpfile)
    {
        if(!file_exists($tmpfile))
        {
            echo " Template Not Found! ";
            exit();
        }
        $tmpfile = preg_replace("/[\\/]{1,}/", "/", $tmpfile);
        $tmpfiles = explode('/',$tmpfile);
        $tmpfileOnlyName = preg_replace("/(.*)\//", "", $tmpfile);
        $this->templateFile = $tmpfile;
        $this->refDir = '';
        for($i=0; $i < count($tmpfiles)-1; $i++)
        {
            $this->refDir .= $tmpfiles[$i].'/';
        }
        if(!is_dir($this->cacheDir))
        {
            $this->cacheDir = $this->refDir;
        }
        if($this->cacheDir!='')
        {
            $this->cacheDir = $this->cacheDir.'/';
        }
        if(isset($GLOBALS['_DEBUG_CACHE']))
        {
            $this->cacheDir = $this->refDir;
        }
        $this->cacheFile = $this->cacheDir.preg_replace("/\.(wml|html|htm|php)$/", "_".$this->GetEncodeStr($tmpfile).'.inc', $tmpfileOnlyName);
        $this->configFile = $this->cacheDir.preg_replace("/\.(wml|html|htm|php)$/", "_".$this->GetEncodeStr($tmpfile).'_config.inc', $tmpfileOnlyName);

        //不开启缓存、当缓存文件不存在、及模板为更新的文件的时候才载入模板并进行解析
        if($this->isCache==FALSE || !file_exists($this->cacheFile)
        || filemtime($this->templateFile) > filemtime($this->cacheFile))
        {
            $t1 = ExecTime(); //debug
            $fp = fopen($this->templateFile,'r');
            $this->sourceString = fread($fp,filesize($this->templateFile));
            fclose($fp);
            $this->ParseTemplate();
            //模板解析时间
            //echo ExecTime() - $t1;
        }
        else
        {
            //如果存在config文件，则载入此文件，该文件用于保存 $this->tpCfgs的内容，以供扩展用途
            //模板中用{tag:config name='' value=''/}来设定该值
            if(file_exists($this->configFile))
            {
                include($this->configFile);
            }
        }
    }

    /**
     *  载入模板字符串
     *
     * @access    public
     * @param     string  $str  模板字符串
     * @return    void
     */
    function LoadString($str='')
    {
        $this->sourceString = $str;
        $hashcode = md5($this->sourceString);
        $this->cacheFile = $this->cacheDir."/string_".$hashcode.".inc";
        $this->configFile = $this->cacheDir."/string_".$hashcode."_config.inc";
        $this->ParseTemplate();
    }

    /**
     *  调用此函数include一个编译后的PHP文件，通常是在最后一个步骤才调用本文件
     *
     * @access    public
     * @return    string
     */
    function CacheFile()
    {
        global $gtmpfile;
        $this->WriteCache();
        return $this->cacheFile;
    }

    /**
     *  显示内容，由于函数中会重新解压一次$GLOBALS变量，所以在动态页中，应该尽量少用本方法，
     *  取代之是直接在程序中 include $tpl->CacheFile()，不过include $tpl->CacheFile()这种方式不能在类或函数内使用
     *
     * @access    public
     * @param     string
     * @return    void
     */
    function Display()
    {
        global $gtmpfile;
        extract($GLOBALS, EXTR_SKIP);
        $this->WriteCache();
        include $this->cacheFile;
    }

    /**
     *  保存运行后的程序为文件
     *
     * @access    public
     * @param     string  $savefile  保存到的文件目录
     * @return    void
     */
    function SaveTo($savefile)
    {
        extract($GLOBALS, EXTR_SKIP);
        $this->WriteCache();
        ob_start();
        include $this->cacheFile;
        $okstr = ob_get_contents();
        ob_end_clean();
        $fp = @fopen($savefile,"w") or die(" Tag Engine Create File FALSE! ");
        fwrite($fp,$okstr);
        fclose($fp);
    }

    // ------------------------------------------------------------------------

    /**
     * CheckDisabledFunctions
     *
     * COMMENT : CheckDisabledFunctions : 检查是否存在禁止的函数
     *
     * @access    public
     * @param    string
     * @return    bool
     */
    function CheckDisabledFunctions($str,&$errmsg='')
    {
        global $cfg_disable_funs;
        $cfg_disable_funs = isset($cfg_disable_funs)? $cfg_disable_funs : 'phpinfo,eval,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,file_put_contents,fsockopen,fopen,fwrite';
        // 模板引擎增加disable_functions
        if (!defined('DEDEDISFUN')) {
            $tokens = token_get_all_nl($str);
            $disabled_functions = explode(',', $cfg_disable_funs);
            foreach ($tokens as $token)
            {
                if (is_array($token))
                {
                    if ($token[0] = '306' && in_array($token[1], $disabled_functions))
                    {
                       $errmsg = 'DedeCMS Error:function disabled "'.$token[1].'" <a href="http://help.dedecms.com/install-use/apply/2013/0711/2324.html" target="_blank">more...</a>';
                       return FALSE;
                    }
                }
            }
        }
        return TRUE;
    }

    /**
     *  解析模板并写缓存文件
     *
     * @access    public
     * @param     string  $ctype  缓存类型
     * @return    void
     */
    function WriteCache($ctype='all')
    {
        if(!file_exists($this->cacheFile) || $this->isCache==FALSE
        || ( file_exists($this->templateFile) && (filemtime($this->templateFile) > filemtime($this->cacheFile)) ) )
        {
                if(!$this->isParse)
                {
                    $this->ParseTemplate();
                }
                $fp = fopen($this->cacheFile,'w') or dir("Write Cache File Error! ");
                flock($fp,3);
                $result = trim($this->GetResult());
                $errmsg = '';
                //var_dump($result);exit();
                if (!$this->CheckDisabledFunctions($result, $errmsg))
                {
                    fclose($fp);
                    @unlink($this->cacheFile);
                    die($errmsg);
                }
                fwrite($fp,$result);
                fclose($fp);
                if(count($this->tpCfgs) > 0)
                {
                    $fp = fopen($this->configFile,'w') or dir("Write Config File Error! ");
                    flock($fp,3);
                    fwrite($fp,'<'.'?php'."\r\n");
                    foreach($this->tpCfgs as $k=>$v)
                    {
                        $v = str_replace("\"","\\\"",$v);
                        $v = str_replace("\$","\\\$",$v);
                        fwrite($fp,"\$this->tpCfgs['$k']=\"$v\";\r\n");
                    }
                    fwrite($fp,'?'.'>');
                    fclose($fp);
                }
        }
        /*
        if(!file_exists($this->cacheFile) || $this->isCache==FALSE
        || ( file_exists($this->templateFile) && (filemtime($this->templateFile) > filemtime($this->cacheFile)) ) )
        {
            if($ctype!='config')
            {
                if(!$this->isParse)
                {
                    $this->ParseTemplate();
                }
                $fp = fopen($this->cacheFile,'w') or dir("Write Cache File Error! ");
                flock($fp,3);
                fwrite($fp,trim($this->GetResult()));
                fclose($fp);

            }
            else
            {
                if(count($this->tpCfgs) > 0)
                {
                    $fp = fopen($this->configFile,'w') or dir("Write Config File Error! ");
                    flock($fp,3);
                    fwrite($fp,'<'.'?php'."\r\n");
                    foreach($this->tpCfgs as $k=>$v)
                    {
                        $v = str_replace("\"","\\\"",$v);
                        $v = str_replace("\$","\\\$",$v);
                        fwrite($fp,"\$this->tpCfgs['$k']=\"$v\";\r\n");
                    }
                    fwrite($fp,'?'.'>');
                    fclose($fp);
                }
            }
        }
        else
        {
            if($ctype=='config' && count($this->tpCfgs) > 0 )
            {
                $fp = fopen($this->configFile,'w') or dir("Write Config File Error! ");
                flock($fp,3);
                fwrite($fp,'<'.'?php'."\r\n");
                foreach($this->tpCfgs as $k=>$v)
                {
                    $v = str_replace("\"","\\\"",$v);
                    $v = str_replace("\$","\\\$",$v);
                    fwrite($fp,"\$this->tpCfgs['$k']=\"$v\";\r\n");
                }
                fwrite($fp,'?'.'>');
                fclose($fp);
            }
        }
        */
    }

    /**
     *  获得模板文件名的md5字符串
     *
     * @access    public
     * @param     string  $tmpfile  模板文件
     * @return    string
     */
    function GetEncodeStr($tmpfile)
    {
        //$tmpfiles = explode('/',$tmpfile);
        $encodeStr = substr(md5($tmpfile),0,24);
        return $encodeStr;
    }

    /**
     *  解析模板
     *
     * @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) )
        {
            return;
        }
        $cAtt = new TagAttributeParse();
        $cAtt->CharToLow = TRUE;

        //遍历模板字符串，请取标记及其属性信息
        $t = 0;
        $preTag = '';
        $tswLen = strlen($tagStartWord);
        $cAtt->cAttributes->items = array();
        for($i=0; $i<$sourceLen; $i++)
        {
            $ttagName = '';

            //如果不进行此判断，将无法识别相连的两个标记
            if($i-1>=0)
            {
                $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;
            }

            //获取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];
                }
            }
            if($ttagName!='')
            {
                $i = $tagPos + $startWordLen;
                $endPos = -1;

                //判断  '/}' '{tag:下一标记开始' '{/tag:标记结束' 谁最靠近
                $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);
                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;

                //分析所找到的标记位置等信息
                $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);
                }
                $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();
        }
    }


    /**
     *  把模板标记转换为PHP代码
     *
     * @access    public
     * @return    void
     */
    function CompilerAll()
    {
        $this->loopNum++;
        if($this->loopNum > 10)
        {
            return; //限制最大递归深度为 10 以防止因标记出错等可能性导致死循环
        }
        $ResultString = '';
        $nextTagEnd = 0;
        for($i=0; isset($this->cTags[$i]); $i++)
        {
            $ResultString .= substr($this->sourceString, $nextTagEnd, $this->cTags[$i]->startPos - $nextTagEnd);
            $ResultString .= $this->CompilerOneTag($this->cTags[$i]);
            $nextTagEnd = $this->cTags[$i]->endPos;
        }
        $slen = strlen($this->sourceString);
        if($slen > $nextTagEnd)
        {
            $ResultString .= substr($this->sourceString,$nextTagEnd,$slen-$nextTagEnd);
        }
        $this->sourceString = $ResultString;
        $this->ParseTemplate();
    }


    /**
     *  获得最终结果
     *
     * @access    public
     * @return    string
     */
    function GetResult()
    {
        if(!$this->isParse)
        {
            $this->ParseTemplate();
        }
        $addset = '';
        $addset .= '<'.'?php'."\r\n".'if(!isset($GLOBALS[\'_vars\'])) $GLOBALS[\'_vars\'] = array(); '."\r\n".'$fields = array();'."\r\n".'?'.'>';
        return preg_replace("/\?".">[ \r\n\t]{0,}<"."\?php/", "", $addset.$this->sourceString);
    }

    /**
     *  编译单个标记
     *
     * @access    public
     * @param     string  $cTag  标签
     * @return    string
     */
    function CompilerOneTag(&$cTag)
    {
        $cTag->isCompiler = TRUE;
        $tagname = $cTag->tagName;
        $varname = $cTag->GetAtt('name');
        $rsvalue = "";

        //用于在模板中设置一个变量以提供作扩展用途
        //此变量直接提交到 this->tpCfgs 中，并会生成与模板对应的缓存文件 ***_config.php 文件
        if( $tagname == 'config' )
        {
            $this->tpCfgs[$varname] = $cTag->GetAtt('value');
        }
        else if( $tagname == 'global' )
        {
            $cTag->tagValue = $this->CompilerArrayVar('global',$varname);
            if( $cTag->GetAtt('function') != '' )
            {
                $cTag->tagValue = $this->CompilerFunction($cTag->GetAtt('function'), $cTag->tagValue);
            }
            $cTag->tagValue = '<'.'?php echo '.$cTag->tagValue.'; ?'.'>';
        }
        else if( $tagname == 'cfg' )
        {
            $cTag->tagValue = '$GLOBALS[\'cfg_'.$varname.'\']'; //处理函数
            if( $cTag->GetAtt('function')!='' )
            {
                $cTag->tagValue = $this->CompilerFunction($cTag->GetAtt('function'), $cTag->tagValue);
            }
            $cTag->tagValue = '<'.'?php echo '.$cTag->tagValue.'; ?'.'>';
        }
        else if( $tagname == 'name' )
        {
            $cTag->tagValue = '$'.$varname; //处理函数
            if( $cTag->GetAtt('function')!='' )
            {
                $cTag->tagValue = $this->CompilerFunction($cTag->GetAtt('function'), $cTag->tagValue);
            }
            $cTag->tagValue = '<'.'?php echo '.$cTag->tagValue.'; ?'.'>';
        }
        else if( $tagname == 'object' )
        {
            list($_obs,$_em) = explode('->',$varname);
            $cTag->tagValue = "\$GLOBALS['{$_obs}']->{$_em}"; //处理函数
            if( $cTag->GetAtt('function')!='' )
            {
                $cTag->tagValue = $this->CompilerFunction($cTag->GetAtt('function'), $cTag->tagValue);
            }
            $cTag->tagValue = '<'.'?php echo '.$cTag->tagValue.'; ?'.'>';
        }
        else if($tagname == 'var')
        {
            $cTag->tagValue = $this->CompilerArrayVar('var', $varname);

            if( $cTag->GetAtt('function')!='' )
            {
                $cTag->tagValue = $this->CompilerFunction($cTag->GetAtt('function'), $cTag->tagValue);
            }
            // 增加默认空值处理
            if ($cTag->GetAtt('default')!='')
            {
                $cTag->tagValue = '<'.'?php echo empty('.$cTag->tagValue.')? \''.addslashes($cTag->GetAtt('default')).'\':'.$cTag->tagValue.'; ?'.'>';
            } else {
                $cTag->tagValue = '<'.'?php echo '.$cTag->tagValue.'; ?'.'>';
            }
        }
        else if($tagname == 'field')
        {
            $cTag->tagValue = '$fields[\''.$varname.'\']';
            if( $cTag->GetAtt('function')!='' )
            {
                $cTag->tagValue = $this->CompilerFunction($cTag->GetAtt('function'), $cTag->tagValue);
            }
            $cTag->tagValue = '<'.'?php echo '.$cTag->tagValue.'; ?'.'>';
        }
        else if( preg_match("/^key[0-9]{0,}/", $tagname) || preg_match("/^value[0-9]{0,}/", $tagname))
        {
            if( preg_match("/^value[0-9]{0,}/", $tagname) && $varname!='' )
            {
                $cTag->tagValue = '<'.'?php echo '.$this->CompilerArrayVar($tagname,$varname).'; ?'.'>';
            }
            else
            {
                $cTag->tagValue = '<'.'?php echo $'.$tagname.'; ?'.'>';
            }
        }
        else if( preg_match("/^if[0-9]{0,}$/", $tagname) )
        {
            $cTag->tagValue = $this->CompilerIf($cTag);
        }
        else if( $tagname=='echo' )
        {
            if(trim($cTag->GetInnerText())=='') $cTag->tagValue = $cTag->GetAtt('code');
            else
            {
                $cTag->tagValue =  '<'."?php echo $".trim($cTag->GetInnerText())." ;?".'>';
            }
        }
        else if( $tagname=='php' )
        {
            if(trim($cTag->GetInnerText())=='') $cTag->tagValue = $cTag->GetAtt('code');
            else
            {
                $cTag->tagValue =  '<'."?php\r\n".trim($cTag->GetInnerText())."\r\n?".'>';
            }
        }

        //遍历数组
        else if( preg_match("/^array[0-9]{0,}/",$tagname) )
        {
            $kk = '$key';
            $vv = '$value';
            if($cTag->GetAtt('key')!='')
            {
                $kk = '$key'.$cTag->GetAtt('key');
            }
            if($cTag->GetAtt('value')!='')
            {
                $vv = '$value'.$cTag->GetAtt('value');
            }
            $addvar = '';
            if(!preg_match("/\(/",$varname))
            {
                $varname = '$GLOBALS[\''.$varname.'\']';
            }
            else
            {
                $addvar = "\r\n".'$myarrs = $pageClass->'.$varname.";\r\n";
                $varname = ' $myarrs ';
            }
            $rsvalue = '<'.'?php '.$addvar.' foreach('.$varname.' as '.$kk.'=>'.$vv.'){ ?'.">";
            $rsvalue .= $cTag->GetInnerText();
            $rsvalue .= '<'.'?php  }    ?'.">\r\n";
            $cTag->tagValue = $rsvalue;
        }

        //include 文件
        else if($tagname == 'include')
        {
            $filename = $cTag->GetAtt('file');
            if($filename=='')
            {
                $filename = $cTag->GetAtt('filename');
            }
            $cTag->tagValue = $this->CompilerInclude($filename, FALSE);
            if($cTag->tagValue==0) $cTag->tagValue = '';
            $cTag->tagValue = '<'.'?php include $this->CompilerInclude("'.$filename.'");'."\r\n".' ?'.'>';
        }
        else if( $tagname=='label' )
        {
            $bindFunc = $cTag->GetAtt('bind');
            $rsvalue = 'echo '.$bindFunc.";\r\n";
            $rsvalue = '<'.'?php  '.$rsvalue.'  ?'.">\r\n";
            $cTag->tagValue = $rsvalue;
        }
        else if( $tagname=='datalist' )
        {
            //生成属性数组
            foreach($cTag->cAtt->items as $k=>$v)
            {
                $v = $this->TrimAtts($v);
                $rsvalue .= '$atts[\''.$k.'\'] = \''.str_replace("'","\\'",$v)."';\r\n";
            }
            $rsvalue = '<'.'?php'."\r\n".'$atts = array();'."\r\n".$rsvalue;
            $rsvalue .= '$blockValue = $this->refObj->GetArcList($atts,$this->refObj,$fields); '."\r\n";
            $rsvalue .= 'if(is_array($blockValue)){'."\r\n";
            $rsvalue .= 'foreach( $blockValue as $key=>$fields )'."\r\n{\r\n".'?'.">";
            $rsvalue .= $cTag->GetInnerText();
            $rsvalue .= '<'.'?php'."\r\n}\r\n}".'?'.'>';
            $cTag->tagValue = $rsvalue;
        }
        else if( $tagname=='pagelist' )
        {
            //生成属性数组
            foreach($cTag->cAtt->items as $k=>$v)
            {
                $v = $this->TrimAtts($v);
                $rsvalue .= '$atts[\''.$k.'\'] = \''.str_replace("'","\\'",$v)."';\r\n";
            }
            $rsvalue = '<'.'?php'."\r\n".'$atts = array();'."\r\n".$rsvalue;
            $rsvalue .= ' echo $this->refObj->GetPageList($atts,$this->refObj,$fields); '."\r\n".'?'.">\r\n";
            $cTag->tagValue = $rsvalue;
        }
        else
        {
            $bindFunc = $cTag->GetAtt('bind');
            $bindType = $cTag->GetAtt('bindtype');
            $rstype =  ($cTag->GetAtt('resulttype')=='' ? $cTag->GetAtt('rstype') : $cTag->GetAtt('resulttype') );
            $rstype = strtolower($rstype);

            //生成属性数组
            foreach($cTag->cAtt->items as $k=>$v)
            {
                if(preg_match("/(bind|bindtype)/i",$k))
                {
                    continue;
                }
                $v = $this->TrimAtts($v);
                $rsvalue .= '$atts[\''.$k.'\'] = \''.str_replace("'","\\'",$v)."';\r\n";
            }
            $rsvalue = '<'.'?php'."\r\n".'$atts = array();'."\r\n".$rsvalue;

            //绑定到默认函数还是指定函数(datasource属性指定)
            if($bindFunc=='')
            {
                $rsvalue .= '$blockValue = MakePublicTag($atts,$this->refObj,$fields); '."\r\n";
            }
            else
            {
                //自定义绑定函数如果不指定 bindtype，则指向$this->refObj->绑定函数名，即是默认指向被引用的类对象
                if($bindType=='') $rsvalue .= '$blockValue = $this->refObj->'.$bindFunc.'($atts,$this->refObj,$fields); '."\r\n";
                else $rsvalue .= '$blockValue = '.$bindFunc.'($atts,$this->refObj,$fields); '."\r\n";
            }

            //返回结果类型：默认为 array 是一个二维数组，string 是字符串
            if($rstype=='string')
            {
                $rsvalue .= 'echo $blockValue;'."\r\n".'?'.">";
            }
            else
            {
                $rsvalue .= 'if(is_array($blockValue) && count($blockValue) > 0){'."\r\n";
                $rsvalue .= 'foreach( $blockValue as $key=>$fields )'."\r\n{\r\n".'?'.">";
                $rsvalue .= $cTag->GetInnerText();
                $rsvalue .= '<'.'?php'."\r\n}\r\n}\r\n".'?'.'>';
            }
            $cTag->tagValue = $rsvalue;
        }
        return $cTag->tagValue;
    }

    /**
     *  编译可能为数组的变量
     *
     * @access    public
     * @param     string  $vartype  变量类型
     * @param     string  $varname  变量名称
     * @return    string
     */
    function CompilerArrayVar($vartype, $varname)
    {
        $okvalue = '';

        if(!preg_match("/\[/", $varname))
        {
            if(preg_match("/^value/",$vartype))
            {
                $varname = $vartype.'.'.$varname;
            }
            $varnames = explode('.',$varname);
            if(isset($varnames[1]))
            {
                $varname = $varnames[0];
                for($i=1; isset($varnames[$i]); $i++)
                {
                    $varname .= "['".$varnames[$i]."']";
                }
            }
        }

        if(preg_match("/\[/", $varname))
        {
            $varnames = explode('[', $varname);
            $arrend = '';
            for($i=1;isset($varnames[$i]);$i++)
            {
                $arrend .= '['.$varnames[$i];
            }
            if(!preg_match("/[\"']/", $arrend)) {
                $arrend = str_replace('[', '', $arrend);
                $arrend = str_replace(']', '', $arrend);
                $arrend = "['{$arrend}']";
            }
            if($vartype=='var')
            {
                $okvalue = '$GLOBALS[\'_vars\'][\''.$varnames[0].'\']'.$arrend;
            }
            else if( preg_match("/^value/", $vartype) )
            {
                $okvalue = '$'.$varnames[0].$arrend;
            }
            else if($vartype=='field')
            {
                $okvalue = '$fields[\''.$varnames[0].'\']'.$arrend;
            }
            else
            {
                $okvalue = '$GLOBALS[\''.$varnames[0].'\']'.$arrend;
            }
        }
        else
        {
            if($vartype=='var')
            {
                $okvalue = '$GLOBALS[\'_vars\'][\''.$varname.'\']';
            }
            else if( preg_match("/^value/",$vartype) )
            {
                $okvalue = '$'.$vartype;
            }
            else if($vartype=='field')
            {
                $okvalue = '$'.str_replace($varname);
            }
            else
            {
                $okvalue = '$GLOBALS[\''.$varname.'\']';
            }
        }
        return $okvalue;
    }

    /**
     *  编译if标记
     *
     * @access    public
     * @param     string  $cTag  标签
     * @return    string
     */
    function CompilerIf($cTag)
    {
        $condition = trim($cTag->GetAtt('condition'));
        if($condition =='')
        {
            $cTag->tagValue=''; return '';
        }
        if (version_compare(PHP_VERSION, '5.5.0', '>='))
        {
            $condition = preg_replace_callback("/((var\.|field\.|cfg\.|global\.|key[0-9]{0,}\.|value[0-9]{0,}\.)[\._a-z0-9]+)/is", "private_rt", $condition);
        } else {
            $condition = preg_replace("/((var\.|field\.|cfg\.|global\.|key[0-9]{0,}\.|value[0-9]{0,}\.)[\._a-z0-9]+)/ies", "private_rt('\\1')", $condition);
        }        $rsvalue = '<'.'?php if('.$condition.'){ ?'.'>';
        $rsvalue .= $cTag->GetInnerText();
        $rsvalue .= '<'.'?php } ?'.'>';
        return $rsvalue;
    }

    /**
     *  处理block区块传递的atts属性的值
     *
     * @access    public
     * @param     string  $v  值
     * @return    string
     */
    function TrimAtts($v)
    {
        $v = str_replace('<'.'?','&lt;?',$v);
        $v = str_replace('?'.'>','?&gt;',$v);
        return  $v;
    }

    /**
     *  函数 function 语法处理
     *
     * @access    public
     * @param     string  $funcstr  函数字符串
     * @param     string  $nvalue  函数值
     * @return    string
     */
    function CompilerFunction($funcstr, $nvalue)
    {
        $funcstr = str_replace('@quote', '"', $funcstr);
        $funcstr = str_replace('@me', $nvalue, $funcstr);
        return $funcstr;
    }

    /**
     *  引入文件 include 语法处理
     *
     * @access    public
     * @param     string  $filename  文件名
     * @param     string  $isload  是否载入
     * @return    string
     */
    function CompilerInclude($filename, $isload=TRUE)
    {
        $okfile = '';
        if( @file_exists($filename) )
        {
            $okfile = $filename;
        }
        else if( @file_exists($this->refDir.$filename) )
        {
            $okfile = $this->refDir.$filename;
        }
        else if( @file_exists($this->refDir."../".$filename) )
        {
            $okfile = $this->refDir."../".$filename;
        }
        if($okfile=='') return 0;
        if( !$isload ) return 1;
        $itpl = new DedeTemplate($this->templateDir);
        $itpl->isCache = $this->isCache;
        $itpl->SetObject($this->refObj);
        $itpl->LoadTemplate($okfile);
        return $itpl->CacheFile();
    }
}

/**
 * class TagAttribute Tag属性集合
 * function C__TagAttribute();
 * 属性的数据描述
 *
 * @package          TagAttribute
 * @subpackage       DedeCMS.Libraries
 * @link             http://www.dedecms.com
 */
class TagAttribute
{
    var $count = -1;
    var $items = array(); //属性元素的集合

    /**
     *  获得某个属性
     *
     * @access    public
     * @param     string    $str  预处理字符串
     * @return    string
     */
    function GetAtt($str)
    {
        if($str=="")
        {
            return "";
        }
        if(isset($this->items[$str]))
        {
            return $this->items[$str];
        }
        else
        {
            return "";
        }
    }

    /**
     *  同上
     *
     * @access    public
     * @param     string    $str  预处理字符串
     * @return    string
     */
    function GetAttribute($str)
    {
        return $this->GetAtt($str);
    }

    /**
     *  判断属性是否存在
     *
     * @access    public
     * @param     string  $str  预处理字符串
     * @return    bool
     */
    function IsAttribute($str)
    {
        if(isset($this->items[$str])) return TRUE;
        else return FALSE;
    }

    /**
     *  获得标记名称
     *
     * @access    public
     * @return    string
     */
    function GettagName()
    {
        return $this->GetAtt("tagname");
    }

    /**
     *  获得属性个数
     *
     * @access    public
     * @return    int
     */
    function Getcount()
    {
        return $this->count+1;
    }
}//End Class

/**
 * 属性解析器
 * function C__TagAttributeParse();
 *
 * @package          TagAttribute
 * @subpackage       DedeCMS.Libraries
 * @link             http://www.dedecms.com
 */
class TagAttributeParse
{
    var $sourceString = "";
    var $sourceMaxSize = 1024;
    var $cAttributes = array();
    var $charToLow = TRUE;
    function SetSource($str="")
    {
        $this->cAttributes = new TagAttribute();
        $strLen = 0;
        $this->sourceString = trim(preg_replace("/[ \r\n\t\f]{1,}/"," ",$str));
        $strLen = strlen($this->sourceString);
        if($strLen>0 && $strLen <= $this->sourceMaxSize)
        {
            $this->ParseAttribute();
        }
    }

    /**
     *  解析属性
     *
     * @access    public
     * @return    void
     */
    function ParseAttribute()
    {
        $d = '';
        $tmpatt = '';
        $tmpvalue = '';
        $startdd = -1;
        $ddtag = '';
        $hasAttribute=FALSE;
        $strLen = strlen($this->sourceString);
        $this->cAttributes->items = array();

        // 获得Tag的名称，解析到 cAtt->GetAtt('tagname') 中
        for($i=0; $i<$strLen; $i++)
        {
            if($this->sourceString[$i]==' ')
            {
                $this->cAttributes->count++;
                $tmpvalues = explode('.', $tmpvalue);
                $this->cAttributes->items['tagname'] = ($this->charToLow ? strtolower($tmpvalues[0]) : $tmpvalues[0]);
                if( isset($tmpvalues[2]) )
                {
                    $okname = $tmpvalues[1];
                    for($j=2;isset($tmpvalues[$j]);$j++)
                    {
                        $okname .= "['".$tmpvalues[$j]."']";
                    }
                    $this->cAttributes->items['name'] = $okname;
                }
                else if(isset($tmpvalues[1]) && $tmpvalues[1]!='')
                {
                    $this->cAttributes->items['name'] = $tmpvalues[1];
                }
                $tmpvalue = '';
                $hasAttribute = TRUE;
                break;
            }
            else
            {
                $tmpvalue .= $this->sourceString[$i];
            }
        }

        //不存在属性列表的情况
        if(!$hasAttribute)
        {
            $this->cAttributes->count++;
            $tmpvalues = explode('.', $tmpvalue);
            $this->cAttributes->items['tagname'] = ($this->charToLow ? strtolower($tmpvalues[0]) : $tmpvalues[0]);
            if( isset($tmpvalues[2]) )
            {
                $okname = $tmpvalues[1];
                for($i=2;isset($tmpvalues[$i]);$i++)
                {
                    $okname .= "['".$tmpvalues[$i]."']";
                 }
                $this->cAttributes->items['name'] = $okname;
            }
            else 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)
            {
                if($d==$ddtag && ( isset($this->sourceString[$i-1]) && $this->sourceString[$i-1]!="\\") )
                {
                    $this->cAttributes->count++;
                    $this->cAttributes->items[$tmpatt] = trim($tmpvalue);
                    $tmpatt = '';
                    $tmpvalue = '';
                    $startdd = -1;
                }
                else
                {
                    $tmpvalue .= $d;
                }
            }
        }//for

        //最后一个属性的给值
        if($tmpatt != '')
        {
            $this->cAttributes->count++;
            $this->cAttributes->items[$tmpatt] = trim($tmpvalue);
        }//print_r($this->cAttributes->items);

    }// end func

}//End Class

/**
 *  私有标签编译,主要用于if标签内的字符串解析
 *
 * @access    public
 * @param     string  $str  需要编译的字符串
 * @return    string
 */
function private_rt($str)
{
    if (is_array($str)) {
        $arr = explode('.', $str[0]);
    } else {
        $arr = explode('.', $str);
    }
    
    $rs = '$GLOBALS[\'';
    if($arr[0] == 'cfg')
    {
        return $rs.'cfg_'.$arr[1]."']";
    }
    elseif($arr[0] == 'var')
    {
        $arr[0] = '_vars';
        $rs .= implode('\'][\'', $arr);
        $rs .= "']";
        return $rs;
    }
    elseif($arr[0] == 'global')
    {
        unset($arr[0]);
        $rs .= implode('\'][\'', $arr);
        $rs .= "']";
        return $rs;
    }
    else
    {
        if($arr[0] == 'field') $arr[0] = 'fields';
        $rs = '$'.$arr[0]."['";
        unset($arr[0]);
        $rs .= implode('\'][\'', $arr);
        $rs .= "']";
        return $rs;
    }
}
