UNPKG

aspose.cells.mcp

Version:

Excel MCP - AI-powered Excel automation server for Excel AI, Excel Formula MCP, spreadsheets MCP, and Excel automation using Aspose.Cells for Node.js

359 lines (315 loc) 13.7 kB
/** * Cell Format Service - Includes font settings */ import { getAbsolutePath, handleExcelFileError, validateRange } from '../core/utils.js'; import { stringToEnum, hexToColor } from '../core/enum-converter.js'; interface FormatConfig { range: string; // Font properties font_name?: string; font_size?: number; bold?: boolean; italic?: boolean; underline?: boolean; strikethrough?: boolean; font_color?: string; subscript?: boolean; superscript?: boolean; // Background properties background_color?: string; pattern?: 'solid' | 'gradient' | 'pattern'; // Alignment properties horizontal_align?: 'left' | 'center' | 'right' | 'justify' | 'fill'; vertical_align?: 'top' | 'middle' | 'bottom' | 'justify'; text_wrap?: boolean; text_rotation?: number; indent?: number; // Border properties border_style?: 'none' | 'thin' | 'thick' | 'medium' | 'dashed' | 'dotted' | 'double'; border_color?: string; border_sides?: Array<'top' | 'bottom' | 'left' | 'right' | 'all'>; // Number format number_format?: string; // Protection locked?: boolean; hidden?: boolean; } interface CellFormatArgs { filepath: string; sheet_name: string; format_ranges: FormatConfig[]; } interface ServiceResponse { content: Array<{ type: 'text'; text: string; }>; [key: string]: unknown; } /** * Apply cell formatting to a range * @param worksheet - Worksheet instance * @param AsposeCells - Aspose cells module * @param range - Cell range * @param formatConfig - Format configuration * @returns Applied formats */ function applyFormatToRange(worksheet: any, AsposeCells: any, range: string, formatConfig: FormatConfig): string[] { const cells = worksheet.getCells(); // Validate range using shared utility function const { cellRange } = validateRange(range, cells); // Create a new style object instead of modifying existing one const workbook = worksheet.getWorkbook(); const style = workbook.createStyle(); // Create StyleFlag to control which properties to apply const styleFlag = new AsposeCells.StyleFlag(); const appliedFormats: string[] = []; // Font settings let fontChanged = false; let font: any = null; const getFont = () => { if (!font) font = style.getFont(); return font; }; if (formatConfig.font_name) { getFont().setName(formatConfig.font_name); fontChanged = true; appliedFormats.push(`font name: ${formatConfig.font_name}`); } if (formatConfig.font_size) { getFont().setSize(formatConfig.font_size); fontChanged = true; appliedFormats.push(`font size: ${formatConfig.font_size}pt`); } if (formatConfig.bold !== undefined) { getFont().setIsBold(formatConfig.bold); fontChanged = true; appliedFormats.push(`bold: ${formatConfig.bold}`); } if (formatConfig.italic !== undefined) { getFont().setIsItalic(formatConfig.italic); fontChanged = true; appliedFormats.push(`italic: ${formatConfig.italic}`); } if (formatConfig.underline !== undefined) { const underlineType = formatConfig.underline ? AsposeCells.FontUnderlineType.Single : AsposeCells.FontUnderlineType.None; getFont().setUnderline(underlineType); fontChanged = true; appliedFormats.push(`underline: ${formatConfig.underline}`); } if (formatConfig.strikethrough !== undefined) { getFont().setIsStrikeout(formatConfig.strikethrough); fontChanged = true; appliedFormats.push(`strikethrough: ${formatConfig.strikethrough}`); } if (formatConfig.font_color) { const color = hexToColor(formatConfig.font_color, AsposeCells); if (color) { getFont().setColor(color); fontChanged = true; appliedFormats.push(`font color: ${formatConfig.font_color}`); } } if (formatConfig.subscript !== undefined) { getFont().setScriptOffset(formatConfig.subscript ? -20 : 0); fontChanged = true; appliedFormats.push(`subscript: ${formatConfig.subscript}`); } if (formatConfig.superscript !== undefined) { getFont().setScriptOffset(formatConfig.superscript ? 20 : 0); fontChanged = true; appliedFormats.push(`superscript: ${formatConfig.superscript}`); } if (fontChanged) { styleFlag.setFont(true); } // Background color and pattern if (formatConfig.background_color) { const color = hexToColor(formatConfig.background_color, AsposeCells); if (color) { styleFlag.setCellShading(true); // Handle pattern - default to solid if not specified const pattern = formatConfig.pattern || 'solid'; const backgroundType = stringToEnum(pattern, AsposeCells.BackgroundType); if (backgroundType) { style.setPattern(backgroundType); } else { style.setPattern(AsposeCells.BackgroundType.Solid); } style.setForegroundColor(color); appliedFormats.push(`background: ${formatConfig.background_color}${formatConfig.pattern ? ` (${formatConfig.pattern})` : ''}`); } } // Alignment if (formatConfig.horizontal_align) { const hAlign = stringToEnum(formatConfig.horizontal_align, AsposeCells.TextAlignmentType); if (hAlign) { style.setHorizontalAlignment(hAlign); styleFlag.setHorizontalAlignment(true); appliedFormats.push(`horizontal align: ${formatConfig.horizontal_align}`); } } if (formatConfig.vertical_align) { const vAlign = stringToEnum(formatConfig.vertical_align, AsposeCells.TextAlignmentType); if (vAlign) { style.setVerticalAlignment(vAlign); styleFlag.setVerticalAlignment(true); appliedFormats.push(`vertical align: ${formatConfig.vertical_align}`); } } if (formatConfig.text_wrap !== undefined) { style.setIsTextWrapped(formatConfig.text_wrap); styleFlag.setWrapText(true); appliedFormats.push(`text wrap: ${formatConfig.text_wrap}`); } if (formatConfig.text_rotation !== undefined) { style.setRotationAngle(formatConfig.text_rotation); styleFlag.setRotation(true); appliedFormats.push(`text rotation: ${formatConfig.text_rotation}°`); } if (formatConfig.indent !== undefined) { style.setIndentLevel(formatConfig.indent); styleFlag.setIndent(true); appliedFormats.push(`indent: ${formatConfig.indent}`); } // Number format if (formatConfig.number_format) { styleFlag.setNumberFormat(true); if (typeof formatConfig.number_format === 'string') { style.setCustom(formatConfig.number_format); } else { style.setNumber(formatConfig.number_format); } appliedFormats.push(`number format: ${formatConfig.number_format}`); } // Protection if (formatConfig.locked !== undefined) { style.setIsLocked(formatConfig.locked); styleFlag.setLocked(true); appliedFormats.push(`locked: ${formatConfig.locked}`); } if (formatConfig.hidden !== undefined) { style.setIsFormulaHidden(formatConfig.hidden); styleFlag.setHideFormula(true); appliedFormats.push(`hidden: ${formatConfig.hidden}`); } // Borders if (formatConfig.border_style && formatConfig.border_style !== 'none') { const borderStyle = stringToEnum(formatConfig.border_style, AsposeCells.CellBorderType); const borderColor = formatConfig.border_color ? hexToColor(formatConfig.border_color, AsposeCells) : null; const borderSides = formatConfig.border_sides || ['all']; if (borderStyle) { styleFlag.setBorders(true); const borders = style.getBorders(); const borderTypes = { 'top': AsposeCells.BorderType.TopBorder, 'bottom': AsposeCells.BorderType.BottomBorder, 'left': AsposeCells.BorderType.LeftBorder, 'right': AsposeCells.BorderType.RightBorder }; for (const side of borderSides) { if (side === 'all') { for (const borderType of Object.values(borderTypes)) { const border = borders.get(borderType); border.setLineStyle(borderStyle); if (borderColor) border.setColor(borderColor); } } else if (borderTypes[side]) { const border = borders.get(borderTypes[side]); border.setLineStyle(borderStyle); if (borderColor) border.setColor(borderColor); } } appliedFormats.push(`border: ${formatConfig.border_style}${borderColor ? ` (${formatConfig.border_color})` : ''}`); } } // Apply the style to the range using StyleFlag cellRange.applyStyle(style, styleFlag); return appliedFormats; } export const cellFormatService = { definition: { name: 'cell_format', description: 'Format Excel cells: font, background color, borders, alignment, number format.', inputSchema: { type: 'object', properties: { filepath: { type: 'string', description: 'Path to the Excel file' }, sheet_name: { type: 'string', description: 'Name of the worksheet' }, format_ranges: { type: 'array', description: 'Array of format configurations', items: { type: 'object', properties: { range: { type: 'string', description: 'Cell range (e.g., A1:B2 or A1)' }, // Font properties font_name: { type: 'string', description: 'Font family name (e.g., "Arial", "Times New Roman")' }, font_size: { type: 'number', description: 'Font size in points' }, bold: { type: 'boolean', description: 'Make text bold' }, italic: { type: 'boolean', description: 'Make text italic' }, underline: { type: 'boolean', description: 'Make text underlined' }, strikethrough: { type: 'boolean', description: 'Add strikethrough to text' }, font_color: { type: 'string', description: 'Font color (hex code, e.g., "#FF0000")' }, subscript: { type: 'boolean', description: 'Make text subscript' }, superscript: { type: 'boolean', description: 'Make text superscript' }, // Background properties background_color: { type: 'string', description: 'Background/fill color (hex code, e.g., "#FFFF00")' }, pattern: { type: 'string', description: 'Fill pattern', enum: ['solid', 'gradient', 'pattern'], default: 'solid' }, // Alignment properties horizontal_align: { type: 'string', description: 'Horizontal alignment', enum: ['left', 'center', 'right', 'justify', 'fill'] }, vertical_align: { type: 'string', description: 'Vertical alignment', enum: ['top', 'middle', 'bottom', 'justify'] }, text_wrap: { type: 'boolean', description: 'Wrap text in cell' }, text_rotation: { type: 'number', description: 'Text rotation angle (-90 to 90 degrees)' }, indent: { type: 'number', description: 'Text indent level' }, // Border properties border_style: { type: 'string', description: 'Border style for all sides', enum: ['none', 'thin', 'thick', 'medium', 'dashed', 'dotted', 'double'] }, border_color: { type: 'string', description: 'Border color (hex code)' }, border_sides: { type: 'array', description: 'Which sides to apply borders to', items: { type: 'string', enum: ['top', 'bottom', 'left', 'right', 'all'] }, default: ['all'] }, // Number format number_format: { type: 'string', description: 'Number format (e.g., "0.00", "0%", "yyyy-mm-dd", "$#,##0.00")' }, // Protection locked: { type: 'boolean', description: 'Lock cell for protection' }, hidden: { type: 'boolean', description: 'Hide cell formula' } }, required: ['range'] } } }, required: ['filepath', 'sheet_name', 'format_ranges'] } }, async handler(args: CellFormatArgs): Promise<ServiceResponse> { let fullPath: string = ''; try { const AsposeCells = await import('aspose.cells.node').then(m => m.default); fullPath = getAbsolutePath(args.filepath); const workbook = new AsposeCells.Workbook(fullPath); const worksheet = workbook.getWorksheets().get(args.sheet_name); if (!worksheet) { throw new Error(`Sheet "${args.sheet_name}" not found`); } if (!args.format_ranges || !Array.isArray(args.format_ranges) || args.format_ranges.length === 0) { throw new Error('format_ranges array is required and cannot be empty'); } let results: string[] = []; // Process each format configuration for (const formatConfig of args.format_ranges) { const appliedFormats = applyFormatToRange(worksheet, AsposeCells, formatConfig.range, formatConfig); results.push(`${formatConfig.range}: ${appliedFormats.join(', ')}`); } workbook.save(fullPath); return { content: [{ type: 'text', text: results.length > 1 ? `Applied formatting to ${results.length} ranges:\n${results.join('\n')}` : `Applied cell formatting to range ${results[0]}` }] }; } catch (error) { const err = error instanceof Error ? error : new Error(String(error)); handleExcelFileError(err, fullPath || args.filepath, args.filepath, 'formatting cells'); } } };