excel-mcp-server 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of excel-mcp-server might be problematic. Click here for more details.
- excel_mcp_server-0.1.0/.gitignore +36 -0
- excel_mcp_server-0.1.0/LICENSE +21 -0
- excel_mcp_server-0.1.0/PKG-INFO +126 -0
- excel_mcp_server-0.1.0/README.md +115 -0
- excel_mcp_server-0.1.0/excel_mcp/__main__.py +21 -0
- excel_mcp_server-0.1.0/excel_mcp/calculations.py +60 -0
- excel_mcp_server-0.1.0/excel_mcp/cell_utils.py +54 -0
- excel_mcp_server-0.1.0/excel_mcp/chart.py +237 -0
- excel_mcp_server-0.1.0/excel_mcp/data.py +294 -0
- excel_mcp_server-0.1.0/excel_mcp/exceptions.py +35 -0
- excel_mcp_server-0.1.0/excel_mcp/formatting.py +249 -0
- excel_mcp_server-0.1.0/excel_mcp/pivot.py +271 -0
- excel_mcp_server-0.1.0/excel_mcp/server.py +495 -0
- excel_mcp_server-0.1.0/excel_mcp/sheet.py +352 -0
- excel_mcp_server-0.1.0/excel_mcp/validation.py +235 -0
- excel_mcp_server-0.1.0/excel_mcp/workbook.py +96 -0
- excel_mcp_server-0.1.0/pyproject.toml +26 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Build and Distribution
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
src/*.egg-info/
|
|
7
|
+
|
|
8
|
+
# Development Environment
|
|
9
|
+
.venv/
|
|
10
|
+
.env
|
|
11
|
+
|
|
12
|
+
# IDE
|
|
13
|
+
.vscode/
|
|
14
|
+
.idea/
|
|
15
|
+
.cursor/
|
|
16
|
+
.cursorignore
|
|
17
|
+
.cursorrules
|
|
18
|
+
.specstory
|
|
19
|
+
|
|
20
|
+
# Testing and Linting
|
|
21
|
+
.coverage
|
|
22
|
+
.pytest_cache/
|
|
23
|
+
.ruff_cache/
|
|
24
|
+
.mypy_cache/
|
|
25
|
+
htmlcov/
|
|
26
|
+
tests/
|
|
27
|
+
|
|
28
|
+
# Project Files
|
|
29
|
+
extras/
|
|
30
|
+
.notes/
|
|
31
|
+
logs/
|
|
32
|
+
output/
|
|
33
|
+
*.xlsx
|
|
34
|
+
*.xls
|
|
35
|
+
*.log
|
|
36
|
+
excel_files/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Haris
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: excel-mcp-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for Excel file manipulation
|
|
5
|
+
Author-email: haris <haris.musa@outlook.com>
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: mcp[cli]>=1.2.0
|
|
9
|
+
Requires-Dist: openpyxl>=3.1.2
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Excel MCP Server
|
|
13
|
+
[](https://smithery.ai/server/@haris-musa/excel-mcp-server)
|
|
14
|
+
|
|
15
|
+
A Model Context Protocol (MCP) server implementation that provides Excel file manipulation capabilities without requiring Microsoft Excel installation. This server enables workbook creation, data manipulation, formatting, and advanced Excel features.
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- Python 3.10+
|
|
20
|
+
- MCP SDK 1.2.0+
|
|
21
|
+
- OpenPyXL 3.1.2+
|
|
22
|
+
|
|
23
|
+
## Components
|
|
24
|
+
|
|
25
|
+
### Resources
|
|
26
|
+
|
|
27
|
+
The server provides Excel workbook manipulation through OpenPyXL:
|
|
28
|
+
|
|
29
|
+
- Creates and modifies Excel workbooks
|
|
30
|
+
- Manages worksheets and ranges
|
|
31
|
+
- Handles formatting and styles
|
|
32
|
+
- Supports charts and pivot tables
|
|
33
|
+
|
|
34
|
+
### Tools
|
|
35
|
+
|
|
36
|
+
This server provides a comprehensive set of Excel manipulation tools. For detailed documentation of all available tools, their parameters, and usage examples, please refer to [TOOLS.md](TOOLS.md).
|
|
37
|
+
|
|
38
|
+
The tools include capabilities for:
|
|
39
|
+
|
|
40
|
+
- Workbook and worksheet management
|
|
41
|
+
- Data reading and writing
|
|
42
|
+
- Formatting and styling
|
|
43
|
+
- Charts and visualizations
|
|
44
|
+
- Pivot tables and data analysis
|
|
45
|
+
|
|
46
|
+
See [TOOLS.md](TOOLS.md) for complete documentation.
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
- Full Excel Support: Comprehensive Excel functionality
|
|
51
|
+
- Data Manipulation: Read, write, and transform data
|
|
52
|
+
- Advanced Features: Charts, pivot tables, and formatting
|
|
53
|
+
- Error Handling: Comprehensive error handling with clear messages
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
### Environment Configuration
|
|
58
|
+
|
|
59
|
+
The server can be configured using the following environment variables:
|
|
60
|
+
|
|
61
|
+
- `EXCEL_FILES_PATH`: Directory where Excel files will be stored (default: `./excel_files`)
|
|
62
|
+
|
|
63
|
+
You can set this in different ways:
|
|
64
|
+
|
|
65
|
+
Windows CMD:
|
|
66
|
+
|
|
67
|
+
```cmd
|
|
68
|
+
set EXCEL_FILES_PATH=C:\path\to\excel\files
|
|
69
|
+
uv run excel-mcp-server
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Windows PowerShell:
|
|
73
|
+
|
|
74
|
+
```powershell
|
|
75
|
+
$env:EXCEL_FILES_PATH="C:\path\to\excel\files"
|
|
76
|
+
uv run excel-mcp-server
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Linux/MacOS:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
export EXCEL_FILES_PATH=/path/to/excel/files
|
|
83
|
+
uv run excel-mcp-server
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Or in Claude Desktop config:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"mcpServers": {
|
|
91
|
+
"excel": {
|
|
92
|
+
"command": "uv run excel-mcp-server",
|
|
93
|
+
"transport": "sse",
|
|
94
|
+
"env": {
|
|
95
|
+
"EXCEL_FILES_PATH": "/path/to/excel/files"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Starting the Server
|
|
103
|
+
|
|
104
|
+
Start the server:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv run excel-mcp-server
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The server will start in SSE mode and wait for connections from MCP clients.
|
|
111
|
+
|
|
112
|
+
### Connecting in Cursor IDE
|
|
113
|
+
|
|
114
|
+
After starting the server, connect to the SSE endpoint in Cursor IDE:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
http://localhost:8000/sse
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The Excel MCP tools will be available through the agent.
|
|
121
|
+
|
|
122
|
+
For available tools and their usage, please refer to [TOOLS.md](TOOLS.md).
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Excel MCP Server
|
|
2
|
+
[](https://smithery.ai/server/@haris-musa/excel-mcp-server)
|
|
3
|
+
|
|
4
|
+
A Model Context Protocol (MCP) server implementation that provides Excel file manipulation capabilities without requiring Microsoft Excel installation. This server enables workbook creation, data manipulation, formatting, and advanced Excel features.
|
|
5
|
+
|
|
6
|
+
## Requirements
|
|
7
|
+
|
|
8
|
+
- Python 3.10+
|
|
9
|
+
- MCP SDK 1.2.0+
|
|
10
|
+
- OpenPyXL 3.1.2+
|
|
11
|
+
|
|
12
|
+
## Components
|
|
13
|
+
|
|
14
|
+
### Resources
|
|
15
|
+
|
|
16
|
+
The server provides Excel workbook manipulation through OpenPyXL:
|
|
17
|
+
|
|
18
|
+
- Creates and modifies Excel workbooks
|
|
19
|
+
- Manages worksheets and ranges
|
|
20
|
+
- Handles formatting and styles
|
|
21
|
+
- Supports charts and pivot tables
|
|
22
|
+
|
|
23
|
+
### Tools
|
|
24
|
+
|
|
25
|
+
This server provides a comprehensive set of Excel manipulation tools. For detailed documentation of all available tools, their parameters, and usage examples, please refer to [TOOLS.md](TOOLS.md).
|
|
26
|
+
|
|
27
|
+
The tools include capabilities for:
|
|
28
|
+
|
|
29
|
+
- Workbook and worksheet management
|
|
30
|
+
- Data reading and writing
|
|
31
|
+
- Formatting and styling
|
|
32
|
+
- Charts and visualizations
|
|
33
|
+
- Pivot tables and data analysis
|
|
34
|
+
|
|
35
|
+
See [TOOLS.md](TOOLS.md) for complete documentation.
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- Full Excel Support: Comprehensive Excel functionality
|
|
40
|
+
- Data Manipulation: Read, write, and transform data
|
|
41
|
+
- Advanced Features: Charts, pivot tables, and formatting
|
|
42
|
+
- Error Handling: Comprehensive error handling with clear messages
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
### Environment Configuration
|
|
47
|
+
|
|
48
|
+
The server can be configured using the following environment variables:
|
|
49
|
+
|
|
50
|
+
- `EXCEL_FILES_PATH`: Directory where Excel files will be stored (default: `./excel_files`)
|
|
51
|
+
|
|
52
|
+
You can set this in different ways:
|
|
53
|
+
|
|
54
|
+
Windows CMD:
|
|
55
|
+
|
|
56
|
+
```cmd
|
|
57
|
+
set EXCEL_FILES_PATH=C:\path\to\excel\files
|
|
58
|
+
uv run excel-mcp-server
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Windows PowerShell:
|
|
62
|
+
|
|
63
|
+
```powershell
|
|
64
|
+
$env:EXCEL_FILES_PATH="C:\path\to\excel\files"
|
|
65
|
+
uv run excel-mcp-server
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Linux/MacOS:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
export EXCEL_FILES_PATH=/path/to/excel/files
|
|
72
|
+
uv run excel-mcp-server
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or in Claude Desktop config:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"mcpServers": {
|
|
80
|
+
"excel": {
|
|
81
|
+
"command": "uv run excel-mcp-server",
|
|
82
|
+
"transport": "sse",
|
|
83
|
+
"env": {
|
|
84
|
+
"EXCEL_FILES_PATH": "/path/to/excel/files"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Starting the Server
|
|
92
|
+
|
|
93
|
+
Start the server:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
uv run excel-mcp-server
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The server will start in SSE mode and wait for connections from MCP clients.
|
|
100
|
+
|
|
101
|
+
### Connecting in Cursor IDE
|
|
102
|
+
|
|
103
|
+
After starting the server, connect to the SSE endpoint in Cursor IDE:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
http://localhost:8000/sse
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The Excel MCP tools will be available through the agent.
|
|
110
|
+
|
|
111
|
+
For available tools and their usage, please refer to [TOOLS.md](TOOLS.md).
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from .server import run_server
|
|
3
|
+
|
|
4
|
+
def main():
|
|
5
|
+
"""Start the Excel MCP server."""
|
|
6
|
+
try:
|
|
7
|
+
print("Excel MCP Server")
|
|
8
|
+
print("---------------")
|
|
9
|
+
print("Starting server... Press Ctrl+C to exit")
|
|
10
|
+
asyncio.run(run_server())
|
|
11
|
+
except KeyboardInterrupt:
|
|
12
|
+
print("\nShutting down server...")
|
|
13
|
+
except Exception as e:
|
|
14
|
+
print(f"\nError: {e}")
|
|
15
|
+
import traceback
|
|
16
|
+
traceback.print_exc()
|
|
17
|
+
finally:
|
|
18
|
+
print("Server stopped.")
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
main()
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from .workbook import get_or_create_workbook
|
|
5
|
+
from .cell_utils import validate_cell_reference
|
|
6
|
+
from .exceptions import ValidationError, CalculationError
|
|
7
|
+
from .validation import validate_formula
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
def apply_formula(
|
|
12
|
+
filepath: str,
|
|
13
|
+
sheet_name: str,
|
|
14
|
+
cell: str,
|
|
15
|
+
formula: str
|
|
16
|
+
) -> dict[str, Any]:
|
|
17
|
+
"""Apply any Excel formula to a cell."""
|
|
18
|
+
try:
|
|
19
|
+
if not validate_cell_reference(cell):
|
|
20
|
+
raise ValidationError(f"Invalid cell reference: {cell}")
|
|
21
|
+
|
|
22
|
+
wb = get_or_create_workbook(filepath)
|
|
23
|
+
if sheet_name not in wb.sheetnames:
|
|
24
|
+
raise ValidationError(f"Sheet '{sheet_name}' not found")
|
|
25
|
+
|
|
26
|
+
sheet = wb[sheet_name]
|
|
27
|
+
|
|
28
|
+
# Ensure formula starts with =
|
|
29
|
+
if not formula.startswith('='):
|
|
30
|
+
formula = f'={formula}'
|
|
31
|
+
|
|
32
|
+
# Validate formula syntax
|
|
33
|
+
is_valid, message = validate_formula(formula)
|
|
34
|
+
if not is_valid:
|
|
35
|
+
raise CalculationError(f"Invalid formula syntax: {message}")
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
# Apply formula to the cell
|
|
39
|
+
cell_obj = sheet[cell]
|
|
40
|
+
cell_obj.value = formula
|
|
41
|
+
except Exception as e:
|
|
42
|
+
raise CalculationError(f"Failed to apply formula to cell: {str(e)}")
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
wb.save(filepath)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
raise CalculationError(f"Failed to save workbook after applying formula: {str(e)}")
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
"message": f"Applied formula '{formula}' to cell {cell}",
|
|
51
|
+
"cell": cell,
|
|
52
|
+
"formula": formula
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
except (ValidationError, CalculationError) as e:
|
|
56
|
+
logger.error(str(e))
|
|
57
|
+
raise
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.error(f"Failed to apply formula: {e}")
|
|
60
|
+
raise CalculationError(str(e))
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from openpyxl.utils import column_index_from_string
|
|
4
|
+
|
|
5
|
+
def parse_cell_range(
|
|
6
|
+
cell_ref: str,
|
|
7
|
+
end_ref: str | None = None
|
|
8
|
+
) -> tuple[int, int, int | None, int | None]:
|
|
9
|
+
"""Parse Excel cell reference into row and column indices."""
|
|
10
|
+
if end_ref:
|
|
11
|
+
start_cell = cell_ref
|
|
12
|
+
end_cell = end_ref
|
|
13
|
+
else:
|
|
14
|
+
start_cell = cell_ref
|
|
15
|
+
end_cell = None
|
|
16
|
+
|
|
17
|
+
match = re.match(r"([A-Z]+)([0-9]+)", start_cell.upper())
|
|
18
|
+
if not match:
|
|
19
|
+
raise ValueError(f"Invalid cell reference: {start_cell}")
|
|
20
|
+
col_str, row_str = match.groups()
|
|
21
|
+
start_row = int(row_str)
|
|
22
|
+
start_col = column_index_from_string(col_str)
|
|
23
|
+
|
|
24
|
+
if end_cell:
|
|
25
|
+
match = re.match(r"([A-Z]+)([0-9]+)", end_cell.upper())
|
|
26
|
+
if not match:
|
|
27
|
+
raise ValueError(f"Invalid cell reference: {end_cell}")
|
|
28
|
+
col_str, row_str = match.groups()
|
|
29
|
+
end_row = int(row_str)
|
|
30
|
+
end_col = column_index_from_string(col_str)
|
|
31
|
+
else:
|
|
32
|
+
end_row = None
|
|
33
|
+
end_col = None
|
|
34
|
+
|
|
35
|
+
return start_row, start_col, end_row, end_col
|
|
36
|
+
|
|
37
|
+
def validate_cell_reference(cell_ref: str) -> bool:
|
|
38
|
+
"""Validate Excel cell reference format (e.g., 'A1', 'BC123')"""
|
|
39
|
+
if not cell_ref:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
# Split into column and row parts
|
|
43
|
+
col = row = ""
|
|
44
|
+
for c in cell_ref:
|
|
45
|
+
if c.isalpha():
|
|
46
|
+
if row: # Letters after numbers not allowed
|
|
47
|
+
return False
|
|
48
|
+
col += c
|
|
49
|
+
elif c.isdigit():
|
|
50
|
+
row += c
|
|
51
|
+
else:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
return bool(col and row)
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
from typing import Any, Optional, Dict
|
|
2
|
+
import logging
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
from openpyxl import load_workbook
|
|
6
|
+
from openpyxl.chart import (
|
|
7
|
+
BarChart, LineChart, PieChart, ScatterChart,
|
|
8
|
+
AreaChart, Reference, Series
|
|
9
|
+
)
|
|
10
|
+
from openpyxl.chart.label import DataLabelList
|
|
11
|
+
from openpyxl.chart.legend import Legend
|
|
12
|
+
from openpyxl.chart.axis import ChartLines
|
|
13
|
+
from openpyxl.drawing.spreadsheet_drawing import (
|
|
14
|
+
AnchorMarker, OneCellAnchor, SpreadsheetDrawing
|
|
15
|
+
)
|
|
16
|
+
from openpyxl.utils import column_index_from_string
|
|
17
|
+
|
|
18
|
+
from .cell_utils import parse_cell_range
|
|
19
|
+
from .exceptions import ValidationError, ChartError
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
class ChartType(str, Enum):
|
|
24
|
+
"""Supported chart types"""
|
|
25
|
+
LINE = "line"
|
|
26
|
+
BAR = "bar"
|
|
27
|
+
PIE = "pie"
|
|
28
|
+
SCATTER = "scatter"
|
|
29
|
+
AREA = "area"
|
|
30
|
+
BUBBLE = "bubble"
|
|
31
|
+
STOCK = "stock"
|
|
32
|
+
SURFACE = "surface"
|
|
33
|
+
RADAR = "radar"
|
|
34
|
+
|
|
35
|
+
class ChartStyle:
|
|
36
|
+
"""Chart style configuration"""
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
title_size: int = 14,
|
|
40
|
+
title_bold: bool = True,
|
|
41
|
+
axis_label_size: int = 12,
|
|
42
|
+
show_legend: bool = True,
|
|
43
|
+
legend_position: str = "r",
|
|
44
|
+
show_data_labels: bool = True,
|
|
45
|
+
grid_lines: bool = False,
|
|
46
|
+
style_id: int = 2
|
|
47
|
+
):
|
|
48
|
+
self.title_size = title_size
|
|
49
|
+
self.title_bold = title_bold
|
|
50
|
+
self.axis_label_size = axis_label_size
|
|
51
|
+
self.show_legend = show_legend
|
|
52
|
+
self.legend_position = legend_position
|
|
53
|
+
self.show_data_labels = show_data_labels
|
|
54
|
+
self.grid_lines = grid_lines
|
|
55
|
+
self.style_id = style_id
|
|
56
|
+
|
|
57
|
+
def create_chart_in_sheet(
|
|
58
|
+
filepath: str,
|
|
59
|
+
sheet_name: str,
|
|
60
|
+
data_range: str,
|
|
61
|
+
chart_type: str,
|
|
62
|
+
target_cell: str,
|
|
63
|
+
title: str = "",
|
|
64
|
+
x_axis: str = "",
|
|
65
|
+
y_axis: str = "",
|
|
66
|
+
style: Optional[Dict] = None
|
|
67
|
+
) -> dict[str, Any]:
|
|
68
|
+
"""Create chart in sheet with enhanced styling options"""
|
|
69
|
+
try:
|
|
70
|
+
wb = load_workbook(filepath)
|
|
71
|
+
if sheet_name not in wb.sheetnames:
|
|
72
|
+
logger.error(f"Sheet '{sheet_name}' not found")
|
|
73
|
+
raise ValidationError(f"Sheet '{sheet_name}' not found")
|
|
74
|
+
|
|
75
|
+
worksheet = wb[sheet_name]
|
|
76
|
+
|
|
77
|
+
# Initialize collections if they don't exist
|
|
78
|
+
if not hasattr(worksheet, '_drawings'):
|
|
79
|
+
worksheet._drawings = []
|
|
80
|
+
if not hasattr(worksheet, '_charts'):
|
|
81
|
+
worksheet._charts = []
|
|
82
|
+
|
|
83
|
+
# Parse the data range
|
|
84
|
+
if "!" in data_range:
|
|
85
|
+
range_sheet_name, cell_range = data_range.split("!")
|
|
86
|
+
if range_sheet_name not in wb.sheetnames:
|
|
87
|
+
logger.error(f"Sheet '{range_sheet_name}' referenced in data range not found")
|
|
88
|
+
raise ValidationError(f"Sheet '{range_sheet_name}' referenced in data range not found")
|
|
89
|
+
else:
|
|
90
|
+
cell_range = data_range
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
start_cell, end_cell = cell_range.split(":")
|
|
94
|
+
start_row, start_col, end_row, end_col = parse_cell_range(start_cell, end_cell)
|
|
95
|
+
except ValueError as e:
|
|
96
|
+
logger.error(f"Invalid data range format: {e}")
|
|
97
|
+
raise ValidationError(f"Invalid data range format: {str(e)}")
|
|
98
|
+
|
|
99
|
+
# Validate chart type
|
|
100
|
+
chart_classes = {
|
|
101
|
+
"line": LineChart,
|
|
102
|
+
"bar": BarChart,
|
|
103
|
+
"pie": PieChart,
|
|
104
|
+
"scatter": ScatterChart,
|
|
105
|
+
"area": AreaChart
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
chart_type_lower = chart_type.lower()
|
|
109
|
+
ChartClass = chart_classes.get(chart_type_lower)
|
|
110
|
+
if not ChartClass:
|
|
111
|
+
logger.error(f"Unsupported chart type: {chart_type}")
|
|
112
|
+
raise ValidationError(
|
|
113
|
+
f"Unsupported chart type: {chart_type}. "
|
|
114
|
+
f"Supported types: {', '.join(chart_classes.keys())}"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
chart = ChartClass()
|
|
118
|
+
|
|
119
|
+
# Basic chart settings
|
|
120
|
+
chart.title = title
|
|
121
|
+
if hasattr(chart, "x_axis"):
|
|
122
|
+
chart.x_axis.title = x_axis
|
|
123
|
+
if hasattr(chart, "y_axis"):
|
|
124
|
+
chart.y_axis.title = y_axis
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
# Create data references
|
|
128
|
+
if chart_type_lower == "scatter":
|
|
129
|
+
# For scatter charts, create series for each pair of columns
|
|
130
|
+
for col in range(start_col + 1, end_col + 1):
|
|
131
|
+
x_values = Reference(
|
|
132
|
+
worksheet,
|
|
133
|
+
min_row=start_row + 1,
|
|
134
|
+
max_row=end_row,
|
|
135
|
+
min_col=start_col
|
|
136
|
+
)
|
|
137
|
+
y_values = Reference(
|
|
138
|
+
worksheet,
|
|
139
|
+
min_row=start_row + 1,
|
|
140
|
+
max_row=end_row,
|
|
141
|
+
min_col=col
|
|
142
|
+
)
|
|
143
|
+
series = Series(y_values, x_values, title_from_data=True)
|
|
144
|
+
chart.series.append(series)
|
|
145
|
+
else:
|
|
146
|
+
# For other chart types
|
|
147
|
+
data = Reference(
|
|
148
|
+
worksheet,
|
|
149
|
+
min_row=start_row,
|
|
150
|
+
max_row=end_row,
|
|
151
|
+
min_col=start_col + 1,
|
|
152
|
+
max_col=end_col
|
|
153
|
+
)
|
|
154
|
+
cats = Reference(
|
|
155
|
+
worksheet,
|
|
156
|
+
min_row=start_row + 1,
|
|
157
|
+
max_row=end_row,
|
|
158
|
+
min_col=start_col
|
|
159
|
+
)
|
|
160
|
+
chart.add_data(data, titles_from_data=True)
|
|
161
|
+
chart.set_categories(cats)
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.error(f"Failed to create chart data references: {e}")
|
|
164
|
+
raise ChartError(f"Failed to create chart data references: {str(e)}")
|
|
165
|
+
|
|
166
|
+
# Apply style if provided
|
|
167
|
+
try:
|
|
168
|
+
if style:
|
|
169
|
+
if style.get("show_legend", True):
|
|
170
|
+
chart.legend = Legend()
|
|
171
|
+
chart.legend.position = style.get("legend_position", "r")
|
|
172
|
+
else:
|
|
173
|
+
chart.legend = None
|
|
174
|
+
|
|
175
|
+
if style.get("show_data_labels", False):
|
|
176
|
+
chart.dataLabels = DataLabelList()
|
|
177
|
+
chart.dataLabels.showVal = True
|
|
178
|
+
|
|
179
|
+
if style.get("grid_lines", False):
|
|
180
|
+
if hasattr(chart, "x_axis"):
|
|
181
|
+
chart.x_axis.majorGridlines = ChartLines()
|
|
182
|
+
if hasattr(chart, "y_axis"):
|
|
183
|
+
chart.y_axis.majorGridlines = ChartLines()
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.error(f"Failed to apply chart style: {e}")
|
|
186
|
+
raise ChartError(f"Failed to apply chart style: {str(e)}")
|
|
187
|
+
|
|
188
|
+
# Set chart size
|
|
189
|
+
chart.width = 15
|
|
190
|
+
chart.height = 7.5
|
|
191
|
+
|
|
192
|
+
# Create drawing and anchor
|
|
193
|
+
try:
|
|
194
|
+
drawing = SpreadsheetDrawing()
|
|
195
|
+
drawing.chart = chart
|
|
196
|
+
|
|
197
|
+
# Validate target cell format
|
|
198
|
+
if not target_cell or not any(c.isalpha() for c in target_cell) or not any(c.isdigit() for c in target_cell):
|
|
199
|
+
raise ValidationError(f"Invalid target cell format: {target_cell}")
|
|
200
|
+
|
|
201
|
+
# Create anchor
|
|
202
|
+
col = column_index_from_string(target_cell[0]) - 1
|
|
203
|
+
row = int(target_cell[1:]) - 1
|
|
204
|
+
anchor = OneCellAnchor()
|
|
205
|
+
anchor._from = AnchorMarker(col=col, row=row)
|
|
206
|
+
drawing.anchor = anchor
|
|
207
|
+
|
|
208
|
+
# Add to worksheet
|
|
209
|
+
worksheet._drawings.append(drawing)
|
|
210
|
+
worksheet._charts.append(chart)
|
|
211
|
+
except ValueError as e:
|
|
212
|
+
logger.error(f"Invalid target cell: {e}")
|
|
213
|
+
raise ValidationError(f"Invalid target cell: {str(e)}")
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.error(f"Failed to create chart drawing: {e}")
|
|
216
|
+
raise ChartError(f"Failed to create chart drawing: {str(e)}")
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
wb.save(filepath)
|
|
220
|
+
except Exception as e:
|
|
221
|
+
logger.error(f"Failed to save workbook: {e}")
|
|
222
|
+
raise ChartError(f"Failed to save workbook with chart: {str(e)}")
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
"message": f"{chart_type.capitalize()} chart created successfully",
|
|
226
|
+
"details": {
|
|
227
|
+
"type": chart_type,
|
|
228
|
+
"location": target_cell,
|
|
229
|
+
"data_range": data_range
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
except (ValidationError, ChartError):
|
|
234
|
+
raise
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.error(f"Unexpected error creating chart: {e}")
|
|
237
|
+
raise ChartError(f"Unexpected error creating chart: {str(e)}")
|