5

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.

Jeff O
  • 133
  • 1
  • 1
  • 7
  • If I were to take up using Python to externally control LO via UNO and was having problems getting things going (Java's what I've used up to now), I'd probably take a careful look at [Designing & Developing Python Applications](https://wiki.documentfoundation.org/Macros/Python_Design_Guide), especially the section entitled "From an IDE via LibreOffice Python interpreter". – David Yockey Mar 14 '20 at 15:47
  • Thanks Dave, I have been playing with it but must not have environment set up correctly. I can run the py script from inside LibreOffice , but I still can't connect from outside using my IDE. I think at this point i'm going to go in another direction. Unfortunately I don't think LO has the support yet for python. – Jeff O Mar 15 '20 at 00:46
  • Normally Ubuntu doesn't require this, but maybe your system is missing the required package: https://wiki.documentfoundation.org/Macros/Python_Guide/Introduction#Installation – Jim K Mar 17 '20 at 15:12
  • Thanks Jim, I've pretty munch given up trying to get this to work. I can get python working fine when I'm running the script from inside LibreOffice, but as soon as I try to use an IDE, I get pyuno errors and Com errors. I've followed the examples in the documents suggested but no luck. All the information seems a little dated. I haven't been able to find anyone who has actual been able to do it. – Jeff O Mar 18 '20 at 01:07
  • Hi Jim, I couldn't just give up on getting this working. I Kept working on it and figured it out. Have a look at my solution. Any feed back will be appreciated. – Jeff O Apr 09 '20 at 16:26

2 Answers2

16

I had the same problem and I solved it like this:

  • install libreoffice in Ubuntu 20:
sudo apt update
sudo apt install libreoffice --no-install-recommends
  • check if uno is accessible (in my case this is all I've done):
python3
Python 3.8.5 (default, Jul 28 2020, 12:59:40)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import uno
>>> print(uno.__file__)
/usr/lib/python3/dist-packages/uno.py
>>> exit()
  • start libreoffice process with opened socket (more info here - page 117):
/usr/lib/libreoffice/program/soffice.bin --headless --invisible --nocrashreport --nodefault --nofirststartwizard --nologo --norestore --accept='socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext'
  • open another terminal and enter python3 interactive mode:
import uno
local_ctx = uno.getComponentContext()
smgr_local = local_ctx.ServiceManager
resolver = smgr_local.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", local_ctx)
url = "uno:socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext"
  • now try to connect:
uno_ctx = resolver.resolve(url)
  • if succeeded, uno context is all you need
  • instantiate Desktop (the root frame for all applications - Calc, Writer, Draw, ...)
uno_smgr = uno_ctx.ServiceManager
desktop = uno_smgr.createInstanceWithContext("com.sun.star.frame.Desktop", uno_ctx )
  • create a new Calc document (private:factory/scalc):
PropertyValue = uno.getClass('com.sun.star.beans.PropertyValue')
inProps = PropertyValue( "Hidden" , 0 , True, 0 ), # this is a tuple
document = desktop.loadComponentFromURL("private:factory/scalc", "_blank", 0, inProps )
  • play with the document:
sheets = document.getSheets()
sheet_1 = sheets[0]
cell_1 = sheet_1.getCellByPosition(1,1)
value = cell_1.setString('Hello World!')
  • save it and check the results:
# UNO requires absolute paths
import os
path = os.path.abspath('./testfile.xls')
uno_url = uno.systemPathToFileUrl(path)
# Filters used when saving document.
# https://github.com/LibreOffice/core/tree/330df37c7e2af0564bcd2de1f171bed4befcc074/filter/source/config/fragments/filters
filter = uno.createUnoStruct('com.sun.star.beans.PropertyValue')
filter.Name = 'FilterName'
filter.Value = 'Calc MS Excel 2007 XML'
filters = (filter,)
# use filters = () to save file as *.ods

document.storeToURL(uno_url, filters)
  • don't forget to terminate the process:
desktop.terminate()
hyper-neutrino
  • 5,272
  • 2
  • 29
  • 50
Cosmin Dinu
  • 171
  • 1
  • 4
7

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.

David Medinets
  • 5,160
  • 3
  • 29
  • 42
Jeff O
  • 133
  • 1
  • 1
  • 7