path = preg_replace('/^\.\//','',$info['dirname']).'/'; $this->path = $tpl_path; $this->filename = $tpl_filename; $this->file = $tpl_file; $this->xe_path = preg_replace('/([^\.^\/]+)\.php$/i','',$_SERVER['SCRIPT_NAME']); $this->web_path = $this->xe_path.str_replace(_XE_PATH_,'',$this->path); // get compiled file name $this->compiled_file = sprintf('%s%s.compiled.php',$this->compiled_path, md5($this->file)); // compare various file's modified time for check changed $this->handler_mtime = filemtime(_XE_PATH_.'classes/template/TemplateHandler.class.php'); $this->buff = null; } /** * @brief compiles specified tpl file and execution result in Context into resultant content * @param[in] $tpl_path path of the directory containing target template file * @param[in] $tpl_filename target template file's name * @param[in] $tpl_file if specified use it as template file's full path * @return Returns compiled result in case of success, NULL otherwise */ function compile($tpl_path, $tpl_filename, $tpl_file = '') { // store the starting time for debug information if(__DEBUG__==3 ) $start = getMicroTime(); // initiation $this->init($tpl_path, $tpl_filename, $tpl_file); // if target file does not exist exit if(!$this->file || !file_exists($this->file)) { Context::close(); printf('"%s" template file is not exists.', $this->file); exit(); } $source_template_mtime = filemtime($this->file); $latest_mtime = $source_template_mtime>$this->handler_mtime?$source_template_mtime:$this->handler_mtime; // cache controll $oCacheHandler = &CacheHandler::getInstance('template'); // get cached buff if($oCacheHandler->isSupport()){ $cache_key = 'template:'.$this->file; $this->buff = $oCacheHandler->get($cache_key, $latest_mtime); } else { if(file_exists($this->compiled_file) && filemtime($this->compiled_file)>$latest_mtime) { $this->buff = FileHandler::readFile($this->compiled_file); } } if(!$this->buff) { $this->parse(); if($oCacheHandler->isSupport()) $oCacheHandler->put($cache_key, $this->buff); else FileHandler::writeFile($this->compiled_file, $this->buff); } $output = $this->_fetch(); // store the ending time for debug information if(__DEBUG__==3 ) $GLOBALS['__template_elapsed__'] += getMicroTime() - $start; return $output; } /** * @brief compile specified file and immediately return * @param[in] $tpl_path path of the directory containing target template file * @param[in] $tpl_filename target template file's name * @return Returns compiled content in case of success or NULL in case of failure **/ function compileDirect($tpl_path, $tpl_filename) { $this->init($tpl_path, $tpl_filename, null); // if target file does not exist exit if(!$this->file || !file_exists($this->file)) { Context::close(); printf('"%s" template file is not exists.', $this->file); exit(); } return $this->parse(); } /** * @brief compile a template file specified in $tpl_file and * @pre files specified by $tpl_file exists. * @param[in] $tpl_file path of tpl file * @param[in] $compiled_tpl_file if specified, write compiled result into the file * @return compiled result in case of success or NULL in case of error **/ function parse() { if(!file_exists($this->file)) return; // read tpl file $buff = FileHandler::readFile($this->file); // replace value of src in img/input/script tag $buff = preg_replace_callback('/<(img|input|script)([^>]*)src="([^"]*?)"/is', array($this, '_replacePath'), $buff); // loop 템플릿 문법을 변환 $buff = $this->_replaceLoop($buff); // |cond 템플릿 문법을 변환 $buff = preg_replace_callback("/<\/?(\w+)((\s+\w+(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)\/?>/i", array($this, '_replacePipeCond'), $buff); // cond 템플릿 문법을 변환 $buff = $this->_replaceCond($buff); // include 태그의 변환 $buff = preg_replace_callback('!]+)>!is', array($this, '_replaceInclude'), $buff); // unload/ load 태그의 변환 $buff = preg_replace_callback('!<(unload|load) ([^>]+)>!is', array($this, '_replaceLoad'), $buff); // 가상 태그인 block의 변환 $buff = preg_replace('/|<\/block>/is','',$buff); // replace include $buff = preg_replace_callback('!<\!--#include\(([^\)]*?)\)-->!is', array($this, '_compileIncludeToCode'), $buff); // replace $buff = preg_replace_callback('!<\!--@(.*?)-->!is', array($this, '_compileFuncToCode'), $buff); // remove comments $buff = preg_replace('!(\n?)( *?)<\!--//(.*?)-->!is', '', $buff); // import xml filter/ css/ js/ files (media is applied to only css) $buff = preg_replace_callback('!<\!--%import\(\"([^\"]*?)\"(,optimized\=(true|false))?(,media\=\"([^\"]*)\")?(,targetie=\"([^\"]*)\")?\)-->!is', array($this, '_compileImportCode'), $buff); // unload css/ js (media is applied to only css) $buff = preg_replace_callback('!<\!--%unload\(\"([^\"]*?)\"(,optimized\=(true|false))?(,media\=\"([^\"]*)\")?(,targetie=\"([^\"]*)\")?\)-->!is', array($this, '_compileUnloadCode'), $buff); // javascript plugin import $buff = preg_replace_callback('!<\!--%load_js_plugin\(\"([^\"]*?)\"\)-->!is', array($this, '_compileLoadJavascriptPlugin'), $buff); // replace variables $buff = preg_replace_callback('/\{[^@^ ]([^\{\}\n]+)\}/i', array($this, '_compileVarToContext'), $buff); // PHP 변수형의 변환 ($문자등을 공유 context로 변환) $buff = $this->_replaceVarInPHP($buff); // replace parts not displaying results $buff = preg_replace_callback('/\{\@([^\{\}]+)\}/i', array($this, '_compileVarToSilenceExecute'), $buff); // prevent from calling directly before writing into file $this->buff = ''.$buff; } /** * @brief fetch using ob_* function * @param[in] $compiled_tpl_file path of compiled template file * @param[in] $buff if buff is not null, eval it instead of including compiled template file * @param[in] $tpl_path set context's tpl path * @return result string **/ function _fetch() { if(!$this->buff) return; $__Context = &$GLOBALS['__Context__']; $__Context->tpl_path = $this->path; if($_SESSION['is_logged']) $__Context->logged_info = $_SESSION['logged_info']; ob_start(); $eval_str = "?>".$this->buff; eval($eval_str); return ob_get_clean(); } /** * @brief change image path * @pre $matches is an array containg three elements * @param[in] $matches match * @return changed result **/ private function _replacePath($matches) { $path = trim($matches[3]); if(substr($path,0,1)=='/' || substr($path,0,1)=='{' || strpos($path,'://')!==false) return $matches[0]; if(substr($path,0,2)=='./') $path = substr($path,2); $target = $this->web_path.$path; while(strpos($target,'/../')!==false) { $target = preg_replace('/\/([^\/]+)\/\.\.\//','/',$target); } return '<'.$matches[1].$matches[2].'src="'.$target.'"'; } /** * @brief loop 문법의 변환 **/ function _replaceLoop($buff) { while(false !== $pos = strpos($buff, ' loop="')) { $pre = substr($buff,0,$pos); $next = substr($buff,$pos); $pre_pos = strrpos($pre, '<'); preg_match('/^ loop="([^"]+)"/i',$next,$m); $tag = substr($next,0,strlen($m[0])); $next = substr($next,strlen($m[0])); $next_pos = strpos($next, '<'); $tag = substr($pre, $pre_pos). $tag. substr($next, 0, $next_pos); $pre = substr($pre, 0, $pre_pos); $next = substr($next, $next_pos); $tag_name = trim(substr($tag,1,strpos($tag,' '))); $tag_head = $tag_tail = ''; if(!preg_match('/ loop="([^"]+)"/is',$tag)) { print "Invalid XpressEngine Template Syntax
"; print "File : ".$this->file."
"; print "Code : ".htmlspecialchars($tag); exit(); } preg_match_all('/ loop="([^"]+)"/is',$tag,$m); $tag = preg_replace('/ loop="([^"]+)"/is','', $tag); for($i=0,$c=count($m[0]);$i<$c;$i++) { $loop = $m[1][$i]; if(false!== $fpos = strpos($loop,'=>')) { $target = trim(substr($loop,0,$fpos)); $vars = trim(substr($loop,$fpos+2)); if(false===strpos($vars,',')) { $tag_head .= ''; $tag_tail .= ''; } else { $t = explode(',',$vars); $tag_head .= ' '.trim($t[1]).') { ?>'; $tag_tail .= ''; } } elseif(false!==strpos($loop,';')) { $tag_head .= ''; $tag_tail .= ''; } else { $t = explode('=',$loop); if(count($t)==2) { $tag_head .= ''; $tag_tail .= ''; } } } if(substr(trim($tag),-2)!='/>') { while(false !== $close_pos = strpos($next, '')); $tag .= $tmp_buff; $next = substr($next, strlen($tmp_buff)); if(false === strpos($tmp_buff, '<'.$tag_name)) break; } } $buff = $pre.$tag_head.$tag.$tag_tail.$next; } return $buff; } /** * @brief pipe cond, |cond= 의 변환 **/ function _replacePipeCond($matches) { while(strpos($matches[0],'|cond="')!==false) { if(preg_match('/ (\w+)=\"([^\"]+)\"\|cond=\"([^\"]+)\"/is', $matches[0], $m)) $matches[0] = str_replace($m[0], sprintf(' %s="%s"', $m[3], $m[1], $m[2]), $matches[0]); } return $matches[0]; } /** * @brief cond 문법의 변환 **/ function _replaceCond($buff) { while(false !== ($pos = strpos($buff, ' cond="'))) { $pre = substr($buff,0,$pos); $next = substr($buff,$pos); $pre_pos = strrpos($pre, '<'); $next_pos = strpos($next, '<'); $tag = substr($pre, $pre_pos). substr($next, 0, $next_pos); $pre = substr($pre, 0, $pre_pos); $next = substr($next, $next_pos); $tag_name = trim(substr($tag,1,strpos($tag,' '))); $tag_head = $tag_tail = ''; if(preg_match_all('/ cond=\"([^\"]+)"/is',$tag,$m)) { for($i=0,$c=count($m[0]);$i<$c;$i++) { $tag_head .= ''; $tag_tail .= ''; } } if(!preg_match('/ cond="([^"]+)"/is',$tag)) { print "Invalid XpressEngine Template Syntax
"; print "File : ".$this->file."
"; print "Code : ".htmlspecialchars($tag); exit(); } $tag = preg_replace('/ cond="([^"]+)"/is','', $tag); if(substr(trim($tag),-2)=='/>') { $buff = $pre.$tag_head.$tag.$tag_tail.$next; } else { while(false !== $close_pos = strpos($next, '')); $tag .= $tmp_buff; $next = substr($next, strlen($tmp_buff)); if(false === strpos($tmp_buff, '<'.$tag_name)) break; } $buff = $pre.$tag_head.$tag.$tag_tail.$next; } } return $buff; } /** * @brief 다른 template파일을 include하는 include tag의 변환 **/ function _replaceInclude($matches) { if(!preg_match('/target=\"([^\"]+)\"/is',$matches[0], $m)) throw new Exception('"target" attribute missing in "'.htmlspecialchars($matches[0]).'"'); $target = $m[1]; if(substr($target,0,1)=='/') { $target = substr($target,1); $pos = strrpos('/',$target); $filename = substr($target,$pos+1); $path = substr($target,0,$pos); } else { if(substr($target,0,2)=='./') $target = substr($target,2); $pos = strrpos('/',$target); $filename = substr($target,$pos); $path = $this->path.substr($target,0,$pos); } return sprintf( 'compile(\'%s\',\'%s\');%s'. '?>%s', "\n", "\n", $path, $filename, "\n", "\n" ); } /** * @brief load 태그의 변환 **/ function _replaceLoad($matches) { $output = $matches[0]; if(!preg_match_all('/ ([^=]+)=\"([^\"]+)\"/is',$matches[0], $m)) return $matches[0]; $type = $matches[1]; for($i=0,$c=count($m[1]);$i<$c;$i++) { if(!trim($m[1][$i])) continue; $attrs[trim($m[1][$i])] = trim($m[2][$i]); } if(!$attrs['target']) return $matches[0]; $web_path = $this->web_path; $base_path = $this->path; $target = $attrs['target']; if(substr($target,0,2)=='./') $target = substr($target,2); if(!substr($target,0,1)!='/') $target = $web_path.$target; // if target ends with lang, load language pack if(substr($target, -4)=='lang') { if(substr($target,0,2)=='./') $target = substr($target, 2); $lang_dir = $base_path.$target; if(is_dir($lang_dir)) $output = sprintf('', $lang_dir); // otherwise try to load xml, css, js file } else { if(substr($target,0,1)!='/') $source_filename = $base_path.$target; else $source_filename = $target; // get filename and path $tmp_arr = explode("/",$source_filename); $filename = array_pop($tmp_arr); $base_path = implode("/",$tmp_arr)."/"; // get the ext $tmp_arr = explode(".",$filename); $ext = strtolower(array_pop($tmp_arr)); // according to ext., import the file switch($ext) { // xml js filter case 'xml' : // create an instance of XmlJSFilter class, then create js and handle Context::addJsFile $output = sprintf( 'compile();%s'. '?>%s', "\n", "\n", $base_path, $filename, "\n", "\n", "\n" ); break; // css file case 'css' : if(!preg_match('/^(http|\/)/i',$source_filename)) $source_filename = $base_path.$filename; if($type == 'unload') $output = ''; else $output = ''; break; // js file case 'js' : if(!preg_match('/^(http|\/)/i',$source_filename)) $source_filename = $base_path.$filename; if($type == 'unload') $output = ''; else $output = ''; break; } } return $output; } /** * @brief $문자 의 PHP 변수 변환 **/ private function _replaceVarInPHP($buff) { $head = $tail = ''; while(false !== $pos = strpos($buff, ''); $body = substr($buff,0,$pos+2); $head .= preg_replace_callback('/(.?)\$([a-z0-9\_\-\[\]\'\"]+)/is',array($this, '_replaceVarString'), $body); $buff = substr($buff,$pos+2); } return $head.$buff; } /** * @brief php5의 class::$변수명의 경우 context를 사용하지 않아야 하기에 함수로 대체 **/ private function _replaceVarString($matches) { if($matches[1]==':') return $matches[0]; if(substr($matches[2],0,1)=='_') return $matches[0]; return $matches[1].'$__Context->'.$matches[2]; } /** * @brief replace with php code * @param[in] $matches match * @return replaced result **/ function _compileIncludeToCode($matches) { // if target string to include contains variables handle them $arg = str_replace(array('"','\''), '', $matches[1]); if(!$arg) return; $tmp_arr = explode("/", $arg); for($i=0;$ifile), $arg); // step2: check path from root if(!file_exists($source_filename)) $source_filename = './'.$arg; if(!file_exists($source_filename)) return; // split into path and filename $tmp_arr = explode('/', $source_filename); $filename = array_pop($tmp_arr); $path = implode('/', $tmp_arr).'/'; // try to include $output = sprintf( 'compile(\'%s\',\'%s\');%s'. '?>%s', "\n", "\n", $path, $filename, "\n", "\n" ); return $output; } /** * @brief replace $... variables in { } with Context::get(...) * @param[in] $matches match * @return replaced result in case of success or NULL in case of error **/ function _compileVarToContext($matches) { $str = trim(substr($matches[0],1,strlen($matches[0])-2)); if(!$str) return $matches[0]; if(!in_array(substr($str,0,1),array('(','$','\'','"'))) { if(preg_match('/^([^\( \.]+)(\(| \.)/i',$str,$m)) { $func = trim($m[1]); if(strpos($func,'::')===false) { if(!function_exists($func)) { return $matches[0]; } } else { list($class, $method) = explode('::',$func); // FIXME regardless of whether class/func name is case-sensitive, it is safe // to assume names are case sensitive. We don't have compare twice. if(!class_exists($class) || !in_array($method, get_class_methods($class))) { // In some environment, the name of classes and methods may be case-sensitive list($class, $method) = explode('::',strtolower($func)); if(!class_exists($class) || !in_array($method, get_class_methods($class))) { return $matches[0]; } } } } else { if(!defined($str)) return $matches[0]; } } return ']+)/i','$__Context->\\1', $str).');?>'; } /** * @brief replace @... function in { } into print func(..) * @param[in] $matches match * @return replaced result **/ function _compileVarToSilenceExecute($matches) { if(strtolower(trim(str_replace(array(';',' '),'', $matches[1])))=='return') return ''; return ']+)/i','$__Context->\\1', trim($matches[1])).';?>'; } /** * @brief replace code in with php code * @param[in] $matches match * @return changed result **/ function _compileFuncToCode($matches) { static $idx = 0; $code = trim($matches[1]); if(!$code) return; switch(strtolower($code)) { case 'else' : $output = '}else{'; break; case 'end' : case 'endif' : case 'endfor' : case 'endforeach' : case 'endswitch' : $output = '}'; break; case 'break' : $output = 'break;'; break; case 'default' : $output = 'default :'; break; case 'break@default' : $output = 'break; default :'; break; default : $suffix = '{'; if(substr($code, 0, 4) == 'else') { $code = '}'.$code; } elseif(substr($code, 0, 7) == 'foreach') { $tmp_str = substr($code, 8); $tmp_arr = explode(' ', $tmp_str); $var_name = $tmp_arr[0]; $prefix = '$Context->__idx['.$idx.']=0;'; if(substr($var_name, 0, 1) == '$') { $prefix .= sprintf('if(count($__Context->%s)) ', substr($var_name, 1)); } else { $prefix .= sprintf('if(count(%s)) ', $var_name); } $idx++; $suffix .= '$__idx['.$idx.']=($__idx['.$idx.']+1)%2; $cycle_idx = $__idx['.$idx.']+1;'; } elseif(substr($code, 0, 4) == 'case') { $suffix = ':'; } elseif(substr($code, 0, 10) == 'break@case') { $code = 'break; case'.substr($code, 10); $suffix = ':'; } $output = preg_replace('/\$([a-zA-Z0-9\_\-]+)/i', '$__Context->\\1', $code.$suffix); break; } return sprintf('', $prefix, $output); } /** * @brief replace xe specific code, "" with appropriate php code * @param[in] $matches match * @return Returns modified result or NULL in case of error **/ function _compileImportCode($matches) { // find xml file $base_path = $this->path; $given_file = trim($matches[1]); if(!$given_file) return; if(isset($matches[3])) $optimized = strtolower(trim($matches[3])); if(!$optimized) $optimized = 'true'; if(isset($matches[5])) $media = trim($matches[5]); if(!$media) $media = 'all'; if(isset($matches[7])) $targetie = trim($matches[7]); if(!$targetie) $targetie = ''; else $optimized = 'false'; // if given_file ends with lang, load language pack if(substr($given_file, -4)=='lang') { if(substr($given_file,0,2)=='./') $given_file = substr($given_file, 2); $lang_dir = $base_path.$given_file; if(is_dir($lang_dir)) $output = sprintf('', $lang_dir); // otherwise try to load xml, css, js file } else { if(substr($given_file,0,1)!='/') $source_filename = sprintf("%s%s",$base_path, $given_file); else $source_filename = $given_file; // get filename and path $tmp_arr = explode("/",$source_filename); $filename = array_pop($tmp_arr); $base_path = implode("/",$tmp_arr)."/"; // get the ext $tmp_arr = explode(".",$filename); $ext = strtolower(array_pop($tmp_arr)); // according to ext., import the file switch($ext) { // xml js filter case 'xml' : // create an instance of XmlJSFilter class, then create js and handle Context::addJsFile $output = sprintf( 'compile();%s'. '?>%s', "\n", "\n", $base_path, $filename, "\n", "\n", "\n" ); break; // css file case 'css' : if(preg_match('/^(http|\/)/i',$source_filename)) { $output = sprintf('', $source_filename, 'false', $media, $targetie); } else { $meta_file = sprintf('%s%s', $base_path, $filename); $output = sprintf('', $base_path, $filename, $optimized, $media, $targetie); } break; // js file case 'js' : if(preg_match('/^(http|\/)/i',$source_filename)) { $output = sprintf('', $source_filename, 'false', $targetie); } else { $meta_file = sprintf('%s%s', $base_path, $filename); $output = sprintf('', $base_path, $filename, $optimized, $targetie); } break; } } if($meta_file) $output = ''.$output; return $output; } /** * @brief import javascript plugin * @param[in] $matches match * @return result loading the plugin * @remarks javascript plugin works as optimized = false **/ function _compileLoadJavascriptPlugin($matches) { $base_path = $this->path; $plugin = trim($matches[1]); return sprintf('', $plugin); } /** * @brief remove loading part of css/ js file * @param[in] $matches match * @return removed result **/ function _compileUnloadCode($matches) { // find xml file $base_path = $this->path; $given_file = trim($matches[1]); if(!$given_file) return; if(isset($matches[3])) $optimized = strtolower(trim($matches[3])); if(!$optimized) $optimized = 'true'; if(isset($matches[5])) $media = trim($matches[5]); if(!$media) $media = 'all'; if(isset($matches[7])) $targetie = trim($matches[7]); if(!$targetie) $targetie = ''; else $optimized = 'false'; if(substr($given_file,0,1)!='/') $source_filename = sprintf("%s%s",$base_path, $given_file); else $source_filename = $given_file; // get path and file nam $tmp_arr = explode("/",$source_filename); $filename = array_pop($tmp_arr); $base_path = implode("/",$tmp_arr)."/"; // get an ext. $tmp_arr = explode(".",$filename); $ext = strtolower(array_pop($tmp_arr)); switch($ext) { // css file case 'css' : if(preg_match('/^(http|\/)/i',$source_filename)) { $output = sprintf('', $source_filename, 'false', $media, $targetie); } else { $meta_file = sprintf('%s%s', $base_path, $filename); $output = sprintf('', $base_path, $filename, $optimized, $media, $targetie); } break; // js file case 'js' : if(preg_match('/^(http|\/)/i',$source_filename)) { $output = sprintf('', $source_filename, 'false', $targetie); } else { $meta_file = sprintf('%s%s', $base_path, $filename); $output = sprintf('', $base_path, $filename, $optimized, $targetie); } break; } return $output; } } ?>