Calc(44)ファイルフィルターのオプションダイアログ

2017-11-12

旧ブログ

t f B! P L
LibreOffice5(93)ファイルフィルターのダイアログ(UIComponent)の一覧を取得で取得したUIComponentsのあるファイルフィルターのうちCalcドキュメントをソースにするもののファイルフィルターのオプションダイアログを使ってみます。

前の関連記事:Calc(43)コメントを非表示にしたのにTargetにセルが入らないことの対策


ファイルフィルターのオプションダイアログの戻り値


showUIComponent.ods

このshowUIComponent.odsをマクロを有効にして開くとCalcドキュメントに使える、オプションダイアログがあるファイルフィルター一覧が出力されます。

FilterNameの列をダブルクリックするとそのファイルフィルターのオプションダイアログが表示されます。

オプションダイアログの「OK」ボタンか「エクスポート」ボタンをクリックしたときは、オプションダイアログからの戻り値である、PropertyValue Structのシークエンスを取得します。

そして各ProperValueのNameアトリビュートとValueアトリビュートを一覧の下に出力します。

最後に取得した結果を新しいものから上に出力します。

https://docs.google.com/spreadsheets/d/e/2PACX-1vR3uvXYML8lRajPL8hkc4Vyo_uPwMJGk4DLQSPXP-CeeiLpTcOCfn9PkheIQEosYphwdmjfKeDXnkEx/pubhtml?gid=1969021982&single=true

FilterNameの列を下から順にダブルクリックして、デフォルトのままオプションダイアログを閉じたときの出力結果です。

HTML (StarCalc)とcalc_HTML_WebQueryは何もダイアログがでてきません。

showUIComponent2.ods

このshowUIComponent2.odsはUIComponentのexecute()の実行前のPropertyValuesも出力するようにしたものです。

CSVを出力するときのオプションダイアログ




このキャプチャ画像のままOKボタンをクリックするとこの一つのPropertyValue Structが返ってきました。

「テキストの区切り記号」というのは、フィールド中に「フィールドの区切り記号」が存在しているときにフィールドを囲むもののようです。

Valueの解説はFilter Options - Apache OpenOffice Wikiにあります。

最初の5トークンまでの解説はOOoBasic/Calc/CSV - ...?にもあります。

PNGやPDFのファイルフィルターと異なって、CSVのオプションダイアログを実行したあとのgetPropertyValues()の戻り値のPropertyValue StructのタプルにはFilterNameが消えてしまっているので、ファイルを保存するときの引数にするときはFilterNameを追加しないといけません。

このダイアログで設定を変更してOKボタンをクリックすると、次にダイアログを表示させたときはその設定が反映されています
必ずしもそうでないようで、いまのところ規則性がよくわからないです。

どこかにその設定が保存されているということですが、registrymodifications.xcuファイルをみてもその設定値は見つけられませんでした。

保存された設定値は、UIComponentをexecute()するとgetPropertyValues()で取得できるようになります。

ということはオプションダイアログを表示させずには既存の設定値を取得することはできないということになります。

PNGを出力するときのオプションダイアログ




FilterNameとFilterDataのPropertyValueが2つ返ってきます。

FilterDataのValueにはまた2つのPropertyValueが入っています。

解像度は幅と高さの計算に使っているだけで、PropertyValueに保存される値ではないようです。

PDFを出力するときのオプションダイアログ



PDFのオプションダイアログはたくさん項目があります。

その項目数だけFilterDataのValueにはPropertyValue Structが入っています。

showUIComponent.odsのマクロ

#!/opt/libreoffice5.4/program/python
# -*- coding: utf-8 -*-
import unohelper  # オートメーションには必須(必須なのはuno)。
from itertools import zip_longest
from com.sun.star.beans import PropertyValue  # Struct
from com.sun.star.sheet import CellFlags as cf # 定数
from com.sun.star.awt import XEnhancedMouseClickHandler
from com.sun.star.awt import MouseButton  # 定数
def macro(documentevent=None):  # 引数は文書のイベント駆動用。  
 doc = XSCRIPTCONTEXT.getDocument() if documentevent is None else documentevent.Source  # ドキュメントのモデルを取得。 
 ctx = XSCRIPTCONTEXT.getComponentContext()  # コンポーネントコンテクストの取得。
 smgr = ctx.getServiceManager()  # サービスマネージャーの取得。
 configreader = createConfigReader(ctx, smgr)  # 読み込み専用の関数を取得。
 root = configreader("/org.openoffice.TypeDetection.Filter/Filters")  # コンフィギュレーションのルートを取得。
 props = "UIName", "UIComponent", "DocumentService"  # 取得するプロパティ名のタプル。
 outputs = []  # 出力行のリスト。
 for childname in root.getElementNames():  # 子ノードの名前のタプルを取得。ノードオブジェクトの直接取得はできない模様。
  uiname, uicomponent, documentservice = root[childname].getPropertyValues(props)  # フィルターのプロパティを取得。
  if uicomponent and documentservice=="com.sun.star.sheet.SpreadsheetDocument":  # スプレッドシートを処理するダイアログがあるもののみ抽出。
   datarow = uiname, childname, uicomponent, documentservice
   outputs.append(datarow)
 datarows = []  # シートに書き込む行のリスト。
 datarow = props[0], "FilterName", *props[1:]  # ヘッダー行。右辺のタプルのアンパックはPython3.5以上でのみ可能。
 datarows.append(datarow)
 sheetname = "UIComponents"  # UIComponentsプロパティがあるノードのみそれでソートして出力する。
 outputs.sort(key=lambda r: r[2])  # 行の列インデックス2。つまりUIComponentでソートする。
 datarows.extend(outputs)  # データ行を追加。 
 sheet = getNewSheet(doc, sheetname)  # 新規シートの取得。
 rowsToSheet(sheet, datarows)  # datarowsをシートに書き出し。  
 annotations = sheet.getAnnotations()  # シートのコメントコレクションを取得。
 txt = "Double-click to expand the return value of the FilterOptionsDialog."
 annotations.insertNew(sheet["B1"].getCellAddress(), txt)  # セル注釈を挿入。
 [i.getAnnotationShape().setPropertyValue("Visible", False) for i in annotations]  # これをしないとmousePressed()のTargetにAnnotationShapeが入ってしまう。
 controller = doc.getCurrentController()  # コントローラの取得。
 controller.setActiveSheet(sheet)  # シートをアクティブにする。 
 r = len(datarows)  # データの最終行の1つ下の行のインデックス。
 args = ctx, smgr, doc, r
 controller.addEnhancedMouseClickHandler(EnhancedMouseClickHandler(args))  # マウスハンドラをコントローラに設定。
class EnhancedMouseClickHandler(unohelper.Base, XEnhancedMouseClickHandler): # マウスハンドラ
 def __init__(self, args):
  ctx, smgr, doc, r = args
  getFilterProperyValues = filterproperyvaluesCreator(ctx, smgr, doc)
  self.args = r, getFilterProperyValues
 def mousePressed(self, enhancedmouseevent):  # マウスボタンをクリックした時。ブーリアンを返さないといけない。
  r, getFilterProperyValues = self.args
  target = enhancedmouseevent.Target  # ターゲットを取得。
  if enhancedmouseevent.Buttons==MouseButton.LEFT:  # 左ボタンのとき
   if enhancedmouseevent.ClickCount==2:  # ダブルクリックの時
    if target.supportsService("com.sun.star.sheet.SheetCell"):  # ターゲットがセルの時。
     sheet = target.getSpreadsheet()  # ターゲットがあるシートを取得。
     celladdress = target.getCellAddress()  # ターゲットのセルアドレスを取得。
     if 0<celladdress.Row<r:  # 行を限定。
      header = sheet[0, celladdress.Column].getString()  # 列のヘッダーを取得。
      if header=="FilterName":  # 列ヘッダーがFilterNameの列のとき。
       filtername = target.getString()  # ターゲットセルの文字列を取得。
       filterpropertyvalues = getFilterProperyValues(filtername)
       if filterpropertyvalues is not None: 
        outputs = []  # 出力行のリスト。
        expandPropertyValueStructs(outputs, filterpropertyvalues, 0)  # PropertyValue Structを展開する。
        header = filtername,
        datarows = [header]
        datarows.extend(outputs)
        sheet.getRows().insertByIndex(r+1, len(datarows)+1)  # 行インデックスrに挿入するデータの行数の行を挿入する。
        rowsToSheet(sheet[r+1, 0], datarows)
        return False  # セル編集モードにしない。
  return True  # Falseを返すと右クリックメニューがでてこなくなる。
 def mouseReleased(self, enhancedmouseevent):  # ブーリアンを返さないといけない。
  return True  # Trueでイベントを次のハンドラに渡す。
 def disposing(self, eventobject):
  pass 
def filterproperyvaluesCreator(ctx, smgr, doc): 
 configreader = createConfigReader(ctx, smgr)  # 読み込み専用の関数を取得。
 def getFilterProperyValues(filtername):
  uicomponent = configreader("/org.openoffice.TypeDetection.Filter/Filters/{}".format(filtername)).getPropertyValue("UIComponent")  # フィルター名からUIComponent名を取得。
  filteroptiondialog = smgr.createInstanceWithContext(uicomponent, ctx)  # UIコンポーネントをインスタンス化。
  filteroptiondialog.setSourceDocument(doc)  # 変換元のドキュメントを設定。
  propertyvalues = PropertyValue(Name="FilterName", Value=filtername),  # 複数のフィルターに対応しているUIComponentはFilterNameを設定しないといけない。
  filteroptiondialog.setPropertyValues(propertyvalues)  # XPropertyAccessインターフェイスのメソッド。
  if filteroptiondialog.execute()==1:  # フィルターのオプションダイアログを表示。
   return filteroptiondialog.getPropertyValues()  # 戻り値はPropertyValue Structのタプル。XPropertyAccessインターフェイスのメソッド。  
 return getFilterProperyValues
def expandPropertyValueStructs(outputs, structs, h):  # outputs: 出力行のリスト、structs: PropertyValuesのタプル、h: 出力階層。
 flg = True  # 先頭行のフラグ。
 for struct in structs:
  ind = [""]*h
  if flg and h>0:  # 出力階層1以上で先頭行のとき。
   ind[-1] = "Value"
   flg = False
  name = "Name", struct.Name
  ind.extend(name)  # 戻り値はない。
  outputs.append(ind)
  v = struct.Value
  if isinstance(v, tuple):  # Valueがタプルの時は入れ子になっているので再帰呼出し。
   expandPropertyValueStructs(outputs, v, h+1)
  else:
   if isinstance(v, bool) or not isinstance(v, int):  # vがint以外の時。bool型はintに含まれるので注意。
    v = str(v)  # setDataArrayではint以外はStringしか代入できないのでStringに変換する。
   ind = [""]*h
   value = "Value", v
   ind.extend(value)  # 戻り値はない。
   outputs.append(ind)
def rowsToSheet(cellrange, datarows):  # 引数のセル範囲を左上端にして一括書き込みして列幅を最適化する。datarowsはタプルのタプル。
 datarows = tuple(zip(*zip_longest(*datarows, fillvalue="")))  # 一番長い行の長さに合わせて空文字を代入。
 sheet = cellrange.getSpreadsheet()  # セル範囲のあるシートを取得。
 cellcursor = sheet.createCursorByRange(cellrange)  # セル範囲のセルカーサーを取得。
 cellcursor.collapseToSize(len(datarows[0]), len(datarows))  # (列、行)で指定。セルカーサーの範囲をdatarowsに合せる。
 cellcursor.setDataArray(datarows)  # セルカーサーにdatarowsを代入。代入できるのは整数(int、ただしboolを除く)か文字列のみ。
 cellcursor.getColumns().setPropertyValue("OptimalWidth", True)  # セルカーサーのセル範囲の列幅を最適化する。  
def getNewSheet(doc, sheetname):  # docに名前sheetnameのシートを返す。sheetnameがすでにあれば連番名を使う。
 cellflags = cf.VALUE+cf.DATETIME+cf.STRING+cf.ANNOTATION+cf.FORMULA+cf.HARDATTR+cf.STYLES
 sheets = doc.getSheets()  # シートコレクションを取得。
 c = 1  # 連番名の最初の番号。
 newname = sheetname
 while newname in sheets: # 同名のシートがあるとき。sheets[newname]ではFalseのときKeyErrorになる。
  if not sheets[newname].queryContentCells(cellflags):  # シートが未使用のとき
   return sheets[newname]  # 未使用の同名シートを返す。
  newname = "{}{}".format(sheetname, c)  # 連番名を作成。
  c += 1 
 sheets.insertNewByName(newname, len(sheets))   # 新しいシートを挿入。同名のシートがあるとRuntimeExceptionがでる。
 if "Sheet1" in sheets:  # デフォルトシートがあるとき。
  if not sheets["Sheet1"].queryContentCells(cellflags):  # シートが未使用のとき
   del sheets["Sheet1"]  # シートを削除する。
 return sheets[newname]
def createConfigReader(ctx, smgr):  # ConfigurationProviderサービスのインスタンスを受け取る高階関数。
 configurationprovider = smgr.createInstanceWithContext("com.sun.star.configuration.ConfigurationProvider", ctx)  # ConfigurationProviderの取得。
 def configReader(path):  # ConfigurationAccessサービスのインスタンスを返す関数。
  node = PropertyValue(Name="nodepath", Value=path)
  return configurationprovider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", (node,))
 return configReader
g_exportedScripts = macro, #マクロセレクターに限定表示させる関数をタプルで指定。  
107行目でsetDataArray()でシートにデータを書き込む前に、13行目で各行のセルに入れるデータの要素の数をそろえています。

参考にしたサイト


Filter Options - Apache OpenOffice Wiki
ファイルフィルターのフィルターオプションの解説。

OOoBasic/Calc/CSV - ...?
CSV形式でエクスポートする解説。

次の関連記事:Calc(45)ファイル保存ダイアログとオプション

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ