%PDF- %PDF-
Direktori : /www/varak.net/losik.varak.net/vendor/latte/latte/src/Latte/Compiler/ |
Current File : /www/varak.net/losik.varak.net/vendor/latte/latte/src/Latte/Compiler/Compiler.php |
<?php /** * This file is part of the Latte (https://latte.nette.org) * Copyright (c) 2008 David Grudl (https://davidgrudl.com) */ declare(strict_types=1); namespace Latte; /** * Latte compiler. */ class Compiler { use Strict; /** Context-aware escaping content types */ public const CONTENT_HTML = Engine::CONTENT_HTML, CONTENT_XHTML = Engine::CONTENT_XHTML, CONTENT_XML = Engine::CONTENT_XML, CONTENT_JS = Engine::CONTENT_JS, CONTENT_CSS = Engine::CONTENT_CSS, CONTENT_ICAL = Engine::CONTENT_ICAL, CONTENT_TEXT = Engine::CONTENT_TEXT; /** @internal Context-aware escaping HTML contexts */ public const CONTEXT_HTML_TEXT = null, CONTEXT_HTML_TAG = 'Tag', CONTEXT_HTML_ATTRIBUTE = 'Attr', CONTEXT_HTML_ATTRIBUTE_JS = 'AttrJs', CONTEXT_HTML_ATTRIBUTE_CSS = 'AttrCss', CONTEXT_HTML_ATTRIBUTE_URL = 'AttrUrl', CONTEXT_HTML_ATTRIBUTE_UNQUOTED_URL = 'AttrUnquotedUrl', CONTEXT_HTML_COMMENT = 'Comment', CONTEXT_HTML_BOGUS_COMMENT = 'Bogus', CONTEXT_HTML_CSS = 'Css', CONTEXT_HTML_JS = 'Js', CONTEXT_XML_TEXT = self::CONTEXT_HTML_TEXT, CONTEXT_XML_TAG = self::CONTEXT_HTML_TAG, CONTEXT_XML_ATTRIBUTE = self::CONTEXT_HTML_ATTRIBUTE, CONTEXT_XML_COMMENT = self::CONTEXT_HTML_COMMENT, CONTEXT_XML_BOGUS_COMMENT = self::CONTEXT_HTML_BOGUS_COMMENT; /** @var string[] @internal */ public $placeholders = []; /** @var string|null */ public $paramsExtraction; /** @var Token[] */ private $tokens; /** @var string pointer to current node content */ private $output; /** @var int position on source template */ private $position; /** @var array<string, Macro[]> */ private $macros = []; /** @var string[] of orig name */ private $functions = []; /** @var string[] of orig name */ private $filters = []; /** @var int[] Macro flags */ private $flags; /** @var HtmlNode|null */ private $htmlNode; /** @var MacroNode|null */ private $macroNode; /** @var string */ private $contentType = self::CONTENT_HTML; /** @var string|null */ private $context; /** @var mixed */ private $lastAttrValue; /** @var int */ private $tagOffset; /** @var bool */ private $inHead; /** @var array<string, ?array{body: string, arguments: string, returns: string, comment: ?string}> */ private $methods = []; /** @var array<string, mixed> */ private $properties = []; /** @var array<string, mixed> */ private $constants = []; /** @var Policy|null */ private $policy; /** * Adds new macro with Macro flags. * @return static */ public function addMacro(string $name, Macro $macro, ?int $flags = null) { if (!preg_match('#^[a-z_=]\w*(?:[.:-]\w+)*$#iD', $name)) { throw new \LogicException("Invalid tag name '$name'."); } elseif (!isset($this->flags[$name])) { $this->flags[$name] = $flags ?: Macro::DEFAULT_FLAGS; } elseif ($flags && $this->flags[$name] !== $flags) { throw new \LogicException("Incompatible flags for tag '$name'."); } $this->macros[$name][] = $macro; return $this; } /** * Registers run-time functions. * @param string[] $names * @return static */ public function setFunctions(array $names) { $this->functions = array_combine(array_map('strtolower', $names), $names); return $this; } public function setFilters(array $names) { $this->filters = $names; return $this; } /** * Compiles tokens to PHP file * @param Token[] $tokens */ public function compile(array $tokens, string $className, ?string $comment = null, bool $strictMode = false): string { $code = "<?php\n\n" . ($strictMode ? "declare(strict_types=1);\n\n" : '') . "use Latte\\Runtime as LR;\n\n" . ($comment === null ? '' : '/** ' . str_replace('*/', '* /', $comment) . " */\n") . "final class $className extends Latte\\Runtime\\Template\n{\n" . $this->buildClassBody($tokens) . "\n\n}\n"; $code = PhpHelpers::inlineHtmlToEcho($code); $code = PhpHelpers::reformatCode($code); return $code; } /** * @param Token[] $tokens */ private function buildClassBody(array $tokens): string { $this->tokens = $tokens; $output = ''; $this->output = &$output; $this->inHead = true; $this->htmlNode = $this->macroNode = $this->context = $this->paramsExtraction = null; $this->placeholders = $this->properties = $this->constants = []; $this->methods = ['main' => null, 'prepare' => null]; $macroHandlers = new \SplObjectStorage; if ($this->macros) { array_map([$macroHandlers, 'attach'], array_merge(...array_values($this->macros))); } foreach ($macroHandlers as $handler) { $handler->initialize($this); } foreach ($tokens as $this->position => $token) { if ($this->inHead && !( $token->type === $token::COMMENT || $token->type === $token::MACRO_TAG && ($this->flags[$token->name] ?? null) & Macro::ALLOWED_IN_HEAD || $token->type === $token::TEXT && trim($token->text) === '' )) { $this->inHead = false; } $this->{"process$token->type"}($token); } while ($this->htmlNode) { $this->closeHtmlTag('end'); } while ($this->macroNode) { if ($this->macroNode->parentNode) { throw new CompileException('Missing {/' . $this->macroNode->name . '}'); } if (~$this->flags[$this->macroNode->name] & Macro::AUTO_CLOSE) { throw new CompileException('Missing ' . self::printEndTag($this->macroNode)); } $this->closeMacro($this->macroNode->name); } $prepare = $epilogs = ''; foreach ($macroHandlers as $handler) { $res = $handler->finalize(); $prepare .= empty($res[0]) ? '' : "<?php $res[0] ?>"; $epilogs = (empty($res[1]) ? '' : "<?php $res[1] ?>") . $epilogs; } $extractParams = $this->paramsExtraction ?? 'extract($this->params);'; $this->addMethod('main', $this->expandTokens($extractParams . "?>\n$output$epilogs<?php return get_defined_vars();"), '', 'array'); if ($prepare) { $this->addMethod('prepare', $extractParams . "?>$prepare<?php", '', 'void'); } if ($this->contentType !== self::CONTENT_HTML) { $this->addConstant('CONTENT_TYPE', $this->contentType); } $members = []; foreach ($this->constants as $name => $value) { $members[] = "\tprotected const $name = " . PhpHelpers::dump($value, true) . ';'; } foreach ($this->properties as $name => $value) { $members[] = "\tpublic $$name = " . PhpHelpers::dump($value, true) . ';'; } foreach (array_filter($this->methods) as $name => $method) { $members[] = ($method['comment'] === null ? '' : "\n\t/** " . str_replace('*/', '* /', $method['comment']) . ' */') . "\n\tpublic function $name($method[arguments])" . ($method['returns'] ? ': ' . $method['returns'] : '') . "\n\t{\n" . ($method['body'] ? "\t\t$method[body]\n" : '') . "\t}"; } return implode("\n\n", $members); } /** @return static */ public function setPolicy(?Policy $policy) { $this->policy = $policy; return $this; } public function getPolicy(): ?Policy { return $this->policy; } /** @return static */ public function setContentType(string $type) { $this->contentType = $type; $this->context = null; return $this; } public function getContentType(): string { return $this->contentType; } public function getMacroNode(): ?MacroNode { return $this->macroNode; } /** * @return Macro[][] */ public function getMacros(): array { return $this->macros; } /** * @return string[] */ public function getFunctions(): array { return $this->functions; } /** * @return string[] */ public function getFilters(): array { return $this->filters; } /** * Returns current line number. */ public function getLine(): ?int { return isset($this->tokens[$this->position]) ? $this->tokens[$this->position]->line : null; } public function isInHead(): bool { return $this->inHead; } /** * Adds custom method to template. * @internal */ public function addMethod( string $name, string $body, string $arguments = '', string $returns = '', ?string $comment = null ): void { $body = trim($body); $this->methods[$name] = compact('body', 'arguments', 'returns', 'comment'); } /** * Returns custom methods. * @return array<string, ?array{body: string, arguments: string, returns: string, comment: ?string}> * @internal */ public function getMethods(): array { return $this->methods; } /** * Adds custom property to template. * @param mixed $value * @internal */ public function addProperty(string $name, $value): void { $this->properties[$name] = $value; } /** * Returns custom properites. * @return array<string, mixed> * @internal */ public function getProperties(): array { return $this->properties; } /** * Adds custom constant to template. * @param mixed $value * @internal */ public function addConstant(string $name, $value): void { $this->constants[$name] = $value; } /** @internal */ public function expandTokens(string $s): string { return strtr($s, $this->placeholders); } private function processText(Token $token): void { if ( $this->lastAttrValue === '' && $this->context && Helpers::startsWith($this->context, self::CONTEXT_HTML_ATTRIBUTE) ) { $this->lastAttrValue = $token->text; } $this->output .= $this->escape($token->text); } private function processMacroTag(Token $token): void { if ( $this->context === self::CONTEXT_HTML_TAG || $this->context && Helpers::startsWith($this->context, self::CONTEXT_HTML_ATTRIBUTE) ) { $this->lastAttrValue = true; } $isRightmost = !isset($this->tokens[$this->position + 1]) || substr($this->tokens[$this->position + 1]->text, 0, 1) === "\n"; if ($token->closing) { $this->closeMacro($token->name, $token->value, $token->modifiers, $isRightmost); } else { if (!$token->empty && ($this->flags[$token->name] ?? null) & Macro::AUTO_EMPTY) { $pos = $this->position; while (($t = $this->tokens[++$pos] ?? null) && ($t->type !== Token::MACRO_TAG || $t->name !== $token->name) && ($t->type !== Token::HTML_ATTRIBUTE_BEGIN || $t->name !== Parser::N_PREFIX . $token->name)); $token->empty = $t ? !$t->closing : true; if ($token->empty) { $tmp = substr($token->text, 0, -1) . ' /}'; trigger_error("Auto-empty behaviour is deprecated, replace {$token->text} with $tmp (on line {$this->getLine()})", E_USER_DEPRECATED); } } $node = $this->openMacro($token->name, $token->value, $token->modifiers, $isRightmost); if ($token->empty) { if ($node->empty) { throw new CompileException("Unexpected /} in tag {$token->text}"); } $this->closeMacro($token->name, '', '', $isRightmost); } } } private function processHtmlTagBegin(Token $token): void { if ($token->closing) { while ($this->htmlNode) { if (strcasecmp($this->htmlNode->name, $token->name) === 0) { break; } $this->closeHtmlTag("</$token->name>"); } if (!$this->htmlNode) { $this->htmlNode = new HtmlNode($token->name); } $this->htmlNode->closing = true; $this->htmlNode->endLine = $this->getLine(); $this->context = self::CONTEXT_HTML_TEXT; } elseif ($token->text === '<!--') { $this->context = self::CONTEXT_HTML_COMMENT; } elseif ($token->text === '<?' || $token->text === '<!') { $this->context = self::CONTEXT_HTML_BOGUS_COMMENT; } else { $this->htmlNode = new HtmlNode($token->name, $this->htmlNode); $this->htmlNode->startLine = $this->getLine(); $this->context = self::CONTEXT_HTML_TAG; } $this->tagOffset = strlen($this->output); $this->output .= $this->escape($token->text); } private function processHtmlTagEnd(Token $token): void { if (in_array($this->context, [self::CONTEXT_HTML_COMMENT, self::CONTEXT_HTML_BOGUS_COMMENT], true)) { $this->output .= $token->text; $this->context = self::CONTEXT_HTML_TEXT; return; } $htmlNode = $this->htmlNode; $end = ''; if (!$htmlNode->closing) { $htmlNode->empty = strpos($token->text, '/') !== false; if (in_array($this->contentType, [self::CONTENT_HTML, self::CONTENT_XHTML], true)) { $emptyElement = isset(Helpers::$emptyElements[strtolower($htmlNode->name)]); $htmlNode->empty = $htmlNode->empty || $emptyElement; if ($htmlNode->empty) { // auto-correct $space = substr(strstr($token->text, '>'), 1); if ($emptyElement) { $token->text = ($this->contentType === self::CONTENT_XHTML ? ' />' : '>') . $space; } else { $token->text = '>'; $end = "</$htmlNode->name>" . $space; } } } } if ($htmlNode->macroAttrs) { $html = substr($this->output, $this->tagOffset) . $token->text; $this->output = substr($this->output, 0, $this->tagOffset); $this->writeAttrsMacro($html, $emptyElement ?? null); } else { $this->output .= $token->text . $end; } if ($htmlNode->empty) { $htmlNode->closing = true; if ($htmlNode->macroAttrs) { $this->writeAttrsMacro($end); } } $this->context = self::CONTEXT_HTML_TEXT; if ($htmlNode->closing) { $this->htmlNode = $this->htmlNode->parentNode; } elseif ( (($lower = strtolower($htmlNode->name)) === 'script' || $lower === 'style') && (!isset($htmlNode->attrs['type']) || preg_match('#(java|j|ecma|live)script|module|json|css|plain#i', $htmlNode->attrs['type'])) ) { $this->context = $lower === 'script' ? self::CONTEXT_HTML_JS : self::CONTEXT_HTML_CSS; } } private function processHtmlAttributeBegin(Token $token): void { if (Helpers::startsWith($token->name, Parser::N_PREFIX)) { $name = substr($token->name, strlen(Parser::N_PREFIX)); if (isset($this->htmlNode->macroAttrs[$name])) { throw new CompileException("Found multiple attributes {$token->name}."); } elseif ($this->macroNode && $this->macroNode->htmlNode === $this->htmlNode) { throw new CompileException("n:attribute must not appear inside tags; found {$token->name} inside {{$this->macroNode->name}}."); } $this->htmlNode->macroAttrs[$name] = $token->value; return; } $this->lastAttrValue = &$this->htmlNode->attrs[$token->name]; $this->output .= $this->escape($token->text); $lower = strtolower($token->name); if (in_array($token->value, ['"', "'"], true)) { $this->lastAttrValue = ''; $this->context = self::CONTEXT_HTML_ATTRIBUTE; if (in_array($this->contentType, [self::CONTENT_HTML, self::CONTENT_XHTML], true)) { if (Helpers::startsWith($lower, 'on')) { $this->context = self::CONTEXT_HTML_ATTRIBUTE_JS; } elseif ($lower === 'style') { $this->context = self::CONTEXT_HTML_ATTRIBUTE_CSS; } } } else { $this->lastAttrValue = $token->value; $this->context = self::CONTEXT_HTML_TAG; } if ( in_array($this->contentType, [self::CONTENT_HTML, self::CONTENT_XHTML], true) && (in_array($lower, ['href', 'src', 'action', 'formaction'], true) || ($lower === 'data' && strtolower($this->htmlNode->name) === 'object')) ) { $this->context = $this->context === self::CONTEXT_HTML_TAG ? self::CONTEXT_HTML_ATTRIBUTE_UNQUOTED_URL : self::CONTEXT_HTML_ATTRIBUTE_URL; } } private function processHtmlAttributeEnd(Token $token): void { $this->context = self::CONTEXT_HTML_TAG; $this->output .= $token->text; } private function processComment(Token $token): void { $leftOfs = ($tmp = strrpos($this->output, "\n")) === false ? 0 : $tmp + 1; $isLeftmost = trim(substr($this->output, $leftOfs)) === ''; $isRightmost = substr($token->text, -1) === "\n"; if ($isLeftmost && $isRightmost) { $this->output = substr($this->output, 0, $leftOfs); } else { $this->output .= substr($token->text, strlen(rtrim($token->text, "\n"))); } } private function escape(string $s): string { return substr(str_replace('<?', '<<?php ?>?', $s . '?'), 0, -1); } /********************* macros ****************d*g**/ /** * Generates code for {macro ...} to the output. * @internal */ public function openMacro( string $name, string $args = '', string $modifiers = '', bool $isRightmost = false, ?string $nPrefix = null ): MacroNode { $node = $this->expandMacro($name, $args, $modifiers, $nPrefix); if ($node->empty) { $this->writeCode((string) $node->openingCode, $node->replaced, $isRightmost); if ($node->prefix && $node->prefix !== MacroNode::PREFIX_TAG) { $this->htmlNode->attrCode .= $node->attrCode; } } else { $this->macroNode = $node; $node->saved = [&$this->output, $isRightmost]; $this->output = &$node->content; $this->output = ''; } return $node; } /** * Generates code for {/macro ...} to the output. * @internal */ public function closeMacro( string $name, string $args = '', string $modifiers = '', bool $isRightmost = false, ?string $nPrefix = null ): MacroNode { $node = $this->macroNode; if ( !$node || ($node->name !== $name && $name !== '') || $modifiers || ($args !== '' && $node->args !== '' && !Helpers::startsWith($node->args . ' ', $args . ' ')) || $nPrefix !== $node->prefix ) { $name = $nPrefix ? "</{$this->htmlNode->name}> for " . Parser::N_PREFIX . implode(' and ' . Parser::N_PREFIX, array_keys($this->htmlNode->macroAttrs)) : '{/' . $name . ($args ? ' ' . $args : '') . $modifiers . '}'; throw new CompileException("Unexpected $name" . ($node ? ', expecting ' . self::printEndTag($node->prefix ? $this->htmlNode : $node) : '')); } $this->macroNode = $node->parentNode; if ($node->args === '') { $node->setArgs($args); } if ($node->prefix === MacroNode::PREFIX_NONE) { $parts = explode($node->htmlNode->innerMarker, $node->content); if (count($parts) === 3) { // markers may be destroyed by inner macro $node->innerContent = $parts[1]; } } $node->closing = true; $node->endLine = $node->prefix ? $node->htmlNode->endLine : $this->getLine(); $node->macro->nodeClosed($node); if (isset($parts[1]) && $node->innerContent !== $parts[1]) { $node->content = implode($node->htmlNode->innerMarker, [$parts[0], $node->innerContent, $parts[2]]); } if ($node->prefix && $node->prefix !== MacroNode::PREFIX_TAG) { $this->htmlNode->attrCode .= $node->attrCode; } $this->output = &$node->saved[0]; $this->writeCode((string) $node->openingCode, $node->replaced, $node->saved[1]); $this->output .= $node->content; $this->writeCode((string) $node->closingCode, $node->replaced, $isRightmost, true); return $node; } private function writeCode(string $code, ?bool $isReplaced, ?bool $isRightmost, bool $isClosing = false): void { if ($isRightmost) { $leftOfs = ($tmp = strrpos($this->output, "\n")) === false ? 0 : $tmp + 1; $isLeftmost = trim(substr($this->output, $leftOfs)) === ''; if ($isReplaced === null) { $isReplaced = preg_match('#<\?php.*\secho\s#As', $code); } if ($isLeftmost && !$isReplaced) { $this->output = substr($this->output, 0, $leftOfs); // alone macro without output -> remove indentation if (!$isClosing && substr($code, -2) !== '?>') { $code .= '<?php ?>'; // consume new line } } elseif (substr($code, -2) === '?>') { $code .= "\n"; // double newline to avoid newline eating by PHP } } $this->output .= $code; } /** * Generates code for macro <tag n:attr> to the output. * @internal */ public function writeAttrsMacro(string $html, ?bool $empty = null): void { // 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 // /inner-1 /inner-2 [none-1] [none-2] tag-1 tag-2 </el> /tag-2 /tag-1 /none-1 /none-2 $attrs = $this->htmlNode->macroAttrs; $left = $right = []; foreach ($this->macros as $name => $foo) { $attrName = MacroNode::PREFIX_INNER . "-$name"; if (!isset($attrs[$attrName])) { continue; } if ($empty) { trigger_error("Unexpected n:$attrName on void element <{$this->htmlNode->name}> (on line {$this->getLine()}", E_USER_WARNING); } if ($this->htmlNode->closing) { $left[] = function () use ($name) { $this->closeMacro($name, '', '', false, MacroNode::PREFIX_INNER); }; } else { array_unshift($right, function () use ($name, $attrs, $attrName) { if ($this->openMacro($name, $attrs[$attrName], '', false, MacroNode::PREFIX_INNER)->empty) { throw new CompileException("Unexpected prefix in n:$attrName."); } }); } unset($attrs[$attrName]); } $innerMarker = ''; if ($this->htmlNode->closing) { $left[] = function () { $this->output .= $this->htmlNode->innerMarker; }; } else { array_unshift($right, function () use (&$innerMarker) { $this->output .= $innerMarker; }); } foreach (array_reverse($this->macros) as $name => $foo) { $attrName = MacroNode::PREFIX_TAG . "-$name"; if (!isset($attrs[$attrName])) { continue; } if ($empty) { trigger_error("Unexpected n:$attrName on void element <{$this->htmlNode->name}> (on line {$this->getLine()}", E_USER_WARNING); } $left[] = function () use ($name, $attrs, $attrName) { if ($this->openMacro($name, $attrs[$attrName], '', false, MacroNode::PREFIX_TAG)->empty) { throw new CompileException("Unexpected prefix in n:$attrName."); } }; array_unshift($right, function () use ($name) { $this->closeMacro($name, '', '', false, MacroNode::PREFIX_TAG); }); unset($attrs[$attrName]); } foreach ($this->macros as $name => $foo) { if (isset($attrs[$name])) { if ($this->htmlNode->closing) { $right[] = function () use ($name) { $this->closeMacro($name, '', '', false, MacroNode::PREFIX_NONE); }; } else { array_unshift($left, function () use ($name, $attrs, &$innerMarker) { $node = $this->openMacro($name, $attrs[$name], '', false, MacroNode::PREFIX_NONE); if ($node->empty) { unset($this->htmlNode->macroAttrs[$name]); // don't call closeMacro } elseif (!$innerMarker) { $this->htmlNode->innerMarker = $innerMarker = '<n:q' . count($this->placeholders) . 'q>'; $this->placeholders[$innerMarker] = ''; } }); } unset($attrs[$name]); } } if ($attrs) { throw new CompileException( 'Unknown attribute ' . Parser::N_PREFIX . implode(' and ' . Parser::N_PREFIX, array_keys($attrs)) . (($t = Helpers::getSuggestion(array_keys($this->macros), key($attrs))) ? ', did you mean ' . Parser::N_PREFIX . $t . '?' : '') ); } if (!$this->htmlNode->closing) { $this->htmlNode->attrCode = &$this->placeholders[$uniq = ' n:q' . count($this->placeholders) . 'q']; $html = substr_replace($html, $uniq, strrpos($html, '/>') ?: strrpos($html, '>'), 0); } foreach ($left as $func) { $func(); } $this->output .= $html; foreach ($right as $func) { $func(); } if ($right && substr($this->output, -2) === '?>') { $this->output .= "\n"; } } /** * Expands macro and returns node & code. * @internal */ public function expandMacro(string $name, string $args, string $modifiers = '', ?string $nPrefix = null): MacroNode { if (empty($this->macros[$name])) { $hint = (($t = Helpers::getSuggestion(array_keys($this->macros), $name)) ? ", did you mean {{$t}}?" : '') . (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)' : ''); throw new CompileException("Unknown tag {{$name}}$hint"); } elseif ($this->policy && !$this->policy->isMacroAllowed($name)) { throw new SecurityViolationException('Tag ' . ($nPrefix ? "n:$name" : "{{$name}}") . ' is not allowed.'); } $modifiers = (string) $modifiers; if (strpbrk($name, '=~%^&_')) { if (in_array($this->context, [self::CONTEXT_HTML_ATTRIBUTE_URL, self::CONTEXT_HTML_ATTRIBUTE_UNQUOTED_URL], true)) { if (!Helpers::removeFilter($modifiers, 'nocheck')) { if (!preg_match('#\|datastream(?=\s|\||$)#Di', $modifiers)) { $modifiers .= '|checkUrl'; } } elseif ($this->policy && !$this->policy->isFilterAllowed('nocheck')) { throw new SecurityViolationException('Filter |nocheck is not allowed.'); } } if (!Helpers::removeFilter($modifiers, 'noescape')) { $modifiers .= '|escape'; } elseif ($this->policy && !$this->policy->isFilterAllowed('noescape')) { throw new SecurityViolationException('Filter |noescape is not allowed.'); } if ( $this->context === self::CONTEXT_HTML_JS && $name === '=' && preg_match('#["\']$#D', $this->tokens[$this->position - 1]->text) ) { throw new CompileException("Do not place {$this->tokens[$this->position]->text} inside quotes in JavaScript."); } } if ($nPrefix === MacroNode::PREFIX_INNER && !strcasecmp($this->htmlNode->name, 'script')) { $context = [$this->contentType, self::CONTEXT_HTML_JS]; } elseif ($nPrefix === MacroNode::PREFIX_INNER && !strcasecmp($this->htmlNode->name, 'style')) { $context = [$this->contentType, self::CONTEXT_HTML_CSS]; } elseif ($nPrefix) { $context = [$this->contentType, self::CONTEXT_HTML_TEXT]; } else { $context = [$this->contentType, $this->context]; } foreach (array_reverse($this->macros[$name]) as $macro) { $node = new MacroNode($macro, $name, $args, $modifiers, $this->macroNode, $this->htmlNode, $nPrefix); $node->context = $context; $node->startLine = $nPrefix ? $this->htmlNode->startLine : $this->getLine(); if ($macro->nodeOpened($node) !== false) { return $node; } } throw new CompileException('Unknown ' . ($nPrefix ? 'attribute ' . Parser::N_PREFIX . ($nPrefix === MacroNode::PREFIX_NONE ? '' : "$nPrefix-") . $name : 'tag {' . $name . ($args ? " $args" : '') . '}' )); } /** * @param HtmlNode|MacroNode $node */ private static function printEndTag($node): string { return $node instanceof HtmlNode ? "</{$node->name}> for " . Parser::N_PREFIX . implode(' and ' . Parser::N_PREFIX, array_keys($node->macroAttrs)) : "{/{$node->name}}"; } private function closeHtmlTag($token): void { if ($this->htmlNode->macroAttrs) { throw new CompileException("Unexpected $token, expecting " . self::printEndTag($this->htmlNode)); } elseif (in_array($this->contentType, [self::CONTENT_HTML, self::CONTENT_XHTML], true) && in_array(strtolower($this->htmlNode->name), ['script', 'style'], true) ) { trigger_error("Unexpected $token, expecting </{$this->htmlNode->name}> (on line {$this->getLine()})", E_USER_DEPRECATED); } $this->htmlNode = $this->htmlNode->parentNode; } }