From 07369fdc65aedb06e38c1799ed7ec67b9dcb9d40 Mon Sep 17 00:00:00 2001 From: lijiepublic <2754140@qq.com> Date: Wed, 30 Apr 2025 01:31:01 +0800 Subject: [PATCH] simplify the format of data in batch read and write function to list of lists (#16) --- src/excel_mcp/data.py | 84 +++++++++-------------------------------- src/excel_mcp/server.py | 31 ++++++++++----- uv.lock | 3 +- 3 files changed, 41 insertions(+), 77 deletions(-) diff --git a/src/excel_mcp/data.py b/src/excel_mcp/data.py index 017a3c7..e4e3117 100644 --- a/src/excel_mcp/data.py +++ b/src/excel_mcp/data.py @@ -51,8 +51,14 @@ def read_excel_range( except ValueError as e: raise DataError(f"Invalid end cell format: {str(e)}") else: - # For single cell, use same coordinates + # Dynamically expand range until all values are empty end_row, end_col = start_row, start_col + while end_row <= ws.max_row and any(ws.cell(row=end_row, column=c).value is not None for c in range(start_col, ws.max_column + 1)): + end_row += 1 + while end_col <= ws.max_column and any(ws.cell(row=r, column=end_col).value is not None for r in range(start_row, ws.max_row + 1)): + end_col += 1 + end_row -= 1 # Adjust back to last non-empty row + end_col -= 1 # Adjust back to last non-empty column # Validate range bounds if start_row > ws.max_row or start_col > ws.max_column: @@ -62,31 +68,13 @@ def read_excel_range( ) data = [] - # If it's a single cell or single row, just read the values directly - if start_row == end_row: - row_data = {} + for row in range(start_row, end_row + 1): + row_data = [] for col in range(start_col, end_col + 1): - cell = ws.cell(row=start_row, column=col) - col_name = f"Column_{col}" - row_data[col_name] = cell.value - if any(v is not None for v in row_data.values()): + cell = ws.cell(row=row, column=col) + row_data.append(cell.value) + if any(v is not None for v in row_data): data.append(row_data) - else: - # Multiple rows - use header row - headers = [] - for col in range(start_col, end_col + 1): - cell_value = ws.cell(row=start_row, column=col).value - headers.append(str(cell_value) if cell_value is not None else f"Column_{col}") - - # Get data rows - max_rows = min(start_row + 5, end_row) if preview_only else end_row - for row in range(start_row + 1, max_rows + 1): - row_data = {} - for col, header in enumerate(headers, start=start_col): - cell = ws.cell(row=row, column=col) - row_data[header] = cell.value - if any(v is not None for v in row_data.values()): - data.append(row_data) wb.close() return data @@ -100,7 +88,7 @@ def read_excel_range( def write_data( filepath: str, sheet_name: str | None, - data: list[dict[str, Any]] | None, + data: list[list] | None, start_cell: str = "A1", ) -> dict[str, str]: """Write data to Excel sheet with workbook handling @@ -230,7 +218,7 @@ def _determine_header_behavior(worksheet, start_row, start_col, data): def _write_data_to_worksheet( worksheet: Worksheet, - data: list[dict[str, Any]], + data: list[list], start_cell: str = "A1", ) -> None: """Write data to worksheet with intelligent header handling""" @@ -246,46 +234,10 @@ def _write_data_to_worksheet( except ValueError as e: raise DataError(f"Invalid start cell format: {str(e)}") - # Validate data structure - if not all(isinstance(row, dict) for row in data): - raise DataError("All data rows must be dictionaries") - - # Get headers from first data row's keys - headers = list(data[0].keys()) - - # Check if first row appears to be headers (keys match values) - first_row_is_headers = _looks_like_headers(data[0]) - - # Determine if we should write headers based on context - should_write_headers = _determine_header_behavior( - worksheet, start_row, start_col, data - ) - - # Determine what data to write - actual_data = data - - # Only skip the first row if it contains headers AND we're writing headers - if first_row_is_headers and should_write_headers: - actual_data = data[1:] - elif first_row_is_headers and not should_write_headers: - actual_data = data - - # Write headers if needed - current_row = start_row - if should_write_headers: - for i, header in enumerate(headers): - cell = worksheet.cell(row=current_row, column=start_col + i) - cell.value = header - cell.font = Font(bold=True) - current_row += 1 # Move down after writing headers - - # Write actual data - for i, row_dict in enumerate(actual_data): - if not all(h in row_dict for h in headers): - raise DataError(f"Row {i+1} is missing required headers") - for j, header in enumerate(headers): - cell = worksheet.cell(row=current_row + i, column=start_col + j) - cell.value = row_dict.get(header, "") + # Write data + for i, row in enumerate(data): + for j, val in enumerate(row): + worksheet.cell(row=start_row + i, column=start_col + j, value=val) except DataError as e: logger.error(str(e)) raise diff --git a/src/excel_mcp/server.py b/src/excel_mcp/server.py index 3a06a16..d186529 100644 --- a/src/excel_mcp/server.py +++ b/src/excel_mcp/server.py @@ -91,7 +91,10 @@ def apply_formula( cell: str, formula: str, ) -> str: - """Apply Excel formula to cell.""" + """ + Apply Excel formula to cell. + Excel formula will write to cell with verification. + """ try: full_path = get_excel_path(filepath) # First validate the formula @@ -188,7 +191,12 @@ def read_data_from_excel( end_cell: str = None, preview_only: bool = False ) -> str: - """Read data from Excel worksheet.""" + """ + Read data from Excel worksheet. + + Returns: + Data from Excel worksheet as json string. list of lists or empty list if no data found. sublists are assumed to be rows. + """ try: full_path = get_excel_path(filepath) from excel_mcp.data import read_excel_range @@ -206,16 +214,19 @@ def read_data_from_excel( def write_data_to_excel( filepath: str, sheet_name: str, - data: List[Dict], + data: List[List], start_cell: str = "A1", ) -> str: - """Write data to Excel worksheet. - - The function automatically detects the context and handles headers intelligently: - - Headers are added when writing to a new area - - Headers are not duplicated when writing below existing headers - - Title areas (rows 1-4) are treated specially - - If the first row of data appears to be headers, it will be used accordingly + """ + Write data to Excel worksheet. + Excel formula will write to cell without any verification. + + PARAMETERS: + filepath: Path to Excel file + sheet_name: Name of worksheet to write to + data: List of lists containing data to write to the worksheet, sublists are assumed to be rows + start_cell: Cell to start writing to, default is "A1" + """ try: full_path = get_excel_path(filepath) diff --git a/uv.lock b/uv.lock index e415b9a..0ea26e9 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,5 @@ version = 1 +revision = 1 requires-python = ">=3.10" [[package]] @@ -66,7 +67,7 @@ wheels = [ [[package]] name = "excel-mcp-server" -version = "0.1.0" +version = "0.1.1" source = { editable = "." } dependencies = [ { name = "mcp", extra = ["cli"] },