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

1

9

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:

Knawel answered 27/4, 2020 at 10:48 Comment(0)
C
9

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.

Cathode answered 27/4, 2020 at 18:54 Comment(4)
Thank you for your detailed answer! I'm going to try this. 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: oh ok, this is what I was trying indeed! In fact I wanted to use my standard python (not the one shipped with LibreOffice), because I already have all my libraries there, etc.Knawel
Yes, I understand why you would want to do that, but it's probably not a good idea.Cathode
@JimK please share the much more difficult wayAllodium
@deostroll: It's linked in the question, where the OP wrote "I've also tried this (unsuccessfully)."Cathode

© 2022 - 2024 — McMap. All rights reserved.