'; $listOpen = false; } continue; } if (preg_match('/^[-*]\s+(.+)$/u', $line, $match)) { if (!$listOpen) { $html[] = ''; $listOpen = false; } if (preg_match('/^(#{1,3})\s+(.+)$/u', $line, $match)) { $level = strlen($match[1]) + 1; $html[] = '' . self::inline($match[2]) . ''; continue; } $html[] = '

' . self::inline($line) . '

'; } if ($listOpen) { $html[] = ''; } return implode("\n", $html); } private static function inline(string $text): string { $links = []; $text = preg_replace_callback('/\[([^\]]+)\]\((https:\/\/[^\s)]+)\)/u', static function (array $match) use (&$links): string { if (!filter_var($match[2], FILTER_VALIDATE_URL)) { return $match[0]; } $token = "\x1A" . count($links) . "\x1A"; $links[] = '' . htmlspecialchars($match[1], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . ''; return $token; }, $text) ?? $text; $text = htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); $text = preg_replace('/\*\*(.+?)\*\*/u', '$1', $text) ?? $text; $text = preg_replace('/(?$1', $text) ?? $text; foreach ($links as $index => $link) { $text = str_replace("\x1A" . $index . "\x1A", $link, $text); } return $text; } }