template.php 8.0 KB

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