HandlebarsTemplate

Handlebars base template contain some utility method to get context and helpers.

Defined (2)

The class is defined in the following location(s).

/vendor/calderawp/metaplate-core/vendor/xamin/handlebars.php/src/Handlebars/Template.php  
  1. class Template 
  2. /** 
  3. * @var Handlebars 
  4. */ 
  5. protected $handlebars; 
  6.  
  7.  
  8. protected $tree = array(); 
  9.  
  10. protected $source = ''; 
  11.  
  12. /** 
  13. * @var array Run stack 
  14. */ 
  15. private $_stack = array(); 
  16.  
  17. /** 
  18. * Handlebars template constructor 
  19. * @param Handlebars $engine handlebar engine 
  20. * @param array $tree Parsed tree 
  21. * @param string $source Handlebars source 
  22. */ 
  23. public function __construct(Handlebars $engine, $tree, $source) 
  24. $this->handlebars = $engine; 
  25. $this->tree = $tree; 
  26. $this->source = $source; 
  27. array_push($this->_stack, array(0, $this->getTree(), false)); 
  28.  
  29. /** 
  30. * Get current tree 
  31. * @return array 
  32. */ 
  33. public function getTree() 
  34. return $this->tree; 
  35.  
  36. /** 
  37. * Get current source 
  38. * @return string 
  39. */ 
  40. public function getSource() 
  41. return $this->source; 
  42.  
  43. /** 
  44. * Get current engine associated with this object 
  45. * @return Handlebars 
  46. */ 
  47. public function getEngine() 
  48. return $this->handlebars; 
  49.  
  50. /** 
  51. * set stop token for render and discard method 
  52. * @param string $token token to set as stop token or false to remove 
  53. * @return void 
  54. */ 
  55.  
  56. public function setStopToken($token) 
  57. $topStack = array_pop($this->_stack); 
  58. $topStack[2] = $token; 
  59. array_push($this->_stack, $topStack); 
  60.  
  61. /** 
  62. * get current stop token 
  63. * @return string|bool 
  64. */ 
  65.  
  66. public function getStopToken() 
  67. $topStack = end($this->_stack); 
  68.  
  69. return $topStack[2]; 
  70.  
  71. /** 
  72. * Get the current token's tree 
  73. * @return array 
  74. */ 
  75. public function getCurrentTokenTree() 
  76. $topStack = end($this->_stack); 
  77.  
  78. return $topStack[1]; 
  79.  
  80. /** 
  81. * Render top tree 
  82. * @param mixed $context current context 
  83. * @throws \RuntimeException 
  84. * @return string 
  85. */ 
  86. public function render($context) 
  87. if (!$context instanceof Context) { 
  88. $context = new Context($context); 
  89. $topTree = end($this->_stack); // never pop a value from stack 
  90. list($index, $tree, $stop) = $topTree; 
  91.  
  92. $buffer = ''; 
  93. $rTrim = false; 
  94. while (array_key_exists($index, $tree)) { 
  95. $current = $tree[$index]; 
  96. $index++; 
  97. //if the section is exactly like waitFor 
  98. if (is_string($stop) 
  99. && $current[Tokenizer::TYPE] == Tokenizer::T_ESCAPED 
  100. && $current[Tokenizer::NAME] === $stop 
  101. ) { 
  102. break; 
  103. if (isset($current[Tokenizer::TRIM_LEFT]) && $current[Tokenizer::TRIM_LEFT]) { 
  104. $buffer = rtrim($buffer); 
  105.  
  106. $tmp = $this->_renderInternal($current, $context); 
  107.  
  108. if (isset($current[Tokenizer::TRIM_LEFT]) && $current[Tokenizer::TRIM_LEFT]) { 
  109. $tmp = rtrim($tmp); 
  110.  
  111. if ($rTrim || (isset($current[Tokenizer::TRIM_RIGHT]) && $current[Tokenizer::TRIM_RIGHT])) { 
  112. $tmp = ltrim($tmp); 
  113.  
  114. $buffer .= $tmp; 
  115. // Some time, there is more than one string token (first is empty),  
  116. //so we need to trim all of them in one shot 
  117.  
  118. $rTrim = (empty($tmp) && $rTrim) || 
  119. isset($current[Tokenizer::TRIM_RIGHT]) && $current[Tokenizer::TRIM_RIGHT]; 
  120. if ($stop) { 
  121. //Ok break here, the helper should be aware of this. 
  122. $newStack = array_pop($this->_stack); 
  123. $newStack[0] = $index; 
  124. $newStack[2] = false; //No stop token from now on 
  125. array_push($this->_stack, $newStack); 
  126.  
  127. return $buffer; 
  128.  
  129. /** 
  130. * Render tokens base on type of tokens 
  131. * @param array $current current token 
  132. * @param mixed $context current context 
  133. * @return string 
  134. */ 
  135. private function _renderInternal($current, $context) 
  136. $result = ''; 
  137. switch ($current[Tokenizer::TYPE]) { 
  138. case Tokenizer::T_END_SECTION: 
  139. break; // Its here just for handling whitespace trim. 
  140. case Tokenizer::T_SECTION : 
  141. $newStack = isset($current[Tokenizer::NODES]) 
  142. ? $current[Tokenizer::NODES] : array(); 
  143. array_push($this->_stack, array(0, $newStack, false)); 
  144. $result = $this->_section($context, $current); 
  145. array_pop($this->_stack); 
  146. break; 
  147. case Tokenizer::T_INVERTED : 
  148. $newStack = isset($current[Tokenizer::NODES]) ? 
  149. $current[Tokenizer::NODES] : array(); 
  150. array_push($this->_stack, array(0, $newStack, false)); 
  151. $result = $this->_inverted($context, $current); 
  152. array_pop($this->_stack); 
  153. break; 
  154. case Tokenizer::T_COMMENT : 
  155. $result = ''; 
  156. break; 
  157. case Tokenizer::T_PARTIAL: 
  158. case Tokenizer::T_PARTIAL_2: 
  159. $result = $this->_partial($context, $current); 
  160. break; 
  161. case Tokenizer::T_UNESCAPED: 
  162. case Tokenizer::T_UNESCAPED_2: 
  163. $result = $this->_get($context, $current, false); 
  164. break; 
  165. case Tokenizer::T_ESCAPED: 
  166. $result = $this->_get($context, $current, true); 
  167. break; 
  168. case Tokenizer::T_TEXT: 
  169. $result = $current[Tokenizer::VALUE]; 
  170. break; 
  171. /** How we could have another type of token? this part of code 
  172. is not used at all. 
  173. default: 
  174. throw new \RuntimeException( 
  175. 'Invalid node type : ' . json_encode($current) 
  176. ); 
  177. */ 
  178.  
  179. return $result; 
  180.  
  181. /** 
  182. * Discard top tree 
  183. * @return string 
  184. */ 
  185. public function discard() 
  186. $topTree = end($this->_stack); //This method never pop a value from stack 
  187. list($index, $tree, $stop) = $topTree; 
  188. while (array_key_exists($index, $tree)) { 
  189. $current = $tree[$index]; 
  190. $index++; 
  191. //if the section is exactly like waitFor 
  192. if (is_string($stop) 
  193. && $current[Tokenizer::TYPE] == Tokenizer::T_ESCAPED 
  194. && $current[Tokenizer::NAME] === $stop 
  195. ) { 
  196. break; 
  197. if ($stop) { 
  198. //Ok break here, the helper should be aware of this. 
  199. $newStack = array_pop($this->_stack); 
  200. $newStack[0] = $index; 
  201. $newStack[2] = false; 
  202. array_push($this->_stack, $newStack); 
  203.  
  204. return ''; 
  205.  
  206. /** 
  207. * Rewind top tree index to the first element 
  208. * @return void 
  209. */ 
  210. public function rewind() 
  211. $topStack = array_pop($this->_stack); 
  212. $topStack[0] = 0; 
  213. array_push($this->_stack, $topStack); 
  214.  
  215. /** 
  216. * Process handlebars section style 
  217. * @param Context $context current context 
  218. * @param array $current section node data 
  219. * @return mixed|string 
  220. */ 
  221. private function _handlebarsStyleSection(Context $context, $current) 
  222. $helpers = $this->handlebars->getHelpers(); 
  223. $sectionName = $current[Tokenizer::NAME]; 
  224.  
  225. if (isset($current[Tokenizer::END])) { 
  226. $source = substr( 
  227. $this->getSource(),  
  228. $current[Tokenizer::INDEX],  
  229. $current[Tokenizer::END] - $current[Tokenizer::INDEX] 
  230. ); 
  231. } else { 
  232. $source = ''; 
  233.  
  234. // subexpression parsing loop 
  235. $subexprs = array(); // will contain all subexpressions inside outermost brackets 
  236. $insideOf = array( 'single' => false, 'double' => false ); 
  237. $lvl = 0; 
  238. $cur_start = 0; 
  239. for ($i=0; $i < strlen($current[Tokenizer::ARGS]); $i++) { 
  240. $cur = substr($current[Tokenizer::ARGS], $i, 1); 
  241. if ($cur == "'" ) { 
  242. $insideOf['single'] = ! $insideOf['single']; 
  243. if ($cur == '"' ) { 
  244. $insideOf['double'] = ! $insideOf['double']; 
  245. if ($cur == '(' && ! $insideOf['single'] && ! $insideOf['double']) { 
  246. if ($lvl == 0) { 
  247. $cur_start = $i+1; 
  248. $lvl++; 
  249. continue; 
  250. if ($cur == ')' && ! $insideOf['single'] && ! $insideOf['double']) { 
  251. $lvl--; 
  252. if ($lvl == 0) { 
  253. $subexprs[] = substr($current[Tokenizer::ARGS], $cur_start, $i - $cur_start); 
  254.  
  255.  
  256. if (! empty($subexprs)) { 
  257. foreach ($subexprs as $expr) { 
  258. $cmd = explode(" ", $expr); 
  259. $name = trim($cmd[0]); 
  260. // construct artificial section node 
  261. $section_node = array( 
  262. Tokenizer::TYPE => Tokenizer::T_ESCAPED,  
  263. Tokenizer::NAME => $name,  
  264. Tokenizer::OTAG => $current[Tokenizer::OTAG],  
  265. Tokenizer::CTAG => $current[Tokenizer::CTAG],  
  266. Tokenizer::INDEX => $current[Tokenizer::INDEX],  
  267. Tokenizer::ARGS => implode(" ", array_slice($cmd, 1)) 
  268. ); 
  269. // resolve the node recursively 
  270. $resolved = addcslashes($this->_handlebarsStyleSection($context, $section_node), '"'); 
  271. // replace original subexpression with result 
  272. $current[Tokenizer::ARGS] = str_replace('('.$expr.')', '"' . $resolved . '"', $current[Tokenizer::ARGS]); 
  273.  
  274. $return = $helpers->call($sectionName, $this, $context, $current[Tokenizer::ARGS], $source); 
  275.  
  276. if ($return instanceof String) { 
  277. return $this->handlebars->loadString($return)->render($context); 
  278. } else { 
  279. return $return; 
  280.  
  281. /** 
  282. * Process Mustache section style 
  283. * @param Context $context current context 
  284. * @param array $current section node data 
  285. * @throws \RuntimeException 
  286. * @return mixed|string 
  287. */ 
  288. private function _mustacheStyleSection(Context $context, $current) 
  289. $sectionName = $current[Tokenizer::NAME]; 
  290.  
  291. // fallback to mustache style each/with/for just if there is 
  292. // no argument at all. 
  293. try { 
  294. $sectionVar = $context->get($sectionName, true); 
  295. } catch (\InvalidArgumentException $e) { 
  296. throw new \RuntimeException( 
  297. $sectionName . ' is not registered as a helper' 
  298. ); 
  299. $buffer = ''; 
  300. if (is_array($sectionVar) || $sectionVar instanceof \Traversable) { 
  301. $isList = is_array($sectionVar) && 
  302. (array_keys($sectionVar) === range(0, count($sectionVar) - 1)); 
  303. $index = 0; 
  304. $lastIndex = $isList ? (count($sectionVar) - 1) : false; 
  305.  
  306. foreach ($sectionVar as $key => $d) { 
  307. $specialVariables = array( 
  308. '@index' => $index,  
  309. '@first' => ($index === 0),  
  310. '@last' => ($index === $lastIndex),  
  311. ); 
  312. if (!$isList) { 
  313. $specialVariables['@key'] = $key; 
  314. $context->pushSpecialVariables($specialVariables); 
  315. $context->push($d); 
  316. $buffer .= $this->render($context); 
  317. $context->pop(); 
  318. $context->popSpecialVariables(); 
  319. $index++; 
  320. } elseif (is_object($sectionVar)) { 
  321. //Act like with 
  322. $context->push($sectionVar); 
  323. $buffer = $this->render($context); 
  324. $context->pop(); 
  325. } elseif ($sectionVar) { 
  326. $buffer = $this->render($context); 
  327.  
  328. return $buffer; 
  329.  
  330. /** 
  331. * Process section nodes 
  332. * @param Context $context current context 
  333. * @param array $current section node data 
  334. * @throws \RuntimeException 
  335. * @return string the result 
  336. */ 
  337. private function _section(Context $context, $current) 
  338. $helpers = $this->handlebars->getHelpers(); 
  339. $sectionName = $current[Tokenizer::NAME]; 
  340. if ($helpers->has($sectionName)) { 
  341. return $this->_handlebarsStyleSection($context, $current); 
  342. } elseif (trim($current[Tokenizer::ARGS]) == '') { 
  343. return $this->_mustacheStyleSection($context, $current); 
  344. } else { 
  345. throw new \RuntimeException( 
  346. $sectionName . ' is not registered as a helper' 
  347. ); 
  348.  
  349. /** 
  350. * Process inverted section 
  351. * @param Context $context current context 
  352. * @param array $current section node data 
  353. * @return string the result 
  354. */ 
  355. private function _inverted(Context $context, $current) 
  356. $sectionName = $current[Tokenizer::NAME]; 
  357. $data = $context->get($sectionName); 
  358. if (!$data) { 
  359. return $this->render($context); 
  360. } else { 
  361. //No need to discard here, since it has no else 
  362. return ''; 
  363.  
  364. /** 
  365. * Process partial section 
  366. * @param Context $context current context 
  367. * @param array $current section node data 
  368. * @return string the result 
  369. */ 
  370. private function _partial(Context $context, $current) 
  371. $partial = $this->handlebars->loadPartial($current[Tokenizer::NAME]); 
  372.  
  373. if ($current[Tokenizer::ARGS]) { 
  374. $context = $context->get($current[Tokenizer::ARGS]); 
  375.  
  376. return $partial->render($context); 
  377.  
  378.  
  379. /** 
  380. * Check if there is a helper with this variable name available or not. 
  381. * @param array $current current token 
  382. * @return boolean 
  383. */ 
  384. private function _isSection($current) 
  385. $helpers = $this->getEngine()->getHelpers(); 
  386. // Tokenizer doesn't process the args -if any- so be aware of that 
  387. $name = explode(' ', $current[Tokenizer::NAME], 2); 
  388.  
  389. return $helpers->has(reset($name)); 
  390.  
  391. /** 
  392. * get replacing value of a tag 
  393. * will process the tag as section, if a helper with the same name could be 
  394. * found, so {{helper arg}} can be used instead of {{#helper arg}}. 
  395. * @param Context $context current context 
  396. * @param array $current section node data 
  397. * @param boolean $escaped escape result or not 
  398. * @return string the string to be replaced with the tag 
  399. */ 
  400. private function _get(Context $context, $current, $escaped) 
  401. if ($this->_isSection($current)) { 
  402. return $this->_getSection($context, $current, $escaped); 
  403. } else { 
  404. return $this->_getVariable($context, $current, $escaped); 
  405.  
  406. /** 
  407. * Process section 
  408. * @param Context $context current context 
  409. * @param array $current section node data 
  410. * @param boolean $escaped escape result or not 
  411. * @return string the result 
  412. */ 
  413. private function _getSection(Context $context, $current, $escaped) 
  414. $args = explode(' ', $current[Tokenizer::NAME], 2); 
  415. $name = array_shift($args); 
  416. $current[Tokenizer::NAME] = $name; 
  417. $current[Tokenizer::ARGS] = implode(' ', $args); 
  418. $result = $this->_section($context, $current); 
  419.  
  420. if ($escaped && !($result instanceof SafeString)) { 
  421. $escape_args = $this->handlebars->getEscapeArgs(); 
  422. array_unshift($escape_args, $result); 
  423. $result = call_user_func_array( 
  424. $this->handlebars->getEscape(),  
  425. array_values($escape_args) 
  426. ); 
  427.  
  428. return $result; 
  429.  
  430. /** 
  431. * Process variable 
  432. * @param Context $context current context 
  433. * @param array $current section node data 
  434. * @param boolean $escaped escape result or not 
  435. * @return string the result 
  436. */ 
  437. private function _getVariable(Context $context, $current, $escaped) 
  438. $name = $current[Tokenizer::NAME]; 
  439. $value = $context->get($name); 
  440. if (is_array($value)) { 
  441. return 'Array'; 
  442. if ($escaped) { 
  443. $args = $this->handlebars->getEscapeArgs(); 
  444. array_unshift($args, $value); 
  445. $value = call_user_func_array( 
  446. $this->handlebars->getEscape(),  
  447. array_values($args) 
  448. ); 
  449.  
  450. return $value; 
  451.  
  452. /** 
  453. * Break an argument string into an array of named arguments 
  454. * @param string $string Argument String as passed to a helper 
  455. * @return array the argument list as an array 
  456. */ 
  457. public function parseNamedArguments($string) 
  458. if ($string instanceof Arguments) { 
  459. // This code is needed only for backward compatibility 
  460. $args = $string; 
  461. } else { 
  462. $args = new Arguments($string); 
  463.  
  464. return $args->getNamedArguments(); 
  465.  
  466. /** 
  467. * Break an argument string into an array of strings 
  468. * @param string $string Argument String as passed to a helper 
  469. * @throws \RuntimeException 
  470. * @return array the argument list as an array 
  471. */ 
  472. public function parseArguments($string) 
  473. if ($string instanceof Arguments) { 
  474. // This code is needed only for backward compatibility 
  475. $args = $string; 
  476. } else { 
  477. $args = new Arguments($string); 
  478.  
  479. return $args->getPositionalArguments(); 
/vendor/xamin/handlebars.php/src/Handlebars/Template.php  
  1. class Template 
  2. /** 
  3. * @var Handlebars 
  4. */ 
  5. protected $handlebars; 
  6.  
  7. /** 
  8. * @var array The tokenized tree 
  9. */ 
  10. protected $tree = array(); 
  11.  
  12. /** 
  13. * @var string The template source 
  14. */ 
  15. protected $source = ''; 
  16.  
  17. /** 
  18. * @var array Run stack 
  19. */ 
  20. private $_stack = array(); 
  21.  
  22. /** 
  23. * Handlebars template constructor 
  24. * @param Handlebars $engine handlebar engine 
  25. * @param array $tree Parsed tree 
  26. * @param string $source Handlebars source 
  27. */ 
  28. public function __construct(Handlebars $engine, $tree, $source) 
  29. $this->handlebars = $engine; 
  30. $this->tree = $tree; 
  31. $this->source = $source; 
  32. array_push($this->_stack, array(0, $this->getTree(), false)); 
  33.  
  34. /** 
  35. * Get current tree 
  36. * @return array 
  37. */ 
  38. public function getTree() 
  39. return $this->tree; 
  40.  
  41. /** 
  42. * Get current source 
  43. * @return string 
  44. */ 
  45. public function getSource() 
  46. return $this->source; 
  47.  
  48. /** 
  49. * Get current engine associated with this object 
  50. * @return Handlebars 
  51. */ 
  52. public function getEngine() 
  53. return $this->handlebars; 
  54.  
  55. /** 
  56. * set stop token for render and discard method 
  57. * @param string $token token to set as stop token or false to remove 
  58. * @return void 
  59. */ 
  60.  
  61. public function setStopToken($token) 
  62. $topStack = array_pop($this->_stack); 
  63. $topStack[2] = $token; 
  64. array_push($this->_stack, $topStack); 
  65.  
  66. /** 
  67. * get current stop token 
  68. * @return string|bool 
  69. */ 
  70.  
  71. public function getStopToken() 
  72. $topStack = end($this->_stack); 
  73.  
  74. return $topStack[2]; 
  75.  
  76. /** 
  77. * Get the current token's tree 
  78. * @return array 
  79. */ 
  80. public function getCurrentTokenTree() 
  81. $topStack = end($this->_stack); 
  82.  
  83. return $topStack[1]; 
  84.  
  85. /** 
  86. * Render top tree 
  87. * @param mixed $context current context 
  88. * @throws \RuntimeException 
  89. * @return string 
  90. */ 
  91. public function render($context) 
  92. if (!$context instanceof Context) { 
  93. $context = new Context($context); 
  94. $topTree = end($this->_stack); // never pop a value from stack 
  95. list($index, $tree, $stop) = $topTree; 
  96.  
  97. $buffer = ''; 
  98. $rTrim = false; 
  99. while (array_key_exists($index, $tree)) { 
  100. $current = $tree[$index]; 
  101. $index++; 
  102. //if the section is exactly like waitFor 
  103. if (is_string($stop) 
  104. && $current[Tokenizer::TYPE] == Tokenizer::T_ESCAPED 
  105. && $current[Tokenizer::NAME] === $stop 
  106. ) { 
  107. break; 
  108. if (isset($current[Tokenizer::TRIM_LEFT]) && $current[Tokenizer::TRIM_LEFT]) { 
  109. $buffer = rtrim($buffer); 
  110.  
  111. $tmp = $this->_renderInternal($current, $context); 
  112.  
  113. if (isset($current[Tokenizer::TRIM_LEFT]) && $current[Tokenizer::TRIM_LEFT]) { 
  114. $tmp = rtrim($tmp); 
  115.  
  116. if ($rTrim || (isset($current[Tokenizer::TRIM_RIGHT]) && $current[Tokenizer::TRIM_RIGHT])) { 
  117. $tmp = ltrim($tmp); 
  118.  
  119. $buffer .= $tmp; 
  120. // Some time, there is more than one string token (first is empty),  
  121. //so we need to trim all of them in one shot 
  122.  
  123. $rTrim = (empty($tmp) && $rTrim) || 
  124. isset($current[Tokenizer::TRIM_RIGHT]) && $current[Tokenizer::TRIM_RIGHT]; 
  125. if ($stop) { 
  126. //Ok break here, the helper should be aware of this. 
  127. $newStack = array_pop($this->_stack); 
  128. $newStack[0] = $index; 
  129. $newStack[2] = false; //No stop token from now on 
  130. array_push($this->_stack, $newStack); 
  131.  
  132. return $buffer; 
  133.  
  134. /** 
  135. * Render tokens base on type of tokens 
  136. * @param array $current current token 
  137. * @param mixed $context current context 
  138. * @return string 
  139. */ 
  140. private function _renderInternal($current, $context) 
  141. $result = ''; 
  142. switch ($current[Tokenizer::TYPE]) { 
  143. case Tokenizer::T_END_SECTION: 
  144. break; // Its here just for handling whitespace trim. 
  145. case Tokenizer::T_SECTION : 
  146. $newStack = isset($current[Tokenizer::NODES]) 
  147. ? $current[Tokenizer::NODES] : array(); 
  148. array_push($this->_stack, array(0, $newStack, false)); 
  149. $result = $this->_section($context, $current); 
  150. array_pop($this->_stack); 
  151. break; 
  152. case Tokenizer::T_INVERTED : 
  153. $newStack = isset($current[Tokenizer::NODES]) ? 
  154. $current[Tokenizer::NODES] : array(); 
  155. array_push($this->_stack, array(0, $newStack, false)); 
  156. $result = $this->_inverted($context, $current); 
  157. array_pop($this->_stack); 
  158. break; 
  159. case Tokenizer::T_COMMENT : 
  160. $result = ''; 
  161. break; 
  162. case Tokenizer::T_PARTIAL: 
  163. case Tokenizer::T_PARTIAL_2: 
  164. $result = $this->_partial($context, $current); 
  165. break; 
  166. case Tokenizer::T_UNESCAPED: 
  167. case Tokenizer::T_UNESCAPED_2: 
  168. $result = $this->_get($context, $current, false); 
  169. break; 
  170. case Tokenizer::T_ESCAPED: 
  171. $result = $this->_get($context, $current, true); 
  172. break; 
  173. case Tokenizer::T_TEXT: 
  174. $result = $current[Tokenizer::VALUE]; 
  175. break; 
  176. /** How we could have another type of token? this part of code 
  177. is not used at all. 
  178. default: 
  179. throw new \RuntimeException( 
  180. 'Invalid node type : ' . json_encode($current) 
  181. ); 
  182. */ 
  183.  
  184. return $result; 
  185.  
  186. /** 
  187. * Discard top tree 
  188. * @return string 
  189. */ 
  190. public function discard() 
  191. $topTree = end($this->_stack); //This method never pop a value from stack 
  192. list($index, $tree, $stop) = $topTree; 
  193. while (array_key_exists($index, $tree)) { 
  194. $current = $tree[$index]; 
  195. $index++; 
  196. //if the section is exactly like waitFor 
  197. if (is_string($stop) 
  198. && $current[Tokenizer::TYPE] == Tokenizer::T_ESCAPED 
  199. && $current[Tokenizer::NAME] === $stop 
  200. ) { 
  201. break; 
  202. if ($stop) { 
  203. //Ok break here, the helper should be aware of this. 
  204. $newStack = array_pop($this->_stack); 
  205. $newStack[0] = $index; 
  206. $newStack[2] = false; 
  207. array_push($this->_stack, $newStack); 
  208.  
  209. return ''; 
  210.  
  211. /** 
  212. * Rewind top tree index to the first element 
  213. * @return void 
  214. */ 
  215. public function rewind() 
  216. $topStack = array_pop($this->_stack); 
  217. $topStack[0] = 0; 
  218. array_push($this->_stack, $topStack); 
  219.  
  220. /** 
  221. * Process handlebars section style 
  222. * @param Context $context current context 
  223. * @param array $current section node data 
  224. * @return mixed|string 
  225. */ 
  226. private function _handlebarsStyleSection(Context $context, $current) 
  227. $helpers = $this->handlebars->getHelpers(); 
  228. $sectionName = $current[Tokenizer::NAME]; 
  229.  
  230. if (isset($current[Tokenizer::END])) { 
  231. $source = substr( 
  232. $this->getSource(),  
  233. $current[Tokenizer::INDEX],  
  234. $current[Tokenizer::END] - $current[Tokenizer::INDEX] 
  235. ); 
  236. } else { 
  237. $source = ''; 
  238.  
  239. // subexpression parsing loop 
  240. $subexprs = array(); // will contain all subexpressions inside outermost brackets 
  241. $insideOf = array( 'single' => false, 'double' => false ); 
  242. $lvl = 0; 
  243. $cur_start = 0; 
  244. for ($i=0; $i < strlen($current[Tokenizer::ARGS]); $i++) { 
  245. $cur = substr($current[Tokenizer::ARGS], $i, 1); 
  246. if ($cur == "'" ) { 
  247. $insideOf['single'] = ! $insideOf['single']; 
  248. if ($cur == '"' ) { 
  249. $insideOf['double'] = ! $insideOf['double']; 
  250. if ($cur == '(' && ! $insideOf['single'] && ! $insideOf['double']) { 
  251. if ($lvl == 0) { 
  252. $cur_start = $i+1; 
  253. $lvl++; 
  254. continue; 
  255. if ($cur == ')' && ! $insideOf['single'] && ! $insideOf['double']) { 
  256. $lvl--; 
  257. if ($lvl == 0) { 
  258. $subexprs[] = substr($current[Tokenizer::ARGS], $cur_start, $i - $cur_start); 
  259.  
  260.  
  261. if (! empty($subexprs)) { 
  262. foreach ($subexprs as $expr) { 
  263. $cmd = explode(" ", $expr); 
  264. $name = trim($cmd[0]); 
  265. // construct artificial section node 
  266. $section_node = array( 
  267. Tokenizer::TYPE => Tokenizer::T_ESCAPED,  
  268. Tokenizer::NAME => $name,  
  269. Tokenizer::OTAG => $current[Tokenizer::OTAG],  
  270. Tokenizer::CTAG => $current[Tokenizer::CTAG],  
  271. Tokenizer::INDEX => $current[Tokenizer::INDEX],  
  272. Tokenizer::ARGS => implode(" ", array_slice($cmd, 1)) 
  273. ); 
  274. // resolve the node recursively 
  275. $resolved = addcslashes($this->_handlebarsStyleSection($context, $section_node), '"'); 
  276. // replace original subexpression with result 
  277. $current[Tokenizer::ARGS] = str_replace('('.$expr.')', '"' . $resolved . '"', $current[Tokenizer::ARGS]); 
  278.  
  279. $return = $helpers->call($sectionName, $this, $context, $current[Tokenizer::ARGS], $source); 
  280.  
  281. if ($return instanceof String) { 
  282. return $this->handlebars->loadString($return)->render($context); 
  283. } else { 
  284. return $return; 
  285.  
  286. /** 
  287. * Process Mustache section style 
  288. * @param Context $context current context 
  289. * @param array $current section node data 
  290. * @throws \RuntimeException 
  291. * @return mixed|string 
  292. */ 
  293. private function _mustacheStyleSection(Context $context, $current) 
  294. $sectionName = $current[Tokenizer::NAME]; 
  295.  
  296. // fallback to mustache style each/with/for just if there is 
  297. // no argument at all. 
  298. try { 
  299. $sectionVar = $context->get($sectionName, true); 
  300. } catch (\InvalidArgumentException $e) { 
  301. throw new \RuntimeException( 
  302. $sectionName . ' is not registered as a helper' 
  303. ); 
  304. $buffer = ''; 
  305. if (is_array($sectionVar) || $sectionVar instanceof \Traversable) { 
  306. $isList = is_array($sectionVar) && 
  307. (array_keys($sectionVar) === range(0, count($sectionVar) - 1)); 
  308. $index = 0; 
  309. $lastIndex = $isList ? (count($sectionVar) - 1) : false; 
  310.  
  311. foreach ($sectionVar as $key => $d) { 
  312. $specialVariables = array( 
  313. '@index' => $index,  
  314. '@first' => ($index === 0),  
  315. '@last' => ($index === $lastIndex),  
  316. ); 
  317. if (!$isList) { 
  318. $specialVariables['@key'] = $key; 
  319. $context->pushSpecialVariables($specialVariables); 
  320. $context->push($d); 
  321. $buffer .= $this->render($context); 
  322. $context->pop(); 
  323. $context->popSpecialVariables(); 
  324. $index++; 
  325. } elseif (is_object($sectionVar)) { 
  326. //Act like with 
  327. $context->push($sectionVar); 
  328. $buffer = $this->render($context); 
  329. $context->pop(); 
  330. } elseif ($sectionVar) { 
  331. $buffer = $this->render($context); 
  332.  
  333. return $buffer; 
  334.  
  335. /** 
  336. * Process section nodes 
  337. * @param Context $context current context 
  338. * @param array $current section node data 
  339. * @throws \RuntimeException 
  340. * @return string the result 
  341. */ 
  342. private function _section(Context $context, $current) 
  343. $helpers = $this->handlebars->getHelpers(); 
  344. $sectionName = $current[Tokenizer::NAME]; 
  345. if ($helpers->has($sectionName)) { 
  346. return $this->_handlebarsStyleSection($context, $current); 
  347. } elseif (trim($current[Tokenizer::ARGS]) == '') { 
  348. return $this->_mustacheStyleSection($context, $current); 
  349. } else { 
  350. throw new \RuntimeException( 
  351. $sectionName . ' is not registered as a helper' 
  352. ); 
  353.  
  354. /** 
  355. * Process inverted section 
  356. * @param Context $context current context 
  357. * @param array $current section node data 
  358. * @return string the result 
  359. */ 
  360. private function _inverted(Context $context, $current) 
  361. $sectionName = $current[Tokenizer::NAME]; 
  362. $data = $context->get($sectionName); 
  363. if (!$data) { 
  364. return $this->render($context); 
  365. } else { 
  366. //No need to discard here, since it has no else 
  367. return ''; 
  368.  
  369. /** 
  370. * Process partial section 
  371. * @param Context $context current context 
  372. * @param array $current section node data 
  373. * @return string the result 
  374. */ 
  375. private function _partial(Context $context, $current) 
  376. $partial = $this->handlebars->loadPartial($current[Tokenizer::NAME]); 
  377.  
  378. if ($current[Tokenizer::ARGS]) { 
  379. $context = $context->get($current[Tokenizer::ARGS]); 
  380.  
  381. return $partial->render($context); 
  382.  
  383.  
  384. /** 
  385. * Check if there is a helper with this variable name available or not. 
  386. * @param array $current current token 
  387. * @return boolean 
  388. */ 
  389. private function _isSection($current) 
  390. $helpers = $this->getEngine()->getHelpers(); 
  391. // Tokenizer doesn't process the args -if any- so be aware of that 
  392. $name = explode(' ', $current[Tokenizer::NAME], 2); 
  393.  
  394. return $helpers->has(reset($name)); 
  395.  
  396. /** 
  397. * get replacing value of a tag 
  398. * will process the tag as section, if a helper with the same name could be 
  399. * found, so {{helper arg}} can be used instead of {{#helper arg}}. 
  400. * @param Context $context current context 
  401. * @param array $current section node data 
  402. * @param boolean $escaped escape result or not 
  403. * @return string the string to be replaced with the tag 
  404. */ 
  405. private function _get(Context $context, $current, $escaped) 
  406. if ($this->_isSection($current)) { 
  407. return $this->_getSection($context, $current, $escaped); 
  408. } else { 
  409. return $this->_getVariable($context, $current, $escaped); 
  410.  
  411. /** 
  412. * Process section 
  413. * @param Context $context current context 
  414. * @param array $current section node data 
  415. * @param boolean $escaped escape result or not 
  416. * @return string the result 
  417. */ 
  418. private function _getSection(Context $context, $current, $escaped) 
  419. $args = explode(' ', $current[Tokenizer::NAME], 2); 
  420. $name = array_shift($args); 
  421. $current[Tokenizer::NAME] = $name; 
  422. $current[Tokenizer::ARGS] = implode(' ', $args); 
  423. $result = $this->_section($context, $current); 
  424.  
  425. if ($escaped && !($result instanceof SafeString)) { 
  426. $escape_args = $this->handlebars->getEscapeArgs(); 
  427. array_unshift($escape_args, $result); 
  428. $result = call_user_func_array( 
  429. $this->handlebars->getEscape(),  
  430. array_values($escape_args) 
  431. ); 
  432.  
  433. return $result; 
  434.  
  435. /** 
  436. * Process variable 
  437. * @param Context $context current context 
  438. * @param array $current section node data 
  439. * @param boolean $escaped escape result or not 
  440. * @return string the result 
  441. */ 
  442. private function _getVariable(Context $context, $current, $escaped) 
  443. $name = $current[Tokenizer::NAME]; 
  444. $value = $context->get($name); 
  445. if (is_array($value)) { 
  446. return 'Array'; 
  447. if ($escaped) { 
  448. $args = $this->handlebars->getEscapeArgs(); 
  449. array_unshift($args, $value); 
  450. $value = call_user_func_array( 
  451. $this->handlebars->getEscape(),  
  452. array_values($args) 
  453. ); 
  454.  
  455. return $value; 
  456.  
  457. /** 
  458. * Break an argument string into an array of named arguments 
  459. * @param string $string Argument String as passed to a helper 
  460. * @return array the argument list as an array 
  461. */ 
  462. public function parseNamedArguments($string) 
  463. if ($string instanceof Arguments) { 
  464. // This code is needed only for backward compatibility 
  465. $args = $string; 
  466. } else { 
  467. $args = new Arguments($string); 
  468.  
  469. return $args->getNamedArguments(); 
  470.  
  471. /** 
  472. * Break an argument string into an array of strings 
  473. * @param string $string Argument String as passed to a helper 
  474. * @throws \RuntimeException 
  475. * @return array the argument list as an array 
  476. */ 
  477. public function parseArguments($string) 
  478. if ($string instanceof Arguments) { 
  479. // This code is needed only for backward compatibility 
  480. $args = $string; 
  481. } else { 
  482. $args = new Arguments($string); 
  483.  
  484. return $args->getPositionalArguments();