2010年1月30日土曜日

OpenOffice.org と Python マクロ 2 限目

今回は、OpenOffice.org Base で登録したデータベースにアクセスしてみる。

では、OpenOffice.org Base を起動する。
起動したら、適当に新規データベースを登録する。

登録されているデータベースを確認してみる。
メイン画面で、「ツール」メニュー > 「オプション」を選択する。
オプション画面が開いたら、「OpenOffice.org Base」 > 「データベース」を選択する。
「登録されたデータベース」に、いくつかデータベースがリストされているのを確認する。
ここに表示されれている登録名の一覧を表示するマクロを作成してみる。

以下のソースコードを helloDb.py としてマクロ格納先(1限目を参照)へ作成する。

#!
# -*- coding: utf_8 -*-

import uno

def helloDb():
    msg = getDataSourceNames()
    message(XSCRIPTCONTEXT.getDesktop(), msg, u"データベース一覧")

def getDataSourceNames():
    ctx = uno.getComponentContext()
    smgr = ctx.ServiceManager
    dbc = smgr.createInstance("com.sun.star.sdb.DatabaseContext")
    dbNames = dbc.getElementNames()
    result = ""
    for name in dbNames:
        result += name + ", "
    return(result)

def message(desktop, msg = "", title = ""):
    frame = desktop.getCurrentFrame()
    win = frame.getContainerWindow()
    toolkit = win.getToolkit()
    rect = uno.createUnoStruct("com.sun.star.awt.Rectangle")
    msgbox = toolkit.createMessageBox(win, rect, "messbox", 1, title, msg)
    msgbox.execute()

g_exportedScripts = helloDb,


実行すると、メッセージダイアログに登録されたデータベース名がリストアップされているはず。


次に、データベース(ここでは”資産管理”)に登録されているクエリーの一覧を取得してみる。

データベースに適当なクエリーを登録する。
以下のソースコードを helloDbQuery.py として保存する。

#!
# -*- coding: utf_8 -*-

import uno

def helloDbQuery():
    msg = getQueryNames()
    message(XSCRIPTCONTEXT.getDesktop(), msg, u"クエリー一覧")

def getQueryNames():
    ctx = uno.getComponentContext()
    smgr = ctx.ServiceManager
    dbc = smgr.createInstance("com.sun.star.sdb.DatabaseContext")
    ds = dbc.getByName(u"資産管理")
    con = ds.getConnection("", "")
    queries = con.getQueries()
    queryNames = queries.getElementNames()
    result = ""
    for name in queryNames:
        result += name + ", "
    return(result)

def message(desktop, msg = "", title = ""):
    frame = desktop.getCurrentFrame()
    win = frame.getContainerWindow()
    toolkit = win.getToolkit()
    rect = uno.createUnoStruct("com.sun.star.awt.Rectangle")
    msgbox = toolkit.createMessageBox(win, rect, "messbox", 1, title, msg)
    msgbox.execute()

g_exportedScripts = helloDbQuery,


実行すると、クエリー一覧が表示される。


次に、SQLを実行してみる。
(ここでは、「資産管理」データベースに「資産」テーブルを、カラム数を 3つで作成してあり、カラムタイプは、 VARCHAR 型としてある。作成した「資産」テーブルには、適当にレコードを追加しておいた。)
以下のソースコードを helloDbQueryResult.py として保存

#!
# -*- coding: utf_8 -*-

import uno

def helloDbQueryResult():
    msg = getQueryResult()
    message(XSCRIPTCONTEXT.getDesktop(), msg, u"クエリー結果一覧")

def getQueryResult():
    ctx = uno.getComponentContext()
    manager = ctx.ServiceManager
    dbctx = manager.createInstance("com.sun.star.sdb.DatabaseContext")
    ds = dbctx.getByName(u"資産管理")
    conn = ds.getConnection("", "")
    stmt = conn.createStatement()

    result = stmt.executeQuery(u"SELECT * FROM \"資産\"")
    msg = ""
    while(result != None and result.next()):
        msg += result.getString(3) + ", "
    return(msg)

def message(desktop, msg = "", title = ""):
    frame = desktop.getCurrentFrame()
    win = frame.getContainerWindow()
    toolkit = win.getToolkit()
    rect = uno.createUnoStruct("com.sun.star.awt.Rectangle")
    msgbox = toolkit.createMessageBox(win, rect, "messbox", 1, title, msg)
    msgbox.execute()

g_exportedScripts = helloDbQueryResult,


実行すると、テーブルに定義したデータの3カラム目の値が全て表示される。


次に、Prepared Statement を使ってみる。
以下のソースコードを helloDbPStmt.py として保存。

#!
# -*- coding: utf_8 -*-

import uno

def helloDbPStmt():
    msg = getQueryResult()
    message(XSCRIPTCONTEXT.getDesktop(), msg, u"クエリー結果一覧")

def getQueryResult():
    ctx = uno.getComponentContext()
    manager = ctx.ServiceManager
    dbctx = manager.createInstance("com.sun.star.sdb.DatabaseContext")
    ds = dbctx.getByName(u"資産管理")
    conn = ds.getConnection("", "")

    stmt = conn.prepareStatement(u"SELECT * FROM \"資産\" WHERE \"AssetName\" like ?")
    stmt.setString(1, u"%株式%")

    result = stmt.executeQuery()
    msg = ""
    while(result != None and result.next()):
        msg += result.getString(3) + ", "
    return(msg)

def message(desktop, msg = "", title = ""):
    frame = desktop.getCurrentFrame()
    win = frame.getContainerWindow()
    toolkit = win.getToolkit()
    rect = uno.createUnoStruct("com.sun.star.awt.Rectangle")
    msgbox = toolkit.createMessageBox(win, rect, "messbox", 1, title, msg)
    msgbox.execute()

g_exportedScripts = helloDbPStmt,


実行すると、3カラム目の内容に”株式”が含まれているものを、中間一致で検索した結果が表示される。

SQL を以下のようにいじった

stmt = conn.prepareStatement(u"SELECT AssetId, 'hoge', ? FROM 資産")

実行してみると、stmt.setString メソッドの箇所で以下のようなエラーとなった
com.sun.star.uno.RuntimeException: 'ascii' codec can't encode characters in position 71-72: ordinal not in range(128), traceback follows
  C:\Program Files (x86)\OpenOffice.org 3\Basis\program\uno.py:327 in function _uno_struct__repr__() [return repr(self.__dict__["value"])]
  C:\Program Files (x86)\OpenOffice.org 3\Basis\program\pythonscript.py:28 in function lastException2String() [ret = str(excType) + ": "+str(excInstance) + "\n" + \]
  C:\Program Files (x86)\OpenOffice.org 3\Basis\program\pythonscript.py:776 in function invoke() [text = lastException2String()]


はは~ん、資産をダブルクォートで囲んでないからだなぁ、と思って以下のように変えて実行した
stmt = conn.prepareStatement(u"SELECT AssetId, 'hoge', ? FROM \"資産\"")


ブブーッ!はずれ!
エラーの内容は変わらず・・・
ウンウン唸って考えて、以下のようにカラム名をダブルクォートで囲ってみた

stmt = conn.prepareStatement(u"SELECT \"AssetId\", 'hoge', ? FROM \"資産\"")


ピンポンピンポ~ン!正解!
試しに、テーブル名のダブルクォートを外してみたら、問題なく動いた・・・なぜ?

まあ、とりあえずは、なんでもダブルクォートで囲っておくことにするか・・・


ようやく、何とかなりそうな気がしてきた。
これで野望に近づける。

まあ、要は、データベースに保存した資産の情報から、現状の資産配分を知りたいのですよ。
データが入れ子になっているので、単純な SQL では難しい・・・

OpenOffice.org と Python マクロ 1 限目

最初に Python マクロの Hello World をと思ったが・・・わからない。なんせ日本語の情報が少ない。
仕方がないので、ネット上を這いずり回った。で以下の情報ソースを確保。

http://hermione.s41.xrea.com/pukiwiki/index.php?OOoPython
http://d.hatena.ne.jp/hanya_orz/20091214/p1
http://udk.openoffice.org/python/python-bridge.html
http://api.openoffice.org/docs/DevelopersGuide/DevelopersGuide.xhtml


気を取り直して作業続行。

まず、環境から整える。
  • OpenOffice.org 3.1.1
    まずこれがないとはじまらない。
  • Python 2.6.1
    マクロを作成するための、エディタとして IDLE を使いたかっただけ。単なるテキストエディタでも、eclipse の Pydev でも、とにかくテキスト編集できて、UTF-8 で保存できればなんでもよい。
IDLE からマクロ実行とかをしたかったのだが、できなかった。
OpenOffice.org は 32bit 版、Python は 64bit 版なのが悪いのかもしれない。
一応手順を書いておくが、この方法で出来る保障はないので悪しからず。
戦略としては、至って簡単。sys.path に UNO パッケージの(uno.py とかがある)場所を追加する。
IDLE 上で以下を実行してみた。(指定したパスは Vista 64bit 版での場合)

>>> import sys
>>> sys.path.append('C:\\Program Files (x86)\\OpenOffice.org 3\\Basis\\program')
>>> import uno

Traceback (most recent call last):
  File "", line 1, in
    import uno
  File "C:\Program Files (x86)\OpenOffice.org 3\Basis\program\uno.py", line 33, in
    import pyuno
ImportError: DLL load failed: %1 は有効な Win32 アプリケーションではありません。


と、怒られた。orz
懲りずに、eclipse(64bit)に Pydev を入れて、OpenOffice.org に付いてきた Python を利用しようとしたら、python.exe を指定したところで、エラーがでた。素直に 32bit にすれば問題ないのだろうが、生憎この PC は、64bit で地獄を見るためのものなので、狙い通りなのだ!

結局 IDLE は、便利なテキストエディタとして使うことにした。

では、再び気を取り直して。
マクロを格納する場所を確認する。Vista 64bit の場合は、以下に格納する。
python ディレクトリは、なかったので作成した。
<ユーザホーム>\AppData\Roaming\OpenOffice.org\3\user\Scripts\python

上記の場所に、helloWorld.py というファイル名で以下の内容を作成する。

#!
# -*- coding: utf_8 -*-

import uno

def helloWorld():
    desktop = XSCRIPTCONTEXT.getDesktop()
    frame = desktop.getCurrentFrame()
    win = frame.getContainerWindow()
    toolkit = win.getToolkit()
    rect = uno.createUnoStruct("com.sun.star.awt.Rectangle")
    title = "title"
    msg = u"メッセージ"
    msgbox = toolkit.createMessageBox(win, rect, "messbox", 1, title, msg)
    msgbox.execute()

g_exportedScripts = helloWorld,

ながい・・・もっとスマートな HelloWorld はないものか・・・
最後の g_exportedScripts は、マクロ実行画面で表示する関数を定義している。この定義をしなかった場合は、ファイル中の全ての関数がリストされる。ちなみに、行の最後のカンマは、タプル値として代入するために必要。

ファイルを作成したら、OpenOffice.org Calc (Base でも Writer でもなんでもいい)を起動して、「ツール」メニュー > 「マクロ」 > 「マクロを実行」を選択。
マクロの選択ダイアログが表示される。
ライブラリの中から、「マイマクロ」 > 「helloWorld」を選択すると、マクロ名に「helloWorld」が表示されるので、選択して「実行」ボタンを押す。
画面にメッセージダイアログが表示されれば成功。