<commit_before><?php
// [$QueryPHP] The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
// ©2010-2017 http://queryphp.com All rights reserved.
namespace queryyetsimple\view;

<<<queryphp
##########################################################
#   ____                          ______  _   _ ______   #
#  /     \       ___  _ __  _   _ | ___ \| | | || ___ \  #
# |   (  ||(_)| / _ \| '__|| | | || |_/ /| |_| || |_/ /  #
#  \____/ |___||  __/| |   | |_| ||  __/ |  _  ||  __/   #
#       \__   | \___ |_|    \__  || |    | | | || |      #
#     Query Yet Simple      __/  |\_|    |_| |_|\_|      #
#                          |___ /  Since 2010.10.03      #
##########################################################
queryphp;

use InvalidArgumentException;
use queryyetsimple\helper\helper;
use queryyetsimple\filesystem\directory;
use queryyetsimple\classs\option as classs_option;
use queryyetsimple\view\interfaces\compiler as interfaces_compiler;

/**
 * 编译器列表
 *
 * @author <NAME> <<EMAIL>>
 * @package $$
 * @since 2016.11.18
 * @version 1.0
 */
class compiler implements interfaces_compiler {
    
    use classs_option;
    
    /**
     * code 支持的特殊别名映射
     *
     * @var array
     */
    protected $arrCodeMap = [ 
            'php' => '~',
            'note' => '#',
            'variable' => '$',
            'foreach' => 'list', // foreach和for冲突，foreach改为list
            'echo' => ':',
            'endtag' => [ 
                    '/list',
                    '/for',
                    '/while',
                    '/if',
                    '/script',
                    '/style' 
            ] 
    ];
    
    /**
     * node 支持的特殊别名映射
     *
     * @var array
     */
    protected $arrNodeMap = [ ];
    
    /**
     * javascript 支持的特殊别名映射
     *
     * @var array
     */
    protected $arrJsMap = [ ];
    
    /**
     * javascript 标签
     *
     * @var array
     */
    protected $arrJsTag = [
            // required 属性不能为空，single 单标签
            'if' => [ 
                    'attr' => [ 
                            'condition' 
                    ],
                    'single' => false,
                    'required' => [ 
                            'condition' 
                    ] 
            ],
            'elseif' => [ 
                    'attr' => [ 
                            'condition' 
                    ],
                    'single' => true,
                    'required' => [ 
                            'condition' 
                    ] 
            ],
            'else' => [ 
                    'attr' => [ ],
                    'single' => true,
                    'required' => [ ] 
            ],
            'each' => [ 
                    'attr' => [ 
                            'for',
                            'value',
                            'index' 
                    ],
                    'single' => false,
                    'required' => [ 
                            'for' 
                    ] 
            ] 
    ];
    
    /**
     * Node标签
     *
     * @var array
     */
    protected $arrNodeTag = [
            // required 属性不能为空，single 单标签
            'assign' => [ 
                    'attr' => [ 
                            'name',
                            'value' 
                    ],
                    'single' => true,
                    'required' => [ 
                            'name' 
                    ] 
            ],
            'if' => [ 
                    'attr' => [ 
                            'condition' 
                    ],
                    'single' => false,
                    'required' => [ 
                            'condition' 
                    ] 
            ],
            'elseif' => [ 
                    'attr' => [ 
                            'condition' 
                    ],
                    'single' => true,
                    'required' => [ 
                            'condition' 
                    ] 
            ],
            'else' => [ 
                    'attr' => [ ],
                    'single' => true,
                    'required' => [ ] 
            ],
            'list' => [ 
                    'attr' => [ 
                            'for',
                            'key',
                            'value',
                            'index' 
                    ],
                    'single' => false,
                    'required' => [ 
                            'for' 
                    ] 
            ],
            'lists' => [ 
                    'attr' => [ 
                            'index',
                            'key',
                            'mod',
                            'empty',
                            'length',
                            'offset',
                            'name',
                            'id' 
                    ],
                    'single' => false,
                    'required' => [ 
                            'name' 
                    ] 
            ],
            'include' => [ 
                    'attr' => [ 
                            'file',
                            'ext' 
                    ],
                    'single' => true,
                    'required' => [ 
                            'file' 
                    ] 
            ],
            'for' => [ 
                    'attr' => [ 
                            'step',
                            'start',
                            'end',
                            'var',
                            'type' 
                    ],
                    'single' => false,
                    'required' => [ ] 
            ],
            'while' => [ 
                    'attr' => [ 
                            'condition' 
                    ],
                    'single' => false,
                    'required' => [ 
                            'condition' 
                    ] 
            ],
            'break' => [ 
                    'attr' => [ ],
                    'single' => true,
                    'required' => [ ] 
            ],
            'continue' => [ 
                    'attr' => [ ],
                    'single' => true,
                    'required' => [ ] 
            ],
            'php' => [ 
                    'attr' => [ ],
                    'single' => false,
                    'required' => [ ] 
            ] 
    ];
    
    /**
     * 配置
     *
     * @var array
     */
    protected $arrOption = [ 
            'cache_children' => false,
            'var_identify' => '',
            'notallows_func' => [ 
                    'exit',
                    'die',
                    'return' 
            ],
            'notallows_func_js' => [ 
                    'alert' 
            ] 
    ];
    
    /**
     * 构造函数
     *
     * @param array $arrOption            
     * @return void
     */
    public function __construct(array $arrOption = []) {
        $this->options ( $arrOption );
    }
    
    /**
     * 获取编译器
     *
     * @return array
     */
    public function getCompilers() {
        $arrMethods = get_class_methods ( $this );
        
        $arrCompilers = [ ];
        foreach ( $arrMethods as $sMethod ) {
            if (substr ( $sMethod, - 8 ) != 'Compiler')
                continue;
            
            $sMethod = substr ( $sMethod, 0, - 8 );
            if (! in_array ( $sMethod, [ 
                    'global',
                    'jsvar',
                    'globalrevert',
                    'revert' 
            ] )) {
                $sType = strtolower ( substr ( $sMethod, - 4 ) );
                $sTag = substr ( $sMethod, 0, - 4 );
                if ($sType == 'code') {
                    $mixName = isset ( $this->arrCodeMap [$sTag] ) ? $this->arrCodeMap [$sTag] : $sTag;
                } elseif ($sType == 'node') {
                    $mixName = isset ( $this->arrNodeMap [$sTag] ) ? $this->arrNodeMap [$sTag] : $sTag;
                } else {
                    $sType = strtolower ( substr ( $sMethod, - 2 ) );
                    $sTag = substr ( $sMethod, 0, - 2 );
                    $mixName = isset ( $this->arrJsMap [$sTag] ) ? $this->arrJsMap [$sTag] : $sTag;
                }
                $arrCompilers [] = [ 
                        $sType,
                        $mixName,
                        $sTag 
                ];
            }
        }
        
        unset ( $arrMethods );
        return $arrCompilers;
    }
    
    /**
     * node.tag
     *
     * @return array
     */
    public function getNodeTagHelp() {
        return $this->arrNodeTag;
    }
    
    /**
     * js.tag
     *
     * @return array
     */
    public function getJsTagHelp() {
        return $this->arrJsTag;
    }
    
    // ######################################################
    // ------------------- 全局编译器 start -------------------
    // ######################################################
    
    /**
     * 全局编译器（保护标签）
     *
     * @param array $arrTheme            
     * @return void
     */
    public function globalCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( $arrTheme ['content'], 'global' );
    }
    
    /**
     * 全局还原编译器（保护标签还原）
     *
     * @param array $arrTheme            
     * @return void
     */
    public function globalrevertCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( $arrTheme ['content'], 'revert' );
    }
    
    /**
     * node.code 还原编译器
     *
     * @param array $arrTheme            
     * @return void
     */
    public function revertCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( $arrTheme ['content'], 'revert' );
    }
    
    // ######################################################
    // --------------------- 全局编译器 end ---------------------
    // ######################################################
    
    // ######################################################
    // ------------------- code 编译器 start -------------------
    // ######################################################
    
    /**
     * 变量
     *
     * @param array $arrTheme            
     * @return void
     */
    public function variableCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = ! empty ( $arrTheme ['content'] ) ? $this->parseContent ( $arrTheme ['content'] ) : null;
        if ($arrTheme ['content'] !== null) {
            $arrTheme ['content'] = '<' . '?php echo ' . $arrTheme ['content'] . '; ?' . '>';
        }
        $arrTheme ['content'] = $this->encodeContent ( $arrTheme ['content'] );
    }
    
    /**
     * if
     *
     * @param array $arrTheme            
     * @return void
     */
    public function ifCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->parseContentIf ( $arrTheme ['content'] );
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php ' . $arrTheme ['content'] . ' : ?' . '>' );
    }
    
    /**
     * elseif
     *
     * @param array $arrTheme            
     * @return void
     */
    public function elseifCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->parseContentIf ( $arrTheme ['content'], 'else' );
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php ' . $arrTheme ['content'] . ' : ?' . '>' );
    }
    
    /**
     * else 标签
     *
     * @param array $arrTheme            
     * @return void
     */
    public function elseCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php else: ?' . '>' );
    }
    
    /**
     * foreach
     *
     * @param array $arrTheme            
     * @return void
     */
    public function foreachCodeCompiler(&$arrTheme) {
        // 分析 foreach
        $calHelp = function ($sContent) {
            preg_match_all ( '/\\$([\S]+)/', $sContent, $arrArray );
            
            $arrArray = $arrArray [1];
            $nNum = count ( $arrArray );
            if ($nNum > 0) {
                if ($nNum == 2) {
                    $sResult = "\${$arrArray[1]}";
                } elseif ($nNum == 3) {
                    $sResult = "\${$arrArray[1]} => \${$arrArray[2]}";
                } else {
                    throw new InvalidArgumentException ( __ ( 'code.foreach 参数最多只能为 3 个' ) );
                }
                
                return "if (is_array ( \${$arrArray[0]} ) ) : foreach( \${$arrArray[0]} as $sResult )";
            }
        };
        
        $arrTheme ['content'] = $calHelp ( $arrTheme ['content'] );
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php ' . $arrTheme ['content'] . ': ?' . '>' );
    }
    
    /**
     * for
     *
     * @param array $arrTheme            
     * @return void
     */
    public function forCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php for( ' . $arrTheme ['content'] . ' ) : ?' . '>' );
    }
    
    /**
     * while 头部
     *
     * @param array $arrTheme            
     * @return void
     */
    public function whileCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php while( ' . $arrTheme ['content'] . ' ) : ?' . '>' );
    }
    
    /**
     * php 脚本
     *
     * @param array $arrTheme            
     * @return void
     */
    public function phpCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php ' . $arrTheme ['content'] . '; ?' . '>' );
    }
    
    /**
     * 注释
     *
     * @param array $arrTheme            
     * @return void
     */
    public function noteCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( ' ' );
    }
    
    /**
     * PHP echo 标签
     *
     * @param array $arrTheme            
     * @return void
     */
    public function echoCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php echo ' . $arrTheme ['content'] . '; ?' . '>' );
    }
    
    /**
     * javascript 初始标签
     *
     * @param array $arrTheme            
     * @return void
     */
    public function scriptCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<script type="text/javascript">' );
    }
    
    /**
     * css 初始标签
     *
     * @param array $arrTheme            
     * @return void
     */
    public function styleCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<style type="text/css">' );
    }
    
    /**
     * endtag
     *
     * @param array $arrTheme            
     * @return void
     */
    public function endtagCodeCompiler(&$arrTheme) {
        // 尾标签
        $calHelp = function ($sContent) {
            $sContent = ltrim ( trim ( $sContent ), '/' );
            switch ($sContent) {
                case 'list' :
                    $sContent = '<' . '?php endforeach; endif; ?' . '>';
                    break;
                case 'for' :
                    $sContent = '<' . '?php endfor; ?' . '>';
                    break;
                case 'while' :
                    $sContent = '<' . '?php endwhile; ?' . '>';
                    break;
                case 'if' :
                    $sContent = '<' . '?php endif; ?' . '>';
                    break;
                case 'script' :
                    $sContent = '</script>';
                    break;
                case 'style' :
                    $sContent = '</style>';
                    break;
            }
            return $sContent;
        };
        
        $arrTheme ['content'] = $calHelp ( substr ( $arrTheme ['source'], strpos ( $arrTheme ['source'], '/' ), strripos ( $arrTheme ['source'], '}' ) - 1 ) );
        $arrTheme ['content'] = $this->encodeContent ( $arrTheme ['content'] );
    }
    
    // ######################################################
    // ------------------- code 编译器 end --------------------
    // ######################################################
    
    // ######################################################
    // --------------- javascript 编译器 start ---------------
    // ######################################################
    
    /**
     * 变量及表达式
     *
     * @param array $arrTheme            
     * @return void
     */
    public function jsvarCompiler(&$arrTheme) {
        $arrTheme ['content'] = "' + " . $this->parseJsContent ( $arrTheme ['content'] ) . " + '";
    }
    
    /**
     * if 编译器
     *
     * @param array $arrTheme            
     * @return void
     */
    public function ifJsCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme, true );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        $arrAttr ['condition'] = $this->parseJsContent ( $arrAttr ['condition'] );
        
        $sCompiled = "';
if( {$arrAttr['condition']} ) {
    out += '" . $this->getNodeBody ( $arrTheme ) . "';
}
out += '";
        
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * elseif 编译器
     *
     * @param array $arrTheme            
     * @return void
     */
    public function elseifJsCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme, true );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        $arrAttr ['condition'] = $this->parseJsContent ( $arrAttr ['condition'] );
        
        $sCompiled = "';
} else if( {$arrAttr['condition']} ) {
        out += '" . $this->getNodeBody ( $arrTheme ) . "';
out += '";
        
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * else
     *
     * @param array $arrTheme            
     * @return void
     */
    public function elseJsCompiler(&$arrTheme) {
        $sCompiled = "';
} else {
out += '";
        
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * each 循环
     *
     * @param array $arrTheme            
     * @return void
     */
    public function eachJsCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme, true );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        foreach ( [ 
                'value',
                'index' 
        ] as $sKey ) {
            $arrAttr [$sKey] === null && $arrAttr [$sKey] = $sKey;
        }
        
        $sCompiled = "';
\$.each( {$arrAttr['for']}, function( {$arrAttr['index']}, {$arrAttr['value']} ) {
    out += '" . $this->getNodeBody ( $arrTheme ) . "';
});
out += '";
        
        $arrTheme ['content'] = $sCompiled;
    }
    
    // ######################################################
    // ---------------- javascript 编译器 end ----------------
    // ######################################################
    
    // ######################################################
    // ------------------ node 编译器 start ------------------
    // ######################################################
    
    /**
     * assign
     *
     * @param array $arrTheme            
     * @return void
     */
    public function assignNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        $arrAttr ['name'] = $this->parseContent ( $arrAttr ['name'], false );
        if ($arrAttr ['value'] === null) {
            $arrAttr ['value'] = '';
        } else {
            if ('$' == substr ( $arrAttr ['value'], 0, 1 )) {
                $arrAttr ['value'] = $this->parseContent ( substr ( $arrAttr ['value'], 1 ) );
            } else {
                $arrAttr ['value'] = '\'' . $arrAttr ['value'] . '\'';
            }
        }
        
        // 编译
        $sCompiled = '<?' . 'php ' . $arrAttr ['name'] . '=' . $arrAttr ['value'] . '; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * if
     *
     * @param array $arrTheme            
     * @return void
     */
    public function ifNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        $arrAttr ['condition'] = $this->parseConditionHelp ( $arrAttr ['condition'] );
        $sCompiled = '<' . '?php if( ' . $arrAttr ['condition'] . ' ) : ?' . '>' . $this->getNodeBody ( $arrTheme ) . '<' . '?php endif; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * elseif
     *
     * @param array $arrTheme            
     * @return void
     */
    public function elseifNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        $arrAttr ['condition'] = $this->parseConditionHelp ( $arrAttr ['condition'] );
        $sCompiled = '<' . '?php elseif( ' . $arrAttr ['condition'] . ' ) : ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * else
     *
     * @param array $arrTheme            
     * @return void
     */
    public function elseNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $sCompiled = '<' . '?php else : ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * foreach list
     *
     * @param array $arrTheme            
     * @return void
     */
    public function listNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        foreach ( [ 
                'key',
                'value',
                'index' 
        ] as $sKey ) {
            $arrAttr [$sKey] === null && $arrAttr [$sKey] = '$' . $sKey;
        }
        
        foreach ( [ 
                'for',
                'key',
                'value',
                'index' 
        ] as $sKey ) {
            if ('$' . $sKey == $arrAttr [$sKey]) {
                continue;
            }
            $arrAttr [$sKey] = $this->parseContent ( $arrAttr [$sKey] );
        }
        
        // 编译
        $sCompiled = '<' . '?php ' . $arrAttr ['index'] . ' = 1; ?' . '>' . '<' . '?php if( is_array( ' . $arrAttr ['for'] . ' ) ) : foreach( ' . $arrAttr ['for'] . ' as ' . $arrAttr ['key'] . ' => ' . $arrAttr ['value'] . ') : ?' . '>' . $this->getNodeBody ( $arrTheme ) . '<' . '?php ' . $arrAttr ['index'] . '++; ?' . '>' . '<' . '?php endforeach; endif; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * lists 增强版
     *
     * @param array $arrTheme            
     * @return void
     */
    public function listsNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        $arrAttr ['index'] === null && $arrAttr ['index'] = 'index';
        $arrAttr ['key'] === null && $arrAttr ['key'] = 'key';
        $arrAttr ['id'] === null && $arrAttr ['id'] = 'id';
        $arrAttr ['mod'] === null && $arrAttr ['mod'] = 2;
        if (preg_match ( "/[^\d-.,]/", $arrAttr ['mod'] )) {
            $arrAttr ['mod'] = '$' . $arrAttr ['mod'];
        }
        $arrAttr ['empty'] === null && $arrAttr ['empty'] = '';
        $arrAttr ['length'] === null && $arrAttr ['length'] = '';
        $arrAttr ['offset'] === null && $arrAttr ['offset'] = '';
        $arrAttr ['name'] = $this->parseContent ( $arrAttr ['name'] );
        
        $sCompiled = '<' . '?php if( is_array ( ' . $arrAttr ['name'] . ' ) ) : $' . $arrAttr ['index'] . ' = 0; ';
        if ('' != $arrAttr ['length']) {
            $sCompiled .= '$arrList = array_slice( ' . $arrAttr ['name'] . ', ' . $arrAttr ['offset'] . ', ' . $arrAttr ['length'] . ' ); ';
        } elseif ('' != $arrAttr ['offset']) {
            $sCompiled .= '$arrList = array_slice ( ' . $arrAttr ['name'] . ', ' . $arrAttr ['offset'] . ' ); ';
        } else {
            $sCompiled .= '$arrList = ' . $arrAttr ['name'] . '; ';
        }
        $sCompiled .= 'if( count( $arrList ) == 0 ) : echo  "' . $arrAttr ['empty'] . '"; ';
        $sCompiled .= 'else : ';
        $sCompiled .= 'foreach ( $arrList as $' . $arrAttr ['key'] . ' => $' . $arrAttr ['id'] . ' ) : ';
        $sCompiled .= '++$' . $arrAttr ['index'] . '; ';
        $sCompiled .= '$mod = ( $' . $arrAttr ['index'] . ' % ' . $arrAttr ['mod'] . ') ?' . '>';
        $sCompiled .= $this->getNodeBody ( $arrTheme );
        $sCompiled .= '<' . '?php endforeach; endif; else: echo "' . $arrAttr ['empty'] . '"; endif; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * include
     *
     * @param array $arrTheme            
     * @return void
     */
    public function includeNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        if (strpos ( $arrAttr ['file'], '$' ) !== 0 && strpos ( $arrAttr ['file'], '(' ) === false) {
            $arrAttr ['file'] = (strpos ( $arrAttr ['file'], '$' ) === 0 ? '' : '\'') . $arrAttr ['file'] . '\'';
        }
        
        // 子模板合并到主模板
        if ($this->getOption ( 'cache_children' )) {
            $sMd5 = md5 ( $arrAttr ['file'] );
            $sCompiled = "<!--<####incl*" . $sMd5 . "*ude####>-->";
            $sCompiled .= '<?' . 'php $this->display( ' . $arrAttr ['file'] . ', true, \'' . ($arrAttr ['ext'] ?  : '') . '\', __FILE__,\'' . $sMd5 . '\'   ); ?' . '>';
            $sCompiled .= "<!--</####incl*" . $sMd5 . "*ude####/>-->";
        } else {
            $sCompiled = '<?' . 'php $this->display( ' . $arrAttr ['file'] . ', true, \'' . ($arrAttr ['ext'] ?  : '') . '\' ); ?' . '>';
        }
        
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * for
     *
     * @param array $arrTheme            
     * @return void
     */
    public function forNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        $arrAttr ['step'] === null && $arrAttr ['step'] = '1';
        $arrAttr ['start'] === null && $arrAttr ['start'] = '0';
        $arrAttr ['end'] === null && $arrAttr ['end'] = '0';
        $arrAttr ['var'] === null && $arrAttr ['var'] = 'var';
        $arrAttr ['var'] = '$' . $arrAttr ['var'];
        if ($arrAttr ['type'] == '-') {
            $sComparison = ' >= ';
            $sMinusPlus = ' -= ';
        } else {
            $sComparison = ' <= ';
            $sMinusPlus = ' += ';
        }
        
        $sCompiled = '<' . '?php for( ' . $arrAttr ['var'] . ' = ' . $arrAttr ['start'] . '; ' . $arrAttr ['var'] . $sComparison . $arrAttr ['end'] . '; ' . $arrAttr ['var'] . $sMinusPlus . $arrAttr ['step'] . ' ) : ?' . '>' . $this->getNodeBody ( $arrTheme ) . '<' . '?php endfor; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * while
     *
     * @param array $arrTheme            
     * @return void
     */
    public function whileNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        $sCompiled = '<' . '?php while( ' . $arrAttr ['condition'] . ' ) : ?' . '>' . $this->getNodeBody ( $arrTheme ) . '<' . '?php endwhile; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * break
     *
     * @param array $arrTheme            
     * @return void
     */
    public function breakNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $sCompiled = '<' . '?php break; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * continue
     *
     * @param array $arrTheme            
     * @return void
     */
    public function continueNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $sCompiled = '<' . '?php continue; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * php
     *
     * @param array $arrTheme            
     * @return void
     */
    public function phpNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $sCompiled = '<' . '?php ' . $this->getNodeBody ( $arrTheme ) . ' ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * 属性编译
     *
     * @param array $arrTheme            
     * @return void
     */
    public function attributeNodeCompiler(&$arrTheme) {
        $sSource = trim ( $arrTheme ['content'] );
        $this->escapeCharacter ( $sSource );
        
        if ($arrTheme ['is_js'] === true) {
            $arrTag = $this->arrJsTag;
        } else {
            $arrTag = $this->arrNodeTag;
        }
        
        $arrAllowedAttr = $arrTag [$arrTheme ['parent_name']] ['attr'];
        
        // 正则匹配
        $arrRegexp = [ ];
        $arrRegexp [] = "/(([^=\s]+)=)?\"([^\"]+)\"/"; // xxx="yyy" 或 "yyy" 格式
        $arrRegexp [] = "/(([^=\s]+)=)?'([^\']+)'/"; // xxx='yyy' 或 'yyy' 格式
        $arrRegexp [] = "/(([^=\s]+)=)?([^\s]+)/"; // xxx=yyy 或 yyy 格式
        $nNameIdx = 2;
        $nValueIdx = 3;
        $nDefaultIdx = 0;
        foreach ( $arrRegexp as $sRegexp ) {
            if (preg_match_all ( $sRegexp, $sSource, $arrRes )) {
                foreach ( $arrRes [0] as $nIdx => $sAttribute ) {
                    $sSource = str_replace ( $sAttribute, '', $sSource );
                    $sName = $arrRes [$nNameIdx] [$nIdx];
                    if (empty ( $sName )) {
                        $nDefaultIdx ++;
                        $sName = 'condition' . $nDefaultIdx;
                    } else {
                        // 过滤掉不允许的属性
                        if (! in_array ( $sName, $arrAllowedAttr )) {
                            continue;
                        }
                    }
                    
                    $sValue = $arrRes [$nValueIdx] [$nIdx];
                    $this->escapeCharacter ( $sValue, false );
                    $arrTheme ['attribute_list'] [strtolower ( $sName )] = $sValue;
                }
            }
        }
        // 补全节点其余参数
        foreach ( $arrAllowedAttr as $str ) {
            if (! isset ( $arrTheme ['attribute_list'] [$str] )) {
                $arrTheme ['attribute_list'] [$str] = null;
            }
        }
        $arrTheme ['content'] = $sSource;
    }
    
    // ######################################################
    // ------------------- node 编译器 end -------------------
    // ######################################################
    
    // ######################################################
    // --------------------- 私有函数 start --------------------
    // ######################################################
    
    /**
     * 分析if
     *
     * @param string $sContent            
     * @param string $sType            
     * @return string
     */
    protected function parseContentIf($sContent, $sType = '') {
        $arrArray = explode ( ' ', $sContent );
        $bObj = false;
        $arrParam = [ ];
        foreach ( $arrArray as $sV ) {
            if (strpos ( $sV, '.' ) > 0) {
                $arrArgs = explode ( '.', $sV );
                $arrParam [] = $arrArgs [0] . ($this->arrayHandler ( $arrArgs, 1, 1 )); // 以$hello['hello1']['hello2']['hello2']方式
                $arrParamTwo [] = $arrArgs [0] . ($this->arrayHandler ( $arrArgs, 2, 1 )); // 以$hello->'hello1->'hello2'->'hello2'方式
                $bObj = true;
            } else {
                $arrParam [] = $sV;
                $arrParamTwo [] = $sV;
            }
        }
        
        if ($bObj) {
            $sStr = 'is_array(' . $arrArgs [0] . ')' . '?' . join ( ' ', $arrParam ) . ':' . join ( ' ', $arrParamTwo );
        } else {
            $sStr = join ( ' ', $arrParam );
        }
        
        $sStr = str_replace ( ':', '->', $sStr );
        $sStr = str_replace ( '+', '::', $sStr );
        
        return $sType . "if ( {$sStr} ) ";
    }
    
    /**
     * 解析 JS 变量内容
     *
     * @param string $sContent            
     * @return string
     */
    protected function parseJsContent($sContent) {
        $arrVar = explode ( '|', $sContent );
        $sContent = array_shift ( $arrVar );
        
        if (count ( $arrVar ) > 0) {
            $sContent = $this->parseJsFunction ( $sContent, $arrVar );
        }
        
        return '(' . $sContent . ')';
    }
    
    /**
     * 解析 JS 函数
     *
     * @param string $sName            
     * @param array $arrVar            
     * @return string
     */
    protected function parseJsFunction($sName, $arrVar) {
        return $this->parseVarFunction ( $sName, $arrVar, true );
    }
    
    /**
     * 解析变量内容
     *
     * @param string $sContent            
     * @param bool $booFunc
     *            是否允许解析函数
     * @return string
     */
    protected function parseContent($sContent, $booFunc = true) {
        $sContent = str_replace ( ':', '->', $sContent ); // 以|分割字符串,数组第一位是变量名字符串,之后的都是函数参数&&函数{$hello|md5}
        
        $arrVar = explode ( '|', $sContent );
        $sVar = array_shift ( $arrVar ); // 弹出第一个元素,也就是变量名
        if (strpos ( $sVar, '.' )) { // 访问数组元素或者属性
            $arrVars = explode ( '.', $sVar );
            if (substr ( $arrVars ['1'], 0, 1 ) == "'" or substr ( $arrVars ['1'], 0, 1 ) == '"' or substr ( $arrVars ['1'], 0, 1 ) == "$") {
                $sName = '$' . $arrVars [0] . '.' . $arrVars [1] . ($this->arrayHandler ( $arrVars, 3 )); // 特殊的.连接字符串
            } else {
                $bIsObject = false; // 解析对象的方法
                if (substr ( $sContent, - 1 ) == ')') {
                    $bIsObject = true;
                }
                
                if ($bIsObject === false) { // 非对象
                    switch (strtolower ( $this->getOption ( 'var_identify' ) )) {
                        case 'array' : // 识别为数组
                            $sName = '$' . $arrVars [0] . '[\'' . $arrVars [1] . '\']' . ($this->arrayHandler ( $arrVars ));
                            break;
                        case 'obj' : // 识别为对象
                            $sName = '$' . $arrVars [0] . '->' . $arrVars [1] . ($this->arrayHandler ( $arrVars, 2 ));
                            break;
                        default : // 自动判断数组或对象 支持多维
                            $sName = 'is_array( $' . $arrVars [0] . ' ) ? $' . $arrVars [0] . '[\'' . $arrVars [1] . '\']' . ($this->arrayHandler ( $arrVars )) . ' : $' . $arrVars [0] . '->' . $arrVars [1] . ($this->arrayHandler ( $arrVars, 2 ));
                            break;
                    }
                } else {
                    $sName = '$' . $arrVars [0] . '->' . $arrVars [1] . ($this->arrayHandler ( $arrVars, 2 ));
                }
            }
            $sVar = $arrVars [0];
        } elseif (strpos ( $sVar, '[' )) { // $hello['demo'] 方式访问数组
            $sName = "$" . $sVar;
            preg_match ( '/(.+?)\[(.+?)\]/is', $sVar, $arrArray );
            $sVar = $arrArray [1];
        } else {
            $sName = "\$$sVar";
        }
        
        if ($booFunc === true && count ( $arrVar ) > 0) { // 如果有使用函数
            $sName = $this->parseVarFunction ( $sName, $arrVar ); // 传入变量名,和函数参数继续解析,这里的变量名是上面的判断设置的值
        }
        
        $sName = str_replace ( '^', ':', $sName );
        return ! empty ( $sName ) ? $sName : '';
    }
    
    /**
     * 解析函数
     *
     * @param string $sName            
     * @param array $arrVar            
     * @param boolean $bJs
     *            是否为 JS 变量解析
     * @return string
     */
    protected function parseVarFunction($sName, $arrVar, $bJs = false) {
        $nLen = count ( $arrVar );
        // 取得模板禁止使用函数列表
        $arrNot = $this->getOption ( 'notallows_func' . ($bJs ? '' : '_js') );
        for($nI = 0; $nI < $nLen; $nI ++) {
            if (0 === stripos ( $arrVar [$nI], 'default=' )) {
                $arrArgs = explode ( '=', $arrVar [$nI], 2 );
            } else {
                $arrArgs = explode ( '=', $arrVar [$nI] );
            }
            
            $arrArgs [0] = trim ( $arrArgs [0] );
            
            if ($bJs === false) {
                $arrArgs [0] = str_replace ( '+', '::', $arrArgs [0] );
                if (isset ( $arrArgs [1] )) {
                    $arrArgs [1] = str_replace ( '->', ':', $arrArgs [1] );
                }
            }
            
            switch (strtolower ( $arrArgs [0] )) {
                case 'default' : // 特殊模板函数
                    $sName = '( ' . $sName . ' ) ? ( ' . $sName . ' ) : ' . $arrArgs [1];
                    break;
                default : // 通用模板函数
                    if (! in_array ( $arrArgs [0], $arrNot )) {
                        if (isset ( $arrArgs [1] )) {
                            if (strstr ( $arrArgs [1], '**' )) {
                                $arrArgs [1] = str_replace ( '**', $sName, $arrArgs [1] );
                                $sName = "$arrArgs[0] ( $arrArgs[1] )";
                            } else {
                                $sName = "$arrArgs[0] ( $sName,$arrArgs[1] )";
                            }
                        } elseif (! empty ( $arrArgs [0] )) {
                            $sName = "$arrArgs[0] ( $sName )";
                        }
                    }
            }
        }
        
        return $sName;
    }
    
    /**
     * 转换对象方法和静态方法连接符<commit_msg>view/compiler opzalition<commit_after><?php
// [$QueryPHP] The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
// ©2010-2017 http://queryphp.com All rights reserved.
namespace queryyetsimple\view;

<<<queryphp
##########################################################
#   ____                          ______  _   _ ______   #
#  /     \       ___  _ __  _   _ | ___ \| | | || ___ \  #
# |   (  ||(_)| / _ \| '__|| | | || |_/ /| |_| || |_/ /  #
#  \____/ |___||  __/| |   | |_| ||  __/ |  _  ||  __/   #
#       \__   | \___ |_|    \__  || |    | | | || |      #
#     Query Yet Simple      __/  |\_|    |_| |_|\_|      #
#                          |___ /  Since 2010.10.03      #
##########################################################
queryphp;

use InvalidArgumentException;
use queryyetsimple\helper\helper;
use queryyetsimple\filesystem\directory;
use queryyetsimple\classs\option as classs_option;
use queryyetsimple\view\interfaces\compiler as interfaces_compiler;

/**
 * 编译器列表
 *
 * @author <NAME> <<EMAIL>>
 * @package $$
 * @since 2016.11.18
 * @version 1.0
 */
class compiler implements interfaces_compiler {
    
    use classs_option;
    
    /**
     * code 支持的特殊别名映射
     *
     * @var array
     */
    protected $arrCodeMap = [ 
            'php' => '~',
            'note' => '#',
            'variable' => '$',
            'foreach' => 'list', // foreach和for冲突，foreach改为list
            'echo' => ':',
            'endtag' => [ 
                    '/list',
                    '/for',
                    '/while',
                    '/if',
                    '/script',
                    '/style' 
            ] 
    ];
    
    /**
     * node 支持的特殊别名映射
     *
     * @var array
     */
    protected $arrNodeMap = [ ];
    
    /**
     * javascript 支持的特殊别名映射
     *
     * @var array
     */
    protected $arrJsMap = [ ];
    
    /**
     * javascript 标签
     *
     * @var array
     */
    protected $arrJsTag = [
            // required 属性不能为空，single 单标签
            'if' => [ 
                    'attr' => [ 
                            'condition' 
                    ],
                    'single' => false,
                    'required' => [ 
                            'condition' 
                    ] 
            ],
            'elseif' => [ 
                    'attr' => [ 
                            'condition' 
                    ],
                    'single' => true,
                    'required' => [ 
                            'condition' 
                    ] 
            ],
            'else' => [ 
                    'attr' => [ ],
                    'single' => true,
                    'required' => [ ] 
            ],
            'each' => [ 
                    'attr' => [ 
                            'for',
                            'value',
                            'index' 
                    ],
                    'single' => false,
                    'required' => [ 
                            'for' 
                    ] 
            ] 
    ];
    
    /**
     * Node标签
     *
     * @var array
     */
    protected $arrNodeTag = [
            // required 属性不能为空，single 单标签
            'assign' => [ 
                    'attr' => [ 
                            'name',
                            'value' 
                    ],
                    'single' => true,
                    'required' => [ 
                            'name' 
                    ] 
            ],
            'if' => [ 
                    'attr' => [ 
                            'condition' 
                    ],
                    'single' => false,
                    'required' => [ 
                            'condition' 
                    ] 
            ],
            'elseif' => [ 
                    'attr' => [ 
                            'condition' 
                    ],
                    'single' => true,
                    'required' => [ 
                            'condition' 
                    ] 
            ],
            'else' => [ 
                    'attr' => [ ],
                    'single' => true,
                    'required' => [ ] 
            ],
            'list' => [ 
                    'attr' => [ 
                            'for',
                            'key',
                            'value',
                            'index' 
                    ],
                    'single' => false,
                    'required' => [ 
                            'for' 
                    ] 
            ],
            'lists' => [ 
                    'attr' => [ 
                            'index',
                            'key',
                            'mod',
                            'empty',
                            'length',
                            'offset',
                            'name',
                            'id' 
                    ],
                    'single' => false,
                    'required' => [ 
                            'name' 
                    ] 
            ],
            'include' => [ 
                    'attr' => [ 
                            'file',
                            'ext' 
                    ],
                    'single' => true,
                    'required' => [ 
                            'file' 
                    ] 
            ],
            'for' => [ 
                    'attr' => [ 
                            'step',
                            'start',
                            'end',
                            'var',
                            'type' 
                    ],
                    'single' => false,
                    'required' => [ ] 
            ],
            'while' => [ 
                    'attr' => [ 
                            'condition' 
                    ],
                    'single' => false,
                    'required' => [ 
                            'condition' 
                    ] 
            ],
            'break' => [ 
                    'attr' => [ ],
                    'single' => true,
                    'required' => [ ] 
            ],
            'continue' => [ 
                    'attr' => [ ],
                    'single' => true,
                    'required' => [ ] 
            ],
            'php' => [ 
                    'attr' => [ ],
                    'single' => false,
                    'required' => [ ] 
            ] 
    ];
    
    /**
     * 配置
     *
     * @var array
     */
    protected $arrOption = [ 
            'cache_children' => false,
            'var_identify' => '',
            'notallows_func' => [ 
                    'exit',
                    'die',
                    'return' 
            ],
            'notallows_func_js' => [ 
                    'alert' 
            ] 
    ];
    
    /**
     * 构造函数
     *
     * @param array $arrOption            
     * @return void
     */
    public function __construct(array $arrOption = []) {
        $this->options ( $arrOption );
    }
    
    /**
     * 获取编译器
     *
     * @return array
     */
    public function getCompilers() {
        $arrMethods = get_class_methods ( $this );
        
        $arrCompilers = [ ];
        foreach ( $arrMethods as $sMethod ) {
            if (substr ( $sMethod, - 8 ) != 'Compiler')
                continue;
            
            $sMethod = substr ( $sMethod, 0, - 8 );
            if (! in_array ( $sMethod, [ 
                    'global',
                    'jsvar',
                    'globalrevert',
                    'revert' 
            ] )) {
                $sType = strtolower ( substr ( $sMethod, - 4 ) );
                $sTag = substr ( $sMethod, 0, - 4 );
                if ($sType == 'code') {
                    $mixName = isset ( $this->arrCodeMap [$sTag] ) ? $this->arrCodeMap [$sTag] : $sTag;
                } elseif ($sType == 'node') {
                    $mixName = isset ( $this->arrNodeMap [$sTag] ) ? $this->arrNodeMap [$sTag] : $sTag;
                } else {
                    $sType = strtolower ( substr ( $sMethod, - 2 ) );
                    $sTag = substr ( $sMethod, 0, - 2 );
                    $mixName = isset ( $this->arrJsMap [$sTag] ) ? $this->arrJsMap [$sTag] : $sTag;
                }
                $arrCompilers [] = [ 
                        $sType,
                        $mixName,
                        $sTag 
                ];
            }
        }
        
        unset ( $arrMethods );
        return $arrCompilers;
    }
    
    /**
     * node.tag
     *
     * @return array
     */
    public function getNodeTagHelp() {
        return $this->arrNodeTag;
    }
    
    /**
     * js.tag
     *
     * @return array
     */
    public function getJsTagHelp() {
        return $this->arrJsTag;
    }
    
    // ######################################################
    // ------------------- 全局编译器 start -------------------
    // ######################################################
    
    /**
     * 全局编译器（保护标签）
     *
     * @param array $arrTheme            
     * @return void
     */
    public function globalCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( $arrTheme ['content'], 'global' );
    }
    
    /**
     * 全局还原编译器（保护标签还原）
     *
     * @param array $arrTheme            
     * @return void
     */
    public function globalrevertCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( $arrTheme ['content'], 'revert' );
    }
    
    /**
     * node.code 还原编译器
     *
     * @param array $arrTheme            
     * @return void
     */
    public function revertCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( $arrTheme ['content'], 'revert' );
    }
    
    // ######################################################
    // --------------------- 全局编译器 end ---------------------
    // ######################################################
    
    // ######################################################
    // ------------------- code 编译器 start -------------------
    // ######################################################
    
    /**
     * 变量
     *
     * @param array $arrTheme            
     * @return void
     */
    public function variableCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = ! empty ( $arrTheme ['content'] ) ? $this->parseContent ( $arrTheme ['content'] ) : null;
        if ($arrTheme ['content'] !== null) {
            $arrTheme ['content'] = '<' . '?php echo ' . $arrTheme ['content'] . '; ?' . '>';
        }
        $arrTheme ['content'] = $this->encodeContent ( $arrTheme ['content'] );
    }
    
    /**
     * if
     *
     * @param array $arrTheme            
     * @return void
     */
    public function ifCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->parseContentIf ( $arrTheme ['content'] );
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php ' . $arrTheme ['content'] . ' : ?' . '>' );
    }
    
    /**
     * elseif
     *
     * @param array $arrTheme            
     * @return void
     */
    public function elseifCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->parseContentIf ( $arrTheme ['content'], 'else' );
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php ' . $arrTheme ['content'] . ' : ?' . '>' );
    }
    
    /**
     * else 标签
     *
     * @param array $arrTheme            
     * @return void
     */
    public function elseCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php else: ?' . '>' );
    }
    
    /**
     * foreach
     *
     * @param array $arrTheme            
     * @return void
     */
    public function foreachCodeCompiler(&$arrTheme) {
        // 分析 foreach
        $calHelp = function ($sContent) {
            preg_match_all ( '/\\$([\S]+)/', $sContent, $arrArray );
            
            $arrArray = $arrArray [1];
            $nNum = count ( $arrArray );
            if ($nNum > 0) {
                if ($nNum == 2) {
                    $sResult = "\${$arrArray[1]}";
                } elseif ($nNum == 3) {
                    $sResult = "\${$arrArray[1]} => \${$arrArray[2]}";
                } else {
                    throw new InvalidArgumentException ( __ ( 'code.foreach 参数最多只能为 3 个' ) );
                }
                
                return "if (is_array ( \${$arrArray[0]} ) ) : foreach( \${$arrArray[0]} as $sResult )";
            }
        };
        
        $arrTheme ['content'] = $calHelp ( $arrTheme ['content'] );
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php ' . $arrTheme ['content'] . ': ?' . '>' );
    }
    
    /**
     * for
     *
     * @param array $arrTheme            
     * @return void
     */
    public function forCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php for( ' . $arrTheme ['content'] . ' ) : ?' . '>' );
    }
    
    /**
     * while 头部
     *
     * @param array $arrTheme            
     * @return void
     */
    public function whileCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php while( ' . $arrTheme ['content'] . ' ) : ?' . '>' );
    }
    
    /**
     * php 脚本
     *
     * @param array $arrTheme            
     * @return void
     */
    public function phpCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php ' . $arrTheme ['content'] . '; ?' . '>' );
    }
    
    /**
     * 注释
     *
     * @param array $arrTheme            
     * @return void
     */
    public function noteCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( ' ' );
    }
    
    /**
     * PHP echo 标签
     *
     * @param array $arrTheme            
     * @return void
     */
    public function echoCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<?' . 'php echo ' . $arrTheme ['content'] . '; ?' . '>' );
    }
    
    /**
     * javascript 初始标签
     *
     * @param array $arrTheme            
     * @return void
     */
    public function scriptCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<script type="text/javascript">' );
    }
    
    /**
     * css 初始标签
     *
     * @param array $arrTheme            
     * @return void
     */
    public function styleCodeCompiler(&$arrTheme) {
        $arrTheme ['content'] = $this->encodeContent ( '<style type="text/css">' );
    }
    
    /**
     * endtag
     *
     * @param array $arrTheme            
     * @return void
     */
    public function endtagCodeCompiler(&$arrTheme) {
        // 尾标签
        $calHelp = function ($sContent) {
            $sContent = ltrim ( trim ( $sContent ), '/' );
            switch ($sContent) {
                case 'list' :
                    $sContent = '<' . '?php endforeach; endif; ?' . '>';
                    break;
                case 'for' :
                    $sContent = '<' . '?php endfor; ?' . '>';
                    break;
                case 'while' :
                    $sContent = '<' . '?php endwhile; ?' . '>';
                    break;
                case 'if' :
                    $sContent = '<' . '?php endif; ?' . '>';
                    break;
                case 'script' :
                    $sContent = '</script>';
                    break;
                case 'style' :
                    $sContent = '</style>';
                    break;
            }
            return $sContent;
        };
        
        $arrTheme ['content'] = $calHelp ( substr ( $arrTheme ['source'], strpos ( $arrTheme ['source'], '/' ), strripos ( $arrTheme ['source'], '}' ) - 1 ) );
        $arrTheme ['content'] = $this->encodeContent ( $arrTheme ['content'] );
    }
    
    // ######################################################
    // ------------------- code 编译器 end --------------------
    // ######################################################
    
    // ######################################################
    // --------------- javascript 编译器 start ---------------
    // ######################################################
    
    /**
     * 变量及表达式
     *
     * @param array $arrTheme            
     * @return void
     */
    public function jsvarCompiler(&$arrTheme) {
        $arrTheme ['content'] = "' + " . $this->parseJsContent ( $arrTheme ['content'] ) . " + '";
    }
    
    /**
     * if 编译器
     *
     * @param array $arrTheme            
     * @return void
     */
    public function ifJsCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme, true );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        $arrAttr ['condition'] = $this->parseJsContent ( $arrAttr ['condition'] );
        
        $sCompiled = "';
if( {$arrAttr['condition']} ) {
    out += '" . $this->getNodeBody ( $arrTheme ) . "';
}
out += '";
        
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * elseif 编译器
     *
     * @param array $arrTheme            
     * @return void
     */
    public function elseifJsCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme, true );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        $arrAttr ['condition'] = $this->parseJsContent ( $arrAttr ['condition'] );
        
        $sCompiled = "';
} else if( {$arrAttr['condition']} ) {
        out += '" . $this->getNodeBody ( $arrTheme ) . "';
out += '";
        
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * else
     *
     * @param array $arrTheme            
     * @return void
     */
    public function elseJsCompiler(&$arrTheme) {
        $sCompiled = "';
} else {
out += '";
        
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * each 循环
     *
     * @param array $arrTheme            
     * @return void
     */
    public function eachJsCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme, true );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        foreach ( [ 
                'value',
                'index' 
        ] as $sKey ) {
            $arrAttr [$sKey] === null && $arrAttr [$sKey] = $sKey;
        }
        
        $sCompiled = "';
\$.each( {$arrAttr['for']}, function( {$arrAttr['index']}, {$arrAttr['value']} ) {
    out += '" . $this->getNodeBody ( $arrTheme ) . "';
});
out += '";
        
        $arrTheme ['content'] = $sCompiled;
    }
    
    // ######################################################
    // ---------------- javascript 编译器 end ----------------
    // ######################################################
    
    // ######################################################
    // ------------------ node 编译器 start ------------------
    // ######################################################
    
    /**
     * assign
     *
     * @param array $arrTheme            
     * @return void
     */
    public function assignNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        $arrAttr ['name'] = $this->parseContent ( $arrAttr ['name'], false );
        if ($arrAttr ['value'] === null) {
            $arrAttr ['value'] = '';
        } else {
            if ('$' == substr ( $arrAttr ['value'], 0, 1 )) {
                $arrAttr ['value'] = $this->parseContent ( substr ( $arrAttr ['value'], 1 ) );
            } else {
                $arrAttr ['value'] = '\'' . $arrAttr ['value'] . '\'';
            }
        }
        
        // 编译
        $sCompiled = '<?' . 'php ' . $arrAttr ['name'] . '=' . $arrAttr ['value'] . '; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * if
     *
     * @param array $arrTheme            
     * @return void
     */
    public function ifNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        $arrAttr ['condition'] = $this->parseConditionHelp ( $arrAttr ['condition'] );
        $sCompiled = '<' . '?php if( ' . $arrAttr ['condition'] . ' ) : ?' . '>' . $this->getNodeBody ( $arrTheme ) . '<' . '?php endif; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * elseif
     *
     * @param array $arrTheme            
     * @return void
     */
    public function elseifNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        $arrAttr ['condition'] = $this->parseConditionHelp ( $arrAttr ['condition'] );
        $sCompiled = '<' . '?php elseif( ' . $arrAttr ['condition'] . ' ) : ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * else
     *
     * @param array $arrTheme            
     * @return void
     */
    public function elseNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $sCompiled = '<' . '?php else : ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * foreach list
     *
     * @param array $arrTheme            
     * @return void
     */
    public function listNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        foreach ( [ 
                'key',
                'value',
                'index' 
        ] as $sKey ) {
            $arrAttr [$sKey] === null && $arrAttr [$sKey] = '$' . $sKey;
        }
        
        foreach ( [ 
                'for',
                'key',
                'value',
                'index' 
        ] as $sKey ) {
            if ('$' . $sKey == $arrAttr [$sKey]) {
                continue;
            }
            $arrAttr [$sKey] = $this->parseContent ( $arrAttr [$sKey] );
        }
        
        // 编译
        $sCompiled = '<' . '?php ' . $arrAttr ['index'] . ' = 1; ?' . '>' . '<' . '?php if( is_array( ' . $arrAttr ['for'] . ' ) ) : foreach( ' . $arrAttr ['for'] . ' as ' . $arrAttr ['key'] . ' => ' . $arrAttr ['value'] . ') : ?' . '>' . $this->getNodeBody ( $arrTheme ) . '<' . '?php ' . $arrAttr ['index'] . '++; ?' . '>' . '<' . '?php endforeach; endif; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * lists 增强版
     *
     * @param array $arrTheme            
     * @return void
     */
    public function listsNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        $arrAttr ['index'] === null && $arrAttr ['index'] = 'index';
        $arrAttr ['key'] === null && $arrAttr ['key'] = 'key';
        $arrAttr ['id'] === null && $arrAttr ['id'] = 'id';
        $arrAttr ['mod'] === null && $arrAttr ['mod'] = 2;
        if (preg_match ( "/[^\d-.,]/", $arrAttr ['mod'] )) {
            $arrAttr ['mod'] = '$' . $arrAttr ['mod'];
        }
        $arrAttr ['empty'] === null && $arrAttr ['empty'] = '';
        $arrAttr ['length'] === null && $arrAttr ['length'] = '';
        $arrAttr ['offset'] === null && $arrAttr ['offset'] = '';
        $arrAttr ['name'] = $this->parseContent ( $arrAttr ['name'] );
        
        $sCompiled = '<' . '?php if( is_array ( ' . $arrAttr ['name'] . ' ) ) : $' . $arrAttr ['index'] . ' = 0; ';
        if ('' != $arrAttr ['length']) {
            $sCompiled .= '$arrList = array_slice( ' . $arrAttr ['name'] . ', ' . $arrAttr ['offset'] . ', ' . $arrAttr ['length'] . ' ); ';
        } elseif ('' != $arrAttr ['offset']) {
            $sCompiled .= '$arrList = array_slice ( ' . $arrAttr ['name'] . ', ' . $arrAttr ['offset'] . ' ); ';
        } else {
            $sCompiled .= '$arrList = ' . $arrAttr ['name'] . '; ';
        }
        $sCompiled .= 'if( count( $arrList ) == 0 ) : echo  "' . $arrAttr ['empty'] . '"; ';
        $sCompiled .= 'else : ';
        $sCompiled .= 'foreach ( $arrList as $' . $arrAttr ['key'] . ' => $' . $arrAttr ['id'] . ' ) : ';
        $sCompiled .= '++$' . $arrAttr ['index'] . '; ';
        $sCompiled .= '$mod = ( $' . $arrAttr ['index'] . ' % ' . $arrAttr ['mod'] . ') ?' . '>';
        $sCompiled .= $this->getNodeBody ( $arrTheme );
        $sCompiled .= '<' . '?php endforeach; endif; else: echo "' . $arrAttr ['empty'] . '"; endif; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * include
     *
     * @param array $arrTheme            
     * @return void
     */
    public function includeNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        if (strpos ( $arrAttr ['file'], '$' ) !== 0 && strpos ( $arrAttr ['file'], '(' ) === false) {
            $arrAttr ['file'] = (strpos ( $arrAttr ['file'], '$' ) === 0 ? '' : '\'') . $arrAttr ['file'] . '\'';
        }
        
        // 子模板合并到主模板
        if ($this->getOption ( 'cache_children' )) {
            $sMd5 = md5 ( $arrAttr ['file'] );
            $sCompiled = "<!--<####incl*" . $sMd5 . "*ude####>-->";
            $sCompiled .= '<?' . 'php $this->display( ' . $arrAttr ['file'] . ', true, \'' . ($arrAttr ['ext'] ?  : '') . '\', __FILE__,\'' . $sMd5 . '\'   ); ?' . '>';
            $sCompiled .= "<!--</####incl*" . $sMd5 . "*ude####/>-->";
        } else {
            $sCompiled = '<?' . 'php $this->display( ' . $arrAttr ['file'] . ', true, \'' . ($arrAttr ['ext'] ?  : '') . '\' ); ?' . '>';
        }
        
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * for
     *
     * @param array $arrTheme            
     * @return void
     */
    public function forNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        $arrAttr ['step'] === null && $arrAttr ['step'] = '1';
        $arrAttr ['start'] === null && $arrAttr ['start'] = '0';
        $arrAttr ['end'] === null && $arrAttr ['end'] = '0';
        $arrAttr ['var'] === null && $arrAttr ['var'] = 'var';
        $arrAttr ['var'] = '$' . $arrAttr ['var'];
        if ($arrAttr ['type'] == '-') {
            $sComparison = ' >= ';
            $sMinusPlus = ' -= ';
        } else {
            $sComparison = ' <= ';
            $sMinusPlus = ' += ';
        }
        
        $sCompiled = '<' . '?php for( ' . $arrAttr ['var'] . ' = ' . $arrAttr ['start'] . '; ' . $arrAttr ['var'] . $sComparison . $arrAttr ['end'] . '; ' . $arrAttr ['var'] . $sMinusPlus . $arrAttr ['step'] . ' ) : ?' . '>' . $this->getNodeBody ( $arrTheme ) . '<' . '?php endfor; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * while
     *
     * @param array $arrTheme            
     * @return void
     */
    public function whileNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $arrAttr = $this->getNodeAttribute ( $arrTheme );
        
        $sCompiled = '<' . '?php while( ' . $arrAttr ['condition'] . ' ) : ?' . '>' . $this->getNodeBody ( $arrTheme ) . '<' . '?php endwhile; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * break
     *
     * @param array $arrTheme            
     * @return void
     */
    public function breakNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $sCompiled = '<' . '?php break; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * continue
     *
     * @param array $arrTheme            
     * @return void
     */
    public function continueNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $sCompiled = '<' . '?php continue; ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * php
     *
     * @param array $arrTheme            
     * @return void
     */
    public function phpNodeCompiler(&$arrTheme) {
        $this->checkNode ( $arrTheme );
        $sCompiled = '<' . '?php ' . $this->getNodeBody ( $arrTheme ) . ' ?' . '>';
        $arrTheme ['content'] = $sCompiled;
    }
    
    /**
     * 属性编译
     *
     * @param array $arrTheme            
     * @return void
     */
    public function attributeNodeCompiler(&$arrTheme) {
        $sSource = trim ( $arrTheme ['content'] );
        $this->escapeCharacter ( $sSource );
        
        if ($arrTheme ['is_js'] === true) {
            $arrTag = $this->arrJsTag;
        } else {
            $arrTag = $this->arrNodeTag;
        }
        
        $arrAllowedAttr = $arrTag [$arrTheme ['parent_name']] ['attr'];
        
        // 正则匹配
        $arrRegexp = [ ];
        $arrRegexp [] = "/(([^=\s]+)=)?\"([^\"]+)\"/"; // xxx="yyy" 或 "yyy" 格式
        $arrRegexp [] = "/(([^=\s]+)=)?'([^\']+)'/"; // xxx='yyy' 或 'yyy' 格式
        $arrRegexp [] = "/(([^=\s]+)=)?([^\s]+)/"; // xxx=yyy 或 yyy 格式
        $nNameIdx = 2;
        $nValueIdx = 3;
        $nDefaultIdx = 0;
        foreach ( $arrRegexp as $sRegexp ) {
            if (preg_match_all ( $sRegexp, $sSource, $arrRes )) {
                foreach ( $arrRes [0] as $nIdx => $sAttribute ) {
                    $sSource = str_replace ( $sAttribute, '', $sSource );
                    $sName = $arrRes [$nNameIdx] [$nIdx];
                    if (empty ( $sName )) {
                        $nDefaultIdx ++;
                        $sName = 'condition' . $nDefaultIdx;
                    } else {
                        // 过滤掉不允许的属性
                        if (! in_array ( $sName, $arrAllowedAttr )) {
                            continue;
                        }
                    }
                    
                    $sValue = $arrRes [$nValueIdx] [$nIdx];
                    $this->escapeCharacter ( $sValue, false );
                    $arrTheme ['attribute_list'] [strtolower ( $sName )] = $sValue;
                }
            }
        }
        // 补全节点其余参数
        foreach ( $arrAllowedAttr as $str ) {
            if (! isset ( $arrTheme ['attribute_list'] [$str] )) {
                $arrTheme ['attribute_list'] [$str] = null;
            }
        }
        $arrTheme ['content'] = $sSource;
    }
    
    // ######################################################
    // ------------------- node 编译器 end -------------------
    // ######################################################
    
    // ######################################################
    // --------------------- 私有函数 start --------------------
    // ######################################################
    
    /**
     * 分析if
     *
     * @param string $sContent            
     * @param string $sType            
     * @return string
     */
    protected function parseContentIf($sContent, $sType = '') {
        $arrArray = explode ( ' ', $sContent );
        $bObj = false;
        $arrParam = [ ];
        foreach ( $arrArray as $sV ) {
            if (strpos ( $sV, '.' ) > 0) {
                $arrArgs = explode ( '.', $sV );
                $arrParam [] = $arrArgs [0] . ($this->arrayHandler ( $arrArgs, 1, 1 )); // 以$hello['hello1']['hello2']['hello2']方式
                $arrParamTwo [] = $arrArgs [0] . ($this->arrayHandler ( $arrArgs, 2, 1 )); // 以$hello->'hello1->'hello2'->'hello2'方式
                $bObj = true;
            } else {
                $arrParam [] = $sV;
                $arrParamTwo [] = $sV;
            }
        }
        
        if ($bObj) {
            $sStr = 'is_array(' . $arrArgs [0] . ')' . '?' . join ( ' ', $arrParam ) . ':' . join ( ' ', $arrParamTwo );
        } else {
            $sStr = join ( ' ', $arrParam );
        }
        
        $sStr = str_replace ( ':', '->', $sStr );
        $sStr = str_replace ( '+', '::', $sStr );
        
        return $sType . "if ( {$sStr} ) ";
    }
    
    /**
     * 解析 JS 变量内容
     *
     * @param string $sContent            
     * @return string
     */
    protected function parseJsContent($sContent) {
        $arrVar = explode ( '|', $sContent );
        $sContent = array_shift ( $arrVar );
        
        if (count ( $arrVar ) > 0) {
            $sContent = $this->parseJsFunction ( $sContent, $arrVar );
        }
        
        return '(' . $sContent . ')';
    }
    
    /**
     * 解析 JS 函数
     *
     * @param string $sName            
     * @param array $arrVar            
     * @return string
     */
    protected function parseJsFunction($sName, $arrVar) {
        return $this->parseVarFunction ( $sName, $arrVar, true );
    }
    
    /**
     * 解析变量内容
     *
     * @param string $sContent            
     * @param bool $booFunc
     *            是否允许解析函数
     * @return string
     */
    protected function parseContent($sContent, $booFunc = true) {
        $sContent = str_replace ( ':', '->', $sContent ); // 以|分割字符串,数组第一位是变量名字符串,之后的都是函数参数&&函数{$hello|md5}
        
        $arrVar = explode ( '|', $sContent );
        $sVar = array_shift ( $arrVar ); // 弹出第一个元素,也就是变量名
        if (strpos ( $sVar, '.' )) { // 访问数组元素或者属性
            $arrVars = explode ( '.', $sVar );
            if (substr ( $arrVars ['1'], 0, 1 ) == "'" or substr ( $arrVars ['1'], 0, 1 ) == '"' or substr ( $arrVars ['1'], 0, 1 ) == "$") {
                $sName = '$' . $arrVars [0] . '.' . $arrVars [1] . ($this->arrayHandler ( $arrVars, 3 )); // 特殊的.连接字符串
            } else {
                $bIsObject = false; // 解析对象的方法
                if (substr ( $sContent, - 1 ) == ')') {
                    $bIsObject = true;
                }
                
                if ($bIsObject === false) { // 非对象
                    switch (strtolower ( $this->getOption ( 'var_identify' ) )) {
                        case 'array' : // 识别为数组
                            $sName = '$' . $arrVars [0] . '[\'' . $arrVars [1] . '\']' . ($this->arrayHandler ( $arrVars ));
                            break;
                        case 'obj' : // 识别为对象
                            $sName = '$' . $arrVars [0] . '->' . $arrVars [1] . ($this->arrayHandler ( $arrVars, 2 ));
                            break;
                        default : // 自动判断数组或对象 支持多维
                            $sName = 'is_array( $' . $arrVars [0] . ' ) ? $' . $arrVars [0] . '[\'' . $arrVars [1] . '\']' . ($this->arrayHandler ( $arrVars )) . ' : $' . $arrVars [0] . '->' . $arrVars [1] . ($this->arrayHandler ( $arrVars, 2 ));
                            break;
                    }
                } else {
                    $sName = '$' . $arrVars [0] . '->' . $arrVars [1] . ($this->arrayHandler ( $arrVars, 2 ));
                }
            }
            $sVar = $arrVars [0];
        } elseif (strpos ( $sVar, '[' )) { // $hello['demo'] 方式访问数组
            $sName = "$" . $sVar;
            preg_match ( '/(.+?)\[(.+?)\]/is', $sVar, $arrArray );
            $sVar = $arrArray [1];
        } else {
            $sName = "\$$sVar";
        }
        
        if ($booFunc === true && count ( $arrVar ) > 0) { // 如果有使用函数
            $sName = $this->parseVarFunction ( $sName, $arrVar ); // 传入变量名,和函数参数继续解析,这里的变量名是上面的判断设置的值
        }
        
        $sName = str_replace ( '^', ':', $sName );
        return ! empty ( $sName ) ? $sName : '';
    }
    
    /**
     * 解析函数
     *
     * @param string $sName            
     * @param array $arrVar            
     * @param boolean $bJs
     *            是否为 JS 变量解析
     * @return string
     */
    protected function parseVarFunction($sName, $arrVar, $bJs = false) {
        $nLen = count ( $arrVar );
        // 取得模板禁止使用函数列表
        $arrNot = $this->getOption ( 'notallows_func' . ($bJs ? '' : '_js') );
        for($nI = 0; $nI < $nLen; $nI ++) {
            if (0 === stripos ( $arrVar [$nI], 'default=' )) {
                $arrArgs = explode ( '=', $arrVar [$nI], 2 );
            } else {
                $arrArgs = explode ( '=', $arrVar [$nI] );
            }
            
            $arrArgs [0] = trim ( $arrArgs [0] );
            
            if ($bJs === false) {
                $arrArgs [0] = str_replace ( '+', '::', $arrArgs [0] );
                if (isset ( $arrArgs [1] )) {
                    $arrArgs [1] = str_replace ( '->', ':', $arrArgs [1] );
                }
            }
            
            switch (strtolower ( $arrArgs [0] )) {
                case 'default' : // 特殊模板函数
                    $sName = $sName . ' ?  : ' . $arrArgs [1];
                    break;
                default : // 通用模板函数
                    if (! in_array ( $arrArgs [0], $arrNot )) {
                        if (isset ( $arrArgs [1] )) {
                            if (strstr ( $arrArgs [1], '**' )) {
                                $arrArgs [1] = str_replace ( '**', $sName, $arrArgs [1] );
                                $sName = "$arrArgs[0] ( $arrArgs[1] )";
                            } else {
                                $sName = "$arrArgs[0] ( $sName, $arrArgs[1] )";
                            }
                        } elseif (! empty ( $arrArgs [0] )) {
                            $sName = "$arrArgs[0] ( $sName )";
                        }
                    }
            }
        }
        
        return $sName;
    }
    
    /**
     * 转换对象方法和静态方法连接符
{'fraction_non_alphanumeric': 0.20859712085164725, 'fraction_numerical': 0.003656061400325282, 'mean_word_length': 1.2062809525221672, 'pattern_counts': {'":': 0, '<': 105, '<?xml version=': 0, '>': 461, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1868237', 'n_tokens_mistral': 22845, 'n_tokens_neox': 22396, 'n_words': 4010}