/application/libraries/ftl/context.php

https://github.com/chamnan/ionize · PHP · 290 lines · 124 code · 42 blank · 124 comment · 17 complexity · eb6cf31e86e10cf052245e793e359a61 MD5 · raw file

  1. <?php
  2. /*
  3. * Created on 2009 Jan 02
  4. * by Martin Wernstahl <m4rw3r@gmail.com>
  5. */
  6. /**
  7. * A context which renders the tags for the parser.
  8. *
  9. * @package FTL_Parser
  10. * @author Martin Wernstahl <m4rw3r@gmail.com>
  11. * @copyright Copyright (c) 2008, Martin Wernstahl <m4rw3r@gmail.com>
  12. */
  13. class FTL_Context
  14. {
  15. /**
  16. * Contains tag definitions.
  17. *
  18. * @var array
  19. */
  20. public $definitions = array();
  21. /**
  22. * Reverse tag finder tree.
  23. *
  24. * @var array
  25. */
  26. public $tree = array();
  27. /**
  28. * The global data.
  29. *
  30. * @var FTL_VarStack
  31. */
  32. public $globals;
  33. /**
  34. * A stack of the tag bindings.
  35. *
  36. * @var FTL_Binding
  37. */
  38. protected $tag_binding_stack = array();
  39. /**
  40. * A stack of tag names.
  41. *
  42. * @var array(string)
  43. */
  44. protected $tag_name_stack = array();
  45. // --------------------------------------------------------------------
  46. /**
  47. * Init.
  48. *
  49. * Creates a var_stack.
  50. */
  51. function __construct()
  52. {
  53. $this->globals = new FTL_VarStack();
  54. }
  55. // --------------------------------------------------------------------
  56. /**
  57. * Defines a tag.
  58. *
  59. * @param string The name of the tags (nestings are separated with ":")
  60. * @param callable The function/method to be called
  61. * @return void
  62. */
  63. public function define_tag($name, $callable)
  64. {
  65. $this->definitions[$name] = $callable;
  66. if(strpos($name, ':') === false)
  67. {
  68. // No nesting, no need for a reverse mapping tree
  69. return;
  70. }
  71. // Create reverse mapping tree, for tags with more than one segment
  72. $l = explode(':', $name);
  73. $c = count($l);
  74. // # key is a tag name
  75. // Fast and nice (currently only up to 5 segment tags):
  76. if($c == 2)
  77. {
  78. $this->tree[$l[1]][$l[0]]['#'] = $name;
  79. }
  80. elseif($c == 3)
  81. {
  82. $this->tree[$l[2]][$l[1]][$l[0]]['#'] = $name;
  83. }
  84. elseif($c == 4)
  85. {
  86. $this->tree[$l[3]][$l[2]][$l[1]][$l[0]]['#'] = $name;
  87. }
  88. elseif($c == 5)
  89. {
  90. $this->tree[$l[4]][$l[3]][$l[2]][$l[1]][$l[0]]['#'] = $name;
  91. }
  92. // TODO: To support more segments, add more rows like this
  93. }
  94. // --------------------------------------------------------------------
  95. /**
  96. * Renders the tags with the name and args supplied.
  97. *
  98. * @param string The tag name
  99. * @param array The args
  100. * @param array The nested block
  101. */
  102. public function render_tag($name, $args = array(), $block = null)
  103. {
  104. // do we have a compund tag?
  105. if(($pos = strpos($name, ':')) != 0)
  106. {
  107. // split them and parse them separately, as if they are nested
  108. $name1 = substr($name, 0, $pos);
  109. $name2 = substr($name, $pos + 1);
  110. return $this->render_tag($name1, array(), array(
  111. 'name' => $name2,
  112. 'args' => $args,
  113. 'content' => $block
  114. ));
  115. }
  116. else
  117. {
  118. $qname = $this->qualified_tag_name($name);
  119. if(is_string($qname) && array_key_exists($qname, $this->definitions))
  120. {
  121. // render
  122. return $this->stack($name, $args, $block, $this->definitions[$qname]);
  123. }
  124. else
  125. {
  126. return $this->tag_missing($name, $args, $block);
  127. }
  128. }
  129. }
  130. // --------------------------------------------------------------------
  131. /**
  132. * Traverses the stack and handles the bindings and var_stack(s).
  133. *
  134. * @param string The tag name
  135. * @param array The tag args
  136. * @param array The nested block
  137. * @param callable The function/method to call
  138. * @return string
  139. */
  140. protected function stack($name, $args, $block, $call)
  141. {
  142. // get previous locals, to let the data "stack"
  143. $previous = end($this->tag_binding_stack);
  144. $previous_locals = $previous == null ? $this->globals : $previous->locals;
  145. // create the stack and binding
  146. $locals = new FTL_VarStack($previous_locals);
  147. $binding = new FTL_Binding($this, $locals, $name, $args, $block);
  148. $this->tag_binding_stack[] = $binding;
  149. $this->tag_name_stack[] = $name;
  150. // Check if we have a function or a method
  151. if(is_callable($call))
  152. {
  153. $result = call_user_func($call, $binding);
  154. }
  155. else
  156. {
  157. show_error('Error in definition of tag "'.$name.'", the associated <b>static function</b> "'.$call.'" cannot be called.');
  158. }
  159. // jump out
  160. array_pop($this->tag_binding_stack);
  161. array_pop($this->tag_name_stack);
  162. return $result;
  163. }
  164. // --------------------------------------------------------------------
  165. /**
  166. * Makes a qualified guess of the tag definition requested depending on the current nesting.
  167. *
  168. * @param string The name of the tag
  169. * @return string
  170. */
  171. function qualified_tag_name($name)
  172. {
  173. // Get the path array
  174. $path_chunks = array_merge($this->tag_name_stack, array($name));
  175. // For literal matches
  176. $path = implode(':', $path_chunks);
  177. // Check if we have a tag or a variable
  178. if( ! isset($this->definitions[$path]) && ! isset($this->globals->hash[$name]))
  179. {
  180. // Reverse the chunks, we're starting with the most precise name
  181. $path_chunks = array_reverse($path_chunks);
  182. // Set default tag, which is the last one
  183. $last = current($path_chunks);
  184. // Do we have a tag which ends at the correct name?
  185. if( ! isset($this->tree[$last]))
  186. {
  187. // Nope
  188. return $last;
  189. }
  190. // Start
  191. $c =& $this->tree;
  192. //echo('<pre>');
  193. //var_dump($this->tree);
  194. //echo('</pre>');
  195. // Go through the whole name
  196. while( ! empty($path_chunks))
  197. {
  198. // Get next
  199. $e = array_shift($path_chunks);
  200. // Do we have a matching segment?
  201. if(isset($c[$e]))
  202. {
  203. // Yes
  204. // Do we have a tag here?
  205. if(isset($c[$e]['#']))
  206. {
  207. // Yes
  208. $last = $c[$e]['#'];
  209. }
  210. // Move deeper, to make sure that we don't have any more specific tags
  211. $c =& $c[$e];
  212. }
  213. }
  214. return $last;
  215. }
  216. else
  217. {
  218. return $path;
  219. }
  220. }
  221. // --------------------------------------------------------------------
  222. /**
  223. * Raises a tag missing error.
  224. *
  225. * @param string The tag name
  226. * @param array The tag parameters
  227. * @param array The nested block
  228. * @return string Or abort if needed (default)
  229. */
  230. public function tag_missing($name, $args = array(), $block = null)
  231. {
  232. throw new Exception('Tag missing: "'.$name.'", scope: "'.$this->current_nesting().'".');
  233. }
  234. // --------------------------------------------------------------------
  235. /**
  236. * Returns the state of the current render stack.
  237. *
  238. * Useful from inside a tag definition. Normally just use XT_Binding::nesting().
  239. *
  240. * @return string
  241. */
  242. function current_nesting()
  243. {
  244. return implode(':', $this->tag_name_stack);
  245. }
  246. }
  247. /* End of file context.php */
  248. /* Location: ./application/libraries/xt_parser/context.php */