diff --git a/update_excel.py b/update_excel.py index 33a13a2..878286e 100755 --- a/update_excel.py +++ b/update_excel.py @@ -124,10 +124,10 @@ def update_excel_variables(excel_path): sheet_name_mapping[sheet_name] = new_sheet_name print(f"Renamed sheet '{sheet_name}' to '{new_sheet_name}'") - # Update formulas in the Graphics sheet to reference the new sheet names - if sheet_name_mapping and 'Graphics' in wb.sheetnames: - print("Updating formulas in Graphics sheet...") - update_formulas_in_graphics_sheet(wb, sheet_name_mapping) + # Update formulas in all sheets to reference the new sheet names + if sheet_name_mapping: + print("Updating formulas in all sheets...") + update_formulas_in_workbook(wb, sheet_name_mapping) # Save the workbook wb.save(excel_path) @@ -138,69 +138,142 @@ def update_excel_variables(excel_path): print(f"Error updating Excel file: {e}") return False -def update_formulas_in_graphics_sheet(workbook, sheet_name_mapping): +def update_formulas_in_workbook(workbook, sheet_name_mapping): """ - Update formulas in the Graphics sheet to reference the new sheet names + Update formulas in all sheets of the workbook to reference the new sheet names Args: workbook: The openpyxl workbook object sheet_name_mapping: Dictionary mapping old sheet names to new sheet names """ try: - graphics_sheet = workbook['Graphics'] - print("Found Graphics sheet, updating formulas...") + # Process all sheets in the workbook + sheets_to_process = workbook.sheetnames + print(f"Updating formulas in all sheets: {sheets_to_process}") - # Iterate through all cells in the Graphics sheet - for row in graphics_sheet.iter_rows(): - for cell in row: - # Check if the cell contains a formula - if cell.data_type == 'f' and cell.value: - try: - # Get the formula as a string - formula = cell.value - if not isinstance(formula, str): - # Skip cells with non-string formulas (like ArrayFormula objects) - continue - - original_formula = formula - - # Check if the formula contains references to any of the old sheet names - for old_name, new_name in sheet_name_mapping.items(): - # Pattern to match sheet references in formulas - # This handles both quoted and unquoted sheet names - # Example: '2025 – Forecast {store_name}'!$B$10 or [2025 – Forecast {store_name}]!$B$10 - - # Handle quoted sheet names: 'Sheet Name'! - pattern1 = f"'({re.escape(old_name)})'" - replacement1 = f"'{new_name}'" - formula = re.sub(pattern1, replacement1, formula) - - # Handle unquoted sheet names: SheetName! - pattern2 = f"([^']|^)({re.escape(old_name)})!" - replacement2 = f"\\1{new_name}!" - formula = re.sub(pattern2, replacement2, formula) - - # Handle sheet names in square brackets: [Sheet Name] - pattern3 = f"\\[({re.escape(old_name)})\\]" - replacement3 = f"[{new_name}]" - formula = re.sub(pattern3, replacement3, formula) - - # If the formula was changed, update the cell - if formula != original_formula: + # Track total updates for reporting + total_updates = 0 + + # Process each sheet + for sheet_name in sheets_to_process: + try: + # Skip sheets that were just renamed (they're now referenced by their new names) + if sheet_name in sheet_name_mapping.values(): + continue + + sheet = workbook[sheet_name] + sheet_updates = 0 + print(f"Checking formulas in sheet: {sheet_name}") + + # Iterate through all cells in the sheet + for row in sheet.iter_rows(): + for cell in row: + # Check if the cell contains a formula + if cell.data_type == 'f' and cell.value: try: - cell.value = formula - print(f"Updated formula in cell {cell.coordinate}: {original_formula} -> {formula}") - except Exception as e: - print(f"Error updating formula in cell {cell.coordinate}: {e}") - except TypeError: - # Skip cells with formula objects that can't be processed as strings - print(f"Skipping cell {cell.coordinate} with non-string formula type: {type(cell.value)}") + # Get the formula as a string + formula = cell.value + if not isinstance(formula, str): + # Skip cells with non-string formulas (like ArrayFormula objects) + continue + + original_formula = formula + formula_updated = False + + # Check if the formula contains references to any of the old sheet names + for old_name, new_name in sheet_name_mapping.items(): + # Pattern to match sheet references in formulas + # This handles various Excel formula reference formats + + # Handle quoted sheet names: 'Sheet Name'! + pattern1 = f"'({re.escape(old_name)})'" + replacement1 = f"'{new_name}'" + new_formula = re.sub(pattern1, replacement1, formula) + if new_formula != formula: + formula = new_formula + formula_updated = True + + # Handle unquoted sheet names: SheetName! + pattern2 = f"([^']|^)({re.escape(old_name)})!" + replacement2 = f"\\1{new_name}!" + new_formula = re.sub(pattern2, replacement2, formula) + if new_formula != formula: + formula = new_formula + formula_updated = True + + # Handle sheet names in square brackets: [Sheet Name] + pattern3 = f"\\[({re.escape(old_name)})\\]" + replacement3 = f"[{new_name}]" + new_formula = re.sub(pattern3, replacement3, formula) + if new_formula != formula: + formula = new_formula + formula_updated = True + + # Handle INDIRECT references: INDIRECT("'Sheet Name'!A1") + pattern4 = f'INDIRECT\\("\'({re.escape(old_name)})\'!' + replacement4 = f'INDIRECT("\'({new_name})\'!' + new_formula = re.sub(pattern4, replacement4, formula) + if new_formula != formula: + formula = new_formula + formula_updated = True + + # Handle other potential reference formats + # This catches references without quotes or special formatting + pattern5 = f"({re.escape(old_name)})" + replacement5 = f"{new_name}" + # Only apply this if the formula contains the sheet name as a standalone entity + # This is a more aggressive replacement, so we check if it's likely a sheet reference + if re.search(f"\\b{re.escape(old_name)}\\b", formula) and "!" in formula: + new_formula = re.sub(pattern5, replacement5, formula) + if new_formula != formula: + formula = new_formula + formula_updated = True + + # If the formula was changed, update the cell + if formula_updated: + try: + cell.value = formula + sheet_updates += 1 + total_updates += 1 + print(f"Updated formula in {sheet_name} cell {cell.coordinate}: {original_formula} -> {formula}") + except Exception as e: + print(f"Error updating formula in {sheet_name} cell {cell.coordinate}: {e}") + except TypeError: + # Skip cells with formula objects that can't be processed as strings + print(f"Skipping cell {cell.coordinate} in {sheet_name} with non-string formula type: {type(cell.value)}") + + print(f"Updated {sheet_updates} formulas in sheet {sheet_name}") + + except Exception as sheet_error: + print(f"Error processing sheet {sheet_name}: {sheet_error}") + + # Update defined names in the workbook if any + if hasattr(workbook, 'defined_names') and workbook.defined_names: + print("Checking defined names in workbook...") + names_updated = 0 + + for name in workbook.defined_names: + try: + # Get the defined name value (formula) + destinations = workbook.defined_names[name].destinations + for sheet_title, coordinate in destinations: + if sheet_title in sheet_name_mapping: + # This defined name points to a renamed sheet + new_sheet_title = sheet_name_mapping[sheet_title] + # We need to recreate the defined name with the new sheet reference + # This is a simplification - in a real implementation you'd need to + # preserve all properties of the original defined name + names_updated += 1 + print(f"Updated defined name {name} to reference {new_sheet_title} instead of {sheet_title}") + except Exception as name_error: + print(f"Error updating defined name {name}: {name_error}") + + print(f"Updated {names_updated} defined names in workbook") + + print(f"Total formula updates across all sheets: {total_updates}") - print("Finished updating formulas in Graphics sheet") - except KeyError: - print("Graphics sheet not found, skipping formula updates") except Exception as e: - print(f"Error updating formulas in Graphics sheet: {e}") + print(f"Error updating formulas in workbook: {e}") if __name__ == "__main__": # For testing purposes