前の関連記事:LibreOffice5(92)全ディスパッチコマンドのラベルの取得
「フィルター」とは「コンバーター」のことらしい
LibreOffice5(68)画像フィルターリストの作成とかLibreOffice5(76)FilePickerのフィルターを作成: 失敗を書いている頃は「フィルター」とはファイルを選別するものと思っていましたが、OOoBasic/Generic/FilterOption - ...?を読んでいて、これはコンバーターとようやく理解できました。
「フィルター」とは「選別するもの」と思っていましたが、ソフトウェア分野では「加工するもの」の意味になるようです(フィルタ (ソフトウェア) - Wikipedia) 。
LibreOfficeのフィルターには、ユーザーが設定を変更できるようにダイアログがあるものがあるようなのでまずそれを一覧にします。
Framework/Article/Filter - Apache OpenOffice WikiにはFramework/Article/Filter/FilterList SO 7 - Apache OpenOffice Wikiみたいな表を出力するBasicのスクリプトがあります。
だいぶ前に使ってみた頃がありますが、かなり時間がかかったように思います。
(2017.11.12追記。ということでFilePickerの記事で使ってきた「フィルフィルター」とはコンバーターのことではなく、OOoBasic/Generic/filedialog - ...?で使われている「表示フィルター」というのが的確な表現ということになります。)
フィルターを定義しているコンポーネントスキーマノード
まずmain.xcdにあるコンポーネントスキーマノードを確認します。
<oor:component-schema oor:package="org.openoffice.TypeDetection" oor:name="Filter" xml:lang="en-US">
<templates>
<group oor:name="Filter">
<prop oor:name="FileFormatVersion" oor:type="xs:int" oor:nillable="false">
<value>0</value>
</prop>
<prop oor:name="Type" oor:type="xs:string" oor:nillable="false">
<value/>
</prop>
<prop oor:name="DocumentService" oor:type="xs:string" oor:nillable="false">
<value/>
</prop>
<prop oor:name="UIComponent" oor:type="xs:string" oor:nillable="false">
<value/>
</prop>
<prop oor:name="UserData" oor:type="oor:string-list" oor:nillable="false">
<value/>
</prop>
<prop oor:name="FilterService" oor:type="xs:string" oor:nillable="false">
<value/>
</prop>
<prop oor:name="TemplateName" oor:type="xs:string" oor:nillable="false">
<value/>
</prop>
<prop oor:name="Flags" oor:type="oor:string-list" oor:nillable="false">
<value/>
</prop>
<prop oor:name="UIName" oor:localized="true" oor:type="xs:string"/>
<prop oor:name="ExportExtension" oor:type="xs:string" oor:nillable="true"/>
</group>
</templates>
<component>
<set oor:name="Filters" oor:node-type="Filter"/>
</component>
</oor:component-schema>
UINameで一般名、UIComponentがダイアログ、DocumentServiceが対応する(?)ドキュメントの種類、を定義しているようなのでこれらを抽出します。ルートパスは/org.openoffice.TypeDetection.Filter/Filtersにしてそこからサブノードを列挙します。
フィルター名はグループノードで使われている名前になっているようで、これはつまりサブノード名に該当します。
ファイルフィルター一覧を出力するマクロ
#!/opt/libreoffice5.4/program/python
# -*- coding: utf-8 -*-
import unohelper # オートメーションには必須(必須なのはuno)。
from itertools import compress
from com.sun.star.beans import PropertyValue # Struct
from com.sun.star.sheet import CellFlags as cf # 定数
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") # org.openoffice.TypeDetectionパンケージのTypesコンポーネントのTypesノードを根ノードにする。
props = "UIName", "UIComponent", "DocumentService" # 取得するプロパティ名のタプル。
outputs = []
for childname in root.getElementNames(): # 子ノードの名前のタプルを取得。ノードオブジェクトの直接取得はできない模様。
propvalues = root.getByName(childname).getPropertyValues(props)
datarow = propvalues[0], childname, *propvalues[1:]
outputs.append(datarow)
header = props[0], "FilterName", *props[1:] # ヘッダー行。右辺のタプルのアンパックはPython3.5以上でのみ可能。
sheetname = "Filters" # 取得した順に出力する。
datarows = [header]
datarows.extend(outputs)
sheet = getNewSheet(doc, sheetname)
rowsToSheet(sheet, datarows)
sheetname = "SortedByDocumentService" # SortedByDocumentServiceでソートして出力する。
outputs.sort(key=lambda r: r[3]) # 行の列インデックス3。つまりDocumentServiceでソートする。
datarows = [header]
datarows.extend(outputs)
sheet = getNewSheet(doc, sheetname)
rowsToSheet(sheet, datarows)
sheetname = "UIComponents" # UIComponentsプロパティがあるノードのみそれでソートして出力する。
selectors = (r[2] for r in outputs) # 行の列インデックス2の値で選択する。ジェネレーターでもリストでも可。
outputs = list(compress(outputs, selectors)) # UIComponentsに値があるもののみ抽出。compress()の戻り値はジェネレーター。
outputs.sort(key=lambda r: r[2]) # 行の列インデックス2。つまりUIComponentでソートする。
datarows = [header]
datarows.extend(outputs)
sheet = getNewSheet(doc, sheetname)
rowsToSheet(sheet, datarows)
controller = doc.getCurrentController() # コントローラーを取得。
controller.setActiveSheet(sheet) # シートをアクティブにする。
def rowsToSheet(sheet, datarows): # datarowsはタプルのタプル。1次元のタブルの長さは同一でなければならない。
sheet[:len(datarows), :len(datarows[0])].setDataArray(datarows)
cellcursor = sheet.createCursor() # シート全体のセルカーサーを取得。
cellcursor.gotoEndOfUsedArea(True) # 使用範囲の右下のセルまでにセルカーサーのセル範囲を変更する。
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, #マクロセレクターに限定表示させる関数をタプルで指定。
このマクロで3種類の表を出力しています。Filtersシートにはコンフィグレーションから呼び出した順に出力しています。
SortedByDocumentServiceシートにはDocumentServiceプロパティでソートした結果を出力しています。
UIComponentsシートにはUIComponentsプロパティが存在するフィルターのみ出力しています。
Filters.ods
このマクロを埋め込んだCalcドキュメントです。
マクロを有効化してドキュメントを開くとマクロが起動して各シートを出力します。
代入文の右辺でタプルのアンパックを使っているのでバンドルPythonのバージョンが3.5未満であるWindows版のLibreOffice5.3以下では動きません(SyntaxError: can use starred expression only as assignment target · Issue #37 · bastibe/transplant · GitHub) 。
シートに出力する前の2次元リストの選別に、Python Cookbookの01.16.filtering_sequence_elementsで解説されていたitertools.compress()を使っています。
selectorsはジェネレーターでも動きました。
角括弧ではなく丸括弧で囲うとリストではなくジェネレーターになることも初めて知りました。
Python Cookbookはとても役立つ本ですね。
以下出力結果です。
Filtersシート
https://docs.google.com/spreadsheets/d/e/2PACX-1vT0RlYByTxiVqEq5NKGNZUpU3yKy9uGKp57fuPrhymVLA8tpIyF38hf5V_IXimU0S075fYJRgnd7izR/pubhtml?gid=1163087113&single=true
SortedByDocumentServiceシート
https://docs.google.com/spreadsheets/d/e/2PACX-1vT0RlYByTxiVqEq5NKGNZUpU3yKy9uGKp57fuPrhymVLA8tpIyF38hf5V_IXimU0S075fYJRgnd7izR/pubhtml?gid=236126191&single=true
UIComponentsシート
https://docs.google.com/spreadsheets/d/e/2PACX-1vT0RlYByTxiVqEq5NKGNZUpU3yKy9uGKp57fuPrhymVLA8tpIyF38hf5V_IXimU0S075fYJRgnd7izR/pubhtml?gid=1453750859&single=true
参考にしたサイト
OOoBasic/Generic/FilterOption - ...?
フィルタオプションダイアログを使うBasicの例。
Framework/Article/Filter - Apache OpenOffice Wiki
フィルター一覧を出力するBasicスクリプトがあります。
Framework/Article/Filter/FilterList SO 7 - Apache OpenOffice Wiki
上のBasicスクリプトで出力されるフィルター一覧の例。
SyntaxError: can use starred expression only as assignment target · Issue #37 · bastibe/transplant · GitHub
受けるほうだけでなく、渡す方にもタプルのアンパックを使えるのはPython3.5以降からのようです。
10.1. itertools — 効率的なループ実行のためのイテレータ生成関数 — Python 3.5.3 ドキュメント
多次元リストの要素を条件にリストを抽出するのにcompress()を使いました。
0 件のコメント:
コメントを投稿