LibreOffice5(1)officehelper.bootstrap()を使う

2015-06-21

旧ブログ

t f B! P L

LibreOffice(27)Java-UNO BootstrapはWindows7 64bitでは動かない?ではJavaのbootstrap()メソッドを使いましたが、Pythonでも同じ働きをするbootstrap()メソッドが用意されていました。linuxBean14.04とWindows7 64bitで動かしてみます。

PythonスクリプトからLibreOfficeを起動するofficehelper.bootstrap()


まずはlinuxBean14.04(25)NetBeans8とLibreOffice5のインストールのlinuxBean14.04とLibreOffice5.0.0.0beta1で行います。

「python bootstrap」で検索するとLibreOffice PART17の81がでてきてofficehelperとは何だろな、と思いながらfaq/5/471 - OpenOffice.org Q&Aを読んでいてuno.pyと同じ/opt/libreofficedev5.0/programフォルダにofficehelper.pyがあることを知りました。

officehelper.pyを読んでみるとPythonからLibreOffieをランダムなパイプ名でpipe接続で起動しています。(どうしてランダムなパイプ名にしないといけないでしょう?重複していたときにやり直しでうまくいくようにするため?)

pipe接続はLibreOffice(4)PyCharmからLibreOfficeを動かす(オートメーション)ででてきたのと同じ方法ですので、LibreOffice(5)PythonでLibreOfficeが動く仕組み:UNOのオートメーションでLibreOfficeを動かしていることになります。

officehelper.bootstrap()の戻り値はLibreOfficeのComponentContextになっています。

ということでLibreOffice(4)PyCharmからLibreOfficeを動かす(オートメーション)のunopy.pyの17行目から19行目の処理をofficehelper.pyにまかせることにしてunopyboot.pyというのを作りました。
#! #unopyboot.py
# -*- coding: utf-8 -*-
import unohelper
import officehelper
from com.sun.star.script.provider import XScriptContext
class ScriptContext(unohelper.Base, XScriptContext):
    def __init__(self, ctx):
        self.ctx = ctx
    def getComponentContext(self):
        return self.ctx
    def getDesktop(self):
        return self.ctx.getServiceManager().createInstanceWithContext("com.sun.star.frame.Desktop", self.ctx)
    def getDocument(self):
        return self.getDesktop().getCurrentComponent()
def connect():
    ctx = None
    try:
        ctx = officehelper.bootstrap()
        if ctx:
            return ScriptContext(ctx)
    except:
        pass
    return None
これを~/.config/libreoffice/4/user/Scripts/pythonフォルダにいれて、さらに以下のboot_Hello.pyを作りました。
#! #boot_Hello.py
# -*- coding: utf-8 -*-
def HelloWorld_Writer():
    doc = XSCRIPTCONTEXT.getDesktop().loadComponentFromURL("private:factory/swriter", "_blank", 0, ())
    doc.getText().setString("Hello World!")
if __name__ == "__main__":
    import unopyboot
    XSCRIPTCONTEXT = unopyboot.connect()
    if not XSCRIPTCONTEXT:
        print("Failed to connect.")
        import sys
        sys.exit(0)
    HelloWorld_Writer()
/opt/libreofficedev5.0/program/python ~/.config/libreofficedev/4/user/Scripts/python/test/boot_Hello.py
と実行するとWriterが立ち上がって「Hello World!」と入力されているはずです。

Windows7 64bitではofficehelper.pyの修正が少し必要


(2018.8.17追記。LibreOffice6では、Windowsと同じビットのLibreOfficeをインストールするとLibreOfficeのインストールパスは常にC:\Program Files\LibreOfficeになるのでここで書いているパスにカッコがある問題は起きなくなりました。ctx = officehelper.bootstrap()でコンポーネントコンテクストも取得できました。


動作は問題ないのですが、たとえばC:\_VirtualBox\カルテ\toolsで実行するとこのようなエラーがでてきます。
このエラーの出どころを探すと、officehelper.pyのos.spawnv(os.P_NOWAIT, sOffice, cmdArray)で発生していました。
cmdArrayはコマンドの引数のタプルで、cmdArray[0]はプログラムのmain()に渡されるargv[0]に該当します(16.1. os — 雑多なオペレーティングシステムインタフェース — Python 3.3.6 ドキュメント)。
officehelper.pyではcmdArray[0]にはsoffice.exeへのフルパスが与えられていますが、それにスペースが入ってるために上記のエラーダイアログがでるようです。エラーダイアログにでているようなパスはコード内にはどこにもでてこないのですが、C:\_VirtualBox\カルテ\toolsとC:\Program Files\LibreOffice\program\soffice.exeが合体しています。これはカンマ以外にスペースも要素の区切りと判断されているからのようです。
cmdArray[0]は空文字では動きませんが、適当な文字列"soffice"などを入れると上記のエラーダイアログがでてこなくなりました。
ちなみに、subprocess.run(cmdArray)ではHDDへのアクセスランプがついたままそこで終わらなくなりました。)

今度はWindows7 64bitとLibreOffice4.2.6.2でやります。

Windows7 64bitではLibreOfficeの実行ファイルsoffice.exeがC:\Program Files (x86)\LibreOffice 4\programというパスにインストールされます。

LibreOffice(17)setsdkenv_windows.batで苦労したように、このパスにある丸括弧への対処がかなりやっかいです。

LibreOffice(27)Java-UNO BootstrapはWindows7 64bitでは動かない?の原因もこのパスに含まれる丸括弧だと思っています。


とりあえずlinuxBeansのときと同じようにやってみると、このようなエラーが出た上にLibreOfficeがフリーズしてしまいました。

LibreOffice(2)Pythonの統合開発環境PyCharmのインストールでインストールしたPyCharmのデバッグモードでステップ実行したところ、officehelper.pyの58行目からexceptに飛んでいるとわかりました。
        # Start the office process, don't check for exit status since an exception is caught anyway if the office terminates unexpectedly.
        cmdArray = (sOffice, "--nologo", "--nodefault", "".join(["--accept=pipe,name=", sPipeName, ";urp;"]))
        os.spawnv(os.P_NOWAIT, sOffice, cmdArray)
os.spawnv(mode, path, args)は2番目の引数にOSで実行するコマンドのパス、3番目以降の引数にコマンドとそれにつづけて引数を続けます。


このsOfficeにコマンドが入っているわけですが、このパスに含まれている「(x86)」が不具合の原因とにらみます。

コマンドウィンドウで、'C:\\Program Files (x86)\\LibreOffice 4\\program\\soffice.exe'としても、「ファイル名、ディレクトリ名、またはボリューム ラベルの構文が間違っています。」といわれて実行できません。

"C:\\Program Files (x86)\\LibreOffice 4\\program\\soffice.exe"はちゃんとLibreOfficeが起動します。
        if platform.startswith("win"):
            #sOffice += ".exe"
            sOffice = "\"" + sOffice + ".exe\"" 
ということでsOfficeの変数の値をダブルクォーテーション「"」で囲ってみました。

だめです。「[Errno 22] Invalid argument」といわれてしまいます。

ダブルクォーテーションで囲んだものはos.spawnv()の引数にできないようです。

結局環境変数PATHを短い名前の形式に変換すると同じようにして短い名前の形式を使うことにしました。
@ECHO OFF
rem short_path.bat
echo %~s1
pause
短いファイル名はこのバッチファイルshort_path.batに、パスを知りたいファイルをドロップすればわかります。

C:\Program Files (x86)\LibreOffice 4\program\soffice.exeの短い形式の名前はC:\PROGRA~2\LIBREO~1\program\soffice.exeでした。
        if platform.startswith("win"):
            #sOffice += ".exe"
            #sOffice = "\"" + sOffice + ".exe\""
            sOffice = "C:\PROGRA~2\LIBREO~1\program\soffice.exe"
これで解決しました。汎用的な解決法ではありませんけど。

本当にWindows7 64bitのパスに含まれる丸括弧問題はやっかいです。

参考にしたサイト


LibreOffice PART17
2ちゃんねるスレッド。結構勉強になります。

faq/5/471 - OpenOffice.org Q&A
以前にも読んだことがある記事のはずですが、そのときはofficehelper.pyが気にとまりませんでした。

os.spawnv(mode, path, args)
いまは17.5. subprocessを使う方がよいそうです。(17.5.4.4. os.spawn 関数群を置き換える)

次の関連記事:LibreOffice5(2)コンテクストメニューをカスタマイズする例

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ