# Copyright (c) 2025 Patrick Motsch # All rights reserved. """ XML code renderer for code generation. """ from .codeRendererBaseTemplate import BaseCodeRenderer from modules.datamodels.datamodelDocument import RenderedDocument from typing import Dict, Any, List, Optional import xml.etree.ElementTree as ET from xml.dom import minidom class RendererCodeXml(BaseCodeRenderer): """Renders XML code files.""" @classmethod def getSupportedFormats(cls) -> List[str]: """Return supported XML formats.""" return ['xml'] @classmethod def getFormatAliases(cls) -> List[str]: """Return format aliases.""" return [] @classmethod def getPriority(cls) -> int: """Return priority for XML code renderer.""" return 80 @classmethod def getOutputStyle(cls, formatName: Optional[str] = None) -> str: """Return output style classification: XML is structured data format.""" return 'code' async def renderCodeFiles( self, codeFiles: List[Dict[str, Any]], metadata: Dict[str, Any], userPrompt: str = None ) -> List[RenderedDocument]: """ Render XML code files. Validates XML syntax and formats (pretty print). """ renderedDocs = [] for codeFile in codeFiles: if not self._validateCodeFile(codeFile): self.logger.warning(f"Invalid code file: {codeFile.get('filename', 'unknown')}") continue filename = codeFile['filename'] content = codeFile['content'] # Validate and format XML formattedContent = self._validateAndFormatXml(content) # Extract XML statistics for validation xmlStats = self._extractXmlStatistics(formattedContent) # Merge file-specific metadata with project metadata fileMetadata = dict(metadata) if metadata else {} fileMetadata.update({ "filename": filename, "fileType": "xml", "statistics": xmlStats }) renderedDocs.append( RenderedDocument( documentData=formattedContent.encode('utf-8'), mimeType="application/xml", filename=filename, metadata=fileMetadata ) ) return renderedDocs async def render(self, extractedContent: Dict[str, Any], title: str, userPrompt: str = None, aiService=None, *, style: Dict[str, Any] = None) -> List[RenderedDocument]: """ Render method for document generation compatibility. For XML, we only support code generation (no document renderer exists yet). """ # Check if this is code generation (has files array) if "files" in extractedContent: # Code generation path - use renderCodeFiles files = extractedContent.get("files", []) metadata = extractedContent.get("metadata", {}) return await self.renderCodeFiles(files, metadata, userPrompt) else: # Document generation path - not supported yet, return error self.logger.warning("XML document generation not supported, only code generation") return [ RenderedDocument( documentData=f"XML document generation not yet supported".encode('utf-8'), mimeType="text/plain", filename="error.txt", metadata={} ) ] def _validateAndFormatXml(self, content: str) -> str: """Validate XML syntax and format (pretty print).""" try: # Parse XML to validate root = ET.fromstring(content) # Format XML (pretty print) rough_string = ET.tostring(root, encoding='unicode') reparsed = minidom.parseString(rough_string) formatted = reparsed.toprettyxml(indent=" ") # Remove extra blank lines lines = [line for line in formatted.split('\n') if line.strip()] return '\n'.join(lines) except ET.ParseError as e: self.logger.warning(f"Invalid XML: {e}, returning original content") return content except Exception as e: self.logger.warning(f"XML formatting failed: {e}, returning original content") return content def _extractXmlStatistics(self, content: str) -> Dict[str, Any]: """Extract XML statistics for validation (element count, attribute count, root element).""" try: root = ET.fromstring(content) # Count all elements recursively elementCount = len(list(root.iter())) # Count attributes attributeCount = sum(len(elem.attrib) for elem in root.iter()) # Get root element name rootElement = root.tag return { "elementCount": elementCount, "attributeCount": attributeCount, "rootElement": rootElement, "hasRoot": True } except Exception as e: self.logger.warning(f"XML statistics extraction failed: {e}") return {}