やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

LibreOffice CalcのPythonマクロを端末でバックグラウンド実行する

 UIを立ち上げることなくodsファイルを書き換える。

成果物

コード

run.sh

soffice --calc --headless "--accept=pipe,name=librepipe;urp;" & {
    sleep 1
    ./run_from_terminal.py
    jobs
    kill %%
}

run_from_terminal.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import uno
import unohelper
from pythonscript import ScriptContext
import os.path

def connect_script_context(host='localhost', port='2002', namedpipe=None):
    UNO_RESOLVER = "com.sun.star.bridge.UnoUrlResolver"
    UNO_DESKTOP = "com.sun.star.frame.Desktop"
    localCtx = uno.getComponentContext()
    localSmgr = localCtx.ServiceManager
    resolver = localSmgr.createInstanceWithContext(UNO_RESOLVER, localCtx)
    if namedpipe is None:
        uno_string = 'uno:socket,host=%s,port=%s;urp;StarOffice.ComponentContext' % (host, port)
    else:
        uno_string = 'uno:pipe,name=%s;urp;StarOffice.ComponentContext' % namedpipe
    ctx = resolver.resolve(uno_string)
    smgr = ctx.ServiceManager
    XSCRIPTCONTEXT = ScriptContext(ctx,
                                   smgr.createInstanceWithContext(UNO_DESKTOP, ctx),
                                   None)
    return XSCRIPTCONTEXT

file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test.ods')
url  = unohelper.systemPathToFileUrl(file)
XSCRIPTCONTEXT = connect_script_context(namedpipe='librepipe')
desktop = XSCRIPTCONTEXT.getDesktop()
doc = desktop.loadComponentFromURL(url, "_blank", 0, ())
sheet = doc.getSheets().getByIndex(0)
sheet.getCellByPosition(0,0).Value = 999
doc.store()

解説

.sh

 sofficeLibreOfficeを起動する。その際、バックグラウンドかつ名前付きパイプでデータ仲介する。

 シェルにて&で並列実行する。LibreOfficeを起動したまま、Pythonマクロを実行する。最後に現在の並列実行中ジョブをkillする。

soffice --calc --headless "--accept=pipe,name=librepipe;urp;" & {
    sleep 1
    ./run_from_terminal.py
    jobs
    kill %%
}

.py

 Pythonからマクロを実行するためにはまずScriptContextインスタンスを取得する。

run_from_terminal.py

import uno
import unohelper
from pythonscript import ScriptContext

def connect_script_context(host='localhost', port='2002', namedpipe=None):
    UNO_RESOLVER = "com.sun.star.bridge.UnoUrlResolver"
    UNO_DESKTOP = "com.sun.star.frame.Desktop"
    localCtx = uno.getComponentContext()
    localSmgr = localCtx.ServiceManager
    resolver = localSmgr.createInstanceWithContext(UNO_RESOLVER, localCtx)
    if namedpipe is None:
        uno_string = 'uno:socket,host=%s,port=%s;urp;StarOffice.ComponentContext' % (host, port)
    else:
        uno_string = 'uno:pipe,name=%s;urp;StarOffice.ComponentContext' % namedpipe
    ctx = resolver.resolve(uno_string)
    smgr = ctx.ServiceManager
    XSCRIPTCONTEXT = ScriptContext(ctx,
                                   smgr.createInstanceWithContext(UNO_DESKTOP, ctx),
                                   None)
    return XSCRIPTCONTEXT

XSCRIPTCONTEXT = connect_script_context(namedpipe='librepipe')

 connect_script_contextの引数namedpipeには、シェルのsofficeコマンドの--accept=pipe,name=で渡した名前付きパイプと同じ値librepipeを渡す。

 次に、XSCRIPTCONTEXTからセルに値をセットするまで。

desktop = XSCRIPTCONTEXT.getDesktop()
doc = desktop.loadComponentFromURL(url, "_blank", 0, ())
sheet = doc.getSheets().getByIndex(0)
sheet.getCellByPosition(0,0).Value = 999

 最後に、所定のodsファイルを開いてから保存する方法。

import os.path

file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test.ods')
url  = unohelper.systemPathToFileUrl(file)
...
doc.store()

所感

 LibreOfficeとマクロをバックグラウンドで起動した。既存のodsファイルを自動的に変更することができた。

 次は指定したパスにodsファイルを作成するマクロを書きたい。

 それにしても、ほぼググってコピペしただけなのでAPIがさっぱりわからない。たぶんココをみればいいんだろうけど。概念がわからない。DesctopとかComponentContextとか何なの?

情報源

対象環境

$ uname -a
Linux raspberrypi 4.19.97-v7l+ #1294 SMP Thu Jan 30 13:21:14 GMT 2020 armv7l GNU/Linux