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.fukurou.model;
017
018import java.io.InputStream;
019import java.io.FileInputStream;
020import java.io.BufferedInputStream;
021import java.io.IOException;
022import java.io.OutputStream;
023import java.io.FileOutputStream;
024import java.io.BufferedOutputStream;
025// import java.text.DecimalFormat;
026// import java.text.NumberFormat;
027import java.util.Locale;
028
029import org.apache.poi.ss.usermodel.WorkbookFactory;
030import org.apache.poi.ss.usermodel.Workbook;
031import org.apache.poi.ss.usermodel.Sheet;
032import org.apache.poi.ss.usermodel.Row;
033import org.apache.poi.ss.usermodel.Cell;
034import org.apache.poi.ss.usermodel.CellStyle;
035import org.apache.poi.ss.usermodel.IndexedColors;
036
037import org.apache.poi.ss.usermodel.CreationHelper;
038import org.apache.poi.ss.usermodel.Font;
039
040import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
041// import org.apache.poi.ss.usermodel.DateUtil;
042import org.apache.poi.ss.usermodel.RichTextString;
043// import org.apache.poi.ss.usermodel.FormulaEvaluator;
044
045import org.opengion.fukurou.util.POIUtil;
046import org.opengion.fukurou.util.Closer;
047// import org.opengion.fukurou.util.HybsDateUtil;
048
049/**
050 * POI による、EXCELバイナリファイルに対する、データモデルクラスです。
051 *
052 * 共通的な EXCEL処理 を集約しています。
053 * staticメソッドによる簡易的なアクセスの他に、順次処理も可能なように
054 * 現在アクセス中の、Workbook、Sheet、Row、Cell オブジェクトを内部で管理しています。
055 *
056 * 入力形式は、openXML形式にも対応しています。
057 * ファイルの内容に応じて、.xlsと.xlsxのどちらで読み取るかは、内部的に
058 * 自動判定されます。
059 *
060 * @og.rev 6.0.2.0 (2014/08/29) 新規作成
061 * @og.group その他
062 *
063 * @version  6.0
064 * @author   Kazuhiko Hasegawa
065 * @since    JDK7.0,
066 */
067public class ExcelModel {
068        //* このプログラムのVERSION文字列を設定します。   {@value} */
069        private static final String VERSION = "6.0.2.0 (2014/08/29)" ;
070
071        private static final String CR = System.getProperty("line.separator");
072
073        private String   inFilename = null;             // エラー発生時のキーとなる、EXCELファイル名
074
075        private Workbook wkbook = null;                 // 現在処理中の Workbook
076        private Sheet    sheet  = null;                 // 現在処理中の Sheet
077        private Row              rowObj = null;                 // 現在処理中の Row
078
079        private CreationHelper createHelper     = null;         // poi.xssf対応
080
081        private CellStyle style = null;                                 // Workbook のセルスタイル
082        private int maxColCount = 5 ;                                   // 標準セル幅の5倍を最大幅とする。
083        private boolean useAutoSizeColumn = false;              // カラム幅の自動調整を行うかどうか(true:行う)
084
085        /**
086         * EXCELファイルのWookbookのデータ処理モデルを作成します。
087         * 
088         * ここでは、既存のファイルを読み込んで、データ処理モデルを作成しますので、
089         * ファイルがオープンできなければエラーになります。
090         *
091         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
092         *
093         * @param   fname  EXCELファイル名
094         * @see         #ExcelModel( String , boolean )
095         */
096        public ExcelModel( final String fname ) {
097                this( fname,true );
098        }
099
100        /**
101         * EXCELファイルのWookbookのデータ処理モデルを作成します。
102         * 
103         * isOpen条件によって、ファイルオープン(true)か、新規作成(false)が分かれます。
104         * ファイルオープンの場合は、EXCELの読み込み以外に、追記するとか、雛形参照する
105         * 場合にも、使用します。
106         * ファイルオープンの場合は、当然、ファイルがオープンできなければエラーになります。
107         *
108         * isOpen=新規作成(false) の場合は、ファイル名の拡張子で、XSSFWorkbook か HSSFWorkbook を
109         * 判定します。.xlsx の場合⇒XSSFWorkbook オブジェクトを使用します。
110         *
111         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
112         *
113         * @param   fname  EXCELファイル名
114         * @param   isOpen true:ファイルオープン/false:新規作成
115         * @see         #ExcelModel( String )
116         */
117        public ExcelModel( final String fname , final boolean isOpen ) {
118                inFilename = fname;
119                if( isOpen ) {
120                        // File オブジェクトでcreate すると、ファイルがオープンされたままになってしまう。
121                        InputStream fileIn = null;
122                        try {
123                                fileIn = new BufferedInputStream( new FileInputStream( fname ) );
124                                wkbook = WorkbookFactory.create( fileIn );
125                        }
126                        catch( IOException ex ) {
127                                String errMsg = "ファイル読込みエラー[" + fname + "]" + CR + ex.getMessage() ;
128                                throw new RuntimeException( errMsg,ex );
129                        }
130                        catch( InvalidFormatException ex ) {
131                                String errMsg = "ファイル形式エラー[" + fname + "]" + CR + ex.getMessage() ;
132                                throw new RuntimeException( errMsg,ex );
133                        }
134                        finally {
135                                Closer.ioClose( fileIn );
136                        }
137                }
138                else {
139                        // 新規の場合、ファイル名に.xlsxで終了した場合⇒.xlsx形式ファイル作成、その他⇒.xls形式ファイル作成
140                        if( fname.toLowerCase(Locale.JAPAN).endsWith( ".xlsx" ) ) {
141                                wkbook = new org.apache.poi.xssf.usermodel.XSSFWorkbook();
142                        }
143                        else {
144                                wkbook = new org.apache.poi.hssf.usermodel.HSSFWorkbook();
145                        }
146                }
147
148                createHelper = wkbook.getCreationHelper();              // poi.xssf対応
149        }
150
151        /**
152         * 内部 Workbook に、フォント名、フォントサイズを設定します。
153         * fontName(フォント名)は、"MS Pゴシック" など名称になります。
154         * fontPoint は、フォントの大きさを指定します。
155         * 内部的には、setFontHeightInPoints(short)メソッドで設定します。
156         *
157         * この処理を行うと、内部の Sheet にも、ここで作成された Sheet が設定されます。
158         *
159         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
160         *
161         * @param       fontName        フォント名("MS Pゴシック" など。nullの場合、セットしません)
162         * @param       fontPoint       フォントの大きさ(0や、マイナスの場合は、セットしません)
163         */
164        public void setFont( final String fontName , final short fontPoint ) {
165                Font font = wkbook.getFontAt((short)0);
166                if( fontName != null ) {
167                        font.setFontName( fontName );   // "MS Pゴシック" など
168                }
169                if( fontPoint > 0 ) {
170                        font.setFontHeightInPoints( fontPoint );
171                }
172
173//              createHelper.applyFont( font );         // Cellの雛形Fontを優先したいので、設定しません。
174        }
175
176        /**
177         * データ設定する セルに、罫線を追加します。
178         *
179         * ここで設定するのは、罫線の種類と、罫線の色ですが、内部的に固定にしています。
180         *   Border=CellStyle.BORDER_THIN
181         *   BorderColor=IndexedColors.BLACK
182         *
183         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
184         *
185         */
186        public void setCellStyle() {
187                style = wkbook.createCellStyle();
188
189                style.setBorderBottom(  CellStyle.BORDER_THIN );
190                style.setBorderLeft(    CellStyle.BORDER_THIN );
191                style.setBorderRight(   CellStyle.BORDER_THIN );
192                style.setBorderTop(             CellStyle.BORDER_THIN );
193
194                style.setBottomBorderColor(     IndexedColors.BLACK.getIndex() );
195                style.setLeftBorderColor(       IndexedColors.BLACK.getIndex() );
196                style.setRightBorderColor(      IndexedColors.BLACK.getIndex() );
197                style.setTopBorderColor(        IndexedColors.BLACK.getIndex() );
198        }
199
200        /**
201         * Sheetに対して、autoSizeColumn設定を行うかどうか指定します。
202         *
203         * autoSize設定で、カラム幅が大きすぎる場合、現状では、
204         * 初期カラム幅の5倍を限度にしています。
205         *
206         * なお、autoSizeColumn設定は負荷の大きな処理なので、saveFile(String)の
207         * 中で実行されます。(セーブしなければ実行されません。)
208         *
209         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
210         *
211         * @param flag  true:自動カラム幅設定を行う / false:行わない。
212         * @see #useAutoSizeColumn( boolean,int )
213         */
214        public void useAutoSizeColumn( final boolean flag ) {
215                this.useAutoSizeColumn( flag , maxColCount );
216        }
217
218        /**
219         * Sheetに対して、autoSizeColumn設定を行うかどうか指定します。
220         *
221         * autoSize設定で、カラム幅が大きすぎる場合、現状では、
222         * 初期カラム幅のcount倍を限度に設定します。
223         * ただし、count がマイナスの場合は、無制限になります。
224         *
225         * なお、autoSizeColumn設定は負荷の大きな処理なので、saveFile(String)の
226         * 中で実行されます。(セーブしなければ実行されません。)
227         *
228         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
229         *
230         * @param flag  true:自動カラム幅設定を行う / false:行わない。
231         * @param count 最大幅を標準セル幅の何倍にするかを指定。マイナスの場合は、無制限
232         * @see #useAutoSizeColumn( boolean )
233         */
234        public void useAutoSizeColumn( final boolean flag, final int count ) {
235                useAutoSizeColumn = flag;
236                maxColCount = count ;
237        }
238
239        /**
240         * 内部 Workbookの Sheet数を返します。
241         *
242         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
243         *
244         * @return      シート数
245         */
246        public int getNumberOfSheets() {
247                return wkbook.getNumberOfSheets();
248        }
249
250        /**
251         * 内部 Workbookの 現在Sheet の最初の行番号を返します。
252         *
253         * 行は、0 から始まります。
254         *
255         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
256         *
257         * @return      最初の行番号
258         */
259        public int getFirstRowNum() {
260                return sheet.getFirstRowNum();
261        }
262
263        /**
264         * 内部 Workbookの 現在Sheet の最後の行番号を返します。
265         *
266         * 最終行は、含みます。よって、行数は、getLastRowNum()+1になります。
267         *
268         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
269         *
270         * @return      最後の行番号
271         */
272        public int getLastRowNum() {
273                return sheet.getLastRowNum();
274        }
275
276        /**
277         * 内部 Workbookより、Sheetを作ります。
278         * Sheetは、useRefName属性に応じて、参照コピーか、新規作成に分かれます。
279         * useRefName=true:参照シート使用 の場合は、refSheetName を cloneして、Sheetを作ります。
280         * refSheetName が null の場合は、第一番目のシートを clone します。
281         * useRefName=false:新規作成 の場合は、新規作成されます。
282         *
283         * この処理を行うと、内部の Sheet にも、ここで作成された Sheet が設定されます。
284         *
285         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
286         *
287         * @param       refSheetName    参照シート名(nullの場合、参照シート使用する場合は、先頭のシートをコピー)
288         * @param       useRefName              true:参照シート使用/false:新規作成
289         */
290        public void createSheet( final String refSheetName , final boolean useRefName ) {
291                // 参照シートを使う場合
292                if( useRefName ) {
293                        // 参照シート名の指定がない場合は、最初のシート
294                        int refSheetIdx = ( refSheetName == null ) ? 0 : wkbook.getSheetIndex( refSheetName );
295
296                        if( refSheetIdx < 0 ) {         // 参照シート名が存在しなかった。
297                                String errMsg = "指定の参照シート名は存在しませんでした。" + CR
298                                                                + " inFilename=[" + inFilename + "] , refSheetName=[" + refSheetName + "]"  + CR ;
299                                throw new IllegalArgumentException( errMsg );
300                        }
301
302                        sheet = wkbook.cloneSheet( refSheetIdx );
303                }
304                else {
305                        sheet = wkbook.createSheet();
306                }
307        }
308
309        /**
310         * 内部 Workbook の指定のシート番号の Sheet の名前を設定します。
311         *
312         * 既存のシート番号に Sheet に対して名前をセットします。
313         * セットしたSheetは、内部の Sheet として設定されます。
314         * シート名の重複を避けるため、すでに、同じ名前のシートが存在する場合は、
315         * そのシート名の後に(2)、(3)のような文字列を追加します。
316         *
317         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
318         *
319         * @param       stNo            シート番号
320         * @param       sheetName       シート名(重複する場合は、(2)、(3)のような文字列を追加)
321         */
322        public void setSheetName( final int stNo , final String sheetName ) {
323                String tempName = sheetName;
324                int cnt = 1;
325
326                while( wkbook.getSheetIndex( tempName ) >= 0 ) {        // シート名が存在している場合
327                        tempName = sheetName + "(" + cnt + ")";
328                        cnt++;
329                }
330                wkbook.setSheetName( stNo, tempName );
331                sheet = wkbook.getSheetAt( stNo );
332        }
333
334        /**
335         * 内部 Workbook のいちばん最後の Sheet の名前を設定します。
336         *
337         * 判りにくいですが、Sheet を新規作成するか、参照コピーして作成した最後の Sheet に対して
338         * 名前をセットします。
339         * シート名の重複を避けるため、すでに、同じ名前のシートが存在する場合は、
340         * そのシート名の後に(2)、(3)のような文字列を追加します。
341         *
342         * この処理を行うと、内部の Sheet にも、ここで作成された Sheet が設定されます。
343         *
344         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
345         *
346         * @param       sheetName       シート名(重複する場合は、(2)、(3)のような文字列を追加)
347         */
348        public void setLastSheetName( final String sheetName ) {
349                int lastNo = wkbook.getNumberOfSheets() -1 ;            // 最後のシート番号は、シート数-1
350                setSheetName( lastNo,sheetName );
351        }
352
353        /**
354         * 内部 Workbook の 指定のSheet番号のシート名前を返します。
355         *
356         * シートが存在しない場合は、null を返します。
357         * この処理で、内部にsheetオブジェクトをキャッシュします。
358         *
359         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
360         *
361         * @param        shNo           シート番号
362         *
363         * @return      sheetName       シート名
364         */
365        public String getSheetName( final int shNo ) {
366                int shLen = wkbook.getNumberOfSheets();
367
368                String shName = null;
369                if( shNo < shLen ) {
370                        sheet = wkbook.getSheetAt( shNo );              // 現在の sheet に設定する。
371                        shName = sheet.getSheetName();
372                }
373
374                return shName ;
375        }
376
377        /**
378         * 内部 Workbook の 指定のSheet名のシート番号を返します。
379         *
380         * シートが存在しない場合は、-1 を返します。
381         * この処理で、内部にsheetオブジェクトをキャッシュします。
382         *
383         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
384         *
385         * @param        shName         シート名
386         *
387         * @return      シート番号(名前のシートがなめれば、-1)
388         */
389        public int getSheetNo( final String shName ) {
390                sheet = wkbook.getSheet( shName );                              // シート名がマッチしなければ、null
391
392                return wkbook.getSheetIndex( shName ) ;                 // シート名がマッチしなければ、-1
393        }
394
395        /**
396         * Excelの指定Sheetオブジェクトを削除します。
397         *
398         * 削除するシートは、シート番号でFrom-To形式で指定します。
399         * Fromも Toも、削除するシート番号を含みます。
400         * 例えば、0,3 と指定すると、0,1,2,3 の 4シート分を削除します。
401         *
402         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
403         *
404         * @param        fromNo         削除する開始シート番号(含む)
405         * @param        toNo           削除する終了シート番号(含む)
406         */
407        public void removeSheet( final int fromNo,final int toNo ) {
408                for( int shtNo=toNo; shtNo>=fromNo; shtNo-- ) {                 // 逆順に処理します。
409                        wkbook.removeSheetAt( shtNo );
410                }
411        }
412
413        /**
414         * Excelの指定行のRowオブジェクトを作成します。
415         *
416         * 指定行の Row オブジェクトが存在しない場合は、新規作成します。
417         * この処理を実行すると、指定行の Rowオブジェクトが内部 Row に設定されます。
418         *
419         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
420         *
421         * @param        rowNo          行の番号
422         */
423        public void createRow( final int rowNo ) {
424                rowObj = sheet.getRow( rowNo );
425                if( rowObj == null ) { rowObj = sheet.createRow( rowNo ); }
426        }
427
428        /**
429         * Excelの指定行以降の余計なRowオブジェクトを削除します。
430         *
431         * 指定行の Row オブジェクトから、getLastRowNum() までの行を、削除します。
432         *
433         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
434         *
435         * @param        startRowNum            指定以降の余計な行を削除
436         */
437        public void removeRow( final int startRowNum ) {
438                int stR = startRowNum;
439                int edR = sheet.getLastRowNum();
440
441                for( int rowNo=edR; rowNo>=stR; rowNo-- ) {                     // 逆順に処理します。
442                        Row rowObj = sheet.getRow( rowNo );
443                        if( rowObj != null ) { sheet.removeRow( rowObj ); }
444                }
445        }
446
447        /**
448         * Excelの処理中のRowオブジェクトの指定カラム以降の余計なCellオブジェクトを削除します。
449         *
450         * 指定行の Row オブジェクトから、getLastCellNum() までのカラムを、削除します。
451         *
452         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
453         *
454         * @param        startCellNum           指定以降の余計なカラムを削除
455         */
456        public void removeCell( final int startCellNum ) {
457                int stC = startCellNum;
458                int edC = rowObj.getLastCellNum();
459
460                for( int colNo=edC; colNo>=stC; colNo-- ) {                     // 逆順に処理します。
461                        Cell colObj = rowObj.getCell( colNo );
462                        if( colObj != null ) { rowObj.removeCell( colObj ); }
463                }
464        }
465
466        /**
467         * row にあるセルのオブジェクト値を設定します。
468         *
469         * 行が存在しない場合、行を追加します。
470         *
471         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
472         *
473         * @param   vals  新しい配列値。
474         * @param   rowNo   値が変更される行(無視されます)
475         */
476        public void setValues( final String[] vals,final int rowNo ) {
477                if( rowObj == null ) { createRow( rowNo ); }
478
479                if( vals != null ) {
480                        for( int colNo=0; colNo<vals.length; colNo++ ) {
481                                setCellValue( vals[colNo],colNo );
482                        }
483                }
484        }
485
486        /**
487         * row にあるセルのオブジェクト値を設定します。
488         *
489         * 行が存在しない場合、行を追加します。
490         * 引数に、カラムがNUMBER型かどうかを指定することが出来ます。
491         *
492         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
493         *
494         * @param   vals  新しい配列値。
495         * @param   rowNo   値が変更される行(無視されます)
496         * @param       isNums  セルが、NUMBER型の場合は、true/それ以外は、false
497         */
498        public void setValues( final String[] vals,final int rowNo,final boolean[] isNums ) {
499                if( rowObj == null ) { createRow( rowNo ); }
500
501                if( vals != null ) {
502                        for( int colNo=0; colNo<vals.length; colNo++ ) {
503                                setCellValue( vals[colNo],colNo,isNums[colNo] );
504                        }
505                }
506        }
507
508        /**
509         * Excelの指定セルにデータを設定します。
510         *
511         * ここで設定する行は、現在の内部 Row です。
512         * Row を切り替えたい場合は、#createRow( int ) を呼び出してください。
513         * このメソッドでは、データを文字列型として設定します。
514         *
515         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
516         *
517         * @param       dataVal    String文字列
518         * @param       colNo           セルの番号(0,1,2・・・・)
519         * @see         #setCellValue( String,int,boolean )
520         */
521        public void setCellValue( final String dataVal , final int colNo ) {
522                this.setCellValue( dataVal,colNo,false );
523        }
524
525        /**
526         * Excelの指定セルにデータを設定します。
527         *
528         * ここで設定する行は、現在の内部 Row です。
529         * Row を切り替えたい場合は、#createRow( int ) を呼び出してください。
530         * このメソッドでは、引数のデータ型をNUMBER型の場合は、doubleに変換して、
531         * それ以外は文字列としてとして設定します。
532         *
533         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
534         *
535         * @param       dataVal         String文字列
536         * @param       colNo           セルの番号(0,1,2・・・・)
537         * @param       isNumber        セルが、NUMBER型の場合は、true/それ以外は、false
538         * @see         #createRow( int )
539         * @see         #setCellValue( String,int )
540         */
541        public void setCellValue( final String dataVal , final int colNo , final boolean isNumber ) {
542                Cell colObj = rowObj.getCell( colNo );
543                if( colObj == null ) { colObj = rowObj.createCell( colNo ); }
544
545                // CELL_TYPE_NUMERIC 以外は、String扱いします。
546//              if( colObj.getCellType() == Cell.CELL_TYPE_NUMERIC ) {
547                if( isNumber ) {
548                        Double dbl = parseDouble( dataVal );
549                        if( dbl != null ) {
550                                colObj.setCellValue( dbl.doubleValue() );
551                                return ;                // Double 変換できた場合は、即抜けます。
552                        }
553                }
554
555                RichTextString richText = createHelper.createRichTextString( dataVal );
556                colObj.setCellValue( richText );
557
558                if( style != null ) { colObj.setCellStyle(style); }
559        }
560
561        /**
562         * 現在のRow にあるセルの属性値を配列で返します。
563         *
564         * Rowオブジェクトが存在しない場合は、null を返します。
565         * また、Rowオブジェクトの中の セルオブジェクトが存在しない場合は、
566         * null がセットされます。
567         *
568         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
569         *
570         * @param       rowNo           行の番号
571         * @return      指定されたセルの属性値。Rowがnullの場合は、nullを返します。
572         */
573        public String[] getValues( final int rowNo ) {
574                rowObj = sheet.getRow( rowNo );
575                if( rowObj == null ) { return null; }
576
577                int len = rowObj.getLastCellNum() + 1;
578                String[] vals = new String[len];
579
580                for( int colNo=0; colNo<len; colNo++ ) {
581                        Cell colObj = rowObj.getCell( colNo );
582                        vals[colNo] = POIUtil.getValue( colObj );
583                }
584
585                return vals ;
586        }
587
588        /**
589         * 現在のrow にあるセルの属性値を返します。
590         *
591         * セルオブジェクトが存在しない場合は、null を返します。
592         *
593         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
594         *
595         * @param   rowNo     値が参照される行
596         * @param   colNo     値が参照される列
597         *
598         * @return  指定されたセルの値 T
599         */
600        public String getValue( final int rowNo, final int colNo ) {
601                rowObj = sheet.getRow( rowNo );
602                if( rowObj == null ) { return null; }
603
604                Cell colObj = rowObj.getCell( colNo );
605                return POIUtil.getValue( colObj );
606        }
607
608        /**
609         * 内部 Workbook オブジェクトをファイルに書き出します。
610         *
611         * 書き出すファイルの拡張子に応じて、自動的に、Excel 2007以降の形式(.xlsx)か、
612         * Excel 2003以前の形式(.xls) で出力されます。
613         *
614         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
615         *
616         * @param       filename        セーブするファイル名
617         */
618        public void saveFile( final String filename ) {
619                if( useAutoSizeColumn ) {
620                        int shCnt = wkbook.getNumberOfSheets();
621                        for( int shNo=0; shNo<shCnt; shNo++ ) {
622                                Sheet sht = wkbook.getSheetAt( shNo );
623                                int defW = sht.getDefaultColumnWidth();         // 標準カラムの文字数
624                                int maxWidth = defW*256*maxColCount ;           // Widthは、文字数(文字幅)*256*最大セル数
625
626                                int stR = sht.getFirstRowNum();
627                                int edR = sht.getLastRowNum();
628
629                                Row rowObj = sht.getRow( stR );
630                                int stC = rowObj.getFirstCellNum();
631                                int edC = rowObj.getLastCellNum();
632                                for( int colNo=stC; colNo<=edC; colNo++ ) {
633                                        sht.autoSizeColumn( colNo );
634                                        if( maxWidth >= 0 ) {                                   // マイナスや0の場合は、幅に制限をかけない。
635                                                int wd = sht.getColumnWidth( colNo );
636                                                if( wd > maxWidth ) { sht.setColumnWidth( colNo,maxWidth ); }
637                                        }
638                                }
639                        }
640                }
641
642                OutputStream fileOut = null ;
643                try {
644                        fileOut = new BufferedOutputStream( new FileOutputStream( filename ) );
645                        wkbook.write( fileOut );
646                }
647                catch( IOException ex ) {
648                        String errMsg = "ファイルへ書込み中にエラーが発生しました。" + CR
649                                                + "  File=" + filename + CR
650                                                + ex.getMessage() ;
651                        throw new RuntimeException( errMsg,ex );
652                }
653                finally {
654                        Closer.ioClose( fileOut );
655                }
656        }
657
658        /**
659         * Workbook の全Sheetを対象に、空行を取り除き、全体をシュリンクします。
660         *
661         * ここでは、Row を逆順にスキャンし、Cellが 存在しない間は、行を削除します。
662         * 途中の行を削除しても、データとしては残っており、LastRowNum との矛盾の為、
663         * 最終行が表示されなくなってしまいます。
664         * よって、途中の削除は行いません。(方法が見つかれば別)
665         *
666         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
667         */
668        public void activeWorkbook() {
669                int shCnt = wkbook.getNumberOfSheets();
670                for( int shNo=0; shNo<shCnt; shNo++ ) {
671                        Sheet sheet = wkbook.getSheetAt( shNo );
672
673                        int stR = sheet.getFirstRowNum();
674                        int edR = sheet.getLastRowNum();
675
676                        boolean isRowDel = true;                                                        // 行の削除は、Cellが見つかるまで。
677                        for( int rowNo=edR; rowNo>=stR; rowNo-- ) {                     // 逆順に処理します。
678                                Row rowObj = sheet.getRow( rowNo );
679                                if( rowObj != null ) {
680                                        int stC = rowObj.getFirstCellNum();
681                                        int edC = rowObj.getLastCellNum();
682                                        for( int colNo=edC; colNo>=stC; colNo-- ) {
683                                                Cell colObj = rowObj.getCell( colNo );
684                                                if( colObj != null ) {
685                                                        String val = POIUtil.getValue( colObj );
686                                                        if( colObj.getCellType() != Cell.CELL_TYPE_BLANK && val != null && val.length() > 0 ) { 
687                                                                isRowDel = false;                                               // 一つでも現れれば、行の削除は中止
688                                                                break;
689                                                        }
690                                                        else {
691                                                                rowObj.removeCell( colObj );                    // CELL_TYPE_BLANK の場合は、削除
692                                                        }
693                                                }
694                                        }
695                                        if( isRowDel ) { sheet.removeRow( rowObj );     }
696        //                              else               { break; }                                                   // Cell の処理を継続しない場合は、break すればよい。
697                                }
698                        }
699                }
700        }
701
702        /**
703         * 文字列を Double オブジェクトに変換します。
704         *
705         * これは、引数の カンマ(,) を削除した文字列から、Double オブジェクトを生成します。
706         * 処理中に、文字列が解析可能な double を含まない場合(NumberFormatException)
707         * また、引数が、null,ゼロ文字列,'_', エラー の時には、null を返します。
708         *
709         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
710         *
711         * @param       value   Doubleに変換する元の文字列
712         *
713         * @return      変換後のDoubleオブジェクト(エラー発生時や変換不可の場合は、null)
714         */
715        private Double parseDouble( final String value ) {
716                Double rtn = null ;
717
718                try {
719                        if( value == null || value.length() == 0 || value.equals( "_" ) ) {
720                                rtn = null;
721                        }
722                        else if( value.indexOf( ',' ) < 0 ) {
723                                rtn = new Double( value );
724                        }
725                        else {
726                                char[] chs = value.toCharArray() ;
727                                int j=0;
728                                for( int i=0;i<chs.length; i++ ) {
729                                        if( chs[i] == ',' ) { continue; }
730                                        chs[j] = chs[i];
731                                        j++;
732                                }
733                                rtn = new Double( String.valueOf( chs,0,j ) );
734                        }
735                }
736                catch( NumberFormatException ex ) {             // 文字列が解析可能な数値を含まない場合
737                        String errMsg = "Double変換できませんでした。" + CR
738                                                + ex.getMessage() + CR
739                                                + "  value=" + value;
740                        System.err.println( errMsg );
741                        rtn = null;
742                }
743
744                return rtn ;
745        }
746
747        /**
748         * アプリケーションのサンプルです。
749         *
750         * Usage: java org.opengion.fukurou.model.ExcelModel 入力ファイル名
751         *
752         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
753         *
754         * @param       args    コマンド引数配列
755         */
756        public static void main( final String[] args ) {
757                if( args.length == 0 ) {
758                        System.err.println( "Usage: java org.opengion.fukurou.model.ExcelModel 入力ファイル名" );
759                        return ;
760                }
761
762                ExcelModel model = new ExcelModel( args[0] , true );
763
764                model.activeWorkbook();                 // 余計な行を削除します。
765
766                StringBuilder buf = new StringBuilder( 200 );
767
768                int shLen = model.getNumberOfSheets();
769                for( int shNo=0; shNo<shLen; shNo++ ) {
770                        String sheetName = model.getSheetName( shNo );
771
772                        int stRow = model.getFirstRowNum();
773                        int edRow = model.getLastRowNum();
774                        for( int rowNo=stRow; rowNo<=edRow; rowNo++ ) {
775                                buf.setLength(0);               // Clearの事
776                                buf.append( sheetName ).append( '\t' ).append( rowNo );
777                                String[] vals = model.getValues( rowNo );
778                                if( vals != null ) {
779                                        for( int colNo=0; colNo<vals.length; colNo++ ) {
780                                                String val = vals[colNo] == null ? "" : vals[colNo];
781                                                buf.append( '\t' ).append( val );
782                                        }
783                                }
784                                System.out.println( buf );
785                        }
786                        System.out.println();
787                }
788        }
789}