. */ //! XML-style template engine class Template extends Preview { //@{ Error messages const E_Method='Call to undefined method %s()'; //@} protected //! Template tags $tags, //! Custom tag handlers $custom=[]; /** * Template -set- tag handler * @return string * @param $node array **/ protected function _set(array $node) { $out=''; foreach ($node['@attrib'] as $key=>$val) $out.='$'.$key.'='. (preg_match('/\{\{(.+?)\}\}/',$val)? $this->token($val): Base::instance()->stringify($val)).'; '; return ''; } /** * Template -include- tag handler * @return string * @param $node array **/ protected function _include(array $node) { $attrib=$node['@attrib']; $hive=isset($attrib['with']) && ($attrib['with']=$this->token($attrib['with'])) && preg_match_all('/(\w+)\h*=\h*(.+?)(?=,|$)/', $attrib['with'],$pairs,PREG_SET_ORDER)? ('['.implode(',', array_map(function($pair) { return '\''.$pair[1].'\'=>'. (preg_match('/^\'.*\'$/',$pair[2]) || preg_match('/\$/',$pair[2])? $pair[2]:Base::instance()->stringify( Base::instance()->cast($pair[2]))); },$pairs)).']+get_defined_vars()'): 'get_defined_vars()'; $ttl=isset($attrib['ttl'])?(int)$attrib['ttl']:0; return 'token($attrib['if']).') '):''). ('echo $this->render('. (preg_match('/^\{\{(.+?)\}\}$/',$attrib['href'])? $this->token($attrib['href']): Base::instance()->stringify($attrib['href'])).','. 'NULL,'.$hive.','.$ttl.'); ?>'); } /** * Template -exclude- tag handler * @return string **/ protected function _exclude() { return ''; } /** * Template -ignore- tag handler * @return string * @param $node array **/ protected function _ignore(array $node) { return $node[0]; } /** * Template -loop- tag handler * @return string * @param $node array **/ protected function _loop(array $node) { $attrib=$node['@attrib']; unset($node['@attrib']); return 'token($attrib['from']).';'. $this->token($attrib['to']).';'. $this->token($attrib['step']).'): ?>'. $this->build($node). ''; } /** * Template -repeat- tag handler * @return string * @param $node array **/ protected function _repeat(array $node) { $attrib=$node['@attrib']; unset($node['@attrib']); return 'token($attrib['counter'])).'=0; '):''). 'foreach (('. $this->token($attrib['group']).'?:[]) as '. (isset($attrib['key'])? ($this->token($attrib['key']).'=>'):''). $this->token($attrib['value']).'):'. (isset($ctr)?(' '.$ctr.'++;'):'').' ?>'. $this->build($node). ''; } /** * Template -check- tag handler * @return string * @param $node array **/ protected function _check(array $node) { $attrib=$node['@attrib']; unset($node['@attrib']); // Grab and blocks foreach ($node as $pos=>$block) if (isset($block['true'])) $true=[$pos,$block]; elseif (isset($block['false'])) $false=[$pos,$block]; if (isset($true,$false) && $true[0]>$false[0]) // Reverse and blocks list($node[$true[0]],$node[$false[0]])=[$false[1],$true[1]]; return 'token($attrib['if']).'): ?>'. $this->build($node). ''; } /** * Template -true- tag handler * @return string * @param $node array **/ protected function _true(array $node) { return $this->build($node); } /** * Template -false- tag handler * @return string * @param $node array **/ protected function _false(array $node) { return ''.$this->build($node); } /** * Template -switch- tag handler * @return string * @param $node array **/ protected function _switch(array $node) { $attrib=$node['@attrib']; unset($node['@attrib']); foreach ($node as $pos=>$block) if (is_string($block) && !preg_replace('/\s+/','',$block)) unset($node[$pos]); return 'token($attrib['expr']).'): ?>'. $this->build($node). ''; } /** * Template -case- tag handler * @return string * @param $node array **/ protected function _case(array $node) { $attrib=$node['@attrib']; unset($node['@attrib']); return 'token($attrib['value']): Base::instance()->stringify($attrib['value'])).': ?>'. $this->build($node). 'token($attrib['break']).') ':''). 'break; ?>'; } /** * Template -default- tag handler * @return string * @param $node array **/ protected function _default(array $node) { return ''. $this->build($node). ''; } /** * Assemble markup * @return string * @param $node array|string **/ function build($node) { if (is_string($node)) return parent::build($node); $out=''; foreach ($node as $key=>$val) $out.=is_int($key)?$this->build($val):$this->{'_'.$key}($val); return $out; } /** * Extend template with custom tag * @return NULL * @param $tag string * @param $func callback **/ function extend($tag,$func) { $this->tags.='|'.$tag; $this->custom['_'.$tag]=$func; } /** * Call custom tag handler * @return string|FALSE * @param $func string * @param $args array **/ function __call($func,array $args) { if ($func[0]=='_') return call_user_func_array($this->custom[$func],$args); if (method_exists($this,$func)) return call_user_func_array([$this,$func],$args); user_error(sprintf(self::E_Method,$func),E_USER_ERROR); } /** * Parse string for template directives and tokens * @return array * @param $text string **/ function parse($text) { $text=parent::parse($text); // Build tree structure for ($ptr=0,$w=5,$len=strlen($text),$tree=[],$tmp='';$ptr<$len;) if (preg_match('/^(.{0,'.$w.'}?)<(\/?)(?:F3:)?'. '('.$this->tags.')\b((?:\s+[\w.:@!-]+'. '(?:\h*=\h*(?:"(?:.*?)"|\'(?:.*?)\'))?|'. '\h*\{\{.+?\}\})*)\h*(\/?)>/is', substr($text,$ptr),$match)) { if (strlen($tmp) || $match[1]) $tree[]=$tmp.$match[1]; // Element node if ($match[2]) { // Find matching start tag $stack=[]; for($i=count($tree)-1;$i>=0;$i--) { $item=$tree[$i]; if (is_array($item) && array_key_exists($match[3],$item) && !isset($item[$match[3]][0])) { // Start tag found $tree[$i][$match[3]]+=array_reverse($stack); $tree=array_slice($tree,0,$i+1); break; } else $stack[]=$item; } } else { // Start tag $node=&$tree[][$match[3]]; $node=[]; if ($match[4]) { // Process attributes preg_match_all( '/(?:(\{\{.+?\}\})|([^\s\/"\'=]+))'. '\h*(?:=\h*(?:"(.*?)"|\'(.*?)\'))?/s', $match[4],$attr,PREG_SET_ORDER); foreach ($attr as $kv) if (!empty($kv[1]) && !isset($kv[3]) && !isset($kv[4])) $node['@attrib'][]=$kv[1]; else $node['@attrib'][$kv[1]?:$kv[2]]= (isset($kv[3]) && $kv[3]!==''? $kv[3]: (isset($kv[4]) && $kv[4]!==''? $kv[4]:NULL)); } } $tmp=''; $ptr+=strlen($match[0]); $w=5; } else { // Text node $tmp.=substr($text,$ptr,$w); $ptr+=$w; if ($w<50) $w++; } if (strlen($tmp)) // Append trailing text $tree[]=$tmp; // Break references unset($node); return $tree; } /** * Class constructor * return object **/ function __construct() { $ref=new ReflectionClass(get_called_class()); $this->tags=''; foreach ($ref->getmethods() as $method) if (preg_match('/^_(?=[[:alpha:]])/',$method->name)) $this->tags.=(strlen($this->tags)?'|':''). substr($method->name,1); parent::__construct(); } }