testing rendering docx and xlsx done
This commit is contained in:
parent
972884098a
commit
5537d3e704
1 changed files with 206 additions and 45 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue