HandlebarsTokenizer

Handlebars tokenizer (based on mustache).

Defined (2)

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

/vendor/calderawp/metaplate-core/vendor/xamin/handlebars.php/src/Handlebars/Tokenizer.php  
  1. class Tokenizer 
  2.  
  3. // Finite state machine states 
  4. const IN_TEXT = 0; 
  5. const IN_TAG_TYPE = 1; 
  6. const IN_TAG = 2; 
  7.  
  8. // Token types 
  9. const T_SECTION = '#'; 
  10. const T_INVERTED = '^'; 
  11. const T_END_SECTION = '/'; 
  12. const T_COMMENT = '!'; 
  13. // XXX: remove partials support from tokenizer and make it a helper? 
  14. const T_PARTIAL = '>'; 
  15. const T_PARTIAL_2 = '<'; 
  16. const T_DELIM_CHANGE = '='; 
  17. const T_ESCAPED = '_v'; 
  18. const T_UNESCAPED = '{'; 
  19. const T_UNESCAPED_2 = '&'; 
  20. const T_TEXT = '_t'; 
  21. const T_ESCAPE = "\\"; 
  22. const T_SINGLE_Q = "'"; 
  23. const T_DOUBLE_Q = "\""; 
  24. const T_TRIM = "~"; 
  25.  
  26. // Valid token types 
  27. private static $_tagTypes = array( 
  28. self::T_SECTION => true,  
  29. self::T_INVERTED => true,  
  30. self::T_END_SECTION => true,  
  31. self::T_COMMENT => true,  
  32. self::T_PARTIAL => true,  
  33. self::T_PARTIAL_2 => true,  
  34. self::T_DELIM_CHANGE => true,  
  35. self::T_ESCAPED => true,  
  36. self::T_UNESCAPED => true,  
  37. self::T_UNESCAPED_2 => true,  
  38. ); 
  39.  
  40. // Interpolated tags 
  41. private static $_interpolatedTags = array( 
  42. self::T_ESCAPED => true,  
  43. self::T_UNESCAPED => true,  
  44. self::T_UNESCAPED_2 => true,  
  45. ); 
  46.  
  47. // Token properties 
  48. const TYPE = 'type'; 
  49. const NAME = 'name'; 
  50. const OTAG = 'otag'; 
  51. const CTAG = 'ctag'; 
  52. const INDEX = 'index'; 
  53. const END = 'end'; 
  54. const INDENT = 'indent'; 
  55. const NODES = 'nodes'; 
  56. const VALUE = 'value'; 
  57. const ARGS = 'args'; 
  58. const TRIM_LEFT = 'tleft'; 
  59. const TRIM_RIGHT = 'tright'; 
  60.  
  61. protected $state; 
  62. protected $tagType; 
  63. protected $tag; 
  64. protected $buffer = ''; 
  65. protected $tokens; 
  66. protected $seenTag; 
  67. protected $lineStart; 
  68. protected $otag; 
  69. protected $ctag; 
  70. protected $escaped; 
  71. protected $escaping; 
  72. protected $trimLeft; 
  73. protected $trimRight; 
  74.  
  75. /** 
  76. * Scan and tokenize template source. 
  77. * @param string $text Mustache template source to tokenize 
  78. * @internal string $delimiters Optional, pass opening and closing delimiters 
  79. * @return array Set of Mustache tokens 
  80. */ 
  81. public function scan($text/**, $delimiters = null*/) 
  82. if ($text instanceof String) { 
  83. $text = $text->getString(); 
  84. $this->reset(); 
  85.  
  86. /** Actually we not support this. so this code is not used at all, yet. 
  87. if ($delimiters = trim($delimiters)) { 
  88. list($otag, $ctag) = explode(' ', $delimiters); 
  89. $this->otag = $otag; 
  90. $this->ctag = $ctag; 
  91. */ 
  92. $len = strlen($text); 
  93. for ($i = 0; $i < $len; $i++) { 
  94.  
  95. $this->escaping = $this->tagChange(self::T_ESCAPE, $text, $i); 
  96.  
  97. // To play nice with helpers' arguments quote and apostrophe marks 
  98. // should be additionally escaped only when they are not in a tag. 
  99. $quoteInTag = $this->state != self::IN_TEXT 
  100. && ($text[$i] == self::T_SINGLE_Q || $text[$i] == self::T_DOUBLE_Q); 
  101.  
  102. if ($this->escaped && $text[$i] != self::T_UNESCAPED && !$quoteInTag) { 
  103. $this->buffer .= "\\"; 
  104.  
  105. switch ($this->state) { 
  106. case self::IN_TEXT: 
  107. if ($this->tagChange($this->otag. self::T_TRIM, $text, $i) and !$this->escaped) { 
  108. $this->flushBuffer(); 
  109. $this->state = self::IN_TAG_TYPE; 
  110. $this->trimLeft = true; 
  111. } elseif ($this->tagChange(self::T_UNESCAPED.$this->otag, $text, $i) and $this->escaped) { 
  112. $this->buffer .= "{{{"; 
  113. $i += 2; 
  114. continue; 
  115. } elseif ($this->tagChange($this->otag, $text, $i) and !$this->escaped) { 
  116. $i--; 
  117. $this->flushBuffer(); 
  118. $this->state = self::IN_TAG_TYPE; 
  119. } elseif ($this->escaped and $this->escaping) { 
  120. $this->buffer .= "\\"; 
  121. } elseif (!$this->escaping) { 
  122. if ($text[$i] == "\n") { 
  123. $this->filterLine(); 
  124. } else { 
  125. $this->buffer .= $text[$i]; 
  126. break; 
  127.  
  128. case self::IN_TAG_TYPE: 
  129.  
  130. $i += strlen($this->otag) - 1; 
  131. if (isset(self::$_tagTypes[$text[$i + 1]])) { 
  132. $tag = $text[$i + 1]; 
  133. $this->tagType = $tag; 
  134. } else { 
  135. $tag = null; 
  136. $this->tagType = self::T_ESCAPED; 
  137.  
  138. if ($this->tagType === self::T_DELIM_CHANGE) { 
  139. $i = $this->changeDelimiters($text, $i); 
  140. $this->state = self::IN_TEXT; 
  141. } else { 
  142. if ($tag !== null) { 
  143. $i++; 
  144. $this->state = self::IN_TAG; 
  145. $this->seenTag = $i; 
  146. break; 
  147.  
  148. default: 
  149. if ($this->tagChange(self::T_TRIM . $this->ctag, $text, $i)) { 
  150. $this->trimRight = true; 
  151. continue; 
  152. if ($this->tagChange($this->ctag, $text, $i)) { 
  153. // Sections (Helpers) can accept parameters 
  154. // Same thing for Partials (little known fact) 
  155. if (($this->tagType == self::T_SECTION) 
  156. || ($this->tagType == self::T_PARTIAL) 
  157. || ($this->tagType == self::T_PARTIAL_2) 
  158. ) { 
  159. $newBuffer = explode(' ', trim($this->buffer), 2); 
  160. $args = ''; 
  161. if (count($newBuffer) == 2) { 
  162. $args = $newBuffer[1]; 
  163. $this->buffer = $newBuffer[0]; 
  164. $t = array( 
  165. self::TYPE => $this->tagType,  
  166. self::NAME => trim($this->buffer),  
  167. self::OTAG => $this->otag,  
  168. self::CTAG => $this->ctag,  
  169. self::INDEX => ($this->tagType == self::T_END_SECTION) ? 
  170. $this->seenTag - strlen($this->otag) : 
  171. $i + strlen($this->ctag),  
  172. self::TRIM_LEFT => $this->trimLeft,  
  173. self::TRIM_RIGHT => $this->trimRight 
  174. ); 
  175. if (isset($args)) { 
  176. $t[self::ARGS] = $args; 
  177. $this->tokens[] = $t; 
  178. unset($t); 
  179. unset($args); 
  180. $this->buffer = ''; 
  181. $this->trimLeft = false; 
  182. $this->trimRight = false; 
  183. $i += strlen($this->ctag) - 1; 
  184. $this->state = self::IN_TEXT; 
  185. if ($this->tagType == self::T_UNESCAPED) { 
  186. if ($this->ctag == '}}') { 
  187. $i++; 
  188. } /** else { // I can't remember why this part is here! the ctag is always }} and 
  189. // Clean up `{{{ tripleStache }}}` style tokens. 
  190. $lastIndex = count($this->tokens) - 1; 
  191. $lastName = $this->tokens[$lastIndex][self::NAME]; 
  192. if (substr($lastName, -1) === '}') { 
  193. $this->tokens[$lastIndex][self::NAME] = trim( 
  194. substr($lastName, 0, -1) 
  195. ); 
  196. } */ 
  197. } else { 
  198. $this->buffer .= $text[$i]; 
  199. break; 
  200.  
  201. $this->escaped = ($this->escaping and !$this->escaped); 
  202.  
  203. $this->filterLine(true); 
  204.  
  205. return $this->tokens; 
  206.  
  207. /** 
  208. * Helper function to reset tokenizer internal state. 
  209. * @return void 
  210. */ 
  211. protected function reset() 
  212. $this->state = self::IN_TEXT; 
  213. $this->escaped = false; 
  214. $this->escaping = false; 
  215. $this->tagType = null; 
  216. $this->tag = null; 
  217. $this->buffer = ''; 
  218. $this->tokens = array(); 
  219. $this->seenTag = false; 
  220. $this->lineStart = 0; 
  221. $this->otag = '{{'; 
  222. $this->ctag = '}}'; 
  223. $this->trimLeft = false; 
  224. $this->trimRight = false; 
  225.  
  226. /** 
  227. * Flush the current buffer to a token. 
  228. * @return void 
  229. */ 
  230. protected function flushBuffer() 
  231. if ($this->buffer !== '') { 
  232. $this->tokens[] = array( 
  233. self::TYPE => self::T_TEXT,  
  234. self::VALUE => $this->buffer 
  235. ); 
  236. $this->buffer = ''; 
  237.  
  238. /** 
  239. * Test whether the current line is entirely made up of whitespace. 
  240. * @return boolean True if the current line is all whitespace 
  241. */ 
  242. protected function lineIsWhitespace() 
  243. $tokensCount = count($this->tokens); 
  244. for ($j = $this->lineStart; $j < $tokensCount; $j++) { 
  245. $token = $this->tokens[$j]; 
  246. if (isset(self::$_tagTypes[$token[self::TYPE]])) { 
  247. if (isset(self::$_interpolatedTags[$token[self::TYPE]])) { 
  248. return false; 
  249. } elseif ($token[self::TYPE] == self::T_TEXT) { 
  250. if (preg_match('/\S/', $token[self::VALUE])) { 
  251. return false; 
  252.  
  253. return true; 
  254.  
  255. /** 
  256. * Filter out whitespace-only lines and store indent levels for partials. 
  257. * @param bool $noNewLine Suppress the newline? (default: false) 
  258. * @return void 
  259. */ 
  260. protected function filterLine($noNewLine = false) 
  261. $this->flushBuffer(); 
  262. if ($this->seenTag && $this->lineIsWhitespace()) { 
  263. $tokensCount = count($this->tokens); 
  264. for ($j = $this->lineStart; $j < $tokensCount; $j++) { 
  265. if ($this->tokens[$j][self::TYPE] == self::T_TEXT) { 
  266. if (isset($this->tokens[$j + 1]) 
  267. && $this->tokens[$j + 1][self::TYPE] == self::T_PARTIAL 
  268. ) { 
  269. $this->tokens[$j + 1][self::INDENT] 
  270. = $this->tokens[$j][self::VALUE]; 
  271.  
  272. $this->tokens[$j] = null; 
  273. } elseif (!$noNewLine) { 
  274. $this->tokens[] = array(self::TYPE => self::T_TEXT, self::VALUE => "\n"); 
  275.  
  276. $this->seenTag = false; 
  277. $this->lineStart = count($this->tokens); 
  278.  
  279. /** 
  280. * Change the current Handlebars delimiters. Set new `otag` and `ctag` values. 
  281. * @param string $text Mustache template source 
  282. * @param int $index Current tokenizer index 
  283. * @return int New index value 
  284. */ 
  285. protected function changeDelimiters($text, $index) 
  286. $startIndex = strpos($text, '=', $index) + 1; 
  287. $close = '=' . $this->ctag; 
  288. $closeIndex = strpos($text, $close, $index); 
  289.  
  290. list($otag, $ctag) = explode( 
  291. ' ',  
  292. trim(substr($text, $startIndex, $closeIndex - $startIndex)) 
  293. ); 
  294. $this->otag = $otag; 
  295. $this->ctag = $ctag; 
  296.  
  297. return $closeIndex + strlen($close) - 1; 
  298.  
  299. /** 
  300. * Test whether it's time to change tags. 
  301. * @param string $tag Current tag name 
  302. * @param string $text Handlebars template source 
  303. * @param int $index Current tokenizer index 
  304. * @return boolean True if this is a closing section tag 
  305. */ 
  306. protected function tagChange($tag, $text, $index) 
  307. return substr($text, $index, strlen($tag)) === $tag; 
  308.  
/vendor/xamin/handlebars.php/src/Handlebars/Tokenizer.php  
  1. class Tokenizer 
  2.  
  3. // Finite state machine states 
  4. const IN_TEXT = 0; 
  5. const IN_TAG_TYPE = 1; 
  6. const IN_TAG = 2; 
  7.  
  8. // Token types 
  9. const T_SECTION = '#'; 
  10. const T_INVERTED = '^'; 
  11. const T_END_SECTION = '/'; 
  12. const T_COMMENT = '!'; 
  13. // XXX: remove partials support from tokenizer and make it a helper? 
  14. const T_PARTIAL = '>'; 
  15. const T_PARTIAL_2 = '<'; 
  16. const T_DELIM_CHANGE = '='; 
  17. const T_ESCAPED = '_v'; 
  18. const T_UNESCAPED = '{'; 
  19. const T_UNESCAPED_2 = '&'; 
  20. const T_TEXT = '_t'; 
  21. const T_ESCAPE = "\\"; 
  22. const T_SINGLE_Q = "'"; 
  23. const T_DOUBLE_Q = "\""; 
  24. const T_TRIM = "~"; 
  25.  
  26. // Valid token types 
  27. private static $_tagTypes = array( 
  28. self::T_SECTION => true,  
  29. self::T_INVERTED => true,  
  30. self::T_END_SECTION => true,  
  31. self::T_COMMENT => true,  
  32. self::T_PARTIAL => true,  
  33. self::T_PARTIAL_2 => true,  
  34. self::T_DELIM_CHANGE => true,  
  35. self::T_ESCAPED => true,  
  36. self::T_UNESCAPED => true,  
  37. self::T_UNESCAPED_2 => true,  
  38. ); 
  39.  
  40. // Interpolated tags 
  41. private static $_interpolatedTags = array( 
  42. self::T_ESCAPED => true,  
  43. self::T_UNESCAPED => true,  
  44. self::T_UNESCAPED_2 => true,  
  45. ); 
  46.  
  47. // Token properties 
  48. const TYPE = 'type'; 
  49. const NAME = 'name'; 
  50. const OTAG = 'otag'; 
  51. const CTAG = 'ctag'; 
  52. const INDEX = 'index'; 
  53. const END = 'end'; 
  54. const INDENT = 'indent'; 
  55. const NODES = 'nodes'; 
  56. const VALUE = 'value'; 
  57. const ARGS = 'args'; 
  58. const TRIM_LEFT = 'tleft'; 
  59. const TRIM_RIGHT = 'tright'; 
  60.  
  61. protected $state; 
  62. protected $tagType; 
  63. protected $tag; 
  64. protected $buffer = ''; 
  65. protected $tokens; 
  66. protected $seenTag; 
  67. protected $lineStart; 
  68. protected $otag; 
  69. protected $ctag; 
  70. protected $escaped; 
  71. protected $escaping; 
  72. protected $trimLeft; 
  73. protected $trimRight; 
  74.  
  75. /** 
  76. * Scan and tokenize template source. 
  77. * @param string $text Mustache template source to tokenize 
  78. * @internal string $delimiters Optional, pass opening and closing delimiters 
  79. * @return array Set of Mustache tokens 
  80. */ 
  81. public function scan($text/**, $delimiters = null*/) 
  82. if ($text instanceof String) { 
  83. $text = $text->getString(); 
  84. $this->reset(); 
  85.  
  86. /** Actually we not support this. so this code is not used at all, yet. 
  87. if ($delimiters = trim($delimiters)) { 
  88. list($otag, $ctag) = explode(' ', $delimiters); 
  89. $this->otag = $otag; 
  90. $this->ctag = $ctag; 
  91. */ 
  92. $len = strlen($text); 
  93. for ($i = 0; $i < $len; $i++) { 
  94.  
  95. $this->escaping = $this->tagChange(self::T_ESCAPE, $text, $i); 
  96.  
  97. // To play nice with helpers' arguments quote and apostrophe marks 
  98. // should be additionally escaped only when they are not in a tag. 
  99. $quoteInTag = $this->state != self::IN_TEXT 
  100. && ($text[$i] == self::T_SINGLE_Q || $text[$i] == self::T_DOUBLE_Q); 
  101.  
  102. if ($this->escaped && $text[$i] != self::T_UNESCAPED && !$quoteInTag) { 
  103. $this->buffer .= "\\"; 
  104.  
  105. switch ($this->state) { 
  106. case self::IN_TEXT: 
  107. if ($this->tagChange($this->otag. self::T_TRIM, $text, $i) and !$this->escaped) { 
  108. $this->flushBuffer(); 
  109. $this->state = self::IN_TAG_TYPE; 
  110. $this->trimLeft = true; 
  111. } elseif ($this->tagChange(self::T_UNESCAPED.$this->otag, $text, $i) and $this->escaped) { 
  112. $this->buffer .= "{{{"; 
  113. $i += 2; 
  114. continue; 
  115. } elseif ($this->tagChange($this->otag, $text, $i) and !$this->escaped) { 
  116. $i--; 
  117. $this->flushBuffer(); 
  118. $this->state = self::IN_TAG_TYPE; 
  119. } elseif ($this->escaped and $this->escaping) { 
  120. $this->buffer .= "\\"; 
  121. } elseif (!$this->escaping) { 
  122. if ($text[$i] == "\n") { 
  123. $this->filterLine(); 
  124. } else { 
  125. $this->buffer .= $text[$i]; 
  126. break; 
  127.  
  128. case self::IN_TAG_TYPE: 
  129.  
  130. $i += strlen($this->otag) - 1; 
  131. if (isset(self::$_tagTypes[$text[$i + 1]])) { 
  132. $tag = $text[$i + 1]; 
  133. $this->tagType = $tag; 
  134. } else { 
  135. $tag = null; 
  136. $this->tagType = self::T_ESCAPED; 
  137.  
  138. if ($this->tagType === self::T_DELIM_CHANGE) { 
  139. $i = $this->changeDelimiters($text, $i); 
  140. $this->state = self::IN_TEXT; 
  141. } else { 
  142. if ($tag !== null) { 
  143. $i++; 
  144. $this->state = self::IN_TAG; 
  145. $this->seenTag = $i; 
  146. break; 
  147.  
  148. default: 
  149. if ($this->tagChange(self::T_TRIM . $this->ctag, $text, $i)) { 
  150. $this->trimRight = true; 
  151. continue; 
  152. if ($this->tagChange($this->ctag, $text, $i)) { 
  153. // Sections (Helpers) can accept parameters 
  154. // Same thing for Partials (little known fact) 
  155. if (($this->tagType == self::T_SECTION) 
  156. || ($this->tagType == self::T_PARTIAL) 
  157. || ($this->tagType == self::T_PARTIAL_2) 
  158. ) { 
  159. $newBuffer = explode(' ', trim($this->buffer), 2); 
  160. $args = ''; 
  161. if (count($newBuffer) == 2) { 
  162. $args = $newBuffer[1]; 
  163. $this->buffer = $newBuffer[0]; 
  164. $t = array( 
  165. self::TYPE => $this->tagType,  
  166. self::NAME => trim($this->buffer),  
  167. self::OTAG => $this->otag,  
  168. self::CTAG => $this->ctag,  
  169. self::INDEX => ($this->tagType == self::T_END_SECTION) ? 
  170. $this->seenTag - strlen($this->otag) : 
  171. $i + strlen($this->ctag),  
  172. self::TRIM_LEFT => $this->trimLeft,  
  173. self::TRIM_RIGHT => $this->trimRight 
  174. ); 
  175. if (isset($args)) { 
  176. $t[self::ARGS] = $args; 
  177. $this->tokens[] = $t; 
  178. unset($t); 
  179. unset($args); 
  180. $this->buffer = ''; 
  181. $this->trimLeft = false; 
  182. $this->trimRight = false; 
  183. $i += strlen($this->ctag) - 1; 
  184. $this->state = self::IN_TEXT; 
  185. if ($this->tagType == self::T_UNESCAPED) { 
  186. if ($this->ctag == '}}') { 
  187. $i++; 
  188. } /** else { // I can't remember why this part is here! the ctag is always }} and 
  189. // Clean up `{{{ tripleStache }}}` style tokens. 
  190. $lastIndex = count($this->tokens) - 1; 
  191. $lastName = $this->tokens[$lastIndex][self::NAME]; 
  192. if (substr($lastName, -1) === '}') { 
  193. $this->tokens[$lastIndex][self::NAME] = trim( 
  194. substr($lastName, 0, -1) 
  195. ); 
  196. } */ 
  197. } else { 
  198. $this->buffer .= $text[$i]; 
  199. break; 
  200.  
  201. $this->escaped = ($this->escaping and !$this->escaped); 
  202.  
  203. $this->filterLine(true); 
  204.  
  205. return $this->tokens; 
  206.  
  207. /** 
  208. * Helper function to reset tokenizer internal state. 
  209. * @return void 
  210. */ 
  211. protected function reset() 
  212. $this->state = self::IN_TEXT; 
  213. $this->escaped = false; 
  214. $this->escaping = false; 
  215. $this->tagType = null; 
  216. $this->tag = null; 
  217. $this->buffer = ''; 
  218. $this->tokens = array(); 
  219. $this->seenTag = false; 
  220. $this->lineStart = 0; 
  221. $this->otag = '{{'; 
  222. $this->ctag = '}}'; 
  223. $this->trimLeft = false; 
  224. $this->trimRight = false; 
  225.  
  226. /** 
  227. * Flush the current buffer to a token. 
  228. * @return void 
  229. */ 
  230. protected function flushBuffer() 
  231. if ($this->buffer !== '') { 
  232. $this->tokens[] = array( 
  233. self::TYPE => self::T_TEXT,  
  234. self::VALUE => $this->buffer 
  235. ); 
  236. $this->buffer = ''; 
  237.  
  238. /** 
  239. * Test whether the current line is entirely made up of whitespace. 
  240. * @return boolean True if the current line is all whitespace 
  241. */ 
  242. protected function lineIsWhitespace() 
  243. $tokensCount = count($this->tokens); 
  244. for ($j = $this->lineStart; $j < $tokensCount; $j++) { 
  245. $token = $this->tokens[$j]; 
  246. if (isset(self::$_tagTypes[$token[self::TYPE]])) { 
  247. if (isset(self::$_interpolatedTags[$token[self::TYPE]])) { 
  248. return false; 
  249. } elseif ($token[self::TYPE] == self::T_TEXT) { 
  250. if (preg_match('/\S/', $token[self::VALUE])) { 
  251. return false; 
  252.  
  253. return true; 
  254.  
  255. /** 
  256. * Filter out whitespace-only lines and store indent levels for partials. 
  257. * @param bool $noNewLine Suppress the newline? (default: false) 
  258. * @return void 
  259. */ 
  260. protected function filterLine($noNewLine = false) 
  261. $this->flushBuffer(); 
  262. if ($this->seenTag && $this->lineIsWhitespace()) { 
  263. $tokensCount = count($this->tokens); 
  264. for ($j = $this->lineStart; $j < $tokensCount; $j++) { 
  265. if ($this->tokens[$j][self::TYPE] == self::T_TEXT) { 
  266. if (isset($this->tokens[$j + 1]) 
  267. && $this->tokens[$j + 1][self::TYPE] == self::T_PARTIAL 
  268. ) { 
  269. $this->tokens[$j + 1][self::INDENT] 
  270. = $this->tokens[$j][self::VALUE]; 
  271.  
  272. $this->tokens[$j] = null; 
  273. } elseif (!$noNewLine) { 
  274. $this->tokens[] = array(self::TYPE => self::T_TEXT, self::VALUE => "\n"); 
  275.  
  276. $this->seenTag = false; 
  277. $this->lineStart = count($this->tokens); 
  278.  
  279. /** 
  280. * Change the current Handlebars delimiters. Set new `otag` and `ctag` values. 
  281. * @param string $text Mustache template source 
  282. * @param int $index Current tokenizer index 
  283. * @return int New index value 
  284. */ 
  285. protected function changeDelimiters($text, $index) 
  286. $startIndex = strpos($text, '=', $index) + 1; 
  287. $close = '=' . $this->ctag; 
  288. $closeIndex = strpos($text, $close, $index); 
  289.  
  290. list($otag, $ctag) = explode( 
  291. ' ',  
  292. trim(substr($text, $startIndex, $closeIndex - $startIndex)) 
  293. ); 
  294. $this->otag = $otag; 
  295. $this->ctag = $ctag; 
  296.  
  297. return $closeIndex + strlen($close) - 1; 
  298.  
  299. /** 
  300. * Test whether it's time to change tags. 
  301. * @param string $tag Current tag name 
  302. * @param string $text Handlebars template source 
  303. * @param int $index Current tokenizer index 
  304. * @return boolean True if this is a closing section tag 
  305. */ 
  306. protected function tagChange($tag, $text, $index) 
  307. return substr($text, $index, strlen($tag)) === $tag; 
  308.