/** * Render a set of tokens with view substitutions * * @param array $tokens * @param mixed $view * @param array|null $partials * @return string */ public function render(array $tokens, $view, array $partials = null) { // Do some pre-initialization of variables used later in the routine $renderer = $this; $inLoop = false; if (is_scalar($view)) { // Iteration over lists will sometimes involve scalars $inLoop = true; } if (null === $partials) { $partials = []; } $rendered = ''; foreach ($tokens as $token) { list($type, $data) = $token; if ($value = $this->handlePragmas($token, $view)) { $rendered .= $value; continue; } switch ($type) { case Lexer::TOKEN_CONTENT: $rendered .= $data; break; case Lexer::TOKEN_VARIABLE: $value = $this->getValue($data, $view); if (is_scalar($value)) { if ($test = $this->handlePragmas($token, $value)) { $rendered .= $test; break; } $rendered .= '' === $value ? '' : $this->escape($value); break; } $pragmaView = [$data => $value]; if ($test = $this->handlePragmas($token, $pragmaView)) { $rendered .= $test; break; } $rendered .= (string) $value; break; case Lexer::TOKEN_VARIABLE_RAW: $value = $this->getValue($data, $view); $rendered .= $value; break; case Lexer::TOKEN_SECTION: if ($inLoop) { // In a loop, with scalar values; skip break; } $section = $this->getValue($data['name'], $view); if (!$section) { // Section is not a true value; skip break; } // Build the section view $sectionView = $section; if (is_bool($section)) { // For a boolean true, pass the current view $sectionView = $view; } if (is_array($section) || $section instanceof Traversable) { if (is_array($section) && $this->isAssocArray($section)) { // Nested view; pass it as the view $sectionView = $section; } else { // Iteration $renderedSection = ''; foreach ($section as $sectionView) { $renderedSection .= $this->render($data['content'], $sectionView); } $rendered .= $renderedSection; break; } } elseif (is_callable($section) && $this->isValidCallback($section)) { // Higher order section // Execute the callback, passing it the section's template // string, as well as a renderer lambda. $mustache = $this->mustache; $invokedPragmas = $this->invokedPragmas; $rendered .= call_user_func($section, $data['template'], function ($text) use($renderer, $mustache, $view, $partials) { $tokens = $mustache->tokenize($text); return $renderer->render($tokens, $view, $partials); }); $this->registerPragmas($invokedPragmas); break; } elseif (is_object($section)) { // In this case, the child object is the view. $sectionView = $section; } else { // All other types, simply pass the current view $sectionView = $view; } // Render the section $invokedPragmas = $this->invokedPragmas; $rendered .= $this->render($data['content'], $sectionView); $this->registerPragmas($invokedPragmas); break; case Lexer::TOKEN_SECTION_INVERT: if ($inLoop) { // In a loop, with scalar values; skip break; } $section = $this->getValue($data['name'], $view); if ($section) { // If a value exists for the section, we skip it $rendered .= ''; break; } // Otherwise, we render it $invokedPragmas = $this->invokedPragmas; $rendered .= $this->render($data['content'], $view); $this->registerPragmas($invokedPragmas); break; case Lexer::TOKEN_PLACEHOLDER: if ($inLoop) { // In a loop, with scalar values; skip break; } $rendered .= $this->render($data['content'], $view); break; case Lexer::TOKEN_PARTIAL: if ($inLoop) { // In a loop, with scalar values; skip break; } $invokedPragmas = $this->invokedPragmas; $this->clearInvokedPragmas(); if (!isset($data['tokens'])) { $name = $data['partial']; if (isset($partials[$data['partial']])) { // Partial invoked is an aliased partial $rendered .= $this->render($partials[$data['partial']], $view); } else { $partialTokens = $this->mustache->tokenize($data['partial']); $rendered .= $this->render($partialTokens, $view); } $this->registerPragmas($invokedPragmas); break; } $rendered .= $this->render($data['tokens'], $view); $this->registerPragmas($invokedPragmas); break; case Lexer::TOKEN_PRAGMA: $this->registerPragma($data); break; case Lexer::TOKEN_DELIM_SET: case Lexer::TOKEN_COMMENT: default: // do nothing; only necessary for tokenization/parsing break; } } return $rendered; }