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 ? 'Invalid XML: ' . htmlspecialchars($errorMsg) . '' : 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 '
' . implode("\n", $formattedLines) . '';
}
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('');
},
$line
);
// CDATA sections
$line = preg_replace_callback(
'//s',
function ($matches) {
$cdataStyle = ConsoleStyle::create(color: ConsoleColor::BRIGHT_MAGENTA);
return $cdataStyle->apply('');
},
$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 '' . htmlspecialchars($attrMatches[1]) . '="' .
'' . htmlspecialchars($attrMatches[2]) . '"';
},
$attributes
);
return '<' . $slash . '' . htmlspecialchars($tagName) . '' .
htmlspecialchars($attributes) . '>';
},
$line
);
// XML comments
$line = preg_replace_callback(
'/<!--(.*?)-->/s',
function ($matches) {
return '<!--' . htmlspecialchars($matches[1]) . '-->';
},
$line
);
// CDATA sections
$line = preg_replace_callback(
'/<!\[CDATA\[(.*?)\]\]>/s',
function ($matches) {
return '<![CDATA[' . htmlspecialchars($matches[1]) . ']]>';
},
$line
);
// Processing instructions
$line = preg_replace_callback(
'/<\?([^?]+)\?>/',
function ($matches) {
return '<?' . htmlspecialchars($matches[1]) . '?>';
},
$line
);
return $line;
}
}