LibreOffice5(108)ドキュメントストレージからファイルシステムストレージにデータをコピーする

2017-12-10

旧ブログ

t f B! P L
LibreOffice5(105)埋め込みマクロフォルダを取り出すスクリプトではデータの書き出しはすべてSimpleFileAccessサービスのメソッドを使いましたが、今回はSimpleFileAccessサービスを使わずにデータを受け渡しさせます。

前の関連記事:LibreOffice5(107)Javaの例:Universal Content Broker (UCB)をPythonにする:その2


ストリームでストレージからストレージにコピーする


XStorageインターフェイスのcopyToStorage()メソッドやcopyElementTo()メソッドでコピーしようと思ったのですが、うまくいかないのでそれぞれのファイルについて、コピー元からインプットストリーム、コピー先からアウトプットストリームを取得して、データを移すようにしました。

新規ストリームはopenStreamElement()メソッド、新規ストレージはopenStorageElement()メソッドで作成できます。

LibreOffice5(105)埋め込みマクロフォルダを取り出すスクリプトと同様に一つ上の階層にあるodsファイルを一つ取得して、そのファイル内のマクロフォルダを、取得したodsファイルと同じ階層のsrcフォルダ内に書き出します。
#!/opt/libreoffice5.4/program/python
# -*- coding: utf-8 -*-
import unohelper  # オートメーションには必須(必須なのはuno)。
import glob
import os
from com.sun.star.embed import ElementModes  # 定数
def macro():  
 ctx = XSCRIPTCONTEXT.getComponentContext()  # コンポーネントコンテクストの取得。
 smgr = ctx.getServiceManager()  # サービスマネージャーの取得。 
 simplefileaccess = smgr.createInstanceWithContext("com.sun.star.ucb.SimpleFileAccess", ctx)  # SimpleFileAccess
 os.chdir("..")  # 一つ上のディレクトリに移動。
 ods = glob.glob("*.ods")[0]  # odsファイルを取得。最初の一つのみ取得。
 systempath = os.path.join(os.getcwd(), ods)  # odsファイルのフルパス。
 doc_fileurl = unohelper.systemPathToFileUrl(systempath)  # fileurlに変換。
 desktop = ctx.getByName('/singletons/com.sun.star.frame.theDesktop')  # デスクトップの取得。
 components = desktop.getComponents()  # ロードしているコンポーネントコレクションを取得。
 for component in components:  # 各コンポーネントについて。
  if hasattr(component, "getURL"):  # スタートモジュールではgetURL()はないため。
   if component.getURL()==doc_fileurl:  # fileurlが一致するとき
    documentstorage = component.getDocumentStorage()  # コンポーネントからストレージを取得。
    break
 else:  # ドキュメントが開いていない時。
  storagefactory = smgr.createInstanceWithContext('com.sun.star.embed.StorageFactory', ctx)  # StorageFactory
  documentstorage = storagefactory.createInstanceWithArguments((doc_fileurl, ElementModes.READ))  # odsファイルからストレージを読み取り専用で取得。
 if not ("Scripts" in documentstorage and "python" in documentstorage["Scripts"]):  # ドキュメント内に埋め込みマクロフォルダがない時は終了する。
  print("The embedded macro folder does not exist in {}.".format(ods))
  return
 dest_dir = createDest(simplefileaccess)  # 出力先フォルダのfileurlを取得。
 filesystemstoragefactory = smgr.createInstanceWithContext('com.sun.star.embed.FileSystemStorageFactory', ctx)
 filesystemstorage = filesystemstoragefactory.createInstanceWithArguments((dest_dir, ElementModes.WRITE))  # ファイルシステムストレージを取得。
 scriptsstorage = documentstorage["Scripts"]  # documentstorage["Scripts"]["python"]ではイテレーターになれない。
 getContents(scriptsstorage["python"], filesystemstorage)  # 再帰的にストレージの内容を出力先ストレージに展開。
def getContents(storage, dest):  # SimpleFileAccess、ストレージ、出力先ストレージ 
 for name in storage:  # ストレージの各要素名について。
  if storage.isStorageElement(name):  # ストレージの時。
   subdest = dest.openStorageElement(name, ElementModes.WRITE)  # 出力先に同名のストレージの作成。
   getContents(storage[name], subdest)  # 子要素について同様にする。
  elif storage.isStreamElement(name):  # ストリームの時。
   subdest = dest.openStreamElement(name, ElementModes.WRITE)  # 出力先に同名のストリームを作成。
   inputstream = storage[name].getInputStream()  # 読み取るファイルのインプットストリームを取得。
   outputstream = subdest.getOutputStream()  # 書き込むファイルのアウトプットストリームを取得。
   dummy, b = inputstream.readBytes([], inputstream.available())  # インプットストリームからデータをすべて読み込む。バイト配列の要素数とバイト配列のタプルが返る。
   outputstream.writeBytes(b)  # バイト配列をアウトプットストリームに渡す。
def createDest(simplefileaccess):  # 出力先フォルダのfileurlを取得する。
 src_path = os.path.join(os.getcwd(), "src")  # srcフォルダのパスを取得。
 src_fileurl = unohelper.systemPathToFileUrl(src_path)  # fileurlに変換。
 destdir = "/".join((src_fileurl, "Scripts/python"))
 if simplefileaccess.exists(destdir):  # pythonフォルダがすでにあるとき
  simplefileaccess.kill(destdir)  # すでにあるpythonフォルダを削除。 
 simplefileaccess.createFolder(destdir)  # pythonフォルダを作成。
 return destdir 
if __name__ == "__main__":  # オートメーションで実行するとき
 def automation():  # オートメーションのためにglobalに出すのはこの関数のみにする。
  import officehelper
  from functools import wraps
  import sys
  from com.sun.star.beans import PropertyValue  # Struct
  from com.sun.star.script.provider import XScriptContext  
  def connectOffice(func):  # funcの前後でOffice接続の処理
   @wraps(func)
   def wrapper():  # LibreOfficeをバックグラウンドで起動してコンポーネントテクストとサービスマネジャーを取得する。
    try:
     ctx = officehelper.bootstrap()  # コンポーネントコンテクストの取得。
    except:
     print("Could not establish a connection with a running office.", file=sys.stderr)
     sys.exit()
    print("Connected to a running office ...")
    smgr = ctx.getServiceManager()  # サービスマネジャーの取得。
    print("Using {} {}".format(*_getLOVersion(ctx, smgr)))  # LibreOfficeのバージョンを出力。
    return func(ctx, smgr)  # 引数の関数の実行。
   def _getLOVersion(ctx, smgr):  # LibreOfficeの名前とバージョンを返す。
    cp = smgr.createInstanceWithContext('com.sun.star.configuration.ConfigurationProvider', ctx)
    node = PropertyValue(Name = 'nodepath', Value = 'org.openoffice.Setup/Product' )  # share/registry/main.xcd内のノードパス。
    ca = cp.createInstanceWithArguments('com.sun.star.configuration.ConfigurationAccess', (node,))
    return ca.getPropertyValues(('ooName', 'ooSetupVersion'))  # LibreOfficeの名前とバージョンをタプルで返す。
   return wrapper
  @connectOffice  # createXSCRIPTCONTEXTの引数にctxとsmgrを渡すデコレータ。
  def createXSCRIPTCONTEXT(ctx, smgr):  # XSCRIPTCONTEXTを生成。
   class ScriptContext(unohelper.Base, XScriptContext):
    def __init__(self, ctx):
     self.ctx = ctx
    def getComponentContext(self):
     return self.ctx
    def getDesktop(self):
     return ctx.getByName('/singletons/com.sun.star.frame.theDesktop')  # com.sun.star.frame.Desktopはdeprecatedになっている。
    def getDocument(self):
     return self.getDesktop().getCurrentComponent()
   return ScriptContext(ctx)  
  return createXSCRIPTCONTEXT()  # XSCRIPTCONTEXTの取得。
 XSCRIPTCONTEXT = automation()  # XSCRIPTCONTEXTを取得。 
 macro()  
スクリプトがあるフォルダを起点にしているのでマクロでは実行できず、オートメーションでしか実行できません。

LibreOffice5(99)インプットストリーム、アウトプットストリーム、パイプの学習のPipeなどのバッファを使わず直接パッシブデータソースからデータを読み取って、パッシブデータシンクにデータを書き込む場合に該当するのですが、なかなか思い付きませんでした。

LibreOffice5(107)Javaの例:Universal Content Broker (UCB)をPythonにする:その2のDataStreamRetrieverをPythonにしてようやく理解できました。

LibreOfficeで開いていないドキュメント内へのURIを使う


VndSunStarPkgUrlReferenceFactoryサービスを使えばfileurlからvnd.sun.star.pkgプロトコールを使った正しいURIを作成できることがわかりました。

そのURIでSimpleFileAccessサービスのcopy()メソッドでドキュメント内のマクロフォルダをコピーします。
import unohelper  # オートメーションには必須(必須なのはuno)。
import glob
import os
def macro():  
 ctx = XSCRIPTCONTEXT.getComponentContext()  # コンポーネントコンテクストの取得。
 smgr = ctx.getServiceManager()  # サービスマネージャーの取得。 
 simplefileaccess = smgr.createInstanceWithContext("com.sun.star.ucb.SimpleFileAccess", ctx)  # SimpleFileAccess
 os.chdir("..")  # 一つ上のディレクトリに移動。
 ods = glob.glob("*.ods")[0]  # odsファイルを取得。最初の一つのみ取得。
 systempath = os.path.join(os.getcwd(), ods)  # odsファイルのフルパス。
 doc_fileurl = unohelper.systemPathToFileUrl(systempath)  # fileurlに変換。
 desktop = ctx.getByName('/singletons/com.sun.star.frame.theDesktop')  # デスクトップの取得。
 components = desktop.getComponents()  # ロードしているコンポーネントコレクションを取得。
 for component in components:  # 各コンポーネントについて。
  if hasattr(component, "getURL"):  # スタートモジュールではgetURL()はないため。
   if component.getURL()==doc_fileurl:  # fileurlが一致するとき、ドキュメントが開いているということ。
    doc = XSCRIPTCONTEXT.getDocument()
    transientdocumentsdocumentcontentfactory = smgr.createInstanceWithContext("com.sun.star.frame.TransientDocumentsDocumentContentFactory", ctx)
    transientdocumentsdocumentcontent = transientdocumentsdocumentcontentfactory.createDocumentContent(doc)
    pkgurl = transientdocumentsdocumentcontent.getIdentifier().getContentIdentifier()  # ex. vnd.sun.star.tdoc:/1
    break
 else:  # ドキュメントを開いていない時。
  urireferencefactory = smgr.createInstanceWithContext("com.sun.star.uri.UriReferenceFactory", ctx)  # UriReferenceFactory
  urireference = urireferencefactory.parse(doc_fileurl)  # ドキュメントのUriReferenceを取得。
  vndsunstarpkgurlreferencefactory = smgr.createInstanceWithContext("com.sun.star.uri.VndSunStarPkgUrlReferenceFactory", ctx)  # VndSunStarPkgUrlReferenceFactory
  vndsunstarpkgurlreference = vndsunstarpkgurlreferencefactory.createVndSunStarPkgUrlReference(urireference)  # ドキュメントのvnd.sun.star.pkgプロトコールにUriReferenceを変換。
  pkgurl = vndsunstarpkgurlreference.getUriReference()  # UriReferenceから文字列のURIを取得。
 python_fileurl = "/".join((pkgurl, "Scripts/python"))  # ドキュメント内フォルダへのフルパスを取得。
 dest_dir = createDest(simplefileaccess)  # 出力先フォルダのfileurlを取得。
 if simplefileaccess.exists(python_fileurl):  # 埋め込みマクロフォルダが存在する時。
  simplefileaccess.copy(python_fileurl, dest_dir)  # 埋め込みマクロフォルダを出力先フォルダにコピーする。ドキュメントを閉じているときはIsFolderプロパティが取得できないとエラー。
 else:  # 埋め込みマクロフォルダが存在しない時。
  print("The embedded macro folder does not exist in {}.".format(ods)) 
略してあるのでこれを実行するには最初のスクリプトの44行目以降を追加します。

この方法はコピー元となるドキュメント内の各フォルダは空であるかファイルが入っている必要があります。

フォルダだけが入っているフォルダをコピーしようとすると次のようなエラーがでてきて失敗します(LibreOffice5(109)子要素にフォルダしか持たないドキュメント内フォルダ参照)。

UnknownPropertyException: Unable to get property 'IsFolder' from source object!

次の関連記事:LibreOffice5(109)子要素にフォルダしか持たないドキュメント内フォルダ

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ