import io import os import tempfile from numbers_parser import Document from openpyxl import Workbook from openpyxl.styles import PatternFill, Font, Alignment import gradio as gr import pandas as pd from pathlib import Path def get_excel_color(numbers_color): """Convert Numbers color to Excel color format""" if not numbers_color: return None # Convert RGB values to hex color r = int(numbers_color.red * 255) g = int(numbers_color.green * 255) b = int(numbers_color.blue * 255) return f"{r:02x}{g:02x}{b:02x}" def apply_cell_formatting(cell, cell_data): """Apply formatting from Numbers cell to Excel cell""" if hasattr(cell_data, 'background_color') and cell_data.background_color: color = get_excel_color(cell_data.background_color) if color: cell.fill = PatternFill(start_color=color, end_color=color, fill_type="solid") if hasattr(cell_data, 'font_bold'): cell.font = Font(bold=cell_data.font_bold) def numbers_to_xlsx(numbers_file): """ Converts a Numbers file to XLSX format preserving colors and formatting. Args: numbers_file: The uploaded Numbers file object from Gradio. Returns: str: Path to the converted xlsx file, or None if conversion fails. """ if not numbers_file: return None try: output_dir = "outputs" os.makedirs(output_dir, exist_ok=True) output_path = os.path.join(output_dir, "converted.xlsx") # Read the Numbers file doc = Document(numbers_file.name) # Get all sheets and validate sheets = doc.sheets if not sheets: print("No sheets found in the document") return None # Create a new workbook wb = Workbook() wb.remove(wb.active) # Remove default sheet for sheet_index, sheet in enumerate(sheets): if not sheet.tables: continue # Create new sheet ws = wb.create_sheet(title=f"Sheet{sheet_index + 1}") table = sheet.tables[0] # Get all rows with their formatting rows = list(table.rows()) if not rows: continue # Write data and apply formatting for row_idx, row in enumerate(rows, 1): for col_idx, cell_data in enumerate(row, 1): # Write cell value cell = ws.cell(row=row_idx, column=col_idx) cell.value = cell_data.value if hasattr(cell_data, 'value') else cell_data # Apply formatting if available if hasattr(cell_data, 'background_color') or hasattr(cell_data, 'font_bold'): apply_cell_formatting(cell, cell_data) # Adjust column widths for column in ws.columns: max_length = 0 column_letter = column[0].column_letter for cell in column: try: if len(str(cell.value)) > max_length: max_length = len(str(cell.value)) except: pass adjusted_width = (max_length + 2) ws.column_dimensions[column_letter].width = adjusted_width # Freeze the header row ws.freeze_panes = 'A2' # Save the workbook wb.save(output_path) # Verify the file was created and has content if os.path.exists(output_path) and os.path.getsize(output_path) > 0: return output_path else: print("Output file is empty or was not created") return None except Exception as e: print(f"Error converting file: {str(e)}") return None # Define the Gradio interface with correct file handling interface = gr.Interface( fn=numbers_to_xlsx, inputs=gr.File(label="Numbers File", file_types=[".numbers"]), outputs=gr.File(label="XLSX file", file_types=[".xlsx"]), title="Numbers to XLSX Converter", description="Convert your Numbers files to Excel format easily and download the result.", examples=None, cache_examples=False ) # Launch the Gradio app if __name__ == "__main__": interface.launch()