First Commit
This commit is contained in:
Executable
+113
@@ -0,0 +1,113 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user