[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 3 /* 4 * This file is part of Mustache.php. 5 * 6 * (c) 2010-2015 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 Compiler class. 14 * 15 * This class is responsible for turning a Mustache token parse tree into normal PHP source code. 16 */ 17 class Mustache_Compiler 18 { 19 private $pragmas; 20 private $defaultPragmas = array(); 21 private $sections; 22 private $blocks; 23 private $source; 24 private $indentNextLine; 25 private $customEscape; 26 private $entityFlags; 27 private $charset; 28 private $strictCallables; 29 30 /** 31 * Compile a Mustache token parse tree into PHP source code. 32 * 33 * @param string $source Mustache Template source code 34 * @param string $tree Parse tree of Mustache tokens 35 * @param string $name Mustache Template class name 36 * @param bool $customEscape (default: false) 37 * @param string $charset (default: 'UTF-8') 38 * @param bool $strictCallables (default: false) 39 * @param int $entityFlags (default: ENT_COMPAT) 40 * 41 * @return string Generated PHP source code 42 */ 43 public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8', $strictCallables = false, $entityFlags = ENT_COMPAT) 44 { 45 $this->pragmas = $this->defaultPragmas; 46 $this->sections = array(); 47 $this->blocks = array(); 48 $this->source = $source; 49 $this->indentNextLine = true; 50 $this->customEscape = $customEscape; 51 $this->entityFlags = $entityFlags; 52 $this->charset = $charset; 53 $this->strictCallables = $strictCallables; 54 55 return $this->writeCode($tree, $name); 56 } 57 58 /** 59 * Enable pragmas across all templates, regardless of the presence of pragma 60 * tags in the individual templates. 61 * 62 * @internal Users should set global pragmas in Mustache_Engine, not here :) 63 * 64 * @param string[] $pragmas 65 */ 66 public function setPragmas(array $pragmas) 67 { 68 $this->pragmas = array(); 69 foreach ($pragmas as $pragma) { 70 $this->pragmas[$pragma] = true; 71 } 72 $this->defaultPragmas = $this->pragmas; 73 } 74 75 /** 76 * Helper function for walking the Mustache token parse tree. 77 * 78 * @throws Mustache_Exception_SyntaxException upon encountering unknown token types. 79 * 80 * @param array $tree Parse tree of Mustache tokens 81 * @param int $level (default: 0) 82 * 83 * @return string Generated PHP source code 84 */ 85 private function walk(array $tree, $level = 0) 86 { 87 $code = ''; 88 $level++; 89 foreach ($tree as $node) { 90 switch ($node[Mustache_Tokenizer::TYPE]) { 91 case Mustache_Tokenizer::T_PRAGMA: 92 $this->pragmas[$node[Mustache_Tokenizer::NAME]] = true; 93 break; 94 95 case Mustache_Tokenizer::T_SECTION: 96 $code .= $this->section( 97 $node[Mustache_Tokenizer::NODES], 98 $node[Mustache_Tokenizer::NAME], 99 isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(), 100 $node[Mustache_Tokenizer::INDEX], 101 $node[Mustache_Tokenizer::END], 102 $node[Mustache_Tokenizer::OTAG], 103 $node[Mustache_Tokenizer::CTAG], 104 $level 105 ); 106 break; 107 108 case Mustache_Tokenizer::T_INVERTED: 109 $code .= $this->invertedSection( 110 $node[Mustache_Tokenizer::NODES], 111 $node[Mustache_Tokenizer::NAME], 112 isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(), 113 $level 114 ); 115 break; 116 117 case Mustache_Tokenizer::T_PARTIAL: 118 $code .= $this->partial( 119 $node[Mustache_Tokenizer::NAME], 120 isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '', 121 $level 122 ); 123 break; 124 125 case Mustache_Tokenizer::T_PARENT: 126 $code .= $this->parent( 127 $node[Mustache_Tokenizer::NAME], 128 isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '', 129 $node[Mustache_Tokenizer::NODES], 130 $level 131 ); 132 break; 133 134 case Mustache_Tokenizer::T_BLOCK_ARG: 135 $code .= $this->blockArg( 136 $node[Mustache_Tokenizer::NODES], 137 $node[Mustache_Tokenizer::NAME], 138 $node[Mustache_Tokenizer::INDEX], 139 $node[Mustache_Tokenizer::END], 140 $node[Mustache_Tokenizer::OTAG], 141 $node[Mustache_Tokenizer::CTAG], 142 $level 143 ); 144 break; 145 146 case Mustache_Tokenizer::T_BLOCK_VAR: 147 $code .= $this->blockVar( 148 $node[Mustache_Tokenizer::NODES], 149 $node[Mustache_Tokenizer::NAME], 150 $node[Mustache_Tokenizer::INDEX], 151 $node[Mustache_Tokenizer::END], 152 $node[Mustache_Tokenizer::OTAG], 153 $node[Mustache_Tokenizer::CTAG], 154 $level 155 ); 156 break; 157 158 case Mustache_Tokenizer::T_COMMENT: 159 break; 160 161 case Mustache_Tokenizer::T_ESCAPED: 162 case Mustache_Tokenizer::T_UNESCAPED: 163 case Mustache_Tokenizer::T_UNESCAPED_2: 164 $code .= $this->variable( 165 $node[Mustache_Tokenizer::NAME], 166 isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(), 167 $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_ESCAPED, 168 $level 169 ); 170 break; 171 172 case Mustache_Tokenizer::T_TEXT: 173 $code .= $this->text($node[Mustache_Tokenizer::VALUE], $level); 174 break; 175 176 default: 177 throw new Mustache_Exception_SyntaxException(sprintf('Unknown token type: %s', $node[Mustache_Tokenizer::TYPE]), $node); 178 } 179 } 180 181 return $code; 182 } 183 184 const KLASS = '<?php 185 186 class %s extends Mustache_Template 187 { 188 private $lambdaHelper;%s 189 190 public function renderInternal(Mustache_Context $context, $indent = \'\') 191 { 192 $this->lambdaHelper = new Mustache_LambdaHelper($this->mustache, $context); 193 $buffer = \'\'; 194 $newContext = array(); 195 %s 196 197 return $buffer; 198 } 199 %s 200 %s 201 }'; 202 203 const KLASS_NO_LAMBDAS = '<?php 204 205 class %s extends Mustache_Template 206 {%s 207 public function renderInternal(Mustache_Context $context, $indent = \'\') 208 { 209 $buffer = \'\'; 210 $newContext = array(); 211 %s 212 213 return $buffer; 214 } 215 }'; 216 217 const STRICT_CALLABLE = 'protected $strictCallables = true;'; 218 219 /** 220 * Generate Mustache Template class PHP source. 221 * 222 * @param array $tree Parse tree of Mustache tokens 223 * @param string $name Mustache Template class name 224 * 225 * @return string Generated PHP source code 226 */ 227 private function writeCode($tree, $name) 228 { 229 $code = $this->walk($tree); 230 $sections = implode("\n", $this->sections); 231 $blocks = implode("\n", $this->blocks); 232 $klass = empty($this->sections) && empty($this->blocks) ? self::KLASS_NO_LAMBDAS : self::KLASS; 233 234 $callable = $this->strictCallables ? $this->prepare(self::STRICT_CALLABLE) : ''; 235 236 return sprintf($this->prepare($klass, 0, false, true), $name, $callable, $code, $sections, $blocks); 237 } 238 239 const BLOCK_VAR = ' 240 $blockFunction = $context->findInBlock(%s); 241 if (is_callable($blockFunction)) { 242 $buffer .= call_user_func($blockFunction, $context); 243 } else {%s 244 } 245 '; 246 247 /** 248 * Generate Mustache Template inheritance block variable PHP source. 249 * 250 * @param array $nodes Array of child tokens 251 * @param string $id Section name 252 * @param int $start Section start offset 253 * @param int $end Section end offset 254 * @param string $otag Current Mustache opening tag 255 * @param string $ctag Current Mustache closing tag 256 * @param int $level 257 * 258 * @return string Generated PHP source code 259 */ 260 private function blockVar($nodes, $id, $start, $end, $otag, $ctag, $level) 261 { 262 $id = var_export($id, true); 263 264 return sprintf($this->prepare(self::BLOCK_VAR, $level), $id, $this->walk($nodes, $level)); 265 } 266 267 const BLOCK_ARG = '$newContext[%s] = array($this, \'block%s\');'; 268 269 /** 270 * Generate Mustache Template inheritance block argument PHP source. 271 * 272 * @param array $nodes Array of child tokens 273 * @param string $id Section name 274 * @param int $start Section start offset 275 * @param int $end Section end offset 276 * @param string $otag Current Mustache opening tag 277 * @param string $ctag Current Mustache closing tag 278 * @param int $level 279 * 280 * @return string Generated PHP source code 281 */ 282 private function blockArg($nodes, $id, $start, $end, $otag, $ctag, $level) 283 { 284 $key = $this->block($nodes); 285 $keystr = var_export($key, true); 286 $id = var_export($id, true); 287 288 return sprintf($this->prepare(self::BLOCK_ARG, 1), $id, $key); 289 } 290 291 const BLOCK_FUNCTION = ' 292 public function block%s($context) 293 { 294 $indent = $buffer = \'\';%s 295 296 return $buffer; 297 } 298 '; 299 300 /** 301 * Generate Mustache Template inheritance block function PHP source. 302 * 303 * @param array $nodes Array of child tokens 304 * 305 * @return string key of new block function 306 */ 307 private function block($nodes) 308 { 309 $code = $this->walk($nodes, 0); 310 $key = ucfirst(md5($code)); 311 312 if (!isset($this->blocks[$key])) { 313 $this->blocks[$key] = sprintf($this->prepare(self::BLOCK_FUNCTION, 0), $key, $code); 314 } 315 316 return $key; 317 } 318 319 const SECTION_CALL = ' 320 // %s section 321 $value = $context->%s(%s);%s 322 $buffer .= $this->section%s($context, $indent, $value); 323 '; 324 325 const SECTION = ' 326 private function section%s(Mustache_Context $context, $indent, $value) 327 { 328 $buffer = \'\'; 329 if (%s) { 330 $source = %s; 331 $result = call_user_func($value, $source, %s); 332 if (strpos($result, \'{{\') === false) { 333 $buffer .= $result; 334 } else { 335 $buffer .= $this->mustache 336 ->loadLambda((string) $result%s) 337 ->renderInternal($context); 338 } 339 } elseif (!empty($value)) { 340 $values = $this->isIterable($value) ? $value : array($value); 341 foreach ($values as $value) { 342 $context->push($value); 343 %s 344 $context->pop(); 345 } 346 } 347 348 return $buffer; 349 } 350 '; 351 352 /** 353 * Generate Mustache Template section PHP source. 354 * 355 * @param array $nodes Array of child tokens 356 * @param string $id Section name 357 * @param string[] $filters Array of filters 358 * @param int $start Section start offset 359 * @param int $end Section end offset 360 * @param string $otag Current Mustache opening tag 361 * @param string $ctag Current Mustache closing tag 362 * @param int $level 363 * @param bool $arg (default: false) 364 * 365 * @return string Generated section PHP source code 366 */ 367 private function section($nodes, $id, $filters, $start, $end, $otag, $ctag, $level, $arg = false) 368 { 369 $source = var_export(substr($this->source, $start, $end - $start), true); 370 $callable = $this->getCallable(); 371 372 if ($otag !== '{{' || $ctag !== '}}') { 373 $delimTag = var_export(sprintf('{{= %s %s =}}', $otag, $ctag), true); 374 $helper = sprintf('$this->lambdaHelper->withDelimiters(%s)', $delimTag); 375 $delims = ', ' . $delimTag; 376 } else { 377 $helper = '$this->lambdaHelper'; 378 $delims = ''; 379 } 380 381 $key = ucfirst(md5($delims . "\n" . $source)); 382 383 if (!isset($this->sections[$key])) { 384 $this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $callable, $source, $helper, $delims, $this->walk($nodes, 2)); 385 } 386 387 if ($arg === true) { 388 return $key; 389 } else { 390 $method = $this->getFindMethod($id); 391 $id = var_export($id, true); 392 $filters = $this->getFilters($filters, $level); 393 394 return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $method, $id, $filters, $key); 395 } 396 } 397 398 const INVERTED_SECTION = ' 399 // %s inverted section 400 $value = $context->%s(%s);%s 401 if (empty($value)) { 402 %s 403 } 404 '; 405 406 /** 407 * Generate Mustache Template inverted section PHP source. 408 * 409 * @param array $nodes Array of child tokens 410 * @param string $id Section name 411 * @param string[] $filters Array of filters 412 * @param int $level 413 * 414 * @return string Generated inverted section PHP source code 415 */ 416 private function invertedSection($nodes, $id, $filters, $level) 417 { 418 $method = $this->getFindMethod($id); 419 $id = var_export($id, true); 420 $filters = $this->getFilters($filters, $level); 421 422 return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $filters, $this->walk($nodes, $level)); 423 } 424 425 const PARTIAL_INDENT = ', $indent . %s'; 426 const PARTIAL = ' 427 if ($partial = $this->mustache->loadPartial(%s)) { 428 $buffer .= $partial->renderInternal($context%s); 429 } 430 '; 431 432 /** 433 * Generate Mustache Template partial call PHP source. 434 * 435 * @param string $id Partial name 436 * @param string $indent Whitespace indent to apply to partial 437 * @param int $level 438 * 439 * @return string Generated partial call PHP source code 440 */ 441 private function partial($id, $indent, $level) 442 { 443 if ($indent !== '') { 444 $indentParam = sprintf(self::PARTIAL_INDENT, var_export($indent, true)); 445 } else { 446 $indentParam = ''; 447 } 448 449 return sprintf( 450 $this->prepare(self::PARTIAL, $level), 451 var_export($id, true), 452 $indentParam 453 ); 454 } 455 456 const PARENT = ' 457 %s 458 459 if ($parent = $this->mustache->LoadPartial(%s)) { 460 $context->pushBlockContext($newContext); 461 $buffer .= $parent->renderInternal($context, $indent); 462 $context->popBlockContext(); 463 } 464 '; 465 466 /** 467 * Generate Mustache Template inheritance parent call PHP source. 468 * 469 * @param string $id Parent tag name 470 * @param string $indent Whitespace indent to apply to parent 471 * @param array $children Child nodes 472 * @param int $level 473 * 474 * @return string Generated PHP source code 475 */ 476 private function parent($id, $indent, array $children, $level) 477 { 478 $realChildren = array_filter($children, array(__CLASS__, 'onlyBlockArgs')); 479 480 return sprintf( 481 $this->prepare(self::PARENT, $level), 482 $this->walk($realChildren, $level), 483 var_export($id, true), 484 var_export($indent, true) 485 ); 486 } 487 488 /** 489 * Helper method for filtering out non-block-arg tokens. 490 * 491 * @param array $node 492 * 493 * @return bool True if $node is a block arg token. 494 */ 495 private static function onlyBlockArgs(array $node) 496 { 497 return $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_BLOCK_ARG; 498 } 499 500 const VARIABLE = ' 501 $value = $this->resolveValue($context->%s(%s), $context);%s 502 $buffer .= %s%s; 503 '; 504 505 /** 506 * Generate Mustache Template variable interpolation PHP source. 507 * 508 * @param string $id Variable name 509 * @param string[] $filters Array of filters 510 * @param bool $escape Escape the variable value for output? 511 * @param int $level 512 * 513 * @return string Generated variable interpolation PHP source 514 */ 515 private function variable($id, $filters, $escape, $level) 516 { 517 $method = $this->getFindMethod($id); 518 $id = ($method !== 'last') ? var_export($id, true) : ''; 519 $filters = $this->getFilters($filters, $level); 520 $value = $escape ? $this->getEscape() : '$value'; 521 522 return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $filters, $this->flushIndent(), $value); 523 } 524 525 const FILTER = ' 526 $filter = $context->%s(%s); 527 if (!(%s)) { 528 throw new Mustache_Exception_UnknownFilterException(%s); 529 } 530 $value = call_user_func($filter, $value);%s 531 '; 532 533 /** 534 * Generate Mustache Template variable filtering PHP source. 535 * 536 * @param string[] $filters Array of filters 537 * @param int $level 538 * 539 * @return string Generated filter PHP source 540 */ 541 private function getFilters(array $filters, $level) 542 { 543 if (empty($filters)) { 544 return ''; 545 } 546 547 $name = array_shift($filters); 548 $method = $this->getFindMethod($name); 549 $filter = ($method !== 'last') ? var_export($name, true) : ''; 550 $callable = $this->getCallable('$filter'); 551 $msg = var_export($name, true); 552 553 return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $callable, $msg, $this->getFilters($filters, $level)); 554 } 555 556 const LINE = '$buffer .= "\n";'; 557 const TEXT = '$buffer .= %s%s;'; 558 559 /** 560 * Generate Mustache Template output Buffer call PHP source. 561 * 562 * @param string $text 563 * @param int $level 564 * 565 * @return string Generated output Buffer call PHP source 566 */ 567 private function text($text, $level) 568 { 569 $indentNextLine = (substr($text, -1) === "\n"); 570 $code = sprintf($this->prepare(self::TEXT, $level), $this->flushIndent(), var_export($text, true)); 571 $this->indentNextLine = $indentNextLine; 572 573 return $code; 574 } 575 576 /** 577 * Prepare PHP source code snippet for output. 578 * 579 * @param string $text 580 * @param int $bonus Additional indent level (default: 0) 581 * @param bool $prependNewline Prepend a newline to the snippet? (default: true) 582 * @param bool $appendNewline Append a newline to the snippet? (default: false) 583 * 584 * @return string PHP source code snippet 585 */ 586 private function prepare($text, $bonus = 0, $prependNewline = true, $appendNewline = false) 587 { 588 $text = ($prependNewline ? "\n" : '') . trim($text); 589 if ($prependNewline) { 590 $bonus++; 591 } 592 if ($appendNewline) { 593 $text .= "\n"; 594 } 595 596 return preg_replace("/\n( {8})?/", "\n" . str_repeat(' ', $bonus * 4), $text); 597 } 598 599 const DEFAULT_ESCAPE = 'htmlspecialchars(%s, %s, %s)'; 600 const CUSTOM_ESCAPE = 'call_user_func($this->mustache->getEscape(), %s)'; 601 602 /** 603 * Get the current escaper. 604 * 605 * @param string $value (default: '$value') 606 * 607 * @return string Either a custom callback, or an inline call to `htmlspecialchars` 608 */ 609 private function getEscape($value = '$value') 610 { 611 if ($this->customEscape) { 612 return sprintf(self::CUSTOM_ESCAPE, $value); 613 } 614 615 return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->entityFlags, true), var_export($this->charset, true)); 616 } 617 618 /** 619 * Select the appropriate Context `find` method for a given $id. 620 * 621 * The return value will be one of `find`, `findDot` or `last`. 622 * 623 * @see Mustache_Context::find 624 * @see Mustache_Context::findDot 625 * @see Mustache_Context::last 626 * 627 * @param string $id Variable name 628 * 629 * @return string `find` method name 630 */ 631 private function getFindMethod($id) 632 { 633 if ($id === '.') { 634 return 'last'; 635 } 636 637 if (isset($this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) && $this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) { 638 if (substr($id, 0, 1) === '.') { 639 return 'findAnchoredDot'; 640 } 641 } 642 643 if (strpos($id, '.') === false) { 644 return 'find'; 645 } 646 647 return 'findDot'; 648 } 649 650 const IS_CALLABLE = '!is_string(%s) && is_callable(%s)'; 651 const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)'; 652 653 /** 654 * Helper function to compile strict vs lax "is callable" logic. 655 * 656 * @param string $variable (default: '$value') 657 * 658 * @return string "is callable" logic 659 */ 660 private function getCallable($variable = '$value') 661 { 662 $tpl = $this->strictCallables ? self::STRICT_IS_CALLABLE : self::IS_CALLABLE; 663 664 return sprintf($tpl, $variable, $variable); 665 } 666 667 const LINE_INDENT = '$indent . '; 668 669 /** 670 * Get the current $indent prefix to write to the buffer. 671 * 672 * @return string "$indent . " or "" 673 */ 674 private function flushIndent() 675 { 676 if (!$this->indentNextLine) { 677 return ''; 678 } 679 680 $this->indentNextLine = false; 681 682 return self::LINE_INDENT; 683 } 684 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |