testing rendering docx and xlsx done

This commit is contained in:
ValueOn AG 2025-10-12 01:02:04 +02:00
parent 972884098a
commit 5537d3e704

View file

@ -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