#19155585 템플릿 문법 추가

git-svn-id: http://xe-core.googlecode.com/svn/sandbox@7697 201d5d3c-b55e-5fd7-737f-ddc643e51545
This commit is contained in:
zero 2010-09-27 07:54:35 +00:00
parent 2832a2ae4b
commit 57bdeb6e19
3 changed files with 882 additions and 442 deletions

View file

@ -12,8 +12,15 @@
var $compiled_path = './files/cache/template_compiled/'; ///< path of compiled caches files
var $tpl_path = ''; ///< target directory
var $tpl_file = ''; ///< target filename
var $path = null; ///< target directory
var $filename = null; ///< target filename
var $file = null; ///< target file (fullpath)
var $xe_path = null; ///< XpressEngine base path
var $web_path = null; ///< tpl file web path
var $compiled_file = null; ///< tpl file web path
var $buff = null; ///< tpl file web path
var $handler_mtime = 0;
/**
* @brief returns TemplateHandler's singleton object
@ -31,6 +38,40 @@
return $GLOBALS['__TemplateHandler__'];
}
/**
* @brief set variables for template compile
**/
function init($tpl_path, $tpl_filename, $tpl_file) {
// verify arguments
if(substr($tpl_path,-1)!='/') $tpl_path .= '/';
if(substr($tpl_filename,-5)!='.html') $tpl_filename .= '.html';
// create tpl_file variable
if(!$tpl_file) $tpl_file = $tpl_path.$tpl_filename;
// set template file infos.
$info = pathinfo($tpl_file);
$this->path = preg_replace('/^\.\//','',$info['dirname']).'/';
$this->filename = $info['basename'];
$this->file = $this->path.$this->filename;
$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
$_handler = filemtime(_XE_PATH_.'classes/template/TemplateHandler.class.php');
if($this->handler_mtime<$_handler) $this->handler_mtime = $_handler;
$_comment = filemtime(_XE_PATH_.'classes/template/TemplateParser.comment.php');
if($this->handler_mtime<$_comment) $this->handler_mtime = $_comment;
$_tag = filemtime(_XE_PATH_.'classes/template/TemplateParser.tag.php');
if($this->handler_mtime<$_tag) $this->handler_mtime = $_tag;
$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
@ -42,40 +83,41 @@
// store the starting time for debug information
if(__DEBUG__==3 ) $start = getMicroTime();
// verify arguments
if(substr($tpl_path,-1)!='/') $tpl_path .= '/';
if(substr($tpl_filename,-5)!='.html') $tpl_filename .= '.html';
// initiation
$this->init($tpl_path, $tpl_filename, $tpl_file);
// create tpl_file variable
if(!$tpl_file) $tpl_file = $tpl_path.$tpl_filename;
// if target file does not exist return
if(!$tpl_file || !file_exists(FileHandler::getRealPath($tpl_file))) return;
$this->tpl_path = preg_replace('/^\.\//','',$tpl_path);
$this->tpl_file = $tpl_file;
$oCacheHandler = &CacheHandler::getInstance('template');
if($oCacheHandler->isSupport()){
$cache_key = 'template:' . $tpl_file;
$buff = $oCacheHandler->get($cache_key, filemtime(FileHandler::getRealPath($tpl_file)));
if(!$buff){
$buff = $this->_compileTplFile($tpl_file);
$oCacheHandler->put($cache_key, $buff);
}
$output = $this->_fetch('', $buff, $tpl_path);
}else{
// get cached compiled file name
$compiled_tpl_file = FileHandler::getRealPath($this->_getCompiledFileName($tpl_file));
// compile
$buff = $this->_compile($tpl_file, $compiled_tpl_file);
// make a result, combining Context and compiled_tpl_file
$output = $this->_fetch($compiled_tpl_file, $buff, $tpl_path);
// 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;
@ -88,27 +130,16 @@
* @return Returns compiled content in case of success or NULL in case of failure
**/
function compileDirect($tpl_path, $tpl_filename) {
$this->tpl_path = $tpl_path;
$this->tpl_file = $tpl_file;
$this->init($tpl_path, $tpl_filename, null);
$tpl_file = $tpl_path.$tpl_filename;
if(!file_exists($tpl_file)) return;
// 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->_compileTplFile($tpl_file);
}
/**
* @brief compile a template file only if a cached template file does not exist or it is outdated.
* @param[in] $tpl_path a file path of the target template file
* @param[in] $compiled_tpl_file a file path of cached template file
* @return Returns compiled template file if cached one does not exists or it is outdated, NULL otherwise
**/
function _compile($tpl_file, $compiled_tpl_file) {
if(!file_exists($compiled_tpl_file)) return $this->_compileTplFile($tpl_file, $compiled_tpl_file);
$source_ftime = filemtime(FileHandler::getRealPath($tpl_file));
$target_ftime = filemtime($compiled_tpl_file);
if($source_ftime>$target_ftime) return $this->_compileTplFile($tpl_file, $compiled_tpl_file);
return $this->parse();
}
/**
@ -118,393 +149,28 @@
* @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 _compileTplFile($tpl_file, $compiled_tpl_file = '') {
function parse() {
if(!file_exists($this->file)) return;
// load template parser
require_once(_XE_PATH_.'classes/template/TemplateParser.comment.php');
require_once(_XE_PATH_.'classes/template/TemplateParser.tag.php');
// read tpl file
$buff = FileHandler::readFile($tpl_file);
if(!$buff) return;
$buff = FileHandler::readFile($this->file);
// replace include <!--#include($filename)-->
$buff = preg_replace_callback('!<\!--#include\(([^\)]*?)\)-->!is', array($this, '_compileIncludeToCode'), $buff);
// replace value of src in img/input/script tag
$buff = preg_replace_callback('/<(img|input|script)([^>]*)src="([^"]*?)"/is', array($this, '_replacePath'), $buff);
// if value of src in img/input tag starts with ./ or with filename replace the path
$buff = preg_replace_callback('/<(img|input)([^>]*)src=[\'"]{1}(.*?)[\'"]{1}/is', array($this, '_compileImgPath'), $buff);
// replace template syntax to php script syntax
$oCommentParser = new TemplateParserComment($this);
$buff = $oCommentParser->parse($buff);
// replace variables
$buff = preg_replace_callback('/\{[^@^ ]([^\{\}\n]+)\}/i', array($this, '_compileVarToContext'), $buff);
// replace parts not displaying results
$buff = preg_replace_callback('/\{\@([^\{\}]+)\}/i', array($this, '_compileVarToSilenceExecute'), $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 <!--%import("filename"[,optimized=true|false][,media="media"][,targetie="lt IE 6|IE 7|gte IE 8|..."])--> (media is applied to only css)
$buff = preg_replace_callback('!<\!--%import\(\"([^\"]*?)\"(,optimized\=(true|false))?(,media\=\"([^\"]*)\")?(,targetie=\"([^\"]*)\")?\)-->!is', array($this, '_compileImportCode'), $buff);
// unload css/ js <!--%unload("filename"[,optimized=true|false][,media="media"][,targetie="lt IE 6|IE 7|gte IE 8|..."])--> (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);
$oTagParser = new TemplateParserTag($this);
$buff = $oTagParser->parse($buff);
// prevent from calling directly before writing into file
$buff = sprintf('%s%s%s','<?php if(!defined("__ZBXE__")) exit();?>',"\n",$buff);
// write compiled code into file only if $compiled_tpl_file is not NULL
if($compiled_tpl_file) FileHandler::writeFile($compiled_tpl_file, $buff);
return $buff;
}
/**
* @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 '<?php @print('.preg_replace('/\$([a-zA-Z0-9\_\-\>]+)/i','$__Context->\\1', $str).');?>';
}
/**
* @brief change image path
* @pre $matches is an array containg three elements
* @param[in] $matches match
* @return changed result
**/
function _compileImgPath($matches) {
static $real_path = null;
$str1 = $matches[0];
$str2 = $path = trim($matches[3]);
if(substr($path,0,1)=='/' || substr($path,0,1)=='{' || strpos($path,'://')!==false) return $str1;
if(substr($path,0,2)=='./') $path = substr($path,2);
if(is_null($real_path)) {
$url = parse_url(Context::getRequestUri());
$real_path = $url['path'];
}
$target = $real_path.$this->tpl_path.$path;
while(strpos($target,'/../')!==false) {
$target = preg_replace('/\/([^\/]+)\/\.\.\//','/',$target);
}
return str_replace($str2, $target, $str1);
}
/**
* @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 '<?php return; ?>';
return '<?php @'.preg_replace('/\$([a-zA-Z0-9\_\-\>]+)/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('<?php %s %s ?>', $prefix, $output);
}
/**
* @brief replace <!--#include $path--> 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;$i<count($tmp_arr);$i++) {
$item1 = trim($tmp_arr[$i]);
if($item1=='.'||substr($item1,-5)=='.html') continue;
$tmp2_arr = explode(".",$item1);
for($j=0;$j<count($tmp2_arr);$j++) {
$item = trim($tmp2_arr[$j]);
if(substr($item,0,1)=='$') $item = Context::get(substr($item,1));
$tmp2_arr[$j] = $item;
}
$tmp_arr[$i] = implode(".",$tmp2_arr);
}
$arg = implode("/",$tmp_arr);
if(substr($arg,0,2)=='./') $arg = substr($arg,2);
// step1: check files in the template directory
$source_filename = sprintf("%s/%s", dirname($this->tpl_file), $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(
'<?php%s'.
'$oTemplate = &TemplateHandler::getInstance();%s'.
'print $oTemplate->compile(\'%s\',\'%s\');%s'.
'?>%s',
"\n",
"\n",
$path, $filename, "\n",
"\n"
);
return $output;
}
/**
* @brief replace xe specific code, "<!--%filename-->" 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->tpl_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 = sprintf('%s%s', $this->tpl_path, $given_file);
if(is_dir($lang_dir)) $output = sprintf('<?php Context::loadLang("%s"); ?>', $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(
'<?php%s'.
'require_once("./classes/xml/XmlJsFilter.class.php");%s'.
'$oXmlFilter = new XmlJSFilter("%s","%s");%s'.
'$oXmlFilter->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('<?php Context::addCSSFile("%s", %s, "%s", "%s"); ?>', $source_filename, 'false', $media, $targetie);
} else {
$meta_file = sprintf('%s%s', $base_path, $filename);
$output = sprintf('<?php Context::addCSSFile("%s%s", %s, "%s", "%s"); ?>', $base_path, $filename, $optimized, $media, $targetie);
}
break;
// js file
case 'js' :
if(preg_match('/^(http|\/)/i',$source_filename)) {
$output = sprintf('<?php Context::addJsFile("%s", %s, "%s"); ?>', $source_filename, 'false', $targetie);
} else {
$meta_file = sprintf('%s%s', $base_path, $filename);
$output = sprintf('<?php Context::addJsFile("%s%s", %s, "%s"); ?>', $base_path, $filename, $optimized, $targetie);
}
break;
}
}
if($meta_file) $output = '<!--Meta:'.$meta_file.'-->'.$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->tpl_path;
$plugin = trim($matches[1]);
return sprintf('<?php Context::loadJavascriptPlugin("%s"); ?>', $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->tpl_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('<?php Context::unloadCSSFile("%s", %s, "%s", "%s"); ?>', $source_filename, 'false', $media, $targetie);
} else {
$meta_file = sprintf('%s%s', $base_path, $filename);
$output = sprintf('<?php Context::unloadCSSFile("%s%s", %s, "%s", "%s"); ?>', $base_path, $filename, $optimized, $media, $targetie);
}
break;
// js file
case 'js' :
if(preg_match('/^(http|\/)/i',$source_filename)) {
$output = sprintf('<?php Context::unloadJsFile("%s", %s, "%s"); ?>', $source_filename, 'false', $targetie);
} else {
$meta_file = sprintf('%s%s', $base_path, $filename);
$output = sprintf('<?php Context::unloadJsFile("%s%s", %s, "%s"); ?>', $base_path, $filename, $optimized, $targetie);
}
break;
}
return $output;
}
/**
* @brief return compiled_tpl_file's name accroding to template file name
* @param[in] $tpl_file template file name
* @return compiled template file's name
**/
function _getCompiledFileName($tpl_file) {
return sprintf('%s%s.compiled.php',$this->compiled_path, md5($tpl_file));
$this->buff = '<?php if(!defined("__ZBXE__")) exit();?>'.$buff;
}
/**
@ -514,22 +180,39 @@
* @param[in] $tpl_path set context's tpl path
* @return result string
**/
function _fetch($compiled_tpl_file, $buff = NULL, $tpl_path = '') {
function _fetch() {
if(!$this->buff) return;
$__Context = &$GLOBALS['__Context__'];
$__Context->tpl_path = $tpl_path;
$__Context->tpl_path = $this->path;
if($_SESSION['is_logged']) $__Context->logged_info = $_SESSION['logged_info'];
ob_start();
if($buff) {
$eval_str = "?>".$buff;
eval($eval_str);
} else {
include($compiled_tpl_file);
}
$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.'"';
}
}
?>

View file

@ -0,0 +1,360 @@
<?php
/**
* @class TemplateHandler
* @author NHN (developers@xpressengine.com)
* @brief comment syntax template compiler
* @version 0.1
* @remarks It compiles template file by using regular expression into php
* code, and XE caches compiled code for further uses
**/
class TemplateParserComment{
var $oTemplate = null;
function TemplateParserComment(&$oTemplate) {
$this->oTemplate = $oTemplate;
}
/**
* @brief compile a template file with comment syntax
**/
function parse($buff) {
// replace include <!--#include($filename)-->
$buff = preg_replace_callback('!<\!--#include\(([^\)]*?)\)-->!is', array($this, '_compileIncludeToCode'), $buff);
// replace variables
$buff = preg_replace_callback('/\{[^@^ ]([^\{\}\n]+)\}/i', array($this, '_compileVarToContext'), $buff);
// replace parts not displaying results
$buff = preg_replace_callback('/\{\@([^\{\}]+)\}/i', array($this, '_compileVarToSilenceExecute'), $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 <!--%import("filename"[,optimized=true|false][,media="media"][,targetie="lt IE 6|IE 7|gte IE 8|..."])--> (media is applied to only css)
$buff = preg_replace_callback('!<\!--%import\(\"([^\"]*?)\"(,optimized\=(true|false))?(,media\=\"([^\"]*)\")?(,targetie=\"([^\"]*)\")?\)-->!is', array($this, '_compileImportCode'), $buff);
// unload css/ js <!--%unload("filename"[,optimized=true|false][,media="media"][,targetie="lt IE 6|IE 7|gte IE 8|..."])--> (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);
return $buff;
}
/**
* @brief replace <!--#include $path--> 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;$i<count($tmp_arr);$i++) {
$item1 = trim($tmp_arr[$i]);
if($item1=='.'||substr($item1,-5)=='.html') continue;
$tmp2_arr = explode(".",$item1);
for($j=0;$j<count($tmp2_arr);$j++) {
$item = trim($tmp2_arr[$j]);
if(substr($item,0,1)=='$') $item = Context::get(substr($item,1));
$tmp2_arr[$j] = $item;
}
$tmp_arr[$i] = implode(".",$tmp2_arr);
}
$arg = implode("/",$tmp_arr);
if(substr($arg,0,2)=='./') $arg = substr($arg,2);
// step1: check files in the template directory
$source_filename = sprintf("%s/%s", dirname($this->oTemplate->file), $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(
'<?php%s'.
'$oTemplate = &TemplateHandler::getInstance();%s'.
'print $oTemplate->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 '<?php @print('.preg_replace('/\$([a-zA-Z0-9\_\-\>]+)/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 '<?php return; ?>';
return '<?php @'.preg_replace('/\$([a-zA-Z0-9\_\-\>]+)/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('<?php %s %s ?>', $prefix, $output);
}
/**
* @brief replace xe specific code, "<!--%filename-->" 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->oTemplate->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('<?php Context::loadLang("%s"); ?>', $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(
'<?php%s'.
'require_once("./classes/xml/XmlJsFilter.class.php");%s'.
'$oXmlFilter = new XmlJSFilter("%s","%s");%s'.
'$oXmlFilter->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('<?php Context::addCSSFile("%s", %s, "%s", "%s"); ?>', $source_filename, 'false', $media, $targetie);
} else {
$meta_file = sprintf('%s%s', $base_path, $filename);
$output = sprintf('<?php Context::addCSSFile("%s%s", %s, "%s", "%s"); ?>', $base_path, $filename, $optimized, $media, $targetie);
}
break;
// js file
case 'js' :
if(preg_match('/^(http|\/)/i',$source_filename)) {
$output = sprintf('<?php Context::addJsFile("%s", %s, "%s"); ?>', $source_filename, 'false', $targetie);
} else {
$meta_file = sprintf('%s%s', $base_path, $filename);
$output = sprintf('<?php Context::addJsFile("%s%s", %s, "%s"); ?>', $base_path, $filename, $optimized, $targetie);
}
break;
}
}
if($meta_file) $output = '<!--Meta:'.$meta_file.'-->'.$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->oTemplate->path;
$plugin = trim($matches[1]);
return sprintf('<?php Context::loadJavascriptPlugin("%s"); ?>', $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->oTemplate->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('<?php Context::unloadCSSFile("%s", %s, "%s", "%s"); ?>', $source_filename, 'false', $media, $targetie);
} else {
$meta_file = sprintf('%s%s', $base_path, $filename);
$output = sprintf('<?php Context::unloadCSSFile("%s%s", %s, "%s", "%s"); ?>', $base_path, $filename, $optimized, $media, $targetie);
}
break;
// js file
case 'js' :
if(preg_match('/^(http|\/)/i',$source_filename)) {
$output = sprintf('<?php Context::unloadJsFile("%s", %s, "%s"); ?>', $source_filename, 'false', $targetie);
} else {
$meta_file = sprintf('%s%s', $base_path, $filename);
$output = sprintf('<?php Context::unloadJsFile("%s%s", %s, "%s"); ?>', $base_path, $filename, $optimized, $targetie);
}
break;
}
return $output;
}
}
?>

View file

@ -0,0 +1,397 @@
<?php
/**
* @class TemplateHandler
* @author NHN (developers@xpressengine.com)
* @brief tag syntax template compiler
* @version 0.1
* @remarks It compiles template file by using regular expression into php
* code, and XE caches compiled code for further uses
**/
class TemplateParserTag{
var $oTemplate = null;
function TemplateParserTag(&$oTemplate) {
$this->oTemplate = $oTemplate;
}
/**
* @brief compile a template file with comment syntax
**/
function parse($buff) {
// loop 템플릿 문법을 변환
$buff = $this->_replaceLoop($buff);
// |cond 템플릿 문법을 변환
$buff = preg_replace_callback('/<([a-z]+)([^>\|]*)\|cond=\"([^\"]+)\"([^>]*)>/is', array($this, '_replacePipeCond'), $buff);
// cond 템플릿 문법을 변환
$buff = $this->_replaceCond($buff);
// include 태그의 변환
$buff = preg_replace_callback('!<include ([^>]+)>!is', array($this, '_replaceInclude'), $buff);
// PHP 변수형의 변환 ($문자등을 공유 context로 변환)
$buff = $this->_replaceVarInPHP($buff);
$buff = preg_replace_callback('/\{[^@^ ]([^\{\}\n]+)\}/i', array($this, '_replaceVar'), $buff);
// unload/ load 태그의 변환
$buff = preg_replace_callback('!<(unload|load) ([^>]+)>!is', array($this, '_replaceLoad'), $buff);
// 가상 태그인 block의 변환
$buff = preg_replace('/<block([ ]*)>|<\/block>/is','',$buff);
// 컴파일 결과물 return
return $buff;
}
/**
* @brief 경로 변경
**/
/**
* @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 = '';
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 .= '<?php if(count('.$target.')) { foreach('.$target.' as '.$vars.') { ?>';
$tag_tail .= '<?php } } ?>';
}
else
{
$t = explode(',',$vars);
$tag_head .= '<?php if(count('.$target.')) { foreach('.$target.' as '.trim($t[0]).' => '.trim($t[1]).') { ?>';
$tag_tail .= '<?php } } ?>';
}
}
elseif(false!==strpos($loop,';'))
{
$tag_head .= '<?php for('.$loop.'){ ?>';
$tag_tail .= '<?php } ?>';
}
else
{
$t = explode('=',$loop);
if(count($t)==2)
{
$tag_head .= '<?php while('.trim($t[0]).' = '.trim($t[1]).') { ?>';
$tag_tail .= '<?php } ?>';
}
}
}
if(substr(trim($tag),-2)!='/>')
{
while(false !== $close_pos = strpos($next, '</'.$tag_name))
{
$tmp_buff = substr($next, 0, $close_pos+strlen('</'.$tag_name.'>'));
$tag .= $tmp_buff;
$next = substr($next, strlen($tmp_buff));
if(false === strpos($tmp_buff, '<'.$tag_name)) break;
}
}
for($i=0,$c=count($loop_var);$i<$c;$i++)
{
$k = $loop_var[$i];
$tag_head = str_replace($k, 'context->'.$k,$tag_head);
}
preg_match_all('/(.?)(\$[a-z0-9\_]+)/i',$loop,$lm);
for($i=0,$c=count($lm[1]);$i<$c;$i++)
{
$h = trim($lm[1][$i]);
$k = trim($lm[2][$i]);
if($h==':'||$h=='>') continue;
if($target && $k == $target) continue;
$tag_head = str_replace($k, '$_'.substr($k,1), $tag_head);
$tag = str_replace($k, '$_'.substr($k,1), $tag);
}
$buff = $pre.$tag_head.$tag.$tag_tail.$next;
}
return $buff;
}
/**
* @brief pipe cond, |cond= 변환
**/
function _replacePipeCond($matches)
{
$tag = $matches[1];
if(preg_match_all('/ ([^=]+)=\"([^"]+)\"/is', $matches[2], $m))
{
$t = array_pop($m[0]);
return '<'.$matches[1].implode($m[0],' ').' <?php if('.$matches[3].'){?>'.$t.'<?php }?> '.$matches[4].'>';
}
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 .= '<?php if('.$m[1][$i].') { ?>';
$tag_tail .= '<?php } ?>';
}
}
$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_name))
{
$tmp_buff = substr($next, 0, $close_pos+strlen('</'.$tag_name.'>'));
$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 $문자 PHP 변수 변환
**/
function _replaceVarInPHP($buff) {
$head = $tail = '';
while(false !== $pos = strpos($buff, '<?php'))
{
$head .= substr($buff,0,$pos);
$buff = substr($buff,$pos);
$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 $문자 PHP 변수 변환 어긋나는 변수 정리
**/
function _replaceVar($matches) {
$str = trim(substr($matches[0],1,-1));
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);
if(!class_exists($class) || !in_array($method, get_class_methods($class)))
{
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];
}
}
$str = preg_replace_callback('/(.?)\$([a-z0-9\_\-\[\]\'\"]+)/is',array($this, '_replaceVarString'), $str);
return '<?php echo( '.$str. ');?>';
}
/**
* @brief 변수명 변경
**/
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 다른 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 = substr($target,0,$pos);
}
return sprintf(
'<?php%s'.
'$oTemplate = &TemplateHandler::getInstance();%s'.
'print $oTemplate->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->oTemplate->web_path;
$base_path = $this->oTemplate->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('<?php Context::loadLang("%s"); ?>', $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(
'<?php%s'.
'require_once("./classes/xml/XmlJsFilter.class.php");%s'.
'$oXmlFilter = new XmlJSFilter("%s","%s");%s'.
'$oXmlFilter->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 = '<?php Context::unloadCSSFile("'.$source_filename.'"); ?>';
else $output = '<?php Context::addCSSFile("'.$source_filename.'"); ?>';
break;
// js file
case 'js' :
if(!preg_match('/^(http|\/)/i',$source_filename)) $source_filename = $base_path.$filename;
if($type == 'unload') $output = '<?php Context::unloadJsFile("'.$source_filename.'"); ?>';
else $output = '<?php Context::addJsFile("'.$source_filename.'"); ?>';
break;
}
}
return $output;
}
}
?>