Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
206 lines
7.0 KiB
PHP
206 lines
7.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Display\Formatters;
|
|
|
|
use App\Framework\Console\ConsoleColor;
|
|
use App\Framework\Console\ConsoleStyle;
|
|
use App\Framework\Display\ValueObjects\DisplayOptions;
|
|
use App\Framework\Display\ValueObjects\OutputFormat;
|
|
use DOMDocument;
|
|
|
|
/**
|
|
* Formatter for XML strings with syntax highlighting
|
|
*/
|
|
final readonly class XmlFormatter
|
|
{
|
|
public function format(string $xml, DisplayOptions $options, OutputFormat $format): string
|
|
{
|
|
// Validate and parse XML
|
|
$dom = new DOMDocument();
|
|
$dom->preserveWhiteSpace = false;
|
|
$dom->formatOutput = true;
|
|
|
|
$previousErrors = libxml_use_internal_errors(true);
|
|
$loaded = $dom->loadXML($xml);
|
|
$errors = libxml_get_errors();
|
|
libxml_use_internal_errors($previousErrors);
|
|
|
|
if (! $loaded || ! empty($errors)) {
|
|
// Invalid XML - return as plain string with error indication
|
|
$errorMsg = ! empty($errors) ? $errors[0]->message : 'Invalid XML';
|
|
return $format === OutputFormat::HTML
|
|
? '<span class="display-error">Invalid XML: ' . htmlspecialchars($errorMsg) . '</span>'
|
|
: ConsoleStyle::create(color: ConsoleColor::BRIGHT_RED)->apply('Invalid XML: ' . $errorMsg);
|
|
}
|
|
|
|
// Pretty print XML
|
|
$prettyXml = $dom->saveXML();
|
|
|
|
return match ($format) {
|
|
OutputFormat::CONSOLE => $this->formatConsole($prettyXml, $options),
|
|
OutputFormat::HTML => $this->formatHtml($prettyXml, $options),
|
|
};
|
|
}
|
|
|
|
public function formatForConsole(string $xml, ?DisplayOptions $options = null): string
|
|
{
|
|
$options ??= DisplayOptions::default();
|
|
|
|
return $this->format($xml, $options, OutputFormat::CONSOLE);
|
|
}
|
|
|
|
public function formatForHtml(string $xml, ?DisplayOptions $options = null): string
|
|
{
|
|
$options ??= DisplayOptions::default();
|
|
|
|
return $this->format($xml, $options, OutputFormat::HTML);
|
|
}
|
|
|
|
private function formatConsole(string $prettyXml, DisplayOptions $options): string
|
|
{
|
|
$lines = explode("\n", trim($prettyXml));
|
|
$formattedLines = [];
|
|
|
|
foreach ($lines as $line) {
|
|
$formattedLines[] = $this->colorizeXmlLine($line);
|
|
}
|
|
|
|
return implode("\n", $formattedLines);
|
|
}
|
|
|
|
private function formatHtml(string $prettyXml, DisplayOptions $options): string
|
|
{
|
|
$lines = explode("\n", trim($prettyXml));
|
|
$formattedLines = [];
|
|
|
|
foreach ($lines as $line) {
|
|
$formattedLines[] = $this->htmlColorizeXmlLine($line);
|
|
}
|
|
|
|
return '<pre class="display-xml"><code>' . implode("\n", $formattedLines) . '</code></pre>';
|
|
}
|
|
|
|
private function colorizeXmlLine(string $line): string
|
|
{
|
|
// XML opening/closing tags
|
|
$line = preg_replace_callback(
|
|
'/<(\/?)([a-zA-Z][a-zA-Z0-9_-]*)([^>]*)>/',
|
|
function ($matches) {
|
|
$slash = $matches[1];
|
|
$tagName = $matches[2];
|
|
$attributes = $matches[3];
|
|
|
|
$tagStyle = ConsoleStyle::create(color: ConsoleColor::BRIGHT_CYAN);
|
|
$tagNameStyle = ConsoleStyle::create(color: ConsoleColor::CYAN);
|
|
|
|
// Colorize attributes
|
|
$attributes = preg_replace_callback(
|
|
'/([a-zA-Z][a-zA-Z0-9_-]*)\s*=\s*"([^"]*)"/',
|
|
function ($attrMatches) {
|
|
$attrNameStyle = ConsoleStyle::create(color: ConsoleColor::YELLOW);
|
|
$attrValueStyle = ConsoleStyle::create(color: ConsoleColor::GREEN);
|
|
return $attrNameStyle->apply($attrMatches[1]) . '="' . $attrValueStyle->apply($attrMatches[2]) . '"';
|
|
},
|
|
$attributes
|
|
);
|
|
|
|
return '<' . $slash . $tagNameStyle->apply($tagName) . $tagStyle->apply($attributes) . '>';
|
|
},
|
|
$line
|
|
);
|
|
|
|
// XML comments
|
|
$line = preg_replace_callback(
|
|
'/<!--(.*?)-->/s',
|
|
function ($matches) {
|
|
$commentStyle = ConsoleStyle::create(color: ConsoleColor::GRAY);
|
|
return $commentStyle->apply('<!--' . $matches[1] . '-->');
|
|
},
|
|
$line
|
|
);
|
|
|
|
// CDATA sections
|
|
$line = preg_replace_callback(
|
|
'/<!\[CDATA\[(.*?)\]\]>/s',
|
|
function ($matches) {
|
|
$cdataStyle = ConsoleStyle::create(color: ConsoleColor::BRIGHT_MAGENTA);
|
|
return $cdataStyle->apply('<![CDATA[' . $matches[1] . ']]>');
|
|
},
|
|
$line
|
|
);
|
|
|
|
// Processing instructions
|
|
$line = preg_replace_callback(
|
|
'/<\?([^?]+)\?>/',
|
|
function ($matches) {
|
|
$piStyle = ConsoleStyle::create(color: ConsoleColor::BRIGHT_YELLOW);
|
|
return $piStyle->apply('<?' . $matches[1] . '?>');
|
|
},
|
|
$line
|
|
);
|
|
|
|
return $line;
|
|
}
|
|
|
|
private function htmlColorizeXmlLine(string $line): string
|
|
{
|
|
$line = htmlspecialchars($line, ENT_QUOTES, 'UTF-8');
|
|
|
|
// XML opening/closing tags
|
|
$line = preg_replace_callback(
|
|
'/<(\/?)([a-zA-Z][a-zA-Z0-9_-]*)([^&]*)>/',
|
|
function ($matches) {
|
|
$slash = $matches[1];
|
|
$tagName = $matches[2];
|
|
$attributes = $matches[3];
|
|
|
|
// Colorize attributes
|
|
$attributes = preg_replace_callback(
|
|
'/([a-zA-Z][a-zA-Z0-9_-]*)\s*=\s*"([^"]*)"/',
|
|
function ($attrMatches) {
|
|
return '<span class="display-xml-attr-name">' . htmlspecialchars($attrMatches[1]) . '</span>="' .
|
|
'<span class="display-xml-attr-value">' . htmlspecialchars($attrMatches[2]) . '</span>"';
|
|
},
|
|
$attributes
|
|
);
|
|
|
|
return '<' . $slash . '<span class="display-xml-tag">' . htmlspecialchars($tagName) . '</span>' .
|
|
htmlspecialchars($attributes) . '>';
|
|
},
|
|
$line
|
|
);
|
|
|
|
// XML comments
|
|
$line = preg_replace_callback(
|
|
'/<!--(.*?)-->/s',
|
|
function ($matches) {
|
|
return '<!--<span class="display-xml-comment">' . htmlspecialchars($matches[1]) . '</span>-->';
|
|
},
|
|
$line
|
|
);
|
|
|
|
// CDATA sections
|
|
$line = preg_replace_callback(
|
|
'/<!\[CDATA\[(.*?)\]\]>/s',
|
|
function ($matches) {
|
|
return '<span class="display-xml-cdata"><![CDATA[' . htmlspecialchars($matches[1]) . ']]></span>';
|
|
},
|
|
$line
|
|
);
|
|
|
|
// Processing instructions
|
|
$line = preg_replace_callback(
|
|
'/<\?([^?]+)\?>/',
|
|
function ($matches) {
|
|
return '<span class="display-xml-pi"><?' . htmlspecialchars($matches[1]) . '?></span>';
|
|
},
|
|
$line
|
|
);
|
|
|
|
return $line;
|
|
}
|
|
}
|
|
|