LibreOffice5(116)埋め込みマクロからPythonモジュールをロードする方法

2017-12-24

旧ブログ

t f B! P L
マイマクロフォルダにあるマクロであればそのマクロがあるファイルと同じフォルダにあるpythonpathフォルダ内のモジュールがインポートできます。しかし埋め込みマクロではpythonpathフォルダのモジュールがインポートできないので代替策を考えます。

前の関連記事:LibreOffice5(115)イベントが呼ばれる順を調べるマクロ


httpプロトコールからモジュールをロードするPython Cookbookの例


Python Cookbook10.11.loading_modules_from_a_remote_machine_using_import_hooksの例をまずやります。

最初に読みとられる側のhttpサーバーを起動します。

python3 -m http.server 15000

Terminalからtestcodeディレクトリに移動してそこでこのコマンドを実行します。
pq@pq-VirtualBox:~/git/python-cookbook/PythonCookbook/src/10/10.11.loading_modules_from_a_remote_machine_using_import_hooks/testcode$ python3 -m http.server 15000
Serving HTTP on 0.0.0.0 port 15000 ...
127.0.0.1 - - [24/Dec/2017 13:06:00] "GET /fib.py HTTP/1.1" 200 -
127.0.0.1 - - [24/Dec/2017 13:06:00] "GET /spam.py HTTP/1.1" 200 -
linuxBean14.04だとこのような結果になります。

これで準備完了です。

あとは別のTerminalやPyDevパッケージでexplicit_load.pyを実行すれば次のような結果になります。
I'm fib
89
I'm spam
Hello Guido
<module 'http://localhost:15000/fib.py' from 'http://localhost:15000/fib.py'>
<module 'http://localhost:15000/spam.py' from 'http://localhost:15000/spam.py'>
Python 3.4.3での実行結果です。

httpプロトコール越しに取得したモジュールが実行されていることがわかります。

Python Cookbookの例のimpモジュールを置き換える


Python Cookbookの例のexplicit_load.pyで使っているimpモジュールはPython3.4で撤廃と書いてあるので代わりのモジュールに置き換えます。
from types import ModuleType
import urllib.request
import sys
def load_module(url):
 u = urllib.request.urlopen(url)
 source = u.read().decode('utf-8')  # モジュールのソースをテキストで取得。
 mod = sys.modules.setdefault(url, ModuleType(url))  # 新規モジュールをsys.modulesに挿入。
 code = compile(source, url, 'exec')  # urlを呼び出し元としてソースコードをコンパイルする。
 mod.__file__ = url  # モジュールの__file__を設定。
 mod.__package__ = ''  # モジュールの__package__を設定。
 exec(code, mod.__dict__)  # モジュールの名前空間を設定する。
 return mod
if __name__ == '__main__':
 fib = load_module('http://localhost:15000/fib.py')
 print(fib.fib(10))
 spam = load_module('http://localhost:15000/spam.py')
 spam.hello('Guido')
 print(fib)
 print(spam)
imp.new_module(name)に書いてある通りに、class types.ModuleType(name, doc=None)で置き換えるとうまくいきました。

7行目で辞書sys.modulesにurlの名前のモジュールを挿入しています。

sys.modulesの辞書の項目が一つ増えているのはわかりましたが、PyDevのデバッガでなぜかキーをみつけることができませんでした。

この手法は単純なモジュールだけでしか機能しないようですが、とりあえずこの方法で埋め込みマクロからモジュールのインポートを実践してみます。

ドキュメント内のモジュールをロードするマクロ

import sys
from types import ModuleType
consts = None  # ドキュメントに埋め込んだモジュール。
def macro(documentevent=None):  # 引数は文書のイベント駆動用。  
 doc = XSCRIPTCONTEXT.getDocument() if documentevent is None else documentevent.Source  # ドキュメントのモデルを取得。 
 controller = doc.getCurrentController()  # コントローラの取得。
 ctx = XSCRIPTCONTEXT.getComponentContext()  # コンポーネントコンテクストの取得。
 smgr = ctx.getServiceManager()  # サービスマネージャーの取得。
 simplefileaccess = smgr.createInstanceWithContext("com.sun.star.ucb.SimpleFileAccess", ctx)  # SimpleFileAccess
 modulefolderpath = getModuleFolderPath(ctx, smgr, doc)  # 埋め込みモジュールフォルダへのURLを取得。
 global consts
 if consts is None:
  consts = load_module(simplefileaccess, "/".join((modulefolderpath, "consts.py")))  # import constsと同等。
 s = consts.LISTSHEET["name"]
 sheet = controller.getActiveSheet()
 sheet["A1"].setString(s)
def load_module(simplefileaccess, modulepath):  # modulepathのモジュールを取得。
 inputstream = simplefileaccess.openFileRead(modulepath)  # モジュールファイルからインプットストリームを取得。
 dummy, b = inputstream.readBytes([], inputstream.available())  # simplefileaccess.getSize(module_tdocurl)は0が返る。
 source = bytes(b).decode("utf-8")  # モジュールのソースをテキストで取得。
 mod = sys.modules.setdefault(modulepath, ModuleType(modulepath))  # 新規モジュールをsys.modulesに挿入。
 code = compile(source, modulepath, 'exec')  # urlを呼び出し元としてソースコードをコンパイルする。
 mod.__file__ = modulepath  # モジュールの__file__を設定。
 mod.__package__ = ''  # モジュールの__package__を設定。
 exec(code, mod.__dict__)  # 実行してモジュールの名前空間を取得。
 return mod
def getModuleFolderPath(ctx, smgr, doc):  # 埋め込みモジュールフォルダへのURLを取得。
 transientdocumentsdocumentcontentfactory = smgr.createInstanceWithContext("com.sun.star.frame.TransientDocumentsDocumentContentFactory", ctx)
 transientdocumentsdocumentcontent = transientdocumentsdocumentcontentfactory.createDocumentContent(doc)
 tdocurl = transientdocumentsdocumentcontent.getIdentifier().getContentIdentifier()  # ex. vnd.sun.star.tdoc:/1 
 return "/".join((tdocurl, "Scripts/python/pythonpath"))  # 開いているドキュメント内の埋め込みマクロフォルダへのパス。
g_exportedScripts = macro, #マクロセレクターに限定表示させる関数をタプルで指定。
18行目でロードしたいモジュールのファイルのインプットストリームを取得してそこから内容をテキストにしてコンパイルして実行しています。

Embeddedmodule.ods

このマクロを埋め込んだCalcドキュメントです。

ドキュメント内のフォルダ構成はこうなっています。

embeddedmacro.pyが埋め込みマクロファイルです。

このマクロからconsts.pyをロードしています。

埋め込みマクロを実行するにはマクロセレクターからEmbeddedmodule.ods内のembeddedmacroのmacroを実行します。

うまくいくとconsts.pyから辞書をインポートしてA1セルに値を出力します。

マクロセレクターからはpythonpathフォルダは見えません。

この方法はimport文でロードする方法と違って重複ロードなどはチェックしていないので、使い方に気をつけないといけません。

import文でロードする方法はうまくいかず


(2018.1.13追記LibreOffice5(120)ドキュメントに埋め込んだモジュールをインポートするでうまくできました。)

Python Cookbookにはimport文でロードできるようにする方法が二つ載っていました。

urlimport.pyはその二つともに対応できるようになっています。

埋め込みマクロからこのモジュールをインポートすることはできないので、sys.meta_pathを変更する方法をマクロファイルに詰め込んで実行してみましたがうまくいきませんでした。

pydevdのデバッグすらできず、PyUNO自体が動かなくなってOSを再起動しないといけませんでした。

次の関連記事:LibreOffice5(117)コンテナウィンドウのサービスとインターフェイスの一覧

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ