Search code examples
python-3.xlibreoffice-calcuno

Using Python to access LibreOffice Calc using Uno


I'm trying to write a script to manipulate a Ubuntu Mint _LibreOffice Calc sheet using Python 3.7,. For some reason I'm getting an error when I try to import Uno. I can run a macro from inside Calc calling this

    desktop = XSCRIPTCONTEXT.getDesktop()
    model = desktop.getCurrentComponent()
    active_sheet = model.CurrentController.ActiveSheet
    write 'Hello World' in A1
    active_sheet.getCellRangeByName("A1").String = "Hello World!

"

but can't directly from the VS_code IDE, interact with the sheet. Is there a path that I need to set to Uno_

Using LibreOffice 6.4, Python 3.7 and APSO. I'm so frustrated with LibreOffice there seems to be very little python support for newbies.


Solution

  • After a lot of research, I have figured out how to manipulate a Ubuntu LibreOffice Calc sheet using Python 3.7 and PyCharm and MySQL. I'm a Newbie at all of this, so please excuse my coding, I'm still learning. I have supplied links to information sources I used to complete this project.

    The main stumbling block that was causing me problems, was not understanding that LibreOffice needed to be initiated prior to connecting through the socket. After that I was able to use Uno and other modules to interact with the Calc spreadsheet. I have supplied copies of all my Py scripts. Hopefully this will help anyone who like me is new to this environment.

    # opens connection to mysql database GVWC
    import mysql.connector
    import pandas as pd
    from gui import validateLogin
    
    def conn_mysql():
        validateLogin #opens input box for username and password
        us, pw=validateLogin() #returns login info
        cnx = mysql.connector.connect(user=us, password=pw ,host='localhost', database= 'GVWC') # connects to DB
        pd.set_option('display.max_columns', None)
        df = pd.read_sql("SELECT * FROM GVWC.2020", cnx) #runs query
        return df, pd #returns data
    print("Database connection resolved.")
    
    # connection to libreoffice
    
    import uno
    import os
    import time
    
    def lo_conn():
        #Open socket to LibraOffice with delay to allow program to wait for connection
        os.popen('/usr/lib/libreoffice/program/soffice --accept="socket,host=localhost,port=2002;urp;StarOffice.ServiceManager"')
        time.sleep(3)  # Sleep for 3 seconds
    
        #=======================================================================================================================================
        # get the uno component context from the PyUNO runtime
        localContext = uno.getComponentContext()
    
        # create the UnoUrlResolver
        resolver = localContext.ServiceManager.createInstanceWithContext(
            "com.sun.star.bridge.UnoUrlResolver", localContext)
    
        # connect to the running office
        ctx = resolver.resolve("uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext")
        smgr = ctx.ServiceManager
    
    # create input box for login info
    from tkinter import *
    from functools import partial
    
    def validateLogin(username, password):
        #print("username entered :", username.get())
        #print("password entered :", password.get())
        global pw
        pw = password.get()
        global us
        us = username.get()
        return us, pw
    
    #window
    tkWindow = Tk()
    tkWindow.geometry("500x100+500+300")
    tkWindow.title('Tkinter Login Form - pythonexamples.org')
    
    #username label and text entry box
    usernameLabel = Label(tkWindow, text="User Name").grid(row=0, column=0)
    username = StringVar()
    usernameEntry = Entry(tkWindow, textvariable=username).grid(row=0, column=1)
    
    #password label and password entry box
    passwordLabel = Label(tkWindow,text="Password").grid(row=1, column=0)
    password = StringVar()
    passwordEntry = Entry(tkWindow, textvariable=password, show='*').grid(row=1, column=1)
    
    validateLogin = partial(validateLogin, username, password)
    
    #login button
    loginButton = Button(tkWindow, text="Login", command=validateLogin).grid(row=4, column=0)
    
    tkWindow.mainloop()
    
    
    #main program to create spreedsheet and fill with data from mysql
    
    import os
    from workingConn3 import conn_mysql
    from lo_conn_3 import lo_conn
    
    def main():
        conn_mysql # call mysql def connection
        df,pd=conn_mysql() # pass table data
        lo_conn #connect to Libreoffice
    
        # open calc and fill spreedsheet
        #=====================================================================================================================================
        writer = pd.ExcelWriter('Test_DF.xlsx',
                                    engine='xlsxwriter',
                                    datetime_format='mm/dd/yyyy')
    
        df.to_excel(writer, index=False, sheet_name='TEST',
                       startrow=5, header = 4)
    
        #Get the xlsxwriter objects from the dataframe writer object.
        workbook  = writer.book
        worksheet = writer.sheets['TEST']
    
    
    
        #Add a header format.
        header_format = workbook.add_format({
            'font_color': 'white',
            'text_wrap': True,
            'font_name': 'Calibri',
            'font_size': 11,
            'fg_color': '#44546a'})
    
        # Write the column headers with the defined format.
        for col_num, value in enumerate(df.columns.values):
            worksheet.write(5, col_num, value, header_format)
    
        colwidths = {}
    
        # Store the defaults.
        for col in range(14):
            colwidths[col] = 15
    
    
    
        # Then set the column widths.
        for col_num, width in colwidths.items():
            worksheet.set_column(col_num, col_num, width)
    
    
        #Center text of column
        cell_format = workbook.add_format()
        cell_format.set_align('center')
        worksheet.set_column('E1:E100',15,cell_format)
        worksheet.set_column('F1:F100',15,cell_format)
        worksheet.set_column('M1:M100',15,cell_format)
    
        #  Freeze pane on the header row.
        #
        worksheet.freeze_panes(6, 0)
        worksheet.autofilter('A6:O6')
    
        #Button text
        worksheet.write('A3', 'Press the button to say hello.')
    
        # Add a button tied to a macro in the VBA project.
        worksheet.insert_button('A1', {'macro': 'start',
                                       'caption': 'Press Me',
                                       'width': 80,
                                       'height': 30})
    
        # Close the Pandas Excel writer and output the Excel file.
        writer.save()
        writer.close()
        #reopen excel file
        os.popen('/usr/lib/libreoffice/program/soffice Test_DF.xlsx  --accept="socket,host=localhost,port=2002;urp;StarOffice.ServiceManager"')
    
    
    if __name__== "__main__" :
        main()
    

    https://medium.com/analytics-vidhya/macro-programming-in-openoffice-libreoffice-with-using-python-en-a37465e9bfa5

    https://medium.com/analytics-vidhya/starting-libreoffice-with-python-macro-programming-in-openoffice-libreoffice-with-using-10310f9e69f1

    https://pjryan126.github.io/jupyter-db-connections/

    http://christopher5106.github.io/office/2015/12/06/openoffice-libreoffice-automate-your-office-tasks-with-python-macros.html

    https://help.libreoffice.org/6.4/en-US/text/shared/guide/start_parameters.html?&DbPAR=WRITER&System=UNIX

    https://www.saltycrane.com/blog/2010/02/using-python-write-excel-openoffice-calc-spreadsheet-ubuntu-linux/

    Add dataframe and button to same sheet with XlsxWriter

    https://pynative.com/python-mysql-database-connection/

    Thanks to everyone who helped.