""" Markdown renderer for report generation. """ from .rendererBaseTemplate import BaseRenderer from typing import Dict, Any, Tuple, List class RendererMarkdown(BaseRenderer): """Renders content to Markdown format with format-specific extraction.""" @classmethod def get_supported_formats(cls) -> List[str]: """Return supported Markdown formats.""" return ['md', 'markdown'] @classmethod def get_format_aliases(cls) -> List[str]: """Return format aliases.""" return ['mdown', 'mkd'] @classmethod def get_priority(cls) -> int: """Return priority for markdown renderer.""" return 95 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 Markdown format.""" try: # Generate markdown from JSON structure markdown_content = self._generate_markdown_from_json(extracted_content, title) return markdown_content, "text/markdown" except Exception as e: self.logger.error(f"Error rendering markdown: {str(e)}") # Return minimal markdown fallback return f"# {title}\n\nError rendering report: {str(e)}", "text/markdown" def _generate_markdown_from_json(self, json_content: Dict[str, Any], title: str) -> str: """Generate markdown 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 markdown content markdown_parts = [] # Document title markdown_parts.append(f"# {document_title}") markdown_parts.append("") # Process each section sections = json_content.get("sections", []) for section in sections: section_markdown = self._render_json_section(section) if section_markdown: markdown_parts.append(section_markdown) markdown_parts.append("") # Add spacing between sections # Add generation info markdown_parts.append("---") markdown_parts.append(f"*Generated: {self._format_timestamp()}*") return '\n'.join(markdown_parts) except Exception as e: self.logger.error(f"Error generating markdown from JSON: {str(e)}") raise Exception(f"Markdown generation failed: {str(e)}") def _render_json_section(self, section: Dict[str, Any]) -> str: """Render a single JSON section to markdown.""" try: section_type = self._get_section_type(section) section_data = self._get_section_data(section) if section_type == "table": # Process the section data to extract table structure processed_data = self._process_section_by_type(section) return self._render_json_table(processed_data) elif section_type == "bullet_list": # Process the section data to extract bullet list structure processed_data = self._process_section_by_type(section) return self._render_json_bullet_list(processed_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": # Process the section data to extract code block structure processed_data = self._process_section_by_type(section) return self._render_json_code_block(processed_data) elif section_type == "image": # Process the section data to extract image structure processed_data = self._process_section_by_type(section) return self._render_json_image(processed_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 markdown.""" try: headers = table_data.get("headers", []) rows = table_data.get("rows", []) if not headers or not rows: return "" markdown_parts = [] # Create table header header_line = " | ".join(str(header) for header in headers) markdown_parts.append(header_line) # Add separator line separator_line = " | ".join("---" for _ in headers) markdown_parts.append(separator_line) # Add data rows for row in rows: row_line = " | ".join(str(cell_data) for cell_data in row) markdown_parts.append(row_line) return '\n'.join(markdown_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 markdown.""" try: items = list_data.get("items", []) if not items: return "" markdown_parts = [] for item in items: if isinstance(item, str): markdown_parts.append(f"- {item}") elif isinstance(item, dict) and "text" in item: markdown_parts.append(f"- {item['text']}") return '\n'.join(markdown_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 markdown.""" try: level = heading_data.get("level", 1) text = heading_data.get("text", "") if text: level = max(1, min(6, level)) 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 markdown.""" 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 markdown.""" try: code = code_data.get("code", "") language = code_data.get("language", "") if code: if language: return f"```{language}\n{code}\n```" else: return f"```\n{code}\n```" 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 markdown.""" try: alt_text = image_data.get("altText", "Image") base64_data = image_data.get("base64Data", "") if base64_data: # For base64 images, we can't embed them directly in markdown # So we'll use a placeholder with the alt text return f"![{alt_text}](data:image/png;base64,{base64_data[:50]}...)" else: return f"![{alt_text}](image-placeholder)" except Exception as e: self.logger.warning(f"Error rendering image: {str(e)}") return f"![{image_data.get('altText', 'Image')}](image-error)"