You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
353 lines
8.5 KiB
353 lines
8.5 KiB
<?php
|
|
|
|
/*
|
|
|
|
Copyright (c) 2009-2019 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();
|
|
}
|
|
|
|
}
|
|
|