$row) { $frame = is_array($row) ? $row : ['.' => $row]; $frame['@index'] = $index + 1; $frame['@first'] = $index === 0; $frame['@last'] = $index === $total - 1; $out .= self::renderStack($body, array_merge($stack, [$frame])); } return $out; } if (is_array($value)) { return self::renderStack($body, array_merge($stack, [$value])); } return self::renderStack($body, $stack); } private static function renderVariables(string $template, array $stack): string { return (string) preg_replace_callback(self::VARIABLE, function (array $m) use ($stack): string { $value = self::lookup($m[1], $stack); if (is_bool($value)) return $value ? '1' : ''; if (is_scalar($value)) return esc_html((string) $value); return ''; }, $template); } private static function lookup(string $name, array $stack) { if ($name === '.') { $top = end($stack); return (is_array($top) && array_key_exists('.', $top)) ? $top['.'] : $top; } $segments = explode('.', $name); $first = array_shift($segments); $value = null; $found = false; for ($i = count($stack) - 1; $i >= 0; $i--) { if (is_array($stack[$i]) && array_key_exists($first, $stack[$i])) { $value = $stack[$i][$first]; $found = true; break; } } if (!$found) return null; foreach ($segments as $segment) { if (!is_array($value) || !array_key_exists($segment, $value)) return null; $value = $value[$segment]; } return $value; } private static function isEmpty($value): bool { return $value === null || $value === false || $value === '' || $value === []; } }