' . self::e(implode("\n", $code)) . '';
$i++;
continue;
}
if (preg_match('/^(#{1,6})\s+(.+)$/', $line, $matches)) {
$level = strlen($matches[1]);
$html[] = '' . self::inline($matches[2]) . '';
$i++;
continue;
}
if (preg_match('/^(-{3,}|\*{3,}|_{3,})$/', trim($line))) {
$html[] = '
';
$i++;
continue;
}
if (preg_match('/^\s*>\s?(.*)$/', $line)) {
$quote = [];
while ($i < $count && preg_match('/^\s*>\s?(.*)$/', rtrim($lines[$i]), $matches)) {
$quote[] = $matches[1];
$i++;
}
$html[] = '' . self::toHtml(implode("\n", $quote), false) . '
';
continue;
}
if (preg_match('/^\s*[-*+]\s+(.+)$/', $line)) {
$items = [];
while ($i < $count && preg_match('/^\s*[-*+]\s+(.+)$/', rtrim($lines[$i]), $matches)) {
$items[] = '' . self::inline($matches[1]) . '';
$i++;
}
$html[] = '' . implode('', $items) . '
';
continue;
}
if (preg_match('/^\s*\d+\.\s+(.+)$/', $line)) {
$items = [];
while ($i < $count && preg_match('/^\s*\d+\.\s+(.+)$/', rtrim($lines[$i]), $matches)) {
$items[] = '' . self::inline($matches[1]) . '';
$i++;
}
$html[] = '' . implode('', $items) . '
';
continue;
}
$paragraph = [$line];
$i++;
while ($i < $count && trim($lines[$i]) !== '' && !self::startsBlock(rtrim($lines[$i]))) {
$paragraph[] = trim($lines[$i]);
$i++;
}
$html[] = '' . self::inline(implode(' ', $paragraph)) . '
';
}
return implode("\n", $html);
}
public static function excerpt(string $markdown, int $words = 28): string
{
$markdown = preg_replace('/<\?(?:php|=)?[\s\S]*?\?>/i', ' ', $markdown) ?? $markdown;
$html = self::toHtml($markdown, false);
$html = preg_replace('#<(script|style)\b[^>]*>.*?\1>#is', ' ', $html) ?? $html;
$text = trim(preg_replace('/\s+/', ' ', strip_tags($html)) ?? '');
if ($text === '') {
return '';
}
$parts = preg_split('/\s+/', $text) ?: [];
if (count($parts) <= $words) {
return $text;
}
return implode(' ', array_slice($parts, 0, $words)) . '...';
}
private static function startsBlock(string $line): bool
{
return self::startsHtmlBlock($line)
|| (bool) preg_match('/^(#{1,6}\s+|```|^\s*[-*+]\s+|^\s*\d+\.\s+|^\s*>\s?|(-{3,}|\*{3,}|_{3,})$)/', $line);
}
private static function inline(string $text): string
{
$placeholders = [];
$text = preg_replace_callback('/`([^`]+)`/', static function (array $matches) use (&$placeholders): string {
$key = "\x1A" . count($placeholders) . "\x1A";
$placeholders[$key] = '' . self::e($matches[1]) . '';
return $key;
}, $text) ?? $text;
$text = preg_replace_callback('/<\/?[A-Za-z][A-Za-z0-9:-]*(?:\s+[^<>]*)?>||]*>/s', static function (array $matches) use (&$placeholders): string {
$key = "\x1A" . count($placeholders) . "\x1A";
$placeholders[$key] = $matches[0];
return $key;
}, $text) ?? $text;
$text = self::e($text);
$text = preg_replace_callback('/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/', static function (array $matches): string {
$title = isset($matches[3]) ? ' title="' . self::e($matches[3]) . '"' : '';
return '
';
}, $text) ?? $text;
$text = preg_replace_callback('/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/', static function (array $matches): string {
$title = isset($matches[3]) ? ' title="' . self::e($matches[3]) . '"' : '';
return '' . $matches[1] . '';
}, $text) ?? $text;
$text = preg_replace('/\*\*([^*]+)\*\*/', '$1', $text) ?? $text;
$text = preg_replace('/\*([^*]+)\*/', '$1', $text) ?? $text;
return strtr($text, $placeholders);
}
private static function executePhp(string $source): string
{
if (!str_contains($source, '')) {
return $source;
}
$placeholders = [];
$source = preg_replace_callback('/```[\s\S]*?```/', static function (array $matches) use (&$placeholders): string {
$key = "\x1BPHPBLOCK" . count($placeholders) . "\x1B";
$placeholders[$key] = $matches[0];
return $key;
}, $source) ?? $source;
$source = preg_replace_callback('/`[^`\n]*<\?[^`\n]*`/', static function (array $matches) use (&$placeholders): string {
$key = "\x1BPHPBLOCK" . count($placeholders) . "\x1B";
$placeholders[$key] = $matches[0];
return $key;
}, $source) ?? $source;
ob_start();
try {
eval('?>' . $source);
return strtr((string) ob_get_clean(), $placeholders);
} catch (\Throwable $error) {
ob_end_clean();
return strtr($source, $placeholders) . "\n\n";
}
}
private static function startsHtmlBlock(string $line): bool
{
$line = trim($line);
return (bool) preg_match('/^(')) {
$block[] = rtrim($lines[$i]);
$i++;
}
return [implode("\n", $block), $i];
}
if (!preg_match('/^<([A-Za-z][A-Za-z0-9:-]*)\b/i', $trimmed, $matches)) {
return [implode("\n", $block), $i];
}
$tag = strtolower($matches[1]);
$voidTags = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
if (in_array($tag, $voidTags, true) || str_ends_with($trimmed, '/>') || stripos($trimmed, '' . $tag . '>') !== false) {
return [implode("\n", $block), $i];
}
while ($i < $count) {
$block[] = rtrim($lines[$i]);
if (stripos((string) end($block), '' . $tag . '>') !== false) {
$i++;
break;
}
if (trim($lines[$i]) === '' && !in_array($tag, ['article', 'aside', 'div', 'footer', 'form', 'header', 'iframe', 'main', 'nav', 'pre', 'script', 'section', 'style', 'table', 'textarea'], true)) {
$i++;
break;
}
$i++;
}
return [implode("\n", $block), $i];
}
private static function e(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
}