LibreOffice5(50)Javaの例:ConfigExamplesをPythonにする その3

browseDataExample()では/org.openoffice.TypeDetection.Filter/Filters以下にあるFilterの値をすべて出力しています。デベロッパーガイドのReading Configuration Data - Apache OpenOffice Wikiに該当します。

前の関連記事:LibreOffice5(49)Javaの例:ConfigExamplesをPythonにする その2


browseDataExample()


この関数を実行すると2200行にわたってmain.xcdにあるパッケージorg.openoffice.TypeDetection、コンポーネントFilterを起点に芋ずる式にFilterという名前の項目が出力されます。
def browseDataExample(cp):
    try:
        print("\n--- starting example: browse filter configuration ------------------")
        printRegisteredFilters(cp)
    except:
        traceback.print_exc()
class IconfigurationProcessor:
    def processValueElement(self, path, values):
        if isinstance(values, tuple):
            print("\tValue: {0} = {{{1}}}".format(path, ", ".join(values)))
        else:
            print("\tValue: {} = {}".format(path, values))
    def processStructuralElement(self, path, elem):
        if hasattr(elem, "getTemplateName") and elem.getTemplateName().endswith("Filter"):
            print("Filter {} ({})".format(elem.getName(), path))
def printRegisteredFilters(cp):
    path = "/org.openoffice.TypeDetection.Filter/Filters"
    browseConfiguration(path, IconfigurationProcessor(), cp)
def browseConfiguration(path, processor, cp):
    ca = createConfigurationView(path, cp)
    browseElementRecursively(ca, processor)
    ca.dispose()
def browseElementRecursively(elem, processor):
    path = elem.getHierarchicalName()
    processor.processStructuralElement(path, elem)
    childnames = elem.getElementNames()
    for childname in childnames:
        child = elem.getByName(childname)
        if hasattr(child, "getTypes"):
            browseElementRecursively(child, processor)
        else:
            childpath = elem.composeHierarchicalName(childname)
            processor.processValueElement(childpath, child)
20行目のcreateConfigurationView()はreadDataExample()で使ったものです。

7行目のクラスIconfigurationProcessorはJavaの例ではAnonymous Classになっています。

これがAnonymous Classと分かるまでなかなかわかりませんでした。

Anonymous Classと分かってからもIconfigurationProcessorという名前がついているのにどうしてAnonymousなの?、と悩みましたが、これはインターフェイスの名前であってそれを実装しているクラスの名前はないのでAnonymousなのでした。

Reading Configuration Data - Apache OpenOffice WikiにはIconfigurationProcessorはコールバックインターフェイスだと書いてありますが、どの結果を待つコールバックなのかよくわかりませんでした(関数を引数で渡すことをコールバックと称している?)。

com.sun.star.container.XNameAccessのgetByName()メソッドを置換してエラー


LibreOffice5.1から導入になったPyUNOの新しい記法(linuxBean14.04(131)LibreOfiice5.2のインストールの参照)を試してみました。

MatthewFrancisPyUNO.pdfのスライドの10枚目に載っているcom.sun.star.container.XNameAccessインターフェイスをもっているUNOオブジェクトを辞書のように扱う方法です。

getByName()メソッドに代わって辞書のキーでデータを取り出せるはずです。

置換するのは上の28行目にあるgetByName()メソッドです。

elem.getByName(childname)をelem[childname]と書き換えれます。

ところが書き換えて実行してみるとTypeError: subscription with invalid typeがでてきました。

原因を突き止めてみるとキーがExportExtensionのときにエラーがでていました。

elem.getByName(childname)ではNoneが返ってきているのに、elem[childname]とするとTypeError: subscription with invalid typeになります。


デバッガで確認するとExportExtensionの値はNoneType: Noneになっています。
(JavaだとAny[Type[void], null]が入っていますので、この点はJava版とPython版と出力が異なります。)

他のキーではちゃんと値が返ってくるのでこれはバグですかね。

LibreOffice 5.2.6.2(バンドルPython3.5.0)を5.3.3.2(バンドルPython3.5.3)にしてみましたが同じ結果でした。

LibreOffce 5.4.0.0.beta2(バンドルPython3.5.3)でも同じですね。

20行目と27行目にでてくる再帰関数browseElementRecursively()をいじります。

ちょっとこれ以上は対処できないので素直にgetByName()を使うことにします。
def browseDataExample(cp):
    try:
        print("\n--- starting example: browse filter configuration ------------------")
        printRegisteredFilters(cp)
    except:
        traceback.print_exc()
def processValueElement(path, values):
    if isinstance(values, tuple):
        print("\tValue: {0} = {{{1}}}".format(path, ", ".join(values)))
    else:
        print("\tValue: {} = {}".format(path, values))
def processStructuralElement(elem):
    if hasattr(elem, "getTemplateName") and elem.getTemplateName().endswith("Filter"):
        print("Filter {} ({})".format(elem.getName(), elem.getHierarchicalName()))
def printRegisteredFilters(cp):
    path = "/org.openoffice.TypeDetection.Filter/Filters"
    browseConfiguration(path, cp)
def browseConfiguration(path, cp):
    ca = createConfigurationView(path, cp)
    browseElementRecursively(ca)
    ca.dispose()
def browseElementRecursively(elem):
    processStructuralElement(elem)
    childnames = elem.getElementNames()
    for childname in childnames:
        child = elem.getByName(childname)
        if hasattr(child, "getTypes"):
            browseElementRecursively(child)
        else:
            childpath = elem.composeHierarchicalName(childname)
            processValueElement(childpath, child)
よくわからないIconfigurationProcessorクラスをやめて、意味のないbrowseConfiguration関数も省きました。

22行目と28行目にでてくる再帰関数browseElementRecursively()をいじります。

再帰関数をwhile文に置換する


Python(8)線形再帰を非再帰に変換する方法を参考にwhile文に変換します、、、と思いましたが、再帰する関数をfor文で何回も呼び出しているので機械的には変換できませんね。

今回再帰しないといけない必要性は全くありませんが、私が再帰が苦手でのちのちいじりにくいというだけのことです。

再帰関数をwhile文に変換するには何をスタックに積むのかを考えます。

スタックに積まないといけないのは再び呼び出す関数のあとに使うものです。

28行目のbrowseElementRecursively(child)の次に実行されるのはfor文の最初に戻った26行目です。

そのあと27行目のif文が偽のときにelse文が実行されます。

なのでスタックに収納しないといけないのはelem(ConfigurationAccessサービスのインスタンス)とchildname(サブノードの名前)ですね。
def browseElementRecursively(ca):
    processStructuralElement(ca)
    subnames = list(reversed(ca.getElementNames()))
    names = subnames
    cas = [ca] * len(subnames)
    while names:
        name = names.pop()
        ca = cas.pop()
        child = ca.getByName(name)
        if hasattr(child, "getTypes"):
            ca = child
            processStructuralElement(ca)
            subnames = list(reversed(ca.getElementNames()))
            names.extend(subnames)
            cas.extend([ca] * len(subnames))
        else:
            childpath = ca.composeHierarchicalName(name)
            processValueElement(childpath, child)
elemをcas、chilcnameをnamesというスタックに入れました。
def processStructuralElement(ca):
    subnames = list(reversed(ca.getElementNames()))
    if hasattr(ca, "getTemplateName") and ca.getTemplateName().endswith("Filter"):
        print("Filter {} ({})".format(ca.getName(), ca.getHierarchicalName()))
    return subnames, [ca] * len(subnames) 
def browseElementRecursively(ca):
    names, cas = processStructuralElement(ca)
    while names:
        name = names.pop()
        ca = cas.pop()
        child = ca.getByName(name)
        if hasattr(child, "getTypes"):
            subnames, caa=processStructuralElement(child)
            names.extend(subnames)
            cas.extend(caa)
        else:
            childpath = ca.composeHierarchicalName(name)
            processValueElement(childpath, child)
processStructuralElement()も含めて整理するとこのようになりました。

ConfigExamples/configexamples.py at 596eae5e2692215b5628fe8c1befee8b2ec4068d · p--q/ConfigExamples

browseDataExample()をwhile文にしたものです。

結果をみてみるとprocessStructuralElement()の中のif文が真になるときはありませんね。

Javaの例でも同じです。

参考にしたサイト


Reading Configuration Data - Apache OpenOffice Wiki
ConfigExamplesのreadDataExample()とbrowseDataExample()のデベロッパーガイドの解説。

Anonymous Classes (The Java™ Tutorials > Learning the Java Language > Classes and Objects)
JavaのAnnoymousクラスの解説。

MatthewFrancisPyUNO.pdf
ConfigurationAccessサービスのgetByName()を辞書で置換するとエラーする場合がありました。

次の関連記事:LibreOffice5(51)Javaの例:ConfigExamplesをPythonにする その4

PR

0 件のコメント:

コメントを投稿