Search code examples
pythonlibreofficeopenoffice.orguno

How to use LibreOffice API (UNO) with Python + Windows?


This question is focused on Windows + LibreOffice + Python 3.

I've installed LibreOffice (6.3.4.2), also pip install unoconv and pip install unotools (pip install uno is another unrelated library), but still I get this error after import uno:

ModuleNotFoundError: No module named 'uno'

More generally, and as an example of use of UNO, how to open a .docx document with LibreOffice UNO and export it to PDF?

I've searched extensively on this since a few days, but I haven't found a reproducible sample code working on Windows:


Solution

  • In order to interact with LibreOffice, start an instance listening on a socket. I don't use COM much, but I think this is the equivalent of the COM interaction you asked about. This can be done most easily on the command line or using a shell script, but it can also work with a system call using a time delay and subprocess.

    chdir "%ProgramFiles%\LibreOffice\program\"
    start soffice -accept=socket,host=localhost,port=2002;urp;
    

    Next, run the installation of python that comes with LibreOffice, which has uno installed by default.

    "C:\Program Files\LibreOffice\program\python.exe"
    >> import uno
    

    If instead you are using an installation of Python on Windows that was not shipped with LibreOffice, then getting it to work with UNO is much more difficult, and I would not recommend it unless you enjoy hacking.

    Now, here is all the code. In a real project, it's probably best to organize into classes, but this is a simplified version.

    import os
    import uno
    from com.sun.star.beans import PropertyValue
    def createProp(name, value):
        prop = PropertyValue()
        prop.Name = name
        prop.Value = value
        return prop
    
    localContext = uno.getComponentContext()
    resolver = localContext.ServiceManager.createInstanceWithContext(
        "com.sun.star.bridge.UnoUrlResolver", localContext)
    ctx = resolver.resolve(
        "uno:socket,host=localhost,port=2002;urp;"
        "StarOffice.ComponentContext")
    smgr = ctx.ServiceManager
    desktop = smgr.createInstanceWithContext(
        "com.sun.star.frame.Desktop", ctx)
    dispatcher = smgr.createInstanceWithContext(
        "com.sun.star.frame.DispatchHelper", ctx)
    filepath = r"C:\Users\JimStandard\Desktop\Untitled 1.docx"
    fileUrl = uno.systemPathToFileUrl(os.path.realpath(filepath))
    uno_args = (
        createProp("Minimized", True),
    )
    document = desktop.loadComponentFromURL(
        fileUrl, "_default", 0, uno_args)
    uno_args = (
        createProp("FilterName", "writer_pdf_Export"),
        createProp("Overwrite", False),
    )
    newpath = filepath[:-len("docx")] + "pdf"
    fileUrl = uno.systemPathToFileUrl(os.path.realpath(newpath))
    try:
        document.storeToURL(fileUrl, uno_args)  # Export
    except ErrorCodeIOException:
        raise
    try:
        document.close(True)
    except CloseVetoException:
        raise
    

    Finally, since speed is a concern, using a listening instance of LibreOffice can be slow. To do this faster, move the code into a macro. APSO provides a menu to organize Python macros. Then call the macro like this:

    soffice "vnd.sun.star.script:myscript.py$name_of_maindef?language=Python&location=user"
    

    In macros, obtain the document objects from XSCRIPTCONTEXT rather than the resolver.