LibreOffice Calc で内部から自作ライブラリの Python コードを使う

ちまちまと LibreOffice Calc を UNO API 経由でアクセスするライブラリ作成中です。UNO API の構造が複雑怪奇なのは、私が Excel の DOM 構造に慣れているからだと思うのですが、いやぁ、なかなか想像に至らないのが難点です。

さて、ちらほら検索すると Calc を Python でアクセスするという記事はいくつか見つかるのですが、Excel VBA のように使えないのが難点です。Excel VBA のように、「ボタンを押したらマクロが動いて、計算をチェックする」なんてのは、どうやるんでしょうね?

まず、Calc にボタンを配置します。「ボタン」というのがないんで、四角の Shape を使っています。

ボタンを右クリックして「マクロの割り当て」を選択します。

マイマクロの中から目的の Python 関数を選んで(ここでは sample001.py にある write_hello_to_cell 関数)ダブルクリックします。ここで割り当てができます。

この状態で、マウスでボタンを押すと “A1” に時刻が表示されます。

Python マクロの位置は?

Calc ファイルにマクロを埋め込むことができるのですが、編集しやすくするために、ローカルスクリプトの位置に保存します。

C:\Users\masuda\AppData\Roaming\LibreOffice\4\user\Scripts\python\

Windows の場合は、ログインユーザーの AppData の下にあります。python フォルダーがない場合は、作成してください。
LibreOffice 自体は Python マクロを編集する機能がないので、vscode で編集します。

Python マクロの書き方

import datetime as dt

def write_hello_to_cell():
    # LibreOffice のコンテキストを取得
    desktop = XSCRIPTCONTEXT.getDesktop()
    doc = desktop.getCurrentComponent()

    # Calc 以外の文書で実行された場合は無視
    if not hasattr(doc, "Sheets"):
        return

    sheet = doc.Sheets[0]  # 最初のシート
    cell = sheet.getCellByPosition(0, 0)  # A1

    cell.String = "Hello, " + dt.datetime.now().strftime("%H:%M:%S")

g_exportedScripts = (
    write_hello_to_cell,
)

LibreOffice の Python マクロを書くときにいくつかルールがあって、

  1. XSCRIPTCONTEXT から、最初の desktop, doc などを取得する
  2. Python は C:\Program Files\LibreOffice\program\python.exe 固定となる
  3. マクロを登録する関数は g_exportedScripts に追加しておく

Python が LibreOffice のものに固定されているため pip などのライブラリ追加がうまくいかないかもしれません。このあたりは、後で調べてみるのですが、バージョンとしては 3.11.14 なので、それなりに使えるでしょう。Python に詳しくないのでよくわからないのですが。

コード自体は AI エージェント(Copilot、ChatGPTなど)に作って貰うことができます。
最終的には Sheet を扱うことになるので、そのあたりのコード補完ができたらいいのですが、すべてが Any 型になってしまっているのでここをなんとかしないといけません。
で、現在、コード補完用のクラスを作っているわけです。

独自ライブラリの読み込みはできるのか?

できます。ちょっと、コツがいるようですが、下記のような excellike というライブラリを作って、読み込ませることができます。

import パスを追加しないといけないのが難点ですが(これは後でもうちょっと調査)、通常の Python マクロと同様に書けます。

"""LibreOffice Calc sample macro."""

import inspect
import os
import sys

# Ensure this script's directory is importable so the local excellike package resolves
BASE_DIR = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
if BASE_DIR not in sys.path:
    sys.path.append(BASE_DIR)

from excellike.calc.sheet import Sheet

def excellike_write_hello_to_cell():
    # LibreOffice のコンテキストを取得
    desktop = XSCRIPTCONTEXT.getDesktop()
    doc = desktop.getCurrentComponent()

    # Calc 以外の文書で実行された場合は無視
    if not hasattr(doc, "Sheets"):
        return

    sheet = doc.Sheets[0]  # 最初のシート
    sheet = Sheet(sheet)
    sheet.setText(0, 0, "Hello from Python!")



g_exportedScripts = (
    excellike_write_hello_to_cell,
)

ここでは Sheet という独自のラッパークラスを作っています。

from __future__ import annotations
from ..core import InterfaceNames, UnoObject

class Sheet(UnoObject):
    def __init__(self, obj):
        self._obj = obj

    def setText(self, column: int, row: int, text: str) -> None:
        cell = self._obj.getCellByPosition(column, row)
        # LibreOffice Calc cells expose setString/String, not setText
        if hasattr(cell, "setString"):
            cell.setString(text)
        else:
            cell.String = text

マクロを直接実行する

マクロを直接実行する場合は、「ツール」→「マクロ」→「マクロの実行」を選択します。

先の Scripts/python フォルダーにあるマクロが選択できます。

Calc の内から Python マクロを使う時は、XSCRIPTCONTEXT
Calc の外から Python マクロを使う時は、UNO API をポート経由で、

ということになっていて、その後の操作は全く同じです。
なので、この初期処理部分を統一化しておけば、マクロ開発も楽なわけですが。ひとまず、Excel VBA エディタでちまちま開発するよりは、VSCode + Python で開発するのが楽なのでは?というぐらいまでは到達しています。ただし、そのまま UNO API のオブジェクトを使うと全て Any 型なので、入力候補が全くでません。
このあたりのサポートは必須ですね。

カテゴリー: 開発, LibreOffice パーマリンク