Enhance formula update to process all sheets and catch all reference formats
This commit is contained in:
179
update_excel.py
179
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
|
||||
# Track total updates for reporting
|
||||
total_updates = 0
|
||||
|
||||
original_formula = formula
|
||||
# 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
|
||||
|
||||
# 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
|
||||
sheet = workbook[sheet_name]
|
||||
sheet_updates = 0
|
||||
print(f"Checking formulas in sheet: {sheet_name}")
|
||||
|
||||
# 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:
|
||||
# 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
|
||||
|
||||
Reference in New Issue
Block a user