1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13:
14:
15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47:
48: class NLatteMacros extends NObject
49: {
50:
51: public static $defaultMacros = array(
52: 'syntax' => '%:macroSyntax%',
53: '/syntax' => '%:macroSyntax%',
54:
55: 'block' => '<?php %:macroBlock% ?>',
56: '/block' => '<?php %:macroBlockEnd% ?>',
57:
58: 'capture' => '<?php %:macroCapture% ?>',
59: '/capture' => '<?php %:macroCaptureEnd% ?>',
60:
61: 'snippet' => '<?php %:macroSnippet% ?>',
62: '/snippet' => '<?php %:macroSnippetEnd% ?>',
63:
64: 'cache' => '<?php %:macroCache% ?>',
65: '/cache' => '<?php array_pop($_l->g->caches)->save(); } ?>',
66:
67: 'if' => '<?php if (%%): ?>',
68: 'elseif' => '<?php elseif (%%): ?>',
69: 'else' => '<?php else: ?>',
70: '/if' => '<?php endif ?>',
71: 'ifset' => '<?php if (isset(%%)): ?>',
72: '/ifset' => '<?php endif ?>',
73: 'elseifset' => '<?php elseif (isset(%%)): ?>',
74: 'foreach' => '<?php foreach (%:macroForeach%): ?>',
75: '/foreach' => '<?php endforeach; array_pop($_l->its); $iterator = end($_l->its) ?>',
76: 'for' => '<?php for (%%): ?>',
77: '/for' => '<?php endfor ?>',
78: 'while' => '<?php while (%%): ?>',
79: '/while' => '<?php endwhile ?>',
80: 'continueIf' => '<?php if (%%) continue ?>',
81: 'breakIf' => '<?php if (%%) break ?>',
82: 'first' => '<?php if ($iterator->isFirst(%%)): ?>',
83: '/first' => '<?php endif ?>',
84: 'last' => '<?php if ($iterator->isLast(%%)): ?>',
85: '/last' => '<?php endif ?>',
86: 'sep' => '<?php if (!$iterator->isLast(%%)): ?>',
87: '/sep' => '<?php endif ?>',
88:
89: 'include' => '<?php %:macroInclude% ?>',
90: 'extends' => '<?php %:macroExtends% ?>',
91: 'layout' => '<?php %:macroExtends% ?>',
92:
93: 'plink' => '<?php echo %:macroEscape%(%:macroPlink%) ?>',
94: 'link' => '<?php echo %:macroEscape%(%:macroLink%) ?>',
95: 'ifCurrent' => '<?php %:macroIfCurrent% ?>',
96: 'widget' => '<?php %:macroControl% ?>',
97: 'control' => '<?php %:macroControl% ?>',
98:
99: 'attr' => '<?php echo NHtml::el(NULL)->%:macroAttr%attributes() ?>',
100: 'contentType' => '<?php %:macroContentType% ?>',
101: 'status' => '<?php NEnvironment::getHttpResponse()->setCode(%%) ?>',
102: 'var' => '<?php %:macroVar% ?>',
103: 'assign' => '<?php %:macroVar% ?>', 104: 'default' => '<?php %:macroDefault% ?>',
105: 'dump' => '<?php %:macroDump% ?>',
106: 'debugbreak' => '<?php %:macroDebugbreak% ?>',
107: 'l' => '{',
108: 'r' => '}',
109:
110: '!_' => '<?php echo %:macroTranslate% ?>',
111: '_' => '<?php echo %:macroEscape%(%:macroTranslate%) ?>',
112: '!=' => '<?php echo %:macroModifiers% ?>',
113: '=' => '<?php echo %:macroEscape%(%:macroModifiers%) ?>',
114: '!$' => '<?php echo %:macroDollar% ?>',
115: '$' => '<?php echo %:macroEscape%(%:macroDollar%) ?>',
116: '?' => '<?php %:macroModifiers% ?>',
117: );
118:
119:
120: const RE_IDENTIFIER = '[_a-zA-Z\x7F-\xFF][_a-zA-Z0-9\x7F-\xFF]*';
121:
122:
123: const T_SYMBOL = -1;
124:
125:
126: public $macros;
127:
128:
129: private $filter;
130:
131:
132: private $blocks = array();
133:
134:
135: private $namedBlocks = array();
136:
137:
138: private $extends;
139:
140:
141: private $uniq;
142:
143:
144: private $cacheCounter;
145:
146:
147: private $oldSnippetMode = TRUE;
148:
149:
150: const BLOCK_NAMED = 1;
151: const BLOCK_CAPTURE = 2;
152: const BLOCK_ANONYMOUS = 3;
153:
154:
155:
156:
157: 158: 159:
160: public function __construct()
161: {
162: $this->macros = self::$defaultMacros;
163: }
164:
165:
166:
167: 168: 169: 170: 171: 172:
173: public function initialize($filter, & $s)
174: {
175: $this->filter = $filter;
176: $this->blocks = array();
177: $this->namedBlocks = array();
178: $this->extends = NULL;
179: $this->uniq = substr(md5(uniqid('', TRUE)), 0, 10);
180: $this->cacheCounter = 0;
181:
182: $filter->context = NLatteFilter::CONTEXT_TEXT;
183: $filter->escape = 'NTemplateHelpers::escapeHtml';
184:
185: 186: $s = NString::replace($s, '#\\{\\*.*?\\*\\}[\r\n]*#s', '');
187:
188: 189: $s = NString::replace(
190: $s,
191: '#@(\\{[^}]+?\\})#s',
192: '<?php } ?>$1<?php if (NSnippetHelper::$outputAllowed) { ?>'
193: );
194: }
195:
196:
197:
198: 199: 200: 201: 202:
203: public function finalize(& $s)
204: {
205: 206: if (count($this->blocks) === 1) { 207: $s .= $this->macro('/block', '', '');
208:
209: } elseif ($this->blocks) {
210: throw new InvalidStateException("There are unclosed blocks.");
211: }
212:
213: 214: $s = "<?php\nif (" . 'NSnippetHelper::$outputAllowed' . ") {\n?>$s<?php\n}\n?>";
215:
216: 217: if ($this->namedBlocks || $this->extends) {
218: $s = "<?php\n"
219: . 'if ($_l->extends) { ob_start(); }' . "\n"
220: . 'elseif (isset($presenter, $control) && $presenter->isAjax()) { NLatteMacros::renderSnippets($control, $_l, get_defined_vars()); }' . "\n"
221: . '?>' . $s . "<?php\n"
222: . 'if ($_l->extends) { ob_end_clean(); NLatteMacros::includeTemplate($_l->extends, get_defined_vars(), $template)->render(); }' . "\n";
223: } else {
224: $s = "<?php\n"
225: . 'if (isset($presenter, $control) && $presenter->isAjax()) { NLatteMacros::renderSnippets($control, $_l, get_defined_vars()); }' . "\n"
226: . '?>' . $s;
227: }
228:
229: 230: if ($this->namedBlocks) {
231: $uniq = $this->uniq;
232: foreach (array_reverse($this->namedBlocks, TRUE) as $name => $foo) {
233: $code = & $this->namedBlocks[$name];
234: $namere = preg_quote($name, '#');
235: $s = NString::replace($s,
236: "#{block $namere} \?>(.*)<\?php {/block $namere}#sU",
237: callback(create_function('$matches', 'extract(NClosureFix::$vars['.NClosureFix::uses(array('name'=>$name, 'code'=>& $code,'uniq'=> $uniq)).'], EXTR_REFS);
238: list(, $content) = $matches;
239: $func = \'_lb\' . substr(md5($uniq . $name), 0, 10) . \'_\' . preg_replace(\'#[^a-z0-9_]#i\', \'_\', $name);
240: $code = "//\\n// block $name\\n//\\n"
241: . "if (!function_exists(\\$_l->blocks[" . var_export($name, TRUE) . "][] = \'$func\')) { "
242: . "function $func(\\$_l, \\$_args) { extract(\\$_args)\\n?>$content<?php\\n}}";
243: return \'\';
244: ')));
245: }
246: $s = "<?php\n\n" . implode("\n\n\n", $this->namedBlocks) . "\n\n//\n// end of blocks\n//\n?>" . $s;
247: }
248:
249: 250: $s = "<?php\n"
251: . '$_l = NLatteMacros::initRuntime($template, ' . var_export($this->extends, TRUE) . ', ' . var_export($this->uniq, TRUE) . '); unset($_extends);'
252: . "\n?>" . $s;
253: }
254:
255:
256:
257: 258: 259: 260: 261: 262: 263:
264: public function macro($macro, $content, $modifiers)
265: {
266: if ($macro === '') {
267: $macro = substr($content, 0, 2);
268: if (!isset($this->macros[$macro])) {
269: $macro = substr($content, 0, 1);
270: if (!isset($this->macros[$macro])) {
271: return NULL;
272: }
273: }
274: $content = substr($content, strlen($macro));
275:
276: } elseif (!isset($this->macros[$macro])) {
277: return NULL;
278: }
279: $This = $this;
280: return NString::replace(
281: $this->macros[$macro],
282: '#%(.*?)%#',
283: callback(create_function('$m', 'extract(NClosureFix::$vars['.NClosureFix::uses(array('This'=>$This,'content'=> $content,'modifiers'=> $modifiers)).'], EXTR_REFS);
284: if ($m[1]) {
285: return callback($m[1][0] === \':\' ? array($This, substr($m[1], 1)) : $m[1])
286: ->invoke($content, $modifiers);
287: } else {
288: return $content;
289: }
290: ')));
291: }
292:
293:
294:
295: 296: 297: 298: 299: 300: 301:
302: public function tagMacro($name, $attrs, $closing)
303: {
304: $knownTags = array(
305: 'include' => 'block',
306: 'for' => 'each',
307: 'block' => 'name',
308: 'if' => 'cond',
309: 'elseif' => 'cond',
310: );
311: return $this->macro(
312: $closing ? "/$name" : $name,
313: isset($knownTags[$name], $attrs[$knownTags[$name]])
314: ? $attrs[$knownTags[$name]]
315: : preg_replace("#'([^\\'$]+)'#", '$1', substr(var_export($attrs, TRUE), 8, -1)),
316: isset($attrs['modifiers']) ? $attrs['modifiers'] : ''
317: );
318: }
319:
320:
321:
322: 323: 324: 325: 326: 327: 328:
329: public function attrsMacro($code, $attrs, $closing)
330: {
331: $left = $right = '';
332: foreach ($this->macros as $name => $foo) {
333: if (!isset($this->macros["/$name"])) { 334: continue;
335: }
336:
337: $macro = $closing ? "/$name" : $name;
338: if (isset($attrs[$name])) {
339: if ($closing) {
340: $right .= $this->macro($macro, '', '');
341: } else {
342: $left = $this->macro($macro, $attrs[$name], '') . $left;
343: }
344: }
345:
346: $innerName = "inner-$name";
347: if (isset($attrs[$innerName])) {
348: if ($closing) {
349: $left .= $this->macro($macro, '', '');
350: } else {
351: $right = $this->macro($macro, $attrs[$innerName], '') . $right;
352: }
353: }
354:
355: $tagName = "tag-$name";
356: if (isset($attrs[$tagName])) {
357: $left = $this->macro($name, $attrs[$tagName], '') . $left;
358: $right .= $this->macro("/$name", '', '');
359: }
360:
361: unset($attrs[$name], $attrs[$innerName], $attrs[$tagName]);
362: }
363:
364: return $attrs ? NULL : $left . $code . $right;
365: }
366:
367:
368:
369:
370:
371:
372:
373: 374: 375:
376: public function macroDollar($var, $modifiers)
377: {
378: return self::formatModifiers('$' . $var, $modifiers);
379: }
380:
381:
382:
383: 384: 385:
386: public function macroTranslate($var, $modifiers)
387: {
388: return self::formatModifiers($var, '|translate' . $modifiers);
389: }
390:
391:
392:
393: 394: 395:
396: public function macroSyntax($var)
397: {
398: switch ($var) {
399: case '':
400: case 'latte':
401: $this->filter->setDelimiters('\\{(?![\\s\'"{}])', '\\}'); 402: break;
403:
404: case 'double':
405: $this->filter->setDelimiters('\\{\\{(?![\\s\'"{}])', '\\}\\}'); 406: break;
407:
408: case 'asp':
409: $this->filter->setDelimiters('<%\s*', '\s*%>');
410: break;
411:
412: case 'python':
413: $this->filter->setDelimiters('\\{[{%]\s*', '\s*[%}]\\}'); 414: break;
415:
416: case 'off':
417: $this->filter->setDelimiters('[^\x00-\xFF]', '');
418: break;
419:
420: default:
421: throw new InvalidStateException("Unknown syntax '$var' on line {$this->filter->line}.");
422: }
423: }
424:
425:
426:
427: 428: 429:
430: public function macroInclude($content, $modifiers, $isDefinition = FALSE)
431: {
432: $destination = self::fetchToken($content); 433: $params = self::formatArray($content) . ($content ? ' + ' : '');
434:
435: if ($destination === NULL) {
436: throw new InvalidStateException("Missing destination in {include} on line {$this->filter->line}.");
437:
438: } elseif ($destination[0] === '#') { 439: $destination = ltrim($destination, '#');
440: if (!NString::match($destination, '#^' . self::RE_IDENTIFIER . '$#')) {
441: throw new InvalidStateException("Included block name must be alphanumeric string, '$destination' given on line {$this->filter->line}.");
442: }
443:
444: $parent = $destination === 'parent';
445: if ($destination === 'parent' || $destination === 'this') {
446: $item = end($this->blocks);
447: while ($item && $item[0] !== self::BLOCK_NAMED) $item = prev($this->blocks);
448: if (!$item) {
449: throw new InvalidStateException("Cannot include $destination block outside of any block on line {$this->filter->line}.");
450: }
451: $destination = $item[1];
452: }
453: $name = var_export($destination, TRUE);
454: $params .= $isDefinition ? 'get_defined_vars()' : '$template->getParams()';
455: $cmd = isset($this->namedBlocks[$destination]) && !$parent
456: ? "call_user_func(reset(\$_l->blocks[$name]), \$_l, $params)"
457: : 'NLatteMacros::callBlock' . ($parent ? 'Parent' : '') . "(\$_l, $name, $params)";
458: return $modifiers
459: ? "ob_start(); $cmd; echo " . self::formatModifiers('ob_get_clean()', $modifiers)
460: : $cmd;
461:
462: } else { 463: $destination = self::formatString($destination);
464: $cmd = 'NLatteMacros::includeTemplate(' . $destination . ', ' . $params . '$template->getParams(), $_l->templates[' . var_export($this->uniq, TRUE) . '])';
465: return $modifiers
466: ? 'echo ' . self::formatModifiers($cmd . '->__toString(TRUE)', $modifiers)
467: : $cmd . '->render()';
468: }
469: }
470:
471:
472:
473: 474: 475:
476: public function macroExtends($content)
477: {
478: $destination = self::fetchToken($content); 479: if ($destination === NULL) {
480: throw new InvalidStateException("Missing destination in {extends} on line {$this->filter->line}.");
481: }
482: if (!empty($this->blocks)) {
483: throw new InvalidStateException("{extends} must be placed outside any block; on line {$this->filter->line}.");
484: }
485: if ($this->extends !== NULL) {
486: throw new InvalidStateException("Multiple {extends} declarations are not allowed; on line {$this->filter->line}.");
487: }
488: $this->extends = $destination !== 'none';
489: return $this->extends ? '$_l->extends = ' . self::formatString($destination) : '';
490: }
491:
492:
493:
494: 495: 496:
497: public function macroBlock($content, $modifiers)
498: {
499: $name = self::fetchToken($content); 500:
501: if ($name === NULL) { 502: $this->blocks[] = array(self::BLOCK_ANONYMOUS, NULL, $modifiers);
503: return $modifiers === '' ? '' : 'ob_start()';
504:
505: } else { 506: $name = ltrim($name, '#');
507: if (!NString::match($name, '#^' . self::RE_IDENTIFIER . '$#')) {
508: throw new InvalidStateException("Block name must be alphanumeric string, '$name' given on line {$this->filter->line}.");
509:
510: } elseif (isset($this->namedBlocks[$name])) {
511: throw new InvalidStateException("Cannot redeclare block '$name'; on line {$this->filter->line}.");
512: }
513:
514: $top = empty($this->blocks);
515: $this->namedBlocks[$name] = $name;
516: $this->blocks[] = array(self::BLOCK_NAMED, $name, '');
517: if ($name[0] === '_') { 518: $tag = self::fetchToken($content); 519: $tag = trim($tag, '<>');
520: $namePhp = var_export(substr($name, 1), TRUE);
521: if (!$tag) $tag = 'div';
522: return "?><$tag id=\"<?php echo \$control->getSnippetId($namePhp) ?>\"><?php "
523: . $this->macroInclude('#' . $name, $modifiers)
524: . " ?></$tag><?php {block $name}";
525:
526: } elseif (!$top) {
527: return $this->macroInclude('#' . $name, $modifiers, TRUE) . "{block $name}";
528:
529: } elseif ($this->extends) {
530: return "{block $name}";
531:
532: } else {
533: return 'if (!$_l->extends) { ' . $this->macroInclude('#' . $name, $modifiers, TRUE) . "; } {block $name}";
534: }
535: }
536: }
537:
538:
539:
540: 541: 542:
543: public function macroBlockEnd($content)
544: {
545: list($type, $name, $modifiers) = array_pop($this->blocks);
546:
547: if ($type === self::BLOCK_CAPTURE) { 548: $this->blocks[] = array($type, $name, $modifiers);
549: return $this->macroCaptureEnd($content);
550: }
551:
552: if (($type !== self::BLOCK_NAMED && $type !== self::BLOCK_ANONYMOUS) || ($content && $content !== $name)) {
553: throw new InvalidStateException("Tag {/block $content} was not expected here on line {$this->filter->line}.");
554:
555: } elseif ($type === self::BLOCK_NAMED) { 556: return "{/block $name}";
557:
558: } else { 559: return $modifiers === '' ? '' : 'echo ' . self::formatModifiers('ob_get_clean()', $modifiers);
560: }
561: }
562:
563:
564:
565: 566: 567:
568: public function macroSnippet($content)
569: {
570: if (substr($content, 0, 1) === ':' || !$this->oldSnippetMode) { 571: $this->oldSnippetMode = FALSE;
572: return $this->macroBlock('_' . ltrim($content, ':'), '');
573: }
574: $args = array('');
575: if ($snippet = self::fetchToken($content)) { 576: $args[] = self::formatString($snippet);
577: }
578: if ($content) {
579: $args[] = self::formatString($content);
580: }
581: return '} if ($_l->foo = NSnippetHelper::create($control' . implode(', ', $args) . ')) { $_l->snippets[] = $_l->foo';
582: }
583:
584:
585:
586: 587: 588:
589: public function macroSnippetEnd($content)
590: {
591: if (!$this->oldSnippetMode) {
592: return $this->macroBlockEnd('', '');
593: }
594: return 'array_pop($_l->snippets)->finish(); } if (NSnippetHelper::$outputAllowed) {';
595: }
596:
597:
598:
599: 600: 601:
602: public function macroCapture($content, $modifiers)
603: {
604: $name = self::fetchToken($content); 605:
606: if (substr($name, 0, 1) !== '$') {
607: throw new InvalidStateException("Invalid capture block parameter '$name' on line {$this->filter->line}.");
608: }
609:
610: $this->blocks[] = array(self::BLOCK_CAPTURE, $name, $modifiers);
611: return 'ob_start()';
612: }
613:
614:
615:
616: 617: 618:
619: public function macroCaptureEnd($content)
620: {
621: list($type, $name, $modifiers) = array_pop($this->blocks);
622:
623: if ($type !== self::BLOCK_CAPTURE || ($content && $content !== $name)) {
624: throw new InvalidStateException("Tag {/capture $content} was not expected here on line {$this->filter->line}.");
625: }
626:
627: return $name . '=' . self::formatModifiers('ob_get_clean()', $modifiers);
628: }
629:
630:
631:
632: 633: 634:
635: public function macroCache($content)
636: {
637: return 'if (NCachingHelper::create('
638: . var_export($this->uniq . ':' . $this->cacheCounter++, TRUE)
639: . ', $_l->g->caches' . self::formatArray($content, ', ') . ')) {';
640: }
641:
642:
643:
644: 645: 646:
647: public function macroForeach($content)
648: {
649: return '$iterator = $_l->its[] = new NSmartCachingIterator(' . preg_replace('# +as +#i', ') as ', $content, 1);
650: }
651:
652:
653:
654: 655: 656:
657: public function macroAttr($content)
658: {
659: return NString::replace($content . ' ', '#\)\s+#', ')->');
660: }
661:
662:
663:
664: 665: 666:
667: public function macroContentType($content)
668: {
669: if (strpos($content, 'html') !== FALSE) {
670: $this->filter->escape = 'NTemplateHelpers::escapeHtml';
671: $this->filter->context = NLatteFilter::CONTEXT_TEXT;
672:
673: } elseif (strpos($content, 'xml') !== FALSE) {
674: $this->filter->escape = 'NTemplateHelpers::escapeXml';
675: $this->filter->context = NLatteFilter::CONTEXT_NONE;
676:
677: } elseif (strpos($content, 'javascript') !== FALSE) {
678: $this->filter->escape = 'NTemplateHelpers::escapeJs';
679: $this->filter->context = NLatteFilter::CONTEXT_NONE;
680:
681: } elseif (strpos($content, 'css') !== FALSE) {
682: $this->filter->escape = 'NTemplateHelpers::escapeCss';
683: $this->filter->context = NLatteFilter::CONTEXT_NONE;
684:
685: } elseif (strpos($content, 'plain') !== FALSE) {
686: $this->filter->escape = '';
687: $this->filter->context = NLatteFilter::CONTEXT_NONE;
688:
689: } else {
690: $this->filter->escape = '$template->escape';
691: $this->filter->context = NLatteFilter::CONTEXT_NONE;
692: }
693:
694: 695: return strpos($content, '/') ? 'NEnvironment::getHttpResponse()->setHeader("Content-Type", "' . $content . '")' : '';
696: }
697:
698:
699:
700: 701: 702:
703: public function macroDump($content)
704: {
705: return 'NDebug::barDump('
706: . ($content ? 'array(' . var_export($content, TRUE) . " => $content)" : 'get_defined_vars()')
707: . ', "Template " . str_replace(dirname(dirname($template->getFile())), "\xE2\x80\xA6", $template->getFile()))';
708: }
709:
710:
711:
712: 713: 714:
715: public function macroDebugbreak()
716: {
717: return 'if (function_exists("debugbreak")) debugbreak(); elseif (function_exists("xdebug_break")) xdebug_break()';
718: }
719:
720:
721:
722: 723: 724:
725: public function macroControl($content)
726: {
727: $pair = self::fetchToken($content); 728: if ($pair === NULL) {
729: throw new InvalidStateException("Missing control name in {control} on line {$this->filter->line}.");
730: }
731: $pair = explode(':', $pair, 2);
732: $name = self::formatString($pair[0]);
733: $method = isset($pair[1]) ? ucfirst($pair[1]) : '';
734: $method = NString::match($method, '#^(' . self::RE_IDENTIFIER . '|)$#') ? "render$method" : "{\"render$method\"}";
735: $param = self::formatArray($content);
736: if (strpos($content, '=>') === FALSE) $param = substr($param, 6, -1); 737: return ($name[0] === '$' ? "if (is_object($name)) {$name}->$method($param); else " : '')
738: . "\$control->getWidget($name)->$method($param)";
739: }
740:
741:
742:
743: 744: 745:
746: public function macroLink($content, $modifiers)
747: {
748: return self::formatModifiers('$control->link(' . $this->formatLink($content) .')', $modifiers);
749: }
750:
751:
752:
753: 754: 755:
756: public function macroPlink($content, $modifiers)
757: {
758: return self::formatModifiers('$presenter->link(' . $this->formatLink($content) .')', $modifiers);
759: }
760:
761:
762:
763: 764: 765:
766: public function macroIfCurrent($content)
767: {
768: return ($content ? 'try { $presenter->link(' . $this->formatLink($content) . '); } catch (NInvalidLinkException $e) {}' : '')
769: . '; if ($presenter->getLastCreatedRequestFlag("current")):';
770: }
771:
772:
773:
774: 775: 776:
777: private function formatLink($content)
778: {
779: return self::formatString(self::fetchToken($content)) . self::formatArray($content, ', '); 780: }
781:
782:
783:
784: 785: 786:
787: public function macroVar($content, $modifiers, $extract = FALSE)
788: {
789: $tokenizer = new NTokenizer(array(
790: NTokenizer::T_WHITESPACE => '\s+',
791: NTokenizer::RE_STRING,
792: 'true|false|null|and|or|xor|clone|new|instanceof',
793: self::T_SYMBOL => '\$?[0-9a-zA-Z_]+', 794: '=>|[^"\']', 795: ), 'i');
796:
797: $out = '';
798: $quote = $var = TRUE;
799: $depth = 0;
800: foreach ($tokenizer->tokenize($content) as $n => $token) {
801: list($token, $name) = $token;
802:
803: if ($name === self::T_SYMBOL) {
804: if ($var) {
805: if ($extract) {
806: $token = "'" . ($token[0] === '$' ? substr($token, 1) : $token) . "'";
807: } else {
808: $token = $token[0] === '$' ? $token : '$' . $token;
809: }
810: } elseif ($quote && $token[0] !== '$' && !is_numeric($token) && in_array($tokenizer->nextToken($n), array(',', '=>', ')', NULL), TRUE)) {
811: $token = "'$token'";
812: }
813: } elseif ($token === '(') {
814: $depth++;
815:
816: } elseif ($token === ')') {
817: $depth--;
818:
819: } elseif (($token === '=' || $token === '=>') && $depth === 0) {
820: $token = $extract ? '=>' : '=';
821: $var = FALSE;
822:
823: } elseif ($token === ',' && $depth === 0) {
824: $token = $extract ? ',' : ';';
825: $var = TRUE;
826: }
827:
828: if ($name !== NTokenizer::T_WHITESPACE) {
829: $quote = in_array($token, array('[', ',', '=', '(', '=>'));
830: }
831: $out .= $token;
832: }
833: return $out;
834: }
835:
836:
837:
838: 839: 840:
841: public function macroDefault($content)
842: {
843: return 'extract(array(' . $this->macroVar($content, '', TRUE) . '), EXTR_SKIP)';
844: }
845:
846:
847:
848: 849: 850:
851: public function macroEscape($content)
852: {
853: return $this->filter->escape;
854: }
855:
856:
857:
858: 859: 860:
861: public function macroModifiers($content, $modifiers)
862: {
863: return self::formatModifiers($content, $modifiers);
864: }
865:
866:
867:
868:
869:
870:
871:
872: 873: 874: 875: 876: 877:
878: public static function formatModifiers($var, $modifiers)
879: {
880: if (!$modifiers) return $var;
881: $tokenizer = new NTokenizer(array(
882: NTokenizer::T_WHITESPACE => '\s+',
883: NTokenizer::RE_STRING,
884: 'true|false|null|and|or|xor|clone|new|instanceof',
885: '\$[_a-z0-9\x7F-\xFF]+', 886: self::T_SYMBOL => '[_a-z0-9\x7F-\xFF]+', 887: '[^"\']', 888: ), 'i');
889:
890: $inside = FALSE;
891: foreach ($tokenizer->tokenize(ltrim($modifiers, '|')) as $n => $token) {
892: list($token, $name) = $token;
893: if ($name === NTokenizer::T_WHITESPACE) {
894: $var = rtrim($var) . ' ';
895:
896: } elseif (!$inside) {
897: if ($name === self::T_SYMBOL) {
898: $var = "\$template->$token($var";
899: $inside = TRUE;
900: } else {
901: throw new InvalidStateException("Modifier name must be alphanumeric string, '$token' given.");
902: }
903: } else {
904: if ($token === ':' || $token === ',') {
905: $var = $var . ', ';
906:
907: } elseif ($token === '|') {
908: $var = $var . ')';
909: $inside = FALSE;
910:
911: } elseif ($name === self::T_SYMBOL && $quote && !is_numeric($token)
912: && in_array($tokenizer->nextToken($n), array(',', ':', ')', '|', NULL), TRUE)) {
913: $var .= "'$token'";
914:
915: } else {
916: $var .= $token;
917: }
918: }
919: if ($name !== NTokenizer::T_WHITESPACE) {
920: $quote = in_array($token, array('[', ',', '=', '(', ':'));
921: }
922: }
923: return $inside ? "$var)" : $var;
924: }
925:
926:
927:
928: 929: 930: 931: 932:
933: public static function fetchToken(& $s)
934: {
935: if ($matches = NString::match($s, '#^((?>'.NTokenizer::RE_STRING.'|[^\'"\s,]+)+)\s*,?\s*(.*)$#')) { 936: $s = $matches[2];
937: return $matches[1];
938: }
939:
940: return NULL;
941: }
942:
943:
944:
945: 946: 947: 948: 949: 950:
951: public static function formatArray($input, $prefix = '')
952: {
953: $tokenizer = new NTokenizer(array(
954: NTokenizer::T_WHITESPACE => '\s+',
955: NTokenizer::T_COMMENT => '/\*.*?\*/',
956: NTokenizer::RE_STRING,
957: 'true|false|null|and|or|xor|clone|new|instanceof',
958: '\$[_a-z0-9\x7F-\xFF]+', 959: self::T_SYMBOL => '[_a-z0-9\x7F-\xFF]+', 960: '=>|[^"\']', 961: ), 'i');
962:
963: $out = '';
964: $quote = TRUE;
965: foreach ($tokenizer->tokenize($input) as $n => $token) {
966: list($token, $name) = $token;
967:
968: if ($name === NTokenizer::T_COMMENT) {
969: continue;
970:
971: } elseif ($name === self::T_SYMBOL && $quote && !is_numeric($token)
972: && in_array($tokenizer->nextToken($n), array(',', '=>', ')', NULL), TRUE)) {
973: $token = "'$token'";
974: }
975: if ($name !== NTokenizer::T_WHITESPACE) {
976: $quote = in_array($token, array('[', ',', '=', '(', '=>'));
977: }
978: $out .= $token;
979: }
980: return $out === '' ? '' : $prefix . "array($out)";
981: }
982:
983:
984:
985: 986: 987: 988: 989:
990: public static function formatString($s)
991: {
992: static $keywords = array('true'=>1, 'false'=>1, 'null'=>1);
993: return (is_numeric($s) || strspn($s, '\'"$') || isset($keywords[strtolower($s)])) ? $s : '"' . $s . '"';
994: }
995:
996:
997:
998:
999:
1000:
1001:
1002: 1003: 1004: 1005: 1006: 1007: 1008:
1009: public static function callBlock($context, $name, $params)
1010: {
1011: if (empty($context->blocks[$name])) {
1012: throw new InvalidStateException("Cannot include undefined block '$name'.");
1013: }
1014: $block = reset($context->blocks[$name]);
1015: $block($context, $params);
1016: }
1017:
1018:
1019:
1020: 1021: 1022: 1023: 1024: 1025: 1026:
1027: public static function callBlockParent($context, $name, $params)
1028: {
1029: if (empty($context->blocks[$name]) || ($block = next($context->blocks[$name])) === FALSE) {
1030: throw new InvalidStateException("Cannot include undefined parent block '$name'.");
1031: }
1032: $block($context, $params);
1033: }
1034:
1035:
1036:
1037: 1038: 1039: 1040: 1041: 1042: 1043:
1044: public static function includeTemplate($destination, $params, $template)
1045: {
1046: if ($destination instanceof ITemplate) {
1047: $tpl = $destination;
1048:
1049: } elseif ($destination == NULL) { 1050: throw new InvalidArgumentException("Template file name was not specified.");
1051:
1052: } else {
1053: $tpl = clone $template;
1054: if ($template instanceof IFileTemplate) {
1055: if (substr($destination, 0, 1) !== '/' && substr($destination, 1, 1) !== ':') {
1056: $destination = dirname($template->getFile()) . '/' . $destination;
1057: }
1058: $tpl->setFile($destination);
1059: }
1060: }
1061:
1062: $tpl->setParams($params); 1063: return $tpl;
1064: }
1065:
1066:
1067:
1068: 1069: 1070: 1071: 1072: 1073: 1074:
1075: public static function initRuntime($template, $extends, $realFile)
1076: {
1077: $local = (object) NULL;
1078:
1079: 1080: if (isset($template->_l)) {
1081: $local->blocks = & $template->_l->blocks;
1082: $local->templates = & $template->_l->templates;
1083: }
1084: $local->templates[$realFile] = $template;
1085: $local->extends = is_bool($extends) ? $extends : (empty($template->_extends) ? FALSE : $template->_extends);
1086: unset($template->_l, $template->_extends);
1087:
1088: 1089: if (!isset($template->_g)) {
1090: $template->_g = (object) NULL;
1091: }
1092: $local->g = $template->_g;
1093:
1094: 1095: if (!empty($local->g->caches)) {
1096: end($local->g->caches)->addFile($template->getFile());
1097: }
1098:
1099: return $local;
1100: }
1101:
1102:
1103:
1104: public static function renderSnippets($control, $local, $params)
1105: {
1106: $payload = $control->getPresenter()->getPayload();
1107: if (isset($local->blocks)) {
1108: foreach ($local->blocks as $name => $function) {
1109: if ($name[0] !== '_' || !$control->isControlInvalid(substr($name, 1))) continue;
1110: ob_start();
1111: $function = reset($function);
1112: $function($local, $params);
1113: $payload->snippets[$control->getSnippetId(substr($name, 1))] = ob_get_clean();
1114: }
1115: }
1116: }
1117:
1118: }
1119: