true, 'tab-size'=>4, 'wrap'=>0, 'wrap-asp'=>false, 'wrap-attributes'=>false, 'wrap-jste'=>false, 'wrap-php'=>false, 'wrap-script-literals'=>false, 'wrap-sections'=>false, 'char-encoding'=>'utf8', 'newline'=>'LF', 'tidy-mark'=>true, 'merge-divs'=>false, 'merge-spans'=>false, 'logical-emphasis'=>false, 'literal-attributes'=>true ]; private static $regex = [ 'match'=>'/\{([^#\/?_][^}\n]*?)\}/i', 'parentmatch'=>'/\{\.\.\/([^#\/?_][^}\n]*?)\}/i', 'rawmatch'=>'/\{@([^#\/?_][^}\n]*?)\}/i', 'rawparentmatch'=>'/\{@\.\.\/([^#\/?_][^}\n]*?)\}/i', 'each'=>'/\{#each ([^}]*)\}([\S\s]*)\{\/each \1\}/i', 'exist'=>'/\{#exist ([^}]*)\}([\S\s]*)\{\/exist \1\}/i', 'existelse'=>'/\{#exist ([^}]*)\}([\S\s]*)\{#else \1\}([\S\s]*)\{\/exist \1\}/i', 'ignore'=>'/\{#ignore\}([\S\s]*)\{\/ignore\}/i', 'ignored'=>'/\{#ignored (\d+?)\}/i', 'gettext'=>"/{_([^,}]+)(?:, ?([^},]+))*\}/i", 'gettext_string'=>'/^([\'"])(.+)\1$/i', 'echo'=>'/\{=([^}]+)\}/i', 'eval'=>'/\{\?([\W\w\S\s]+)\?\}/i', 'include'=>'/{#include ([^}]+)}/i', 'define'=>'/\{#define ([^}]*)\}([\S\s]*)\{\/define \1\}/i', 'widget'=>'/{#widget ([^}]+)}/i' ]; protected static $parsers; private $template; private $name; private $path; public function __construct(string $name, string $template, bool $is_file = false){ if(is_null(static::$parsers)){ static::$parsers = [ 'ignore'=>function(&$output, &$ignored){ $output = preg_replace_callback(static::$regex['ignore'], function($matches) use(&$ignored){ $ignored[] = $matches[1]; return '{#ignored '.(count($ignored) - 1).'}'; }, $output); }, 'include'=>function(&$output, &$ignored = null){ while(preg_match(static::$regex['include'], $output)){ $output = preg_replace_callback(static::$regex['include'], function($matches) use(&$ignored){ $path = static::$basedir.'/'.$matches[1]; if(file_exists($path)){ $output = file_get_contents($path); if(!is_null($ignored)){ static::$parsers['ignore']($output, $ignored); } return $output; } return ''; }, $output); } }, 'define'=>function(&$output, &$widgets){ while(preg_match(static::$regex['define'], $output)){ $output = preg_replace_callback(static::$regex['define'], function($matches) use(&$widgets){ $name = $matches[1]; if(isset($widgets[$name])){ throw new \Exception("Widget {$name} is already defined"); } $widgets[$name] = $matches[2]; return ''; }, $output); } }, 'widget'=>function(&$output, $widgets){ while(preg_match(static::$regex['widget'], $output)){ $output = preg_replace_callback(static::$regex['widget'], function($matches) use(&$widgets){ $name = $matches[1]; if(!isset($widgets[$name])){ throw new \Exception("Widget {$name} is not defined"); } return $widgets[$name]; }, $output); } }, 'each'=>function(&$output){ $output = preg_replace_callback(static::$regex['each'], function($matches){ $output = ""; $output .= static::compile($matches[2]); $output .= ""; return $output; }, $output); }, 'existelse'=>function(&$output){ $output = preg_replace_callback(static::$regex['existelse'], function($matches){ $output = ""; $output .= static::compile($matches[2]); $output .= ""; $output .= static::compile($matches[3]); $output .= ""; return $output; }, $output); }, 'exist'=>function(&$output){ $output = preg_replace_callback(static::$regex['exist'], function($matches){ $output = ""; $output .= static::compile($matches[2]); $output .= ""; return $output; }, $output); }, 'gettext'=>function(&$output){ $output = preg_replace_callback(static::$regex['gettext'], function($matches){ if(count($matches) > 2){ $output = ""; }, $output); }, 'echo'=>function(&$output){ $output = preg_replace_callback(static::$regex['echo'], function($matches){ return ""; }, $output); }, 'eval'=>function(&$output){ $output = preg_replace_callback(static::$regex['eval'], function($matches){ return ""; }, $output); }, 'rawmatch'=>function(&$output){ $output = preg_replace_callback(static::$regex['rawmatch'], function($matches){ return ""; }, $output); }, 'rawparentmatch'=>function(&$output){ $output = preg_replace_callback(static::$regex['rawparentmatch'], function($matches){ return ""; }, $output); }, 'match'=>function(&$output){ $output = preg_replace_callback(static::$regex['match'], function($matches){ return ""; }, $output); }, 'parentmatch'=>function(&$output){ $output = preg_replace_callback(static::$regex['parentmatch'], function($matches){ return ""; }, $output); }, 'ignored'=>function(&$output, $ignored){ $output = preg_replace_callback(static::$regex['ignored'], function($matches) use($ignored){ return htmlentities($ignored[(int)$matches[1]] ?? ''); }, $output); } ]; } if(isset(static::$templates[$name])){ throw new Exception("Template {$name} already exists"); } if($is_file){ $path = realpath($template); if(!file_exists($path)){ throw new Exception("Template file {$template} doesn't exist"); } $template = file_get_contents($path); } static::$parsers['include']($template); $widgets = []; static::$parsers['define']($template, $widgets); static::$parsers['widget']($template, $widgets); $this->template = $template; $this->name = $name; $this->path = static::$cachedir."/{$this->name}.".md5($this->template).'.php'; static::$templates[$name] = $this; } public function run(array $data) : string{ $data = EArray::from($data); if($this->fire('before', $data) === false){ throw new Exception("Render on template {$this->name} cancelled. Before."); } if(!file_exists($this->path)){ file_put_contents($this->path, static::compile($this->template)); } try{ $output = static::execute($this->path, $data); }catch(Exception $e){ file_put_contents($this->path, static::compile($this->template)); $output = static::execute($this->path, $data); } if(class_exists('tidy')){ if(is_null(static::$tidy)){ static::$tidy = new \tidy(); } $tidy = static::$tidy; $tidy->parseString($output, static::$tidyConfig); if(!$tidy->cleanRepair()){ throw new \Exception($tidy->errorBuffer); } $output = "{$tidy}"; } if($this->fire('after', $output) === false){ throw new Exception("Render on template {$this->name} cancelled. After"); } return (string)$output; } public static function from(string $name, array $data = []) : string{ $template = static::$templates[$name] ?? null; if(is_null($template)){ throw new Exception("Template {$name} does not exist"); } return $template->run($data); } public static function compile(string $template) : string{ $ignored = []; $output = $template; // Handle {#ignore code} static::$parsers['ignore']($output, $ignored); // Handle {#include path/to/file} static::$parsers['include']($output, $ignored); // Handle {#each name}{/each} static::$parsers['each']($output); // Handle {#exist name}{#else}{/exist} static::$parsers['existelse']($output); // Handle {#exist name}{/exist} static::$parsers['exist']($output); // Handle {gettext} static::$parsers['gettext']($output); // Handle {=expression} static::$parsers['echo']($output); // Handle {? expression ?} static::$parsers['eval']($output); // Handle {@../name} static::$parsers['rawparentmatch']($output); // Handle {@name} static::$parsers['rawmatch']($output); // Handle {../name} static::$parsers['parentmatch']($output); // Handle {name} static::$parsers['match']($output); // Handle {#ignored i} static::$parsers['ignored']($output, $ignored); return $output; } public static function parse(string $template, $data) : string{ $id = md5($template); if(!isset(static::$templates[$id])){ new Template($id, $template); } return static::$templates[$id]->run($data); } public static function execute(string $path, $data) : string{ ob_start(); include($path); $output = ob_get_contents(); ob_end_clean(); return $output; } } ?>