PluginProbe
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor / 3.16.3
User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor v3.16.3
4.0.3 4.0.2 4.0.1 4.0.0 3.16.6 3.16.5 3.16.4 3.16.3 3.16.2 3.16.1 3.16.0 3.15.9 3.9.9 3.9.5 3.9.6 3.9.7 3.9.8 1.1.7 1.1.8 1.1.9 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 341 releases
profile-builder / assets / lib / Mustache / Tokenizer.php

Tokenizer.php in User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor 3.16.3, at assets/lib/Mustache/Tokenizer.php

379 lines 12.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of Mustache.php.
5 *
6 * (c) 2010-2017 Justin Hileman
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 /**
13 * Mustache Tokenizer class.
14 *
15 * This class is responsible for turning raw template source into a set of Mustache tokens.
16 */
17 class Mustache_Tokenizer
18 {
19 // Finite state machine states
20 const IN_TEXT = 0;
21 const IN_TAG_TYPE = 1;
22 const IN_TAG = 2;
23
24 // Token types
25 const T_SECTION = '#';
26 const T_INVERTED = '^';
27 const T_END_SECTION = '/';
28 const T_COMMENT = '!';
29 const T_PARTIAL = '>';
30 const T_PARENT = '<';
31 const T_DELIM_CHANGE = '=';
32 const T_ESCAPED = '_v';
33 const T_UNESCAPED = '{';
34 const T_UNESCAPED_2 = '&';
35 const T_TEXT = '_t';
36 const T_PRAGMA = '%';
37 const T_BLOCK_VAR = '$';
38 const T_BLOCK_ARG = '$arg';
39
40 // Valid token types
41 private static $tagTypes = array(
42 self::T_SECTION => true,
43 self::T_INVERTED => true,
44 self::T_END_SECTION => true,
45 self::T_COMMENT => true,
46 self::T_PARTIAL => true,
47 self::T_PARENT => true,
48 self::T_DELIM_CHANGE => true,
49 self::T_ESCAPED => true,
50 self::T_UNESCAPED => true,
51 self::T_UNESCAPED_2 => true,
52 self::T_PRAGMA => true,
53 self::T_BLOCK_VAR => true,
54 );
55
56 // Token properties
57 const TYPE = 'type';
58 const NAME = 'name';
59 const OTAG = 'otag';
60 const CTAG = 'ctag';
61 const LINE = 'line';
62 const INDEX = 'index';
63 const END = 'end';
64 const INDENT = 'indent';
65 const NODES = 'nodes';
66 const VALUE = 'value';
67 const FILTERS = 'filters';
68
69 private $state;
70 private $tagType;
71 private $buffer;
72 private $tokens;
73 private $seenTag;
74 private $line;
75
76 private $otag;
77 private $otagChar;
78 private $otagLen;
79
80 private $ctag;
81 private $ctagChar;
82 private $ctagLen;
83
84 /**
85 * Scan and tokenize template source.
86 *
87 * @throws Mustache_Exception_SyntaxException when mismatched section tags are encountered
88 * @throws Mustache_Exception_InvalidArgumentException when $delimiters string is invalid
89 *
90 * @param string $text Mustache template source to tokenize
91 * @param string $delimiters Optionally, pass initial opening and closing delimiters (default: empty string)
92 *
93 * @return array Set of Mustache tokens
94 */
95 public function scan($text, $delimiters = '')
96 {
97 // Setting mbstring.func_overload makes things *really* slow.
98 // Let's do everyone a favor and scan this string as ASCII instead.
99 //
100 // The INI directive was removed in PHP 8.0 so we don't need to check there (and can drop it
101 // when we remove support for older versions of PHP).
102 //
103 // @codeCoverageIgnoreStart
104 $encoding = null;
105 if (version_compare(PHP_VERSION, '8.0.0', '<')) {
106 if (function_exists('mb_internal_encoding') && ini_get('mbstring.func_overload') & 2) {
107 $encoding = mb_internal_encoding();
108 mb_internal_encoding('ASCII');
109 }
110 }
111 // @codeCoverageIgnoreEnd
112
113 $this->reset();
114
115 if (is_string($delimiters) && $delimiters = trim($delimiters)) {
116 $this->setDelimiters($delimiters);
117 }
118
119 $len = strlen($text);
120 for ($i = 0; $i < $len; $i++) {
121 switch ($this->state) {
122 case self::IN_TEXT:
123 $char = $text[$i];
124 // Test whether it's time to change tags.
125 if ($char === $this->otagChar && substr($text, $i, $this->otagLen) === $this->otag) {
126 $i--;
127 $this->flushBuffer();
128 $this->state = self::IN_TAG_TYPE;
129 } else {
130 $this->buffer .= $char;
131 if ($char === "\n") {
132 $this->flushBuffer();
133 $this->line++;
134 }
135 }
136 break;
137
138 case self::IN_TAG_TYPE:
139 $i += $this->otagLen - 1;
140 $char = $text[$i + 1];
141 if (isset(self::$tagTypes[$char])) {
142 $tag = $char;
143 $this->tagType = $tag;
144 } else {
145 $tag = null;
146 $this->tagType = self::T_ESCAPED;
147 }
148
149 if ($this->tagType === self::T_DELIM_CHANGE) {
150 $i = $this->changeDelimiters($text, $i);
151 $this->state = self::IN_TEXT;
152 } elseif ($this->tagType === self::T_PRAGMA) {
153 $i = $this->addPragma($text, $i);
154 $this->state = self::IN_TEXT;
155 } else {
156 if ($tag !== null) {
157 $i++;
158 }
159 $this->state = self::IN_TAG;
160 }
161 $this->seenTag = $i;
162 break;
163
164 default:
165 $char = $text[$i];
166 // Test whether it's time to change tags.
167 if ($char === $this->ctagChar && substr($text, $i, $this->ctagLen) === $this->ctag) {
168 $token = array(
169 self::TYPE => $this->tagType,
170 self::NAME => trim($this->buffer),
171 self::OTAG => $this->otag,
172 self::CTAG => $this->ctag,
173 self::LINE => $this->line,
174 self::INDEX => ($this->tagType === self::T_END_SECTION) ? $this->seenTag - $this->otagLen : $i + $this->ctagLen,
175 );
176
177 if ($this->tagType === self::T_UNESCAPED) {
178 // Clean up `{{{ tripleStache }}}` style tokens.
179 if ($this->ctag === '}}') {
180 if (($i + 2 < $len) && $text[$i + 2] === '}') {
181 $i++;
182 } else {
183 $msg = sprintf(
184 'Mismatched tag delimiters: %s on line %d',
185 $token[self::NAME],
186 $token[self::LINE]
187 );
188
189 throw new Mustache_Exception_SyntaxException($msg, $token);
190 }
191 } else {
192 $lastName = $token[self::NAME];
193 if (substr($lastName, -1) === '}') {
194 $token[self::NAME] = trim(substr($lastName, 0, -1));
195 } else {
196 $msg = sprintf(
197 'Mismatched tag delimiters: %s on line %d',
198 $token[self::NAME],
199 $token[self::LINE]
200 );
201
202 throw new Mustache_Exception_SyntaxException($msg, $token);
203 }
204 }
205 }
206
207 $this->buffer = '';
208 $i += $this->ctagLen - 1;
209 $this->state = self::IN_TEXT;
210 $this->tokens[] = $token;
211 } else {
212 $this->buffer .= $char;
213 }
214 break;
215 }
216 }
217
218 if ($this->state !== self::IN_TEXT) {
219 $this->throwUnclosedTagException();
220 }
221
222 $this->flushBuffer();
223
224 // Restore the user's encoding...
225 // @codeCoverageIgnoreStart
226 if ($encoding) {
227 mb_internal_encoding($encoding);
228 }
229 // @codeCoverageIgnoreEnd
230
231 return $this->tokens;
232 }
233
234 /**
235 * Helper function to reset tokenizer internal state.
236 */
237 private function reset()
238 {
239 $this->state = self::IN_TEXT;
240 $this->tagType = null;
241 $this->buffer = '';
242 $this->tokens = array();
243 $this->seenTag = false;
244 $this->line = 0;
245
246 $this->otag = '{{';
247 $this->otagChar = '{';
248 $this->otagLen = 2;
249
250 $this->ctag = '}}';
251 $this->ctagChar = '}';
252 $this->ctagLen = 2;
253 }
254
255 /**
256 * Flush the current buffer to a token.
257 */
258 private function flushBuffer()
259 {
260 if (strlen($this->buffer) > 0) {
261 $this->tokens[] = array(
262 self::TYPE => self::T_TEXT,
263 self::LINE => $this->line,
264 self::VALUE => $this->buffer,
265 );
266 $this->buffer = '';
267 }
268 }
269
270 /**
271 * Change the current Mustache delimiters. Set new `otag` and `ctag` values.
272 *
273 * @throws Mustache_Exception_SyntaxException when delimiter string is invalid
274 *
275 * @param string $text Mustache template source
276 * @param int $index Current tokenizer index
277 *
278 * @return int New index value
279 */
280 private function changeDelimiters($text, $index)
281 {
282 $startIndex = strpos($text, '=', $index) + 1;
283 $close = '=' . $this->ctag;
284 $closeIndex = strpos($text, $close, $index);
285
286 if ($closeIndex === false) {
287 $this->throwUnclosedTagException();
288 }
289
290 $token = array(
291 self::TYPE => self::T_DELIM_CHANGE,
292 self::LINE => $this->line,
293 );
294
295 try {
296 $this->setDelimiters(trim(substr($text, $startIndex, $closeIndex - $startIndex)));
297 } catch (Mustache_Exception_InvalidArgumentException $e) {
298 throw new Mustache_Exception_SyntaxException($e->getMessage(), $token);
299 }
300
301 $this->tokens[] = $token;
302
303 return $closeIndex + strlen($close) - 1;
304 }
305
306 /**
307 * Set the current Mustache `otag` and `ctag` delimiters.
308 *
309 * @throws Mustache_Exception_InvalidArgumentException when delimiter string is invalid
310 *
311 * @param string $delimiters
312 */
313 private function setDelimiters($delimiters)
314 {
315 if (!preg_match('/^\s*(\S+)\s+(\S+)\s*$/', $delimiters, $matches)) {
316 throw new Mustache_Exception_InvalidArgumentException(sprintf('Invalid delimiters: %s', $delimiters));
317 }
318
319 list($_, $otag, $ctag) = $matches;
320
321 $this->otag = $otag;
322 $this->otagChar = $otag[0];
323 $this->otagLen = strlen($otag);
324
325 $this->ctag = $ctag;
326 $this->ctagChar = $ctag[0];
327 $this->ctagLen = strlen($ctag);
328 }
329
330 /**
331 * Add pragma token.
332 *
333 * Pragmas are hoisted to the front of the template, so all pragma tokens
334 * will appear at the front of the token list.
335 *
336 * @param string $text
337 * @param int $index
338 *
339 * @return int New index value
340 */
341 private function addPragma($text, $index)
342 {
343 $end = strpos($text, $this->ctag, $index);
344 if ($end === false) {
345 $this->throwUnclosedTagException();
346 }
347
348 $pragma = trim(substr($text, $index + 2, $end - $index - 2));
349
350 // Pragmas are hoisted to the front of the template.
351 array_unshift($this->tokens, array(
352 self::TYPE => self::T_PRAGMA,
353 self::NAME => $pragma,
354 self::LINE => 0,
355 ));
356
357 return $end + $this->ctagLen - 1;
358 }
359
360 private function throwUnclosedTagException()
361 {
362 $name = trim($this->buffer);
363 if ($name !== '') {
364 $msg = sprintf('Unclosed tag: %s on line %d', $name, $this->line);
365 } else {
366 $msg = sprintf('Unclosed tag on line %d', $this->line);
367 }
368
369 throw new Mustache_Exception_SyntaxException($msg, array(
370 self::TYPE => $this->tagType,
371 self::NAME => $name,
372 self::OTAG => $this->otag,
373 self::CTAG => $this->ctag,
374 self::LINE => $this->line,
375 self::INDEX => $this->seenTag - $this->otagLen,
376 ));
377 }
378 }
379