Files
dockerank2/app/Services/MarkdownService.php
T
2026-09-01 07:49:48 -03:00

113 lines
3.5 KiB
PHP
Executable File

<?php
declare(strict_types=1);
namespace App\Services;
/**
* Simple Markdown parser service for rendering AI-generated insights.
* Converts basic Markdown syntax to HTML without external dependencies.
*/
class MarkdownService
{
/**
* Convert Markdown text to HTML.
*
* @param string|null $text The markdown text to convert.
* @return string The converted HTML.
*/
public static function parse(?string $text): string
{
if (empty($text)) {
return '';
}
$html = $text;
// Preserve safe HTML tags before escaping (e.g., <span class="tts" data-text="...">)
$placeholders = [];
$html = preg_replace_callback(
'/<span\s+class="tts"\s+data-text="([^"]+)"\s*>(.*?)<\/span>/',
function ($matches) use (&$placeholders) {
$key = '___TTS_' . count($placeholders) . '___';
$placeholders[$key] = $matches[0];
return $key;
},
$html
);
$html = htmlspecialchars($html, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
// Restore preserved tags
foreach ($placeholders as $key => $original) {
$html = str_replace($key, $original, $html);
}
// Headers (h1-h4)
$html = preg_replace('/^### (.+)$/m', '<h4>$1</h4>', $html);
$html = preg_replace('/^## (.+)$/m', '<h3>$1</h3>', $html);
$html = preg_replace('/^# (.+)$/m', '<h2>$1</h2>', $html);
// Bold and italic
$html = preg_replace('/\*\*\*(.+?)\*\*\*/', '<strong><em>$1</em></strong>', $html);
$html = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $html);
$html = preg_replace('/\*(.+?)\*/', '<em>$1</em>', $html);
// Inline code
$html = preg_replace('/`(.+?)`/', '<code>$1</code>', $html);
// Lines starting with dashes or asterisks (unordered list)
$html = preg_replace('/^[-*] (.+)$/m', '<li>$1</li>', $html);
// Wrap consecutive <li> in <ul>
$html = preg_replace('/((?:<li>.*?<\/li>\n?)+)/', '<ul>$1</ul>', $html);
// Lines starting with numbers (ordered list)
$html = preg_replace('/^\d+\.\s(.+)$/m', '<li>$1</li>', $html);
// Horizontal rules
$html = preg_replace('/^---$/m', '<hr>', $html);
// Blockquotes
$html = preg_replace('/^> (.+)$/m', '<blockquote>$1</blockquote>', $html);
// Line breaks (double newline = paragraph)
$paragraphs = preg_split('/\n\n+/', $html);
$result = [];
foreach ($paragraphs as $paragraph) {
$paragraph = trim($paragraph);
if (empty($paragraph)) {
continue;
}
// Skip if already wrapped in block-level tags
if (preg_match('/^<(?:ul|ol|li|h[2-4]|hr|blockquote|div)/', $paragraph)) {
$result[] = $paragraph;
} else {
$result[] = '<p>' . $paragraph . '</p>';
}
}
return implode("\n", $result);
}
/**
* Strip markdown formatting, returning plain text.
*
* @param string|null $text The markdown text to strip.
* @return string Plain text without markdown formatting.
*/
public static function strip(?string $text): string
{
if (empty($text)) {
return '';
}
$text = preg_replace('/[*_~`#>`\[\]()!-]/', '', $text);
$text = preg_replace('/\n{3,}/', "\n\n", $text);
return trim($text);
}
}