initial COmmit: Add KB Antora Importer plugin files

This commit is contained in:
Sven Steinert
2026-05-12 14:37:09 +02:00
parent cf253c1367
commit 6abf6f9c3d
39 changed files with 2945 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
namespace KbAntoraImporter\AsciiDoc;
final class AsciiDocRenderer
{
private ShortcodeTransformer $transformer;
public function __construct(?ShortcodeTransformer $transformer = null)
{
$this->transformer = $transformer ?: new ShortcodeTransformer();
}
public function render(string $adoc, array $context = []): string
{
$lines = preg_split('/\R/', $adoc) ?: [];
$html = '';
$paragraph = [];
$listOpen = false;
$codeOpen = false;
$code = [];
$flushParagraph = function () use (&$html, &$paragraph, $context): void {
if (! $paragraph) {
return;
}
$text = implode(' ', array_map('trim', $paragraph));
$html .= '<p>' . $this->transformer->transformInline($text, $context) . '</p>' . "\n";
$paragraph = [];
};
$closeList = static function () use (&$html, &$listOpen): void {
if ($listOpen) {
$html .= "</ul>\n";
$listOpen = false;
}
};
foreach ($lines as $line) {
$trimmed = trim($line);
if ('----' === $trimmed) {
$flushParagraph();
$closeList();
if ($codeOpen) {
$html .= '<pre><code>' . esc_html(implode("\n", $code)) . '</code></pre>' . "\n";
$code = [];
$codeOpen = false;
} else {
$codeOpen = true;
}
continue;
}
if ($codeOpen) {
$code[] = $line;
continue;
}
if ('' === $trimmed) {
$flushParagraph();
$closeList();
continue;
}
if (preg_match('/^:[A-Za-z0-9_-]+:\s*/', $trimmed)) {
continue;
}
if (preg_match('/^(={1,6})\s+(.+)$/', $trimmed, $matches)) {
$flushParagraph();
$closeList();
$level = min(6, strlen($matches[1]));
$html .= sprintf('<h%d>%s</h%d>', $level, esc_html($matches[2]), $level) . "\n";
continue;
}
if (preg_match('/^\*\s+(.+)$/', $trimmed, $matches)) {
$flushParagraph();
if (! $listOpen) {
$html .= "<ul>\n";
$listOpen = true;
}
$html .= '<li>' . $this->transformer->transformInline($matches[1], $context) . '</li>' . "\n";
continue;
}
if (preg_match('/^(NOTE|TIP|IMPORTANT|WARNING|CAUTION):\s+(.+)$/', $trimmed, $matches)) {
$flushParagraph();
$closeList();
$class = strtolower($matches[1]);
$html .= '<aside class="kb-admonition kb-admonition-' . esc_attr($class) . '"><strong>' . esc_html($matches[1]) . '</strong><p>' . $this->transformer->transformInline($matches[2], $context) . '</p></aside>' . "\n";
continue;
}
if (preg_match('/^image::([^\[]+)\[([^\]]*)\]/', $trimmed, $matches)) {
$flushParagraph();
$closeList();
$imageName = trim($matches[1]);
$alt = trim($matches[2]) ?: basename($imageName);
$url = (string) ($context['images'][$imageName] ?? $context['images'][basename($imageName)] ?? '');
if ($url) {
$image = sprintf('<img src="%s" alt="%s">', esc_url($url), esc_attr($alt));
if (! empty($context['lightbox'])) {
$image = sprintf('<a href="%s" class="kb-lightbox">%s</a>', esc_url($url), $image);
}
$html .= '<figure class="kb-image">' . $image . '</figure>' . "\n";
} else {
$html .= '<figure class="kb-image kb-image-missing"><span>' . esc_html($alt) . '</span></figure>' . "\n";
}
continue;
}
if (preg_match('/^\|===/', $trimmed)) {
$flushParagraph();
$closeList();
$html .= "<hr>\n";
continue;
}
$paragraph[] = $line;
}
$flushParagraph();
$closeList();
return wp_kses_post($html);
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace KbAntoraImporter\AsciiDoc;
use KbAntoraImporter\Frontend\UrlBuilder;
final class ShortcodeTransformer
{
public function transformInline(string $text, array $context): string
{
$pattern = '/\b(xref|link):([^\[]+)\[([^\]]*)\]/';
$output = '';
$offset = 0;
if (! preg_match_all($pattern, $text, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
return esc_html($text);
}
foreach ($matches as $match) {
$start = (int) $match[0][1];
$output .= esc_html(substr($text, $offset, $start - $offset));
$output .= $this->renderLink((string) $match[1][0], (string) $match[2][0], (string) $match[3][0], $context);
$offset = $start + strlen((string) $match[0][0]);
}
$output .= esc_html(substr($text, $offset));
return $output;
}
private function renderLink(string $type, string $target, string $label, array $context): string
{
$target = trim($target);
$label = $label ?: basename($target);
if ('link' === $type && preg_match('#^https?://#i', $target)) {
return sprintf('<a href="%s" rel="nofollow noopener">%s</a>', esc_url($target), esc_html($label));
}
if (preg_match('#^https?://#i', $target)) {
return sprintf('<a href="%s" rel="nofollow noopener">%s</a>', esc_url($target), esc_html($label));
}
$target = preg_replace('/^[^:]+:/', '', $target) ?: $target;
$fragment = '';
if (str_contains($target, '#')) {
[$target, $fragment] = explode('#', $target, 2);
$fragment = '#' . sanitize_title($fragment);
}
$target = preg_replace('/\.adoc$/', '', $target) ?: $target;
$slug = in_array(basename($target), ['index', 'dokumentation'], true) ? '' : sanitize_title(basename($target));
$url = UrlBuilder::page((string) $context['product_slug'], (string) $context['version_slug'], $slug) . $fragment;
return sprintf('<a href="%s">%s</a>', esc_url($url), esc_html($label));
}
}