LibreOffice extensions with Python – part 1

Ever wondered how to create a LibreOffice extension? Here I discuss how to do that via Python programming language. It is possible to run and debug the resulting Python code in an IDE, and then package the content as an extension.

LibreOffice Extensions with Python

If you have used LibreOffice extensions, you know that many exciting things can be done with extensions. Extensions can open LibreOffice applications, create new documents, read and write text and images inside the documents, and convert them to all possible formats. They can have their own menus and toolbar buttons, and have nice looking GUIs to interact with the users.

To write an extension, the easiest way is to use LibreOffice BASIC language. You can refer to this tutorial for such an approach here:

But with Python, you will have access to a big set of packages, that is one of the many strengths of the Python programming language. You can do almost anything possible with a software with those packages. Furthermore, LibreOffice has its own Python interpreter! In this way, installing and using a Python extension would be much easier.

Handling Context in LibreOffice Extensions

First of all, you should know about context, and you should be able to have that variable to be able to use LibreOffice API.

There can be at least 3 different possibilities for running a Python program with LibreOffice:

  1. Running the Python program with APSO inside LibreOffice
  2. Running the Python program as an extension inside LibreOffice
  3. Running the Python program as a process outside LibreOffice

In each of these possibilities, the way to get the context and use them is different.

Structure of a LibreOffice Extension

Extensions are essentially zip files that have specific files known to LibreOffice inside them. This is the structure of a Python extension:

  • META-INF/ : required folder
    META-INF/manifest.xml: Specification of the script(s), menu/toolbar and language files
  • pkg-description/ : required folder
    pkg-description/pkg-description.en: Description of the extension in text, which can be also in languages other than English
  • registration/: required folder
    registration/license.txt: License of the extension
  • description.xml: Description of the extension in XML format, as displayed in the extension manager
  • main.py: The main script. Then name can be anything but it should
    be specified in the META-INF/manifest.xml

Contents of the Files

Most of the contents of the files are re-usable, so you can use the skeleton extension, and build your extension around that. But, the Python script is important and we will talk about it here.

From the above 3 possible situations for LibreOffice, in order to be able to use the code as extension, you should add these this 2 lines should be in the Python file

g_ImplementationHelper = unohelper.ImplementationHelper()
g_ImplementationHelper.addImplementation(MainJob,
"org.extension.sample.do",("com.sun.star.task.Job",), )

In addition, this import is also required:

from com.sun.star.task import XJobExecutor

Then, a Python class with this definition is needed:

class MainJob(unohelper.Base, XjobExecutor)

The program should have this method:

def trigger(self, args):

To be able to debug the program, the main function should be defined as something like this:

def main():
    try:
        ctx = XSCRIPTCONTEXT
    except NameError:
        ctx = officehelper.bootstrap()
        if ctx is None:
            print("ERROR: Could not bootstrap default Office.")
            sys.exit(1)
    job = MainJob(ctx)
    job.trigger("keywords")


if __name__ == "__main__":
    main()

This is a sample implementation of the MainJob class:

class MainJob(unohelper.Base, XJobExecutor):
    def __init__(self, ctx):
        self.ctx = ctx
        # handling different situations (inside LibreOffice / different process)
        try:
            self.sm = ctx.getServiceManager()
            self.desktop = XSCRIPTCONTEXT.getDesktop()
        except NameError:
            self.sm = ctx.ServiceManager
            self.desktop = self.ctx.getServiceManager().createInstanceWithContext(
                "com.sun.star.frame.Desktop", self.ctx)

And this is a sample trigger() function that opens Writer, and write a sample text consisting of the argument passed to it.

    def trigger(self, args):
        desktop = self.ctx.ServiceManager.createInstanceWithContext(
            "com.sun.star.frame.Desktop", self.ctx)
        model = desktop.getCurrentComponent()
        if not hasattr(model, "Text"):
            model = self.desktop.loadComponentFromURL("private:factory/swriter", "_blank", 0, ())
        text = model.Text
        cursor = text.createTextCursor()
        text.insertString(cursor, "Hello Extension argument -> " + args + "\n", 0)

You can use this structure to create Python extension that you want to create.

A complete extension with the above files is available here, and the plan is to make it available among the other LibreOffice SDK examples:

https://gerrit.libreoffice.org/c/core/+/159938

Final Notes

This year we had an extensive workshop on LibreOffice development in LibreOffice conference 2023. If you want to know more about using LibreOffice API in Python, you can refer to the presentation: