Top > OOoPython > Memo

メモ Edit

Py-UNO を利用していて他の言語 (OOo Basic など) とは違った挙動を示したときなど。

それ以外も。

Python 関連 issue Edit

  • Tkinter が動かない話 i30349

Python... メニュー Edit

Python スクリプトをマクロとして実行しているときにツール - マクロ - マクロの管理 - Python とたどるのは面倒。BookmarksMenu などで次のような Command URL をメニューに追加する。

.uno:ScriptOrganizer?ScriptOrganizer.Language:string=Python

Python の部分は Java など他のものにも変更できます。

普段は pipe などで接続したり Basic から実行したりしていますが、.frame.Desktop から getCurrentComponent などとしているときには時々ドキュメントのフレームから実行することがあるので。

__name__ Edit

Python のマクロを scripting framework で実行する際の __name__ = 'ooo_script_framework'

DEBUG Edit

OOo 2.4.1 の pythonloader はうるさい。Linux 環境で標準出力に色々吐き出してくれる。これは pythonloader.py ファイルの 45 行目付近にある DEBUG が 1 になっているため。

DEBUG = 0

に書き換えて黙っててもらう。色々吐かれると UNO コンポーネントの出力が分かりにくくなる…。

OOo 2.4 -> OOo 2.4.1 で fileUrlToSystemPath などの修正をしたときに戻されなかったらしい。

Python バージョン Edit

OOo 2.4 現在、OOo に付いてくる Python のバージョンは 2.3.4。

  • 問題点
    • 日本語コーデックが入っていない。shift_jis や euc-jp が使えない
    • 色んな新しい構文が使えない

OOo 3.1 以降では Python 2.6.1

Py-UNO がインストールされていない場合のエラー Edit

Py-UNO を利用して作成された拡張機能をインストールするときに、Py-UNO がインストールされていない場合には次のようなエラーが出ます。

ImplementationRegistration::registerImplementation() - The service
com.sun.star.loader.Python cannot be instantiated

non_python.png

Py-UNO による拡張機能をアップデートするときのエラー Edit

Py-UNO を利用した拡張機能をアップデートするときに、そのまま上書きしようとすると出ることがあるエラー。(2.4 以降では出ないような気がします。)

installation-exception.png

Py-UNO による拡張機能のインストール時の不明なエラー Edit

インストール時に出るらしい不明なエラー。詳細不明。

pythonloader.png

このエラーが出る場合にはすべての Py-UNO 拡張機能が利用できません。が、なぜか Scripting FrameWork Script の Python スクリプトは動くらしいです。

pythonloader.py が壊れている様子。

Python スクリプトの実行 Edit

Python スクリプトが実行される経過。

すべての Scripting 言語に対して LANGUAGE + ScriptProvider がサービスがあり、そのサービスが実行できるファイルや関数などを調べて情報を提供します。Python では com.sun.star.script.provider.PythonScriptProvider サービスがそれに相当します。 このサービスは program/pythonscript.py の PythonScriptProvider クラスで実装されています。

  1. ユーザーなどからの要求で ScriptProviderFactory が PythonScriptProvider のインスタンスを作成
  2. 言語に対応した Script/python ディレクトリ内のファイルを調べる
  3. (Python スクリプトで実行されるのは function なので一度ファイルが読み込まれる必要がある)
  4. スクリプトを compile でコンパイルする。このとき、コンパイルエラーなどが出るとツール - ... - Python のダイアログに表示されないことになります。
  5. getScript で指定された実行可能関数を含む PythonScript を返す。(ここでもう一度コンパイルされている?)
  6. invoke される。と、関数に引数を与える。
  7. 実行結果を返す。exception はトラップされて表示されます。

エクスポートオプション Edit

エクスポート時に FilterData を与えることがあるが FilterData の型を指定しないと失敗する。PDF エクスポート 参照。

Any と invoke Edit

uno.Any を使うときには uno.inovoke を利用します。これは Invokation bridge によりメソッドを呼び出します。

これが必要なのは any 型に対して com.sun.star.beans.PropertyValue などの tuple を渡すときです。ブリッジを通 じての値の型問題によるようです。

代表的なのは container モジュールのインターフェースのメソッドです。次のようなコードは失敗します。

  0
  1
  2
  3
  4
  5
 from com.sun.star.beans imoprt PropertyValue
 p = PropertyValue()
 p.Name = "CommandURL"
 p.Value = ".uno:Paste"
 
 mgr.insertByIndex(0, tuple(p))

これの代わりに次のようにします。

  0
  1
  2
  3
 import uno
 
 any_value = uno.Any("[]com.sun.star.beans.PropertyValue", tuple(p))
 uno.invoke(mgr, "insertByIndex", (0, any_value,))

uno.Any と uno.invoke の詳細は uno.py 参照。invoke は Py-UNO ブリッジのソースを見ないと分かりませんが。

.container.XNameAccess Edit

ドキュメントの DialogLibraries から Standard ライブラリを取得、さらにダイアログ名全てを getElementNames で取得しようとしたとき。

ダイアログがあるときには tuple で要素名が戻るが、一つも無いときにはなぜか uno.ByteSequence のインスタンスが戻る。事前に .container.XElementAccess の hasElements で調べておくのが正解。

OOo Basic ではこの場合、空の配列が戻る。一方、introspection から getElementNames メソッドを invoke すると空の tuple か None が戻る (どちらか未確認)。

UNO コンポーネントの initialize Edit

Py-UNO で実装した UNO コンポーネントの初期化が必要な場合に com.sun.star.lang.XInitialization インターフェースを実装する。このインターフェースのメソッドは initialize。

void initialize( [in] []any aArgs )
    raise( com.sun.star.uno.Exception )

Java などで実装した UNO コンポーネントはこのメソッドを通じて (ある意味) 初期化される。DispatchProvider や StatusbarController、PopupMenuController などのフレームごとのサービスではそのフレームごとにインスタンスが作成されるため、それに対応してそのフレームへの参照が渡される。

上記のような初期化が行われると IDL リファレンスなどに記載があるが、Py-UNO コンポーネントで XInitialize インターフェースを実装していてもなぜか initialize メソッドは呼ばれない時がある。

Py-UNO コンポーネントの場合、initialize メソッドで初期化時に渡される引数 (上記では aArgs) は __init__ と同時に渡される!。

しかも、

  • 引数の数は OOo のバージョンによって変化することもある
  • (普通、aArgs は []com.sun.star.beans.PropertyValue) tuple のはずの引数が展開して渡される

というわけで、Py-UNO コンポーネントで XInitialize インターフェースを通じて初期化される場合には、__init__ メソッドの引数はそれぞれ次のような感じにすると吉。

  1. もちろん self
  2. ctx (component context)
  3. *args (残り全部)

import Edit

2.4 から独自のモジュールを import しやすくなりましたが、それ以前ではインポートできないというわけではありません。

以下は Python マクロとして作成。ファイル読み込み時に sys.path に Script/python へのパスを追加しています。

また、このとき import しているファイルが変更されても変更が適用されないようです。reload してやる必要があります。import したライブラリを修正した後で reload が必要なのは 2.4 以降でも変わりません。

  0
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
import uno
import sys
 
 
def py_test():
    # reload module B
    reload(B)
    print B.Var
    return
 
 
ctx = XSCRIPTCONTEXT.getComponentContext()
smgr = ctx.ServiceManager
ps = smgr.createInstanceWithContext(
    "com.sun.star.util.PathSubstitution", ctx )
python_url = ps.substituteVariables( "$(user)/Scripts/python", True )
python_path = uno.fileUrlToSystemPath( python_url )
 
if not python_path in sys.path:
    sys.path.append( python_path )
    
import B

import 問題 Edit

2.4 から容易にインポート出来るようになりました。これは、sys.path に実行されるスクリプトと同じディレクトリにある pythonpath ディレクトリまたは pythonpath.zip ファイルのパスが追加されるためです。

この path への追加は実行時に行われるため、拡張機能内にあるスクリプトが実行されると新しくエントリが追加されることになります。

別の拡張機能でも同じように追加されます。ここで、前のエントリに含まれるモジュールと同じ名前のモジュールがあると衝突して先に見つかった方がインポートされてしまいます。

パッケージに入れたりして解決しましょう。

これが起きる原因は OOo のプロセス内に存在する Python のインスタンスが一つだけなため。

import 問題2 Edit

python では先に見つかったものが利用されます。

二つの拡張機能パッケージに、次のような python のパッケージがあったとき競合が起きて後で sys.path に追加された方はインポートに失敗します。

A を先に実行した場合、mytools パッケージにある modA からインポートする。その後で B を実行すると検索されるのが A に含まれるパッケージの後なので modB が見つからないことになります。

  • A
    • pythonpath
      • mytools
      • /modA
  • B
    • pythonpath
      • mytools
      • /modB

これは普通の Python の挙動です。が、普通は別々に置いたりしません。一方で OOo の拡張機能は別々のディレクトリに配置されてしまうため、パッケージ名が衝突する可能性が出てきます。

要約すると、以下参照。multiple_source_files

Python スクリプトの URI (URL) Edit

Python スクリプトを実行する時の URL は次のような感じになります。

vnd.sun.star.script:FILE_NAME.py$FUNCTION_NAME?language=Python&location=user

ここで、FILE_NAME は user/Script/python ディレクトリからの相対パスになります。(./ や ../ などは不可(?)。)

A.py ファイルが python/test ディレクトリにあるときには次のように指定します。

vnd.sun.star.script:test|A.py$py_test?language=Python&location=user

URL を記述するのは面倒ですが、BookmarksMenu 拡張機能の mytools_BookmarksMenu scriptbrowser モジュールの ScriptBrowsing を実行すると簡単に URL を取得できます (単独で利用可)。

Python スクリプトの URI 2 (拡張機能) Edit

拡張機能に Scripting Framework Script として Python スクリプトを入れた場合の URL。

拡張機能に SFS として入れる場合には manifest.xml は次の様になります。

 <manifest:file-entry manifest:full-path="python/" 
   manifest:media-type="application/vnd.sun.star.framework-script"/>

python ディレクトリに NAME.py などのファイルを入れておきます。

拡張機能パッケージの名前が NAME_VERSION.oxt などの場合 URI は次の様になります。

vnd.sun.star.script:NAME_VERSION|python|NAME.py$FUNCTION?language=Python&location=user:uno_packages

URI に拡張機能パッケージ名が入るためバージョンなどが変わると一々書き換えなければいけなくなります。

また、Base URI に ../ などの相対パスが含まれていると実行に失敗します。

Python マクロの実行 Edit

Scripting Framwork Script として Python マクロを書いているときに実行を一々、ツール - マクロ - マクロの管理 - Python... とするのは面倒。

大抵 Basic IDE が開いているので次のような OOo Basic のコードを書いておいて Basic プログラムの実行ボタンを押してやり Python スクリプトを実行する。

sURI を書き換えるだけで実行するスクリプトを変更できます。

  0
  1
  2
  3
  4
  5
  6
  7
  8
  9
Sub py_testv
  sURI = "flw4.py$a_test"
  sURI = "vnd.sun.star.script:" & sURI & "?language=Python&location=user"
  oMSPF = GetDefaultContext().getValueByName( _
    "/singletons/com.sun.star.script.provider.theMasterScriptProviderFactory")
  oSP = oMSPF.createScriptProvider("")
  oScript = oSP.getScript(sURI)
  sTxt = oScript.invoke(Array(),Array(),Array())
  'megbox sTxt
End Sub

現在のロケールを取得 Edit

現在のロケールをコンフィグレーションから取得して .lang.Locale にして戻す。

  0
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
from com.sun.star.lang import Locale
 
def get_current_locale(ctx):
    """ get current locale value and returns .lang.Locale struct """
    cp = ctx.ServiceManager.createInstanceWithContext(
        "com.sun.star.configuration.ConfigurationProvider", ctx)
    props = PropertyValue()
    props.Name = "nodepath"
    props.Value = "/org.openoffice.Setup/L10N"
    cra = cp.createInstanceWithArguments(
        "com.sun.star.configuration.ConfigurationAccess", (props,))
    locale_str = cra.getPropertyValue("ooLocale")
    
    parts = locale_str.split("-")
    if len(parts) == 2:
        return createLocale(parts[0],parts[1])
    else:
        return createLocale(parts[0],"")
 
def createLocale(lang,country):
    aLocale = Locale()
    aLocale.Language = lang
    aLocale.Country = country
    return aLocale

拡張機能ディレクトリ URL の取得 Edit

拡張機能 ID を設定しておく必要があります。拡張機能のターゲットが 2.3 以降であれば if 文の前半までしか必要ありません。else 以降は 2.0.4-2.2 もターゲットにする場合には必要。

  0
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
def get_extension_dirurl(ctx,id):
    """ get extension directory url from the extension id """
        
    pip_name = "/singletons/com.sun.star.deployment.PackageInformationProvider"
    if ctx.hasByName(pip_name):
        pip = ctx.getByName(pip_name)
        return pip.getPackageLocation(id)
    else:
        pmf_name = "/singletons/com.sun.star.deployment.thePackageManagerFactory"
        if ctx.hasByName(pmf_name):
            pmf = ctx.getByName(pmf_name).getPackageManager("user")
            packages = pmf.getDeployedPackages(pmf.createAbortChannel(),None)
            for package in packages:
                if package.getIdentifier().Value == id:
                    uri = package.getURL()
                    expander = ctx.getByName("/singletons/com.sun.star.util.theMacroExpander")
                    return expander.expandMacros(uri)[21:]
                    break
    return ""

[] Edit

[] は Python だと空のリスト。UNO で []long などと表記する時には UNO の Sequence で中身が long であることを示す。Py-UNO の UNO 側で Sequence のときには Python 側では tuple。

この [] を使った表記は内部で利用されているもので、IDL ガイドなどでは sequence<long> などのようになっている。IDL ファイルを書くときや Reflection、TypeDetection などを利用する時には []long などの表記を使う。

[][]long だと Sequence の Sequence で内容が long 型。

(
  (1,2,3),
  (4,5,6)
)

また、UNO の Sequence は内容が単一の型のみ許容。Any も一つの決まった型なので com.sun.star.beans.PropertyValue など様々な値を保持する場合には Any が使われている。

IDL に定義された型以外の値を渡そうとすると (メソッドや要素などに) そのときに例外が送出される。

UNO の TypeClass に Array の定義があるがこれは使われない。

UNO の型定義は com.sun.star.uno.TypeClass enum にある。

XTransferable Edit

Python のユニコード文字を XTransferable インターフェースを利用してコピーできるようにしようとしたが、 text/plain;charset=utf-8 ではうまく行かない。そもそも Basic IDE では ...=utf-16 しか要求がこない。といっても、エディタに貼り付けも失敗する。

ascii 文字しかなかったので、次のようにしておいた。

df = MimeType()
df.MimeType = u'text/plain;charset=utf-16'

data = text.encode('ascii')

fileUrlToSystemPath Edit

OOo では file:/// 形式でファイルパスを取り扱うが、システムパスに変換したいときには fileUrlToSystemPath メソッドを利用することがある。しかし、このメソッドは日本語などの文字があると失敗する。

urllib で変換しようとすると、次のようにする必要があるらしい。OOo 側から Py-UNO ブリッジを通じて来る文字列はいつも Python のユニコード文字。

import urllib
st = u'mojimoji'
en = urllib.unquote(st.encode('ascii'))
print unicode(en,'utf8')

2.4.1 で修正予定。i86251

修正済みのバイナリを試してみたが、変わりなし。URL エンコードされた "file:///(略)%E3%83%87%E3%82%B9%E3%82%AF%E3%83%88%E3%83%83%E3%83%97" (デスクトップ)をファイルパスに変換しようとしてみたがエラー。

UnicodeEncodeError: 'ascii' codec can't encode 
characters in position 35-40: ordinal not in range(128)

UNO コンポーネント作成 Edit

UNO コンポーネントを作成する場合、登録が必要なコンポーネント以外では外部からオートメーションとして接続して作成するほうが手間が少なくて済む。

次のようなもので行うと楽。Windows での OOo インストールでは python23.dll が program に配置されているが、実行する Python は program/python-2.3.4/bin/python.exe なので、program にパスを通しておかないと失敗する。もちろん、tcp コネクトでもよいが Win 環境の OOo 2.4 では tcp コネクトでバグがあるので注意。

  0
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
#!
# -*- coding: utf_8 -*-
 
import sys
#sys.path.append("/opt/openoffice.org2.4/program")
sys.path.append("C:\\usr\\local\\OOo\\OOo2.4\\program")
 
import uno
import unohelper
 
def test(ctx):
    smgr = ctx.ServiceManager
 
def connect():
    try:
        localctx = uno.getComponentContext()
        resolver = localctx.ServiceManager.createInstanceWithContext(
            "com.sun.star.bridge.UnoUrlResolver",localctx)
        ctx = resolver.resolve(
            "uno:pipe,name=pypipe;urp;StarOffice.ComponentContext")
    except:
        return None
    return ctx
 
# starting OOo with this command.
# /opt/openoffice.org2.4/program/soffice 
# '-accept=pipe,name=pypipe;urp;StarOffice.ServiceManager'
 
 
if __name__=="__main__":
    ctx = connect()
    if ctx == None:
        print "Failed to connect."
        sys.exit()
    
    test(ctx)

stream への保存 Edit

storeAsURL メソッドなどでエクスポートするときに private:stream へ保存することができる。OutputStream で指定されたストリームへデータが渡されてディスク上に書き込まれない。

com.sun.star.io.Pipe などを使用してもよいが、com.sun.star.io.XOutputStream インターフェースを備えたクラスを代わりに使用できる。必要に応じて XInputStream も継承する。

  0
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
import unohelper
from com.sun.star.io import XOutputStream #, XInputStream
 
class PyPipe(unohelper.Base, XOutputStream):#, XInputStream):
    def __init__(self):
        self.txt = []
    
    def get(self):
        return u''.join(self.txt)
    
    # XOutputStream
    def writeBytes(self, aData):
        self.txt.append(aData.value)
    def flush(self):
        pass
    def closeOutput(self):
        pass
    
    # XInputStream
    def readBytes(self, aData, n):
        pass
    def readSomeBytes(self, aData, n):
        pass
    def skipBytes(self, n):
        pass
    def available(self):
        return sum([len(t) for t in self.txt])
    def closeInput(self):
        pass

wxPython Edit

OOo の GUI ツールキットである awt ツールキットはいろいろなコントロールを提供してくれない。また、TKinter は動かない?ので他の GUI ツールキットとして wxPython を OOo の Python から利用してみよう。

Windows XP、OOo 2.4 で試してみた。簡単に追加、削除およびインポートできるように拡張機能パッケージ化して入れてみることに。

  1. wxPython をダウンロードして来る。wxPython2.8-win32-unicode-2.8.7.1-py23.exe
  2. これを適当な場所にインストール
  3. site-packages\wx-2.8-msw-unicode の中身を取り出す
  4. META-INF/manifest.xml ファイルはヘッダとフッタのみ
  5. description.xml ファイルは拡張機能 ID を設定する
  6. ZIP アーカイブでパッケージ化するときのディレクトリ構成
    • wx
    • wxPython
    • docs
    • META-INF/manifest.xml
    • description.xml
  7. インストール

インストールは特に登録するファイルがないため解凍されるだけです。

wx モジュールをインポートするには次のように拡張機能 ID を指定して PackageInformationProvider サービスを使ってディレクトリパスを取得、sys.path のインクルードパスに追加します。

Scripting Framework Script として実行する場合は次のような形になる。

  0
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
import sys
import uno
 
 
def get_extension_dirurl(ctx,extid=""):
    """Get extension directory url from the extension id."""
    pip_name = "/singletons/com.sun.star.deployment.PackageInformationProvider"
    if ctx.hasByName(pip_name):
        pip = ctx.getByName(pip_name)
        return pip.getPackageLocation(extid)
    return ""
 
def make_import_wxPython(ctx):
    """Add path to the wxPython to sys.path."""
    platform = sys.platform
    
    if platform[:3] == 'win':
        wxPython_id = 'python.wxPython-windows_x86'
    elif platform[:5] == 'linux':
        wxPython_id = 'python.wxPython-linux_x86'
    else:
        wxPython_id = ''# 'python.wxPython-linux_x86'
    
    wxPython_url = get_extension_dirurl(ctx,wxPython_id)
    wxPython_path = uno.fileUrlToSystemPath(wxPython_url)
    
    if not wxPython_path in sys.path:
        sys.path.append(wxPython_path)
 
 
ctx = XSCRIPTCONTEXT.getComponentContext()
 
make_import_wxPython(ctx)
 
 
import wx
 
# code...

linux だとなんかエラーが出るので不明。

ツールバーのボタンに Python マクロを割り当てる Edit

ツールバーのボタンに Python で記述したマクロを割り当てる場合には、実行時に引数が与えられることを考慮する必要があります。

  0
  1
  2
def button(*args):
    print(args)
#>>(0,)

特に必要な引数ではありませんが、不要な引数でも受け取るようにしておかなければ exceptions.TypeError になります。

ssl Edit

3.3 からは ssl モジュールが利用できます (Windows 上のみ)。winpdb デバッガなども容易に利用できるようになります。


Attach file: filepythonloader.png 330 download [Information]

Reload   New Lower page making Edit Freeze Diff Upload Copy Rename   Front page List of pages Search Recent changes Backup   Help   RSS of recent changes