From 5537d3e704688a8e084042f413b79eedd9a796b3 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Sun, 12 Oct 2025 01:02:04 +0200
Subject: [PATCH] testing rendering docx and xlsx done
---
.../renderers/rendererExcel.py | 251 ++++++++++++++----
1 file changed, 206 insertions(+), 45 deletions(-)
diff --git a/modules/services/serviceGeneration/renderers/rendererExcel.py b/modules/services/serviceGeneration/renderers/rendererExcel.py
index 142892eb..6ea4ff32 100644
--- a/modules/services/serviceGeneration/renderers/rendererExcel.py
+++ b/modules/services/serviceGeneration/renderers/rendererExcel.py
@@ -262,22 +262,110 @@ class RendererExcel(BaseRenderer):
}
style_template = self._create_ai_style_template("xlsx", user_prompt, style_schema)
- styles = await self._get_ai_styles(ai_service, style_template, self._get_default_excel_styles())
+ # Use our own _get_ai_styles_with_excel_colors method to ensure proper color conversion
+ styles = await self._get_ai_styles_with_excel_colors(ai_service, style_template, self._get_default_excel_styles())
- # Convert colors to aRGB format and validate
- styles = self._convert_colors_format(styles)
+ # Validate and fix contrast issues
return self._validate_excel_styles_contrast(styles)
+ async def _get_ai_styles_with_excel_colors(self, ai_service, style_template: str, default_styles: Dict[str, Any]) -> Dict[str, Any]:
+ """Get AI styles with proper Excel color conversion."""
+ if not ai_service:
+ return default_styles
+
+ try:
+ from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, OperationType
+
+ request_options = AiCallOptions()
+ request_options.operationType = OperationType.GENERAL
+
+ request = AiCallRequest(prompt=style_template, context="", options=request_options)
+ response = await ai_service.aiObjects.call(request)
+
+ import json
+ import re
+
+ # Debug output
+ print(f"🔍 AI STYLING RESPONSE TYPE: {type(response)}")
+ print(f"🔍 AI STYLING RESPONSE LENGTH: {len(response.content) if response and hasattr(response, 'content') and response.content else 0}")
+
+ # Clean and parse JSON
+ result = response.content.strip() if response and response.content else ""
+
+ # Check if result is empty
+ if not result:
+ self.logger.warning("AI styling returned empty response, using defaults")
+ return default_styles
+
+ # Extract JSON from markdown if present
+ json_match = re.search(r'```json\s*\n(.*?)\n```', result, re.DOTALL)
+ if json_match:
+ result = json_match.group(1).strip()
+ print(f"🔍 EXTRACTED JSON FROM MARKDOWN: {result[:100]}...")
+ elif result.startswith('```json'):
+ result = re.sub(r'^```json\s*', '', result)
+ result = re.sub(r'\s*```$', '', result)
+ print(f"🔍 CLEANED JSON FROM MARKDOWN: {result[:100]}...")
+ elif result.startswith('```'):
+ result = re.sub(r'^```\s*', '', result)
+ result = re.sub(r'\s*```$', '', result)
+ print(f"🔍 CLEANED JSON FROM GENERIC MARKDOWN: {result[:100]}...")
+
+ # Try to parse JSON
+ try:
+ styles = json.loads(result)
+ print(f"🔍 AI STYLING PARSED KEYS: {list(styles.keys()) if isinstance(styles, dict) else 'Not a dict'}")
+ except json.JSONDecodeError as json_error:
+ print(f"🔍 AI STYLING JSON ERROR: {json_error}")
+ print(f"🔍 AI STYLING RAW RESULT: {result[:200]}...")
+ self.logger.warning(f"AI styling returned invalid JSON: {json_error}, using defaults")
+ return default_styles
+
+ # Convert colors to Excel aRGB format
+ styles = self._convert_colors_format(styles)
+
+ return styles
+
+ except Exception as e:
+ self.logger.warning(f"AI styling failed: {str(e)}, using defaults")
+ return default_styles
+
+ def _get_safe_color(self, color_value: str, default: str = "FF000000") -> str:
+ """Get a safe aRGB color value for Excel (without # prefix)."""
+ if not isinstance(color_value, str):
+ return default
+
+ # Remove # prefix if present
+ if color_value.startswith('#'):
+ color_value = color_value[1:]
+
+ if len(color_value) == 6:
+ # Convert RRGGBB to AARRGGBB
+ return f"FF{color_value}"
+ elif len(color_value) == 8:
+ # Already aRGB format
+ return color_value
+ else:
+ # Unexpected format, return default
+ return default
+
def _convert_colors_format(self, styles: Dict[str, Any]) -> Dict[str, Any]:
"""Convert hex colors to aRGB format for Excel compatibility."""
try:
+ print(f"🔍 CONVERTING COLORS IN STYLES: {styles}")
for style_name, style_config in styles.items():
if isinstance(style_config, dict):
for prop, value in style_config.items():
if isinstance(value, str) and value.startswith('#') and len(value) == 7:
# Convert #RRGGBB to #AARRGGBB (add FF alpha channel)
+ old_value = value
styles[style_name][prop] = f"FF{value[1:]}"
- print(f"🔍 CONVERTED COLOR: {value} → {styles[style_name][prop]}")
+ print(f"🔍 CONVERTED COLOR: {old_value} → {styles[style_name][prop]}")
+ elif isinstance(value, str) and value.startswith('#') and len(value) == 9:
+ print(f"🔍 COLOR ALREADY aRGB: {value}")
+ elif isinstance(value, str) and value.startswith('#'):
+ print(f"🔍 UNEXPECTED COLOR FORMAT: {value} (length: {len(value)})")
+ print(f"🔍 FINAL CONVERTED STYLES: {styles}")
return styles
except Exception as e:
print(f"🔍 COLOR CONVERSION ERROR: {e}")
@@ -361,23 +449,30 @@ class RendererExcel(BaseRenderer):
if not sections:
return ["Content"]
- # Generate sheet names based on content types
+ # Generate sheet names based on content structure
sheet_names = []
- # Always start with a main content sheet
- document_title = json_content.get("metadata", {}).get("title", "Document")
- sheet_names.append(document_title[:31]) # Excel sheet name limit
+ # Check if we have multiple table sections
+ table_sections = [s for s in sections if s.get("type") == "table"]
- # Add sheets based on content types found
- content_types = set()
- for section in sections:
- content_type = section.get("content_type", "paragraph")
- content_types.add(content_type)
-
- # Create sheets for different content types if we have multiple types
- if len(content_types) > 1:
- if "table" in content_types:
- sheet_names.append("Tables")
+ if len(table_sections) > 1:
+ # Create separate sheets for each table
+ for i, section in enumerate(table_sections, 1):
+ section_title = section.get("title", f"Table {i}")
+ sheet_names.append(section_title[:31]) # Excel sheet name limit
+ else:
+ # Single table or mixed content - create main sheet
+ document_title = json_content.get("metadata", {}).get("title", "Document")
+ sheet_names.append(document_title[:31]) # Excel sheet name limit
+
+ # Add additional sheets for other content types
+ content_types = set()
+ for section in sections:
+ content_type = section.get("type", "paragraph")
+ content_types.add(content_type)
+
+ if "table" in content_types and len(table_sections) == 1:
+ sheet_names.append("Table Data")
if "list" in content_types:
sheet_names.append("Lists")
if "paragraph" in content_types or "heading" in content_types:
@@ -395,17 +490,69 @@ class RendererExcel(BaseRenderer):
if not sheet_names:
return
- # Populate the first sheet with all content
- first_sheet_name = sheet_names[0]
- self._populate_main_sheet(sheets[first_sheet_name], json_content, styles)
+ sections = json_content.get("sections", [])
+ table_sections = [s for s in sections if s.get("type") == "table"]
- # If we have multiple sheets, distribute content by type
- if len(sheet_names) > 1:
- self._populate_content_type_sheets(sheets, json_content, styles, sheet_names[1:])
+ if len(table_sections) > 1:
+ # Multiple tables - populate each sheet with its corresponding table
+ for i, section in enumerate(table_sections):
+ if i < len(sheet_names):
+ sheet_name = sheet_names[i]
+ sheet = sheets[sheet_name]
+ self._populate_table_sheet(sheet, section, styles, f"Table {i+1}")
+ else:
+ # Single table or mixed content - use original logic
+ first_sheet_name = sheet_names[0]
+ self._populate_main_sheet(sheets[first_sheet_name], json_content, styles)
+
+ # If we have multiple sheets, distribute content by type
+ if len(sheet_names) > 1:
+ self._populate_content_type_sheets(sheets, json_content, styles, sheet_names[1:])
except Exception as e:
self.logger.warning(f"Could not populate Excel sheets: {str(e)}")
+ def _populate_table_sheet(self, sheet, section: Dict[str, Any], styles: Dict[str, Any], sheet_title: str):
+ """Populate a sheet with a single table section."""
+ try:
+ # Sheet title
+ sheet['A1'] = sheet_title
+ sheet['A1'].font = Font(size=16, bold=True, color=self._get_safe_color(styles.get("title", {}).get("color", "FF1F4E79")))
+ sheet['A1'].alignment = Alignment(horizontal="center")
+
+ # Get table data
+ table_data = section.get("data", {})
+ headers = table_data.get("headers", [])
+ rows = table_data.get("rows", [])
+
+ if not headers and not rows:
+ sheet['A3'] = "No table data available"
+ return
+
+ # Add headers
+ header_style = styles.get("table_header", {})
+ for col, header in enumerate(headers, 1):
+ cell = sheet.cell(row=3, column=col, value=header)
+ if header_style.get("bold"):
+ cell.font = Font(bold=True, color=self._get_safe_color(header_style.get("text_color", "FF000000")))
+ if header_style.get("background"):
+ cell.fill = PatternFill(start_color=self._get_safe_color(header_style["background"]), end_color=self._get_safe_color(header_style["background"]), fill_type="solid")
+
+ # Add rows
+ cell_style = styles.get("table_cell", {})
+ for row_idx, row_data in enumerate(rows, 4):
+ for col_idx, cell_value in enumerate(row_data, 1):
+ cell = sheet.cell(row=row_idx, column=col_idx, value=cell_value)
+ if cell_style.get("text_color"):
+ cell.font = Font(color=self._get_safe_color(cell_style["text_color"]))
+
+ # Auto-adjust column widths
+ for col in range(1, len(headers) + 1):
+ sheet.column_dimensions[get_column_letter(col)].width = 20
+
+ except Exception as e:
+ self.logger.warning(f"Could not populate table sheet: {str(e)}")
+
def _populate_main_sheet(self, sheet, json_content: Dict[str, Any], styles: Dict[str, Any]):
"""Populate the main sheet with document overview and all content."""
try:
@@ -416,8 +563,17 @@ class RendererExcel(BaseRenderer):
# Safety check for title style
title_style = styles.get("title", {"font_size": 16, "bold": True, "color": "#FF1F4E79", "align": "center"})
print(f"🔍 EXCEL TITLE STYLE: {title_style}")
- sheet['A1'].font = Font(size=title_style["font_size"], bold=title_style["bold"], color=title_style["color"])
- sheet['A1'].alignment = Alignment(horizontal=title_style["align"])
+ print(f"🔍 EXCEL TITLE COLOR: {title_style['color']} (type: {type(title_style['color'])}, length: {len(title_style['color']) if isinstance(title_style['color'], str) else 'not string'})")
+ try:
+ safe_color = self._get_safe_color(title_style["color"])
+ sheet['A1'].font = Font(size=title_style["font_size"], bold=title_style["bold"], color=safe_color)
+ sheet['A1'].alignment = Alignment(horizontal=title_style["align"])
+ print(f"🔍 EXCEL TITLE FONT CREATED SUCCESSFULLY with color: {safe_color}")
+ except Exception as font_error:
+ print(f"🔍 EXCEL TITLE FONT ERROR: {font_error}")
+ # Try with a safe color
+ sheet['A1'].font = Font(size=title_style["font_size"], bold=title_style["bold"], color="FF000000")
+ sheet['A1'].alignment = Alignment(horizontal=title_style["align"])
# Generation info
sheet['A3'] = "Generated:"
@@ -516,21 +672,26 @@ class RendererExcel(BaseRenderer):
sheet[f'A{start_row}'].font = Font(bold=True)
start_row += 1
- # Process section elements
- elements = section.get("elements", [])
- content_type = section.get("content_type", "paragraph")
+ # Process section based on type
+ section_type = section.get("type", "paragraph")
- for element in elements:
- if content_type == "table":
- start_row = self._add_table_to_excel(sheet, element, styles, start_row)
- elif content_type == "list":
- start_row = self._add_list_to_excel(sheet, element, styles, start_row)
- elif content_type == "paragraph":
- start_row = self._add_paragraph_to_excel(sheet, element, styles, start_row)
- elif content_type == "heading":
- start_row = self._add_heading_to_excel(sheet, element, styles, start_row)
- else:
- start_row = self._add_paragraph_to_excel(sheet, element, styles, start_row)
+ if section_type == "table":
+ # Handle table section directly
+ table_data = section.get("data", {})
+ if table_data:
+ start_row = self._add_table_to_excel(sheet, table_data, styles, start_row)
+ else:
+ # Handle other section types
+ elements = section.get("elements", [])
+ for element in elements:
+ if section_type == "list":
+ start_row = self._add_list_to_excel(sheet, element, styles, start_row)
+ elif section_type == "paragraph":
+ start_row = self._add_paragraph_to_excel(sheet, element, styles, start_row)
+ elif section_type == "heading":
+ start_row = self._add_heading_to_excel(sheet, element, styles, start_row)
+ else:
+ start_row = self._add_paragraph_to_excel(sheet, element, styles, start_row)
return start_row
@@ -553,7 +714,7 @@ class RendererExcel(BaseRenderer):
for col, header in enumerate(headers, 1):
cell = sheet.cell(row=start_row, column=col, value=header)
if header_style.get("bold"):
- cell.font = Font(bold=True, color=header_style.get("text_color", "#FF000000"))
+ cell.font = Font(bold=True, color=self._get_safe_color(header_style.get("text_color", "FF000000")))
if header_style.get("background"):
cell.fill = PatternFill(start_color=header_style["background"], end_color=header_style["background"], fill_type="solid")
@@ -565,7 +726,7 @@ class RendererExcel(BaseRenderer):
for col, cell_value in enumerate(row_data, 1):
cell = sheet.cell(row=start_row, column=col, value=cell_value)
if cell_style.get("text_color"):
- cell.font = Font(color=cell_style["text_color"])
+ cell.font = Font(color=self._get_safe_color(cell_style["text_color"]))
start_row += 1
return start_row
@@ -583,7 +744,7 @@ class RendererExcel(BaseRenderer):
for item in list_items:
sheet.cell(row=start_row, column=1, value=f"• {item}")
if list_style.get("color"):
- sheet.cell(row=start_row, column=1).font = Font(color=list_style["color"])
+ sheet.cell(row=start_row, column=1).font = Font(color=self._get_safe_color(list_style["color"]))
start_row += 1
return start_row
@@ -601,7 +762,7 @@ class RendererExcel(BaseRenderer):
paragraph_style = styles.get("paragraph", {})
if paragraph_style.get("color"):
- sheet.cell(row=start_row, column=1).font = Font(color=paragraph_style["color"])
+ sheet.cell(row=start_row, column=1).font = Font(color=self._get_safe_color(paragraph_style["color"]))
start_row += 1
@@ -628,7 +789,7 @@ class RendererExcel(BaseRenderer):
sheet.cell(row=start_row, column=1).font = Font(
size=font_size,
bold=True,
- color=heading_style.get("color", "#FF000000")
+ color=self._get_safe_color(heading_style.get("color", "FF000000"))
)
start_row += 1