template.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. <?php
  2. /*
  3. Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
  4. This file is part of the Fat-Free Framework (http://fatfreeframework.com).
  5. This is free software: you can redistribute it and/or modify it under the
  6. terms of the GNU General Public License as published by the Free Software
  7. Foundation, either version 3 of the License, or later.
  8. Fat-Free Framework is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. General Public License for more details.
  12. You should have received a copy of the GNU General Public License along
  13. with Fat-Free Framework. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. //! XML-style template engine
  16. class Template extends Preview {
  17. //@{ Error messages
  18. const
  19. E_Method='Call to undefined method %s()';
  20. //@}
  21. protected
  22. //! Template tags
  23. $tags,
  24. //! Custom tag handlers
  25. $custom=[];
  26. /**
  27. * Template -set- tag handler
  28. * @return string
  29. * @param $node array
  30. **/
  31. protected function _set(array $node) {
  32. $out='';
  33. foreach ($node['@attrib'] as $key=>$val)
  34. $out.='$'.$key.'='.
  35. (preg_match('/\{\{(.+?)\}\}/',$val)?
  36. $this->token($val):
  37. Base::instance()->stringify($val)).'; ';
  38. return '<?php '.$out.'?>';
  39. }
  40. /**
  41. * Template -include- tag handler
  42. * @return string
  43. * @param $node array
  44. **/
  45. protected function _include(array $node) {
  46. $attrib=$node['@attrib'];
  47. $hive=isset($attrib['with']) &&
  48. ($attrib['with']=$this->token($attrib['with'])) &&
  49. preg_match_all('/(\w+)\h*=\h*(.+?)(?=,|$)/',
  50. $attrib['with'],$pairs,PREG_SET_ORDER)?
  51. ('['.implode(',',
  52. array_map(function($pair) {
  53. return '\''.$pair[1].'\'=>'.
  54. (preg_match('/^\'.*\'$/',$pair[2]) ||
  55. preg_match('/\$/',$pair[2])?
  56. $pair[2]:Base::instance()->stringify(
  57. Base::instance()->cast($pair[2])));
  58. },$pairs)).']+get_defined_vars()'):
  59. 'get_defined_vars()';
  60. $ttl=isset($attrib['ttl'])?(int)$attrib['ttl']:0;
  61. return
  62. '<?php '.(isset($attrib['if'])?
  63. ('if ('.$this->token($attrib['if']).') '):'').
  64. ('echo $this->render('.
  65. (preg_match('/^\{\{(.+?)\}\}$/',$attrib['href'])?
  66. $this->token($attrib['href']):
  67. Base::instance()->stringify($attrib['href'])).','.
  68. 'NULL,'.$hive.','.$ttl.'); ?>');
  69. }
  70. /**
  71. * Template -exclude- tag handler
  72. * @return string
  73. **/
  74. protected function _exclude() {
  75. return '';
  76. }
  77. /**
  78. * Template -ignore- tag handler
  79. * @return string
  80. * @param $node array
  81. **/
  82. protected function _ignore(array $node) {
  83. return $node[0];
  84. }
  85. /**
  86. * Template -loop- tag handler
  87. * @return string
  88. * @param $node array
  89. **/
  90. protected function _loop(array $node) {
  91. $attrib=$node['@attrib'];
  92. unset($node['@attrib']);
  93. return
  94. '<?php for ('.
  95. $this->token($attrib['from']).';'.
  96. $this->token($attrib['to']).';'.
  97. $this->token($attrib['step']).'): ?>'.
  98. $this->build($node).
  99. '<?php endfor; ?>';
  100. }
  101. /**
  102. * Template -repeat- tag handler
  103. * @return string
  104. * @param $node array
  105. **/
  106. protected function _repeat(array $node) {
  107. $attrib=$node['@attrib'];
  108. unset($node['@attrib']);
  109. return
  110. '<?php '.
  111. (isset($attrib['counter'])?
  112. (($ctr=$this->token($attrib['counter'])).'=0; '):'').
  113. 'foreach (('.
  114. $this->token($attrib['group']).'?:[]) as '.
  115. (isset($attrib['key'])?
  116. ($this->token($attrib['key']).'=>'):'').
  117. $this->token($attrib['value']).'):'.
  118. (isset($ctr)?(' '.$ctr.'++;'):'').' ?>'.
  119. $this->build($node).
  120. '<?php endforeach; ?>';
  121. }
  122. /**
  123. * Template -check- tag handler
  124. * @return string
  125. * @param $node array
  126. **/
  127. protected function _check(array $node) {
  128. $attrib=$node['@attrib'];
  129. unset($node['@attrib']);
  130. // Grab <true> and <false> blocks
  131. foreach ($node as $pos=>$block)
  132. if (isset($block['true']))
  133. $true=[$pos,$block];
  134. elseif (isset($block['false']))
  135. $false=[$pos,$block];
  136. if (isset($true,$false) && $true[0]>$false[0])
  137. // Reverse <true> and <false> blocks
  138. list($node[$true[0]],$node[$false[0]])=[$false[1],$true[1]];
  139. return
  140. '<?php if ('.$this->token($attrib['if']).'): ?>'.
  141. $this->build($node).
  142. '<?php endif; ?>';
  143. }
  144. /**
  145. * Template -true- tag handler
  146. * @return string
  147. * @param $node array
  148. **/
  149. protected function _true(array $node) {
  150. return $this->build($node);
  151. }
  152. /**
  153. * Template -false- tag handler
  154. * @return string
  155. * @param $node array
  156. **/
  157. protected function _false(array $node) {
  158. return '<?php else: ?>'.$this->build($node);
  159. }
  160. /**
  161. * Template -switch- tag handler
  162. * @return string
  163. * @param $node array
  164. **/
  165. protected function _switch(array $node) {
  166. $attrib=$node['@attrib'];
  167. unset($node['@attrib']);
  168. foreach ($node as $pos=>$block)
  169. if (is_string($block) && !preg_replace('/\s+/','',$block))
  170. unset($node[$pos]);
  171. return
  172. '<?php switch ('.$this->token($attrib['expr']).'): ?>'.
  173. $this->build($node).
  174. '<?php endswitch; ?>';
  175. }
  176. /**
  177. * Template -case- tag handler
  178. * @return string
  179. * @param $node array
  180. **/
  181. protected function _case(array $node) {
  182. $attrib=$node['@attrib'];
  183. unset($node['@attrib']);
  184. return
  185. '<?php case '.(preg_match('/\{\{(.+?)\}\}/',$attrib['value'])?
  186. $this->token($attrib['value']):
  187. Base::instance()->stringify($attrib['value'])).': ?>'.
  188. $this->build($node).
  189. '<?php '.(isset($attrib['break'])?
  190. 'if ('.$this->token($attrib['break']).') ':'').
  191. 'break; ?>';
  192. }
  193. /**
  194. * Template -default- tag handler
  195. * @return string
  196. * @param $node array
  197. **/
  198. protected function _default(array $node) {
  199. return
  200. '<?php default: ?>'.
  201. $this->build($node).
  202. '<?php break; ?>';
  203. }
  204. /**
  205. * Assemble markup
  206. * @return string
  207. * @param $node array|string
  208. **/
  209. function build($node) {
  210. if (is_string($node))
  211. return parent::build($node);
  212. $out='';
  213. foreach ($node as $key=>$val)
  214. $out.=is_int($key)?$this->build($val):$this->{'_'.$key}($val);
  215. return $out;
  216. }
  217. /**
  218. * Extend template with custom tag
  219. * @return NULL
  220. * @param $tag string
  221. * @param $func callback
  222. **/
  223. function extend($tag,$func) {
  224. $this->tags.='|'.$tag;
  225. $this->custom['_'.$tag]=$func;
  226. }
  227. /**
  228. * Call custom tag handler
  229. * @return string|FALSE
  230. * @param $func string
  231. * @param $args array
  232. **/
  233. function __call($func,array $args) {
  234. if ($func[0]=='_')
  235. return call_user_func_array($this->custom[$func],$args);
  236. if (method_exists($this,$func))
  237. return call_user_func_array([$this,$func],$args);
  238. user_error(sprintf(self::E_Method,$func),E_USER_ERROR);
  239. }
  240. /**
  241. * Parse string for template directives and tokens
  242. * @return array
  243. * @param $text string
  244. **/
  245. function parse($text) {
  246. $text=parent::parse($text);
  247. // Build tree structure
  248. for ($ptr=0,$w=5,$len=strlen($text),$tree=[],$tmp='';$ptr<$len;)
  249. if (preg_match('/^(.{0,'.$w.'}?)<(\/?)(?:F3:)?'.
  250. '('.$this->tags.')\b((?:\s+[\w.:@!-]+'.
  251. '(?:\h*=\h*(?:"(?:.*?)"|\'(?:.*?)\'))?|'.
  252. '\h*\{\{.+?\}\})*)\h*(\/?)>/is',
  253. substr($text,$ptr),$match)) {
  254. if (strlen($tmp) || $match[1])
  255. $tree[]=$tmp.$match[1];
  256. // Element node
  257. if ($match[2]) {
  258. // Find matching start tag
  259. $stack=[];
  260. for($i=count($tree)-1;$i>=0;$i--) {
  261. $item=$tree[$i];
  262. if (is_array($item) &&
  263. array_key_exists($match[3],$item) &&
  264. !isset($item[$match[3]][0])) {
  265. // Start tag found
  266. $tree[$i][$match[3]]+=array_reverse($stack);
  267. $tree=array_slice($tree,0,$i+1);
  268. break;
  269. }
  270. else $stack[]=$item;
  271. }
  272. }
  273. else {
  274. // Start tag
  275. $node=&$tree[][$match[3]];
  276. $node=[];
  277. if ($match[4]) {
  278. // Process attributes
  279. preg_match_all(
  280. '/(?:(\{\{.+?\}\})|([^\s\/"\'=]+))'.
  281. '\h*(?:=\h*(?:"(.*?)"|\'(.*?)\'))?/s',
  282. $match[4],$attr,PREG_SET_ORDER);
  283. foreach ($attr as $kv)
  284. if (!empty($kv[1]) && !isset($kv[3]) && !isset($kv[4]))
  285. $node['@attrib'][]=$kv[1];
  286. else
  287. $node['@attrib'][$kv[1]?:$kv[2]]=
  288. (isset($kv[3]) && $kv[3]!==''?
  289. $kv[3]:
  290. (isset($kv[4]) && $kv[4]!==''?
  291. $kv[4]:NULL));
  292. }
  293. }
  294. $tmp='';
  295. $ptr+=strlen($match[0]);
  296. $w=5;
  297. }
  298. else {
  299. // Text node
  300. $tmp.=substr($text,$ptr,$w);
  301. $ptr+=$w;
  302. if ($w<50)
  303. $w++;
  304. }
  305. if (strlen($tmp))
  306. // Append trailing text
  307. $tree[]=$tmp;
  308. // Break references
  309. unset($node);
  310. return $tree;
  311. }
  312. /**
  313. * Class constructor
  314. * return object
  315. **/
  316. function __construct() {
  317. $ref=new ReflectionClass(get_called_class());
  318. $this->tags='';
  319. foreach ($ref->getmethods() as $method)
  320. if (preg_match('/^_(?=[[:alpha:]])/',$method->name))
  321. $this->tags.=(strlen($this->tags)?'|':'').
  322. substr($method->name,1);
  323. parent::__construct();
  324. }
  325. }