Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

  • Compiler
  • Engine
  • Helpers
  • HtmlNode
  • MacroNode
  • MacroTokens
  • Parser
  • PhpHelpers
  • PhpWriter
  • Token
  • TokenIterator
  • Tokenizer

Interfaces

  • ILoader
  • IMacro

Traits

  • Strict

Exceptions

  • CompileException
  • RegexpException
  • RuntimeException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Latte (https://latte.nette.org)
  5:  * Copyright (c) 2008 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Latte;
  9: 
 10: 
 11: /**
 12:  * Latte compiler.
 13:  */
 14: class Compiler
 15: {
 16:     use Strict;
 17: 
 18:     /** Context-aware escaping content types */
 19:     const
 20:         CONTENT_HTML = Engine::CONTENT_HTML,
 21:         CONTENT_XHTML = Engine::CONTENT_XHTML,
 22:         CONTENT_XML = Engine::CONTENT_XML,
 23:         CONTENT_JS = Engine::CONTENT_JS,
 24:         CONTENT_CSS = Engine::CONTENT_CSS,
 25:         CONTENT_ICAL = Engine::CONTENT_ICAL,
 26:         CONTENT_TEXT = Engine::CONTENT_TEXT;
 27: 
 28:     /** @internal Context-aware escaping HTML contexts */
 29:     const
 30:         CONTEXT_HTML_TEXT = null,
 31:         CONTEXT_HTML_TAG = 'Tag',
 32:         CONTEXT_HTML_ATTRIBUTE = 'Attr',
 33:         CONTEXT_HTML_ATTRIBUTE_JS = 'AttrJs',
 34:         CONTEXT_HTML_ATTRIBUTE_CSS = 'AttrCss',
 35:         CONTEXT_HTML_ATTRIBUTE_URL = 'AttrUrl',
 36:         CONTEXT_HTML_ATTRIBUTE_UNQUOTED_URL = 'AttrUnquotedUrl',
 37:         CONTEXT_HTML_COMMENT = 'Comment',
 38:         CONTEXT_HTML_BOGUS_COMMENT = 'Bogus',
 39:         CONTEXT_HTML_CSS = 'Css',
 40:         CONTEXT_HTML_JS = 'Js',
 41: 
 42:         CONTEXT_XML_TEXT = self::CONTEXT_HTML_TEXT,
 43:         CONTEXT_XML_TAG = self::CONTEXT_HTML_TAG,
 44:         CONTEXT_XML_ATTRIBUTE = self::CONTEXT_HTML_ATTRIBUTE,
 45:         CONTEXT_XML_COMMENT = self::CONTEXT_HTML_COMMENT,
 46:         CONTEXT_XML_BOGUS_COMMENT = self::CONTEXT_HTML_BOGUS_COMMENT;
 47: 
 48:     /** @var Token[] */
 49:     private $tokens;
 50: 
 51:     /** @var string pointer to current node content */
 52:     private $output;
 53: 
 54:     /** @var int  position on source template */
 55:     private $position;
 56: 
 57:     /** @var array of [name => IMacro[]] */
 58:     private $macros = [];
 59: 
 60:     /** @var int[] IMacro flags */
 61:     private $flags;
 62: 
 63:     /** @var HtmlNode */
 64:     private $htmlNode;
 65: 
 66:     /** @var MacroNode */
 67:     private $macroNode;
 68: 
 69:     /** @var string[] */
 70:     private $placeholders = [];
 71: 
 72:     /** @var string */
 73:     private $contentType = self::CONTENT_HTML;
 74: 
 75:     /** @var string|null */
 76:     private $context;
 77: 
 78:     /** @var mixed */
 79:     private $lastAttrValue;
 80: 
 81:     /** @var int */
 82:     private $tagOffset;
 83: 
 84:     /** @var bool */
 85:     private $inHead;
 86: 
 87:     /** @var array of [name => [body, arguments]] */
 88:     private $methods = [];
 89: 
 90:     /** @var array of [name => serialized value] */
 91:     private $properties = [];
 92: 
 93: 
 94:     /**
 95:      * Adds new macro with IMacro flags.
 96:      * @param  string
 97:      * @return static
 98:      */
 99:     public function addMacro($name, IMacro $macro, $flags = null)
100:     {
101:         if (!isset($this->flags[$name])) {
102:             $this->flags[$name] = $flags ?: IMacro::DEFAULT_FLAGS;
103:         } elseif ($flags && $this->flags[$name] !== $flags) {
104:             throw new \LogicException("Incompatible flags for macro $name.");
105:         }
106:         $this->macros[$name][] = $macro;
107:         return $this;
108:     }
109: 
110: 
111:     /**
112:      * Compiles tokens to PHP code.
113:      * @param  Token[]
114:      * @return string
115:      */
116:     public function compile(array $tokens, $className)
117:     {
118:         $this->tokens = $tokens;
119:         $output = '';
120:         $this->output = &$output;
121:         $this->inHead = true;
122:         $this->htmlNode = $this->macroNode = $this->context = null;
123:         $this->placeholders = $this->properties = [];
124:         $this->methods = ['main' => null, 'prepare' => null];
125: 
126:         $macroHandlers = new \SplObjectStorage;
127: 
128:         if ($this->macros) {
129:             array_map([$macroHandlers, 'attach'], call_user_func_array('array_merge', $this->macros));
130:         }
131: 
132:         foreach ($macroHandlers as $handler) {
133:             $handler->initialize($this);
134:         }
135: 
136:         foreach ($tokens as $this->position => $token) {
137:             if ($this->inHead && !($token->type === $token::COMMENT
138:                 || $token->type === $token::MACRO_TAG && isset($this->flags[$token->name]) && $this->flags[$token->name] & IMacro::ALLOWED_IN_HEAD
139:                 || $token->type === $token::TEXT && trim($token->text) === ''
140:             )) {
141:                 $this->inHead = false;
142:             }
143:             $this->{"process$token->type"}($token);
144:         }
145: 
146:         while ($this->htmlNode) {
147:             if (!empty($this->htmlNode->macroAttrs)) {
148:                 throw new CompileException('Missing ' . self::printEndTag($this->htmlNode));
149:             }
150:             $this->htmlNode = $this->htmlNode->parentNode;
151:         }
152: 
153:         while ($this->macroNode) {
154:             if (~$this->flags[$this->macroNode->name] & IMacro::AUTO_CLOSE) {
155:                 throw new CompileException('Missing ' . self::printEndTag($this->macroNode));
156:             }
157:             $this->closeMacro($this->macroNode->name);
158:         }
159: 
160:         $prepare = $epilogs = '';
161:         foreach ($macroHandlers as $handler) {
162:             $res = $handler->finalize();
163:             $prepare .= empty($res[0]) ? '' : "<?php $res[0] ?>";
164:             $epilogs = (empty($res[1]) ? '' : "<?php $res[1] ?>") . $epilogs;
165:         }
166: 
167:         $this->addMethod('main', $this->expandTokens("extract(\$this->params);?>\n$output$epilogs<?php return get_defined_vars();"));
168: 
169:         if ($prepare) {
170:             $this->addMethod('prepare', "extract(\$this->params);?>$prepare<?php");
171:         }
172:         if ($this->contentType !== self::CONTENT_HTML) {
173:             $this->addProperty('contentType', $this->contentType);
174:         }
175: 
176:         foreach ($this->properties as $name => $value) {
177:             $members[] = "\tpublic $$name = " . PhpHelpers::dump($value) . ';';
178:         }
179:         foreach (array_filter($this->methods) as $name => $method) {
180:             $members[] = "\n\tfunction $name($method[arguments])\n\t{\n" . ($method['body'] ? "\t\t$method[body]\n" : '') . "\t}";
181:         }
182: 
183:         return "<?php\n"
184:             . "use Latte\\Runtime as LR;\n\n"
185:             . "class $className extends Latte\\Runtime\\Template\n{\n"
186:             . implode("\n\n", $members)
187:             . "\n\n}\n";
188:     }
189: 
190: 
191:     /**
192:      * @return static
193:      */
194:     public function setContentType($type)
195:     {
196:         $this->contentType = $type;
197:         $this->context = null;
198:         return $this;
199:     }
200: 
201: 
202:     /**
203:      * @deprecated
204:      */
205:     public function getContentType()
206:     {
207:         trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED);
208:         return $this->contentType;
209:     }
210: 
211: 
212:     /**
213:      * @internal
214:      */
215:     public function setContext($context)
216:     {
217:         trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED);
218:         $this->context = $context;
219:         return $this;
220:     }
221: 
222: 
223:     /**
224:      * @deprecated
225:      */
226:     public function getContext()
227:     {
228:         trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED);
229:         return $this->context;
230:     }
231: 
232: 
233:     /**
234:      * @return MacroNode|null
235:      */
236:     public function getMacroNode()
237:     {
238:         return $this->macroNode;
239:     }
240: 
241: 
242:     /**
243:      * Returns current line number.
244:      * @return int|null
245:      */
246:     public function getLine()
247:     {
248:         return isset($this->tokens[$this->position]) ? $this->tokens[$this->position]->line : null;
249:     }
250: 
251: 
252:     /**
253:      * @return bool
254:      */
255:     public function isInHead()
256:     {
257:         return $this->inHead;
258:     }
259: 
260: 
261:     /**
262:      * Adds custom method to template.
263:      * @return void
264:      * @internal
265:      */
266:     public function addMethod($name, $body, $arguments = '')
267:     {
268:         $this->methods[$name] = ['body' => trim($body), 'arguments' => $arguments];
269:     }
270: 
271: 
272:     /**
273:      * Returns custom methods.
274:      * @return array
275:      * @internal
276:      */
277:     public function getMethods()
278:     {
279:         return $this->methods;
280:     }
281: 
282: 
283:     /**
284:      * Adds custom property to template.
285:      * @return void
286:      * @internal
287:      */
288:     public function addProperty($name, $value)
289:     {
290:         $this->properties[$name] = $value;
291:     }
292: 
293: 
294:     /**
295:      * Returns custom properites.
296:      * @return array
297:      * @internal
298:      */
299:     public function getProperties()
300:     {
301:         return $this->properties;
302:     }
303: 
304: 
305:     /** @internal */
306:     public function expandTokens($s)
307:     {
308:         return strtr($s, $this->placeholders);
309:     }
310: 
311: 
312:     private function processText(Token $token)
313:     {
314:         if ($this->lastAttrValue === '' && $this->context && Helpers::startsWith($this->context, self::CONTEXT_HTML_ATTRIBUTE)) {
315:             $this->lastAttrValue = $token->text;
316:         }
317:         $this->output .= $this->escape($token->text);
318:     }
319: 
320: 
321:     private function processMacroTag(Token $token)
322:     {
323:         if ($this->context === self::CONTEXT_HTML_TAG || $this->context && Helpers::startsWith($this->context, self::CONTEXT_HTML_ATTRIBUTE)) {
324:             $this->lastAttrValue = true;
325:         }
326: 
327:         $isRightmost = !isset($this->tokens[$this->position + 1])
328:             || substr($this->tokens[$this->position + 1]->text, 0, 1) === "\n";
329: 
330:         if ($token->closing) {
331:             $this->closeMacro($token->name, $token->value, $token->modifiers, $isRightmost);
332:         } else {
333:             if (!$token->empty && isset($this->flags[$token->name]) && $this->flags[$token->name] & IMacro::AUTO_EMPTY) {
334:                 $pos = $this->position;
335:                 while (($t = isset($this->tokens[++$pos]) ? $this->tokens[$pos] : null)
336:                     && ($t->type !== Token::MACRO_TAG || $t->name !== $token->name)
337:                     && ($t->type !== Token::HTML_ATTRIBUTE_BEGIN || $t->name !== Parser::N_PREFIX . $token->name));
338:                 $token->empty = $t ? !$t->closing : true;
339:             }
340:             $node = $this->openMacro($token->name, $token->value, $token->modifiers, $isRightmost);
341:             if ($token->empty) {
342:                 if ($node->empty) {
343:                     throw new CompileException("Unexpected /} in tag {$token->text}");
344:                 }
345:                 $this->closeMacro($token->name, null, null, $isRightmost);
346:             }
347:         }
348:     }
349: 
350: 
351:     private function processHtmlTagBegin(Token $token)
352:     {
353:         if ($token->closing) {
354:             while ($this->htmlNode) {
355:                 if (strcasecmp($this->htmlNode->name, $token->name) === 0) {
356:                     break;
357:                 }
358:                 if ($this->htmlNode->macroAttrs) {
359:                     throw new CompileException("Unexpected </$token->name>, expecting " . self::printEndTag($this->htmlNode));
360:                 }
361:                 $this->htmlNode = $this->htmlNode->parentNode;
362:             }
363:             if (!$this->htmlNode) {
364:                 $this->htmlNode = new HtmlNode($token->name);
365:             }
366:             $this->htmlNode->closing = true;
367:             $this->htmlNode->endLine = $this->getLine();
368:             $this->context = self::CONTEXT_HTML_TEXT;
369: 
370:         } elseif ($token->text === '<!--') {
371:             $this->context = self::CONTEXT_HTML_COMMENT;
372: 
373:         } elseif ($token->text === '<?' || $token->text === '<!') {
374:             $this->context = self::CONTEXT_HTML_BOGUS_COMMENT;
375:             $this->output .= $token->text === '<?' ? '<<?php ?>?' : '<!'; // bypass error in escape()
376:             return;
377: 
378:         } else {
379:             $this->htmlNode = new HtmlNode($token->name, $this->htmlNode);
380:             $this->htmlNode->startLine = $this->getLine();
381:             $this->context = self::CONTEXT_HTML_TAG;
382:         }
383:         $this->tagOffset = strlen($this->output);
384:         $this->output .= $token->text;
385:     }
386: 
387: 
388:     private function processHtmlTagEnd(Token $token)
389:     {
390:         if (in_array($this->context, [self::CONTEXT_HTML_COMMENT, self::CONTEXT_HTML_BOGUS_COMMENT], true)) {
391:             $this->output .= $token->text;
392:             $this->context = self::CONTEXT_HTML_TEXT;
393:             return;
394:         }
395: 
396:         $htmlNode = $this->htmlNode;
397:         $end = '';
398: 
399:         if (!$htmlNode->closing) {
400:             $htmlNode->empty = strpos($token->text, '/') !== false;
401:             if (in_array($this->contentType, [self::CONTENT_HTML, self::CONTENT_XHTML], true)) {
402:                 $emptyElement = isset(Helpers::$emptyElements[strtolower($htmlNode->name)]);
403:                 $htmlNode->empty = $htmlNode->empty || $emptyElement;
404:                 if ($htmlNode->empty) { // auto-correct
405:                     $space = substr(strstr($token->text, '>'), 1);
406:                     if ($emptyElement) {
407:                         $token->text = ($this->contentType === self::CONTENT_XHTML ? ' />' : '>') . $space;
408:                     } else {
409:                         $token->text = '>';
410:                         $end = "</$htmlNode->name>" . $space;
411:                     }
412:                 }
413:             }
414:         }
415: 
416:         if ($htmlNode->macroAttrs) {
417:             $html = substr($this->output, $this->tagOffset) . $token->text;
418:             $this->output = substr($this->output, 0, $this->tagOffset);
419:             $this->writeAttrsMacro($html);
420:         } else {
421:             $this->output .= $token->text . $end;
422:         }
423: 
424:         if ($htmlNode->empty) {
425:             $htmlNode->closing = true;
426:             if ($htmlNode->macroAttrs) {
427:                 $this->writeAttrsMacro($end);
428:             }
429:         }
430: 
431:         $this->context = self::CONTEXT_HTML_TEXT;
432: 
433:         if ($htmlNode->closing) {
434:             $this->htmlNode = $this->htmlNode->parentNode;
435: 
436:         } elseif (
437:             (($lower = strtolower($htmlNode->name)) === 'script' || $lower === 'style')
438:             && (!isset($htmlNode->attrs['type']) || preg_match('#(java|j|ecma|live)script|json|css#i', $htmlNode->attrs['type']))
439:         ) {
440:             $this->context = $lower === 'script' ? self::CONTEXT_HTML_JS : self::CONTEXT_HTML_CSS;
441:         }
442:     }
443: 
444: 
445:     private function processHtmlAttributeBegin(Token $token)
446:     {
447:         if (Helpers::startsWith($token->name, Parser::N_PREFIX)) {
448:             $name = substr($token->name, strlen(Parser::N_PREFIX));
449:             if (isset($this->htmlNode->macroAttrs[$name])) {
450:                 throw new CompileException("Found multiple attributes $token->name.");
451: 
452:             } elseif ($this->macroNode && $this->macroNode->htmlNode === $this->htmlNode) {
453:                 throw new CompileException("n:attributes must not appear inside macro; found $token->name inside {{$this->macroNode->name}}.");
454:             }
455:             $this->htmlNode->macroAttrs[$name] = $token->value;
456:             return;
457:         }
458: 
459:         $this->lastAttrValue = &$this->htmlNode->attrs[$token->name];
460:         $this->output .= $this->escape($token->text);
461: 
462:         $lower = strtolower($token->name);
463:         if (in_array($token->value, ['"', "'"], true)) {
464:             $this->lastAttrValue = '';
465:             $this->context = self::CONTEXT_HTML_ATTRIBUTE;
466:             if (in_array($this->contentType, [self::CONTENT_HTML, self::CONTENT_XHTML], true)) {
467:                 if (Helpers::startsWith($lower, 'on')) {
468:                     $this->context = self::CONTEXT_HTML_ATTRIBUTE_JS;
469:                 } elseif ($lower === 'style') {
470:                     $this->context = self::CONTEXT_HTML_ATTRIBUTE_CSS;
471:                 }
472:             }
473:         } else {
474:             $this->lastAttrValue = $token->value;
475:             $this->context = self::CONTEXT_HTML_TAG;
476:         }
477: 
478:         if (
479:             in_array($this->contentType, [self::CONTENT_HTML, self::CONTENT_XHTML], true)
480:             && (in_array($lower, ['href', 'src', 'action', 'formaction'], true)
481:                 || ($lower === 'data' && strtolower($this->htmlNode->name) === 'object'))
482:         ) {
483:             $this->context = $this->context === self::CONTEXT_HTML_TAG ? self::CONTEXT_HTML_ATTRIBUTE_UNQUOTED_URL : self::CONTEXT_HTML_ATTRIBUTE_URL;
484:         }
485:     }
486: 
487: 
488:     private function processHtmlAttributeEnd(Token $token)
489:     {
490:         $this->context = self::CONTEXT_HTML_TAG;
491:         $this->output .= $token->text;
492:     }
493: 
494: 
495:     private function processComment(Token $token)
496:     {
497:         $leftOfs = ($tmp = strrpos($this->output, "\n")) === false ? 0 : $tmp + 1;
498:         $isLeftmost = trim(substr($this->output, $leftOfs)) === '';
499:         $isRightmost = substr($token->text, -1) === "\n";
500:         if ($isLeftmost && $isRightmost) {
501:             $this->output = substr($this->output, 0, $leftOfs);
502:         } else {
503:             $this->output .= substr($token->text, strlen(rtrim($token->text, "\n")));
504:         }
505:     }
506: 
507: 
508:     private function escape($s)
509:     {
510:         return preg_replace_callback('#<(\z|\?xml|\?)#', function ($m) {
511:             if ($m[1] === '?') {
512:                 trigger_error('Inline <?php ... ?> is deprecated, use {php ... } on line ' . $this->getLine(), E_USER_DEPRECATED);
513:                 return '<?';
514:             } else {
515:                 return '<<?php ?>' . $m[1];
516:             }
517:         }, $s);
518:     }
519: 
520: 
521:     /********************* macros ****************d*g**/
522: 
523: 
524:     /**
525:      * Generates code for {macro ...} to the output.
526:      * @param  string
527:      * @param  string
528:      * @param  string
529:      * @param  bool
530:      * @return MacroNode
531:      * @internal
532:      */
533:     public function openMacro($name, $args = null, $modifiers = null, $isRightmost = false, $nPrefix = null)
534:     {
535:         $node = $this->expandMacro($name, $args, $modifiers, $nPrefix);
536:         if ($node->empty) {
537:             $this->writeCode((string) $node->openingCode, $node->replaced, $isRightmost);
538:             if ($node->prefix && $node->prefix !== MacroNode::PREFIX_TAG) {
539:                 $this->htmlNode->attrCode .= $node->attrCode;
540:             }
541:         } else {
542:             $this->macroNode = $node;
543:             $node->saved = [&$this->output, $isRightmost];
544:             $this->output = &$node->content;
545:             $this->output = '';
546:         }
547:         return $node;
548:     }
549: 
550: 
551:     /**
552:      * Generates code for {/macro ...} to the output.
553:      * @param  string
554:      * @param  string
555:      * @param  string
556:      * @param  bool
557:      * @return MacroNode
558:      * @internal
559:      */
560:     public function closeMacro($name, $args = null, $modifiers = null, $isRightmost = false, $nPrefix = null)
561:     {
562:         $node = $this->macroNode;
563: 
564:         if (
565:             !$node
566:             || ($node->name !== $name && $name !== '')
567:             || $modifiers
568:             || ($args && $node->args && !Helpers::startsWith("$node->args ", "$args "))
569:             || $nPrefix !== $node->prefix
570:         ) {
571:             $name = $nPrefix
572:                 ? "</{$this->htmlNode->name}> for " . Parser::N_PREFIX . implode(' and ' . Parser::N_PREFIX, array_keys($this->htmlNode->macroAttrs))
573:                 : '{/' . $name . ($args ? ' ' . $args : '') . $modifiers . '}';
574:             throw new CompileException("Unexpected $name" . ($node ? ', expecting ' . self::printEndTag($node->prefix ? $this->htmlNode : $node) : ''));
575:         }
576: 
577:         $this->macroNode = $node->parentNode;
578:         if (!$node->args) {
579:             $node->setArgs($args);
580:         }
581: 
582:         if ($node->prefix === MacroNode::PREFIX_NONE) {
583:             $parts = explode($node->htmlNode->innerMarker, $node->content);
584:             if (count($parts) === 3) { // markers may be destroyed by inner macro
585:                 $node->innerContent = $parts[1];
586:             }
587:         }
588: 
589:         $node->closing = true;
590:         $node->endLine = $node->prefix ? $node->htmlNode->endLine : $this->getLine();
591:         $node->macro->nodeClosed($node);
592: 
593:         if (isset($parts[1]) && $node->innerContent !== $parts[1]) {
594:             $node->content = implode($node->htmlNode->innerMarker, [$parts[0], $node->innerContent, $parts[2]]);
595:         }
596: 
597:         if ($node->prefix && $node->prefix !== MacroNode::PREFIX_TAG) {
598:             $this->htmlNode->attrCode .= $node->attrCode;
599:         }
600:         $this->output = &$node->saved[0];
601:         $this->writeCode((string) $node->openingCode, $node->replaced, $node->saved[1]);
602:         $this->output .= $node->content;
603:         $this->writeCode((string) $node->closingCode, $node->replaced, $isRightmost);
604:         return $node;
605:     }
606: 
607: 
608:     private function writeCode($code, $isReplaced, $isRightmost)
609:     {
610:         if ($isRightmost) {
611:             $leftOfs = ($tmp = strrpos($this->output, "\n")) === false ? 0 : $tmp + 1;
612:             $isLeftmost = trim(substr($this->output, $leftOfs)) === '';
613:             if ($isReplaced === null) {
614:                 $isReplaced = preg_match('#<\?php.*\secho\s#As', $code);
615:             }
616:             if ($isLeftmost && !$isReplaced) {
617:                 $this->output = substr($this->output, 0, $leftOfs); // alone macro without output -> remove indentation
618:                 if (substr($code, -2) !== '?>') {
619:                     $code .= '<?php ?>'; // consume new line
620:                 }
621:             } elseif (substr($code, -2) === '?>') {
622:                 $code .= "\n"; // double newline to avoid newline eating by PHP
623:             }
624:         }
625:         $this->output .= $code;
626:     }
627: 
628: 
629:     /**
630:      * Generates code for macro <tag n:attr> to the output.
631:      * @param  string HTML tag
632:      * @return void
633:      * @internal
634:      */
635:     public function writeAttrsMacro($html)
636:     {
637:         //     none-2 none-1 tag-1 tag-2       <el attr-1 attr-2>   /tag-2 /tag-1 [none-2] [none-1] inner-2 inner-1
638:         // /inner-1 /inner-2 [none-1] [none-2] tag-1 tag-2  </el>   /tag-2 /tag-1 /none-1 /none-2
639:         $attrs = $this->htmlNode->macroAttrs;
640:         $left = $right = [];
641: 
642:         foreach ($this->macros as $name => $foo) {
643:             $attrName = MacroNode::PREFIX_INNER . "-$name";
644:             if (isset($attrs[$attrName])) {
645:                 if ($this->htmlNode->closing) {
646:                     $left[] = function () use ($name) {
647:                         $this->closeMacro($name, '', null, false, MacroNode::PREFIX_INNER);
648:                     };
649:                 } else {
650:                     array_unshift($right, function () use ($name, $attrs, $attrName) {
651:                         if ($this->openMacro($name, $attrs[$attrName], null, false, MacroNode::PREFIX_INNER)->empty) {
652:                             throw new CompileException("Unable to use empty macro as n:$attrName.");
653:                         }
654:                     });
655:                 }
656:                 unset($attrs[$attrName]);
657:             }
658:         }
659: 
660:         $innerMarker = '';
661:         if ($this->htmlNode->closing) {
662:             $left[] = function () {
663:                 $this->output .= $this->htmlNode->innerMarker;
664:             };
665:         } else {
666:             array_unshift($right, function () use (&$innerMarker) {
667:                 $this->output .= $innerMarker;
668:             });
669:         }
670: 
671: 
672:         foreach (array_reverse($this->macros) as $name => $foo) {
673:             $attrName = MacroNode::PREFIX_TAG . "-$name";
674:             if (isset($attrs[$attrName])) {
675:                 $left[] = function () use ($name, $attrs, $attrName) {
676:                     if ($this->openMacro($name, $attrs[$attrName], null, false, MacroNode::PREFIX_TAG)->empty) {
677:                         throw new CompileException("Unable to use empty macro as n:$attrName.");
678:                     }
679:                 };
680:                 array_unshift($right, function () use ($name) {
681:                     $this->closeMacro($name, '', null, false, MacroNode::PREFIX_TAG);
682:                 });
683:                 unset($attrs[$attrName]);
684:             }
685:         }
686: 
687:         foreach ($this->macros as $name => $foo) {
688:             if (isset($attrs[$name])) {
689:                 if ($this->htmlNode->closing) {
690:                     $right[] = function () use ($name) {
691:                         $this->closeMacro($name, '', null, false, MacroNode::PREFIX_NONE);
692:                     };
693:                 } else {
694:                     array_unshift($left, function () use ($name, $attrs, &$innerMarker) {
695:                         $node = $this->openMacro($name, $attrs[$name], null, false, MacroNode::PREFIX_NONE);
696:                         if ($node->empty) {
697:                             unset($this->htmlNode->macroAttrs[$name]); // don't call closeMacro
698:                         } elseif (!$innerMarker) {
699:                             $this->htmlNode->innerMarker = $innerMarker = '<n:q' . count($this->placeholders) . 'q>';
700:                             $this->placeholders[$innerMarker] = '';
701:                         }
702:                     });
703:                 }
704:                 unset($attrs[$name]);
705:             }
706:         }
707: 
708:         if ($attrs) {
709:             throw new CompileException(
710:                 'Unknown attribute ' . Parser::N_PREFIX
711:                 . implode(' and ' . Parser::N_PREFIX, array_keys($attrs))
712:                 . (($t = Helpers::getSuggestion(array_keys($this->macros), key($attrs))) ? ', did you mean ' . Parser::N_PREFIX . $t . '?' : '')
713:             );
714:         }
715: 
716:         if (!$this->htmlNode->closing) {
717:             $this->htmlNode->attrCode = &$this->placeholders[$uniq = ' n:q' . count($this->placeholders) . 'q'];
718:             $html = substr_replace($html, $uniq, strrpos($html, '/>') ?: strrpos($html, '>'), 0);
719:         }
720: 
721:         foreach ($left as $func) {
722:             $func();
723:         }
724: 
725:         $this->output .= $html;
726: 
727:         foreach ($right as $func) {
728:             $func();
729:         }
730: 
731:         if ($right && substr($this->output, -2) === '?>') {
732:             $this->output .= "\n";
733:         }
734:     }
735: 
736: 
737:     /**
738:      * Expands macro and returns node & code.
739:      * @param  string
740:      * @param  string
741:      * @param  string
742:      * @return MacroNode
743:      * @internal
744:      */
745:     public function expandMacro($name, $args, $modifiers = null, $nPrefix = null)
746:     {
747:         if (empty($this->macros[$name])) {
748:             $hint = (($t = Helpers::getSuggestion(array_keys($this->macros), $name)) ? ", did you mean {{$t}}?" : '')
749:                 . (in_array($this->context, [self::CONTEXT_HTML_JS, self::CONTEXT_HTML_CSS], true) ? ' (in JavaScript or CSS, try to put a space after bracket or use n:syntax=off)' : '');
750:             throw new CompileException("Unknown macro {{$name}}$hint");
751:         }
752: 
753:         if ($modifiers && preg_match('#\|(no)?safeurl(?!\w)#i', $modifiers, $m)) {
754:             $hint = $m[1] ? '|nocheck' : '|checkurl';
755:             $modifiers = str_replace($m[0], $hint, $modifiers);
756:             trigger_error("Modifier $m[0] is deprecated, please replace it with $hint.", E_USER_DEPRECATED);
757:         }
758: 
759:         if (strpbrk($name, '=~%^&_')) {
760:             if (in_array($this->context, [self::CONTEXT_HTML_ATTRIBUTE_URL, self::CONTEXT_HTML_ATTRIBUTE_UNQUOTED_URL], true)) {
761:                 if (!Helpers::removeFilter($modifiers, 'nosafeurl|nocheck') && !preg_match('#\|datastream(?=\s|\||\z)#i', $modifiers)) {
762:                     $modifiers .= '|checkurl';
763:                 }
764:             }
765: 
766:             if (!Helpers::removeFilter($modifiers, 'noescape')) {
767:                 $modifiers .= '|escape';
768:                 if ($this->context === self::CONTEXT_HTML_JS && $name === '=' && preg_match('#["\'] *\z#', $this->tokens[$this->position - 1]->text)) {
769:                     throw new CompileException("Do not place {$this->tokens[$this->position]->text} inside quotes.");
770:                 }
771:             }
772:         }
773: 
774:         if ($nPrefix === MacroNode::PREFIX_INNER && !strcasecmp($this->htmlNode->name, 'script')) {
775:             $context = [$this->contentType, self::CONTEXT_HTML_JS];
776:         } elseif ($nPrefix === MacroNode::PREFIX_INNER && !strcasecmp($this->htmlNode->name, 'style')) {
777:             $context = [$this->contentType, self::CONTEXT_HTML_CSS];
778:         } elseif ($nPrefix) {
779:             $context = [$this->contentType, self::CONTEXT_HTML_TEXT];
780:         } else {
781:             $context = [$this->contentType, $this->context];
782:         }
783: 
784:         foreach (array_reverse($this->macros[$name]) as $macro) {
785:             $node = new MacroNode($macro, $name, $args, $modifiers, $this->macroNode, $this->htmlNode, $nPrefix);
786:             $node->context = $context;
787:             $node->startLine = $nPrefix ? $this->htmlNode->startLine : $this->getLine();
788:             if ($macro->nodeOpened($node) !== false) {
789:                 return $node;
790:             }
791:         }
792: 
793:         throw new CompileException('Unknown ' . ($nPrefix
794:             ? 'attribute ' . Parser::N_PREFIX . ($nPrefix === MacroNode::PREFIX_NONE ? '' : "$nPrefix-") . $name
795:             : 'macro {' . $name . ($args ? " $args" : '') . '}'
796:         ));
797:     }
798: 
799: 
800:     private static function printEndTag($node)
801:     {
802:         if ($node instanceof HtmlNode) {
803:             return  "</{$node->name}> for " . Parser::N_PREFIX
804:                 . implode(' and ' . Parser::N_PREFIX, array_keys($node->macroAttrs));
805:         } else {
806:             return "{/$node->name}";
807:         }
808:     }
809: }
810: 
Nette 2.4-20180918 API API documentation generated by ApiGen 2.8.0