#!/usr/bin/env python3 import json import os import re import openpyxl from openpyxl.utils import get_column_letter from direct_xml_update import update_excel_with_direct_xml def update_excel_variables(excel_path): """ Update the Variables sheet in the Excel file with values from config.json Args: excel_path (str): Path to the Excel file to update Returns: bool: True if successful, False otherwise """ # Define paths script_dir = os.path.dirname(os.path.abspath(__file__)) config_path = os.path.join(script_dir, 'config.json') try: # Load config.json with open(config_path, 'r') as f: config = json.load(f) user_data = config.get('user_data', {}) # Load Excel workbook print(f"Opening Excel file: {excel_path}") wb = openpyxl.load_workbook(excel_path) # Try to access the Variables sheet try: # First try by name sheet = wb['Variables'] except KeyError: # If not found by name, try to access the last sheet sheet_names = wb.sheetnames if sheet_names: print(f"Variables sheet not found by name. Using last sheet: {sheet_names[-1]}") sheet = wb[sheet_names[-1]] else: print("No sheets found in the workbook") return False # Map config variables to Excel cells based on the provided mapping cell_mappings = { 'B2': user_data.get('store_name', ''), 'B31': user_data.get('starting_date', ''), 'B32': user_data.get('duration', 36), 'B37': user_data.get('open_days_per_month', 0), # Convenience store type 'H37': user_data.get('convenience_store_type', {}).get('stores_number', 0), 'C37': user_data.get('convenience_store_type', {}).get('monthly_transactions', 0), # Convert boolean to 1/0 for has_digital_screens 'I37': 1 if user_data.get('convenience_store_type', {}).get('has_digital_screens', False) else 0, 'J37': user_data.get('convenience_store_type', {}).get('screen_count', 0), 'K37': user_data.get('convenience_store_type', {}).get('screen_percentage', 0), # Convert boolean to 1/0 for has_in_store_radio 'M37': 1 if user_data.get('convenience_store_type', {}).get('has_in_store_radio', False) else 0, 'N37': user_data.get('convenience_store_type', {}).get('radio_percentage', 0), # Supermarket store type 'H38': user_data.get('supermarket_store_type', {}).get('stores_number', 0), 'C38': user_data.get('supermarket_store_type', {}).get('monthly_transactions', 0), # Convert boolean to 1/0 for has_digital_screens 'I38': 1 if user_data.get('supermarket_store_type', {}).get('has_digital_screens', False) else 0, 'J38': user_data.get('supermarket_store_type', {}).get('screen_count', 0), 'K38': user_data.get('supermarket_store_type', {}).get('screen_percentage', 0), # Convert boolean to 1/0 for has_in_store_radio 'M38': 1 if user_data.get('supermarket_store_type', {}).get('has_in_store_radio', False) else 0, 'N38': user_data.get('supermarket_store_type', {}).get('radio_percentage', 0), # Hypermarket store type 'H39': user_data.get('hypermarket_store_type', {}).get('stores_number', 0), 'C39': user_data.get('hypermarket_store_type', {}).get('monthly_transactions', 0), # Convert boolean to 1/0 for has_digital_screens 'I39': 1 if user_data.get('hypermarket_store_type', {}).get('has_digital_screens', False) else 0, 'J39': user_data.get('hypermarket_store_type', {}).get('screen_count', 0), 'K39': user_data.get('hypermarket_store_type', {}).get('screen_percentage', 0), # Convert boolean to 1/0 for has_in_store_radio 'M39': 1 if user_data.get('hypermarket_store_type', {}).get('has_in_store_radio', False) else 0, 'N39': user_data.get('hypermarket_store_type', {}).get('radio_percentage', 0), # On-site channels 'B43': user_data.get('website_visitors', 0), 'B44': user_data.get('app_users', 0), 'B45': user_data.get('loyalty_users', 0), # Off-site channels 'B49': user_data.get('facebook_followers', 0), 'B50': user_data.get('instagram_followers', 0), 'B51': user_data.get('google_views', 0), 'B52': user_data.get('email_subscribers', 0), 'B53': user_data.get('sms_users', 0), 'B54': user_data.get('whatsapp_contacts', 0) } # Update the cells for cell_ref, value in cell_mappings.items(): try: sheet[cell_ref] = value print(f"Updated {cell_ref} with value: {value}") except Exception as e: print(f"Error updating cell {cell_ref}: {e}") # Update sheet names - replace {store_name} with actual store name store_name = user_data.get('store_name', '') if store_name: # Dictionary to store old sheet name to new sheet name mappings sheet_name_mapping = {} # Make a copy of the sheet names to avoid modifying during iteration sheet_names = wb.sheetnames.copy() for sheet_name in sheet_names: if '{store_name}' in sheet_name: new_sheet_name = sheet_name.replace('{store_name}', store_name) # Get the sheet by its old name sheet = wb[sheet_name] # Set the new title sheet.title = new_sheet_name # Store the mapping sheet_name_mapping[sheet_name] = new_sheet_name print(f"Renamed sheet '{sheet_name}' to '{new_sheet_name}'") # Use direct XML modification to replace all instances of {store_name} print("Using direct XML modification to update all formulas...") modified_file = update_excel_with_direct_xml(excel_path, store_name) if modified_file and os.path.exists(modified_file): # Use the modified file instead of the original print(f"Using modified file: {modified_file}") # Copy the modified file back to the original location import shutil shutil.copy2(modified_file, excel_path) # Remove the modified file os.remove(modified_file) # Reload the workbook to get the changes wb = openpyxl.load_workbook(excel_path) # Save the workbook wb.save(excel_path) print(f"Excel file updated successfully: {excel_path}") return True except Exception as e: print(f"Error updating Excel file: {e}") return False def update_formula_references(workbook, sheet_name_mapping): """ Update formula references in the Graphics sheet to point to the renamed sheets Args: workbook: The openpyxl workbook object sheet_name_mapping: Dictionary mapping old sheet names to new sheet names """ try: # Get the Graphics sheet graphics_sheet = workbook['Graphics'] print("Updating formula references in Graphics sheet...") # Track the number of updates updates_count = 0 total_formulas = 0 # Create a dictionary to track which cells have been processed processed_cells = {} # First pass: Identify all cells with formulas that reference the forecast sheets formula_cells = [] # 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 and isinstance(cell.value, str): total_formulas += 1 original_formula = cell.value # Check if the formula references any of the renamed sheets references_renamed_sheet = False for old_name in sheet_name_mapping.keys(): if old_name in original_formula: references_renamed_sheet = True break if references_renamed_sheet: formula_cells.append((cell, original_formula)) print(f"Found {len(formula_cells)} cells with formulas referencing renamed sheets (out of {total_formulas} total formulas)") # Second pass: Update the formulas for cell, original_formula in formula_cells: updated_formula = original_formula formula_updated = False # Check if the formula references any of the renamed sheets for old_name, new_name in sheet_name_mapping.items(): # Pattern 1: Sheet name in single quotes: 'Sheet Name'! if f"'{old_name}'!" in updated_formula: updated_formula = updated_formula.replace(f"'{old_name}'!", f"'{new_name}'!") formula_updated = True # Pattern 2: Sheet name without quotes: SheetName! # We need to be careful with this pattern to avoid partial matches pattern = re.compile(f"(^|[^'])({re.escape(old_name)})!") if pattern.search(updated_formula): updated_formula = pattern.sub(f"\\1{new_name}!", updated_formula) formula_updated = True # Pattern 3: Sheet name in INDIRECT function: INDIRECT("'Sheet Name'!...") if f"INDIRECT(\"'{old_name}'!" in updated_formula: updated_formula = updated_formula.replace( f"INDIRECT(\"'{old_name}'!", f"INDIRECT(\"'{new_name}'!" ) formula_updated = True # Pattern 4: Sheet name in double quotes: "Sheet Name"! if f"\"{old_name}\"!" in updated_formula: updated_formula = updated_formula.replace(f"\"{old_name}\"!", f"\"{new_name}\"!") formula_updated = True # Pattern 5: Simple text replacement for any remaining instances # Only do this if we're sure it's a sheet reference if old_name in updated_formula and not formula_updated: updated_formula = updated_formula.replace(old_name, new_name) formula_updated = True # If the formula was updated, set it back to the cell if formula_updated: try: cell.value = updated_formula updates_count += 1 print(f"Updated formula in cell {cell.coordinate}: {original_formula} -> {updated_formula}") except Exception as e: print(f"Error updating formula in cell {cell.coordinate}: {e}") # Special handling for specific cells that might have been missed # These are known cells with complex formulas referencing the forecast sheets special_cells = ['C4', 'H4', 'O4', 'U4', 'AA4', 'AG4', 'C5', 'H5', 'O5', 'U5', 'AA5', 'AG5', 'C6', 'H6', 'O6', 'U6', 'AA6', 'AG6', 'C25', 'H25', 'O25', 'U25', 'AA25', 'AG25', 'C26', 'H26', 'O26', 'U26', 'AA26', 'AG26', 'C27', 'H27', 'O27', 'U27', 'AA27', 'AG27'] # Print all cells in the Graphics sheet that have formulas print("\nDiagnostic: Checking all cells with formulas in Graphics sheet:") formula_cells_diagnostic = [] # Special check for rows 25-27 print("\nDiagnostic: Checking cells in rows 25-27:") # Define columns to check, focusing on G through AG columns_to_check = ['C', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'AA', 'AB', 'AC', 'AD', 'AE', 'AF', 'AG'] # Cells with ArrayFormula objects that need special handling array_formula_cells = [] # Check each cell in rows 25-27 for all specified columns for row_num in range(25, 28): # rows 25, 26, 27 for col in columns_to_check: cell_coord = f"{col}{row_num}" try: cell = graphics_sheet[cell_coord] # Print cell information regardless of whether it has a formula if cell.value is not None: value_type = cell.data_type value_preview = str(cell.value)[:50] if cell.value else "None" print(f"Row {row_num}, Cell {cell_coord}: Type={value_type}, Value={value_preview}...") # Check if it's a formula that references our sheets if cell.data_type == 'f': if isinstance(cell.value, str) and any(old_name in cell.value for old_name in sheet_name_mapping.keys()): formula_cells_diagnostic.append(f"{cell_coord}: {cell.value[:50]}...") # Add this cell to our special handling if cell_coord not in special_cells: special_cells.append(cell_coord) # Check for ArrayFormula objects elif str(cell.value).startswith(" {updated_formula}") except Exception as e: print(f"Error in special handling for cell {cell_coord}: {e}") print(f"Updated {updates_count} formula references in Graphics sheet") # Check if there are any remaining formulas with the old sheet names remaining_formulas = [] for row in graphics_sheet.iter_rows(): for cell in row: if cell.data_type == 'f' and cell.value and isinstance(cell.value, str): formula = cell.value if any(old_name in formula for old_name in sheet_name_mapping.keys()): remaining_formulas.append(f"{cell.coordinate}: {formula[:50]}...") if remaining_formulas: print(f"Warning: Found {len(remaining_formulas)} formulas that still reference old sheet names:") for formula in remaining_formulas: print(f" {formula}") else: print("All formulas have been successfully updated!") return updates_count except KeyError: print("Graphics sheet not found in workbook") return 0 except Exception as e: print(f"Error updating formula references: {e}") return 0 if __name__ == "__main__": # For testing purposes import sys if len(sys.argv) > 1: excel_path = sys.argv[1] update_excel_variables(excel_path) else: print("Please provide the path to the Excel file as an argument")