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
text/typescript
/**
* 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');
}
}
};