LibreOffice5(11)unoinsp.py:自作IDLへの対応

2015-12-15

旧ブログ

t f B! P L

前の関連記事:LibreOffice5(10)Calcに関数を追加するPython拡張機能の例その1


LibreOffice5(5)unoinsp.pyでAPIリファレンスへのリンクを付けるでもう追加する機能はないと思ったunoinsp.pyですが、com.sun.starパッケージ以外のパッケージがあると処理できないことがわかりました。

unoinsp.pyはUNOオブジェクトのタイプ名の先頭がcom.sun.starであることが前提になっている


LibreOffice5(10)Calcに関数を追加するPython拡張機能の例その1のpython-tokencounter-calc-addin.oxtではcom.sun.star.sheet.AddInサービスにorg.openoffice.addin.sample.XTokenCounterインターフェイスを作成してその実装をしています。

この新しく作ったインターフェイスを調べるためにcom.sun.star.sheet.AddInのインスタンスをunoinsp.pyで調べることにしました。
def obj_test():
    service = "com.sun.star.sheet.AddIn"
    ctx = XSCRIPTCONTEXT.getComponentContext()
    obj = ctx.getServiceManager().createInstanceWithContext(service, ctx)
    import unoinsp
    ins = unoinsp.ObjInsp(XSCRIPTCONTEXT)
    ins.tree(obj)
if __name__ == "__main__":
    import unopy
    XSCRIPTCONTEXT = unopy.connect()
    if not XSCRIPTCONTEXT:
        print("Failed to connect.")
        import sys
        sys.exit(0)
    obj_test()
マイマクロフォルダにこれをobj_test.pyという名前で保存しました。

python-tokencounter-calc-addin.oxtをLibreOfficeの拡張機能マネージャーに登録してLibreOfficeお再起動してからPyCharmでオートメーションで実行してみました。

unoinsp.NoSuchElementException: com.sun.starorg.openoffice.addin.sample.XTokenCounter

このようなエラーがでてきます。
            self.stack = [tdm.getByHierarchicalName("com.sun.star" + i) for i in lst_si]  # TypeDescriptionオブジェクトに変換。"com.sun.star"が必要。
デバッグボタンでobj_test.pyを実行するとunoinsp.pyの87行目で止まりました。


iにはorg.openoffice.addin.sample.XTokenCounterが入っていることがわかります。

これはUNOオブジェクトのタイプ名がすべてcom.sun.starで始まっているという前提でunoinsp.pyを作っているのが原因です。

そのためにcom.sun.starorg.openoffice.addin.sample.XTokenCounterという名前で処理を進めようにしてしまってエラーになっています。

これを改善したいと思います。

(python-tokencounter-calc-addin.oxtをLibreOfficeの拡張機能マネージャーに登録してCalcでtokencount()関数が使える状態になっていてもLibreOfficeを再起動しないとuno.RuntimeException: Binary URP bridge disposed during callというエラーがでてきます。これはURP(UNO remote protocolのエラーですのでオートメーションに起因する問題と思われます。)

unoinsp.pyのリストの内包表記の部分を1箇所修正するだけ


"com.sun.star"がコードにたくさんあるので大掛かりな改造が必要かと思ったのですがよく見たら"com.sun.star"を追加しているのは1箇所しかなく他は置換しているだけなので修正箇所は1箇所で済みました。

LibreOffice5(5)unoinsp.pyでAPIリファレンスへのリンクを付ける · GitHubの87行目を書き換えます。
            self.stack = [tdm.getByHierarchicalName("com.sun.star" + i) for i in lst_si]
まずわかりやすくするためにLibreOffice(50)オブジェクトから継承図をたどってメソッド一覧を得るでやったようにこの右辺のリストの内包表記をfor文に分解します。
for i in lst_si:
    tdm.getByHierarchicalName("com.sun.star" + i)
"com.sun.star"を追加するのはiの最初の一文字がドットの時だけに場合分けします。
for i in lst_si:
    if i[0] == ".":
        tdm.getByHierarchicalName("com.sun.star" + i)
    else:
        tdm.getByHierarchicalName(i)
これでやりたいことは達成していますがリストの内包表記が長くなるのでもう一捻り。
for i in lst_si:
    if i[0] == ".":
        i = "com.sun.star" + i
    tdm.getByHierarchicalName(i)
うーん、最後の行が入れ子から外れているので内包表記への変化のやり方がわかりませんね。
for i in lst_si:
    tdm.getByHierarchicalName(i if not i[0] == "." else "com.sun.star" + i)
条件演算を使いました。
            self.stack = [tdm.getByHierarchicalName(i if not i[0] == "." else "com.sun.star" + i) for i in lst_si]
これでうまくいくはずです。

unoinsp.py

これで先程のobj_test.pyを実行してみました。
.lang.XServiceName
      string  getServiceName()
.sheet.XAddIn
│   string  getArgumentDescription( [in] string aProgrammaticFunctionName,
│                                   [in]   long nArgument)
│   string  getDisplayArgumentName( [in] string aProgrammaticFunctionName,
│                                   [in]   long nArgument)
│   string  getDisplayCategoryName( [in] string aProgrammaticFunctionName)
│   string  getDisplayFunctionName( [in] string aProgrammaticName)
│   string  getFunctionDescription( [in] string aProgrammaticName)
│   string  getProgrammaticCategoryName( [in] string aProgrammaticFunctionName)
│   string  getProgrammaticFuntionName( [in] string aDisplayName)
└─.lang.XLocalizable
            .lang.Locale  getLocale()
                    void  setLocale( [in] .lang.Locale eLocale)
org.openoffice.addin.sample.XTokenCounter
      long  tokencount( [in] string str)
これで狙い通りになりました。

APIリファレンスへのアンカータグをつけるIDL名を抽出する正規表現を変更


unoinspのwtree()メソッドで出力するとIDLリファレンスにページがない自作IDLにもアンカータグがついてしまいますのでそれを修正します。
        self.reg_idl = r'\.[0-9a-zA-Z_\.]+'  # IDL名を抽出する正規表現パターン。
        self.reg_i = r'\.[0-9a-zA-Z_\.]+\.X[0-9a-zA-Z_]+'  # インターフェイス名を抽出する正規表現パターン。
        self.reg_e = r'\.[0-9a-zA-Z_\.]+\.[0-9a-zA-Z_]+Exception'  # 例外名を抽出する正規表現パターン。
ドットで始まるものを抽出しているのでこれで問題ないと思っていたのですがこれではorg.openoffice.addin.sample.XTokenCounterのうち.openoffice.addin.sample.XTokenCounterが抽出されています。

LibreOffice(42)UNOオブジェクトの属性1:正規表現パターンを作成でみつけたPyRegexというツールを使ってみようとしましたが枝が特殊文字のせいか思ったような結果がでませんでした。

仕方ないので6.2. re — 正規表現操作 — Python 3.3.6 ドキュメントをみながら試行錯誤です。

\A\.[0-9a-zA-Z_\.]+、これで先頭がドットに始まるIDL名だけ抽出できると思ったのですがこれだと行頭にあるものが抽出できませんでした。

\W\.[0-9a-zA-Z_\.]+、これでドットの前にアルファベットがあるIDLが除外出ると思ったのですがこれも行頭にあるものが抽出されない上treeの枝まで含まれてしまいます。

行頭にあるIDL名の抽出方法を思いつくまでかなり時間がかかってしまいましたが、結局「ドットの前がアルファベットでない」という条件でうまく抽出できることにようやく気が付きました。
        self.reg_idl = r'(?<!\w)\.[0-9a-zA-Z_\.]+'  # IDL名を抽出する正規表現パターン。
        self.reg_i = r'(?<!\w)\.[0-9a-zA-Z_\.]+\.X[0-9a-zA-Z_]+'  # インターフェイス名を抽出する正規表現パターン。
        self.reg_e = r'(?<!\w)\.[0-9a-zA-Z_\.]+\.[0-9a-zA-Z_]+Exception'  # 例外名を抽出する正規表現パターン。
この条件でうまくいきました。
    def fn_s(self, item_with_branch):  # サービス名にアンカータグをつける。
        idl = re.findall(self.reg_idl, item_with_branch)[0] # 正規表現でIDL名を抽出する。
        lnk = "<a href='" + self.prefix + "servicecom_1_1sun_1_1star" + idl.replace(".", "_1_1") + ".html'>" + idl + "</a>"  # サービス名のアンカータグを作成。
        self.lst_output.append(item_with_branch.replace(" ", "&nbsp;").replace(idl, lnk))  # 半角スペースを置換後にサービス名をアンカータグに置換。
    def fn_i(self, item_with_branch):  # インターフェイス名にアンカータグをつける。
        idl = re.findall(self.reg_i, item_with_branch)[0] # 正規表現でインターフェイス名を抽出する。
        lnk = "<a href='" + self.prefix + "interfacecom_1_1sun_1_1star" + idl.replace(".", "_1_1") + ".html'>" + idl + "</a>" # インターフェイス名のアンカータグを作成。
        self.lst_output.append(item_with_branch.replace(" ", "&nbsp;").replace(idl, lnk))  # 半角スペースを置換後にインターフェイス名をアンカータグに置換。
自作IDL名だと正規表現パターンにマッチしないので41行目と45行目でリストが返ってこずエラーになってしまいますのでここも修正します。
    def fn_s(self, item_with_branch):  # サービス名にアンカータグをつける。
        idl = re.findall(self.reg_idl, item_with_branch)  # 正規表現でIDL名を抽出する。
        idl = idl[0] if idl else ""  # 正規表現パターンにマッチするものがないときは""を入れる。
        lnk = "<a href='" + self.prefix + "servicecom_1_1sun_1_1star" + idl.replace(".", "_1_1") + ".html'>" + idl + "</a>"  # サービス名のアンカータグを作成。
        self.lst_output.append(item_with_branch.replace(" ", "&nbsp;").replace(idl, lnk))  # 半角スペースを置換後にサービス名をアンカータグに置換。
    def fn_i(self, item_with_branch):  # インターフェイス名にアンカータグをつける。
        idl = re.findall(self.reg_i, item_with_branch)  # 正規表現でインターフェイス名を抽出する。
        idl = idl[0] if idl else ""  # 正規表現パターンにマッチするものがないときは""を入れる。
        lnk = "<a href='" + self.prefix + "interfacecom_1_1sun_1_1star" + idl.replace(".", "_1_1") + ".html'>" + idl + "</a>" # インターフェイス名のアンカータグを作成。
        self.lst_output.append(item_with_branch.replace(" ", "&nbsp;").replace(idl, lnk))  # 半角スペースを置換後にインターフェイス名をアンカータグに置換。
また条件演算を使いました。
(2015.12.19追記。""で置換するこの方法は無駄なタグが多量にくっついてくるのでLibreOffice5(13)unoinsp.py:IDL名から継承図を出力するで修正しました。)

unoinsp.py

treeの根を出力する


インスタンスの元になったサービス名をオブジェクトから得たいのですがその方法が未だわかりません。

これがわかればunoinsp.pyの出力結果treeに根が描けるんですけが、調べ直してもやっぱりわかりません。

とりあえずtreeに根がつくように最初は「pyuno object」にするようにします。

大掛かりな改造がいるかと思いましたが結構簡単にできそうです。
            lst_level = [0 for i in self.stack]  # self.stackの要素すべてについて階層を取得。
この最初の階層設定を0から1に変更してみました。
├─.lang.XServiceName
│         string  getServiceName()
├─.sheet.XAddIn
│   │   string  getArgumentDescription( [in] string aProgrammaticFunctionName,
│   │                                   [in]   long nArgument)
│   │   string  getDisplayArgumentName( [in] string aProgrammaticFunctionName,
│   │                                   [in]   long nArgument)
│   │   string  getDisplayCategoryName( [in] string aProgrammaticFunctionName)
│   │   string  getDisplayFunctionName( [in] string aProgrammaticName)
│   │   string  getFunctionDescription( [in] string aProgrammaticName)
│   │   string  getProgrammaticCategoryName( [in] string aProgrammaticFunctionName)
│   │   string  getProgrammaticFuntionName( [in] string aDisplayName)
│   └─.lang.XLocalizable
│               .lang.Locale  getLocale()
│                       void  setLocale( [in] .lang.Locale eLocale)
└─org.openoffice.addin.sample.XTokenCounter
            long  tokencount( [in] string str)
あとはこの最初の行に元となるオブジェクトの情報を入れるだけです。

これも簡単にできました。
2015.12.18追記。オブジェクトがサービスを介さずインターフェイスをもっているときは根に枝が繋がっていないことに気が付きました。LibreOffice5(13)unoinsp.py:IDL名から継承図を出力するで修正しました。)

すべてのIDL名の前に枝がつくようになったので折角行頭のIDL名を抽出できる正規表現を先ほど作ったのに骨折り損でした。

unoinsp.py

ついでに"com.sun.star"も変数にしました。

参考にしたサイト


6.11. 条件演算 (Conditional Expressions)
JavaScriptでいう三項演算子に該当する条件演算子。

PyRegex
Pythonの正規表現のシミュレーター。

6.2. re — 正規表現操作 — Python 3.3.6 ドキュメント
なかなか使いこなすのが難しい正規表現です。

次の関連記事:LibreOffice5(12)PythonでLibreOfficeのバージョン番号を得る方法

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ