excel-mcp-server 2.2.4__tar.gz → 2.2.6__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.
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/PKG-INFO +6 -4
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/README.md +5 -3
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/pyproject.toml +1 -1
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/pyproject.toml.orig +1 -1
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/charts.py +15 -2
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/charts_check.py +1 -1
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/charts_data.py +30 -11
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/charts_modern.py +19 -11
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/charts_options.py +1 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/layout.py +12 -1
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/sheet_shapes.py +2 -1
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/slicer_manage.py +18 -10
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/slicer_place.py +5 -3
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/slicer_xml.py +2 -2
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/slicers.py +12 -1
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/vba_write.py +0 -2
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/capture_sheet.py +1 -1
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/extensions.py +25 -1
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/references.py +1 -1
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/restore.py +4 -2
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/restore_sheet.py +1 -1
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/scan.py +1 -10
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/object_tools.py +1 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/registry.py +25 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/values.py +1 -3
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/workspace.py +15 -7
- excel_mcp_server-2.2.6/src/excel_mcp/xml_text.py +18 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/LICENSE +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/__init__.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/__main__.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/cells.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/criteria.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/dependencies.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/dynamic.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/engine.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/__init__.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/arrays.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/conditional.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/datetime.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/depreciation.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/distributions.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/financial.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/helpers.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/indirect.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/information.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/logical.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/lookup.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/math.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/regression.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/securities.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/statistical.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/subtotals.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/functions/text.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/operators.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/parser.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/r1c1.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/registry.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/scheduler.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/solver.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/steps.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/tablerefs.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/textformat.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/values.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/calc/wildcard.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/cell_budget.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/cfb.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/chart_roundtrip.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/cli.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/config.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/errors.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/formula_syntax.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/formulas.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/function_arguments.json +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/inputs.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/lazy_edit.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/lazy_find.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/lazy_lines.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/lazy_rows.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/lazy_shared.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/lazy_source.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/lazy_workbook.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/links.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/macros.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/__init__.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/calculated.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/cells.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/chart_index.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/chart_info.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/chartex.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/chartex_parts/box_whisker_style.xml +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/chartex_parts/colors.xml +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/chartex_parts/funnel_style.xml +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/chartex_parts/histogram_style.xml +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/chartex_parts/sunburst_style.xml +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/chartex_parts/treemap_style.xml +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/chartex_parts/waterfall_style.xml +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/chartex_xml.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/charts_axes.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/charts_look.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/charts_series.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/charts_style.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/columns.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/comparison.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/conditional.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/conditional_kinds.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/conditional_rule.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/conditional_x14.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/csv_export.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/csv_format.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/csv_import.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/csv_values.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/date_width.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/drawings.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/duplicates.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/files.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/fill.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/filter_criteria.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/filter_stored.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/filters.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/formatting.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/goal_seek.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/hyperlinks.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/images.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/inspect.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/line_edit_objects.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/line_edit_rules.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/line_edit_styles.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/line_edits.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/names.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/notes.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/paste.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_axis.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_cache.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_calc.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_definition.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_fields.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_groups.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_index.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_options.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_rebuild.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_records.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_render.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_showas.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_source.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_specs.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/pivot_values.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/print_setup.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/protection.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/replace.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/rule_clear.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/rules.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/sheet_copy.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/sheet_counts.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/sheet_refs.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/sheet_view.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/sheets.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/shifting.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/slicer_index.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/slicer_pivot.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/slicer_table.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/sorting.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/spans.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/sparkline_style.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/sparkline_xml.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/sparklines.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/spill.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/table_options.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/tables.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/text_split.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/trace.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/transform.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/used_range.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/vba.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/workbook_rewrite.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/workbook_settings.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/x14_xml.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/ovba.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/ovba_compress.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/ovba_write.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/__init__.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/anchors.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/arrays.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/capture.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/consistency.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/guards.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/lazy_records.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/lines.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/metadata.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/model.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/opc.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/patch.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/pivot_records.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/pivots.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/properties.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/reader.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/shape_names.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/package/vml.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/paths.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/plain_refs.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/py.typed +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/refs.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/rewrite.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/__init__.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/annotation_tools.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/areas.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/csv_tools.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/data_tools.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/format_tools.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/formula_tools.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/http.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/image_tools.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/params.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/pivot_tools.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/results.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/sheet_tools.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/slicer_tools.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/validation.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/vba_tools.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/vba_write_tools.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/server/workbook_tools.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/spill.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/structured.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/text.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/upload_links.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/upload_scan.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/xlfn.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/xlm.py +0 -0
- {excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/zip_limits.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: excel-mcp-server
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.6
|
|
4
4
|
Summary: MCP server for creating, reading and editing Excel workbooks without Microsoft Excel
|
|
5
5
|
Keywords: mcp,mcp-server,model-context-protocol,excel,xlsx,xlsm,spreadsheet,openpyxl,charts,pivot-table,formulas,vba,llm,ai,claude,cursor
|
|
6
6
|
Author: Haris Musa
|
|
@@ -104,7 +104,8 @@ Excel**, and runs on Windows, macOS and Linux.
|
|
|
104
104
|
|
|
105
105
|
**Macros (opt-in)**
|
|
106
106
|
- Read the VBA code in `.xlsm` files. Writing VBA is off unless you start the server with
|
|
107
|
-
`--allow-vba-write
|
|
107
|
+
`--allow-vba-write`, which also decides whether `create_workbook` and `import_workbook` may
|
|
108
|
+
make `.xlsm`/`.xltm` files; the server never runs macros
|
|
108
109
|
|
|
109
110
|
**Safe by design**
|
|
110
111
|
- Optional folder confinement, a formula safety check, read-only mode, localhost-only HTTP by
|
|
@@ -323,8 +324,9 @@ next call can refer to it. Charts and images are selected by name, as listed by
|
|
|
323
324
|
|
|
324
325
|
With `--allow-vba-write` (or `EXCEL_MCP_ALLOW_VBA_WRITE=1`) the server can set the code of
|
|
325
326
|
standard, class, workbook and sheet modules in `.xlsm` and `.xltm` files, delete standard and
|
|
326
|
-
class modules, and create
|
|
327
|
-
|
|
327
|
+
class modules, and create or import `.xlsm`/`.xltm` workbooks (without the option both are
|
|
328
|
+
refused, and `import_workbook` removes macros from uploads to `.xlsx`/`.xltx`). The server only
|
|
329
|
+
stores the code; it never runs it, and Excel asks before enabling macros. Digitally signed VBA projects are
|
|
328
330
|
refused, since any change would invalidate the signature.
|
|
329
331
|
|
|
330
332
|
> **Warning:** macro code runs with your user's rights once you enable macros in Excel. A
|
|
@@ -68,7 +68,8 @@ Excel**, and runs on Windows, macOS and Linux.
|
|
|
68
68
|
|
|
69
69
|
**Macros (opt-in)**
|
|
70
70
|
- Read the VBA code in `.xlsm` files. Writing VBA is off unless you start the server with
|
|
71
|
-
`--allow-vba-write
|
|
71
|
+
`--allow-vba-write`, which also decides whether `create_workbook` and `import_workbook` may
|
|
72
|
+
make `.xlsm`/`.xltm` files; the server never runs macros
|
|
72
73
|
|
|
73
74
|
**Safe by design**
|
|
74
75
|
- Optional folder confinement, a formula safety check, read-only mode, localhost-only HTTP by
|
|
@@ -287,8 +288,9 @@ next call can refer to it. Charts and images are selected by name, as listed by
|
|
|
287
288
|
|
|
288
289
|
With `--allow-vba-write` (or `EXCEL_MCP_ALLOW_VBA_WRITE=1`) the server can set the code of
|
|
289
290
|
standard, class, workbook and sheet modules in `.xlsm` and `.xltm` files, delete standard and
|
|
290
|
-
class modules, and create
|
|
291
|
-
|
|
291
|
+
class modules, and create or import `.xlsm`/`.xltm` workbooks (without the option both are
|
|
292
|
+
refused, and `import_workbook` removes macros from uploads to `.xlsx`/`.xltx`). The server only
|
|
293
|
+
stores the code; it never runs it, and Excel asks before enabling macros. Digitally signed VBA projects are
|
|
292
294
|
refused, since any change would invalidate the signature.
|
|
293
295
|
|
|
294
296
|
> **Warning:** macro code runs with your user's rights once you enable macros in Excel. A
|
|
@@ -78,6 +78,7 @@ def create_chart(
|
|
|
78
78
|
options: ChartOptions,
|
|
79
79
|
replace: str | None,
|
|
80
80
|
name: str | None,
|
|
81
|
+
max_cells: int,
|
|
81
82
|
) -> tuple[str | None, str | None]:
|
|
82
83
|
"""Add a chart (or replace the chart called `replace`); return its name and cells."""
|
|
83
84
|
host = None if at is None else get_sheet(workbook, sheet)
|
|
@@ -96,7 +97,17 @@ def create_chart(
|
|
|
96
97
|
f"{chart_type} charts read their data in columns; list rows with `series`."
|
|
97
98
|
)
|
|
98
99
|
return _create_modern(
|
|
99
|
-
workbook,
|
|
100
|
+
workbook,
|
|
101
|
+
host,
|
|
102
|
+
anchor,
|
|
103
|
+
chart_type,
|
|
104
|
+
source,
|
|
105
|
+
series,
|
|
106
|
+
categories,
|
|
107
|
+
options,
|
|
108
|
+
replace,
|
|
109
|
+
name,
|
|
110
|
+
max_cells,
|
|
100
111
|
)
|
|
101
112
|
plots = resolve_series(
|
|
102
113
|
workbook,
|
|
@@ -106,6 +117,7 @@ def create_chart(
|
|
|
106
117
|
series_in,
|
|
107
118
|
series,
|
|
108
119
|
categories,
|
|
120
|
+
max_cells,
|
|
109
121
|
)
|
|
110
122
|
check_chart(plots, chart_type, options)
|
|
111
123
|
chart = build_chart(plots, chart_type, options)
|
|
@@ -146,10 +158,11 @@ def _create_modern(
|
|
|
146
158
|
options: ChartOptions,
|
|
147
159
|
replace: str | None,
|
|
148
160
|
name: str | None,
|
|
161
|
+
max_cells: int,
|
|
149
162
|
) -> tuple[str, str]:
|
|
150
163
|
if host is None or anchor is None:
|
|
151
164
|
raise InvalidArgumentError(f"A {chart_type} chart sits on the sheet: give at, e.g. 'E2'.")
|
|
152
|
-
plots = resolve_modern(workbook, host.title, chart_type, source, series, categories)
|
|
165
|
+
plots = resolve_modern(workbook, host.title, chart_type, source, series, categories, max_cells)
|
|
153
166
|
check_modern(plots, chart_type, options)
|
|
154
167
|
index, name = _place(host, replace, name)
|
|
155
168
|
if index is None:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from excel_mcp.errors import InvalidArgumentError
|
|
4
4
|
from excel_mcp.operations.charts_data import Plot
|
|
5
5
|
from excel_mcp.operations.charts_options import (
|
|
6
|
+
MAX_SERIES,
|
|
6
7
|
ROUND_TYPES,
|
|
7
8
|
Axis,
|
|
8
9
|
ChartOptions,
|
|
@@ -12,7 +13,6 @@ from excel_mcp.operations.charts_options import (
|
|
|
12
13
|
)
|
|
13
14
|
from excel_mcp.operations.formatting import parse_color
|
|
14
15
|
|
|
15
|
-
MAX_SERIES = 255
|
|
16
16
|
_COMBINABLE = ("column", "line", "area")
|
|
17
17
|
_SECONDARY = ("column", "line", "area", "scatter")
|
|
18
18
|
_GROUPED = ("column", "bar", "line", "area")
|
|
@@ -9,7 +9,7 @@ from openpyxl.chart.series import SeriesLabel
|
|
|
9
9
|
from openpyxl.utils import absolute_coordinate, quote_sheetname
|
|
10
10
|
|
|
11
11
|
from excel_mcp.errors import InvalidArgumentError
|
|
12
|
-
from excel_mcp.operations.charts_options import ChartType, SeriesSpec
|
|
12
|
+
from excel_mcp.operations.charts_options import MAX_SERIES, ChartType, SeriesSpec
|
|
13
13
|
from excel_mcp.refs import CellRange, parse_cell, parse_range
|
|
14
14
|
from excel_mcp.workspace import get_sheet
|
|
15
15
|
|
|
@@ -36,6 +36,7 @@ def resolve_series(
|
|
|
36
36
|
series_in: SeriesIn,
|
|
37
37
|
series: list[SeriesSpec],
|
|
38
38
|
categories: str | None,
|
|
39
|
+
max_cells: int,
|
|
39
40
|
) -> list[Plot]:
|
|
40
41
|
if (source is None) == (not series):
|
|
41
42
|
raise InvalidArgumentError(
|
|
@@ -43,24 +44,34 @@ def resolve_series(
|
|
|
43
44
|
"and not neither."
|
|
44
45
|
)
|
|
45
46
|
if source is not None:
|
|
46
|
-
series = _block_series(workbook, sheet, source, chart_type, series_in)
|
|
47
|
+
series = _block_series(workbook, sheet, source, chart_type, series_in, max_cells)
|
|
47
48
|
categories = None
|
|
48
|
-
|
|
49
|
+
check_series_count(len(series))
|
|
50
|
+
return [_resolve(workbook, sheet, item, categories, max_cells) for item in series]
|
|
49
51
|
|
|
50
52
|
|
|
51
|
-
def _resolve(
|
|
52
|
-
|
|
53
|
+
def _resolve(
|
|
54
|
+
workbook: Workbook, sheet: str, spec: SeriesSpec, categories: str | None, max_cells: int
|
|
55
|
+
) -> Plot:
|
|
56
|
+
values, points = one_line(workbook, sheet, spec.values, "values", max_cells)
|
|
53
57
|
labels = spec.categories or categories
|
|
54
58
|
return Plot(
|
|
55
59
|
spec=spec,
|
|
56
60
|
values=values,
|
|
57
|
-
categories=one_line(workbook, sheet, labels, "categories")[0]
|
|
58
|
-
|
|
61
|
+
categories=one_line(workbook, sheet, labels, "categories", max_cells)[0]
|
|
62
|
+
if labels
|
|
63
|
+
else None,
|
|
64
|
+
sizes=one_line(workbook, sheet, spec.sizes, "sizes", max_cells)[0] if spec.sizes else None,
|
|
59
65
|
name=series_name(workbook, spec.name),
|
|
60
66
|
points=points,
|
|
61
67
|
)
|
|
62
68
|
|
|
63
69
|
|
|
70
|
+
def check_series_count(count: int) -> None:
|
|
71
|
+
if count > MAX_SERIES:
|
|
72
|
+
raise InvalidArgumentError(f"A chart has at most {MAX_SERIES} series, got {count}.")
|
|
73
|
+
|
|
74
|
+
|
|
64
75
|
def split_sheet(workbook: Workbook, default: str, text: str) -> tuple[str, str]:
|
|
65
76
|
"""Split 'Data!B2:B9' (or 'My Sheet'!B2:B9) into the worksheet's title and the cells."""
|
|
66
77
|
qualifier, mark, cells = text.rpartition("!")
|
|
@@ -74,10 +85,12 @@ def qualified(sheet: str, area: CellRange) -> str:
|
|
|
74
85
|
return f"{quote_sheetname(sheet)}!{absolute_coordinate(str(area))}"
|
|
75
86
|
|
|
76
87
|
|
|
77
|
-
def one_line(
|
|
88
|
+
def one_line(
|
|
89
|
+
workbook: Workbook, default: str, text: str, what: str, max_cells: int
|
|
90
|
+
) -> tuple[str, int]:
|
|
78
91
|
"""A one-row or one-column range as an absolute formula, and its number of cells."""
|
|
79
92
|
sheet, cells = split_sheet(workbook, default, text)
|
|
80
|
-
area = parse_range(cells)
|
|
93
|
+
area = parse_range(cells).within(max_cells)
|
|
81
94
|
if area.rows > 1 and area.cols > 1:
|
|
82
95
|
raise InvalidArgumentError(
|
|
83
96
|
f"Series {what} must be one row or one column, got {text!r}. List one series per "
|
|
@@ -99,11 +112,16 @@ def series_name(workbook: Workbook, text: str | None) -> SeriesLabel | None:
|
|
|
99
112
|
|
|
100
113
|
|
|
101
114
|
def _block_series(
|
|
102
|
-
workbook: Workbook,
|
|
115
|
+
workbook: Workbook,
|
|
116
|
+
default: str,
|
|
117
|
+
source: str,
|
|
118
|
+
chart_type: ChartType,
|
|
119
|
+
series_in: SeriesIn,
|
|
120
|
+
max_cells: int,
|
|
103
121
|
) -> list[SeriesSpec]:
|
|
104
122
|
"""Series for a block with a header line and a label line, by columns or by rows."""
|
|
105
123
|
sheet, cells = split_sheet(workbook, default, source)
|
|
106
|
-
area = parse_range(cells)
|
|
124
|
+
area = parse_range(cells).within(max_cells)
|
|
107
125
|
if area.rows < 2 or area.cols < 2:
|
|
108
126
|
raise InvalidArgumentError(
|
|
109
127
|
"source needs a header row and a label column plus at least one series, "
|
|
@@ -112,6 +130,7 @@ def _block_series(
|
|
|
112
130
|
by_columns = series_in == "columns"
|
|
113
131
|
lines = area.cols if by_columns else area.rows
|
|
114
132
|
points = (area.rows if by_columns else area.cols) - 1
|
|
133
|
+
check_series_count(lines - 1)
|
|
115
134
|
|
|
116
135
|
def segment(line: int, start: int, stop: int) -> str:
|
|
117
136
|
"""Cells `start` to `stop` along line number `line`, the label line being 0."""
|
|
@@ -5,9 +5,10 @@ from openpyxl.worksheet.worksheet import Worksheet
|
|
|
5
5
|
from pydantic import BaseModel
|
|
6
6
|
|
|
7
7
|
from excel_mcp.errors import InvalidArgumentError
|
|
8
|
-
from excel_mcp.operations.charts_check import
|
|
8
|
+
from excel_mcp.operations.charts_check import check_labels, require
|
|
9
9
|
from excel_mcp.operations.charts_data import (
|
|
10
10
|
Plot,
|
|
11
|
+
check_series_count,
|
|
11
12
|
one_line,
|
|
12
13
|
qualified,
|
|
13
14
|
series_name,
|
|
@@ -51,6 +52,7 @@ def resolve_modern(
|
|
|
51
52
|
source: str | None,
|
|
52
53
|
series: list[SeriesSpec],
|
|
53
54
|
categories: str | None,
|
|
55
|
+
max_cells: int,
|
|
54
56
|
) -> list[Plot]:
|
|
55
57
|
if (source is None) == (not series):
|
|
56
58
|
raise InvalidArgumentError(
|
|
@@ -58,33 +60,41 @@ def resolve_modern(
|
|
|
58
60
|
"and not neither."
|
|
59
61
|
)
|
|
60
62
|
if source is not None:
|
|
61
|
-
series, categories = _block(workbook, sheet, chart_type, source), None
|
|
62
|
-
|
|
63
|
+
series, categories = _block(workbook, sheet, chart_type, source, max_cells), None
|
|
64
|
+
check_series_count(len(series))
|
|
65
|
+
plots = [_plot(workbook, sheet, chart_type, spec, categories, max_cells) for spec in series]
|
|
63
66
|
if chart_type not in _MANY and len(plots) != 1:
|
|
64
67
|
raise InvalidArgumentError(f"A {chart_type} chart plots one series, got {len(plots)}.")
|
|
65
68
|
return plots
|
|
66
69
|
|
|
67
70
|
|
|
68
71
|
def _plot(
|
|
69
|
-
workbook: Workbook,
|
|
72
|
+
workbook: Workbook,
|
|
73
|
+
sheet: str,
|
|
74
|
+
chart_type: str,
|
|
75
|
+
spec: SeriesSpec,
|
|
76
|
+
categories: str | None,
|
|
77
|
+
max_cells: int,
|
|
70
78
|
) -> Plot:
|
|
71
|
-
values, points = one_line(workbook, sheet, spec.values, "values")
|
|
79
|
+
values, points = one_line(workbook, sheet, spec.values, "values", max_cells)
|
|
72
80
|
labels = spec.categories or categories
|
|
73
81
|
if labels is None:
|
|
74
82
|
found = None
|
|
75
83
|
elif chart_type in _HIERARCHIES:
|
|
76
84
|
title, cells = split_sheet(workbook, sheet, labels)
|
|
77
|
-
found = qualified(title, parse_range(cells))
|
|
85
|
+
found = qualified(title, parse_range(cells).within(max_cells))
|
|
78
86
|
else:
|
|
79
|
-
found = one_line(workbook, sheet, labels, "categories")[0]
|
|
87
|
+
found = one_line(workbook, sheet, labels, "categories", max_cells)[0]
|
|
80
88
|
return Plot(spec, values, found, None, series_name(workbook, spec.name), points)
|
|
81
89
|
|
|
82
90
|
|
|
83
|
-
def _block(
|
|
91
|
+
def _block(
|
|
92
|
+
workbook: Workbook, default: str, chart_type: str, source: str, max_cells: int
|
|
93
|
+
) -> list[SeriesSpec]:
|
|
84
94
|
"""Series for a block with a header row, read as Excel reads it: leading text columns are
|
|
85
95
|
labels (one, or the levels of a treemap or sunburst), the number columns are the series."""
|
|
86
96
|
sheet, cells = split_sheet(workbook, default, source)
|
|
87
|
-
area = parse_range(cells)
|
|
97
|
+
area = parse_range(cells).within(max_cells)
|
|
88
98
|
if area.rows < 2:
|
|
89
99
|
raise InvalidArgumentError("source needs a header row and at least one row of data.")
|
|
90
100
|
labels = _text_columns(workbook[sheet], area)
|
|
@@ -120,8 +130,6 @@ def _text_columns(sheet: Worksheet, area: CellRange) -> int:
|
|
|
120
130
|
|
|
121
131
|
|
|
122
132
|
def check_modern(plots: list[Plot], chart_type: ChartType, options: ChartOptions) -> None:
|
|
123
|
-
if len(plots) > MAX_SERIES:
|
|
124
|
-
raise InvalidArgumentError(f"A chart has at most {MAX_SERIES} series, got {len(plots)}.")
|
|
125
133
|
_check_options(options, chart_type)
|
|
126
134
|
for number, plot in enumerate(plots, start=1):
|
|
127
135
|
try:
|
{excel_mcp_server-2.2.4 → excel_mcp_server-2.2.6}/src/excel_mcp/operations/charts_options.py
RENAMED
|
@@ -10,6 +10,7 @@ ChartType = Literal[
|
|
|
10
10
|
"column", "bar", "line", "area", "pie", "doughnut", "radar", "scatter", "bubble",
|
|
11
11
|
"waterfall", "histogram", "pareto", "box_whisker", "treemap", "sunburst", "funnel",
|
|
12
12
|
] # fmt: skip
|
|
13
|
+
MAX_SERIES = 255
|
|
13
14
|
SeriesType = Literal["column", "line", "area"]
|
|
14
15
|
Grouping = Literal["standard", "stacked", "percent_stacked"]
|
|
15
16
|
LegendPosition = Literal["right", "left", "top", "bottom", "none"]
|
|
@@ -16,7 +16,7 @@ from excel_mcp.operations.formatting import parse_color
|
|
|
16
16
|
from excel_mcp.operations.print_setup import PrintSetup, apply_print_setup
|
|
17
17
|
from excel_mcp.operations.protection import Protection, apply_protection
|
|
18
18
|
from excel_mcp.operations.sheet_view import ViewOptions, apply_view, move_sheet
|
|
19
|
-
from excel_mcp.operations.spans import parse_span, to_spans
|
|
19
|
+
from excel_mcp.operations.spans import MAX_ROW_SPAN, parse_span, to_spans
|
|
20
20
|
from excel_mcp.package.lines import Axis
|
|
21
21
|
from excel_mcp.refs import MAX_ROW, parse_cell
|
|
22
22
|
|
|
@@ -88,6 +88,7 @@ def apply_layout(
|
|
|
88
88
|
apply_auto_filter(sheet, layout.auto_filter, formula_values, max_cells)
|
|
89
89
|
if layout.tab_color is not None:
|
|
90
90
|
sheet.sheet_properties.tabColor = parse_color(layout.tab_color)
|
|
91
|
+
check_row_spans(layout.rows or [])
|
|
91
92
|
for action in layout.rows or []:
|
|
92
93
|
change_lines(sheet, "rows", action)
|
|
93
94
|
for action in layout.columns or []:
|
|
@@ -102,6 +103,16 @@ def apply_layout(
|
|
|
102
103
|
apply_view(sheet, layout.view)
|
|
103
104
|
|
|
104
105
|
|
|
106
|
+
def check_row_spans(actions: list[LineAction]) -> None:
|
|
107
|
+
"""Every row of a span gets an object, so the spans of one call share the span limit."""
|
|
108
|
+
total = 0
|
|
109
|
+
for action in actions:
|
|
110
|
+
first, last = parse_span(action.span, "rows")
|
|
111
|
+
total += last - first + 1
|
|
112
|
+
if total > MAX_ROW_SPAN:
|
|
113
|
+
raise InvalidArgumentError(f"One call can change at most {MAX_ROW_SPAN:,} rows.")
|
|
114
|
+
|
|
115
|
+
|
|
105
116
|
def split_column_dimensions(sheet: Worksheet) -> None:
|
|
106
117
|
"""Give every column of a multi-column definition its own, as openpyxl expects.
|
|
107
118
|
|
|
@@ -4,6 +4,7 @@ connectors, groups and form controls."""
|
|
|
4
4
|
import re
|
|
5
5
|
import uuid
|
|
6
6
|
from collections.abc import Callable
|
|
7
|
+
from html import unescape
|
|
7
8
|
from xml.sax.saxutils import escape, quoteattr
|
|
8
9
|
|
|
9
10
|
from openpyxl.workbook import Workbook
|
|
@@ -15,7 +16,7 @@ from excel_mcp.package import PackageState, Part, SheetPackage, state_of
|
|
|
15
16
|
from excel_mcp.package.consistency import THREADED_COMMENTS
|
|
16
17
|
from excel_mcp.package.extensions import CONDITIONAL_FORMATS, SPARKLINES
|
|
17
18
|
from excel_mcp.package.model import cloned
|
|
18
|
-
from excel_mcp.package.scan import relationship_ids
|
|
19
|
+
from excel_mcp.package.scan import relationship_ids
|
|
19
20
|
|
|
20
21
|
# Charts of Excel 2016 and slicers are copied with the parts they need, by their own code.
|
|
21
22
|
_COPIED_ELSEWHERE = (
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
import uuid
|
|
5
|
-
from xml.sax.saxutils import quoteattr
|
|
6
5
|
|
|
7
6
|
from openpyxl.pivot.table import TableDefinition
|
|
8
7
|
from openpyxl.workbook import Workbook
|
|
@@ -29,7 +28,9 @@ from excel_mcp.operations.slicer_index import (
|
|
|
29
28
|
from excel_mcp.operations.slicer_place import add_cache, add_entry, next_shape_id
|
|
30
29
|
from excel_mcp.package import state_of
|
|
31
30
|
from excel_mcp.package.anchors import Corner
|
|
31
|
+
from excel_mcp.package.scan import attributes
|
|
32
32
|
from excel_mcp.text import quoted
|
|
33
|
+
from excel_mcp.xml_text import attribute
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
class SlicerInfo(BaseModel):
|
|
@@ -112,10 +113,12 @@ def placed_range(workbook: Workbook, sheet: Worksheet, name: str) -> str | None:
|
|
|
112
113
|
|
|
113
114
|
|
|
114
115
|
def _anchor_of(anchors: list[str], name: str) -> str | None:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
return next((a for a in anchors if _shape_name(a) == name), None)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _shape_name(anchor: str) -> str | None:
|
|
120
|
+
tag = re.search(r"<\w+:(?:slicer|timeslicer)\b[^>]*>", anchor)
|
|
121
|
+
return attributes(tag[0]).get("name") if tag else None
|
|
119
122
|
|
|
120
123
|
|
|
121
124
|
def delete_slicer(
|
|
@@ -141,7 +144,11 @@ def remove_entry(workbook: Workbook, entry: Entry) -> None:
|
|
|
141
144
|
"""Take a slicer or timeline out of its sheet; its cache goes when nothing uses it."""
|
|
142
145
|
package = state_of(workbook).sheet(entry.sheet)
|
|
143
146
|
text = entry.part.data.decode("utf-8")
|
|
144
|
-
text = re.sub(
|
|
147
|
+
text = re.sub(
|
|
148
|
+
r"<(?:slicer|timeline)\b[^>]*/>",
|
|
149
|
+
lambda tag: "" if attributes(tag[0]).get("name") == entry.name else tag[0],
|
|
150
|
+
text,
|
|
151
|
+
)
|
|
145
152
|
entry.part.data = text.encode("utf-8")
|
|
146
153
|
if not xml.read_entries(text):
|
|
147
154
|
package.links.remove(entry.link)
|
|
@@ -200,6 +207,7 @@ __all__ = ["Corner", "SlicerInfo", "delete_slicer", "list_slicers", "sheet_of"]
|
|
|
200
207
|
# -- copying a sheet ---------------------------------------------------------------------------
|
|
201
208
|
|
|
202
209
|
_SHAPE_ID = re.compile(r'(<xdr:cNvPr\b[^>]*?\bid=")\d+(")')
|
|
210
|
+
_SHAPE_NAME = re.compile(r'(<xdr:cNvPr\b[^>]*?\bname=)"[^"]*"')
|
|
203
211
|
_GUID = re.compile(r'(<a16:creationId\b[^>]*?\bid=")[^"]*(")')
|
|
204
212
|
_UID = re.compile(r'(\b\w+:uid=")[^"]*(")')
|
|
205
213
|
|
|
@@ -236,7 +244,7 @@ def copy_slicers(
|
|
|
236
244
|
def _join_cache(workbook: Workbook, name: str, pivots: list[tuple[int, str]], tab: int) -> None:
|
|
237
245
|
link = caches(workbook)[name][0]
|
|
238
246
|
text = link.target.data.decode("utf-8") # pyright: ignore[reportAttributeAccessIssue]
|
|
239
|
-
added = "".join(f'<pivotTable tabId="{tab}" name=
|
|
247
|
+
added = "".join(f'<pivotTable tabId="{tab}" name={attribute(p)}/>' for _, p in pivots)
|
|
240
248
|
link.target.data = text.replace("</pivotTables>", added + "</pivotTables>").encode("utf-8") # pyright: ignore[reportAttributeAccessIssue]
|
|
241
249
|
|
|
242
250
|
|
|
@@ -259,7 +267,7 @@ def _copy_cache(
|
|
|
259
267
|
number += 1
|
|
260
268
|
new = f"{stem}{number}"
|
|
261
269
|
text = link.target.data.decode("utf-8") # pyright: ignore[reportAttributeAccessIssue]
|
|
262
|
-
text =
|
|
270
|
+
text = re.sub(r'\bname="[^"]*"', lambda _: f"name={attribute(new)}", text, count=1)
|
|
263
271
|
text = _UID.sub(lambda m: m[1] + _new_guid() + m[2], text, count=1)
|
|
264
272
|
if tab_id is not None:
|
|
265
273
|
text = re.sub(r'(<pivotTable\b[^>]*?\btabId=")\d+', rf"\g<1>{tab_id}", text)
|
|
@@ -281,11 +289,11 @@ def _copy_entry(workbook: Workbook, entry: Entry, target: Worksheet, cache: str,
|
|
|
281
289
|
"name": name,
|
|
282
290
|
"cache": cache,
|
|
283
291
|
}
|
|
284
|
-
tag = entry.kind + "".join(f" {k}={
|
|
292
|
+
tag = entry.kind + "".join(f" {k}={attribute(v)}" for k, v in values.items()) + "/>"
|
|
285
293
|
package = state_of(workbook).sheet(entry.sheet)
|
|
286
294
|
shape = _anchor_of(package.anchors, entry.name)
|
|
287
295
|
assert shape is not None
|
|
288
|
-
shape =
|
|
296
|
+
shape = _SHAPE_NAME.sub(lambda m: f"{m[1]}{attribute(name)}", shape, count=1)
|
|
289
297
|
shape = _SHAPE_ID.sub(
|
|
290
298
|
rf"\g<1>{next_shape_id(state_of(workbook).sheet(target))}\g<2>", shape, count=1
|
|
291
299
|
)
|
|
@@ -18,6 +18,7 @@ from excel_mcp.package.scan import (
|
|
|
18
18
|
root_scope,
|
|
19
19
|
used_prefixes,
|
|
20
20
|
)
|
|
21
|
+
from excel_mcp.xml_text import attribute
|
|
21
22
|
|
|
22
23
|
_XDR = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
|
|
23
24
|
_A = "http://schemas.openxmlformats.org/drawingml/2006/main"
|
|
@@ -154,12 +155,13 @@ def anchor(
|
|
|
154
155
|
end_col, col_off = _end(geometry, False, column, left + size[0], left)
|
|
155
156
|
end_row, row_off = _end(geometry, True, row, top + size[1], top)
|
|
156
157
|
timeline = kind == "timeline"
|
|
158
|
+
shape_name = attribute(name)
|
|
157
159
|
requires = "tsle" if timeline else "sle15" if kind == "table" else "a14"
|
|
158
160
|
declared = {"tsle": _TSLE, "sle15": _SLE15, "a14": _A14}[requires]
|
|
159
161
|
graphic = (
|
|
160
|
-
f'<tsle:timeslicer xmlns:tsle="{_TSLE}" name=
|
|
162
|
+
f'<tsle:timeslicer xmlns:tsle="{_TSLE}" name={shape_name}/>'
|
|
161
163
|
if timeline
|
|
162
|
-
else f'<sle:slicer xmlns:sle="{_SLE}" name=
|
|
164
|
+
else f'<sle:slicer xmlns:sle="{_SLE}" name={shape_name}/>'
|
|
163
165
|
)
|
|
164
166
|
uri = _TSLE if timeline else _SLE
|
|
165
167
|
guid = "{" + str(uuid.uuid4()).upper() + "}"
|
|
@@ -173,7 +175,7 @@ def anchor(
|
|
|
173
175
|
f'<mc:AlternateContent xmlns:mc="{xml.MC}">'
|
|
174
176
|
f'<mc:Choice xmlns:{requires}="{declared}" Requires="{requires}">'
|
|
175
177
|
f'<xdr:graphicFrame macro=""><xdr:nvGraphicFramePr>'
|
|
176
|
-
f'<xdr:cNvPr id="{shape_id}" name=
|
|
178
|
+
f'<xdr:cNvPr id="{shape_id}" name={shape_name}><a:extLst>'
|
|
177
179
|
f'<a:ext uri="{{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}}">'
|
|
178
180
|
f'<a16:creationId xmlns:a16="{_A16}" id="{guid}"/></a:ext></a:extLst></xdr:cNvPr>'
|
|
179
181
|
f"<xdr:cNvGraphicFramePr/></xdr:nvGraphicFramePr>"
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import datetime as dt
|
|
4
4
|
import re
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from xml.sax.saxutils import quoteattr
|
|
7
6
|
|
|
8
7
|
from excel_mcp.package.opc import XML_DECLARATION
|
|
9
8
|
from excel_mcp.package.scan import attributes
|
|
9
|
+
from excel_mcp.xml_text import attribute
|
|
10
10
|
|
|
11
11
|
X14 = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
|
|
12
12
|
X15 = "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"
|
|
@@ -47,7 +47,7 @@ class CacheOptions:
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
def _attrs(**values: object) -> str:
|
|
50
|
-
return "".join(f" {k}={
|
|
50
|
+
return "".join(f" {k}={attribute(str(v))}" for k, v in values.items() if v is not None)
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
def _date(moment: dt.date | dt.datetime) -> str:
|
|
@@ -31,6 +31,7 @@ from excel_mcp.package import state_of
|
|
|
31
31
|
from excel_mcp.refs import parse_cell
|
|
32
32
|
|
|
33
33
|
EMU_PER_CM = 360_000
|
|
34
|
+
MAX_TEXT_LENGTH = 255
|
|
34
35
|
_SLICER_SIZE = (1_828_800, 2_667_000)
|
|
35
36
|
_TIMELINE_SIZE = (3_333_750, 1_371_600)
|
|
36
37
|
|
|
@@ -71,6 +72,8 @@ def add_slicer(
|
|
|
71
72
|
) -> tuple[str, bool]:
|
|
72
73
|
"""Add the slicer or timeline to ``sheet``; returns its name, and whether the figures of
|
|
73
74
|
PivotTables it limits were left for Excel to recalculate when it opens the file."""
|
|
75
|
+
if request.caption:
|
|
76
|
+
_check_text(request.caption, "caption")
|
|
74
77
|
owner, target = find_source(workbook, request.source.sheet, request.source.name)
|
|
75
78
|
if isinstance(target, Table):
|
|
76
79
|
if request.timeline or request.connect:
|
|
@@ -235,6 +238,7 @@ def _names(workbook: Workbook, request: SlicerRequest, stem: str) -> tuple[str,
|
|
|
235
238
|
"""The slicer's name and its cache's, unique in the workbook as Excel makes them."""
|
|
236
239
|
taken = {entry.name.casefold() for entry in entries(workbook)}
|
|
237
240
|
if request.name is not None:
|
|
241
|
+
_check_text(request.name, "name")
|
|
238
242
|
if request.name.casefold() in taken:
|
|
239
243
|
raise InvalidArgumentError(f"A slicer or timeline named {request.name!r} exists.")
|
|
240
244
|
name = request.name
|
|
@@ -243,7 +247,8 @@ def _names(workbook: Workbook, request: SlicerRequest, stem: str) -> tuple[str,
|
|
|
243
247
|
while name.casefold() in taken:
|
|
244
248
|
number += 1
|
|
245
249
|
name = f"{request.field} {number}"
|
|
246
|
-
|
|
250
|
+
# Excel keeps letters, digits and . \ ? in a cache name, and a defined name is at most 255.
|
|
251
|
+
stem += re.sub(r"[^\w.\\?]", "_", request.field).rstrip("_")[: MAX_TEXT_LENGTH - 20]
|
|
247
252
|
used = {n.casefold() for n in workbook.defined_names} | {c.casefold() for c in caches(workbook)}
|
|
248
253
|
cache, number = stem, 0
|
|
249
254
|
while cache.casefold() in used:
|
|
@@ -252,6 +257,12 @@ def _names(workbook: Workbook, request: SlicerRequest, stem: str) -> tuple[str,
|
|
|
252
257
|
return name, cache
|
|
253
258
|
|
|
254
259
|
|
|
260
|
+
def _check_text(text: str, what: str) -> None:
|
|
261
|
+
"""Excel takes any slicer name, caption and cache name of 1 to 255 characters."""
|
|
262
|
+
if not text.strip() or len(text) > MAX_TEXT_LENGTH:
|
|
263
|
+
raise InvalidArgumentError(f"A slicer's {what} must be 1 to {MAX_TEXT_LENGTH} characters.")
|
|
264
|
+
|
|
265
|
+
|
|
255
266
|
def _check_style(style: str | None, kind: str) -> None:
|
|
256
267
|
wanted = "TimeSlicerStyle" if kind == "timeline" else "SlicerStyle"
|
|
257
268
|
if style is not None and not re.fullmatch(wanted + r"(Light|Dark|Other)\d", style):
|
|
@@ -30,8 +30,6 @@ def write_module(
|
|
|
30
30
|
raise InvalidArgumentError(
|
|
31
31
|
"Code lines cannot start with 'Attribute': the server writes module attributes itself."
|
|
32
32
|
)
|
|
33
|
-
if "\0" in code:
|
|
34
|
-
raise InvalidArgumentError("The code contains a NUL character.")
|
|
35
33
|
project = _project(workbook)
|
|
36
34
|
existing = project.find(name)
|
|
37
35
|
if existing is None and not _MODULE_NAME.fullmatch(name):
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Reading from the original file what openpyxl is going to drop from a worksheet."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
+
from html import unescape
|
|
4
5
|
from typing import Literal
|
|
5
6
|
from xml.etree import ElementTree
|
|
6
7
|
|
|
@@ -21,7 +22,6 @@ from excel_mcp.package.scan import (
|
|
|
21
22
|
relationship_ids,
|
|
22
23
|
scan,
|
|
23
24
|
scan_fragment,
|
|
24
|
-
unescape,
|
|
25
25
|
with_namespaces,
|
|
26
26
|
)
|
|
27
27
|
from excel_mcp.package.shape_names import anchored_name, restore_names
|
|
@@ -7,6 +7,7 @@ in the sheet proper. They hold ranges and formulas that must follow the cells th
|
|
|
7
7
|
import re
|
|
8
8
|
import uuid
|
|
9
9
|
from collections.abc import Callable
|
|
10
|
+
from html import unescape
|
|
10
11
|
from typing import Protocol
|
|
11
12
|
from xml.sax.saxutils import escape
|
|
12
13
|
|
|
@@ -14,7 +15,6 @@ from openpyxl.utils.cell import column_index_from_string, get_column_letter
|
|
|
14
15
|
|
|
15
16
|
from excel_mcp.errors import WorkbookError
|
|
16
17
|
from excel_mcp.package.lines import LineEdit
|
|
17
|
-
from excel_mcp.package.scan import unescape
|
|
18
18
|
from excel_mcp.refs import CellRange, clip_areas, parse_range
|
|
19
19
|
|
|
20
20
|
SPARKLINES = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"
|
|
@@ -24,6 +24,30 @@ DATA_VALIDATIONS = "{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}"
|
|
|
24
24
|
RULE_ID = "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}"
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
# The order Excel writes the entries it knows of in an <extLst>: in the workbook, the slicer
|
|
28
|
+
# and timeline caches and the workbook properties; in a sheet, the lists of slicers, table
|
|
29
|
+
# slicers and timelines. Excel does not open a file with them in another order.
|
|
30
|
+
_EXCEL_ORDER = [
|
|
31
|
+
"{BBE1A952-AA13-448e-AADC-164F8A28A991}",
|
|
32
|
+
"{79F54976-1DA5-4618-B147-4CDE4B953A38}",
|
|
33
|
+
"{D0CA8CA8-9F24-4464-BF8E-62219DCF47F9}",
|
|
34
|
+
"{46BE6895-7355-4a93-B00E-2C351335B9C9}",
|
|
35
|
+
"{140A7094-0E35-4892-8432-C4D2E57EDEB5}",
|
|
36
|
+
"{B58B0392-4F1F-4190-BB64-5DF3571DCE5F}",
|
|
37
|
+
"{A8765BA9-456A-4dab-B4F3-ACF838C121DE}",
|
|
38
|
+
"{3A4CF648-6AED-40f4-86FF-DC5316D8AED3}",
|
|
39
|
+
"{7E03D99C-DC04-49d9-9315-930204A7B6E9}",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def in_excel_order(extensions: dict[str, str]) -> dict[str, str]:
|
|
44
|
+
"""The ``<ext>`` entries of an ``<extLst>`` with those that Excel orders in its order;
|
|
45
|
+
the others stay where they are."""
|
|
46
|
+
known = iter(sorted((u for u in extensions if u in _EXCEL_ORDER), key=_EXCEL_ORDER.index))
|
|
47
|
+
uris = [next(known) if uri in _EXCEL_ORDER else uri for uri in extensions]
|
|
48
|
+
return {uri: extensions[uri] for uri in uris}
|
|
49
|
+
|
|
50
|
+
|
|
27
51
|
class Rewriter(Protocol):
|
|
28
52
|
"""What the rewriting of references needs to know; see `package.references`."""
|
|
29
53
|
|
|
@@ -21,6 +21,7 @@ and panes of custom sheet views, and consolidation sources.
|
|
|
21
21
|
|
|
22
22
|
import re
|
|
23
23
|
from collections.abc import Callable
|
|
24
|
+
from html import unescape
|
|
24
25
|
from xml.sax.saxutils import escape
|
|
25
26
|
|
|
26
27
|
from openpyxl import Workbook
|
|
@@ -31,7 +32,6 @@ from excel_mcp.package import extensions
|
|
|
31
32
|
from excel_mcp.package.anchors import Geometry, Moved, move_anchors, move_vml_shape
|
|
32
33
|
from excel_mcp.package.lines import LineEdit
|
|
33
34
|
from excel_mcp.package.model import Part, SheetPackage, state_of
|
|
34
|
-
from excel_mcp.package.scan import unescape
|
|
35
35
|
from excel_mcp.refs import parse_range
|
|
36
36
|
|
|
37
37
|
Formula = Callable[[str, str], str]
|