excel-mcp-server 2.2.2__tar.gz → 2.2.4__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.2 → excel_mcp_server-2.2.4}/PKG-INFO +1 -1
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/pyproject.toml +1 -1
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/pyproject.toml.orig +1 -1
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/slicer_place.py +3 -1
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/sparklines.py +2 -4
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/consistency.py +7 -6
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/extensions.py +37 -23
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/opc.py +26 -14
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/patch.py +36 -8
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/references.py +5 -2
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/restore.py +8 -3
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/LICENSE +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/README.md +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/__init__.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/__main__.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/cells.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/criteria.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/dependencies.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/dynamic.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/engine.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/__init__.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/arrays.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/conditional.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/datetime.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/depreciation.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/distributions.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/financial.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/helpers.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/indirect.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/information.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/logical.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/lookup.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/math.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/regression.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/securities.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/statistical.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/subtotals.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/text.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/operators.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/parser.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/r1c1.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/registry.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/scheduler.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/solver.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/steps.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/tablerefs.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/textformat.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/values.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/wildcard.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/cell_budget.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/cfb.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/chart_roundtrip.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/cli.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/config.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/errors.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/formula_syntax.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/formulas.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/function_arguments.json +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/inputs.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/lazy_edit.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/lazy_find.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/lazy_lines.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/lazy_rows.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/lazy_shared.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/lazy_source.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/lazy_workbook.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/links.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/macros.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/__init__.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/calculated.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/cells.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/chart_index.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/chart_info.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/chartex.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/chartex_parts/box_whisker_style.xml +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/chartex_parts/colors.xml +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/chartex_parts/funnel_style.xml +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/chartex_parts/histogram_style.xml +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/chartex_parts/sunburst_style.xml +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/chartex_parts/treemap_style.xml +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/chartex_parts/waterfall_style.xml +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/chartex_xml.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/charts.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/charts_axes.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/charts_check.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/charts_data.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/charts_look.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/charts_modern.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/charts_options.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/charts_series.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/charts_style.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/columns.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/comparison.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/conditional.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/conditional_kinds.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/conditional_rule.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/conditional_x14.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/csv_export.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/csv_format.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/csv_import.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/csv_values.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/date_width.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/drawings.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/duplicates.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/files.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/fill.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/filter_criteria.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/filter_stored.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/filters.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/formatting.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/goal_seek.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/hyperlinks.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/images.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/inspect.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/layout.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/line_edit_objects.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/line_edit_rules.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/line_edit_styles.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/line_edits.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/names.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/notes.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/paste.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_axis.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_cache.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_calc.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_definition.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_fields.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_groups.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_index.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_options.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_rebuild.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_records.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_render.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_showas.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_source.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_specs.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/pivot_values.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/print_setup.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/protection.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/replace.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/rule_clear.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/rules.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/sheet_copy.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/sheet_counts.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/sheet_refs.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/sheet_shapes.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/sheet_view.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/sheets.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/shifting.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/slicer_index.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/slicer_manage.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/slicer_pivot.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/slicer_table.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/slicer_xml.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/slicers.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/sorting.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/spans.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/sparkline_style.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/sparkline_xml.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/spill.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/table_options.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/tables.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/text_split.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/trace.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/transform.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/used_range.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/vba.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/vba_write.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/workbook_rewrite.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/workbook_settings.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/x14_xml.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/ovba.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/ovba_compress.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/ovba_write.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/__init__.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/anchors.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/arrays.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/capture.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/capture_sheet.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/guards.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/lazy_records.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/lines.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/metadata.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/model.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/pivot_records.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/pivots.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/properties.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/reader.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/restore_sheet.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/scan.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/shape_names.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/package/vml.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/paths.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/plain_refs.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/py.typed +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/refs.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/rewrite.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/__init__.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/annotation_tools.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/areas.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/csv_tools.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/data_tools.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/format_tools.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/formula_tools.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/http.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/image_tools.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/object_tools.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/params.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/pivot_tools.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/registry.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/results.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/sheet_tools.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/slicer_tools.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/validation.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/vba_tools.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/vba_write_tools.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/server/workbook_tools.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/spill.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/structured.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/text.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/upload_links.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/upload_scan.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/values.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/workspace.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/xlfn.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/xlm.py +0 -0
- {excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/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.4
|
|
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
|
|
@@ -7,6 +7,7 @@ from openpyxl.workbook import Workbook
|
|
|
7
7
|
from openpyxl.workbook.defined_name import DefinedName
|
|
8
8
|
from openpyxl.worksheet.worksheet import Worksheet
|
|
9
9
|
|
|
10
|
+
from excel_mcp.errors import WorkbookError
|
|
10
11
|
from excel_mcp.operations import slicer_xml as xml
|
|
11
12
|
from excel_mcp.package import Link, Part, SheetPackage, state_of
|
|
12
13
|
from excel_mcp.package.anchors import Geometry
|
|
@@ -119,7 +120,8 @@ def _list_in(extensions: dict[str, str], uri: str, element: str) -> None:
|
|
|
119
120
|
assert reference is not None
|
|
120
121
|
existing = extensions[uri]
|
|
121
122
|
closing = re.search(r"</(?:\w+:)(?:slicerCaches|timelineCacheRefs)>", existing)
|
|
122
|
-
|
|
123
|
+
if closing is None:
|
|
124
|
+
raise WorkbookError("The workbook's list of slicer or timeline caches is malformed.")
|
|
123
125
|
extensions[uri] = existing[: closing.start()] + reference[0] + existing[closing.start() :]
|
|
124
126
|
|
|
125
127
|
|
|
@@ -17,7 +17,7 @@ from excel_mcp.operations.sparkline_style import SparklineInfo, SparklineStyle,
|
|
|
17
17
|
from excel_mcp.operations.sparkline_xml import build_group, read_groups
|
|
18
18
|
from excel_mcp.operations.x14_xml import X14, XM, sheet_prefix
|
|
19
19
|
from excel_mcp.package import state_of
|
|
20
|
-
from excel_mcp.package.extensions import SPARKLINES, edit_sparklines
|
|
20
|
+
from excel_mcp.package.extensions import SPARKLINES, edit_sparklines, sparkline_location
|
|
21
21
|
from excel_mcp.refs import CellRange, cell_name, parse_range
|
|
22
22
|
from excel_mcp.workspace import get_sheet
|
|
23
23
|
|
|
@@ -117,9 +117,7 @@ def _delete(extensions: dict[str, str], doomed: Callable[[str], bool]) -> int:
|
|
|
117
117
|
|
|
118
118
|
def handle(item: str) -> list[str]:
|
|
119
119
|
nonlocal removed
|
|
120
|
-
|
|
121
|
-
assert location is not None
|
|
122
|
-
if doomed(location[1]):
|
|
120
|
+
if doomed(sparkline_location(item)):
|
|
123
121
|
removed += 1
|
|
124
122
|
return []
|
|
125
123
|
return [item]
|
|
@@ -27,7 +27,8 @@ _CACHE_LISTS = {
|
|
|
27
27
|
}
|
|
28
28
|
_SLICER_REFERENCE = re.compile(r'<(?:[\w.-]+:)?(?:slicer|timeline)\b[^>]*?\bcache="([^"]*)"')
|
|
29
29
|
PIVOT_ENTRY = re.compile(r"<(?:[\w.-]+:)?pivotTable\b[^>]*/>")
|
|
30
|
-
_CACHE_ENTRY = r
|
|
30
|
+
_CACHE_ENTRY = re.compile(r"<(?:[\w.-]+:)?(?:slicerCache|timelineCacheRef)\b[^>]*/>")
|
|
31
|
+
_RELATIONSHIP_ID = re.compile(r'\b[\w.-]+:id="([^"]*)"')
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def cache_name(part: Part) -> str:
|
|
@@ -80,11 +81,11 @@ def without_caches(
|
|
|
80
81
|
and link.target.content_type in _CACHES
|
|
81
82
|
and cache_name(link.target) in dropped
|
|
82
83
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
|
|
85
|
+
def entry(match: re.Match[str]) -> str:
|
|
86
|
+
return "" if gone.intersection(_RELATIONSHIP_ID.findall(match[0])) else match[0]
|
|
87
|
+
|
|
88
|
+
kept = [_CACHE_ENTRY.sub(entry, text) for text in texts] if gone else texts
|
|
88
89
|
return [link for link in links if link.id not in gone], kept
|
|
89
90
|
|
|
90
91
|
|
|
@@ -12,6 +12,7 @@ from xml.sax.saxutils import escape
|
|
|
12
12
|
|
|
13
13
|
from openpyxl.utils.cell import column_index_from_string, get_column_letter
|
|
14
14
|
|
|
15
|
+
from excel_mcp.errors import WorkbookError
|
|
15
16
|
from excel_mcp.package.lines import LineEdit
|
|
16
17
|
from excel_mcp.package.scan import unescape
|
|
17
18
|
from excel_mcp.refs import CellRange, clip_areas, parse_range
|
|
@@ -41,21 +42,27 @@ class Rewriter(Protocol):
|
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
_SPARKLINE = re.compile(r"<x14:sparkline>.*?</x14:sparkline>", re.S)
|
|
45
|
+
# An element is its start tag, then either "/>" or its content up to its end tag. Written
|
|
46
|
+
# like this, a self-closing element never makes the match run on to some later end tag, so
|
|
47
|
+
# a document of many such elements is scanned once and not once per element.
|
|
44
48
|
_EMPTY_GROUP = re.compile(
|
|
45
|
-
r"<x14:sparklineGroup\b[^>]
|
|
49
|
+
r"<x14:sparklineGroup\b[^>]*?(?:/>|>(?:(?!<x14:sparkline>).)*?</x14:sparklineGroup>)", re.S
|
|
46
50
|
)
|
|
47
|
-
_RULE_GROUP = re.compile(
|
|
48
|
-
|
|
51
|
+
_RULE_GROUP = re.compile(
|
|
52
|
+
r"<x14:conditionalFormatting\b[^>]*?(?:/>|>.*?</x14:conditionalFormatting>)", re.S
|
|
53
|
+
)
|
|
54
|
+
_RULE = re.compile(r"<x14:cfRule\b[^>]*?(?:/>|>.*?</x14:cfRule>)", re.S)
|
|
55
|
+
_VALIDATION = re.compile(r"<x14:dataValidation\b[^>]*?(?:/>|>.*?</x14:dataValidation>)", re.S)
|
|
49
56
|
_DATED_GROUP = re.compile(
|
|
50
|
-
r"(<x14:sparklineGroup(?!s)[^>]
|
|
57
|
+
r"(<x14:sparklineGroup(?!s)[^>]*(?<!/)>(?:(?!<x14:sparklines>|</x14:sparklineGroup>).)*?)"
|
|
58
|
+
r"<xm:f>([^<]*)</xm:f>(<x14:sparklines>)",
|
|
51
59
|
re.S,
|
|
52
60
|
)
|
|
53
61
|
_GROUP_DATES = re.compile(r"<xm:f>([^<]*)</xm:f>(?=<x14:sparklines>)")
|
|
54
62
|
_RANGE = re.compile(r"<xm:sqref>([^<]*)</xm:sqref>")
|
|
55
63
|
_FORMULA = re.compile(r"<xm:f>([^<]*)</xm:f>")
|
|
56
|
-
_FORMULA_ITEM = re.compile(
|
|
57
|
-
|
|
58
|
-
)
|
|
64
|
+
_FORMULA_ITEM = re.compile(f"{_RULE.pattern}|{_VALIDATION.pattern}", re.S)
|
|
65
|
+
_RULE_ID_ATTRIBUTE = re.compile(r'\bid="([^"]*)"')
|
|
59
66
|
_GUID = re.compile(r'(<x14:id>|\b(?:xr2:uid|id)=")(\{[0-9A-Fa-f-]{36}\})')
|
|
60
67
|
_CELL = re.compile(r"(\$?)\b([A-Z]{1,3})(\$?)(\d+)\b(?![!(\w])")
|
|
61
68
|
|
|
@@ -196,21 +203,18 @@ def rule_sub(xml: str, rule: Callable[[str], str]) -> str:
|
|
|
196
203
|
|
|
197
204
|
|
|
198
205
|
def without_rules(extensions: dict[str, str], identifiers: set[str]) -> None:
|
|
199
|
-
"""Remove the Excel 2010 conditional format rules with these ids (``{GUID}``)
|
|
206
|
+
"""Remove the Excel 2010 conditional format rules with these ids (``{GUID}``), and the
|
|
207
|
+
groups left without a rule."""
|
|
200
208
|
xml = extensions.get(CONDITIONAL_FORMATS)
|
|
201
209
|
if xml is None:
|
|
202
210
|
return
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
r"<x14:conditionalFormatting\b[^>]*>(?:(?!<x14:cfRule\b).)*?"
|
|
211
|
-
r"</x14:conditionalFormatting>"
|
|
212
|
-
)
|
|
213
|
-
xml = re.sub(emptied, "", xml, flags=re.S)
|
|
211
|
+
|
|
212
|
+
def dropped(rule: re.Match[str]) -> str:
|
|
213
|
+
start_tag = rule[0][: rule[0].index(">") + 1]
|
|
214
|
+
return "" if identifiers.intersection(_RULE_ID_ATTRIBUTE.findall(start_tag)) else rule[0]
|
|
215
|
+
|
|
216
|
+
xml = _RULE.sub(dropped, xml)
|
|
217
|
+
xml = _RULE_GROUP.sub(lambda group: group[0] if "<x14:cfRule" in group[0] else "", xml)
|
|
214
218
|
if "<x14:cfRule" in xml:
|
|
215
219
|
extensions[CONDITIONAL_FORMATS] = xml
|
|
216
220
|
else:
|
|
@@ -229,11 +233,21 @@ def _sparklines(xml: str, handle: Callable[[str], list[str]]) -> str | None:
|
|
|
229
233
|
return xml if re.search(r"<x14:sparklineGroup\b", xml) else None
|
|
230
234
|
|
|
231
235
|
|
|
232
|
-
def
|
|
236
|
+
def sparkline_location(item: str) -> str:
|
|
237
|
+
"""The cell of a ``<x14:sparkline>``. Excel refuses a file with a sparkline that has none."""
|
|
233
238
|
location = _RANGE.search(item)
|
|
239
|
+
if location is None:
|
|
240
|
+
raise WorkbookError(
|
|
241
|
+
"The workbook has a sparkline without a location (<xm:sqref>), which Excel "
|
|
242
|
+
"cannot open. Remove it from the file."
|
|
243
|
+
)
|
|
244
|
+
return location[1]
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _moved_sparkline(item: str, rewriter: Rewriter) -> list[str]:
|
|
248
|
+
location = sparkline_location(item)
|
|
234
249
|
source = _FORMULA.search(item)
|
|
235
|
-
|
|
236
|
-
moved = rewriter.ranges(location[1], grow=False)
|
|
250
|
+
moved = rewriter.ranges(location, grow=False)
|
|
237
251
|
if moved is None:
|
|
238
252
|
return []
|
|
239
253
|
item = _RANGE.sub(lambda _: f"<xm:sqref>{moved}</xm:sqref>", item)
|
|
@@ -243,7 +257,7 @@ def _moved_sparkline(item: str, rewriter: Rewriter) -> list[str]:
|
|
|
243
257
|
item = item.replace(source[0], "")
|
|
244
258
|
else:
|
|
245
259
|
item = item.replace(source[0], f"<xm:f>{escape(read)}</xm:f>")
|
|
246
|
-
return [item, *_inserted_copies(item, location
|
|
260
|
+
return [item, *_inserted_copies(item, location, rewriter)]
|
|
247
261
|
|
|
248
262
|
|
|
249
263
|
def _moved_formula(match: re.Match[str], rewriter: Rewriter) -> str:
|
|
@@ -4,7 +4,9 @@ import io
|
|
|
4
4
|
import posixpath
|
|
5
5
|
import re
|
|
6
6
|
import zipfile
|
|
7
|
+
from collections.abc import Callable
|
|
7
8
|
from dataclasses import dataclass
|
|
9
|
+
from itertools import count
|
|
8
10
|
from xml.sax.saxutils import quoteattr
|
|
9
11
|
|
|
10
12
|
from excel_mcp.errors import WorkbookError
|
|
@@ -75,15 +77,25 @@ def write_rels(rels: list[Rel]) -> bytes:
|
|
|
75
77
|
return (XML_DECLARATION + "".join(lines)).encode()
|
|
76
78
|
|
|
77
79
|
|
|
78
|
-
def
|
|
79
|
-
"""
|
|
80
|
+
def free_ids(rels: list[Rel]) -> Callable[[str], str]:
|
|
81
|
+
"""A function that gives, for each id wanted, that id if it is a free ``rIdN`` and else the
|
|
82
|
+
first free one, and then counts it as taken. Many calls cost no more than one scan."""
|
|
80
83
|
taken = {rel.id for rel in rels}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
numbers = count(1)
|
|
85
|
+
|
|
86
|
+
def allocate(wanted: str) -> str:
|
|
87
|
+
if re.fullmatch(r"rId\d+", wanted) and wanted not in taken:
|
|
88
|
+
chosen = wanted
|
|
89
|
+
else:
|
|
90
|
+
chosen = next(f"rId{n}" for n in numbers if f"rId{n}" not in taken)
|
|
91
|
+
taken.add(chosen)
|
|
92
|
+
return chosen
|
|
93
|
+
|
|
94
|
+
return allocate
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def free_id(rels: list[Rel], wanted: str) -> str:
|
|
98
|
+
return free_ids(rels)(wanted)
|
|
87
99
|
|
|
88
100
|
|
|
89
101
|
class ContentTypes:
|
|
@@ -135,15 +147,16 @@ class Package:
|
|
|
135
147
|
|
|
136
148
|
def __init__(self, data: bytes) -> None:
|
|
137
149
|
self._source = zipfile.ZipFile(io.BytesIO(data))
|
|
150
|
+
self._source_names = set(self._source.namelist())
|
|
138
151
|
self._changes: dict[str, bytes] = {}
|
|
139
152
|
self._rels: dict[str, list[Rel]] = {}
|
|
140
153
|
self.content_types = ContentTypes(self.read(CONTENT_TYPES))
|
|
141
154
|
|
|
142
155
|
def names(self) -> set[str]:
|
|
143
|
-
return
|
|
156
|
+
return self._source_names | set(self._changes)
|
|
144
157
|
|
|
145
158
|
def exists(self, name: str) -> bool:
|
|
146
|
-
return name in self._changes or name in self.
|
|
159
|
+
return name in self._changes or name in self._source_names
|
|
147
160
|
|
|
148
161
|
def read(self, name: str) -> bytes:
|
|
149
162
|
if name in self._changes:
|
|
@@ -162,13 +175,12 @@ class Package:
|
|
|
162
175
|
|
|
163
176
|
def unique_name(self, wanted: str) -> str:
|
|
164
177
|
"""``wanted``, or the same name with the next free number in it."""
|
|
165
|
-
|
|
166
|
-
if wanted not in taken:
|
|
178
|
+
if not self.exists(wanted):
|
|
167
179
|
return wanted
|
|
168
180
|
match = re.fullmatch(r"(.*?)(\d*)(\.[^./]*)?", wanted)
|
|
169
181
|
stem, number, extension = match.groups() if match else (wanted, "", "")
|
|
170
182
|
counter = int(number or 0) + 1
|
|
171
|
-
while f"{stem}{counter}{extension or ''}"
|
|
183
|
+
while self.exists(f"{stem}{counter}{extension or ''}"):
|
|
172
184
|
counter += 1
|
|
173
185
|
return f"{stem}{counter}{extension or ''}"
|
|
174
186
|
|
|
@@ -189,6 +201,6 @@ class Package:
|
|
|
189
201
|
elif content:
|
|
190
202
|
target.writestr(name, content)
|
|
191
203
|
for name, content in self._changes.items():
|
|
192
|
-
if content and name not in self.
|
|
204
|
+
if content and name not in self._source_names:
|
|
193
205
|
target.writestr(name, content)
|
|
194
206
|
return output.getvalue()
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from collections.abc import Callable
|
|
5
|
+
from itertools import pairwise
|
|
5
6
|
|
|
6
7
|
from excel_mcp.errors import WorkbookError
|
|
7
8
|
from excel_mcp.package.scan import Scan, scan
|
|
@@ -146,17 +147,44 @@ def _with_extension(rule: bytes, qname: str, extension: str, offset: int) -> Edi
|
|
|
146
147
|
return (offset, offset + len(rule), text)
|
|
147
148
|
|
|
148
149
|
|
|
150
|
+
# Looking each cell up by its address reads the sheet once per cell, which is fastest for a
|
|
151
|
+
# few cells; for more, one pass over all the cells does not grow with the number to patch.
|
|
152
|
+
_FEW_CELLS = 16
|
|
153
|
+
_CELL = re.compile(rb'<c r="([^"]*)"([^>]*?)(?:/>|>(.*?)</c>)', re.S)
|
|
154
|
+
|
|
155
|
+
|
|
149
156
|
def patch_cells(data: bytes, edits: dict[str, CellEdit]) -> bytes:
|
|
150
157
|
"""Rewrite chosen cells of ``sheetData``; an edit gets a cell's attributes and its content."""
|
|
151
158
|
if not edits:
|
|
152
159
|
return data
|
|
153
|
-
|
|
154
|
-
|
|
160
|
+
if len(edits) <= _FEW_CELLS:
|
|
161
|
+
return splice(data, _located_edits(data, edits))
|
|
155
162
|
|
|
156
163
|
def rewrite(match: re.Match[bytes]) -> bytes:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
164
|
+
edit = edits.get(match[1].decode())
|
|
165
|
+
return _rewritten(match, edit) if edit else match[0]
|
|
166
|
+
|
|
167
|
+
return _CELL.sub(rewrite, data)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _located_edits(data: bytes, edits: dict[str, CellEdit]) -> list[Edit]:
|
|
171
|
+
found: list[Edit] = []
|
|
172
|
+
for coordinate, edit in edits.items():
|
|
173
|
+
opening = b'<c r="%b"' % coordinate.encode()
|
|
174
|
+
start = data.find(opening)
|
|
175
|
+
while start >= 0:
|
|
176
|
+
match = _CELL.match(data, start)
|
|
177
|
+
if match is None:
|
|
178
|
+
raise WorkbookError(f"Cell {coordinate} of a worksheet is malformed.")
|
|
179
|
+
found.append((match.start(), match.end(), _rewritten(match, edit)))
|
|
180
|
+
start = data.find(opening, match.end())
|
|
181
|
+
found.sort()
|
|
182
|
+
if any(before[1] > after[0] for before, after in pairwise(found)):
|
|
183
|
+
raise WorkbookError("A worksheet has cells inside other cells.")
|
|
184
|
+
return found
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _rewritten(match: re.Match[bytes], edit: CellEdit) -> bytes:
|
|
188
|
+
attributes, body = edit(match[2].decode(), (match[3] or b"").decode())
|
|
189
|
+
start = f'<c r="{match[1].decode()}"{attributes}'
|
|
190
|
+
return f"{start}>{body}</c>".encode() if body else f"{start}/>".encode()
|
|
@@ -26,6 +26,7 @@ from xml.sax.saxutils import escape
|
|
|
26
26
|
from openpyxl import Workbook
|
|
27
27
|
from openpyxl.worksheet.worksheet import Worksheet
|
|
28
28
|
|
|
29
|
+
from excel_mcp.errors import WorkbookError
|
|
29
30
|
from excel_mcp.package import extensions
|
|
30
31
|
from excel_mcp.package.anchors import Geometry, Moved, move_anchors, move_vml_shape
|
|
31
32
|
from excel_mcp.package.lines import LineEdit
|
|
@@ -120,7 +121,8 @@ def _ranged_items(xml: str, item: str, attribute: str, rewriter: _Rewriter) -> s
|
|
|
120
121
|
def move(match: re.Match[str]) -> str:
|
|
121
122
|
text = match[0]
|
|
122
123
|
value = re.search(rf'\b{attribute}="([^"]*)"', text)
|
|
123
|
-
|
|
124
|
+
if value is None:
|
|
125
|
+
raise WorkbookError(f"A <{item}> of the workbook has no {attribute} attribute.")
|
|
124
126
|
moved = rewriter.ranges(value[1], grow=attribute == "sqref")
|
|
125
127
|
return "" if moved is None else text.replace(value[0], f'{attribute}="{moved}"', 1)
|
|
126
128
|
|
|
@@ -134,7 +136,8 @@ def _sort_state(xml: str, rewriter: _Rewriter) -> str | None:
|
|
|
134
136
|
|
|
135
137
|
def move(match: re.Match[str]) -> str:
|
|
136
138
|
found = ref.search(match[0])
|
|
137
|
-
|
|
139
|
+
if found is None:
|
|
140
|
+
raise WorkbookError("A <sortCondition> of the workbook has no ref attribute.")
|
|
138
141
|
moved = rewriter.ranges(found[1])
|
|
139
142
|
return "" if moved is None else match[0].replace(found[0], f'ref="{moved}"', 1)
|
|
140
143
|
|
|
@@ -14,7 +14,7 @@ from excel_mcp.package.model import (
|
|
|
14
14
|
state_of,
|
|
15
15
|
workbook_sheets,
|
|
16
16
|
)
|
|
17
|
-
from excel_mcp.package.opc import Package, Rel,
|
|
17
|
+
from excel_mcp.package.opc import Package, Rel, free_ids
|
|
18
18
|
from excel_mcp.package.patch import WORKBOOK_ORDER, append_to_root, insertions, splice
|
|
19
19
|
from excel_mcp.package.restore_sheet import restore_sheet
|
|
20
20
|
from excel_mcp.package.scan import attributes, rel_id, remap_relationship_ids, scan, tags
|
|
@@ -103,13 +103,18 @@ class Restorer:
|
|
|
103
103
|
) -> list[str]:
|
|
104
104
|
"""Add relationships to a part that is being merged into, and renumber the XML to match."""
|
|
105
105
|
rels = self.package.rels(owner)
|
|
106
|
+
free_id = free_ids(rels)
|
|
107
|
+
known: dict[tuple[str, str], Rel] = {}
|
|
108
|
+
for rel in rels:
|
|
109
|
+
known.setdefault((rel.type, rel.target), rel)
|
|
106
110
|
mapping: dict[str, str] = {}
|
|
107
111
|
for link in links:
|
|
108
112
|
rel = Rel(link.id, link.type, self.target(link), isinstance(link.target, str))
|
|
109
|
-
existing =
|
|
113
|
+
existing = known.get((rel.type, rel.target))
|
|
110
114
|
if existing is None:
|
|
111
|
-
rel.id = free_id(
|
|
115
|
+
rel.id = free_id(link.id)
|
|
112
116
|
rels.append(rel)
|
|
117
|
+
known[rel.type, rel.target] = rel
|
|
113
118
|
mapping[link.id] = (existing or rel).id
|
|
114
119
|
return [remap_relationship_ids(text, mapping, scope) for text in texts]
|
|
115
120
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/conditional.py
RENAMED
|
File without changes
|
|
File without changes
|
{excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/depreciation.py
RENAMED
|
File without changes
|
{excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/distributions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/information.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/regression.py
RENAMED
|
File without changes
|
{excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/securities.py
RENAMED
|
File without changes
|
{excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/calc/functions/statistical.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/chartex_parts/colors.xml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{excel_mcp_server-2.2.2 → excel_mcp_server-2.2.4}/src/excel_mcp/operations/charts_options.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|