PluginBench
Skill
Review
Audit score 70

excel-automation

daymade/claude-code-skills

Create, parse, and control Excel files on macOS with professional formatting and complex model support.

What is excel-automation?

Excel Automation provides three capabilities: create formatted Excel reports using openpyxl, parse complex financial models and xlsm files using stdlib zipfile+xml, and control Excel windows via AppleScript. Use this when building professional spreadsheets, handling investment bank financial models that openpyxl cannot parse, or automating Excel on macOS.

  • Create professionally formatted Excel files with investment banking color conventions, borders, and number formats using openpyxl
  • Parse complex xlsm and xlsx files by directly reading ZIP and XML structures, handling files with VBA macros and corrupted metadata
  • Extract cell data and resolve sheet names from Excel's internal ZIP structure using stdlib tools
  • Fix corrupted DefinedNames in investment bank financial models
  • Control Excel window behavior (zoom, scroll, cell selection) via AppleScript on macOS

How to install excel-automation

npx skills add https://github.com/daymade/claude-code-skills --skill excel-automation
Prerequisites
  • openpyxl library (for creating Excel files)
  • Python 3.6+ with zipfile and xml.etree.ElementTree (stdlib)
  • Microsoft Excel installed (for AppleScript control on macOS)
  • uv package manager for running scripts
Claude Code
Cursor
Windsurf
Cline

How to use excel-automation

  1. 1.Install the skill using the provided npx command
  2. 2.For creating formatted Excel: run `uv run --with openpyxl scripts/create_formatted_excel.py output.xlsx`
  3. 3.For parsing complex xlsm files: run `uv run scripts/parse_complex_excel.py model.xlsm` to list sheets or `uv run scripts/parse_complex_excel.py model.xlsm "SheetName"` to extract a specific sheet
  4. 4.For fixing corrupted files: run `uv run scripts/parse_complex_excel.py model.xlsm --fix`
  5. 5.For Excel window control: use `timeout 5 osascript -e 'tell application "Microsoft Excel" to activate'` to prevent hangs

Use cases

Good for
  • Generate formatted financial reports and dashboards with color-coded assumptions and calculations
  • Parse large (>1MB) or complex financial models from investment banks that openpyxl cannot handle
  • Extract data from xlsm files containing VBA macros or corrupted metadata
  • Automate Excel window interactions on macOS without using Excel's COM interface
  • Build sensitivity analysis tables with conditional formatting and red-to-green gradients
Who it's for
  • Financial analysts and investment banking professionals
  • Data engineers building Excel-based reporting pipelines
  • macOS users automating Excel workflows
  • Developers parsing legacy or complex Excel files

excel-automation FAQ

When should I use zipfile+xml instead of openpyxl?

Use zipfile+xml when the file is >1MB, has .xlsm extension, contains VBA macros, is from an investment bank, or when openpyxl fails to parse it. Always run `file <path>` first since file extensions can be misleading.

What color conventions should I use for professional Excel files?

Use blue (0000FF) for user inputs/assumptions, black (000000) for calculated values, green (008000) for cross-sheet references, and white text on dark blue (4472C4) for section headers, following investment banking standards.

How do I extract data from a specific sheet in a complex Excel file?

Use the sheet name resolution function to map the sheet name to its physical XML file path inside the ZIP, then parse the cells using the shared strings lookup table for text values.

Can this work on Windows or Linux?

The openpyxl and zipfile+xml parsing work on all platforms. AppleScript control is macOS-only; Windows users would need to use Excel COM automation instead.

What does 'Formula removed' in DefinedNames mean?

This is a corruption marker in investment bank xlsm files where Excel has removed formulas from named ranges. The skill includes a function to detect and remove these corrupted entries before repackaging the file.

Full instructions (SKILL.md)

Source of truth, from daymade/claude-code-skills.


name: excel-automation disable-model-invocation: true description: Create, parse, and control Excel files on macOS. Professional formatting with openpyxl, complex xlsm parsing with stdlib zipfile+xml for investment bank financial models, and Excel window control via AppleScript. Use when creating formatted Excel reports, parsing financial models that openpyxl cannot handle, or automating Excel on macOS.

Excel Automation

Create professional Excel files, parse complex financial models, and control Excel on macOS.

Quick Start

# Create a formatted Excel report
uv run --with openpyxl scripts/create_formatted_excel.py output.xlsx

# Parse a complex xlsm that openpyxl can't handle
uv run scripts/parse_complex_excel.py model.xlsm              # List sheets
uv run scripts/parse_complex_excel.py model.xlsm "DCF"        # Extract a sheet
uv run scripts/parse_complex_excel.py model.xlsm --fix        # Fix corrupted names

# Control Excel via AppleScript (with timeout to prevent hangs)
timeout 5 osascript -e 'tell application "Microsoft Excel" to activate'

Overview

Three capabilities:

CapabilityToolWhen to Use
Create formatted ExcelopenpyxlReports, mockups, dashboards
Parse complex xlsm/xlsxzipfile + xml.etreeFinancial models, VBA workbooks, >1MB files
Control Excel windowAppleScript (osascript)Zoom, scroll, select cells programmatically

Tool Selection Decision Tree

Is the file simple (data export, no VBA, <1MB)?
├─ YES → openpyxl or pandas
└─ NO
   ├─ Is it .xlsm or from investment bank / >1MB?
   │   └─ YES → zipfile + xml.etree.ElementTree (stdlib)
   └─ Is it truly .xls (BIFF format)?
       └─ YES → xlrd

Signals of "complex" Excel: file >1MB, .xlsm extension, from investment bank/broker, contains VBA macros.

IMPORTANT: Always run file <path> first — extensions lie. A .xls file may actually be a ZIP-based xlsx.

Creating Excel Files (openpyxl)

Professional Color Convention (Investment Banking Standard)

ColorRGB CodeMeaning
Blue0000FFUser input / assumption
Black000000Calculated value
Green008000Cross-sheet reference
White on dark blueFFFFFF on 4472C4Section headers
Dark blue text1F4E79Title

Core Formatting Patterns

from openpyxl.styles import Font, PatternFill, Border, Side, Alignment

# Fonts
BLUE_FONT = Font(color="0000FF", size=10, name="Calibri")
BLACK_FONT_BOLD = Font(color="000000", size=10, name="Calibri", bold=True)
GREEN_FONT = Font(color="008000", size=10, name="Calibri")
HEADER_FONT = Font(color="FFFFFF", size=12, name="Calibri", bold=True)

# Fills
DARK_BLUE_FILL = PatternFill("solid", fgColor="4472C4")
LIGHT_BLUE_FILL = PatternFill("solid", fgColor="D9E1F2")
INPUT_GREEN_FILL = PatternFill("solid", fgColor="E2EFDA")
LIGHT_GRAY_FILL = PatternFill("solid", fgColor="F2F2F2")

# Borders
THIN_BORDER = Border(bottom=Side(style="thin", color="B2B2B2"))
BOTTOM_DOUBLE = Border(bottom=Side(style="double", color="000000"))

Number Format Codes

FormatCodeExample
Currency'$#,##0'$1,234
Currency with decimals'$#,##0.00'$1,234.56
Percentage'0.0%'12.3%
Percentage (2 decimal)'0.00%'12.34%
Number with commas'#,##0'1,234
Multiplier'0.0x'1.5x

Conditional Formatting (Sensitivity Tables)

Red-to-green gradient for sensitivity analysis:

from openpyxl.formatting.rule import ColorScaleRule

rule = ColorScaleRule(
    start_type="min", start_color="F8696B",   # Red (low)
    mid_type="percentile", mid_value=50, mid_color="FFEB84",  # Yellow (mid)
    end_type="max", end_color="63BE7B"         # Green (high)
)
ws.conditional_formatting.add(f"B2:F6", rule)

Execution

uv run --with openpyxl scripts/create_formatted_excel.py

Full template script: See scripts/create_formatted_excel.py

Parsing Complex Excel (zipfile + xml)

When openpyxl fails on complex xlsm files (corrupted DefinedNames, complex VBA), use stdlib directly.

XLSX Internal ZIP Structure

file.xlsx (ZIP archive)
├── [Content_Types].xml
├── xl/
│   ├── workbook.xml          ← Sheet names + order
│   ├── sharedStrings.xml     ← All text values (lookup table)
│   ├── worksheets/
│   │   ├── sheet1.xml        ← Cell data for sheet 1
│   │   ├── sheet2.xml        ← Cell data for sheet 2
│   │   └── ...
│   └── _rels/
│       └── workbook.xml.rels ← Maps rId → sheetN.xml
└── _rels/.rels

Sheet Name Resolution (Two-Step)

Sheet names in workbook.xml link to physical files via _rels/workbook.xml.rels:

import zipfile
import xml.etree.ElementTree as ET

MAIN_NS = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
REL_NS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
RELS_NS = 'http://schemas.openxmlformats.org/package/2006/relationships'

def get_sheet_path(zf, sheet_name):
    """Resolve sheet name to physical XML file path inside ZIP."""
    # Step 1: workbook.xml → find rId for the sheet name
    wb_xml = ET.fromstring(zf.read('xl/workbook.xml'))
    sheets = wb_xml.findall(f'.//{{{MAIN_NS}}}sheet')
    rid = None
    for s in sheets:
        if s.get('name') == sheet_name:
            rid = s.get(f'{{{REL_NS}}}id')
            break
    if not rid:
        raise ValueError(f"Sheet '{sheet_name}' not found")

    # Step 2: workbook.xml.rels → map rId to file path
    rels_xml = ET.fromstring(zf.read('xl/_rels/workbook.xml.rels'))
    for rel in rels_xml.findall(f'{{{RELS_NS}}}Relationship'):
        if rel.get('Id') == rid:
            return 'xl/' + rel.get('Target')

    raise ValueError(f"No file mapping for {rid}")

Cell Data Extraction

def extract_cells(zf, sheet_path):
    """Extract all cell values from a sheet XML."""
    # Build shared strings lookup
    shared = []
    try:
        ss_xml = ET.fromstring(zf.read('xl/sharedStrings.xml'))
        for si in ss_xml.findall(f'{{{MAIN_NS}}}si'):
            texts = si.itertext()
            shared.append(''.join(texts))
    except KeyError:
        pass  # No shared strings

    # Parse sheet cells
    sheet_xml = ET.fromstring(zf.read(sheet_path))
    rows = sheet_xml.findall(f'.//{{{MAIN_NS}}}row')

    data = {}
    for row in rows:
        for cell in row.findall(f'{{{MAIN_NS}}}c'):
            ref = cell.get('r')         # e.g., "A1"
            cell_type = cell.get('t')   # "s" = shared string, None = number
            val_el = cell.find(f'{{{MAIN_NS}}}v')

            if val_el is not None and val_el.text:
                if cell_type == 's':
                    data[ref] = shared[int(val_el.text)]
                else:
                    try:
                        data[ref] = float(val_el.text)
                    except ValueError:
                        data[ref] = val_el.text
    return data

Fixing Corrupted DefinedNames

Investment bank xlsm files often have corrupted <definedName> entries containing "Formula removed":

def fix_defined_names(zf_in_path, zf_out_path):
    """Remove corrupted DefinedNames and repackage."""
    import shutil, tempfile
    with tempfile.TemporaryDirectory() as tmp:
        tmp = Path(tmp)
        with zipfile.ZipFile(zf_in_path, 'r') as zf:
            zf.extractall(tmp)

        wb_xml_path = tmp / 'xl' / 'workbook.xml'
        tree = ET.parse(wb_xml_path)
        root = tree.getroot()

        ns = {'main': MAIN_NS}
        defined_names = root.find('.//main:definedNames', ns)
        if defined_names is not None:
            for name in list(defined_names):
                if name.text and "Formula removed" in name.text:
                    defined_names.remove(name)

        tree.write(wb_xml_path, encoding='utf-8', xml_declaration=True)

        with zipfile.ZipFile(zf_out_path, 'w', zipfile.ZIP_DEFLATED) as zf:
            for fp in tmp.rglob('*'):
                if fp.is_file():
                    zf.write(fp, fp.relative_to(tmp))

Full template script: See scripts/parse_complex_excel.py

Controlling Excel on macOS (AppleScript)

All commands verified on macOS with Microsoft Excel.

Verified Commands

# Activate Excel (bring to front)
osascript -e 'tell application "Microsoft Excel" to activate'

# Open a file
osascript -e 'tell application "Microsoft Excel" to open POSIX file "/path/to/file.xlsx"'

# Set zoom level (percentage)
osascript -e 'tell application "Microsoft Excel"
    set zoom of active window to 120
end tell'

# Scroll to specific row
osascript -e 'tell application "Microsoft Excel"
    set scroll row of active window to 45
end tell'

# Scroll to specific column
osascript -e 'tell application "Microsoft Excel"
    set scroll column of active window to 3
end tell'

# Select a cell range
osascript -e 'tell application "Microsoft Excel"
    select range "A1" of active sheet
end tell'

# Select a specific sheet by name
osascript -e 'tell application "Microsoft Excel"
    activate object sheet "DCF" of active workbook
end tell'

Timing and Timeout

Always add sleep 1 between AppleScript commands and subsequent operations (e.g., screenshot) to allow UI rendering.

IMPORTANT: osascript will hang indefinitely if Excel is not running or not responding. Always wrap with timeout:

# Safe pattern: 5-second timeout
timeout 5 osascript -e 'tell application "Microsoft Excel" to activate'

# Check exit code: 124 = timed out
if [ $? -eq 124 ]; then
    echo "Excel not responding — is it running?"
fi

Common Mistakes

MistakeCorrection
openpyxl fails on complex xlsm → try monkey-patchingSwitch to zipfile + xml.etree immediately
Count Chinese characters with wc -cUse wc -m (chars, not bytes; Chinese = 3 bytes/char)
Trust file extensionRun file <path> first to confirm actual format
openpyxl load_workbook hangs on large xlsmUse zipfile for targeted extraction instead of loading entire workbook

Important Notes

  • Execute Python scripts with uv run --with openpyxl (never use system Python)
  • LibreOffice (soffice --headless) can convert formats and recalculate formulas
  • Detailed formatting reference: See references/formatting-reference.md

Related skills

More from daymade/claude-code-skills and the wider catalog.

FAfact-checker logo

fact-checker

daymade/claude-code-skills

Verify factual claims in documents using web search and official sources, then propose corrections.

1.4k installs
GIgithub-contributor logo

github-contributor

daymade/claude-code-skills

End-to-end playbook for shipping high-quality pull requests to open-source projects you don't maintain — discovery, CONTRIBUTING compliance, PR-size check, minimal-diff implementation, PR description with AI-assisted disclosure, conflict resolution, and post-submission maintainer interaction. Use whenever creating, editing, or pushing a PR to a third-party GitHub repo — "submit a PR", "open a PR", "fix this upstream", "rebase against main", "respond to the bot review", an `owner/repo` target, or 提 PR / 上游 PR / 贡献代码 / rebase 冲突 / 回应维护者.

616 installs
GIgithub-ops logo

github-ops

daymade/claude-code-skills

Operate GitHub state via gh CLI and REST/GraphQL—PRs, issues, Actions, repos, permissions, 2FA.

1.4k installs
I1i18n-expert logo

i18n-expert

daymade/claude-code-skills

Set up, audit, and enforce internationalization in React/Vue/Next.js codebases with i18next or similar frameworks.

2.5k installs
IMima-copilot logo

ima-copilot

daymade/claude-code-skills

Install, diagnose, repair, and personalize Tencent IMA skill with fan-out search and priority boosting.

1.5k installs
LLllm-icon-finder logo

llm-icon-finder

daymade/claude-code-skills

Finding and accessing AI/LLM model brand icons from lobe-icons library. Use when users need icon URLs, want to download brand logos for AI models/providers/applications (Claude, GPT, Gemini, etc.), or request icons in SVG/PNG/WEBP formats.

612 installs