' . htmlspecialchars(implode("\n", $code), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '';
continue;
}
if ($i + 1 < $count && self::isTableRow($line) && self::isTableSeparator($lines[$i + 1])) {
$headers = self::tableCells($line);
$alignments = array_map([self::class, 'tableAlignment'], self::tableCells($lines[$i + 1]));
$rows = [];
for ($i += 2; $i < $count && self::isTableRow($lines[$i]) && trim($lines[$i]) !== ''; $i++) $rows[] = self::tableCells($lines[$i]);
$table = '| ' . self::inline($header) . ' | '; } $table .= '
|---|
| ' . self::inline($row[$column] ?? '') . ' | '; } $table .= '
' . self::render(implode("\n", $quote)) . ''; continue; } if (preg_match('/^\s*(?:[-+*]|\d+[.)])\s+(.+)$/u', $line)) { $ordered = (bool) preg_match('/^\s*\d+[.)]\s+/u', $line); $tag = $ordered ? 'ol' : 'ul'; $list = '<' . $tag . '>'; while ($i < $count) { $pattern = $ordered ? '/^\s*\d+[.)]\s+(.+)$/u' : '/^\s*[-+*]\s+(.+)$/u'; if (!preg_match($pattern, $lines[$i], $item)) break; $value = $item[1]; $taskClass = ''; if (preg_match('/^\[([ xX])\]\s+(.+)$/u', $value, $task)) { $taskClass = ' class="task-list-item"'; $value = ' ' . self::inline($task[2]); } else $value = self::inline($value); $list .= '
' . self::inline(implode(' ', $paragraph)) . '
'; } return implode("\n", $html); } private static function startsBlock(array $lines, int $index): bool { $line = rtrim((string) ($lines[$index] ?? '')); return preg_match('/^\s*(?:```|#{1,6}\s+|>\s?|[-+*]\s+|\d+[.)]\s+)/u', $line) === 1 || preg_match('/^\s*([-*_])(?:\s*\1){2,}\s*$/u', $line) === 1 || ($index + 1 < count($lines) && self::isTableRow($line) && self::isTableSeparator($lines[$index + 1])); } private static function isTableRow(string $line): bool { return str_contains(trim($line), '|'); } private static function isTableSeparator(string $line): bool { $cells = self::tableCells($line); return $cells !== [] && count(array_filter($cells, static fn (string $cell): bool => preg_match('/^:?-{3,}:?$/', trim($cell)) === 1)) === count($cells); } private static function tableCells(string $line): array { $line = trim($line); if (str_starts_with($line, '|')) $line = substr($line, 1); if (str_ends_with($line, '|') && !str_ends_with($line, '\\|')) $line = substr($line, 0, -1); $marker = "\x1D"; return array_map(static fn (string $cell): string => str_replace($marker, '|', trim($cell)), explode('|', str_replace('\\|', $marker, $line))); } private static function tableAlignment(string $separator): string { $separator = trim($separator); if (str_starts_with($separator, ':') && str_ends_with($separator, ':')) return 'center'; if (str_ends_with($separator, ':')) return 'right'; return str_starts_with($separator, ':') ? 'left' : ''; } private static function inline(string $text): string { $tokens = []; $reserve = static function (string $html) use (&$tokens): string { $token = "\x1A" . count($tokens) . "\x1A"; $tokens[] = $html; return $token; }; $text = preg_replace_callback('/`([^`]+)`/u', static fn (array $match): string => $reserve('' . htmlspecialchars($match[1], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . ''), $text) ?? $text;
$text = preg_replace_callback('/!\[([^\]]*)\]\((\/media\/site\/[A-Za-z0-9._%-]+)(?:\s+["\']([^"\']*)["\'])?\)/u', static fn (array $match): string => $reserve('