1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Latte;
9:
10:
11: 12: 13:
14: class Compiler
15: {
16: use Strict;
17:
18:
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:
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: = 'Comment',
38: = '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: = self::CONTEXT_HTML_COMMENT,
46: = self::CONTEXT_HTML_BOGUS_COMMENT;
47:
48:
49: private $tokens;
50:
51:
52: private $output;
53:
54:
55: private $position;
56:
57:
58: private $macros = [];
59:
60:
61: private $flags;
62:
63:
64: private $htmlNode;
65:
66:
67: private $macroNode;
68:
69:
70: private $placeholders = [];
71:
72:
73: private $contentType = self::CONTENT_HTML;
74:
75:
76: private $context;
77:
78:
79: private $lastAttrValue;
80:
81:
82: private $tagOffset;
83:
84:
85: private $inHead;
86:
87:
88: private $methods = [];
89:
90:
91: private $properties = [];
92:
93:
94: 95: 96: 97: 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: 113: 114: 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: 193:
194: public function setContentType($type)
195: {
196: $this->contentType = $type;
197: $this->context = null;
198: return $this;
199: }
200:
201:
202: 203: 204:
205: public function getContentType()
206: {
207: trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED);
208: return $this->contentType;
209: }
210:
211:
212: 213: 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: 225:
226: public function getContext()
227: {
228: trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED);
229: return $this->context;
230: }
231:
232:
233: 234: 235:
236: public function getMacroNode()
237: {
238: return $this->macroNode;
239: }
240:
241:
242: 243: 244: 245:
246: public function getLine()
247: {
248: return isset($this->tokens[$this->position]) ? $this->tokens[$this->position]->line : null;
249: }
250:
251:
252: 253: 254:
255: public function isInHead()
256: {
257: return $this->inHead;
258: }
259:
260:
261: 262: 263: 264: 265:
266: public function addMethod($name, $body, $arguments = '')
267: {
268: $this->methods[$name] = ['body' => trim($body), 'arguments' => $arguments];
269: }
270:
271:
272: 273: 274: 275: 276:
277: public function getMethods()
278: {
279: return $this->methods;
280: }
281:
282:
283: 284: 285: 286: 287:
288: public function addProperty($name, $value)
289: {
290: $this->properties[$name] = $value;
291: }
292:
293:
294: 295: 296: 297: 298:
299: public function getProperties()
300: {
301: return $this->properties;
302: }
303:
304:
305:
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 ?>?' : '<!';
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) {
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 (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:
522:
523:
524: 525: 526: 527: 528: 529: 530: 531: 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: 553: 554: 555: 556: 557: 558: 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) {
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);
618: if (substr($code, -2) !== '?>') {
619: $code .= '<?php ?>';
620: }
621: } elseif (substr($code, -2) === '?>') {
622: $code .= "\n";
623: }
624: }
625: $this->output .= $code;
626: }
627:
628:
629: 630: 631: 632: 633: 634:
635: public function writeAttrsMacro($html)
636: {
637:
638:
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]);
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: 739: 740: 741: 742: 743: 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: