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(''); }, $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; } }