2019年9月8日日曜日

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

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

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

話題


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

この記事の目次


開始コンテキスト



ターゲットコンテキスト



  • 読者は、Javaにて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、Douglas(Javaプログラマー)、Renee(Javaプログラマー)がコンピュータの前にいる。


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


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

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


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


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

@Java ソースコード
package theBiasPlanet.unoUtilitiesTests.fileConvertingTest1;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.sun.star.beans.PropertyState;
import com.sun.star.beans.PropertyValue;
import com.sun.star.frame.XDispatchProvider;
import com.sun.star.frame.XStorable2;
import com.sun.star.frame.XSynchronousDispatch;
import com.sun.star.uno.AnyConverter;
import com.sun.star.uno.UnoRuntime;
~
import com.sun.star.uno.XInterface;
import com.sun.star.util.XCloseable;
~

public class Test1Test {
	private static Pattern c_urlRegularExpression;
	
	static {
		 c_urlRegularExpression = Pattern.compile ("(.*:)(.*)");
	}
	
	// the 'com.sun.star.util.URLTransformer' UNO service can be used if you want to, as I do this manually.
	private static com.sun.star.util.URL getUrlInURL (String a_url) {
		com.sun.star.util.URL l_urlInURL = new com.sun.star.util.URL ();
		l_urlInURL.Complete = a_url;
		l_urlInURL.Main = l_urlInURL.Complete;
		Matcher l_regularExpressionMatcher = c_urlRegularExpression.matcher (l_urlInURL.Complete);
		if (l_regularExpressionMatcher != null && l_regularExpressionMatcher.lookingAt ()) {
			l_urlInURL.Protocol = l_regularExpressionMatcher.group (1);
			l_urlInURL.User = "";
			l_urlInURL.Password = "";
			l_urlInURL.Server = "";
			l_urlInURL.Port = 0;
			l_urlInURL.Path = l_regularExpressionMatcher.group (2);
			l_urlInURL.Name = "";
			l_urlInURL.Arguments = "";
			l_urlInURL.Mark = "";
		}
		return l_urlInURL;
	}
	
	public static void main (String [] a_argumentsArray) {
		~
				String l_originalFileUrl = a_argumentsArray [1];
				String l_targetFileUrl = a_argumentsArray [2];
				String l_fileStoringFilterName = a_argumentsArray [3];
				
				XDispatchProvider l_underlyingUnoDesktopInXDispatchProvider = UnoRuntime.queryInterface (XDispatchProvider.class, (XInterface) l_underlyingRemoteUnoObjectsContextInXComponentContext.getValueByName ("/singletons/com.sun.star.frame.theDesktop"));
				XSynchronousDispatch l_underlyingFileOpeningUnoDispatcherInXSynchronousDispatch = UnoRuntime.queryInterface (XSynchronousDispatch.class, l_underlyingUnoDesktopInXDispatchProvider.queryDispatch (getUrlInURL ("file:///"), "_blank", -1));
				
				com.sun.star.util.URL l_originalFileUrlInURL = getUrlInURL (l_originalFileUrl);
				PropertyValue [] l_unoDocumentOpeningPropertiesArray = new PropertyValue [4];
				l_unoDocumentOpeningPropertiesArray [0] = new PropertyValue ("ReadOnly", -1, Boolean.valueOf (true), PropertyState.DIRECT_VALUE);
				l_unoDocumentOpeningPropertiesArray [1] = new PropertyValue ("Hidden", -1, Boolean.valueOf (true), PropertyState.DIRECT_VALUE);
				l_unoDocumentOpeningPropertiesArray [2] = new PropertyValue ("OpenNewView", -1, Boolean.valueOf (true), PropertyState.DIRECT_VALUE);
				l_unoDocumentOpeningPropertiesArray [3] = new PropertyValue ("Silent", -1, Boolean.valueOf (true), PropertyState.DIRECT_VALUE);
				PropertyValue [] l_unoDocumentStoringPropertiesArray = new PropertyValue [2];
				l_unoDocumentStoringPropertiesArray [0] = new PropertyValue ("FilterName", -1, l_fileStoringFilterName, PropertyState.DIRECT_VALUE);
				l_unoDocumentStoringPropertiesArray [1] = new PropertyValue ("Overwrite", -1, Boolean.valueOf (true), PropertyState.DIRECT_VALUE);
				
				com.sun.star.uno.Any l_underlyingOriginalUnoDocumentInAny = (com.sun.star.uno.Any) (l_underlyingFileOpeningUnoDispatcherInXSynchronousDispatch.dispatchWithReturnValue (l_originalFileUrlInURL, l_unoDocumentOpeningPropertiesArray));
				boolean l_hasSucceeded = false;
				if (! (AnyConverter.isVoid (l_underlyingOriginalUnoDocumentInAny))) {
					XStorable2 l_underlyingOriginalUnoDocumentInXStorable2 = UnoRuntime.queryInterface (XStorable2.class, (XInterface) l_underlyingOriginalUnoDocumentInAny.getObject ());
					try {
						l_underlyingOriginalUnoDocumentInXStorable2.storeToURL (l_targetFileUrl, l_unoDocumentStoringPropertiesArray);
						l_hasSucceeded = true;
					}
					catch (Exception l_exception) {
						throw l_exception;
					}
					UnoRuntime.queryInterface (XCloseable.class, l_underlyingOriginalUnoDocumentInXStorable2).close (false); 
				}
				else {
					System.out.println (String.format ("### The original file: '%s' cannot be opened.", l_originalFileUrl));
				}
		~
	}
}


Douglas
. . . えーと、それって、それをただ自分のコードの中にコピーすれば、俺のプログラムはもうファイルをコンバートし始めるってことかな?

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

Douglas
"UNOオブジェクト群コン . . ."?一体、そりゃあ何だい?

Special-Student-7
. . . それをある記事(単にまたはscrupulous(用意周到)なやり方用)にしたがって取得されることができます。

Douglas
"scrupulous(良心的)"って言った?

Renee
Douglasはそのコンセプトを理解すらしてない。

Douglas
可能なフィルター名は何?

Special-Student-7
それらは、本記事のコンセプトパートに挙げられています。

Douglas
それじゃあ、あるフィルター名をただセットすれば、ファイルは対応するフォーマットで格納されるってこと?

Special-Student-7
はい。

コードについて簡潔にコメントさせてください。

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

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

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


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


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

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

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

Renee
それじゃあ、オープニングパスワードでプロテクトされたファイルをサイレントにコンバートできるわけね。

Douglas
でも、俺はパスワードを知らない。ユーザーだけがそれを知ってる。

Special-Student-7
ということは、あなたは、ユーザーがパスワードを入力するインターフェイスを提供する必要があるということになります、すると、あなたのプログラムはそのパスワードをそこにセットする。

Renee
Douglas!あなたは子供なの?そんなの、自分で考えられないといけないでしょう!

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

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

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

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

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

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

Douglas
. . . 俺が理解したのは、それは簡単じゃないということだけだ。

Special-Student-7
私の動かせるサンプルはそのロジックを実装してあるので、もしよろしければ、それをお使いになることができます。

Renee
'FilterData'とか'FilterOptions'とかの"フィルター特有"プロパティには、具体的何をセットできるわけ?  ?

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


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


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

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

Douglas
"調整する"?どうやって?

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

Douglas
"あなたは~できます"と言われても . . .

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: %非表示スプレッドシートもエクスポートされるか否か%}, 
	. . .
	{. . .}
]

Douglas
"テイラー"って一体全体何だ?

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

Douglas
それじゃあ、俺の欲しいやつはどこで見つけられるんだ?

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

Douglas
はあ?俺は作るように想定されているのか?

Renee
ええ、そうよ。

それはとにかく、私はクラス名を指定するってことなの?

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

Douglas
JSONファイルは俺には役に立たないな。俺の命令はWebリクエストとしてなんだ。

Special-Student-7
それはサンプルです、サー。あなたはそれをお好きなように変更できます。例えば、各Webリクエストからメモリ内JSONレコードを形成して、そのJSONレコードを、そうしたJSONレコードを受け取る'executeConversionOrder'メソッドに渡すことができます。

Douglas
おう?それじゃあ、そのメソッドを使えるというわけか、そのままで?

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

Douglas
それじゃあ、俺の当面の仕事は、各WebリクエストからJSONレコードを構成することだな . . .

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_executeJarTask -Pi_mainClassName="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_executeJarTask -Pi_mainClassName="theBiasPlanet.filesConverter.programs.FilesConverterConsoleProgram" -Pi_commandLineArguments="\"socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext\" \"data/FileConversionOrders.json\" \"data/FileConversionOrders2.json\""


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


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

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

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


参考資料


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