2020年2月9日日曜日

29: LibreOfficeをファイルコンバーターとして最適に用いる(Python実装)

<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>

OpenDocument/Microsoft Office/等フォーマット間で、PDF/CSVフォーマットへ、高速かつ万能に、ドキュメントを調整することも可能。動かせるサンプルあり。

話題


About: UNO (Universal Network Objects)
About: LibreOffice
About: Apache OpenOffice
About: Pythonプログラミング言語

この記事の目次


開始コンテキスト



ターゲットコンテキスト



  • 読者は、PythonにてUNO APIを直接に用いてファイルコンバージョンを実装する方法を理解し、動かせるサンプルを得る。

オリエンテーション


本シリーズの任意のサンプルプログラムをビルドする方法の記事があります。

LibreOfficeまたはApache OpenOfficeデーモンを作成する方法の記事があります。

LibreOfficeまたはApache OpenOffice Microsoft Windowsサービスを作成する方法の記事があります。

オフィスファイルをオープニングパスワードでUNOを用いて暗号化する方法の記事があります。

OpenDocumentファイルに編集パスワードをUNOを用いてセットすることについての記事があります。

バイナリWord/Excelファイルに編集パスワードをUNOを用いてセットすることについての記事があります。

Office Open XMLファイルに編集パスワードをUNOを用いてセットすることについての記事があります。

ワードプロセッサドキュメントのページのサイズをUNOを用いてセットすることについての記事があります。

スプレッドシートドキュメントのページのサイズをUNOを用いてセットすることについての記事があります。

ワードプロセッサドキュメントの任意のページプロパティをUNOを用いてセットすることについての記事があります。

スプレッドシートドキュメントの任意のページプロパティをUNOを用いてセットすることについての記事があります。

任意のオフィスドキュメントをPDFファイルとしてフル仕様指定でUNOを用いてエクスポートすることについての記事があります。

任意のスプレッドシートをCSVへ任意のフォーマットでUNOを用いて書き出すことについての記事があります。

任意のスプレッドシートドキュメントの全スプレッドシートをCSVファイル群へUNOを用いて書き出すことについての記事があります。


本体

ト書き
Special-Student-7、Lenard(Pythonプログラマー)、Jane(Pythonプログラマー)がコンピュータの前にいる。


0: 注意: 'soffice --convert-to' は決して使いません


Special-Student-7
本記事のコンセプトパートが読まれているものと想定されていることは明確に述べられているものの、読者の内のかなりの割合が読んでいないし読まないだろうことが推測でき、多くの人々が'soffice --convert-to'を使うことに固執している事実を鑑みて、念押しすることが適切だろうと感じて申しますが、ここでは、'soffice --convert-to'は決して使われません。

その理由は、コンセプトパートに詳述されています。


1: UNO APIを用いたファイルコンバージョンロジックの実装


Special-Student-7
知っておく必要のあることは本記事のコンセプトパートから既にご存知であると想定して、UNO APIを用いたファイルコンバージョンロジックのPython実装をすぐに見ましょう、ここで、'l_underlyingRemoteUnoObjectsContextInXComponentContext'はLibreOfficeまたはApache OpenOfficeインスタンスへのUNOオブジェクト群コンテキスト、'l_originalFileUrl'は元ファイルのURL、'l_targetFileUrl'はターゲットファイルのURL、'l_fileStoringFilterName'はファイル格納フィルター名です。

@Python ソースコード
import sys
from typing import List
from typing import Match
from typing import Optional
from typing import Pattern
from typing import cast
import re
import uno
from com.sun.star.beans import PropertyValue
from com.sun.star.frame import XDispatchProvider
from com.sun.star.frame import XStorable2
from com.sun.star.frame import XSynchronousDispatch
from com.sun.star.uno import XComponentContext
from com.sun.star.uno import XInterface
from com.sun.star.util import URL as com_sun_star_util_URL
from com.sun.star.util import XCloseable
~

class Test1Test:
	c_urlRegularExpression: Pattern  = re.compile ("(.*:)(.*)")
	
	@staticmethod
	def getUrlInURL (a_url: str) -> com_sun_star_util_URL:
		l_urlInURL: com_sun_star_util_URL = com_sun_star_util_URL ()
		l_urlInURL.Complete = a_url
		l_urlInURL.Main = l_urlInURL.Complete
		l_regularExpressionMatch: Optional [Match] = Test1Test.c_urlRegularExpression.match (l_urlInURL.Complete, 0)
		if l_regularExpressionMatch is not None:
			l_urlInURL.Protocol = l_regularExpressionMatch.groups () [0]
			l_urlInURL.User = ""
			l_urlInURL.Password = ""
			l_urlInURL.Server = ""
			l_urlInURL.Port = 0
			l_urlInURL.Path = l_regularExpressionMatch.groups () [1]
			l_urlInURL.Name = ""
			l_urlInURL.Arguments = ""
			l_urlInURL.Mark = ""
		return l_urlInURL
	
	@staticmethod
	def main (a_arguments: List [str]) -> None:
		~
				l_originalFileUrl: str = a_arguments [2];
				l_targetFileUrl: str = a_arguments [3];
				l_fileStoringFilterName: str = a_arguments [4];
				
				l_underlyingUnoDesktopInXDispatchProvider: XDispatchProvider = cast (XDispatchProvider, cast (XInterface, l_underlyingRemoteUnoObjectsContextInXComponentContext.getValueByName ("/singletons/com.sun.star.frame.theDesktop")))
				l_underlyingFileOpeningUnoDispatcherInXSynchronousDispatch: XSynchronousDispatch = cast (XSynchronousDispatch, l_underlyingUnoDesktopInXDispatchProvider.queryDispatch (Test1Test.getUrlInURL ("file:///"), "_blank", -1))
				
				l_originalFileUrlInURL: com_sun_star_util_URL = Test1Test.getUrlInURL (l_originalFileUrl)
				l_unoDocumentOpeningProperties: List [PropertyValue] = [None, None, None, None]
				l_unoDocumentOpeningProperties [0] = PropertyValue ("ReadOnly", -1, True, 0);
				l_unoDocumentOpeningProperties [1] = PropertyValue ("Hidden", -1, True, 0);
				l_unoDocumentOpeningProperties [2] = PropertyValue ("OpenNewView", -1, True, 0);
				l_unoDocumentOpeningProperties [3] = PropertyValue ("Silent", -1, True, 0);
				l_unoDocumentStoringProperties: List [PropertyValue] = [None, None]
				l_unoDocumentStoringProperties [0] = PropertyValue ("FilterName", -1, l_fileStoringFilterName, 0)
				l_unoDocumentStoringProperties [1] = PropertyValue ("Overwrite", -1, True, 0);
				l_underlyingOriginalUnoDocumentInXStorable2: XStorable2 = cast (XStorable2, l_underlyingFileOpeningUnoDispatcherInXSynchronousDispatch.dispatchWithReturnValue (l_originalFileUrlInURL, l_unoDocumentOpeningProperties))
				l_hasSucceeded: bool = False
				if l_underlyingOriginalUnoDocumentInXStorable2 is not None:
					try:
						l_underlyingOriginalUnoDocumentInXStorable2.storeToURL (l_targetFileUrl, l_unoDocumentStoringProperties)
						l_hasSucceeded = True
					except (Exception) as l_exception:
						raise l_exception
					cast (XCloseable, l_underlyingOriginalUnoDocumentInXStorable2).close (False)
				else:
					sys.stdout.write ("### The original file: '{0:s}' cannot be opened.\n".format (l_originalFileUrl))
					sys.stdout.flush ()
		~

if __name__ == "__main__":
	Test1Test.main (sys.argv)


Lenard
そのコードはそのままコピーしていいの?何か変えないといけない?

Special-Student-7
最小要件のためには、あなたは何も変更する必要はありません、あなたのコードが、既にUNOオブジェクト群コンテキストを得ていて、それらのURLおよびファイル格納フィルター名を適切にセット済みだとしたら。

Lenard
私のコードは既にUNOオブジェクト群コンテキストを得ていますか?

Special-Student-7
私は存じませんが。 . . . もしも、そうでなければ、ある記事(単にまたはscrupulous(用意周到)なやり方用)にしたがって得ることができます。.

Jane
私の望むフィルター名をどこで見つけられるの?

Special-Student-7
可能なフィルター名は、本記事のコンセプトパートに挙げられています。

Jane
えーと、そのコードは何をやってるの?

Lenard
それは、あるファイルをコンバートしてる。

Jane
それは知ってる。どうやってそうしてるの?

Special-Student-7
そのコードは、まさに、本記事のコンセプトパートのあるセクションにて申し上げたことを行なっています: 元ファイルをオープンする、オープンされたドキュメントをターゲットフォーマットにて格納する、オープンされたドキュメントをクローズする。

'dispatchWithReturnValue'が元ファイルをオープンして、'storeToURL'がオープンされたドキュメントを格納して、'close'がオープンされたドキュメントをクローズしています。

'l_underlyingFileOpeningUnoDispatcherInXSynchronousDispatch'はUNOディスパッチャであり、ファイル毎に取得する必要はありません。


2: オープンするまたは格納することに対するプロパティ群について


Special-Student-7
上記コードに見られるとおり、ファイルをオープンすることに対していくつかのプロパティをセットすることができます。実のところ、以下が、可能なプロパティ群の仕様です、ここで、データタイプはPythonタイプです(UNOタイプではなく)。

名前データタイプ説明
ReadOnlyboolオープンされたドキュメントを元ファイルに書き戻せないかどうか: 'true'-> できない、'false'-> できる
Hiddenboolオープンされたドキュメントは不可視かどうか: 'true'-> 不可視、'false'-> 不可視でない
OpenNewViewboolドキュメントは新たなビュー内にオープンされるかどうか: 'true'-> 新たなビュー内に、'false'-> 新たなビュー内にではない
Silentboolドキュメントはサイレントにオープンされるかどうか: 'true'-> サイレントに、'false'-> サイレントにではない
Passwordstrオープニングパスワード

関心を持たれるかもしれないプロパティに、オープニングパスワードがあります。

Jane
ふーん、オープニングパスワードでプロテクトされたファイルもプログラムからコンバートできるのね。

Lenard
ファイルオーナーたちにパスワードを私に教えさせる。

Jane
そんなことしちゃだめ。それはパスワード盗みよ。ユーザーにパスワードをプログラムに入れさせなさい。

Special-Student-7
ご注意いただきたいのですが、UNO 'string'は公式には'None'を受け入れません、したがって、もしもオープニングパスワードがなければ、プロパティ自体、指定すべきではありません、'None'が偶然受け入れられることはあるかもしれませんが。

これもご注意いただきたいのですが、'ReadOnly'は、オープンされたドキュメントが編集できないことを意味せず、そのドキュメントは元ファイルへ書き戻せないことを意味します、したがって、ドキュメントが調整されて新たなファイルへ書かれるのに、それが'False'である必要はありません。

ドキュメントを格納することに対しても、いくつかのプロパティをセットすることができます。実のところ、以下が、可能なプロパティ群の仕様です、ここで、データタイプはPythonタイプです(UNOタイプではなく)。

名前データタイプ説明
FilterNamestrフィルター名
FilterData場合による一部のフィルターに対するフィルター特有のデータ: 常にというわけではないが、しばしば'com.sun.star.beans.PropertyValue'のリスト
FilterOptionsstr一部のフィルターに対するフィルター特有のデータ
AsTemplateboolドキュメントがテンプレートとして格納されるかどうか: 'true'-> テンプレートとして、'false'-> テンプレートとしてではない
Authorstr著者名
DocumentTitlestrドキュメントタイトル
EncryptionDatacom.sun.star.beans.NamedValue []一部のフィルターに対する暗号化データ
Passwordstr一部のフィルターに対するオープニングパスワード
CharacterSetstr文字セット
Versionintバージョン番号
Commentstrバージョン記述
Overwriteboolファイルが上書きされるかどうか: 'true' -> 上書きされる、'false' -> 上書きされない
ComponentData場合による一部のフィルター特有データ: 常にというわけではないが、しばしば'com.sun.star.beans.NamedValue'のリスト
ModifyPasswordInfo場合による一部のフィルターに対する編集パスワードデータ

オープニングパスワードは実のところ暗号化データに他なりません。なぜ、'EncryptionData'と'Password'の両方があるかという理由は、一部のフィルターは一方を使い、他の一部のフィルターは他方を使うことです。詳細は、別の記事をご参照ください。

編集パスワードをセットすることに関する事情はかなり複雑です: 編集パスワードは、フォーマット特有のアルゴリズムでハッシュ化されなければならず、ハッシュは、フォーマット特有の場所へセットされなければならず、場所は必ずしもファイル格納プロパティ群の中ではありません。 . . . 詳細は、OpenDocumentファイル用Microsoftバイナリファイル用Office Open XMLファイル用のある記事に説明されています。

Lenard
私は編集パスワードなど嫌いだ。なんで私はファイルを編集すべきじゃないんだ?

Jane
あんたはコンテンツを変更する権限を与えられてないからよ。

Special-Student-7
えーと、編集パスワードは、改ざん防止としては効果的ではなく、主に、コンテンツを偶然に変更してしまわないためのものです、しかし、いずれにせよ、私の動かせるサンプルはそのロジックを実装してあるので、それをお使いいただけます、もしも、よろしければ。

Jane
'FilterData'や'FilterOptions'のようなそれら"フィルター特有"プロパティを具体的にどうできるのか、分からないんだけど。

Special-Student-7
それは、ファイルコンバージョンの一般的ロジックのためのものである本記事では説明できません: PDFファイル用CSVファイル用といった、いくつかの他の記事をご参照ください。 .


3: ドキュメントを調整することについて


Special-Student-7
ターゲットファイルは、ファイル格納プロパティ群を介してのみでは満足のいくようにコントロールされないかもしれません。

その場合は、オープンされたドキュメントを調整することができます。

Lenard
"調整"?それは改ざんじゃないの?

Special-Student-7
いいえ、特には: 変更はターゲットファイルのみへ行くものと意図されています。元ファイルはそのままであるように意図されています。

Lenard
"意図されて" . . .、それじゃあ、プログラマーは密かに元ファイルを改ざんできる!

Special-Student-7
もしも、プログラマーに悪意があれば、ファイルを改ざんするというのは、あなたが心配しなければいけない唯一のことではありません。えーと、あなたは、元ファイル群に対して当該プログラムに読み取り権限のみを付与するというような対策を取ることができます。

Lenard
おお、それを誰にも教えないで!

Jane
彼は、プログラマーとしてファイルを改ざんしたいのよ。

Special-Student-7
上記コードにおいて、'l_underlyingOriginalUnoDocumentInXStorable2'は、オープンされたドキュメントのUNOオブジェクトを代表しており、それを介して、あなたは、オープンされたドキュメントを調整することができます。

Lenard
具体的にどう、私はドキュメントを調整できるのか?

Special-Student-7
それは、勿論ドキュメント特有、要件特有であって、いくつかの他の記事、例えば、ワードプロセッサドキュメントのページプロパティ群を調整するためのもの(ここおよびここ)やスプレッドシートドキュメントのページプロパティ群を調整するためのもの(ここおよびここ)をご参照ください。


4: 動かせるサンプルがここにあります


Special-Student-7
ある動かせるサンプルがここからダウンロード可能です。

そのサンプルプログラムは、引数の1つとして、ファイルコンバージョン命令群を格納したJSONファイルのパス(実のところ、複数のJSONファイルパスを取れます)を取り、それらの命令を実現します。

JSONファイルの仕様は以下のとおりです。

@JSON ソースコード
[
	{Disabled: %本レコードが無効化されているか否か -> 'true'または'false'%, 
	OriginalFileUrl: %元ファイルURL、その中では任意のオペレーティングシステム環境変数を使用できる、'%{HOME}'のように%, 
	TargetFileUrl: %ターゲットファイルURL、その中では任意のオペレーティングシステム環境変数を使用できる、'%{HOME}'のように%, 
	FileStoringFilterName: %ファイル格納フィルター名%, 
	OpeningPassword: %元ファイルに対するオープニングパスワード%, 
	Title: %ターゲットファイルタイトル%, 
	EncryptingPassword: %ターゲットファイルに対する暗号化パスワード%, 
	EditingPassword: %ターゲットファイルに対する編集パスワード%, 
	CertificateIssuerName: %ターゲットファイルに署名するための証明書発行者名%, 
	CertificatePassword: %ターゲットファイルに署名するための証明書パスワード%, 
	SignatureTimeStampAuthorityUrl: %ターゲットファイルに署名するためのタイムスタンプオーソリティURL%, 
	DocumentTailorNames: [%ドキュメントテイラー名%, . . .], 
	AllSpreadSheetsAreExported: %全スプレッドシートがエクスポートされるか否か: 'true'または'false'%, 
	TargetFileNamingRule: %ターゲットCSVファイル群に対する命名規則: '0'-> 各CSVファイルはシートインデックスを使って命名される、'1'-> 各CSVファイルはシート名を使って命名される%, 
	CsvItemsDelimiterCharacterCode: %CSV項目間デリミタの文字コード: 例えば、'44'-> ','%, 
	CsvTextItemQuotationCharacterCode: %CSVテキスト項目クォーテーションの文字コード: 例えば、'34'-> '"'%, 
	CsvCharactersEncodingCode: %CSV文字群エンコーディングコード: '76' -> UTF-8、'65535' -> UCS-2、'65534' -> UCS-4、'11' -> US-ASCII、'69' -> EUC_JP、'64' -> SHIFT_JIS%, 
	CsvAllTextItemsAreQuoted: %全てのCSVテキスト項目がクウォートされるか否か: 'true'または'false'%, 
	CsvContentsAreExportedAsShown: %CSVコンテンツがシート上に表示されているとおりにエクスポートされるか否か: 'true'または'false'%, 
	CsvFormulaeThemselvesAreExported: %シートセルフォーミュラ自体がCSVファイルへエクスポートされるか否か: 'true'または'false'%, 
	HiddenSpreadSheetsAreExported: %非表示スプレッドシートもエクスポートされるか否か%}, 
	. . .
	{. . .}
]

Jane
"テイラー"って何、ところで?

Special-Student-7
テイラーとは、ドキュメントを特定の方法にて調整する任務を帯びたオブジェクトのことです。

Jane
それじゃあ、それを私が作らないといけないってこと?

Special-Student-7
私はいくつかのサンプルテイラーを用意しました、しかし、はい、特定のニーズをお持ちでしたら、ご自分でお作りになる必要があるでしょう。

Jane
それで、そのクラスのフルネームを指定すると?

Special-Student-7
いいえ、オブジェクト名を指定する必要があります: 1つのクラス由来の複数のテイラーがあるかもしれません、それぞれ違うパラメータでコンストラクトされた。

Jane
はあ?"オブジェクト名"ってどういう意味?変数名?

Special-Student-7
いいえ。そのオブジェクトのための、テイラーたちが登録されるディクショナリ内のキー値です。 . . . ご心配なく。それについては、後のサブセクションで話します。

それはとにかく、あなたのコンバージョン命令たちが、例えばWebリクエストから来るのであれば、あなたは、各Webリクエストからメモリ内JSONレコードを形成して、そのJSONレコードを、そうしたJSONレコードを受け取る'executeConversionOrder'メソッドへ渡すことができます。

Lenard
それでは、私はそのメソッドを使えるわけだ、プログラムがどんな形で命令を受け取るとしても。

Special-Student-7
もしも、JSONレコードに追加の機能が必要でしたら、あなたは、'executeConversionOrder'メソッドを'FileConversionOrderExtendedJsonDatumParseEventsHandler' JSON解析イベント群ハンドラと共に変更することができます。

Jane
ふーむ。

Special-Student-7
サンプルは複数の命令群ファイルを受け付けるようにしてあり、それらのファイルは同時実行で処理されます、その目的は、この最適な方法では、コンバージョンは同時実行で行なえることをデモンストレーションすることです。


4-1: サンプルプロジェクトをビルドする


Special-Student-7
サンプルプロジェクトをビルドする方法は、ある記事に説明されています。.

特に、注意すべきは、プロジェクト群は、いくつか別のプログラミング言語による実装も含んでおり、それらは、Javaプログラマーであるあなたは多分必要としないだろうということです。そうした不要なコードを無視する方法はそこに説明されています。

メインプロジェクトは'filesConverter'です。


4-2: サンプルプログラムを実行する


Special-Student-7
サンプルプログラムを実行する前に、LibreOfficeまたはApache OpenOfficeインスタンスを、それがクライアントからのコネクションを受け付けるように開始しておかなければなりません、ある以前の記事にて学んだ方法によって。

ト書き
Special-Student-7は、LibreOfficeインスタンスをポート番号2002で開始する。

Special-Student-7
コンバージョン命令群JSONファイルが必要です(上に挙げられた仕様で)で、私たちは、アーカイブファイルに'filesConverter/data/FileConversionOrders.json'として含まれているサンプルJSONファイルを使用しますが、少なくとも、その中のファイルURL群は調整しなければなりません。

サンプルプログラムは以下のように実行できます、カレントディレクトリをサンプルプロジェクトディレクトリに位置させて。

@cmd ソースコード
gradle i_executePythonExecutableFileTask -Pi_mainModuleName="theBiasPlanet.filesConverter.programs.FilesConverterConsoleProgram" -Pi_commandLineArguments="\"socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext\" \"data/FileConversionOrders.json\""

ト書き
Special-Student-7は、上記コマンドを、ターミナル内でカレントディレクトリをサンプルプロジェクトディレクトリに位置させて実行する。

Special-Student-7
もしも、LibreOfficeまたはApache OpenOfficeインスタンスがリモートホストにいるのであれば、ホスト名は'localhost'から変えればよいだけです、もしも、ファイアウォールが通信をブロックしないのであればですが、勿論。

そして、以下のように複数のJSONファイルを指定することができます。

@cmd ソースコード
gradle i_executePythonExecutableFileTask -Pi_mainModuleName="theBiasPlanet.filesConverter.programs.FilesConverterConsoleProgram" -Pi_commandLineArguments="\"socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext\" \"data/FileConversionOrders.json\" \"data/FileConversionOrders2.json\""


4-3: Understanding the Sample Code サンプルコードを理解する


Special-Student-7
注意として、サンプルコードは私のユーティリティプロジェクト('coreUtilitiesToBeDisclosed'および'unoUtilitiesToBeDisclosed')を使っており、それらは、直接にはファイルコンバージョンの作業に関係していないコードを含んでいます。それらユーティリティプロジェクトの無関係な部分のみを取り去るのはそれほど容易でないことをご理解ください、コードは関連しあっているので。

ファイルコンバージョンロジックは、'theBiasPlanet.unoUtilities.filesConverting.FilesConverter.FilesConverter'クラスの'convertFile'メソッド(CSVファイルへコンバートするためのものを除き)または'convertSpreadSheetsDocumentFileToCsvFiles'メソッド(CSVファイルへコンバートするためのもの)内にあり、メソッドは、ドキュメントテイラー群とファイル格納プロパティ群を取ります、いくつか他のものに加えて。

新たなドキュメントテイラーをインストールするためには、当該クラスを'theBiasPlanet.unoUtilities.documentsHandling.UnoDocumentTailor.UnoDocumentTailor'の子として、そのクラスのインスタンスを一意な名前で、'theBiasPlanet.filesConverter.programs.FilesConverterConsoleProgram.FilesConverterConsoleProgram'クラスの'l_unoDocumentTailorNameToUnoDocumentTailorMap'変数へ登録しなければなりません。


参考資料


<このシリーズの前の記事 | このシリーズの目次 | このシリーズの次の記事>