| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- <?php
- /*
- Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
- This file is part of the Fat-Free Framework (http://fatfreeframework.com).
- This is free software: you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation, either version 3 of the License, or later.
- Fat-Free Framework is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
- You should have received a copy of the GNU General Public License along
- with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>.
- */
- //! 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 '<?php '.$out.'?>';
- }
- /**
- * 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
- '<?php '.(isset($attrib['if'])?
- ('if ('.$this->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
- '<?php for ('.
- $this->token($attrib['from']).';'.
- $this->token($attrib['to']).';'.
- $this->token($attrib['step']).'): ?>'.
- $this->build($node).
- '<?php endfor; ?>';
- }
- /**
- * Template -repeat- tag handler
- * @return string
- * @param $node array
- **/
- protected function _repeat(array $node) {
- $attrib=$node['@attrib'];
- unset($node['@attrib']);
- return
- '<?php '.
- (isset($attrib['counter'])?
- (($ctr=$this->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).
- '<?php endforeach; ?>';
- }
- /**
- * Template -check- tag handler
- * @return string
- * @param $node array
- **/
- protected function _check(array $node) {
- $attrib=$node['@attrib'];
- unset($node['@attrib']);
- // Grab <true> and <false> 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 <true> and <false> blocks
- list($node[$true[0]],$node[$false[0]])=[$false[1],$true[1]];
- return
- '<?php if ('.$this->token($attrib['if']).'): ?>'.
- $this->build($node).
- '<?php endif; ?>';
- }
- /**
- * 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 '<?php else: ?>'.$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
- '<?php switch ('.$this->token($attrib['expr']).'): ?>'.
- $this->build($node).
- '<?php endswitch; ?>';
- }
- /**
- * Template -case- tag handler
- * @return string
- * @param $node array
- **/
- protected function _case(array $node) {
- $attrib=$node['@attrib'];
- unset($node['@attrib']);
- return
- '<?php case '.(preg_match('/\{\{(.+?)\}\}/',$attrib['value'])?
- $this->token($attrib['value']):
- Base::instance()->stringify($attrib['value'])).': ?>'.
- $this->build($node).
- '<?php '.(isset($attrib['break'])?
- 'if ('.$this->token($attrib['break']).') ':'').
- 'break; ?>';
- }
- /**
- * Template -default- tag handler
- * @return string
- * @param $node array
- **/
- protected function _default(array $node) {
- return
- '<?php default: ?>'.
- $this->build($node).
- '<?php break; ?>';
- }
- /**
- * 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();
- }
- }
|