LibreOffice5(86)拡張機能: TCU - Tree Command for UNO の作成

2017-09-26

旧ブログ

t f B! P L
LibreOffice5(35)unoinsp.py:PyDevパッケージで管理するを拡張機能にしました。オートメーションでの実行と違ってUNOコンポーネントとしての実行になったので圧倒的に速度が改善しました。
(2018.3.2追記LibreOffice5(147)TCUの改良でアルゴリズムを変更しました。)

前の関連記事:LibreOffice5(85)FileURLとSystemPathの変換方法


TCU - Tree Command for UNO の使い方


TCU.oxt

ここからDownloadボタンをクリックしてoxtファイルをダウンロードしてLibreOfficeの拡張機能マネージャーでインストールします。

description.xmlでLibreOffice-minimal-versionを5.2に設定しているので、5.2以上にしかインストールできませんが、たぶんそれ以下でも動くと思います。

Windows10 Home 64bitのLibreOffice5.4でも動きました。

オプションダイアログがありますが、インストール直後はコントロールが表示されないので、まずLibreOfficeを再起動します。
def macro(): 
 ctx = XSCRIPTCONTEXT.getComponentContext()  # コンポーネントコンテクストの取得。
 smgr = ctx.getServiceManager()  # サービスマネージャーの取得。
 doc = XSCRIPTCONTEXT.getDocument()
 tcu = smgr.createInstanceWithContext("pq.Tcu", ctx)  # サービス名か実装名でインスタンス化。
 tcu.wtree(doc)
g_exportedScripts = macro, #マクロセレクターに限定表示させる関数をタプルで指定。
このPythonマクロを呼び出すとXSCRIPTCONTEXT.getDocument()で取得できるオブジェクトのAPIツリーがデフォルトブラウザに出力されます。

wtree()メソッドではなく、tree()メソッドにオブジェクトを渡した場合は、1行を要素とするタプルが返ってきます。
s = tcu.tree(arg)  # 1行を要素とするタプルが返ってくる。
print("\n".join(s))  # 各要素を改行コードで結合して出力する。
これでオートメーションでは標準出力にAPIツリーが表示されます。

ツール→オプション、拡張機能→TreeCommandforUNO、でオプションの設定ができます。


(2018.2.27追記。v2.0.0以降からXInitializationXComponentはデフォルトからの無視するインターフェイスからはずすことにしました。)

ウェブブラウザに出力したときはAPIリファレンスへのアンカータグをつけています。

APIリファレンスURLはオンラインのLibreOffice: Main Pageへのパスです。

このLibreOffice: Main Pageの更新はすごく速くて、現在利用できるリリース前のバージョンが、5.4.2なのにオンラインAPIリファレンスのバージョンは6.0になっています。

使っているLibreOfficeと同じバージョンのAPIリファレンスを見たいときは同じバージョンのSDKをインストールします。

SDKをインストールした後に右下の「デフォルトに戻す」ボタンをクリックすると「ローカルリファレンスへのパス」が取得できるはずです。

取得できないときは「検索」ボタンをクリックしてrefフォルダを選択します。

ローカルリファレンスへのパスが取得出来たら、「ローカルリファレンスを使う」にチェックをつけて、wtree()を実行するとウェブブラウザにローカルリファレンスへのアンカーがついたAPIツリーが表示されます。

「無視するインターフェイス」には出力しないインターフェイス名を,でつないで入れておきます。

デフォルトではCore Interfacesを指定しています。

他のインターフェイスでも複数回出力しないようになっています。

TCU - Tree Command for UNO 作成メモ


p--q/TCU

オプションダイアログ付きの拡張機能なので、LibreOffice5(63)オプションページを持つ拡張機能の例を作る: その1で作成したp--q/OptionsDialogを元にしましたが、あれこれ手直しが必要でした。

デフォルト値を保持しておくためだけのコンポーネントスキーマノードを作成しておく

まずはデフォルト値を呼び出すためのコンポーネントスキーマノードです。

LibreOffice5(65)オプションページを持つ拡張機能の例を作る: その3config.xcsではノードタイプにデフォルト値をもつノードを用意しましたが、この方法だと設定値を増やしたりするときなど、どうしても2つの編集箇所ができてしまいます。

これは面倒なので今回のconfig.xcsは、デフォルト値を設定したノードタイプを一つ用意して、それをコンポーネントノードの2つのノード(DefaultsOptionDialog)からそれぞれ呼び出すことにしました。

Defaultsノードには値を書き込まないようにすることで常にデフォルト値を呼び出せるようにしておきます。

OptionDialogノードにはオプションダイアログで「OK」ボタンをクリックしたときに値を書き込むようにします。

一旦「OK」ボタンがクリックされた後はOptionDialogノードの値はregistrymodifications.xcuから呼び出されることになりますが、「OK」ボタンがクリックされるまではconfig.xcsのデフォルト値を呼び出すことができます。

設定値は随時呼び出す

最初はコンポーネントスキーマノードからの呼び出す機会を減らすために変数に値を保持しておこうと思いましたが、それぞれ用途によって値の形式が違うのでかなりややこしくなるので、随時コンポーネントスキーマノードから呼び出すようにしました。

コンポーネントスキーマノードで値を保存するpropノードには値の型を設定しないといけません。

ブーリアンはxs:boolean型を使えば問題なく、true/falseを設定しておけば読み書きのときに変換してTrue/Falseを返してくれます。

文字列はxs:string型で保存できます。

問題はIgnoredIDLsノードの形式ですが、oor:string-listの使い方がよくわからず、またその必要性もわからなかったのでリストの要素を結合して文字列で保存し、その出し入れの時に毎回変換することにしました。

RefDirノードの型は文字列なのですが、SystemPathで保存しておくのか、FileURLで保存しておくのか、悩みました。

結局デフォルト値はPathSubstitutionサービスで変換してFileURLを得るので、保存するときもFileURLにして保存することにしました。

オプションダイアログに表示する値の形式と、APIツリーを表示するときに使う値の形式が違うのでそれぞれ別の呼び出し関数(getConfigs()toDialog())を使っています。

これによってオプションダイアログに関するコードは一つのモジュール(optiondialog.py)にまとめることができました。

Python UNO Componentにするクラスの__init__()は最小限にする

component.pyTreeCommandクラスはpyunocomponent.pyg_ImplementationHelper.addImplementation()でPython UNO Componentとなるクラスとして渡されています。

このクラスのインスタンス化に失敗すると、IDLで定義しているインターフェイス(この拡張機能の場合はfrom pq import XTcu)がインポートできないというエラーがでてきます。

このエラーメッセージからわかることはTreeCommandクラスの__init__()でエラーがでているということしかわからず、デバッグするのがなかなか難しいので、TreeCommandクラスの__init__()でやることは変数の代入程度にとどめて、エラーが出ないようにしておきます。

PyDevのデバッガーと接続するデコレーター

common.pyenableRemoteDebuggingはデバッグするときに使うデコレーターです。

このデコレーターでデバッガに接続するにはsites.pthにpydevd.pyへのパスを書いておく必要があります(Eclipse: PyDevメモ: LibreOfficeのPythonマクロのデバッグ)。

デバッグしたい関数かメソッド(__init__()は不可)をenableRemoteDebuggingでデコレートします。

あとはEclipseでPyDev Debug Serverを起動した状態で、UNOコンポーネントを実行します。

あとはStep Intoしていくとデコレーターをつけた関数(またはメソッド)に入ることができます。

木を出力するアルゴリズムは変更せず

LibreOffice5(51)Javaの例:ConfigExamplesをPythonにする その4で知ったvistorパターンで書き換えられるかと思ったのですが、木を出力するためにはノードの深さ(階層)を取得しないといけないのですが、visitorパターンでうまく処理する方法がわからなかったので、結局スタックを使う方法のままにしています。

LibreOffice5.2とソケット接続したJupyter Notebookでpq.Tcuをインスタンス化するとなぜかソケット接続が切断されてしまって動作確認ができなかったので、IPython形式で出力するitree()メソッドは削除しました。

その他冗長なコードの整理をしました。

不必要にジェネレーターをリストに変換している箇所があったので、リストに変換するのをやめました。

Python Cookbookの7.9.Replacing Single Method Classes with Functionsと14.14.Making Your Programs Run Fasterを読んで、次の3点を修正しました。

継承する必要がないものはクラスではなく関数を使う。

ドット演算子つまりアトリビュートはなるべく使わないようにする。

グローバル変数へのアクセスはなるべく避ける。

これ以外に文字列の結合は+ではなくて、全体像がわかりやすいformat()を使うように変更しました。

ドット演算子を避けてローカル変数にしたらだいぶコードが読みやすくなりました(trees.py)。

ウェブブラウザの出力にはREST-Based Interfaceを使う

ウェブブラウザへの出力にはPython Cookbook11.05.creating_a_simple_rest_based_interfaceにあるREST-Based Interfaceを使うようにしました。

これによってhtmlファイルを作成せずに直接ウェブブラウザに出力しています。

ただしserve_forever()にするとOSを再起動しないと次に実行できないので、handle_request()にしています。

wtree()メソッドは1回のプロセスの実行で1回しか実行できません。

この方法は、ウェブブラウザのセキュリティの制限によって、file://のアンカーは無効にされているので、ローカルAPIリファレンスを使うときはunoinsp.pyと同様に一旦htmlファイルに出力してからそれをブラウザで開いています。

拡張機能で実行したときはhtmlファイルはホームフォルダに出力されました。

 ダイアログの翻訳文は原文と長さを揃える

オプションダイアログはlinuxBean14.04(158)Pythonモジュールを多言語対応にするの方法で国際化しています。

ダイアログ上の単語を翻訳するときは原文と翻訳文の長さをできるだけ揃えるようにします。

そうしないとコントロールからはみ出した部分は切れて表示されてしまいます。

また翻訳文ではショートカットキーの表示部分も長さに考慮しておかないといけません。

可能であればコントロールの幅は最大限にとると翻訳文の長さの制限が緩やかになります。

toolsフォルダのスクリプトでxmlファイルやoxtファイルを作成する

これまでと同様に拡張機能に必要なファイルを作成するためのスクリプトを使いました。
.
├── LICENSE
├── META-INF
│   └── manifest.xml
├── OptionsDialog.xcu
├── TCU.components
├── TCU.uno.rdb
├── config.xcs
├── description.xml
├── descriptions
│   ├── description_en.txt
│   └── description_ja.txt
├── dialogs
│   └── optionsdialog.xdl
├── icons
│   └── extension.png
├── pythonpath
│   └── inoxt
│       ├── common.py
│       ├── component.py
│       ├── locale
│       │   └── ja
│       │       └── LC_MESSAGES
│       │           ├── default.mo
│       │           └── default.po
│       ├── optiondialog.py
│       ├── trees.py
│       └── wsgi.py
└── pyunocomponent.py
oxtファイルに含まれるこれらのファイルのうちmanifest.xml(createXMLs.py)、OptionsDialog.xcu(createOptionsDialogXcu.py)、TCU.components(createXMLs.py)、TCU.uno.rdb(createRDB.py)、config.xcs(createXcs.py)、description.xml(createXMLs.py)の6つはtoolsフォルダのスクリプトを使って作成しています。

rdbファイルのソースであるidlファイルもスクリプト(createIDLs.py)で作成しています。

description.xmlの内容はconfig.iniファイルを読み込んで作成しています。

description.xmlで拡張機能のバージョンを定義しているので、頻繁にdescription.xmlを作り直さないといけないことにあとで気が付きました。

次に作る拡張機能では拡張機能のバージョンの定義を自動的に増やしてdescription.xmlを単独で出力するスクリプトを作ろうと思います。

(2017.10.9追記)

wtree()メソッドの結果をこのブログに貼り付けるための私的メモ


wtree()メソッドの結果をこのブログに貼り付けるときは、等幅フォント、折り返しをしない、行の高さを等倍にする、テキストリンクアイコンを表示させない(Blogger:テキストリンクにアイコンをつける参照)、の設定をするようにスタイルを追加します。
<style>
.tcu {
   font-family: monospace;
   overflow: auto;
   white-space: nowrap;
   line-height: 1.0;
}
.tcu a[href ^="http"] {
   padding-right: 0px;
   background: none;
}
 </style>
<div class="tcu">
 wtree()の出力のhtmlを入れる。
</div>

次の関連記事:LibreOffice5(87)Pythonマクロのための拡張機能APSOのインストール

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ