LibreOffice(42)UNOオブジェクトの属性1:正規表現パターンを作成

ラベル: ,

前の関連記事:LibreOffice(41)埋め込みマクロ3:呼び出し元に作用させる


LibreOffice(40)埋め込みマクロ2:値の引渡しと受け取りでオートメーションでprint(UNOオブジェクト)とするとUNOオブジェクトの属性をPyCharmでみれることに気がつきました。とても見にくいので正規表現を使って各属性を抜き出す方法を考えます。

str(UNOオブジェクト)でUNOオブジェクトの属性が得られる


print(UNOオブジェクト)で属性が得られたのでPythonのprintの項printの項を読んでみると「str() がするように文字列に変換され」と書いてあります。
#libreoffice42.py
def libreoffice42():
    desktop = XSCRIPTCONTEXT.getDesktop() #デスクトップオブジェクトを取得。
    atr = str(desktop) #デスクトップオブジェクトを文字列に変換。
    print(atr) #標準出力に表示。
if __name__ == "__main__":
    import unopy
    XSCRIPTCONTEXT = unopy.connect()
    if not XSCRIPTCONTEXT:
        print("Failed to connect.")
        import sys
        sys.exit(0)
    libreoffice42()
ということでこのようにstr()でデスクトップを文字列に変換指定見るとその属性が得られました。

print()の結果は標準出力に表示されるのでこれはPyCharmからオートメーションで実行しないと結果が見れません。
pyuno object (com.sun.star.uno.XInterface)0x2e11214{implementationName=com.sun.star.comp.framework.Desktop, supportedServices={com.sun.star.frame.Desktop}, supportedInterfaces={com.sun.star.lang.XServiceInfo,com.sun.star.frame.XDesktop2,com.sun.star.frame.XTasksSupplier,com.sun.star.frame.XDispatchResultListener,com.sun.star.task.XInteractionHandler,com.sun.star.frame.XUntitledNumbers,com.sun.star.lang.XTypeProvider,com.sun.star.uno.XWeak,com.sun.star.beans.XPropertySet,com.sun.star.beans.XPropertySetOption,com.sun.star.beans.XMultiPropertySet,com.sun.star.beans.XFastPropertySet}}
これは上記のスクリプトで得られたデスクトップの属性です。

ドキュメントの属性だとこれの4倍ぐらいもあります。

これを見やすくしていきます。

PyRegexでPythonの正規表現の結果を見る


Pythonの正規表現の解説は6.2. re — 正規表現操作正規表現 HOWTOにありますが、まずは正規表現の使い方を学習しないといけません。

そこでPyRegexというリアルタイムに正規表現を反映させることができるツールを使って試行錯誤で正規表現を試します。


上のテキストボックスに正規表現パターン、下のテキストボックスに対象文字列を入力すると、その下に続くコマンドに従って右側に結果がすぐに表示されます。

先ほどのデスクトップオブジェクトの属性を対象文字列のテキストボックスに入力します。

対象文字列をみるとUNOオブジェクトの型は丸括弧に囲まれた部分になっています。

それを得るために正規表現パターンに「\(.+?\)」を入力します。

「\(」は 「(」、「.」は任意の一文字、「+」はその前の「.」が一文字以上あるということを示します。

「?」はその左の条件が成立する文字列の長さが最小になるようにします。(6.2.1. 正規表現の構文の「*?, +?, ??」の項の非貪欲 (non-greedy)マッチの指定。)

これがないともし対象文字列に「)」が2個あると2個目の「)」まで含めてしまいます。

最後の「\)」は「)」ですので、「(任意の1文字以上の文字)」、にあてはまるものが抽出されるはずです。

デフォルトではmatchメソッドになっており、丸括弧の部分が対象文字列の先頭にないので「No results found.」と言われます。


そこで先頭以外の部分からも抽出できるように「match」を「search」へ変更します。

\(.+?\)

狙い通り「(com.sun.star.uno.XInterface)」が抽出されました。

丸括弧の中身を取り出すにはグルーピングを使って正規表現を「\((.+?)\)」とすると丸括弧で囲んだ「.+?」に該当する部分をGroupの1に取り出すことができ丸括弧を除いた「com.sun.star.uno.XInterface」がgroup(1)で得られます。
#libreoffice42.py
import re #正規表現モジュールのインポート。
def libreoffice42():
    regex = r"\((.+?)\)" #raw stringsで正規表現パターンを指定。
    test_string = str(XSCRIPTCONTEXT.getDesktop()) #UNOオブジェクトXSCRIPTCONTEXT.getDesktop()を文字列に変換する。
    flags = 0
    match = re.compile(regex, flags).search(test_string) #マッチオブジェクトを取得。
    txt = match.group(1) #マッチオブジェクトからサブグループの1番目を得る。
    print(match) #Pythonの標準出力に出力する。
if __name__ == "__main__":
    import unopy
    XSCRIPTCONTEXT = unopy.connect()
    if not XSCRIPTCONTEXT:
        print("Failed to connect.")
        import sys
        sys.exit(0)
    libreoffice42()
print()の出力結果はPyCharmに結果画面に出力されるのでこれはオートメーションで実行しないといけません。

4行目の正規表現パターンの指定でraw stringsを使っています。

「\」は正規表現の特殊文字をエスケープ(「(」を「(」として認識させるために「\(」と書くこと)するためによく使います。

しかし「\」自体がPythonでは特殊文字になるので「\」を「\」と認識させるためには「\」を「\\」と書かないといけません。(6.2.5.8. Raw 文字列記法)

ですのでraw stringsを使わなければ4行目は次のように書けます。
    regex = "\\((.+?)\\)"
でもこの書き方ですと正規表現とPythonの両方のエスケープを考えないといけないのでややこしくなります。

パターン文字列の前に「r」を付けてraw stringsを使ってregex = r"\((.+?)\)"と書いた方がわかりやすいです。

7行目で正規表現パターンを使って対象文字列から該当部分を抽出しています。

PyRegexにならって正規表現パターンをまずコンパイルしています。(re.compile(pattern, flags=0))
    match = re.search(regex, test_string, flags)
このようにre.search(pattern, string, flags=0)を使ってコンパイルを暗黙的にすることもできます。

8行目でmatch.group([group1, ...])でマッチオブジェクトから必要な要素を取り出しています。

group(0)やgroup()では正規表現パターンで該当した部分全部が得られます。

グルーピングを使っているときはgroup(1)で1番目のグループが取り出せます。

グルーピングの要素全てをタプルで得るにはmatch.groups(default=None)を使います。

PyRegex

re.findall(pattern, string, flags=0)はPyRegexではSuccess!か否かしかわかりませんね。

6.2.5.6. すべての形容動詞を見つけるの例では['carefully', 'quickly']というリストが返るはずなのですが、それは表示されません。

属性の各項目を抽出する


\((.*?)\).*?\{.*?=(.*?),.*?\{(.*?)\}.*?\{(.*?)\}

あれこれ試行錯誤して全項目を抜き出す正規表現パターンができました。

これでグループの1から4まで、継承している型、実装名、サービス名、インターフェイス名の一覧が得られました。

ただこの方法ですと一つでも項目が抜けたりするともう動きません。

もう少し汎用的な方法を考えます。

最初の「継承している型」を除き、他は全て「=」で項目名と項目内容がつながっていることに着目します。

\w+?(?==)

これで「=」の左辺が抽出できました。

PyRegexではfindallの結果が表示されないのでオートメーションで確認します。
#libreoffice42.py
import re
def libreoffice42():
    regex = r"\w+?(?==)"
    test_string = str(XSCRIPTCONTEXT.getDesktop())
    match = re.findall(regex, test_string)
    print(match)
if __name__ == "__main__":
    import unopy
    XSCRIPTCONTEXT = unopy.connect()
    if not XSCRIPTCONTEXT:
        print("Failed to connect.")
        import sys
        sys.exit(0)
    libreoffice42()
これでリスト['implementationName', 'supportedServices', 'supportedInterfaces']が得られました。

今度は「=」の右辺です。

まずは波括弧{}で囲われている場合。

(?<==)({)([^}]+?)(?(1)})

findallの結果は上のlibreoffice42.pyの4行目のregexの代入するraw stringsを変更すると得られます。

グルーピングを2つ使っているのでfindallの結果はタプルのリストが得られそのタプルの2列目の要素に得られます。

「{」で始まる時は「}」で終わる範囲を抽出します。

入れ子の括弧からの抽出は正規表現では苦手な分野のようですが今回は幸い「=」があるのでそれを目印に左波括弧を指定して、続く右波括弧を得ます。

「=」の後ろが波括弧で囲われていない場合は「,」で終わる範囲を抽出するようにします。

(?<==)({)?([^}]+?)(?(1)}|,)

これで実装名、サービス名、インターフェイス名の内容が全て得られました。

今度は両辺を同時に取得するパターンです。

(\w+?)=({)?([^}]+?)(?(2)}|,)

「({)」のグループがずれるので(?<==)({)?([^}]+?)(?(1)}|,)のグループ番号を1から2へ変更しています。

[('implementationName', '', 'com.sun.star.comp.framework.Desktop'), ('supportedServices', '{', 'com.sun.star.frame.Desktop'), ('supportedInterfaces', '{', 'com.sun.star.lang.XServiceInfo,com.sun.star.frame.XDesktop2,com.sun.star.frame.XTasksSupplier,com.sun.star.frame.XDispatchResultListener,com.sun.star.task.XInteractionHandler,com.sun.star.frame.XUntitledNumbers,com.sun.star.lang.XTypeProvider,com.sun.star.uno.XWeak,com.sun.star.beans.XPropertySet,com.sun.star.beans.XPropertySetOption,com.sun.star.beans.XMultiPropertySet,com.sun.star.beans.XFastPropertySet')]

findallではこのようなタプルのリストが得られました。

各タプルの0番目の要素に左辺、2番目の要素に右辺が入っています。

最後に「=」で結ばれていない「継承している型」も同じパターンで得るために\((.+?)\)(\w+?)=({)?([^}]+?)(?(2)}|,)をor演算子の「|」でつなぎます。

\((.+)\)|(\w+?)=({)?([^}]+?)(?(3)}|,)

グルーピングが前にさらに1個追加されたので2番から3番にずらしています。

[('com.sun.star.uno.XInterface', '', '', ''), ('', 'implementationName', '', 'com.sun.star.comp.framework.Desktop'), ('', 'supportedServices', '{', 'com.sun.star.frame.Desktop'), ('', 'supportedInterfaces', '{', 'com.sun.star.lang.XServiceInfo,com.sun.star.frame.XDesktop2,com.sun.star.frame.XTasksSupplier,com.sun.star.frame.XDispatchResultListener,com.sun.star.task.XInteractionHandler,com.sun.star.frame.XUntitledNumbers,com.sun.star.lang.XTypeProvider,com.sun.star.uno.XWeak,com.sun.star.beans.XPropertySet,com.sun.star.beans.XPropertySetOption,com.sun.star.beans.XMultiPropertySet,com.sun.star.beans.XFastPropertySet')]

これで属性のいずれかが抜けても正規表現パターンが一致しないというエラーはでないはずです。

グループ1は継承している型、グループ2は=の左辺、グループ4は=の右辺が入ります。

それぞれがfindallで返されるリストの各要素のタプルの要素になっています。

タプルの0番目の要素がグループ1になっています。

次はこの情報を見やすく加工します。

最初はさっぱりわからなかった正規表現でしたが順番に処理していくとほしいパターンがうまくできましたね。

PyRegexはとても便利です。

findallの結果も表示されるともっといいですね。

私が最初につまづいたのは6.2.1. 正規表現の構文の「*?, +?, ??」の項の非貪欲 (non-greedy)マッチの指定。

MS-DOSのワイルドカードの?しか知らなかったのですが、正規表現にやたら出てくる?の意味がわかりました。

あとはこれ「(?(id/name)yes-pattern|no-pattern)」。

これはつまずいたというよりこれをみて波括弧の中身を抽出する方法がわかりました。

この項目の例(<)?(\w+@\w+(?:\.\w+)+)(?(1)>|$)(<)(\w+@\w+(?:\.\w+)+)(?(1)>|$)としないと'<user@host.com'にもマッチしてしまいます。

これは誤植のような気がしましたがi don't understand this RE example from the documentation - Pythonでは誰も同意してくれていないようなので間違っていないのかもしれません。


参考にしたサイト


2. 組み込み関数 — Python 3.3.3 ドキュメント
print()やstr()の解説が載っています。

6.2. re — 正規表現操作 — Python 3.3.3 ドキュメント
Pythonの正規表現の解説。

正規表現 HOWTO — Python 3.3.3 ドキュメント
Pythonの正規表現の易しい解説。ところどころ未翻訳部分があります。

PyRegex
Pythonの正規表現をいろいろ試せます。

UNO の関連ツール - Apache OpenOffice Wiki
BasicではDBG_propertiesやDBG_methods、DBG_supportedInterfacesで属性が得られます。

次の関連記事:LibreOffice(43)UNOオブジェクトの属性2:メッセージボックスに表示

PR

0 件のコメント:

コメントを投稿