LibreOffice(35)マクロの記録をPythonに翻訳1:リストとタプル

2014-03-31

旧ブログ

t f B! P L

前の関連記事:LibreOffice(34)Basicマクロのモジュールやライブラリーは改名不能?


LibreOffice(33)デベロッパーガイド5:ディスパッチフレームワークでマクロの記録で生成したマクロをPythonに作り変えていきます。

コマンドURLのオプション引数のUNOのデータ型: シーケンスとStruct


LibreOfficeのフレームワークAPIで使われるUNOのデータ型(3.2.1 データ・タイプ Data Types)とPythonのデータ型(4. 組み込み型 5. データ構造)は一致しないため、データをやりとりするには互換性を考えないといけません。

LibreOffice(33)デベロッパーガイド5:ディスパッチフレームワークで生成したマクロでとくに互換性を考えないといけないのはcom.sun.star.frame.XDispatchHelperのexecuteDispatchのオプション引数です。
any executeDispatch ( [in] XDispatchProvider                                DispatchProvider,
                      [in] string                                           URL,
                      [in] string                                           TargetFrameName,
                      [in] long                                             SearchFlags,
                      [in] sequence< com::sun::star::beans::PropertyValue > Arguments
                    )  
5個目の引数のUNOのデータ型はsequence(2.5.6 シーケンス Sequences)ですが、その要素のcom.sun.star.beans.PropertyValueのUNOのデータ型はStruct(Structs(このリンク先の上)  Structs) です。

UNOのStructのデータ型は構造体 - Wikipediaといわれるもので、名前付データ要素をStruct名と"."でつなぐことでひとまとめにできるデータ型です。

Pythonでの構造体のようなデータ型


class PropertyValue:
    pass
Pythonでもこのように空のクラス定義で簡単に構造体のようなデータ型が作れます(9.7. 残りのはしばし)。

実際に構造体のようなデータ型を実現するのは空のクラスによる性質ではなく、このクラスにより生成されたインスタンスのデータ属性の下記の性質により実現されます。(9.3.3. インスタンスオブジェクト)

オブジェクト.名前 という構文で表される属性参照はデータ属性を宣言する必要がありません。

なので空のクラスから生成したものに限らず、インスタンスであれば構造体のようなデータ型が作れることになります。

もちろん、このPythonの性質を利用して作った構造体PropertyValueはUNOのデータ型のStructではないのでexecuteDispatchの引数にはできません。

Python3.3.3ドキュメントには7.1. structの項目がありますがこれはCの構造体の互換性のためにあるものでバイナリデータに関するもののようです。

UNOのシーケンス型はPythonのタプルに相当


executeDispatchのオプション引数はUNOのStructを順番をつけて収納したシーケンス(2.5.6シーケンス   Sequences)という型になります。

JavaやLibreOffice Basicの配列はUNOのシーケンス型と互換性があるので、とくに違いを考える必要がありません。

ところがPythonのシーケンス型にはlist、tuple、range(リスト、タプル、レンジ)と3種類もあります。

さらに標準ライブラリにまで目を広げればアレイarrayとかコレクションcollectionsなどもあります。(11.7. リスト操作のためのツール

このうちUNOのシーケンス型と互換性があるのはタプルだけです。(UNO Type mapping)

ですので、executeDispatchのオプション引数のPythonのシーケンス型にはタプルを使わないといけませんが、タプルは使いにくいのでまずリストで必要なシーケンスを作成することになります。

Pythonのシーケンス型list、tuple、range(リスト、タプル、レンジ)


4.6. シーケンス型 — list, tuple, rangeにかなり詳しく解説されています。

list、tuple、range(リスト、タプル、レンジ)いずれも要素を順番をつけて収納したものです。

rangeはとくに整数(int型)の要素のみに限定したものです。

listは要素の入れ換えができる(ミュータブル)のに対し、tupleとrangeは生成したあとは要素の入れ換えはできません(イミュータブル)。

イミュータブルであるtuple、rangeとミュータブルであるlistとの機能上の大きな違いはhash()ハッシュ関数 - Wikipedia)を備えているかいないかです。

ハッシュ関数でハッシュ値を生成したデータ群は高速に検索可能です。

ただハッシュ値を生成する負荷が生じます。

そこでPythonではシーケンス型をlist、tuple、rangeの3つに分けて機能特化させることで負荷の軽減を図っています。

Pythonを書くときはこれらを使い分けないといけません。

UNOオブジェクトに渡すときはUNOのシーケンス型と互換性のあるタプルtupleに変換しておかないといけません。(UNO Type mapping)

UNOのデータ型とPythonのデータ型との対応表


Python-UNO bridge(PyUNO)のUNO Type mappingがUNOのデータ型とPythonのデータ型との対応表になります。

Python Language Bindingにも簡単な解説があります。

OOoPython/OverView - ...?では例もついて日本語で解説されています。

UNO Type mappingによるとStructについてはUNOのStructをインポートしたときにPyUNOが互換性のあるPythonのクラスを勝手に生成してくれるそうです。

あとはPythonでそのクラスをインスタンス化して値を入れて使います。
from com.sun.star.beans import PropertyValue
args1 = PropertyValue()
args1.Name = "Text"
args1.Value = "例文"
オートメーションでPropertyValueをインポートするためにはその前にimport unoとしてcomからモジュールをインポートできるようにしておく必要があります。

そうしないとオートメーションで実行するとImportError: No module named 'com'というエラーがでてきます。

LibreOffice(5)PythonでLibreOfficeが動く仕組み:UNOのScriptingFrameworkではPythonマクロ実行前にunoモジュールがインポートされる機会があり、そのときに import ステートメントが uno._uno_import 関数で置き換えられてcomの場所がわかるようになるそうです(Pythonマクロ作成環境について)。

オートメーションではその機会はないので、事前にimport unoとしないと先ほどのエラーがでてくることになります。

Python Language Bindingに書いてあるモジュール関数uno.createUnoStruct(typeName, *args)を使ってもUNOのStructを作れます。

これを使うためにはオートメーションのときだけでなく、LibreOfficeから実行するときもimport unoが必要になります。
import uno
args1 = uno.createUnoStruct("com.sun.star.beans.PropertyValue")
args1.Name = "Text"
args1.Value = "例文"
この書き方はLibreOffice Basicの書き方に近いですね。

マクロの記録のLibreOffice BasicをPythonに書き換える


さあ、ここまでわかればあとはPythonでのアルゴリズムだけを考えればよいはずです。

動作環境は以下です。
Windows7 Home Premium 64bit Service Pack1
LibreOffice 4.2.2.1(LibreOffice(28)4.1.4.2から4.2.2.1にアップグレード
Python LibreOfficeバンドル版 3.3.3(LibreOffice(30)PythonのSQLiteモジュールを導入
PyCharm Community Edition 3.0.2(LibreOffice(2)Pythonの統合開発環境PyCharmのインストール
LibreOffice(4)PyCharmからLibreOfficeを動かす(オートメーション)を設定。

まずはマクロの記録の結果の最初のコマンドURLの部分だけをLibreOffice BasicからPythonへ翻訳します。

その部分をコメント行を省いて書き出してみます。
sub Main
dim document   as object
dim dispatcher as object
document   = ThisComponent.CurrentController.Frame
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
dim args1(0) as new com.sun.star.beans.PropertyValue
args1(0).Name = "Text"
args1(0).Value = "例文"
dispatcher.executeDispatch(document, ".uno:InsertText", "", 0, args1())
end sub
このマクロはWriterのドキュメントに「例文」と入力します。


6行目でargs1(0)を定義しています。

args1(0)の丸括弧はLibreOffice Basicでは配列を意味し、丸括弧のなかの数字は最終要素番号を表します。(配列)

asのあとには配列の要素の型を定義します。

オブジェトの型の場合はnewで新しいインスタンスを生成します。(UNO 構造体)
dim args1(0) as new com.sun.star.beans.PropertyValue
ということでこの6行目はUNOのStructであるcom.sun.star.beans.PropertyValueのインスタンスを要素にもつ配列args1()を定義し、その要素数は1個ということになります。

LibreOffice Basicの配列はUNOのシークエンス型と互換性があるので、9行目のexecuteDispatchのオプション引数に使っています。

これらを考慮してPythonに書き換えます。
from com.sun.star.beans import PropertyValue #UNOのStructであるPropertyValueをインポート。
def dispatch_writer_ex(): #Pythonの関数名がLibreofficeのマクロ名になる。
    frame = XSCRIPTCONTEXT.getDesktop().getCurrentFrame() #フレームを取得。
    ctx = XSCRIPTCONTEXT.getComponentContext() #コンポーネントコンテクストを取得。
    dispatcher = ctx.getServiceManager().createInstanceWithContext("com.sun.star.frame.DispatchHelper",ctx) #com.sun.star.frame.DispatchHelperオブジェクトを生成。
    args1 = [PropertyValue() for i in range(1)] #要素の型がPropertyValueの要素数1のリストargs1を生成。
    args1[0].Name = "Text" #PropertyValueのName属性に代入。
    args1[0].Value = "例文" #PropertyValueのValue属性に代入。
    dispatcher.executeDispatch(frame, ".uno:InsertText", "", 0, tuple(args1)) #リストargs1をタプルargs1に変換してdispatcher.executeDispatchの引数にする。
オートメーションの部分は省いています。

このPythonのマクロdispatch_writer_exをLibreOffice Writerから実行するとマクロの記録と同じ結果が得られました。

リスト、レンジ、タプルすべてを使っています。

とくに6行目はリストの内包表記を使って要素の型がPropertyValueの要素数1のリストargs1を生成しています。(5.1.3. リストの内包表記)

(2014.4.3追記。同じ要素の固定長リストの生成は内包表記ではなくてargs = [PropertyValue()]*n_args、でいいようです。【教えてPythonのエロい人】固定長配列を一気に作るには? | okkyの日記 | スラッシュドット・ジャパン

3,4,5行目はTransfer from Basic to Pythonに書いてある通り、「ThisComponent」や「createUnoService」を「XSCRIPTCONTEXT」から得るように書き換えるだけです。

5行目のUNOオブジェクトdispatcherの生成はLibreOffice(15)デベロッパーガイド3 コンポーネントコンテクストで学習しましたね。

PyCharmはUNOの型を知らないので正しくない助言をするときがある


PyCharmは細かいところまでいろいろ指摘してくれてとても助かります。

でもUNOの型のことまでは知らないので、Pythonがマクロが動かなくなるような助言してくるときがあるので注意が必要です。


関数名や変数名は全部小文字にしなさい、と助言してきます。

しかし、この部分はPyCharmの助言に従ってはいけません。

PropertyValue()のNameやValueをnameやvalueのように小文字にするとエラーでてマクロが動きません。


Scripting Frameworkのグローバル変数XSCRIPTCONTEXTもスペルチェックの下波線が表示されます。

これはXSCRIPTCONTEXTをPyCharmの辞書に登録することで下線が表示されなくなります。

辞書に登録するには、下波線がでているXSCRIPTCONTEXTの部分をクリックしてでてくる電球マークをクリックしてメニューを表示させます。

「Typo: Save 'XSCRIPTCONTEXT' to dictionary」をクリックするとPyCharmの辞書にXSCRIPTCONTEXTが登録されて下波線がでてこなくなります。

ちなみにXSCRIPTCONTEXTはこのように全部大文字にしておかないといけません。

小文字にするとオートメーションでは動きますが、LibreOfficeからの呼び出しでは動きません。


辞書に登録した単語の変更はPyCharmのメニューからFile→Settings、SpellingからAccepted Wordsのタブを選択してそこで行います。

オートメーションの部分も加えたPythonスクリプト


オートメーションの部分を加えると以下になります。

これでPyCharmからも動作させることができます。
#WriterExp.py #pyファイル名
import uno #オートメーションでのみ必要。
from com.sun.star.beans import PropertyValue
def dispatch_writer_ex():
    frame = XSCRIPTCONTEXT.getDesktop().getCurrentFrame()
    ctx = XSCRIPTCONTEXT.getComponentContext()
    dispatcher = ctx.getServiceManager().createInstanceWithContext("com.sun.star.frame.DispatchHelper",ctx)
    args1 = [PropertyValue() for i in range(1)]
    args1[0].Name = "Text"
    args1[0].Value = "例文"
    dispatcher.executeDispatch(frame, ".uno:InsertText", "", 0, tuple(args1))
#以下はオートメーションでのみ必要。
if __name__ == "__main__":
    import unopy
    XSCRIPTCONTEXT = unopy.connect()
    if not XSCRIPTCONTEXT:
        print("Failed to connect.")
        import sys
        sys.exit(0)
    dispatch_writer_ex()

参考にしたサイト


The OpenOffice.org recorder and UNO dispatch calls - Apache OpenOffice Wiki
OpenOffice..orgでの「マクロの記録」の例。

Python 標準ライブラリ — Python 3.3.3 ドキュメント
Python3.3.3の解説。

Python チュートリアル — Python 3.3.3 ドキュメント
Python3.3.3のチュートリアル。

構造体 - Wikipedia
Structの解説。

Apache OpenOffice Developer's Guide - Apache OpenOffice Wiki
PDF版の表紙にOpenOffice.org 3.1 Developer's Guideと書いてあります。

Developer's Guide
OpenOffice Developer's Guideの日本語版。OpenOffice.org 2.0より前のもののようです。

LibreOffice: Namespace List
LibreOffice 4.2 SDK APIの入り口com.sun.star。

ハッシュ関数 - Wikipedia
Pythonのlistとtuple、rangeとの大きな違いはハッシュが作成されるかどうかです。

Python-UNO bridge
UNO Language bindingの項目にUNOのデータ型とPythonのデータ型との対応表があります。

OOoPython/OverView - ...?
PythonでのUNOの型の扱いなどについて日本語で例もつけて解説されています。

Python Language Binding - Apache OpenOffice Wiki
PythonでのUNOのモジュール関数について一覧があります。

Pythonマクロ作成環境について
オートメーションではUNOのモジュールをインポートする前にimport unoがないとImportError: No module named 'com'というエラーがでてきます。

OpenOffice.org BASIC プログラミングガイド - Apache OpenOffice Wiki
LibreOffice Basicにも使えます。

Extensions development basic ja - Apache OpenOffice Wiki
これを読めばOpenOffice Basicの使い方がひととおりわかります。

Transfer from Basic to Python - Apache OpenOffice Wiki
LibreOffice BasicをPythonに書き換える方法。

【教えてPythonのエロい人】固定長配列を一気に作るには? | okkyの日記 | スラッシュドット・ジャパン
固定長リストを作成する方法。

次の関連記事:LibreOffice(36)マクロの記録をPythonに翻訳2:反復部分を関数にする

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ