""" Text renderer for report generation. """ from .rendererBaseTemplate import BaseRenderer from typing import Dict, Any, Tuple, List class RendererText(BaseRenderer): """Renders content to plain text format with format-specific extraction.""" @classmethod def get_supported_formats(cls) -> List[str]: """Return supported text formats (excluding formats with dedicated renderers).""" return [ 'txt', 'text', 'plain', # Programming languages 'js', 'javascript', 'ts', 'typescript', 'jsx', 'tsx', 'py', 'python', 'java', 'cpp', 'c', 'h', 'hpp', 'cs', 'csharp', 'php', 'rb', 'ruby', 'go', 'rs', 'rust', 'swift', 'kt', 'kotlin', 'scala', 'r', 'm', 'objc', 'sh', 'bash', 'zsh', 'fish', 'ps1', 'bat', 'cmd', # Web technologies (excluding html/htm which have dedicated renderer) 'css', 'scss', 'sass', 'less', 'xml', 'yaml', 'yml', 'toml', 'ini', 'cfg', # Data formats (excluding csv, md/markdown which have dedicated renderers) 'tsv', 'log', 'rst', 'sql', 'dockerfile', 'dockerignore', 'gitignore', # Configuration files 'env', 'properties', 'conf', 'config', 'rc', 'gitattributes', 'editorconfig', 'eslintrc', # Documentation 'readme', 'changelog', 'license', 'authors', 'contributing', 'todo', 'notes', 'docs' ] @classmethod def get_format_aliases(cls) -> List[str]: """Return format aliases.""" return [ 'ascii', 'utf8', 'utf-8', 'code', 'source', 'script', 'program', 'file', 'document', 'raw', 'unformatted', 'plaintext' ] @classmethod def get_priority(cls) -> int: """Return priority for text renderer.""" return 90 async def render(self, extracted_content: Dict[str, Any], title: str, user_prompt: str = None, ai_service=None) -> Tuple[str, str]: """Render extracted JSON content to plain text format.""" try: # Generate text from JSON structure text_content = self._generate_text_from_json(extracted_content, title) return text_content, "text/plain" except Exception as e: self.logger.error(f"Error rendering text: {str(e)}") # Return minimal text fallback return f"{title}\n\nError rendering report: {str(e)}", "text/plain" def _generate_text_from_json(self, json_content: Dict[str, Any], title: str) -> str: """Generate text content from structured JSON document.""" try: # Validate JSON structure if not isinstance(json_content, dict): raise ValueError("JSON content must be a dictionary") if "sections" not in json_content: raise ValueError("JSON content must contain 'sections' field") # Use title from JSON metadata if available, otherwise use provided title document_title = json_content.get("metadata", {}).get("title", title) # Build text content text_parts = [] # Document title text_parts.append(document_title) text_parts.append("=" * len(document_title)) text_parts.append("") # Process each section sections = json_content.get("sections", []) for section in sections: section_text = self._render_json_section(section) if section_text: text_parts.append(section_text) text_parts.append("") # Add spacing between sections # Add generation info text_parts.append("") text_parts.append(f"Generated: {self._format_timestamp()}") return '\n'.join(text_parts) except Exception as e: self.logger.error(f"Error generating text from JSON: {str(e)}") raise Exception(f"Text generation failed: {str(e)}") def _render_json_section(self, section: Dict[str, Any]) -> str: """Render a single JSON section to text.""" try: section_type = self._get_section_type(section) section_data = self._get_section_data(section) if section_type == "table": return self._render_json_table(section_data) elif section_type == "bullet_list": return self._render_json_bullet_list(section_data) elif section_type == "heading": return self._render_json_heading(section_data) elif section_type == "paragraph": return self._render_json_paragraph(section_data) elif section_type == "code_block": return self._render_json_code_block(section_data) elif section_type == "image": return self._render_json_image(section_data) else: # Fallback to paragraph for unknown types return self._render_json_paragraph(section_data) except Exception as e: self.logger.warning(f"Error rendering section {self._get_section_id(section)}: {str(e)}") return f"[Error rendering section: {str(e)}]" def _render_json_table(self, table_data: Dict[str, Any]) -> str: """Render a JSON table to text.""" try: headers = table_data.get("headers", []) rows = table_data.get("rows", []) if not headers or not rows: return "" text_parts = [] # Create table header header_line = " | ".join(str(header) for header in headers) text_parts.append(header_line) # Add separator line separator_line = " | ".join("-" * len(str(header)) for header in headers) text_parts.append(separator_line) # Add data rows for row in rows: row_line = " | ".join(str(cell_data) for cell_data in row) text_parts.append(row_line) return '\n'.join(text_parts) except Exception as e: self.logger.warning(f"Error rendering table: {str(e)}") return "" def _render_json_bullet_list(self, list_data: Dict[str, Any]) -> str: """Render a JSON bullet list to text.""" try: items = list_data.get("items", []) if not items: return "" text_parts = [] for item in items: if isinstance(item, str): text_parts.append(f"- {item}") elif isinstance(item, dict) and "text" in item: text_parts.append(f"- {item['text']}") return '\n'.join(text_parts) except Exception as e: self.logger.warning(f"Error rendering bullet list: {str(e)}") return "" def _render_json_heading(self, heading_data: Dict[str, Any]) -> str: """Render a JSON heading to text.""" try: level = heading_data.get("level", 1) text = heading_data.get("text", "") if text: level = max(1, min(6, level)) if level == 1: return f"{text}\n{'=' * len(text)}" elif level == 2: return f"{text}\n{'-' * len(text)}" else: return f"{'#' * level} {text}" return "" except Exception as e: self.logger.warning(f"Error rendering heading: {str(e)}") return "" def _render_json_paragraph(self, paragraph_data: Dict[str, Any]) -> str: """Render a JSON paragraph to text.""" try: text = paragraph_data.get("text", "") return text if text else "" except Exception as e: self.logger.warning(f"Error rendering paragraph: {str(e)}") return "" def _render_json_code_block(self, code_data: Dict[str, Any]) -> str: """Render a JSON code block to text.""" try: code = code_data.get("code", "") language = code_data.get("language", "") if code: if language: return f"Code ({language}):\n{code}" else: return code return "" except Exception as e: self.logger.warning(f"Error rendering code block: {str(e)}") return "" def _render_json_image(self, image_data: Dict[str, Any]) -> str: """Render a JSON image to text.""" try: alt_text = image_data.get("altText", "Image") return f"[Image: {alt_text}]" except Exception as e: self.logger.warning(f"Error rendering image: {str(e)}") return f"[Image: {image_data.get('altText', 'Image')}]"