Excel MCP Server

by fish0710
Verified
import logging from typing import Any, Dict from openpyxl.styles import ( PatternFill, Border, Side, Alignment, Protection, Font, Color ) from openpyxl.formatting.rule import ( ColorScaleRule, DataBarRule, IconSetRule, FormulaRule, CellIsRule ) from .workbook import get_or_create_workbook from .cell_utils import parse_cell_range, validate_cell_reference from .exceptions import ValidationError, FormattingError logger = logging.getLogger(__name__) def format_range( filepath: str, sheet_name: str, start_cell: str, end_cell: str = None, bold: bool = False, italic: bool = False, underline: bool = False, font_size: int = None, font_color: str = None, bg_color: str = None, border_style: str = None, border_color: str = None, number_format: str = None, alignment: str = None, wrap_text: bool = False, merge_cells: bool = False, protection: Dict[str, Any] = None, conditional_format: Dict[str, Any] = None ) -> Dict[str, Any]: """Apply formatting to a range of cells. This function handles all Excel formatting operations including: - Font properties (bold, italic, size, color, etc.) - Cell fill/background color - Borders (style and color) - Number formatting - Alignment and text wrapping - Cell merging - Protection - Conditional formatting Args: filepath: Path to Excel file sheet_name: Name of worksheet start_cell: Starting cell reference end_cell: Optional ending cell reference bold: Whether to make text bold italic: Whether to make text italic underline: Whether to underline text font_size: Font size in points font_color: Font color (hex code) bg_color: Background color (hex code) border_style: Border style (thin, medium, thick, double) border_color: Border color (hex code) number_format: Excel number format string alignment: Text alignment (left, center, right, justify) wrap_text: Whether to wrap text merge_cells: Whether to merge the range protection: Cell protection settings conditional_format: Conditional formatting rules Returns: Dictionary with operation status """ try: # Validate cell references if not validate_cell_reference(start_cell): raise ValidationError(f"Invalid start cell reference: {start_cell}") if end_cell and not validate_cell_reference(end_cell): raise ValidationError(f"Invalid end cell reference: {end_cell}") wb = get_or_create_workbook(filepath) if sheet_name not in wb.sheetnames: raise ValidationError(f"Sheet '{sheet_name}' not found") sheet = wb[sheet_name] # Get cell range coordinates try: start_row, start_col, end_row, end_col = parse_cell_range(start_cell, end_cell) except ValueError as e: raise ValidationError(f"Invalid cell range: {str(e)}") # If no end cell specified, use start cell coordinates if end_row is None: end_row = start_row if end_col is None: end_col = start_col # Apply font formatting font_args = { "bold": bold, "italic": italic, "underline": 'single' if underline else None, } if font_size is not None: font_args["size"] = font_size if font_color is not None: try: # Ensure color has FF prefix for full opacity font_color = font_color if font_color.startswith('FF') else f'FF{font_color}' font_args["color"] = Color(rgb=font_color) except ValueError as e: raise FormattingError(f"Invalid font color: {str(e)}") font = Font(**font_args) # Apply fill fill = None if bg_color is not None: try: # Ensure color has FF prefix for full opacity bg_color = bg_color if bg_color.startswith('FF') else f'FF{bg_color}' fill = PatternFill( start_color=Color(rgb=bg_color), end_color=Color(rgb=bg_color), fill_type='solid' ) except ValueError as e: raise FormattingError(f"Invalid background color: {str(e)}") # Apply borders border = None if border_style is not None: try: border_color = border_color if border_color else "000000" border_color = border_color if border_color.startswith('FF') else f'FF{border_color}' side = Side( style=border_style, color=Color(rgb=border_color) ) border = Border( left=side, right=side, top=side, bottom=side ) except ValueError as e: raise FormattingError(f"Invalid border settings: {str(e)}") # Apply alignment align = None if alignment is not None or wrap_text: try: align = Alignment( horizontal=alignment, vertical='center', wrap_text=wrap_text ) except ValueError as e: raise FormattingError(f"Invalid alignment settings: {str(e)}") # Apply protection protect = None if protection is not None: try: protect = Protection(**protection) except ValueError as e: raise FormattingError(f"Invalid protection settings: {str(e)}") # Apply formatting to range for row in range(start_row, end_row + 1): for col in range(start_col, end_col + 1): cell = sheet.cell(row=row, column=col) cell.font = font if fill is not None: cell.fill = fill if border is not None: cell.border = border if align is not None: cell.alignment = align if protect is not None: cell.protection = protect if number_format is not None: cell.number_format = number_format # Merge cells if requested if merge_cells and end_cell: try: range_str = f"{start_cell}:{end_cell}" sheet.merge_cells(range_str) except ValueError as e: raise FormattingError(f"Failed to merge cells: {str(e)}") # Apply conditional formatting if conditional_format is not None: range_str = f"{start_cell}:{end_cell}" if end_cell else start_cell rule_type = conditional_format.get('type') if not rule_type: raise FormattingError("Conditional format type not specified") params = conditional_format.get('params', {}) # Handle fill parameter for cell_is rule if rule_type == 'cell_is' and 'fill' in params: fill_params = params['fill'] if isinstance(fill_params, dict): try: fill_color = fill_params.get('fgColor', 'FFC7CE') # Default to light red fill_color = fill_color if fill_color.startswith('FF') else f'FF{fill_color}' params['fill'] = PatternFill( start_color=fill_color, end_color=fill_color, fill_type='solid' ) except ValueError as e: raise FormattingError(f"Invalid conditional format fill color: {str(e)}") try: if rule_type == 'color_scale': rule = ColorScaleRule(**params) elif rule_type == 'data_bar': rule = DataBarRule(**params) elif rule_type == 'icon_set': rule = IconSetRule(**params) elif rule_type == 'formula': rule = FormulaRule(**params) elif rule_type == 'cell_is': rule = CellIsRule(**params) else: raise FormattingError(f"Invalid conditional format type: {rule_type}") sheet.conditional_formatting.add(range_str, rule) except Exception as e: raise FormattingError(f"Failed to apply conditional formatting: {str(e)}") wb.save(filepath) range_str = f"{start_cell}:{end_cell}" if end_cell else start_cell return { "message": f"Applied formatting to range {range_str}", "range": range_str } except (ValidationError, FormattingError) as e: logger.error(str(e)) raise except Exception as e: logger.error(f"Failed to apply formatting: {e}") raise FormattingError(str(e))