Search code examples
pythonmacroswxpythonlibreoffice

How to call an existing LibreOffice python macro from a python script


Currently I call an existing existing LibreOffice macro with this:

def OnLOtimestamp(self):
        try:
            pid= Popen(['lowriter '"'"'vnd.sun.star.script:fs2TimeStamp.py$fs2_TimeStamp?language=Python&location=user'"'"],shell=True).pid
        except OSError, e:
            self.notify_show("Timestamp Error",str(e))
        self.ma2.SetLabel("Macro timestamp")
        self.database['Time_stamp'] = self.database['Time_stamp'] + 1

The key bit being the Popen call, where the macro name is fs2TimeStamp.py and the function is fs2_TimeStamp but this feels like a cop out and I would rather perform a direct call via Uno. My research suggests that I may well need to use MasterScriptProvider, XscriptProvider and XscriptInvocation but trying to decipher the Uno API is like swimming through treacle. Has anybody got a code sample of calling an existing macro in Libreoffice, using Uno?

Edit:
So far the answer appears to be No! This is the current state of play.

#!/usr/bin/python3
# -*- coding: utf-8 -*-
##
# a python script to run a libreoffice python macro externally
#
import uno
from com.sun.star.connection import NoConnectException
from com.sun.star.uno  import RuntimeException
from com.sun.star.uno  import Exception
from com.sun.star.lang import IllegalArgumentException
def test2(*args):
    localContext = uno.getComponentContext()
    localsmgr = localContext.ServiceManager
    resolver = localsmgr.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext )
    try:
        ctx = resolver.resolve("uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext")
    except NoConnectException as e:
        print ("LibreOffice is not running or not listening on the port given - ("+e.Message+")")
        return
    except IllegalArgumentException as e:
        print ("Invalid argument given - ( "+ e.Message+ ")")
        return
    except RuntimeException as e:
        print ("An unknown error occurred: " + e.Message)
        return

    servmgr = ctx.ServiceManager
    desktop = servmgr.createInstanceWithContext( "com.sun.star.frame.Desktop",ctx)
    model = desktop.getCurrentComponent()
#    scriptP = model.getScriptProvider()
#    print("scriptP", scriptP)
    scriptx = model.getScriptProvider().getScript('vnd.sun.star.script:fs2TimeStamp.py$fs2_TimeStamp?language=Python&location=user')
    print("scriptx", scriptx)
    try:
        scriptx.invoke("",0,0)
    except IllegalArgumentException as e:
        print ("The command given is invalid ( "+ e.Message+ ")")
        return
    except RuntimeException as e:
        print("An unknown error occurred: " + e.Message)
        return
    except Exception as e:
        print ("Script error ( "+ e.Message+ ")")
        print(e)
        return
    except:
        print("Error")
    return(None)

test2()

This code works happily when invoked as a macro within Libreoffice and scriptx prints out as:

scriptx <pythonscript.PythonScript object at 0x7fa2879c42e8>

however when run from the command line the script does nothing and scriptx prints out as:

scriptx pyuno object (com.sun.star.script.provider.XScript)0x1e749d8{, supportedInterfaces={com.sun.star.lang.XTypeProvider,com.sun.star.script.provider.XScript}}

So either getScriptProvider or getScript are not being provided with something that they require. I am currently at a loss as to what is missing and yet, I feel in my bones that I'm close to a solution.

Can anyone see where I have made a mistake?


Solution

  • Finally, I have a working solution. Ding! Dong!

    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    ##
    # a python script to run a libreoffice python macro externally
    #
    import uno
    from com.sun.star.connection import NoConnectException
    from com.sun.star.uno  import RuntimeException
    from com.sun.star.uno  import Exception
    from com.sun.star.lang import IllegalArgumentException
    def test2(*args):
        localContext = uno.getComponentContext()
        localsmgr = localContext.ServiceManager
        resolver =  localsmgr.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext )
        try:
            ctx = resolver.resolve("uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext")
        except NoConnectException as e:
            print ("LibreOffice is not running or not listening on the port given - ("+e.Message+")")
            return
        msp = ctx.getValueByName("/singletons/com.sun.star.script.provider.theMasterScriptProviderFactory")
        sp = msp.createScriptProvider("")
        scriptx = sp.getScript('vnd.sun.star.script:fs2TimeStamp.py$fs2_TimeStamp?language=Python&location=user')
        try:
            scriptx.invoke((), (), ())
        except IllegalArgumentException as e:
            print ("The command given is invalid ( "+ e.Message+ ")")
            return
        except RuntimeException as e:
            print("An unknown error occurred: " + e.Message)
            return
        except Exception as e:
            print ("Script error ( "+ e.Message+ ")")
    
    return(None)
    
    test2()
    

    Note: For clarity the existing python script is called fs2TimeStamp.py, it contains 1 (one) function defined as def fs2_TimeStamp(*args):
    See the line:

    scriptx = sp.getScript('vnd.sun.star.script:fs2TimeStamp.py$fs2_TimeStamp?language=Python&location=user')   
    

    and it is stored in $HOME/.config/libreoffice/4/user/Scripts/python

    For this solution to work, libreoffice must be running in listening mode, so start libreoffice with a command like:

    soffice "--accept=socket,host=127.0.0.1,port=2002,tcpNoDelay=1;urp;" --writer --norestore
    

    OR

    nohup soffice "--accept=socket,host=127.0.0.1,port=2002,tcpNoDelay=1;urp;" --writer --norestore &
    

    Alternatively you could use the more direct method (for writer in this example):

    lowriter "--accept=socket,host=127.0.0.1,port=2002,tcpNoDelay=1;urp"
    

    OR

    nohup lowriter "--accept=socket,host=127.0.0.1,port=2002,tcpNoDelay=1;urp" &
    

    Also note that you must run the script with python3