MD Umbau
This commit is contained in:
190
kb-markdown-importer/includes/Markdown/MarkdownRenderer.php
Normal file
190
kb-markdown-importer/includes/Markdown/MarkdownRenderer.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KbMarkdownImporter\Markdown;
|
||||
|
||||
use KbMarkdownImporter\Frontend\UrlBuilder;
|
||||
|
||||
final class MarkdownRenderer
|
||||
{
|
||||
public function render(string $markdown, array $context = []): string
|
||||
{
|
||||
$lines = preg_split('/\R/', str_replace(["\r\n", "\r"], "\n", $markdown)) ?: [];
|
||||
$html = [];
|
||||
$paragraph = [];
|
||||
$listType = '';
|
||||
$codeFence = false;
|
||||
$code = [];
|
||||
|
||||
$flushParagraph = function () use (&$html, &$paragraph, $context): void {
|
||||
if (! $paragraph) {
|
||||
return;
|
||||
}
|
||||
|
||||
$html[] = '<p>' . $this->renderInline(trim(implode(' ', $paragraph)), $context) . '</p>';
|
||||
$paragraph = [];
|
||||
};
|
||||
|
||||
$closeList = function () use (&$html, &$listType): void {
|
||||
if ('' === $listType) {
|
||||
return;
|
||||
}
|
||||
|
||||
$html[] = '</' . $listType . '>';
|
||||
$listType = '';
|
||||
};
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/^\s*```/', $line)) {
|
||||
if ($codeFence) {
|
||||
$html[] = '<pre><code>' . esc_html(implode("\n", $code)) . '</code></pre>';
|
||||
$code = [];
|
||||
$codeFence = false;
|
||||
} else {
|
||||
$flushParagraph();
|
||||
$closeList();
|
||||
$codeFence = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($codeFence) {
|
||||
$code[] = $line;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('' === trim($line)) {
|
||||
$flushParagraph();
|
||||
$closeList();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match('/^(#{1,6})\s+(.+)$/', $line, $matches)) {
|
||||
$flushParagraph();
|
||||
$closeList();
|
||||
$level = strlen($matches[1]);
|
||||
$text = trim($matches[2]);
|
||||
$id = sanitize_title(wp_strip_all_tags($text));
|
||||
$html[] = sprintf('<h%d id="%s">%s</h%d>', $level, esc_attr($id), $this->renderInline($text, $context), $level);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match('/^\s*[-*]\s+(.+)$/', $line, $matches)) {
|
||||
$flushParagraph();
|
||||
if ('ul' !== $listType) {
|
||||
$closeList();
|
||||
$html[] = '<ul>';
|
||||
$listType = 'ul';
|
||||
}
|
||||
$html[] = '<li>' . $this->renderInline(trim($matches[1]), $context) . '</li>';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match('/^\s*\d+\.\s+(.+)$/', $line, $matches)) {
|
||||
$flushParagraph();
|
||||
if ('ol' !== $listType) {
|
||||
$closeList();
|
||||
$html[] = '<ol>';
|
||||
$listType = 'ol';
|
||||
}
|
||||
$html[] = '<li>' . $this->renderInline(trim($matches[1]), $context) . '</li>';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match('/^>\s?(.*)$/', $line, $matches)) {
|
||||
$flushParagraph();
|
||||
$closeList();
|
||||
$html[] = '<blockquote><p>' . $this->renderInline(trim($matches[1]), $context) . '</p></blockquote>';
|
||||
continue;
|
||||
}
|
||||
|
||||
$paragraph[] = trim($line);
|
||||
}
|
||||
|
||||
if ($codeFence) {
|
||||
$html[] = '<pre><code>' . esc_html(implode("\n", $code)) . '</code></pre>';
|
||||
}
|
||||
|
||||
$flushParagraph();
|
||||
$closeList();
|
||||
|
||||
return wp_kses_post(implode("\n", $html));
|
||||
}
|
||||
|
||||
private function renderInline(string $text, array $context): string
|
||||
{
|
||||
$escaped = esc_html($text);
|
||||
|
||||
$escaped = preg_replace_callback('/!\[([^\]]*)\]\(([^)]+)\)/', function (array $matches) use ($context): string {
|
||||
$alt = html_entity_decode($matches[1], ENT_QUOTES);
|
||||
$src = html_entity_decode($matches[2], ENT_QUOTES);
|
||||
$url = $this->resolveImageUrl($src, (array) ($context['images'] ?? []));
|
||||
$image = sprintf('<img src="%s" alt="%s">', esc_url($url ?: $src), esc_attr($alt));
|
||||
|
||||
if ($url && ! empty($context['lightbox'])) {
|
||||
return sprintf('<a href="%s" class="kb-lightbox">%s</a>', esc_url($url), $image);
|
||||
}
|
||||
|
||||
return $image;
|
||||
}, $escaped) ?? $escaped;
|
||||
|
||||
$escaped = preg_replace_callback('/(?<!!)\[([^\]]+)\]\(([^)]+)\)/', function (array $matches) use ($context): string {
|
||||
$label = $this->renderInline($matches[1], $context);
|
||||
$href = html_entity_decode($matches[2], ENT_QUOTES);
|
||||
$url = $this->rewriteLink($href, $context) ?: $href;
|
||||
|
||||
return sprintf('<a href="%s">%s</a>', esc_url($url), $label);
|
||||
}, $escaped) ?? $escaped;
|
||||
|
||||
$escaped = preg_replace('/`([^`]+)`/', '<code>$1</code>', $escaped) ?? $escaped;
|
||||
$escaped = preg_replace('/\*\*([^*]+)\*\*/', '<strong>$1</strong>', $escaped) ?? $escaped;
|
||||
$escaped = preg_replace('/\*([^*]+)\*/', '<em>$1</em>', $escaped) ?? $escaped;
|
||||
|
||||
return $escaped;
|
||||
}
|
||||
|
||||
private function rewriteLink(string $href, array $context): string
|
||||
{
|
||||
if ('' === $href || str_starts_with($href, '#') || preg_match('#^(?:https?:|mailto:|tel:)#i', $href)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$parts = wp_parse_url($href);
|
||||
if (! is_array($parts)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$path = (string) ($parts['path'] ?? '');
|
||||
if (! preg_match('/\.md$/i', $path)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$page = preg_replace('/\.md$/i', '', basename($path)) ?: basename($path);
|
||||
$slug = in_array(strtolower($page), ['doku', 'index'], true) ? '' : sanitize_title($page);
|
||||
$fragment = isset($parts['fragment']) ? '#' . sanitize_title((string) $parts['fragment']) : '';
|
||||
|
||||
return UrlBuilder::page((string) $context['product_slug'], (string) $context['version_slug'], $slug) . $fragment;
|
||||
}
|
||||
|
||||
private function resolveImageUrl(string $src, array $images): string
|
||||
{
|
||||
if ('' === $src || preg_match('#^(?:https?:|data:)#i', $src)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$candidates = array_unique([
|
||||
$src,
|
||||
ltrim($src, '/'),
|
||||
basename($src),
|
||||
preg_replace('#^images/#', '', $src) ?: $src,
|
||||
]);
|
||||
|
||||
foreach ($candidates as $candidate) {
|
||||
if (isset($images[$candidate])) {
|
||||
return (string) $images[$candidate];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user