How to preserve macro buttons in Excel when adding data with Python - python

For a process I am maintaining, I have a script that creates a csv file and then I copy the csv file into an Excel workbook with buttons that activate macros. This process works just fine.
I am trying to improve that process by writing a script that builds the workbook directly, thus eliminating a step. I thought the best way to do that was to create a template workbook where the first worksheet has the macro button. Then I would simply copy the template workbook, add in my data and save the new workbook under a new custom name. My test code is below:
import csv, os, sys, xlrd, xlwt, xlutils, shutil
from copy import deepcopy
from xlutils import save
from xlutils.copy import copy
templatefile = 'N:\Tools\Scripts-DEV\Testing_Template.xls'
Destfile = 'N:\Tools\Scripts-DEV\Testing_Dest.xls'
shutil.copy(templatefile,Destfile)
# Works fine up to here.
# If you look at the new file, it has the button that is in the template file.
rb = xlrd.open_workbook(Destfile)
rs = rb.sheet_by_index(0)
wb = copy(rb)
wb.get_sheet(0).write(3, 0, 'Due Date')
wb.get_sheet(0).write(3, 1, 'Name')
wb.get_sheet(0).write(3, 3, 'Category')
wb.get_sheet(0).write(3, 4, 'Number')
wb.save(Destfile)
Here is where the problem shows up. After you save, the macro button disappears. I've been looking for a couple days but I haven't (yet) found a way to save the updated Excel file without losing the macro button.
I've been looking at Preserving styles using python's xlrd,xlwt, and xlutils.copy but that doesn't quite meet my needs as I'm trying to preserve a button, not a style.
Does anyone know a way to do this?
I'm about to start looking at alternatives to xlutils, xlrd and xlwt as well, but I thought I'd ask here first.

From you comment part C:\Python27\ I deduce that you are on Windows. In that case you are probably better off with using pywin32 and a template .xls or .xlsm file.
Open the file using os.startfile(filename) then connect using workbook = win32com.client.GetObject(filename). The resulting workbook can be filled with the data and written to a new file with `workbook.SaveAs(newfilename).
Anything you do not touch explicitly is preserved. Excel is, of course, somewhat better at that than xlrd, xlwt and xlutils.

Related

Openpyxl-Made changes to excel and store it in a dataframe, how to kill the Excel without saving all the changes and avoid further recovery dialogue?

I need to open and edit my Excel with openpyxl, store the excel as a dataframe, and close the excel without any changes. Are there any ways to kill the excel and disable the auto-recovery dialogue which may pop out later?
The reason I'm asking is that my code worked perfectly fine in Pycharm, however after I packed it into .exe with pyinstaller, the code stopped working, the error said "Excel cannot access the file, there are serval possible reasons, the file name or path does not exist, or the file is being used by another program, or the workbook you are saving has the same name as a currently open workbook.
I assume it is because the openpyxl did not really close the excel, and I exported it to a different folder with the same file name.
Here is my code:
wb1 = openpyxl.load_workbook(my_path, keep_vba=True)
ws1 = wb1["sheet name"]
making changes...
ws1_df = pd.DataFrame(ws1.values)
wb1.close()
Many thanks ahead :)
The following way you can do this. solution
from win32com.client import Dispatch
# Start excel application
xl = Dispatch('Excel.Application')
# Open existing excel file
book = xl.Workbooks.Open('workbook.xlsx')
# Some arbitrary excel operations ...
# Close excel application without saving file
book.Close(SaveChanges=False)
xl.Quit()

Openpyxl don't preserve formatting

I have an excel file that i want to use as template, what i need is just change the values of some cells, and save it as another excel file. The problem is that when i save it the formatting, style and some date values are changed
from openpyxl import load_workbook
wb = load_workbook("test.xlsx")
ws = wb["RDO"]
ws["B8"].value = "MAI MAN"
wb.save("new.xlsx")
The old file:
The new one:
As you can see the borders and date fields were changed.
I was thinking in just unzip the excel and modify the xml files, then zip it back, but this approach has a problem. I will need to make a copy of some worksheets, so i tought i should be ok in just copy and paste the sheet.xml file and change the workbook.xml file to add this new sheet, but when i do this all the cells are cleared which is weird because when i copy the sheet in the excel program the output sheet file it's exactly the same as the original
I would like some simple solution if possible, maybe some other library or a fix for this xml sheet problem

'[Errno 13]' Permission denied: Openpyxl and win32com conflict

I'm using win32com to run macros in excel and openpyxl to modify cell values. In the process of debugging, I attempted to create a simplified version of existing code but still ran into the same
[Errno 13] Permission denied:'C:\\Users\\NAME\\Desktop\\old\\Book1.xlsx'.
I believe that the error is caused by the two packages (win32com and openpyxl) opening the same file and, when attempting to save/close, cannot close the instance open in the other package.
When I attempt to save/close with openpyxl before saving/closing with win32com, I run into the permission denied error. This makes sense; Openpyxl probably does not have permission to close the excel instance open through win32com. Code is below:
wb.save(r"C:\Users\NAME\Desktop\old\Book1.xlsx")
xel.Workbooks(1).Close(SaveChanges=True)
However, when I switch the order:
xel.Workbooks(1).Close(SaveChanges=True)
wb.save(r"C:\Users\NAME\Desktop\old\Book1.xlsx")
Excel attempts to save a backup file (randomly named "522FED10" or "35C0ED10", etc.) and when I press save, Excel crashes.
What's the workaround? I was thinking that you could use win32com to run the macros, save under a different filename, then use openpyxl to access that file and edit values. However, this is extremely inefficient (I'm dealing with excel files that have hundreds of thousands of rows of data). I could consider just using win32com, but that would require a revamp of a system.
Simple code:
import openpyxl as xl
import win32com.client
xel=win32com.client.Dispatch("Excel.Application")
xel.Workbooks.Open(Filename=r"C:\Users\NAME\Desktop\old\Book1.xlsx")
wb = xl.load_workbook(r"C:\Users\NAME\Desktop\old\Book1.xlsx")
ws = wb.active
xel.visible = False
xel.Cells(1,1).Value = 'Hello Excel'
ws.cell(row = 1,column = 2).value = "test"
xel.Workbooks(1).Close(SaveChanges=True)
wb.save(r"C:\Users\NAME\Desktop\old\Book1.xlsx")
Current issue
You should definitely not mix win32com and openpyxl operations.
The win32com statement xel.Workbooks.Open() loads the workbook contents into a memory space controlled by an Excel process. The openpyxl xl.load_workbook() statement on the other hand loads the workbook contents into a completely separate memory space controlled by a Python process.
Hence any subsequent win32com commands will do nothing to affect the workbook that's living inside the python-process-controlled memory, and vice versa any openpxyl commands will do nothing to affect the workbook that's living inside the Excel-process-controlled memory.
Solution
You mentioned that you have to run some excel macros. This rules out an openpyxl-only solution. My suggestion would be to use xlwings, which is in essence a powerful and user-friendly wrapper around the win32com API.
Here is a simple example of how you can execute Excel macros and manually update cell values within a single python script:
import xlwings as xw
# Start Excel app (invisibly in the background)
app = xw.App(visible=False)
# Load excel file into active Excel app
book = app.books.open(r"Book1.xlsm")
# Instruct Excel to execute the pre-existing excel macro named "CleanUpMacro"
book.macro("CleanUpMacro")()
# Instruct Excel to write a cell value in the first sheet
book.sheets["Sheet1"].range('A1').value = 42
# Save workbook and terminate Excel application
book.save()
book.close()
app.kill()

use data from excel file in another python script

I want to write a python script that takes data from one excel file and uses this data and inputs it in another excel file to get the output. For eg, if i have input.csv, it takes the data from there, and replaces certain cells of output.csv and gets the value based of the calculation
import pandas as pd
import numpy as np
data=pd.read_excel("Data.xlsx")
Depth=data["Depth (D):"];
ID=data["Tubing inner diameter (dti):"];
API=data["Oil gravity (API):"];
oilvisc=data["Oil viscosity (cp):"];
this is the script i have currently, these are the inputs.
import xlwt
import xlrd
from xlutils.copy import copy
rb=xlrd.open_workbook("hagedornbrowncorrelation.xls")
wb=copy(rb)
w_sheet=wb.get_sheet(0)
w_sheet.write(4,2,700)
wb.save("hagedornbrowncorrelation.xls")
the workbook "hagedornbrowncorrelation.xls" is my calculator, i am replacing the C5 with 700, but when i save it, all the macros and formulas in the workbook just go away and it becomes a useless workbook with numbers
I have done a similar project with openpyxl module which can be found here
https://openpyxl.readthedocs.io/en/stable/
Because I build a UI with Tkinter, I did to open a file, you may not want to use a global variable like I did, this was a quick hack.
def getFilecurrent():
global path
# open dialog box to select file
path = filedialog.askopenfilename(initialdir="/", title="Select file")
Then you can store it using
ref_workbook = openpyxl.load_workbook("filevariable")
Then do your manipulation of the data by selecting the right cell using, also remember to select the right worksheet.
weeklyengagement = ws['B18'].value
Afterwards, you create a new template for the file pasted into like
template = openpyxl.load_workbook("Section12Grades.xlsx") #Add file name
temp_sheet = template.get_sheet_by_name("Sheet1") #Add Sheet name
Lastly, you copy the range and paste the range using loops. There are so many resources out there I'm not going to paste my code as it has some custom set up and it would only confuse you.
Edit: if you wish to save with Macro, you can do:
wb = load_workbook(filename='filename.xlsm', read_only=False, keep_vba=True)
Formulas are string and if you wish to save the formulas, you have to keep it in the string format and save.

Python openpyxl data_only=True returning None

I have a simple excel file:
A1 = 200
A2 = 300
A3 = =SUM(A1:A2)
this file works in excel and shows proper value for SUM, but while using openpyxl module for python I cannot get value in data_only=True mode
Python code from shell:
wb = openpyxl.load_workbook('writeFormula.xlsx', data_only = True)
sheet = wb.active
sheet['A3']
<Cell Sheet.A3> # python response
print(sheet['A3'].value)
None # python response
while:
wb2 = openpyxl.load_workbook('writeFormula.xlsx')
sheet2 = wb2.active
sheet2['A3'].value
'=SUM(A1:A2)' # python response
Any suggestions what am I doing wrong?
It depends upon the provenance of the file. data_only=True depends upon the value of the formula being cached by an application like Excel. If, however, the file was created by openpyxl or a similar library, then it's probable that the formula was never evaluated and, thus, no cached value is available and openpyxl will report None as the value.
I have replicated the issue with Openpyxl and Python.
I am currently using openpyxl version 2.6.3 and Python 3.7.4. Also I am assuming that you are trying to complete an exercise from ATBSWP by Al Sweigart.
I tried and tested Charlie Clark's answer, considering that Excel may indeed cache values. I opened the spreadsheet in Excel, copied and pasted the formula into the same exact cell, and finally saved the workbook. Upon reopening the workbook in Python with Openpyxl with the data_only=True option, and reading the value of this cell, I saw the proper value, 500, instead of the wrong value, the None type.
I hope this helps.
I had the same issue. This may not be the most elegant solution, but this is what worked for me:
import xlwings
from openpyxl import load_workbook
excel_app = xlwings.App(visible=False)
excel_book = excel_app.books.open('writeFormula.xlsx')
excel_book.save()
excel_book.close()
excel_app.quit()
workbook = load_workbook(filename='writeFormula.xlsx', data_only=True)
I have suggestion to this problem. Convert xlsx file to csv :).
You will still have the original xlsx file. The conversion is done by libreoffice (it is that subprocess.call() line).You can use also Pandas for this as a more pythonic way.
from subprocess import call
from openpyxl import load_workbook
from csv import reader
filename="test"
wb = load_workbook(filename+".xlsx")
spread_range = wb['Sheet1']
#what ever function there is in A1 cell to be evaluated
print(spread_range.cell(row=1,column=1).value)
wb.close()
#this line can be done with subprocess or os.system()
#libreoffice --headless --convert-to csv $filename --outdir $outdir
call("libreoffice --headless --convert-to csv "+filename+".xlsx", shell=True)
with open(filename+".csv", newline='') as f:
reader = reader(f)
data = list(reader)
print(data[0][0])
or
# importing pandas as pd
import pandas as pd
# read an excel file and convert
# into a dataframe object
df = pd.DataFrame(pd.read_excel("Test.xlsx"))
# show the dataframe
df
I hope this helps somebody :-)
Yes, #Beno is right. If you want to edit the file without touching it, you can make a little "robot" that edits your excel file.
WARNING: This is a recursive way to edit the excel file. These libraries are depend on your machine, make sure you set time.sleep properly before continuing the rest of the code.
For instance, I use time.sleep, subprocess.Popen, and pywinauto.keyboard.send_keys, just add random character to any cell that you set, then save it. Then the data_only=True is working perfectly.
for more info about pywinauto.keyboard: pywinauto.keyboard
# import these stuff
import subprocess
from pywinauto.keyboard import send_keys
import time
import pygetwindow as gw
import pywinauto
excel_path = r"C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE"
excel_file_path = r"D:\test.xlsx"
def focus_to_window(window_title=None): # function to focus to window. https://stackoverflow.com/a/65623513/8903813
window = gw.getWindowsWithTitle(window_title)[0]
if not window.isActive:
pywinauto.application.Application().connect(handle=window._hWnd).top_window().set_focus()
subprocess.Popen([excel_path, excel_file_path])
time.sleep(1.5) # wait excel to open. Depends on your machine, set it propoerly
focus_to_window("Excel") # focus to that opened file
send_keys('%{F3}') # excel's name box | ALT+F3
send_keys('AA1{ENTER}') # whatever cell do you want to insert somthing | Type 'AA1' then press Enter
send_keys('Stackoverflow.com') # put whatever you want | Type 'Stackoverflow.com'
send_keys('^s') # save | CTRL+S
send_keys('%{F4}') # exit | ALT+F4
print("Done")
Sorry for my bad english.
As others already mentioned, Openpyxl only reads cashed formula value in data_only mode. I have used PyWin32 to open and save each XLSX file before it's processed by Openpyxl to read the formulas result value. This works for me well, as I don't process large files. This solution will work only if you have MS Excel installed on your PC.
import os
import win32com.client
from openpyxl import load_workbook
# Opening and saving XLSX file, so results for each stored formula can be evaluated and cashed so OpenPyXL can read them.
excel_file = os.path.join(path, file)
excel = win32com.client.gencache.EnsureDispatch('Excel.Application')
excel.DisplayAlerts = False # disabling prompts to overwrite existing file
excel.Workbooks.Open(excel_file )
excel.ActiveWorkbook.SaveAs(excel_file, FileFormat=51, ConflictResolution=2)
excel.DisplayAlerts = True # enabling prompts
excel.ActiveWorkbook.Close()
wb = load_workbook(excel_file)
# read your formula values with openpyxl and do other stuff here
I ran into the same issue. After reading through this thread I managed to fix it by simply opening the excel file, making a change then saving the file again. What a weird issue.

Categories

Resources