gateway/modules/services/serviceGeneration/renderers/rendererText.py
2025-10-14 22:48:55 +02:00

248 lines
10 KiB
Python

"""
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":
# Render each heading element in the elements array
# section_data is already the elements array from _get_section_data
rendered_elements = []
for element in section_data:
rendered_elements.append(self._render_json_heading(element))
return "\n".join(rendered_elements)
elif section_type == "paragraph":
# Render each paragraph element in the elements array
# section_data is already the elements array from _get_section_data
rendered_elements = []
for element in section_data:
rendered_elements.append(self._render_json_paragraph(element))
return "\n".join(rendered_elements)
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 - render each element
# section_data is already the elements array from _get_section_data
rendered_elements = []
for element in section_data:
rendered_elements.append(self._render_json_paragraph(element))
return "\n".join(rendered_elements)
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')}]"