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

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

OpenOffice.orgのマクロをPythonで記述して動かす(Calcのセル内容の操作に関する例その2)

OpenOffice.orgのマクロをPythonで記述して動かす(Calcのセル操作に関する追加メモ・ページ1/3)」「OpenOffice.orgのマクロをPythonで記述して動かす(Calcのセル内容へのアクセスに関する追加メモ・ページ2/3)」「OpenOffice.orgのマクロをPythonで記述して動かす(Calcのセル内容へのアクセスに関する追加メモ・ページ3/3)」の内容を踏まえて、OpenOffice.org Calcのセル操作に関するマクロの2つ目の例を下に貼り付ける。
これまでにも似たような名簿のようなサンプルを貼り付けたことがあるが、例の中で使用した名前は架空のものであり、実在の個人名や団体名などと一致するものがあったとしても関係はない。
(2014/11/20)Pythonのrandomモジュールについてのリファレンスのリンク先を修正した。
[任意]ファイル名: [OOoユーザディレクトリ]/user/Scripts/python/calc_cell_test_2.py

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

import random
import uno
#import unohelper  # このコードでは未使用

class InvalidColorException(Exception):
  """
  RGB()に不正な色が指定された場合に発生する例外
  """
  def __init__(self, red, green, blue):
    (self.__red, self.__green, self.__blue) = (red, green, blue)
  def __str__(self):
    return 'Invalid color(R=%d,G=%d,B=%d)' % (self.__red, self.__green, self.__blue)

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

class Bridge(object):
  """
  PythonとOOoの仲立ちをする各種オブジェクトと
  それを用いた幾つかの操作を提供
  """
  # enum定数(どれが選択されたかだけを示し、値に意味がないもの)は
  # uno.Enumオブジェクトとして得る
  # com.sun.star.table.CellHoriJustify.CENTERの場合は
  # uno.Enum('com.sun.star.table.CellHoriJustify', 'CENTER')とする
  com_sun_star_table_CellHoriJustify_CENTER = uno.Enum('com.sun.star.table.CellHoriJustify', 'CENTER')
  # constant定数(名前から値に展開されるもの)は
  # uno.getConstantByName()で得る
  com_sun_star_awt_FontWeight_NORMAL = uno.getConstantByName('com.sun.star.awt.FontWeight.NORMAL')
  com_sun_star_awt_FontWeight_BOLD = uno.getConstantByName('com.sun.star.awt.FontWeight.BOLD')
  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_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()
    # http://api.openoffice.org/docs/common/ref/com/sun/star/frame/XModel.html
    self.__current_controller = self._document.CurrentController
  def get_active_sheet(self):
    return self.__current_controller.ActiveSheet
  def set_active_sheet(self, value):
    self.__current_controller.ActiveSheet = value
  active_sheet = property(get_active_sheet, set_active_sheet)
  @property
  def supported(self): return self.__supported

class Data:
  """
  各種データ
  """
  header = ('出席番号', '性別', 'Family', 'Given', '国語', '算数', '理科', '社会', '合計', '平均')
  students = \
  [
    # 出席番号, 性別(M:True F:False), Family, Given
    (1, True, 'Tanaka', 'Ichiro'),
    (2, False, 'Yamana', 'Hanako'),
    (3, True, 'Urashima', 'Saburo'),
    (4, True, 'Kurusu', 'Santa'),
    (5, True, 'Handa', 'Fuuta'),
    (6, False, 'Umeno', 'Tsubomi'),
    (7, True, 'Yoshi', 'Yaruzo'),
    (8, False, 'Kawai', 'Nuko'),
    (9, True, 'Hoshi', 'Kintaro'),
    (10, False, 'Shirayuki', 'Himeko'),
    (11, False, 'Ashigaka', 'Yui'),
    (12, False, 'Ageyanagi', 'Masako'),
    (13, True, 'Torino', 'Kenta'),
    (14, True, 'Kubota', 'Mochio'),
    (15, False, 'Kuroi', 'Sora'),
    (16, True, 'Hirai', 'Shin'),
    (17, False, 'Akai', 'Midori'),
    (18, False, 'Nakano', 'Anko'),
    (19, True, 'Imai', 'Takeo'),
    (20, True, 'Kouno', 'Torio'),
    (21, True, 'Yoshino', 'Yasu'),
    (22, True, 'Komatsu', 'Taro'),
    (23, True, 'Kondo', 'Musashi'),
    (24, True, 'Ono', 'Ken'),
    (25, True, 'Mochida', 'Usuichi'),
    (26, False, 'Mochida', 'Kineko'),
    (27, False, 'Honma', 'Kayo'),
    (28, True, 'Matsuno', 'Sarunosuke'),
    (29, False, 'Nishi', 'Minami'),
    (30, False, 'Usui', 'Hikaru'),
    (31, True, 'Sato', 'Toshio'),
    (32, True, 'Doi', 'Tsubasa'),
    (33, False, 'Ishimaru', 'Denko'),
    (34, False, 'Usami', 'Mimi'),
    (35, True, 'Hattori', 'Shinobu'),
    (36, False, 'Kago', 'Yuri'),
    (37, True, 'Takeda', 'Ingen'),
    (38, True, 'Kai', 'Dankichi'),
    (39, True, 'Okusa', 'Ben'),
    (40, True, 'Hara', 'Tatsuo'),
    (41, False, 'Mizuno', 'Shizuku'),
    (42, False, 'Baba', 'Nana'),
  ]


def RGB(red, green, blue):
  """
  com.sun.star.util.Color(内部的にはlong)型の色を
  RGB各成分を0(0x0)-255(0xff)の範囲で受け付けて指定する
  """
  if red > 0xff or red < 0x00 or \
   green > 0xff or green < 0x00 or \
   blue > 0xff or blue < 0x00:
    raise InvalidColorException(red, green, blue)
  return red * 0x010000 + green * 0x000100 + blue * 0x000001

# 以下マクロ本体の関数

def m01_set_sampledata():
  "サンプルデータを入力"
  try:
    calc = OOoCalc()
  except NotOOoCalcException:
    return
  else:
    sheet = calc.active_sheet
    # ヘッダを入力
    for (i, item) in enumerate(Data.header):
      cell = sheet.getCellByPosition(i, 0)
      cell.Formula = item  # 単なる文字列なのでStringでもよい
      # セルの左右方向の中央寄せ
      # http://api.openoffice.org/docs/common/ref/com/sun/star/table/CellProperties.html
      # http://api.openoffice.org/docs/common/ref/com/sun/star/table/CellHoriJustify.html
      cell.HoriJustify = Bridge.com_sun_star_table_CellHoriJustify_CENTER
      # 隣のヘッダ項目と色を変える
      if i % 2:
        cell.CellBackColor = 0xffffee
      else:
        cell.CellBackColor = 0xffeeff
    for (i, item) in enumerate(Data.students):
      y = i + 1
      (num, gender, family, given) = item
      # 点数はランダムで生成することにする
      (lang, math, sci, soc) = (random.randint(0, 100), random.randint(0, 100), random.randint(0, 100), random.randint(0, 100))
      # それぞれのデータを入力
      sheet.getCellByPosition(0, y).Formula = num
      if gender:
        sheet.getCellByPosition(1, y).Formula = '♂'
      else:
        sheet.getCellByPosition(1, y).Formula = '♀'
      sheet.getCellByPosition(2, y).Formula = family
      sheet.getCellByPosition(3, y).Formula = given
      sheet.getCellByPosition(4, y).Formula = lang
      sheet.getCellByPosition(5, y).Formula = math
      sheet.getCellByPosition(6, y).Formula = sci
      sheet.getCellByPosition(7, y).Formula = soc
      # ガイドのシマシマを付ける
      # getCellRangeByPosition()はセル範囲を得る・引数は
      # 1:新しい選択範囲の左上セルの(外範囲から見た)X(列/右)方向index
      # 2:新しい選択範囲の左上セルの(外範囲から見た)Y(行/下)方向index
      # 3:新しい選択範囲の右下セルの(外範囲から見た)X(列/右)方向index
      # 4:新しい選択範囲の右下セルの(外範囲から見た)Y(行/下)方向index
      # となる(indexは0から始まる整数)
      if y % 2:
        # 背景色なし
        sheet.getCellRangeByPosition(0, y, 9, y).CellBackColor = -1
      else:
        # 背景色を「#eaebec」にする
        sheet.getCellRangeByPosition(0, y, 9, y).CellBackColor = 0xeaebec

def m02_calculate():
  "データの計算を行う式を設定する\nこの後でデータが変更されるとすぐに反映される"
  try:
    calc = OOoCalc()
  except NotOOoCalcException:
    return
  sheet = calc.active_sheet
  sheetname = sheet.Name
  for (i, item) in enumerate(Data.students):
    y = i + 1
    y2 = y + 1
    # セル名の数字部分を項目の番号から決めている
    sheet.getCellByPosition(8, y).Formula = '=SUM(E%d:H%d)' % (y2, y2)
    sheet.getCellByPosition(9, y).Formula = '=AVERAGE(E%d:H%d)' % (y2, y2)

def m03_calculate_2():
  "データの計算を行う式を設定する\nこの後でデータが変更されるとすぐに反映される"
  try:
    calc = OOoCalc()
  except NotOOoCalcException:
    return
  sheet = calc.active_sheet
  sheetname = sheet.Name
  for (i, item) in enumerate(Data.students):
    y = i + 1
    y2 = y + 1
    # セル名でなく座標を用いて表計算の関数を呼ぶには
    # INDIRECT()関数とADDRESS()関数を組み合わせて引数に渡す
    # http://wiki.services.openoffice.org/wiki/Documentation/OOo3_User_Guides/Calc_Guide/Address,_Indirect,_Offset,_Index
    # ADDRESS()の3番目の引数は「4」にする
    # 詳しくはヘルプの「Calc - 表計算ドキュメント - 表計算ドキュメントの関数」
    # ADDRESS()の引数は1からなので指定するときにはズラす必要がある(y2とした)
    sheet.getCellByPosition(8, y).Formula = '=SUM(INDIRECT(ADDRESS(%d;5;4;"%s")):INDIRECT(ADDRESS(%d;8;4;"%s")))' % (y2, sheetname, y2, sheetname)
    sheet.getCellByPosition(9, y).Formula = '=AVERAGE(INDIRECT(ADDRESS(%d;5;4;"%s")):INDIRECT(ADDRESS(%d;8;4;"%s")))' % (y2, sheetname, y2, sheetname)

def m04_check_failed():
  "赤点(30点未満とする)をチェック\n"
  try:
    calc = OOoCalc()
  except NotOOoCalcException:
    return
  sheet = calc.active_sheet
  for (i, item) in enumerate(Data.students):
    y = i + 1
    # 出席番号-Givenまでの背景色を初期化
    # 強調しない状態ではガイドのシマシマを付ける
    if i % 2:
      sheet.getCellRangeByPosition(0, y, 3, y).CellBackColor = 0xeaebec
    else:
      sheet.getCellRangeByPosition(0, y, 3, y).CellBackColor = -1
    # 科目ごとに赤点をチェック
    for j in range(4):
      # 科目の強調表示を初期化
      sheet.getCellByPosition(4 + j, y).CharColor = -1
      sheet.getCellByPosition(4 + j, y).CharWeight = Bridge.com_sun_star_awt_FontWeight_NORMAL  # NORMALの値は100(%)
      # 「4 + j」としているのは国語のマスの右に算数,理科,社会が並ぶため
      # 国語のマスのX座標を基準にしている
      if sheet.getCellByPosition(4 + j, y).Value < 30:
        # 赤点の科目を赤くして強調表示
        # http://api.openoffice.org/docs/common/ref/com/sun/star/style/CharacterProperties.html
        # ユーザ定義関数RGB()を用いた例
        # 下に続く2つのコメント行は同じ色指定を別の形で書いたもの
        sheet.getCellByPosition(4 + j, y).CharColor = RGB(204, 0, 0)
#       sheet.getCellByPosition(4 + j, y).CharColor = RGB(0xcc, 0x00, 0x00)
#       sheet.getCellByPosition(4 + j, y).CharColor = 0xcc0000
        # 更に太字にする
        sheet.getCellByPosition(4 + j, y).CharWeight = Bridge.com_sun_star_awt_FontWeight_BOLD  # BOLDの値は150(%)
        # 赤点者の出席番号-Givenまでの背景色を変更する場合は
        # 下の行のコメントを外す
#       sheet.getCellRangeByPosition(0, y, 3, y).CellBackColor = 0xffcccc

def m05_invalid_rgb():
  "ユーザ定義関数RGB()に不正な値を指定して例外が発生することを確認するテスト\n"
  try:
    calc = OOoCalc()
  except NotOOoCalcException:
    return
  # 256という不正な値を入れているため例外が発生して実行が止まる
  calc.active_sheet.getCellByPosition(0, 0).CellBackColor = RGB(64, 128, 256)


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

以下は実行内容となる。処理が重いマクロもあるので、CPUの処理能力によってはしばらく固まることがあるかもしれない。

  • 1番目のマクロではサンプルデータを現在のシートに入力する
  • 2番目のマクロではSUM()関数とAVERAGE()関数を行ごとに入れていき、合計と平均の列を埋める
  • 3番目のマクロでは2番目と同様の処理をINDIRECT()関数とADDRESS()関数を用いて行う(シート名依存になるので注意)
  • 4番目のマクロでは各生徒の科目ごとの点数をチェックし、30点未満のものがある場合にそれを赤い太字で強調する
  • 5番目のマクロでは「OpenOffice.orgのマクロをPythonで記述して動かす(Calcのセル内容へのアクセスに関する追加メモ・ページ2/3)」で扱ったユーザ定義関数RGB()にわざと不正な値を代入して例外が起こることを確認できる

参考URL:

使用したバージョン: