LibreOffice5(112)埋め込みマクロフォルダを書き込むスクリプト

2017-12-17

旧ブログ

t f B! P L
読み込むときと違って書き込む方法はそんなに選択肢はありませんでした。

前の関連記事:LibreOffice5(111)インプットストリームのバイトシークエンスをファイルに書き込む


LibreOfficeで開いているドキュメントに埋め込みマクロフォルダを書き込むスクリプト


一つ上の階層にあるodsファイルを一つ取得して、src/Scripts/pythonにあるマクロファイルを書き込みます。
#!/opt/libreoffice5.4/program/python
# -*- coding: utf-8 -*-
import unohelper  # オートメーションには必須(必須なのはuno)。
import glob
import os
def main():  # マクロでは利用不可。docを取得している行以降のみはマクロで実行可。 
 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:  # ドキュメントを開いていない時。__main__.InteractiveAugmentedIOException: Cannot store persistent data!と言われてできない。
  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"))  # ドキュメント内フォルダへのフルパスを取得。  
 if simplefileaccess.exists(python_fileurl):  # 埋め込みマクロフォルダがすでにあるとき
  simplefileaccess.kill(python_fileurl)  # 埋め込みマクロフォルダを削除。
 simplefileaccess.createFolder(python_fileurl)  # 埋め込みマクロフォルダを作成。
 sourcedir = getSource(simplefileaccess)  # コピー元フォルダのfileurlを取得。 
 if sourcedir:  # コピー元フォルダが存在するとき。
  simplefileaccess.copy(sourcedir, python_fileurl)  # 埋め込みマクロフォルダを出力先フォルダにコピーする。
 else:
  print("{} does not exist.".format(sourcedir)) 
def getSource(simplefileaccess):  # コピー元フォルダのfileurlを取得する。
 src_path = os.path.join(os.getcwd(), "src")  # srcフォルダのパスを取得。
 src_fileurl = unohelper.systemPathToFileUrl(src_path)  # fileurlに変換。
 sourcedir = "/".join((src_fileurl, "Scripts/python"))
 if simplefileaccess.exists(sourcedir):  # pythonフォルダがすでにあるとき
  return sourcedir
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を取得。 
 main()  
vnd.sun.star.tdocプロトコールを使って開いているドキュメント内のURLを取得して、SimpleFileAccessのcopy()メソッドを使って、マクロフォルダを書き込んでいます。

書き込み後にドキュメントを保存してアーカイバで確認してみるとmanifest.xmlファイルもLibreOffice(31)Pythonマクロをドキュメントファイルに埋め込むと同様にちゃんと編集されていました。

ただ、開いているドキュメントの埋め込みマクロを編集する場合はLibreOffice5(101)埋め込みマクロの更新とマクロセレクターとAPSOの問題を考慮する必要があります。

24行目ではドキュメントをLibreOfficeで開いていないときもvnd.sun.star.pkgプロトコールでドキュメント内のURLを取得して同様にマクロフォルダを書き込もうとしましたが、InteractiveAugmentedIOExceptionがでて、書き込めませんでした。
(2017.12.20追記。Scripts/pythonフォルダだけがvnd.sun.star.pkgプロトコールで書き込めないことが原因とわかりました。Scripts/pythonフォルダがすでに存在するドキュメントにはvnd.sun.star.pkgプロトコールで書き込めます。)

Packageを取得して埋め込みマクロフォルダを書き込むスクリプト


LibreOfficeで開いていないドキュメントにはドキュメントをPackageとして取得すれば書き込みできました。

#!/opt/libreoffice5.4/program/python
# -*- coding: utf-8 -*-
import unohelper  # オートメーションには必須(必須なのはuno)。
import glob
import os
def main():  
 ctx = XSCRIPTCONTEXT.getComponentContext()  # コンポーネントコンテクストの取得。
 smgr = ctx.getServiceManager()  # サービスマネージャーの取得。 
 simplefileaccess = smgr.createInstanceWithContext("com.sun.star.ucb.SimpleFileAccess", ctx)  # SimpleFileAccess
 os.chdir("..")  # 一つ上のディレクトリに移動。
 source_path = os.path.join(os.getcwd(), "src", "Scripts", "python")  # コピー元フォルダのパスを取得。 
 source_fileurl = unohelper.systemPathToFileUrl(source_path)  # fileurlに変換。 
 if not simplefileaccess.exists(source_fileurl):  # ソースにするフォルダがないときは終了する。
  print("The source macro folder does not exist.") 
  return
 ods = glob.glob("*.ods")[0]  # odsファイルを取得。最初の一つのみ取得。
 systempath = os.path.join(os.getcwd(), ods)  # odsファイルのフルパス。
 doc_fileurl = unohelper.systemPathToFileUrl(systempath)  # fileurlに変換。
 package = smgr.createInstanceWithArgumentsAndContext("com.sun.star.packages.Package", (doc_fileurl,), ctx)  # Package。第2引数はinitialize()メソッドで後でも渡せる。
 docroot = package.getByHierarchicalName("/")  # /Scripts/pythonは不可。
 if "Scripts" in docroot:  # Scriptsフォルダがすでにあるときは削除する。
  del docroot["Scripts"]
 docroot["Scripts"] = package.createInstanceWithArguments((True,))  # ScriptsキーにPackageFolderを挿入。
 docroot["Scripts"]["python"] = package.createInstanceWithArguments((True,))  # pythonキーにPackageFolderを挿入。
 writeScripts(simplefileaccess, package, source_fileurl, docroot["Scripts"]["python"])  # 再帰的にマクロフォルダーにコピーする。
 package.commitChanges()  # ファイルにパッケージの変更を書き込む。manifest.xmlも編集される。フォルダは書き込まれない。
def writeScripts(simplefileaccess, package, source_fileurl, packagefolder):  # コピー元フォルダのパス、出力先パッケージフォルダ。
 for fileurl in simplefileaccess.getFolderContents(source_fileurl, True):  # Trueでフォルダも含む。再帰的ではない。フルパスのfileurlが返る。
  name = fileurl.split("/")[-1]  # 要素名を取得。
  if simplefileaccess.isFolder(fileurl):  # フォルダの時。
   if not name in packagefolder:  # パッケージの同名のPackageFolderがない時。
    packagefolder[name] = package.createInstanceWithArguments((True,))  # キーをnameとするPackageFolderを挿入。
   writeScripts(simplefileaccess, package, fileurl, packagefolder[name])  # 再帰呼び出し。   
  else:
   packagefolder[name] = package.createInstance()  # キーをnameとするPackageStreamを挿入。
#    packagefolder[name] = package.createInstanceWithArguments((False,))  # キーをnameとするPackageStreamを挿入。
   packagefolder[name].setInputStream(simplefileaccess.openFileRead(fileurl))  # ソースファイルからインプットストリームを取得。
動かすには最初のスクリプトの45行目以降を追加します。

manifest.xmlも編集されていますが、manifest.xmlファイルに登録されているのはpyファイルのみで、ScriptsフォルダとScripts/pythonフォルダは登録されていませんでした。

しかしそれでも埋め込みマクロをマクロセレクターから呼び出して実行することができました。

ということはmanifest:media-type="application/binary"でマクロフォルダを登録することは不必要なことでしょうか。
(2017.12.20追記。Scripts/pythonフォルダがmanifest.xmlに登録されないこの方法には大きな問題があることがわかりました。ドキュメントを編集して保存するときにmanifext.xmlに登録されていないScripts/pythonフォルダは丸ごと削除されてしまいます。)

ドキュメントストレージには書き込みできず


ドキュメントストレージから埋め込みマクロフォルダを読み込むことができたのに対して、書き込むことはできませんでした。
#!/opt/libreoffice5.4/program/python
# -*- coding: utf-8 -*-
import unohelper  # オートメーションには必須(必須なのはuno)。
import glob
import os
from com.sun.star.embed import ElementModes  # 定数
def main():  
 ctx = XSCRIPTCONTEXT.getComponentContext()  # コンポーネントコンテクストの取得。
 smgr = ctx.getServiceManager()  # サービスマネージャーの取得。 
 simplefileaccess = smgr.createInstanceWithContext("com.sun.star.ucb.SimpleFileAccess", ctx)  # SimpleFileAccess
 os.chdir("..")  # 一つ上のディレクトリに移動。
 source_path = os.path.join(os.getcwd(), "src", "Scripts", "python")  # コピー元フォルダのパスを取得。 
 source_fileurl = unohelper.systemPathToFileUrl(source_path)  # fileurlに変換。 
 if not simplefileaccess.exists(source_fileurl):  # ソースにするフォルダがないときは終了する。
  print("The source macro folder does not exist.") 
  return
 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.READWRITE))  # odsファイルからストレージを取得。
 if "Scripts" in documentstorage:  # Scriptsフォルダがすでにあるときは削除する。
  documentstorage.removeElement("Scripts")
  documentstorage.commit()
 scriptsstorage = documentstorage.openStorageElement("Scripts", ElementModes.READWRITE) 
 documentstorage.commit()
 pythonstorage = scriptsstorage.openStorageElement("python", ElementModes.READWRITE) 
 scriptsstorage.commit()
 writeScripts(simplefileaccess, source_fileurl, pythonstorage)  # 再帰的にマクロフォルダーにコピーする。
 documentstorage.commit()
def writeScripts(simplefileaccess, source_fileurl, storage):  # コピー元フォルダのパス、出力先ストレージ。
 for fileurl in simplefileaccess.getFolderContents(source_fileurl, True):  # Trueでフォルダも含む。再帰的ではない。フルパスのfileurlが返る。
  name = fileurl.split("/")[-1]  # 要素名を取得。
  if simplefileaccess.isFolder(fileurl):  # フォルダの時。
   if not name in storage:  # ストレージに同名のストレージがない時。
    substorage = storage.openStorageElement(name, ElementModes.READWRITE) 
    storage.commit()
   writeScripts(simplefileaccess, fileurl, substorage)  # 再帰呼び出し。   
  else:
   xstream = storage.openStreamElement(name, ElementModes.WRITE) 
   outputstream = xstream.getOutputStream()
   inputstream = simplefileaccess.openFileRead(fileurl)
   dummy, b = inputstream.readBytes([], simplefileaccess.getSize(fileurl))
   outputstream.writeBytes(b)
   storage.commit()
commit()をできる限りいれてみましたが、やはり書き込みできず、エラーもでません。

どうもストレージはWRITEするようなものではないようです。

参考にしたサイト


OOoBasic/Generic/Zip - ...?
Packageの解説。

次の関連記事:LibreOffice5(113)埋め込みマクロフォルダを読み書きする方法:まとめ

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ