2020年3月22日日曜日

35: UNOディスパッチコマンドを実行し、全情報を取得する(Python編)

<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>

あなたの怠惰な方法は、実行から入手可能な一部の有用な情報を逃しているかもしれません。

話題


About: UNO (Universal Network Objects)
About: LibreOffice
About: Apache OpenOffice
About: Python

この記事の目次


開始コンテキスト



ターゲットコンテキスト



  • 読者は、PythonでUNOディスパッチコマンドを実行してその結果情報および関連情報を取得する方法を知る。
ト書き
Hypothesizer 7、Objector 35A、Objector 35Bがコンピューターの前にいる。


オリエンテーション


Hypothesizer 7
本記事では、PythonでUNOディスパッチコマンドを実行してその結果情報および関連情報を取得する方法を知ることになります。

Objector 35B
「関連情報」?なんで私はそれを欲しがるべきなわけ?あのね、実行が成功したかどうかは知らなきゃいけないけど、それが私の知りたい全てなの。

Hypothesizer 7
マダム、多くのUNOディスパッチコマンドにおいて、実のところ、それがあなたの知ることのできる全てです、しかし、一部のUNOディスパッチコマンドにおいては、関連情報は、本当に有用であるか、または実行の目的全体です。

Objector 35B
「目的全体」ってどういう意味?

Hypothesizer 7
例えば、'.uno:FontNameList'というUNOディスパッチコマンドが行なうことは、フォント名群を関連情報としてリストする事以外の何物でもありません。そのコマンドを関連情報を取得せずに実行するのは徒労です。

Objector 35B
ふーん、そのコマンドを私が使うとは思わないけど。...とにかく、関連情報が「本当に有用」なのはどんな時?

Hypothesizer 7
例えば、'.uno:GoToCell'というUNOディスパッチコマンドは、直前のカレントセル位置を関連情報内にレポートします。

Objector 35B
その情報は「本当に有用」?

Hypothesizer 7
はい、少なくとも私にとっては。実際、カレントセル位置を知る方法を私は他に知りません、選択されているセル位置群は容易に知ることができますが。

Objector 35B
はあ?選択されているセルがカレントセルじゃないの?

Hypothesizer 7
お分かりのように、選択されているセルは複数あり得、カレントセル(キー入力が向かう先)は、それらの内の1つかもしれないし、それらの内の1つでない可能性さえあります。

Objector 35B
ああ、カレントセルのことを言ってるのね...

Hypothesizer 7
はい、'カレントセル'という用語を使用するときは私は常にカレントセルを意味しています。

Objector 35A
関連情報を欲しくなければ、ディスパッチヘルパーを使えるんだろ?

Hypothesizer 7
サー、UNOディスパッチヘルパーを使用する方法を既にご存知のようですね。仰るとおり、関連情報を必要とされないのであれば、UNOディスパッチヘルパーをご使用になれます、その方法は最も効率的というわけでは特にありませんが。

Objector 35A
「最も効率的というわけでは特にありません」とは、どういう意味だ?

Hypothesizer 7
そのヘルパーは、URLオブジェクトやディスパッチャーを再利用しませんが、それらはコマンドURLが複数回使用されるときは本来は再利用可能なものです、しかし、そうした非効率性があなたの使用用途にとって有意なものであるか否かはあなたがお決めになることです、まあ、私はUNOディスパッチヘルパーを使用しませんが、最良の方法よりも得られる情報が少なくて効率性が劣る方法を使用する理由が全くないので。

Objector 35A
...


本体


1: 当該フレームのUNOディスパッチャー群プロバイダーUNOプロキシを取得する


Hypothesizer 7
UNOディスパッチコマンドというのはどれも、あるフレームに対して実行されるものであり、まず、そのフレームを取得しなければなりません。UNOディスパッチコマンドを、あるドキュメントに対して実行したいのであれば、そのドキュメントを格納しているフレームを取得しなければなりませんし、そうでなければ、UNOデスクトップフレームを使用します。

もしもUNOディスパッチヘルパーを使用するとしても、それを行なう必要はあります。

Objector 35B
「UNOデスクトップフレーム」ってどういう意味?

Hypothesizer 7
UNOデスクトップは、フレームであり、他の全フレームの最上位祖先です。

Objector 35B
そういうオブジェクトが必ず存在しているってこと?

Hypothesizer 7
はい、それは存在します、それがお気に入るか否かにかかわらず。

ドキュメントを格納するフレームを取得するためには、まず、そのドキュメントUNOオブジェクトの'com.sun.star.frame.XModel' UNOプロキシを取得しなければなりません。

特定のドキュメントのUNOオブジェクトへのアクセスを取得する方法がいくつかあります、しかし、それらの全てに立ち入ることは、ここではしません(ある以降の記事でそうするでしょう)。その代わりに、カレントドキュメントのUNOオブジェクトへのアクセスを取得します、以下のようにして('l_remoteUnoObjectsContext'は、UNOオブジェクト群コンテキスト、'l_unoDocumentInXModel'が、当該ドキュメントUNOオブジェクトの'com.sun.star.frame.XModel' UNOプロキシ)。

@Python ソースコード
from typing import cast
from com.sun.star.frame import XDesktop2
from com.sun.star.frame import XModel

					l_unoDesktopInXDesktop2: XDesktop2 = cast (XDesktop2, l_remoteUnoObjectsContext.getServiceManager ().createInstanceWithContext ("com.sun.star.frame.Desktop", l_remoteUnoObjectsContext))
					l_unoDocumentInXModel: XModel = cast (XModel, l_unoDesktopInXDesktop2.getCurrentComponent ())

Objector 35B
「cast」?

Hypothesizer 7
私は、変数タイプアノテーションおよびmypyを使用するので、'cast'を使用しますが、実際には、'cast'には実行時効果は何もありません。実のところ、私は、「'l_unoDocumentInXModel'は'com.sun.star.frame.XModel' UNOプロキシです」というような言い方をしますが、実際には、それは、実行時動作に正確に対応するわけではない抽象的な言明です。

Objector 35B
それじゃあ、'l_unoDocumentInXModel'は、本当は'com.sun.star.frame.XModel' UNO プロキシじゃないの?

Hypothesizer 7
多分違うでしょう。Pythonでは、必要なUNOプロキシは暗黙に取得されます(Basicにおいてと同様に)、Java、C++、.NET Frameworkにおけるように明示的に取得されるのではなく。

Objector 35B
ふーん...、まあどうでも。

Hypothesizer 7
当該フレームUNOオブジェクトの'com.sun.star.frame.XDispatchProvider' UNOプロキシを取得します、以下のように。

@Python ソースコード
from com.sun.star.frame import XController
from com.sun.star.frame import XDispatchProvider

					l_controllerInXController: XController = cast (XController, l_unoDocumentInXModel.getCurrentController ())
					l_frameInXDispatchProvider: XDispatchProvider  = cast (XDispatchProvider, l_controllerInXController.getFrame ())

Objector 35B
抽象的に言えばね...

Hypothesizer 7
その一方、UNOデスクトップフレームUNOオブジェクトの'com.sun.star.frame.XDispatchProvider'UNOプロキシは、以下のようにして取得できます。

@Python ソースコード
from com.sun.star.frame import XDispatchProvider

						l_frameInXDispatchProvider: XDispatchProvider = cast (XDispatchProvider, l_remoteUnoObjectsContext.getServiceManager ().createInstanceWithContext ("com.sun.star.frame.Desktop", l_remoteUnoObjectsContext))

Objector 35B
ふーん。


2: 任意のUNOディスパッチコマンドを実行し、結果情報および関連情報を取得する


Hypothesizer 7
結果情報および関連情報はそれぞれ'com.sun.star.frame.XDispatchResultListener'および'com.sun.star.frame.XStatusListener'に通知されるので、'com.sun.star.frame.XDispatchResultListener'を実装するクラス1つおよび'com.sun.star.frame.XStatusListener'を実装するクラス1つ以上を作成します。単一のクラスに両UNOインターフェースを実装させることもできます。

例えば、以下は、'com.sun.star.frame.XDispatchResultListener'の1実装と'com.sun.star.frame.XStatusListener'の1実装です('Test1Test'は、それらが定義されているクラスです)。

@Python ソースコード
import sys
from com.sun.star.frame import XDispatchResultListener
from com.sun.star.frame import XStatusListener
from com.sun.star.lang import EventObject as com_sun_star_lang_EventObject
from com.sun.star.frame import DispatchResultEvent
from com.sun.star.frame import FeatureStateEvent
from unohelper import Base as UnoBase

	class ResultInformationListener (UnoBase, XDispatchResultListener):
		def __init__ (a_this: "Test1Test.ResultInformationListener") -> None:
			None
		
		def dispatchFinished (a_this: "Test1Test.ResultInformationListener", a_dispatchResultEvent: DispatchResultEvent) -> None:
			sys.stdout.write ("### The dispatch execution is finished: {0:s}.\n".format (str (a_dispatchResultEvent)))
		
		def disposing (a_this: "Test1Test.ResultInformationListener", a_source: com_sun_star_lang_EventObject) -> None:
			None
	
	class RelatedInformationListener (UnoBase, XStatusListener):
		def __init__ (a_this: "Test1Test.RelatedInformationListener") -> None:
			None
		
		def statusChanged (a_this: "Test1Test.RelatedInformationListener", a_featureStateEvent: FeatureStateEvent) -> None:
			sys.stdout.write ("### A dispatch execution related information piece is offered: {0:s}.\n".format (str (a_featureStateEvent)))
		
		def disposing (a_this: "Test1Test.RelatedInformationListener", a_source: com_sun_star_lang_EventObject) -> None:
			None
	
					l_resultInformationListener: "Test1Test.ResultInformationListener" = Test1Test.ResultInformationListener ()
					l_relatedInformationListener: "Test1Test.RelatedInformationListener"  = Test1Test.RelatedInformationListener ()

Objector 35B
それらイベントオブジェクト群の仕様は?

Hypothesizer 7
それらのクラスは、UNO APIドキュメント('com.sun.star.frame.DispatchResultEvent'は、ここ、'com.sun.star.lang.EventObject'は、ここ、'com.sun.star.frame.FeatureStateEvent'は、ここ)で調べてください。

Objector 35B
分かった。

Hypothesizer 7
次に、'com.sun.star.util.URL'オブジェクト(UNOオブジェクトではない)を以下のようにセットアップします。

@Python ソースコード
from typing import Match
from typing import Optional
from typing import Pattern
import re
from com.sun.star.util import URL as com_sun_star_util_URL

					l_url: str = ".uno:GoToCell"
					l_urlInURL: com_sun_star_util_URL = com_sun_star_util_URL ()
					l_urlInURL.Complete = l_url
					l_urlInURL.Main = l_url
					l_regularExpressionPattern: Pattern = re.compile ("(.*:)(.*)")
					l_regularExpressionMatch: Optional [Match] = l_regularExpressionPattern.match (l_urlInURL.Complete, 0)
					if l_regularExpressionMatch is not None:
						l_urlInURL.Protocol = l_regularExpressionMatch.groups () [0]
						l_urlInURL.User = ""
						l_urlInURL.Password = ""
						l_urlInURL.Server = ""
						l_urlInURL.Port = 0
						l_urlInURL.Path = l_regularExpressionMatch.groups () [1]
						l_urlInURL.Name = ""
						l_urlInURL.Arguments = ""
						l_urlInURL.Mark = ""

ご注意頂きたいのですが、Pythonでは、'com.sun.star.util.URLTransformer' UNOサービスを使用することができません(他のプログラミング言語においてとは異なり)、その理由は、そのUNOサービスは、「参照渡し」と呼ばれる引数値渡しを必要としますが、Pythonは「値渡し」と呼ばれるもののみをサポートしており、Python版UNOも、Java版UNOがサポートしているような回避策(代わりに配列を渡す)をサポートしていないためです。

Objector 35B
...まあ、URLが問題なく準備できれば私は不平は言わないけど。

Hypothesizer 7
とにかく、次に、ディスパッチャーUNOオブジェクトの'com.sun.star.frame.XNotifyingDispatch' UNOプロキシを取得します、以下のように。

@Python ソースコード
from com.sun.star.frame import XNotifyingDispatch

					l_dispatchInXNotifyingDispatch: XNotifyingDispatch = cast (XNotifyingDispatch, l_frameInXDispatchProvider.queryDispatch (l_urlInURL, "_self", -1))

「_self」および「-1」は、コマンドのターゲットフレーム名とそのフレームを発見するための検索フラグです。

Objector 35B
ターゲットフレームは前セクションで既に取得したと思ったが。

Hypothesizer 7
しました、元々ここで'_self'を指定するつもりだったので。

Objector 35B
他のつもりだったらどうなの?

Hypothesizer 7
勿論、別のフレームから開始して、ターゲットフレーム名および検索フラグをここで適切に指定することも可能です。

Objector 35B
ターゲットフレーム名にはどんな名前を指定できるの?

Hypothesizer 7
'_self'は、固有名ではなく、問題にしているフレーム自体(本ケースでは'l_frameInXDispatchProvider')を意味する特殊フレーム名です。

他の特殊フレーム名たちを理解するには、フレームたちがどのように組織されているかを知らなければなりません: どのフレームも1つのフレーム群階層に属し、どの階層もルートフレーム(それがデスクトップです)の下に属します。

'_parent'は、問題にしているフレームの直接の親を意味し、'_top'は、問題にしているフレームが属する階層のトップフレームを意味し('_top'はルートフレームを意味せず、ルートフレームの下のフレームを意味することにご注意ください)、'_blank'は、新たなトップフレームを意味します。

Objector 35B
Calcドキュメントはどんな階層を持ってるの?

Hypothesizer 7
Calcドキュメントについて言うと、私たちが取得したフレームがトップフレームであり、子はありません。

Objector 35B
固有名は、ファイル名か何かなの?

Hypothesizer 7
実のところ、固有名はブランクです、明示的にあなたがセットしない限り。

Objector 35B
それじゃあ、ルートフレームから開始したら、どうすれば、Calcドキュメントをターゲットフレーム名と検索フラグで一意に特定できるわけ?

Hypothesizer 7
ブランク名を持っている複数のドキュメントがオープンされていたら、おできになるとは私は思いません。

Objector 35B
...「-1」は検索フラグとして何を意味しているの?

Hypothesizer 7
実のところ、「-1」は何も意味していません、その引数は、その前の引数に特殊フレーム名が指定された場合は無視されるので。

Objector 35B
なるほど...

ディスパッチャーは再利用できるとあなたは言ってたけど。

Hypothesizer 7
同一のURLには、できます。異なるURLには、一般的にはできません、ディスパッチャーが偶然その異なるURLのディスパッチャーでもある場合はできますが。

Objector 35B
偶然そうであるかどうかをどうすれば知れるの?

Hypothesizer 7
最も容易な方法は、ディスパッチャー再利用しようとしてみることでしょう。

Objector 35B
あっ、そう。

Hypothesizer 7
次に、関連情報リスナー(複数可能)をディスパッチャーに登録し、最後に、私たちのコマンドをディスパッチしてくれるようにディスパッチャーに依頼します、以下のように。

@Python ソースコード
from com.sun.star.beans import PropertyValue

					l_dispatchInXNotifyingDispatch.addStatusListener (l_relatedInformationListener, l_urlInURL)
					l_dispatchArgumentProperties: List [PropertyValue] = []
					l_dispatchArgumentProperties.append (PropertyValue ())
					l_dispatchArgumentProperties [0].Name = "SynchronMode"
					l_dispatchArgumentProperties [0].Value = True
					l_dispatchArgumentProperties.append (PropertyValue ())
					l_dispatchArgumentProperties [1].Name = "ToPoint"
					l_dispatchArgumentProperties [1].Value = "$B$2"
					l_dispatchInXNotifyingDispatch.dispatchWithNotification (l_urlInURL, l_dispatchArgumentProperties, l_resultInformationListener)
					l_dispatchInXNotifyingDispatch.removeStatusListener (l_relatedInformationListener, l_urlInURL)

Objector 35A
同期にも非同期にもできるようだな。

Hypothesizer 7
はい。

'statusChanged'メソッドは複数回呼ばれる可能性があり(または全然呼ばれないかもしれない)、'dispatchFinished'メソッドは最後に一度だけ呼ばれます。

私の各種サンプル(コンソールPython UNOクライアントファイルコンバーターなど)で使用されている私のUNOユーティリティプロジェクト、'unoUtilitiesToBeDisclosed'では、UNOドキュメントラッパークラスである'theBiasPlanet.unoUtilities.documentsHandling.UnoDocument'が両リスナーインターフェースを実装しており、'dispatch'というメソッドも持っています。このメソッドは、UNOディスパッチコマンドを実行し、結果情報および関連情報をリターン値にパックして戻します。

Objector 35B
そりゃ、よかったわね。

Hypothesizer 7
'dispatch'メソッドの第1引数である'UnoDispatchSlotsConstantsGroup.BaseDispatchSlot a_dispatchSlot'は、実行しようとするUNOディスパッチコマンドのURLと引数名群を保持しているオブジェクトです。実は、'theBiasPlanet.unoUtilities.constantsGroups.UnoDispatchSlotsConstantsGroup'クラスがそうしたオブジェクトたちを保持しています。そうした情報は、各プログラムに文字列リテラルとして散らばらせるよりも1箇所においたほうが望ましいだろうと思いました。


3: UNOディスパッチヘルパーを使用する方法


Hypothesizer 7
余談として、UNOディスパッチヘルパーを使用する方法も見てみましょう。

上記の方法は、以下の方法に比べて、コードを数行多く要することを除いていかなる側面においてもより悪いということが決してない(いくつかの側面においてより良いとしても(実際により良い))ので、私は以下の方法を使用しませんが、いずれにせよ、以下の方法を使用したい方はがいらっしゃるかもしれません。

Objector 35A
間違いなく数行より多いが、...

Hypothesizer 7
えーと、URLオブジェクトの準備に20行程度を要しますが、それらは、1ファンクション内に入れ込むができます(実際、私のUNOユーティリティプロジェクト内の'theBiasPlanet.unoUtilities.connectionsHandling.UnoObjectsContext.createUrlInURL'はそういうファンクションです)。リスナー群実装に30行程度を要しますが、それは、情報を取得することに興味がなければ割愛することができます。それらを除けば、差異は数行にすぎません。

Objector 35A
「それらを除けば」ね...

Hypothesizer 7
いずれにせよ、もしも、それが100行や1,000行であったとしても、別に問題ありません、なぜなら、あなたは、それらをただ1度ご自分のコードにコピーすればよいだけだからです(コピーするべき行の数はあなたの手間に大した違いを及ぼさないでしょう)。

Objector 35A
...

Hypothesizer 7
いずれにせよ、UNOディスパッチヘルパーUNOオブジェクトの'com.sun.star.frame.XDispatchHelper' UNOプロキシを、以下のようにして取得します。

@Python ソースコード
from com.sun.star.frame import XDispatchHelper

					l_dispatchHelperInXDispatchHelper: XDispatchHelper = cast (XDispatchHelper, l_remoteUnoObjectsContext.getServiceManager ().createInstanceWithContext ("com.sun.star.frame.DispatchHelper", l_remoteUnoObjectsContext))

Objector 35A
ははあ。

Hypothesizer 7
次に、UNOディスパッチコマンドを実行します、フレーム、コマンドURL、ターゲットフレーム名、ターゲットフレーム検索フラグ、コマンド引数群を指定して、以下のように。

@Python ソースコード
from com.sun.star.beans import PropertyValue

					l_dispatchArgumentProperties: List [PropertyValue]  = []
					l_dispatchArgumentProperties.append (PropertyValue ())
					l_dispatchArgumentProperties [0].Name = "ToPoint"
					l_dispatchArgumentProperties [0].Value = "$B$2"
					l_commandResult: object  = l_dispatchHelperInXDispatchHelper.executeDispatch (l_frameInXDispatchProvider, ".uno:GoToCell", "_self", -1, l_dispatchArgumentProperties)


4: 結びとその先


Hypothesizer 7
これで、PythonでUNOディスパッチコマンドを実行してその結果情報および関連情報を取得する方法を知りました。

もちろん、UNOディスパッチコマンドは、他のプログラミング言語(Java、C++、C#、LibreOffice BasicまたはApache OpenOffice Basic、BeanShell、JavaScript)で実行することもできます。JavaC++C#LibreOffice BasicまたはApache OpenOffice Basicでそうする方法を以前の記事で学びました。

LibreOffice基盤のためのUNOディスパッチコマンドの未完の(今のところ)リストとリストされたコマンドの仕様説明および LibreOffice CalcのためのUNOディスパッチコマンドの未完の(今のところ)リストとリストされたコマンドの仕様説明があります。

UNOディスパッチコマンドを実行するのは、LibreOfficeまたはApache OpenOfficeのインスタンスを操作する1つの方法ですが、LibreOfficeまたはApache OpenOfficeのインスタンスの操作で私たちができることの全てをカバーしているわけではありません: UNOオブジェクトを直接操作することでもっと多くのことを行なえます(本シリーズの目次を御参照ください)。


参考資料


<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>