001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.hayabusa.report2;
017
018import java.io.File;
019import java.util.Map;
020import java.util.HashMap;
021import java.util.Locale;
022// import java.util.List;                                                                               // 8.0.3.0 (2021/12/17) 【保留】
023
024import org.opengion.fukurou.system.HybsConst;                                   // 8.0.1.0 (2021/10/29)
025// import org.opengion.fukurou.model.ExcelModel;                                // 8.0.3.0 (2021/12/17)【保留】
026import org.opengion.fukurou.util.FileUtil;
027import org.opengion.fukurou.util.StringUtil;
028import org.opengion.hayabusa.common.HybsSystem;
029import org.opengion.hayabusa.common.HybsSystemException;
030import static org.opengion.fukurou.system.HybsConst.CR ;                // 6.1.0.0 (2014/12/26)
031// import static org.opengion.fukurou.system.HybsConst.FS ;             // 8.0.3.0 (2021/12/17)
032
033import com.sun.star.beans.PropertyValue;
034import com.sun.star.frame.XComponentLoader;
035import com.sun.star.frame.XController;
036import com.sun.star.frame.XDispatchHelper;
037import com.sun.star.frame.XDispatchProvider;
038import com.sun.star.frame.XModel;
039import com.sun.star.frame.XStorable;
040import com.sun.star.io.IOException;
041import com.sun.star.lang.EventObject;
042import com.sun.star.lang.IllegalArgumentException;
043import com.sun.star.lang.XComponent;
044import com.sun.star.uno.UnoRuntime;
045import com.sun.star.util.CloseVetoException;
046import com.sun.star.util.XCloseable;
047import com.sun.star.view.PrintJobEvent;
048import com.sun.star.view.PrintableState;
049import com.sun.star.view.XPrintJobBroadcaster;
050import com.sun.star.view.XPrintJobListener;
051import com.sun.star.view.XPrintable;
052
053/**
054 * OpenOfficeを利用して様々な形式のファイルを読み込み、出力・印刷を行うための変換クラスです。
055 *
056 * 変換を行うことのできる入出力のフォーマット以下の通りです。
057 *
058 * [対応フォーマット]
059 *  入力[Calc(ods)   ,Excel(xls)     ] ⇒ 出力[Calc(ods)   ,Excel(xls,xlsx)      ,PDF]
060 *  入力[Writer(odt) ,Word(doc)      ] ⇒ 出力[Writer(odt) ,Word(doc,docx)       ,PDF]
061 *  入力[Impress(odp),PowerPoint(ppt)] ⇒ 出力[Impress(odp),PowerPoint(ppt,pptx) ,PDF]
062 *  入力[ * 上記の全て               ] ⇒ 印刷
063 *
064 * ※ xlsx,docx,pptx は、MS 2007形式の為、LibreOffice のみ利用できます。
065 *
066 * 変換を行うには、以下の2通りの方法があります。
067 * <del>
068 * (1)簡易的な変換メソッドを利用する場合
069 *   #convert(String, String)を利用して、変換を行います。
070 *   この場合、出力形式は、出力ファイルの拡張子に従って自動的に決定されます。
071 *   このため、印刷処理などを行う場合は、(2)の方法で出力して下さい。
072 * </del>
073 * (2)段階的に各メソッドを呼び出して変換する場合
074 *   オブジェクトを生成した後、{@link #open()}、#(各種変換メソッド)、{@link #clone()}を
075 *   順番に呼び出して変換を行います。
076 *   この場合、出力形式は、それに対応するメソッドを呼び出すことで決定されます。
077 *
078 *   また、変換を行うための、各種メソッドは、例外としてThrowableを投げるように定義されています。
079 *   このクラスを利用する場合は、このThrowableをcatchし、catch句で、必ず{@link #close( boolean )}に、
080 *   "true"(エラー発生時のクローズ処理)を指定して、終了処理を行って下さい。
081 *   (これを行わない場合、OpenOfficeの不要なプロセスが残ってしまう可能性があります)
082 *
083 * また、出力ファイルが既に存在する場合、出力ファイルは一旦削除された後、処理されます。
084 * なお、入力ファイルと出力ファイルが同じ場合、何も処理されません。(例外も発行されません)
085 *
086 * 入力ファイルを、CSV形式で複数指定した場合、複数の入力ファイルをマージして出力します。
087 * ※1 現状は、ファイルのマージは、入力ファイルがExcelまたはCalcの場合のみ対応しています。
088 *
089 * ※ DocConverter は、クラウド対応されていません。変換時はローカルファイル間で行ってください。
090 *
091 * @og.group 帳票システム
092 *
093 * @version  4.0
094 * @author   Hiroki.Nakamura
095 * @since    JDK1.6
096 */
097public class DocConverter_OOO {
098        private static final String OFFICE_VER = HybsConst.getenv( "OPENOFFICE_VER","NULL" );   // 8.0.1.0 (2021/10/29) 環境変数から取得
099
100        private final boolean                   isOnline;                       // オンライン処理かどうか(オンライン処理の場合、プロセスはファクトリクラス経由で生成されます)
101        private final String[]                  mergeFile;
102
103        private String                                  inputName;
104        private String                                  origName;
105
106        private XComponent                              xComp;                          // 5.1.8.0 (2010/07/01) メソッドと重なる変数名の変更
107        private SOfficeProcess                  soffice;
108
109        // 入力、出力の拡張子とこれに対応するフィルター名
110        // \share\config\registry\instance\org\openoffice\office\TypeDetection.xml
111        // share¥registry¥calc.xcd 形式のファイル <node oor:name="****" 部分?
112
113        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
114        private static final Map<String,String> FILTER_MAP      = new HashMap<>();              // 6.4.1.1 (2016/01/16) filterMap → FILTER_MAP refactoring
115        static {
116                FILTER_MAP.put( "ods_ods", "calc8" );
117                FILTER_MAP.put( "xls_ods", "calc8" );
118                FILTER_MAP.put( "ods_xls", "MS Excel 97" );
119                FILTER_MAP.put( "xls_xls", "MS Excel 97" );
120                FILTER_MAP.put( "ods_pdf", "calc_pdf_Export" );
121                FILTER_MAP.put( "xls_pdf", "calc_pdf_Export" );
122                FILTER_MAP.put( "odt_odt", "writer8" );
123                FILTER_MAP.put( "doc_odt", "writer8" );
124                FILTER_MAP.put( "odt_doc", "MS Word 97" );
125                FILTER_MAP.put( "doc_doc", "MS Word 97" );
126                FILTER_MAP.put( "odt_pdf", "writer_pdf_Export" );
127                FILTER_MAP.put( "doc_pdf", "writer_pdf_Export" );
128                FILTER_MAP.put( "odp_odp", "impress8" );
129                FILTER_MAP.put( "ppt_odp", "impress8" );
130                FILTER_MAP.put( "odp_ppt", "MS PowerPoint 97" );
131                FILTER_MAP.put( "ppt_ppt", "MS PowerPoint 97" );
132                FILTER_MAP.put( "odp_pdf", "impress_pdf_Export" );
133                FILTER_MAP.put( "ppt_pdf", "impress_pdf_Export" );
134
135                if( OFFICE_VER.contains( "LibreOffice" ) ) {                                            // 8.0.1.0 (2021/10/29) LibreOffice の時のみ使用可能
136        //              FILTER_MAP.put( "ods_xlsx", "Calc MS Excel 2007 XML" );                 // 8.0.1.0 (2021/10/29)
137        //              FILTER_MAP.put( "xls_xlsx", "Calc MS Excel 2007 XML" );                 // 8.0.1.0 (2021/10/29)
138
139                        FILTER_MAP.put( "ods_xlsx", "Calc Office Open XML" );                   // 8.0.1.0 (2021/10/29)
140                        FILTER_MAP.put( "xls_xlsx", "Calc Office Open XML" );                   // 8.0.1.0 (2021/10/29)
141
142                        // share¥registry¥writer.xcd
143                        FILTER_MAP.put( "odt_docx", "MS Word 2007 XML" );                               // 8.0.1.0 (2021/10/29)
144                        FILTER_MAP.put( "doc_docx", "MS Word 2007 XML" );                               // 8.0.1.0 (2021/10/29)
145                        // ※ writer_MS_Word_2007 は、failed: 0x81a(Error Area:Io Class:Parameter Code:26)
146
147                        // share¥registry¥impress.xcd
148                        FILTER_MAP.put( "odp_pptx", "Impress MS PowerPoint 2007 XML" ); // 8.0.1.0 (2021/10/29)
149                        FILTER_MAP.put( "ppt_pptx", "Impress MS PowerPoint 2007 XML" ); // 8.0.1.0 (2021/10/29)
150                        // ※ MS PowerPoint 2007 XML は、failed: 0x81a(Error Area:Io Class:Parameter Code:26)
151                        // ※ Office Open XML Presentation は、failed: 0x81a(Error Area:Io Class:Parameter Code:26)
152                }
153        };
154
155        /**
156         * コンストラクタです。
157         *
158         * #DocConverter(input, true)と同じです。
159         *
160         * @param input ファイル一覧(CSV形式)
161         * @see #DocConverter_OOO(String[])
162         */
163        public DocConverter_OOO( final String input ) {
164                this( StringUtil.csv2Array( input ), true );
165        }
166
167        /**
168         * コンストラクタです。
169         *
170         * #DocConverter(input, true)と同じです。
171         *
172         * @param input ファイル一覧(配列)
173         * @see #DocConverter_OOO(String[], boolean)
174         */
175        public DocConverter_OOO( final String input[] ) {
176                this( input, true );
177        }
178
179        /**
180         * コンストラクタです。
181         *
182         * isOnline(isOl)がtrueに指定された場合、soffice.binのプロセスをファクトリークラス経由で生成し、
183         * キャッシュします。
184         * 但し、システムリソースが読み込まれないような、バッチファイルから起動した場合は、この方法は
185         * 利用できないため、isOnlineをfalseに指定する必要があります。
186         *
187         * @param input ファイル一覧(配列)
188         * @param isOl オンライン(Web環境での使用)かどうか
189         */
190        public DocConverter_OOO( final String input[], final boolean isOl ) {
191                if( input == null || input.length == 0 || input[0].isEmpty() ) {
192                        throw new HybsSystemException( "入力ファイルが指定されていません。" );
193                }
194                origName = input[0];
195                final File inFile = new File( origName );
196                if( !inFile.exists() ) {
197                        throw new HybsSystemException( "入力ファイルが存在しません。[file=" + input[0] + "]" );
198                }
199                isOnline  = isOl;
200                inputName = origName;
201//              origName  = input[0];
202
203                if( input.length == 1 ) {
204                        mergeFile = null;
205                }
206                else {
207                        if( !"xls".equals( getSuffix( origName ) ) && !"ods".equals( getSuffix( origName ) ) ) {
208                                throw new HybsSystemException( "ファイルのマージを行う場合、入力ファイルは、ExcelまたはCacl形式である必要があります。" );
209                        }
210
211                        mergeFile = new String[input.length-1];
212                        for( int i=0; i<mergeFile.length; i++ ) {
213                                final String mrgFile = input[i+1];
214                                if( mrgFile.isEmpty() || !( new File( mrgFile ) ).exists() ) {
215                                        throw new HybsSystemException( "マージファイルが指定されていないか、または存在しません。[file=" + mrgFile + "]" );
216                                }
217                                if( inputName.equals( mrgFile ) ) {
218                                        throw new HybsSystemException( "マージファイルに入力ファイルと同じファイルが指定されてます。[file=" + mrgFile + "]" );
219                                }
220                                if( !"xls".equals( getSuffix( mrgFile ) ) && !"ods".equals( getSuffix( mrgFile ) ) ) {
221                                        throw new HybsSystemException( "ファイルのマージを行う場合、マージファイルは、ExcelまたはCacl形式である必要があります。" );
222                                }
223                                mergeFile[i] = mrgFile;
224                        }
225                }
226        }
227
228        /**
229         * SOficeのコンポーネントを起動します。
230         *
231         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
232         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
233         *
234         * @og.rev 5.1.7.0 (2010/06/01) マージ処理対応
235         *
236         * @throws Throwable 何らかのエラーが発生した場合。
237         * @see #close()
238         * @see #close(boolean)
239         */
240        public void open() throws Throwable {
241                // プロセスの取得
242                if( soffice == null ) {
243                        if( isOnline ) {
244                                soffice = ProcessFactory.newInstance();
245                        }
246                        else {
247                                soffice = new SOfficeProcess( "docconverter.class" );
248                                soffice.bootstrap();
249                        }
250
251                        // マージする場合は、マージ対象のファイルをテンポラリにコピーする(readOnly回避のため)
252                        // テンプレート(無題)として上げると、シートコピー先として特定できなくなるため
253                        if( mergeFile != null ) {
254                                final File origFile = new File( origName );
255                                inputName = soffice.getTempPath() + System.currentTimeMillis() + "_" + origFile.getName();
256                                FileUtil.copy( origFile, new File( inputName ) );
257                        }
258                }
259
260                // 5.1.7.0 (2010/06/01) マージ処理対応
261                xComp = getComponent( inputName, ( mergeFile == null ? true : false ), false );
262
263                if( mergeFile != null ) {
264                        for( int i=0; i<mergeFile.length; i++ ) {
265                                merge( mergeFile[i] );
266                        }
267                }
268        }
269
270        /**
271         * ドキュメントコンポーネントを取得します。
272         *
273         * @param       input                   ファイル名
274         * @param       isHidden                隠し属性[true/false]
275         * @param       isAsTemplate    OpenOffice上のTemplate属性[true/false]
276         *
277         * @return      ドキュメントコンポーネント
278         */
279        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
280        private XComponent getComponent( final String input, final boolean isHidden, final boolean isAsTemplate ) {
281                final PropertyValue[] calcProps = new PropertyValue[2];
282                calcProps[0] = new PropertyValue();
283                calcProps[0].Name = "Hidden";
284                calcProps[0].Value = isHidden;
285                calcProps[1] = new PropertyValue();
286                calcProps[1].Name = "AsTemplate";
287                calcProps[1].Value = isAsTemplate;
288
289                final String url = "file:///" + input.replace( '\\', '/' );
290
291                XComponent rtnDoc;
292                final XComponentLoader cloader = (XComponentLoader) UnoRuntime.queryInterface( XComponentLoader.class, soffice.getDesktop() );
293                try {
294                        rtnDoc = cloader.loadComponentFromURL( url, "_blank", 0, calcProps );
295                }
296                catch( final IOException ex ) {
297                        throw new HybsSystemException( "OpenOfficeの立ち上げ時にエラーが発生しました(入出力エラー)。", ex );
298                }
299                catch( final IllegalArgumentException ex ) {
300                        throw new HybsSystemException( "OpenOfficeの立ち上げ時にエラーが発生しました(パラメーター不正)。", ex );
301                }
302                return rtnDoc;
303        }
304
305        /**
306         * ドキュメント(xls,ods)のマージを行います。
307         *
308         * @param mergeInputName マージ対象のファイル名
309         */
310        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
311        private void merge( final String mergeInputName ) {
312                // マージする副ファイルは、テンプレート(無題)として上げる(readOnly回避のため)
313                final XComponent subDoc = getComponent( mergeInputName, false, true );
314
315                final XDispatchProvider dispatchProvider
316                        = (XDispatchProvider)UnoRuntime.queryInterface( XDispatchProvider.class
317                                ,((XController)UnoRuntime.queryInterface( XController.class
318                                        ,((XModel)UnoRuntime.queryInterface( XModel.class
319                                                ,subDoc
320                                        )).getCurrentController()
321                                )).getFrame()
322                        );
323
324                final XDispatchHelper xDispatchHelper = soffice.getDispatcher();
325                xDispatchHelper.executeDispatch(dispatchProvider, ".uno:TableSelectAll", "", 0, new PropertyValue[0]);
326
327                String title = new File( inputName ).getName() ;
328                title = title.substring( 0, title.indexOf( '.' ) );
329
330                final PropertyValue[] moveProps = new PropertyValue[3];
331                moveProps[0] = new PropertyValue();
332                moveProps[0].Name = "DocName";
333                moveProps[0].Value = title;
334                moveProps[1] = new PropertyValue();
335                moveProps[1].Name = "Index";
336                moveProps[1].Value = 32767;
337                moveProps[2] = new PropertyValue();
338                moveProps[2].Name = "Copy";
339                moveProps[2].Value = true;
340                xDispatchHelper.executeDispatch(dispatchProvider, ".uno:Move", "", 0, moveProps);
341
342                closeComponent( subDoc );
343        }
344
345        /**
346         * Calcコンポーネントをクローズします。
347         *
348         * このクローズ処理は、処理が正常終了した場合に呼び出しする必要があります。
349         * 例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
350         *
351         * このメソッドは#close(false)と同じです。
352         *
353         * @throws Throwable 何らかのエラーが発生した場合。
354         * @see #close(boolean)
355         */
356        public void close() throws Throwable {
357                close( false );
358        }
359
360        /**
361         * Calcコンポーネントをクローズします。
362         *
363         * 引数のisErrがtrueの場合、この変換オブジェクトで生成されたプロセスは強制的に破棄されます。
364         * falseの場合は、プロセスは、ファクトリクラスを経由して、キャッシュに戻されます。
365         * (バッチ処理の場合は、いずれの場合も、プロセスは強制的に破棄されます)
366         *
367         * 起動から変換、クローズまでの書く処理で例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
368         *
369         * #close(false)は#close()と同じであるため、通常利用することはありません。
370         *
371         * @og.rev 4.2.4.1 (2008/07/07 ) 終了処理を60回で終わるように修正
372         * @og.rev 4.3.0.0 (2008/07/15 ) ↑は6秒しか待っていなかったので、60秒待つように修正
373         *
374         * @param       isErr   trueの場合、この変換オブジェクトで生成されたプロセスは強制的に破棄されます。
375         */
376        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
377        public void close( final boolean isErr ) {
378                if( xComp != null ) {
379                        closeComponent( xComp );
380                }
381
382                if( soffice != null ) {
383                        if( isOnline ) {
384                                if( isErr ) {
385                                        ProcessFactory.remove( soffice );
386                                }
387                                else {
388                                        ProcessFactory.release( soffice );
389                                }
390                        }
391                        else {
392                                soffice.close();
393                        }
394                }
395
396                // マージした場合は、テンポラリにコピーしたファイルを削除
397                // 6.0.0.1 (2014/04/25) These nested if statements could be combined
398                if( mergeFile != null && ! ( new File( inputName ) ).delete() ) {
399                        System.err.println( "テンポラリにコピーしたファイルを削除できませんでした。[" + inputName + "]" );
400                }
401        }
402
403        /**
404         * ドキュメントコンポーネントをクローズします。
405         *
406         * @param comp ドキュメントコンポーネント
407         */
408        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
409        private void closeComponent( final XComponent comp ) {
410                XCloseable closeable = null;
411                for( int i=0;; ++i ) {
412                        try {
413                                closeable = (XCloseable) UnoRuntime.queryInterface( XCloseable.class, comp );
414                                closeable.close( true );
415                                break;
416                        }
417                        catch( final CloseVetoException ex ) {
418                                // 4.2.4.1 (2008/07/07 )
419                                // 4.3.4.4 (2009/01/01)
420                                if( i == 600 ) { throw new HybsSystemException( "sofficeプロセスに接続できません。", ex ); }
421                                try {
422                                        Thread.sleep( 100 );
423                                }
424                                catch( final InterruptedException ex2 ) {
425                        //              throw new HybsSystemException( ex2 );
426                                }
427                        }
428                }
429        }
430
431        /**
432         * 印刷を行います。
433         *
434         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
435         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
436         *
437         * @og.rev 4.3.0.0 (2008/07/16) スプールが終わるまでwaitし、さらにプリンタ発行の状況を監視し、正常終了かどうかを判断
438         * @og.rev 4.3.7.3 (2009/06/22) 存在しないプリンターを指定した場合のエラーハンドリングを追加
439         * @og.rev 5.1.2.0 (2010/01/01) CentOS等は、OS_INFOがLinux UNKNOWNとなるため、判定条件を変更
440         *
441         * @param       printer プリンター名
442         * @throws Throwable 何らかのエラーが発生した場合。
443         */
444        public void print( final String printer ) throws Throwable {
445                if( printer == null || printer.isEmpty() ) {
446                        throw new HybsSystemException( "プリンターが指定されていません。" );
447                }
448
449        //      if( xComp == null ) { throw new HybsSystemException( "初めに、#open()を実行して下さい(1)" ); }
450                @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
451                final XPrintable xprintable = (XPrintable) UnoRuntime.queryInterface( XPrintable.class, xComp );
452                @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
453                final XPrintJobBroadcaster selection = (XPrintJobBroadcaster) UnoRuntime.queryInterface(XPrintJobBroadcaster.class, xprintable);
454                final MyPrintJobListener listener = new MyPrintJobListener();
455                selection.addPrintJobListener( listener );
456
457                final PropertyValue[] tmpProps = new PropertyValue[1];
458                tmpProps[0] = new PropertyValue();
459                tmpProps[0].Name = "Name";
460                // 5.1.2.0 (2010/01/01) CentOS等は、OS_INFOがLinux UNKNOWNとなるため、判定条件を変更
461                // OSがLinuxの場合は、プリンタ名称の前後に"<",">"を付加
462                tmpProps[0].Value = "LINUX".indexOf( HybsSystem.sys( "OS_INFO" ).toUpperCase( Locale.JAPAN ) ) >= 0 ? "<" + printer + ">" : printer;
463
464                // 4.3.4.4 (2009/01/01)
465                try {
466                        xprintable.setPrinter( tmpProps );
467                }
468                catch( final IllegalArgumentException ex ) {
469                        throw new HybsSystemException( "印刷時にエラーが発生しました。", ex );
470                }
471
472                // 4.3.7.3 (2009/06/22) 存在しないプリンタを指定した場合は、PropertyValueに
473                // デフォルトプリンターが入るため、引数の値と合致しているかで正しく設定されたかを確認
474                String curPrinter = null;
475                for( final PropertyValue chkProp : xprintable.getPrinter() ) {
476                        if( "Name".equals( chkProp.Name) ) {
477                                curPrinter = (String)chkProp.Value;
478                                break;
479                        }
480                }
481
482//              final PropertyValue[] chkProps = xprintable.getPrinter();
483//              for( int i=0; i<chkProps.length; i++ ) {
484//                      if( "Name".equals( chkProps[i].Name) ) {
485//                              curPrinter = (String)chkProps[i].Value;
486//                              break;
487//                      }
488//              }
489                if( !(printer.equalsIgnoreCase( curPrinter ) ) ) {
490                        final String errMsg = "プリンター[" + printer + "]を発行先に指定できませんでした。" + CR
491                                                        + "存在しないプリンタ名が指定されている可能性があります。";
492                        throw new HybsSystemException( errMsg );
493                }
494
495                // 4.3.0.0 (2008/07/16)
496                final PropertyValue[] printProps = new PropertyValue[1];
497                printProps[0] = new PropertyValue();
498                printProps[0].Name = "Wait";
499                printProps[0].Value = true;
500
501                // 4.3.4.4 (2009/01/01)
502                try {
503                        xprintable.print( printProps );
504                }
505                catch( final IllegalArgumentException ex ) {
506                        throw new HybsSystemException( "印刷時にエラーが発生しました。", ex );
507                }
508
509                // 4.3.0.0 (2008/07/16)
510                // 6.9.7.0 (2018/05/14) PMD Useless parentheses.
511//              if( listener.getStatus() == null
512//                              || listener.getStatus() != PrintableState.JOB_COMPLETED && listener.getStatus() != PrintableState.JOB_SPOOLED ) {
513                if( listener.isError() ) {
514                        throw new HybsSystemException ( "Error Occured while spooling print job. Check Spooler-Service!!!");
515                }
516        }
517
518        /**
519         * プリンタジョブの状況を監視するリスナーです。
520         *
521         * @author Hiroki.Nakamura
522         */
523        private static final class MyPrintJobListener implements XPrintJobListener {
524                private PrintableState status   ;
525
526                /**
527                 * PrintJobEventのステータスを内部変数にセットします。
528                 *
529                 * @param       event   PrintJobEventオブジェクト
530                 */
531                @Override
532                public void printJobEvent( final PrintJobEvent event ) {
533                        status = event.State;
534                }
535
536                /**
537                 * EventObjectの処理を実施します。(ここでは何も行いません。)
538                 *
539                 * @param       event   EventObjectオブジェクト
540                 */
541                @Override
542                public void disposing( final EventObject event ) {
543                        // 何もありません。(PMD エラー回避)
544                }
545
546//              /**
547//               * PrintableStateオブジェクトを返します。
548//               *
549//               * @og.rev 8.0.3.0 (2021/12/17) Delete
550//               *
551//               * @return      PrintableStateオブジェクト
552//               */
553//              public PrintableState getStatus() {
554//                      return status;
555//              }
556
557                /**
558                 * PrintableStateオブジェクトの状態を返します。
559                 *
560                 * statusが nullか、COMPLETEDでなく、SPOOLEDでない場合にエラーと判断します。
561                 *
562                 * @return      エラーの場合、true
563                 */
564                public boolean isError() {
565                        return status == null || status != PrintableState.JOB_COMPLETED && status != PrintableState.JOB_SPOOLED ;
566                }
567        }
568
569        /**
570         * Calc(ods)出力を行います。
571         *
572         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
573         *
574         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
575         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
576         *
577         * @param       outputName      出力ファイル名
578         * @throws Throwable 何らかのエラーが発生した場合。
579         */
580//      public void ods( final String outputName ) throws Throwable {
581//      //      saveDoc( outputName, getFilterName( getSuffix( inputName ), "ods" ) );
582//              saveDoc( outputName, getFilterName( inputName, "ods" ) );
583//      }
584
585//      /**
586//       * Excel(xls)出力を行います。
587//       *
588//       * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
589//       *
590//       * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
591//       * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
592//       *
593//       * @param       outputName      出力ファイル名
594//       * @throws Throwable 何らかのエラーが発生した場合。
595//       */
596//      public void xls( final String outputName ) throws Throwable {
597//      //      saveDoc( outputName, getFilterName( getSuffix( inputName ), "xls" ) );
598//              saveDoc( outputName, getFilterName( inputName, "xls" ) );
599//      }
600
601//      /**
602//       * Writer(ods)出力を行います。
603//       *
604//       * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
605//       *
606//       * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
607//       * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
608//       *
609//       * @param       outputName      出力ファイル名
610//       * @throws Throwable 何らかのエラーが発生した場合。
611//       */
612//      public void odt( final String outputName ) throws Throwable {
613//      //      saveDoc( outputName, getFilterName( getSuffix( inputName ), "odt" ) );
614//              saveDoc( outputName, getFilterName( inputName, "odt" ) );
615//      }
616
617//      /**
618//       * Word(doc)出力を行います。
619//       *
620//       * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
621//       *
622//       * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
623//       * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
624//       *
625//       * @param       outputName      出力ファイル名
626//       * @throws Throwable 何らかのエラーが発生した場合。
627//       */
628//      public void doc( final String outputName ) throws Throwable {
629//      //      saveDoc( outputName, getFilterName( getSuffix( inputName ), "doc" ) );
630//              saveDoc( outputName, getFilterName( inputName, "doc" ) );
631//      }
632
633//      /**
634//       * Impress(odp)出力を行います。
635//       *
636//       * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
637//       *
638//       * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
639//       * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
640//       *
641//       * @param       outputName      出力ファイル名
642//       * @throws Throwable 何らかのエラーが発生した場合。
643//       */
644//      public void odp( final String outputName ) throws Throwable {
645//      //      saveDoc( outputName, getFilterName( getSuffix( inputName ), "odp" ) );
646//              saveDoc( outputName, getFilterName( inputName, "odp" ) );
647//      }
648
649//      /**
650//       * PowerPoint(ppt)出力を行います。
651//       *
652//       * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
653//       *
654//       * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
655//       * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
656//       *
657//       * @param       outputName      出力ファイル名
658//       * @throws Throwable 何らかのエラーが発生した場合。
659//       */
660//      public void ppt( final String outputName ) throws Throwable {
661//      //      saveDoc( outputName, getFilterName( getSuffix( inputName ), "ppt" ) );
662//              saveDoc( outputName, getFilterName( inputName, "ppt" ) );
663//      }
664
665        /**
666         * PDF出力を行います。
667         *
668         * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
669         *
670         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
671         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
672         *
673         * @param       outputName      出力ファイル名
674         * @param       pdfPasswd       PDFパスワード
675         * @throws Throwable 何らかのエラーが発生した場合。
676         */
677        public void pdf( final String outputName, final String pdfPasswd ) throws Throwable {
678        //      savePdf( outputName, getFilterName( getSuffix( inputName ), "pdf" ), pdfPasswd );
679                savePdf( outputName, getFilterName( inputName, "pdf" ), pdfPasswd );
680        }
681
682        //      /**
683        //       * 【保留】Excel2007(xlsx)出力を行います。
684        //       *
685        //       * 入力形式で未対応の場合(形式は入力ファイルの拡張子で判別)、例外が発行されます。
686        //       *
687        //       * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
688        //       * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
689        //       *
690        //       * @og.rev 8.0.3.0 (2021/12/17) ods→xlsx変換時のシート毎の行数
691        //       *
692        //       * @param       outputName      出力ファイル名
693        //       * @param       sheetRows       シート毎の行数List
694        //       * @throws Throwable 何らかのエラーが発生した場合。
695        //       */
696        //      public void xlsx( final String outputName,final List<Integer> sheetRows ) throws Throwable {
697        //              saveDoc( outputName, getFilterName( inputName, "xlsx" ) );
698        //
699        //              // 余分な行やカラムを除去(ただし、ものすごく遅い)
700        //              final File outputFile = new File( outputName );
701        //              final ExcelModel excel = new ExcelModel( outputFile , true );
702        //              excel.activeWorkbook( sheetRows );
703        //              excel.saveFile( outputFile );
704        //      }
705
706        /**
707         * 出力ファイルから出力形式を自動判別し、変換を行います。
708         *
709         * 入出力形式で未対応の場合(形式は入出力ファイルの拡張子で判別)、例外が発行されます。
710         *
711         * 正常に処理が終了した場合は、#close()を発行し、終了処理を行って下さい。
712         * また、例外が発生した場合は、#close(true)を発行し、終了処理を行って下さい。
713         *
714         * @param       outputName      出力ファイル名
715         * @throws Throwable 何らかのエラーが発生した場合。
716         */
717        public void auto( final String outputName ) throws Throwable {
718                final String outSuffix = getSuffix( outputName );
719                if( "pdf".equalsIgnoreCase( outSuffix ) ) {
720//                      savePdf( outputName, getFilterName( getSuffix( inputName ), outSuffix ), null );
721                        savePdf( outputName, getFilterName( inputName, outSuffix ), null );
722                }
723                else {
724//                      saveDoc( outputName, getFilterName( getSuffix( inputName ), outSuffix ) );
725                        saveDoc( outputName, getFilterName( inputName, outSuffix ) );
726                }
727        }
728
729        /**
730         * フィルター名を指定して、各種ファイル形式に出力を行います。
731         *
732         * @param       outputName      出力ファイル名
733         * @param       filter          フィルター名
734         */
735        private void saveDoc( final String outputName, final String filter ) {
736                if( !checkOutput( outputName ) ){ return; }
737
738                final PropertyValue[] storeProps = new PropertyValue[1];
739                storeProps[0] = new PropertyValue();
740                storeProps[0].Name = "FilterName";
741                storeProps[0].Value = filter;
742
743                final String url = "file:///" + outputName.replace( '\\', '/' );
744        //      if( xComp == null ) { throw new HybsSystemException( "初めに、#open()を実行して下さい(2)" ); }
745                @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
746                final XStorable xstorable = (XStorable) UnoRuntime.queryInterface( XStorable.class, xComp );
747                try {
748                        xstorable.storeAsURL( url, storeProps );
749                }
750                catch( final Throwable th ) {
751                        throw new HybsSystemException( "ファイルへの変換時にエラーが発生しました。[filter=" + filter + "]", th );
752                }
753        }
754
755        /**
756         * フィルターを指定してPDF出力を行います。
757         *
758         * @param       outputName      出力ファイル名
759         * @param       filter          フィルター名
760         * @param       pdfPasswd       PDFパスワード
761         */
762        private void savePdf( final String outputName, final String filter, final String pdfPasswd ) {
763                if( !checkOutput( outputName ) ){ return; }
764
765                final PropertyValue[] storeProps;
766                if( pdfPasswd == null || pdfPasswd.isEmpty() ) {
767                        storeProps = new PropertyValue[1];
768                        storeProps[0] = new PropertyValue();
769                        storeProps[0].Name = "FilterName";
770                        storeProps[0].Value = filter;
771                }
772                // 帳票要求テーブルでPDFパスワードが設定されている場合
773                else {
774                        final PropertyValue[] filterProps = new PropertyValue[2];
775                        filterProps[0] = new PropertyValue();
776                        filterProps[0].Name = "EncryptFile";
777                        filterProps[0].Value = true;
778                        filterProps[1] = new PropertyValue();
779                        filterProps[1].Name = "DocumentOpenPassword";
780                        filterProps[1].Value = pdfPasswd;
781
782                        storeProps = new PropertyValue[2];
783                        storeProps[0] = new PropertyValue();
784                        storeProps[0].Name = "FilterName";
785                        storeProps[0].Value = "calc_pdf_Export";
786                        storeProps[1] = new PropertyValue();
787                        storeProps[1].Name = "FilterData";
788                        storeProps[1].Value = filterProps;
789                }
790
791                final String url = "file:///" + outputName.replace( '\\', '/' );
792        //      if( xComp == null ) { throw new HybsSystemException( "初めに、#open()を実行して下さい(3)" ); }
793                @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
794                final XStorable xstorable = (XStorable) UnoRuntime.queryInterface( XStorable.class, xComp );
795                try {
796                        xstorable.storeToURL( url, storeProps );
797                }
798                catch( final Throwable th ) {
799                        final String err = "PDFファイルへの変換時にエラーが発生しました。[filter=" + filter + "]"
800                                                        + " URL=" + url + " , storeProps=" + storeProps + " , xComp=" + xComp ;
801
802//                      throw new HybsSystemException( "PDFファイルへの変換時にエラーが発生しました。[filter=" + filter + "]", th );
803                        throw new HybsSystemException( err, th );
804                }
805        }
806
807        /**
808         * 出力ファイルのチェックを行います。
809         *
810         * @param       outputName      出力ファイル名
811         *
812         * @return      処理対象かどうか(入力ファイルと出力ファイルが同じ場合は、falseが返ります)
813         */
814        private boolean checkOutput( final String outputName ) {
815                if( outputName == null || outputName.isEmpty() ) {
816                        throw new HybsSystemException( "出力ファイルが指定されていません。" );
817                }
818
819                final File inFile  = new File( inputName );
820                final File outFile = new File( outputName );
821
822                if( outFile.exists() ) {
823                        if( inFile.getAbsoluteFile().equals( outFile.getAbsoluteFile() ) ) {
824                                // 入力と出力が同じファイルの場合な何もしない
825                                return false;
826                        }
827                        else if( !outFile.delete() ) {
828                                throw new HybsSystemException( "出力先の既存ファイルが削除できません。[file=" + outputName + "]" );
829                        }
830                }
831                return true;
832        }
833
834        /**
835         * 入出力の形式(拡張子)からフィルター名を取得します。
836         *
837         * 入力ファイル名からサフィックスを取り出して、FILTER_MAP からフィルター名を取り出します。
838         *
839         * @og.rev 8.0.1.0 (2021/10/29) メッセージ修正
840         *
841//       * @param       inSuffix        入力拡張子
842         * @param       inputName       入力ファイル名
843         * @param       outSuffix       出力拡張子
844         *
845         * @return      フィルター名
846         */
847//      private static String getFilterName( final String inSuffix, final String outSuffix ) {
848        private static String getFilterName( final String inputName, final String outSuffix ) {
849                final String inSuffix = getSuffix( inputName );
850
851                final String filterName = FILTER_MAP.get( inSuffix + "_" + outSuffix );
852                if( filterName == null ) {
853                        final String errMsg = "入力ファイル=[" + inputName + "] , 出力形式=[" + outSuffix + "]" + CR
854                                                        + "入力形式、出力形式は、以下の対応表に基づき、設定して下さい。" + CR
855                                                        + "入力[Calc(ods)   ,Excel(xls)     ] ⇒ 出力[Calc(ods)   ,Excel(xls,xlsx)     ,PDF]" + CR
856                                                        + "入力[Writer(odt) ,Word(doc)      ] ⇒ 出力[Writer(odt) ,Word(doc,docx)      ,PDF]" + CR
857                                                        + "入力[Impress(odp),PowerPoint(ppt)] ⇒ 出力[Impress(odp),PowerPoint(ppt,pptx),PDF]" + CR
858                                                        + "xlsx,docx,pptx は、MS 2007形式の為、LibreOffice のみ利用できます。" + CR ;
859                        throw new HybsSystemException( errMsg );
860                }
861                return filterName;
862        }
863
864        /**
865         * ファイル名から拡張子(小文字)を求めます。
866         *
867         * @param       fileName        ファイル名
868         *
869         * @return      拡張子(小文字)
870         */
871        private static String getSuffix( final String fileName ) {
872                String suffix = null;
873                if( fileName != null ) {
874                        final int sufIdx = fileName.lastIndexOf( '.' );
875                        if( sufIdx >= 0 ) {
876                                suffix = fileName.substring( sufIdx + 1 ).toLowerCase( Locale.JAPAN );
877                        }
878                }
879                return suffix;
880        }
881
882//      /**
883//       * ドキュメントの変換を行うための簡易メソッドです。
884//       *
885//       * 変換方法は、出力ファイルの拡張子により自動的に決定されます。
886//       *
887//       * @og.rev 8.0.3.0 (2021/12/17) ファイル連結の簡易メソッドは、使用しません。
888//       *
889//       * @param       inputFile       入力ファイル名
890//       * @param       outputFile      出力ファイル名
891//       * @see #convert(String[], String, boolean)
892//       */
893//      public static final void convert( final String inputFile, final String outputFile ) {
894////            convert( StringUtil.csv2Array( inputFile ), outputFile );
895//              convert( StringUtil.csv2Array( inputFile ), outputFile, true ); // 8.0.3.0 (2021/12/17)
896//      }
897
898//      /**
899//       * ドキュメントの変換を行うための簡易メソッドです。
900//       *
901//       * 変換方法は、出力ファイルの拡張子により自動的に決定されます。
902//       *
903//       * @og.rev 8.0.3.0 (2021/12/17) ファイル連結の簡易メソッドは、使用しません。
904//       *
905//       * @param       inputFile       入力ファイル名配列
906//       * @param       outputFile      出力ファイル名
907//       * @see #convert(String[], String, boolean)
908//       */
909//      public static final void convert( final String[] inputFile, final String outputFile ) {
910//              convert( inputFile, outputFile, true );
911//      }
912
913        /**
914         * ドキュメントの変換を行うための簡易メソッドです。
915         *
916         * 変換方法は、出力ファイルの拡張子により自動的に決定されます。
917         *
918         * isOnlineがtrueに指定された場合、soffice.binのプロセスをファクトリークラス経由で生成し、
919         * キャッシュします。
920         * 但し、システムリソースが読み込まれないような、バッチファイルから起動した場合は、この方法は
921         * 利用できないため、isOnlineをfalseに指定する必要があります。
922         *
923         * @og.rev 8.0.3.0 (2021/12/17) ファイル連結の簡易メソッドは、使用しません。
924         *
925         * @param       inputFile       入力ファイル名配列
926         * @param       outputFile      出力ファイル名
927         * @param       isOnline        オンライン(Web環境での使用)かどうか
928         */
929        public static final void convert( final String inputFile[], final String outputFile, final boolean isOnline ) {
930                final DocConverter_OOO dc = new DocConverter_OOO( inputFile, isOnline );
931                try {
932                        dc.open();
933                        dc.auto( outputFile );
934                        dc.close();
935                }
936                catch( final Throwable th ) {
937                        dc.close( true );
938                        throw new HybsSystemException( th );
939                }
940        }
941
942        /**
943         * ドキュメントの変換を行います。
944         *
945         * 変換方法は、出力ファイルの拡張子により自動的に決定されます。
946         *
947         * @og.rev 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
948         * @og.rev 6.3.9.1 (2015/11/27) A method/constructor shouldnt explicitly throw java.lang.Exception(PMD)。
949         * @og.rev 8.0.3.0 (2021/12/17) ファイル連結の簡易メソッドは、使用しません。
950         *
951         * @param       args    コマンド引数配列
952         */
953        public static void main( final String[] args ) {
954                if( args.length < 2 ) {
955                        System.out.println( "usage : OdsConverter [inputFile] [outputFile]" );
956                        return;
957                }
958
959                DocConverter_OOO.convert( new String[] {args[0]}, args[1], false );
960
961//              final File input  = new File( args[0] );
962//              final File output = new File( args[1] );
963//
964//              // 4.3.1.1 (2008/08/23) mkdirs の戻り値判定
965//              if( outPath.mkdirs() ) {
966//                      System.err.println( args[1] + " の ディレクトリ作成に失敗しました。" );
967//              }
968//
969//              final String absPath = output.getAbsolutePath() + FS ;          // 8.0.3.0 (2021/12/17)
970//              if( input.isDirectory() ) {
971//                      final File[] inputFiles = input.listFiles();
972//                      // 6.3.9.0 (2015/11/06) null になっている可能性があるメソッドの戻り値を利用している(findbugs)
973//                      if( inputFiles != null ) {
974//                              for( final File file : inputFiles ) {
975//                                      final String inputFile = file.getAbsolutePath();
976////                                    final String outputFile = output.getAbsolutePath() + File.separator + file.getName().replace( ".xls", ".ods" );
977//                                      final String outputFile = absPath + file.getName().replace( ".xls", ".ods" );
978//                                      convert( StringUtil.csv2Array( inputFile ), outputFile, false );
979//                              }
980//                      }
981//              }
982//              else {
983//                      final String inputFile = input.getAbsolutePath();
984////                    final String outputFile = output.getAbsolutePath() + File.separator + input.getName().replace( ".xls", ".ods" );
985//                      final String outputFile = absPath + input.getName().replace( ".xls", ".ods" );
986//                      convert( StringUtil.csv2Array( inputFile ), outputFile, false );
987//              }
988        }
989}