]*>/i', function($matches) use (&$htmlTags) { $id = count($htmlTags); $placeholder = "XHTMLTAGREPLACEX" . $id . "XHTMLTAGREPLACEX"; $htmlTags[$placeholder] = $matches[0]; return $placeholder; }, $text); $text = preg_replace_callback('/<\/(img|a|p|div|span|picture|source|br|hr)>/i', function($matches) use (&$htmlTags) { $id = count($htmlTags); $placeholder = "XHTMLTAGREPLACEX" . $id . "XHTMLTAGREPLACEX"; $htmlTags[$placeholder] = $matches[0]; return $placeholder; }, $text); $text = preg_replace_callback('//s', function($matches) use (&$htmlTags) { $id = count($htmlTags); $placeholder = "XHTMLTAGREPLACEX" . $id . "XHTMLTAGREPLACEX"; $htmlTags[$placeholder] = $matches[0]; return $placeholder; }, $text); $text = preg_replace_callback('/&[a-zA-Z]+;|&#[0-9]+;|&#x[0-9a-fA-F]+;/', function($matches) use (&$htmlTags) { $id = count($htmlTags); $placeholder = "XHTMLTAGREPLACEX" . $id . "XHTMLTAGREPLACEX"; $htmlTags[$placeholder] = $matches[0]; return $placeholder; }, $text); $text = preg_replace_callback('/```([a-zA-Z0-9\-_]*)\n?(.*?)\n?```/s', function($matches) use (&$codeBlocks) { $id = count($codeBlocks); $placeholder = "XCODEBLOCKREPLACEX" . $id . "XCODEBLOCKREPLACEX"; $codeBlocks[$placeholder] = trim($matches[2]); return "\n" . $placeholder . "\n"; }, $text); $text = preg_replace_callback('/`([^`\n]+?)`/', function($matches) use (&$inlineCodes) { $id = count($inlineCodes); $placeholder = "XINLINECODEREPLACEX" . $id . "XINLINECODEREPLACEX"; $inlineCodes[$placeholder] = $matches[1]; return $placeholder; }, $text); $text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); $text = preg_replace_callback('/^###### (.+?)$/m', function($m) { $id = self::generateHeaderId($m[1]); return '
' . $m[1] . '
'; }, $text); $text = preg_replace_callback('/^##### (.+?)$/m', function($m) { $id = self::generateHeaderId($m[1]); return '
' . $m[1] . '
'; }, $text); $text = preg_replace_callback('/^#### (.+?)$/m', function($m) { $id = self::generateHeaderId($m[1]); return '

' . $m[1] . '

'; }, $text); $text = preg_replace_callback('/^### (.+?)$/m', function($m) { $id = self::generateHeaderId($m[1]); return '

' . $m[1] . '

'; }, $text); $text = preg_replace_callback('/^## (.+?)$/m', function($m) { $id = self::generateHeaderId($m[1]); return '

' . $m[1] . '

'; }, $text); $text = preg_replace_callback('/^# (.+?)$/m', function($m) { $id = self::generateHeaderId($m[1]); return '

' . $m[1] . '

'; }, $text); $text = preg_replace_callback('/!\[([^\]]*?)\]\(([^)]+?)\)/', function($matches) { $alt = $matches[1]; $src = $matches[2]; if (!preg_match('/^(https?:\/\/|\/\/|data:)/', $src)) { $baseUrl = self::$currentFilePath; $src = rtrim($baseUrl, '/') . '/' . ltrim($src, '/'); } return '' . htmlspecialchars($alt, ENT_QUOTES, 'UTF-8') . ''; }, $text); $text = preg_replace_callback('/\[([^\]]+?)\]\(([^)]+?)\)/', function($matches) { $linkText = $matches[1]; $linkUrl = $matches[2]; $processedUrl = self::processMarkdownLink($linkUrl); return '' . $linkText . ''; }, $text); $text = preg_replace('/(?$1', $text); $text = preg_replace('/(?$1', $text); $text = preg_replace('/(?$1', $text); $text = preg_replace('/(?$1', $text); $text = preg_replace('/(?$1', $text); $text = preg_replace('/(?$1', $text); $text = preg_replace('/~~([^~\n]+?)~~/', '$1', $text); $text = preg_replace('/^\s*---\s*$/m', '
', $text); $text = preg_replace('/^\s*\*\*\*\s*$/m', '
', $text); $text = preg_replace('/^> (.+?)$/m', '
$1
', $text); $text = preg_replace('/^(\s*)[\*\-\+] (.+?)$/m', '$1
  • $2
  • ', $text); $text = preg_replace('/^(\s*)\d+\. (.+?)$/m', '$1
  • $2
  • ', $text); $text = self::wrapLists($text); $text = preg_replace_callback('/(?:^\|.+\|\s*$\n?)+/m', function($matches) { return self::parseTable($matches[0]); }, $text); $text = self::wrapParagraphs($text); foreach ($codeBlocks as $placeholder => $content) { $escapedContent = htmlspecialchars($content, ENT_QUOTES, 'UTF-8'); $codeHtml = '
    ' . $escapedContent . '
    '; $text = str_replace($placeholder, $codeHtml, $text); } foreach ($inlineCodes as $placeholder => $content) { $escapedContent = htmlspecialchars($content, ENT_QUOTES, 'UTF-8'); $codeHtml = '' . $escapedContent . ''; $text = str_replace($placeholder, $codeHtml, $text); } foreach ($htmlTags as $placeholder => $content) { if (preg_match('/]*?)>/i', function($m) { $attrs = $m[1]; $style = ''; if (preg_match('/width\s*=\s*["\']?(\d+)["\']?/i', $attrs, $widthMatch)) { $style .= 'width: ' . $widthMatch[1] . 'px; '; } if (preg_match('/height\s*=\s*["\']?(\d+)["\']?/i', $attrs, $heightMatch)) { $style .= 'height: ' . $heightMatch[1] . 'px; '; } if ($style) { $style = 'max-width: 100%; ' . $style; if (preg_match('/style\s*=\s*["\']([^"\']*)["\']/', $attrs)) { $attrs = preg_replace('/style\s*=\s*["\']([^"\']*)["\']/', 'style="$1 ' . $style . '"', $attrs); } else { $attrs .= ' style="' . trim($style) . '"'; } } return ''; }, $content); if (preg_match('/class\s*=\s*["\']([^"\']*)["\']/', $content, $classMatch)) { if (strpos($classMatch[1], 'markdown-img') === false) { $newClass = trim($classMatch[1] . ' markdown-img'); $content = preg_replace('/class\s*=\s*["\']([^"\']*)["\']/', 'class="' . $newClass . '"', $content); } } else { $content = preg_replace('/ 0) { array_pop($parts); } } else { $parts[] = $part; } } return implode('/', $parts); } private static function generateHeaderId($headerText) { $headerText = strip_tags($headerText); $id = strtolower($headerText); $id = preg_replace('/[^a-z0-9]+/', '-', $id); $id = trim($id, '-'); return $id; } private static function wrapLists($text) { $lines = explode("\n", $text); $result = []; $inList = false; $listType = ''; $lastWasListItem = false; foreach ($lines as $line) { if (preg_match('/^(\s*)
  • "; $listType = $newListType; $inList = true; } elseif ($listType !== $newListType) { if (!($listType === 'ol' && $newListType === 'ol')) { $result[] = ""; $result[] = "<$newListType class=\"markdown-list\">"; $listType = $newListType; } } $result[] = $line; $lastWasListItem = true; } else { if ($inList && trim($line) === '' && $lastWasListItem) { $lastWasListItem = false; continue; } if ($inList && trim($line) !== '') { $result[] = ""; $inList = false; } if (trim($line) !== '') { $result[] = $line; $lastWasListItem = false; } } } if ($inList) { $result[] = ""; } return implode("\n", $result); } private static function parseTable($table) { $table = trim($table); $rows = explode("\n", $table); $html = ''; $isHeader = true; foreach ($rows as $row) { if (empty(trim($row))) continue; if (preg_match('/^\|[\s\-\|:]+\|$/', $row)) { $isHeader = false; continue; } $cells = explode('|', trim($row, '|')); $cells = array_map('trim', $cells); $tag = $isHeader ? 'th' : 'td'; $class = $isHeader ? 'markdown-th' : 'markdown-td'; $html .= ''; foreach ($cells as $cell) { $html .= "<$tag class=\"$class\">$cell"; } $html .= ''; if ($isHeader) $isHeader = false; } $html .= '
    '; return $html; } private static function wrapParagraphs($text) { $paragraphs = preg_split('/\n\s*\n/', $text); $result = []; foreach ($paragraphs as $paragraph) { $paragraph = trim($paragraph); if (empty($paragraph)) continue; if (preg_match('/^XCODEBLOCKREPLACEX\d+XCODEBLOCKREPLACEX$/', $paragraph)) { $result[] = $paragraph; } elseif (preg_match('/^<(h[1-6]|ul|ol|blockquote|pre|hr|table|div)/i', $paragraph)) { $result[] = $paragraph; } else { if (preg_match('/<(p|div|a|picture|img)[^>]*>/', $paragraph)) { $result[] = $paragraph; } else { $paragraph = preg_replace('/\n(?!<)/', '
    ', $paragraph); $result[] = '

    ' . $paragraph . '

    '; } } } return implode("\n\n", $result); } } ?>