試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

OpenOffice.orgのマクロをPythonで記述して動かす(Calc上でマクロが動作しているかのチェックを改善)

OpenOffice.orgのマクロをPythonで記述して動かす(Calc上でマクロが動作しているかのチェックと全シートのオブジェクトの取得に関するメモ)」では「XSCRIPTCONTEXT.getDocument()」で得たドキュメントオブジェクトのメンバ関数supportsService()の結果によって自作クラスのOOoCalcの中のメンバsupportedを設定し、オブジェクト生成後にこれをチェックするという流れをとっていたが、これはクラスの利用(マクロ本体の関数)側の処理の流れを制限し、オブジェクトの使い方によっては意図しないエラーが出ることにもなって好ましくないと思い、ユーザ定義例外を用いて処理を変更することにした。

class NotOOoCalcException(Exception):
  pass

class Bridge(object):
  def __init__(self):
    self._desktop = XSCRIPTCONTEXT.getDesktop()
    self._document = XSCRIPTCONTEXT.getDocument()
    self._frame = self._desktop.CurrentFrame
    self._window = self._frame.ContainerWindow
    self._toolkit = self._window.Toolkit
  def run_errordialog(self, title='', message=''):
    ...

class OOoCalc(Bridge):
  def __init__(self):
    Bridge.__init__(self)
    if not self._document.supportsService('com.sun.star.sheet.SpreadsheetDocument'):
      self.run_errordialog(title='エラー', message='このマクロはOpenOffice.org Calcの中で実行してください')
      raise NotOOoCalcException()
    # 以下、Calc固有のオブジェクトの取得などが続く

def macroname():
  try:
    calc = OOoCalc()
  # Calcの中かどうかをチェックする
  except NotOOoCalcException:
    return
  # 以下、Calc固有の処理が続く

「このマクロは...」のエラーダイアログの表示処理は各マクロの先頭からOOoCalcクラスの__init__()のチェック部分のほうに移動して、マクロ側からは単にtry-except文でNotOOoCalcException例外の処理でreturn文を実行するようにするだけにした。もしtry-except文の中でOOoCalcオブジェクトを生成しなくても、「このマクロは...」のダイアログの後に「OpenOffice.org のエラー」というダイアログが例外の発生を知らせてそこでマクロの実行は自動的に止まることになる。
BridgeやOOoCalcというような自作クラスを用意しているのは、OpenOffice.orgとの仲立ちをしている部分における決まりきった独特な書き方や中間のオブジェクトなどをできるだけ見えなくしてマクロ本体の関数の中をできるだけ見やすくするのが目的。
OpenOffice.orgのマクロをPythonで記述して動かす(Calcのシート追加に関するメモとシート追加のコード例)」で貼り付けた例は以下のようになった。
[任意]ファイル名: [OOoユーザディレクトリ]/user/Scripts/python/calc_insert_sheet_2.py

# -*- mode: python; coding: utf-8 -*-

import uno
import unohelper

class NotOOoCalcException(Exception):
  """
  OOo Calcの中でマクロが呼ばれなかったときに発生する例外
  """
  pass

class Bridge(object):
  """
  PythonとOOoの仲立ちをする各種オブジェクトと
  それを用いた幾つかの操作を提供
  """
  def __init__(self):
    """
    各種オブジェクトの取得
    """
    # http://api.openoffice.org/docs/common/ref/com/sun/star/script/provider/XScriptContext.html
    # http://api.openoffice.org/docs/common/ref/com/sun/star/uno/XComponentContext.html
#    self._context = XSCRIPTCONTEXT.getComponentContext()  # このコードでは未使用
#    self._manager = self._context.ServiceManager  # このコードでは未使用
    self._desktop = XSCRIPTCONTEXT.getDesktop()
    self._document = XSCRIPTCONTEXT.getDocument()
    # http://api.openoffice.org/docs/common/ref/com/sun/star/frame/XDesktop.html
    self._frame = self._desktop.CurrentFrame
    # http://api.openoffice.org/docs/common/ref/com/sun/star/frame/XFrame.html
    self._window = self._frame.ContainerWindow
    self._toolkit = self._window.Toolkit
  def run_infodialog(self, title='', message=''):
    """
    情報ダイアログを表示する
    http://api.openoffice.org/docs/common/ref/com/sun/star/awt/XMessageBoxFactory.html
    http://hermione.s41.xrea.com/pukiwiki/pukiwiki.php?OOoPython%2FOverView
    """
    msgbox = self._toolkit.createMessageBox(self._window,
                                            uno.createUnoStruct('com.sun.star.awt.Rectangle'),
                                            # ダイアログの種類を指定する文字列
                                            # infobox,warningbox,errorbox,
                                            # querybox,messboxのいずれか
                                            'infobox',
                                            1,
                                            title,
                                            message)
    msgbox.execute()
    msgbox.dispose()
  def run_errordialog(self, title='', message=''):
    """
    エラーダイアログを表示する
    http://api.openoffice.org/docs/common/ref/com/sun/star/awt/XMessageBoxFactory.html
    """
    msgbox = self._toolkit.createMessageBox(self._window,
                                            uno.createUnoStruct('com.sun.star.awt.Rectangle'),
                                            'errorbox',
                                            1,
                                            title,
                                            message)
    msgbox.execute()
    msgbox.dispose()

class OOoCalc(Bridge):
  """
  OOo Calcの制御
  BridgeをベースにCalcのサポート状態と全シートを追加したもの
  """
  def __init__(self):
    Bridge.__init__(self)  # 必須
    # Calcの中から実行されたならTrue,それ以外ならFalse
    # http://api.openoffice.org/docs/common/ref/com/sun/star/lang/XServiceInfo.html
    if not self._document.supportsService('com.sun.star.sheet.SpreadsheetDocument'):
      self.run_errordialog(title='エラー', message='このマクロはOpenOffice.org Calcの中で実行してください')
      raise NotOOoCalcException()
    # Calcの全シート
    # プロパティSheetsかメンバ関数getSheets()
    # http://api.openoffice.org/docs/common/ref/com/sun/star/sheet/XSpreadsheetDocument.html
    self.__sheets = self._document.Sheets
  @property
  def sheets(self): return self.__sheets


def m01_add_sheet():
  "OOo Calcでシート「新しいシート」を一番左に追加"
  try:
    calc = OOoCalc()
  # Calcの中かどうかをチェックする
  except NotOOoCalcException:
    return
  # 新規シートを作成
  sheetname = '新しいシート'
  # シートの存在をチェックする
  # 指定された名前のシートが既に存在していればTrue
  # http://api.openoffice.org/docs/common/ref/com/sun/star/container/XNameAccess.html
  if calc.sheets.hasByName(sheetname):
    calc.run_errordialog(title='エラー', message='既にシート "%s" が存在します' % sheetname)
  else:
    # 1番目の引数は名前
    # 2番目の引数は追加位置(0は一番左に追加する)
    # http://api.openoffice.org/docs/common/ref/com/sun/star/sheet/XSpreadsheets.html
    try:
      calc.sheets.insertNewByName(sheetname, 0)
      # ここでは毎回ダイアログを出すことにする
      calc.run_infodialog(title='シート追加成功', message='シート "%s" を追加しました' % sheetname)
    except unohelper.RuntimeException:
      # 上限シート数を超えると例外unohelper.RuntimeExceptionが発生
      calc.run_errordialog(title='エラー', message='シート数が上限に達したため\n新しいシートを追加できませんでした')
      return

def m02_add_5_sheets():
  "OOo Calcでシート「シート [番号]」を5つ続けて右側に追加"
  try:
    calc = OOoCalc()
  except NotOOoCalcException:
    return
  # 現在のシート数を取得
  # http://api.openoffice.org/docs/common/ref/com/sun/star/container/XIndexAccess.html
  # プロパティCountかメンバ関数getCount()
  cnt = calc.sheets.Count
  for i in range(5):  # iは0から4
    sheetname = 'シート %d' % (i + 1,)
    if calc.sheets.hasByName(sheetname):
      calc.run_errordialog(title='エラー', message='既にシート "%s" が存在します' % sheetname)
      return
    else:
      # 追加シートが一番右に来るように2番目の引数を調整
      try:
        calc.sheets.insertNewByName(sheetname, i + cnt)
        calc.run_infodialog(title='シート追加成功', message='シート "%s" を追加しました' % sheetname)
      except unohelper.RuntimeException:
        calc.run_errordialog(title='エラー', message='シート数が上限に達したため\n新しいシートを追加できませんでした')
        return

def m03_add_300_sheets():
  "OOo Calcでシート「シート [番号]」を300個続けて右側に追加(しようとする)"
  try:
    calc = OOoCalc()
  except NotOOoCalcException:
    return
  cnt = calc.sheets.Count
  for i in range(300):
    sheetname = 'シート %d' % (i + 1,)
    if calc.sheets.hasByName(sheetname):
      calc.run_errordialog(title='エラー', message='既にシート "%s" が存在します' % sheetname)
      return
    else:
      try:
        calc.sheets.insertNewByName(sheetname, i + cnt)
      except unohelper.RuntimeException:
        calc.run_errordialog(title='エラー', message='シート数が上限に達したため\n新しいシートを追加できませんでした')
        return


# このタプルに名前を書いた関数のみマクロ選択ダイアログから実行できる
# g_exportedScriptsを記述しない場合は全て実行可
g_exportedScripts = (m01_add_sheet, m02_add_5_sheets, m03_add_300_sheets)

関連記事:

使用したバージョン: