113 lines
3.5 KiB
PHP
Executable File
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);
|
|
}
|
|
} |