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.File;                                                                    // 6.2.0.0 (2015/02/27)
019import java.io.IOException;
020import java.io.OutputStream;
021import java.io.FileOutputStream;
022import java.io.BufferedOutputStream;
023import java.util.Locale;
024import java.util.Map;                                                                   // 6.0.2.3 (2014/10/10) 画像関連
025import java.util.HashMap;                                                               // 6.0.2.3 (2014/10/10) 画像関連
026
027import org.apache.poi.common.usermodel.HyperlinkType;   // 6.5.0.0 (2016/09/30) poi-3.15
028import org.apache.poi.ss.util.WorkbookUtil;
029import org.apache.poi.ss.usermodel.Workbook;
030import org.apache.poi.ss.usermodel.Sheet;
031import org.apache.poi.ss.usermodel.Row;
032import org.apache.poi.ss.usermodel.Cell;
033import org.apache.poi.ss.usermodel.CellType;                    // 6.5.0.0 (2016/09/30) poi-3.15
034import org.apache.poi.ss.usermodel.CellStyle;
035import org.apache.poi.ss.usermodel.VerticalAlignment;   // 6.5.0.0 (2016/09/30) poi-3.15
036import org.apache.poi.ss.usermodel.BorderStyle;                 // 6.5.0.0 (2016/09/30) poi-3.15
037import org.apache.poi.ss.usermodel.Font;
038import org.apache.poi.ss.usermodel.IndexedColors;
039import org.apache.poi.ss.usermodel.RichTextString;
040import org.apache.poi.ss.usermodel.Hyperlink;
041import org.apache.poi.ss.usermodel.CreationHelper;
042import org.apache.poi.ss.usermodel.Drawing;                             // 6.0.2.3 (2014/10/10) 画像関連
043import org.apache.poi.ss.usermodel.ClientAnchor;                // 6.0.2.3 (2014/10/10) 画像関連
044import org.apache.poi.ss.usermodel.Picture;                             // 6.0.2.3 (2014/10/10) 画像関連
045
046import org.apache.poi.hssf.usermodel.HSSFWorkbook;              // .xls
047
048import org.apache.poi.POIXMLDocumentPart;                               // 6.2.4.2 (2015/05/29) テキスト変換処理
049import org.apache.poi.xssf.usermodel.XSSFDrawing;               // 6.2.4.2 (2015/05/29) テキスト変換処理
050import org.apache.poi.xssf.usermodel.XSSFShape;                 // 6.2.4.2 (2015/05/29) テキスト変換処理
051import org.apache.poi.xssf.usermodel.XSSFSimpleShape;   // 6.2.4.2 (2015/05/29) テキスト変換処理
052import org.apache.poi.xssf.usermodel.XSSFTextParagraph; // 6.2.4.2 (2015/05/29) テキスト変換処理
053import org.apache.poi.xssf.usermodel.XSSFTextRun;               // 6.2.4.2 (2015/05/29) テキスト変換処理
054import org.apache.poi.xssf.streaming.SXSSFWorkbook;             // .xlsx 6.3.7.0 (2015/09/04) 制限あり 高速、低メモリ消費
055
056import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29)
057import org.opengion.fukurou.system.Closer;
058import org.opengion.fukurou.util.ImageUtil;                             // 6.0.2.3 (2014/10/10) 画像関連
059
060import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
061import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
062
063/**
064 * POI による、EXCELバイナリファイルに対する、データモデルクラスです。
065 *
066 * 共通的な EXCEL処理 を集約しています。
067 * staticメソッドによる簡易的なアクセスの他に、順次処理も可能なように
068 * 現在アクセス中の、Workbook、Sheet、Row、Cell オブジェクトを内部で管理しています。
069 *
070 * 入力形式は、openXML形式にも対応しています。
071 * ファイルの内容に応じて、.xlsと.xlsxのどちらで読み取るかは、内部的に
072 * 自動判定されます。
073 *
074 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
075 * @og.group その他
076 *
077 * @version  6.0
078 * @author   Kazuhiko Hasegawa
079 * @since    JDK7.0,
080 */
081public class ExcelModel {
082        /** このプログラムのVERSION文字列を設定します。   {@value} */
083        private static final String VERSION = "6.8.2.4 (2017/11/20)" ;
084
085        private static final String DEF_SHEET_NAME = "Sheet" ;
086
087        // 6.0.2.3 (2014/10/10) ImageUtil の Suffix と、Workbook.PICTURE_TYPE_*** の関連付けをしておきます。
088        // Suffix 候補は、[bmp, gif, jpeg, jpg, png, wbmp] だが、対応する PICTURE_TYPE は一致しない。
089        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
090        private static final Map<String,Integer> PICTURE_TYPE ;
091        static {
092                PICTURE_TYPE = new HashMap<>() ;
093                PICTURE_TYPE.put( "png"  , Integer.valueOf( Workbook.PICTURE_TYPE_PNG   ) );
094                PICTURE_TYPE.put( "jpeg" , Integer.valueOf( Workbook.PICTURE_TYPE_JPEG  ) );
095                PICTURE_TYPE.put( "jpg"  , Integer.valueOf( Workbook.PICTURE_TYPE_JPEG  ) );
096        }
097
098        private final String inFilename ;               // エラー発生時のキーとなる、EXCELファイル名
099        private final String sufix              ;               // 6.1.0.0 (2014/12/26) オープンしたファイル形式を記憶(ピリオドを含む)
100
101        private final Workbook  wkbook  ;               // 現在処理中の Workbook
102        private Sheet                   sheet   ;               // 現在処理中の Sheet
103        private Row                             rowObj  ;               // 現在処理中の Row
104
105        private int refSheetIdx = -1;                   // 雛形シートのインデックス
106
107        private final CreationHelper createHelper       ;       // poi.xssf対応
108
109        private CellStyle style                 ;               // 共通のセルスタイル
110        private CellStyle hLinkStyle    ;               // Hyperlink用のセルスタイル(青文字+下線)
111
112        private int maxColCount                 = 5 ;   // 標準セル幅の5倍を最大幅とする。
113        private int dataStartRow                = -1;   // データ行の開始位置。未設定時は、-1
114        private boolean isAutoCellSize  ;               // カラム幅の自動調整を行うかどうか(true:行う/false:行わない)
115
116        private String addTitleSheet    ;               // Sheet一覧を先頭Sheetに作成する場合のSheet名
117
118        private String[] recalcSheetNames       ;       // 6.5.0.0 (2016/09/30) セルの計算式の再計算をさせるシート名の配列。
119
120        /**
121         * EXCELファイルのWookbookのデータ処理モデルを作成します。
122         * 
123         * ここでは、既存のファイルを読み込んで、データ処理モデルを作成しますので、
124         * ファイルがオープンできなければエラーになります。
125         *
126         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
127         * @og.rev 6.2.0.0 (2015/02/27) ファイル引数を、String → File に変更
128         *
129         * @param   file  EXCELファイル
130         * @see         #ExcelModel( File , boolean )
131         */
132        public ExcelModel( final File file ) {
133                this( file,true );
134        }
135
136        /**
137         * EXCELファイルのWookbookのデータ処理モデルを作成します。
138         * 
139         * isOpen条件によって、ファイルオープン(true)か、新規作成(false)が分かれます。
140         * ファイルオープンの場合は、EXCELの読み込み以外に、追記するとか、雛形参照する
141         * 場合にも、使用します。
142         * ファイルオープンの場合は、当然、ファイルがオープンできなければエラーになります。
143         *
144         * isOpen=新規作成(false) の場合は、ファイル名の拡張子で、XSSFWorkbook か HSSFWorkbook を
145         * 判定します。.xlsx の場合⇒XSSFWorkbook オブジェクトを使用します。
146         *
147         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
148         * @og.rev 6.0.2.3 (2014/10/10) POIUtil#createWorkbook( String ) を使用するように変更
149         * @og.rev 6.1.0.0 (2014/12/26) 入力ファイルの拡張子判定の対応
150         * @og.rev 6.2.0.0 (2015/02/27) ファイル引数を、String → File に変更
151         * @og.rev 6.2.2.0 (2015/03/27) マクロ付Excel(.xlsm)対応
152         * @og.rev 6.3.7.0 (2015/09/04),5.9.0.0 (2015/09/04) 標準を、SXSSFWorkbook に切り替えてみる。
153         *
154         * @param   file   EXCELファイル
155         * @param   isOpen true:ファイルオープン/false:新規作成
156         * @see         #ExcelModel( File )
157         */
158        public ExcelModel( final File file , final boolean isOpen ) {
159                inFilename      = file.getName();
160
161                final int idx = inFilename.lastIndexOf( '.' );  // 拡張子の位置
162                if( idx >= 0 ) {
163                        sufix = inFilename.substring( idx ).toLowerCase( Locale.JAPAN );                // ピリオドを含む
164                }
165                else {
166                        final String errMsg = "ファイルの拡張子が見当たりません。(.xls か .xlsx/.xlsm を指定下さい)" + CR
167                                                        + " filename=[" + file + "]"  + CR ;
168                        throw new IllegalArgumentException( errMsg );
169                }
170
171                if( isOpen ) {
172                        wkbook = POIUtil.createWorkbook( file );
173                }
174                else {
175                        // 新規の場合、ファイル名に.xlsxで終了した場合⇒.xlsx形式ファイル作成、その他⇒.xls形式ファイル作成
176                        if( ".xlsx".equals( sufix ) || ".xlsm".equals( sufix ) ) {              // 6.2.2.0 (2015/03/27)
177                                // 6.3.7.0 (2015/09/04),5.9.0.0 (2015/09/04) 標準を、SXSSFWorkbook に切り替えてみる。
178        //                      wkbook = new XSSFWorkbook();
179                                wkbook = new SXSSFWorkbook();   // 機能制限有:シートや行の削除や、AutoCellSize の指定ができないなど。
180                        }
181                        else if( ".xls".equals( sufix ) ) {
182                                wkbook = new HSSFWorkbook();
183                        }
184                        else {
185                                final String errMsg = "ファイルの拡張子が不正です。(.xls か .xlsx/.xlsm のみ可能)" + CR
186                                                                + " filename=[" + file + "]"  + CR ;
187                                throw new IllegalArgumentException( errMsg );
188                        }
189                }
190
191                createHelper = wkbook.getCreationHelper();              // poi.xssf対応
192        }
193
194        /**
195         * 内部 Workbook に、フォント名、フォントサイズを設定します。
196         * fontName(フォント名)は、"MS Pゴシック" など名称になります。
197         * fontPoint は、フォントの大きさを指定します。
198         * 内部的には、setFontHeightInPoints(short)メソッドで設定します。
199         *
200         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
201         *
202         * @param       fontName        フォント名 ("MS Pゴシック" など。nullの場合セットしません)
203         * @param       fontPoint       フォントの大きさ (0やマイナスの場合はセットしません)
204         */
205        public void setFont( final String fontName , final short fontPoint ) {
206        //      System.out.println( "FontName=" + fontName + " , Point=" + fontPoint );
207
208                if( style == null ) { style = wkbook.createCellStyle(); }
209
210                final Font font = wkbook.createFont();
211        //      final Font font = wkbook.getFontAt( style.getFontIndex() );                             // A,B などのヘッダーもフォントが
212                if( fontName != null ) {
213                        font.setFontName( fontName );   // "MS Pゴシック" など
214                }
215                if( fontPoint > 0 ) {
216                        font.setFontHeightInPoints( fontPoint );
217                }
218
219                style.setFont( font );
220        }
221
222        /**
223         * データ設定する セルに、罫線を追加します。
224         *
225         * ここで設定するのは、罫線の種類と、罫線の色ですが、内部的に固定にしています。
226         *   Border=CellStyle.BORDER_THIN
227         *   BorderColor=IndexedColors.BLACK
228         *
229         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
230         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX)
231         */
232        public void setCellStyle() {
233                if( style == null ) { style = wkbook.createCellStyle(); }
234
235        //      style.setBorderBottom(  CellStyle.BORDER_THIN );        // 6.5.0.0 (2016/09/30) poi-3.12
236        //      style.setBorderLeft(    CellStyle.BORDER_THIN );        // 6.5.0.0 (2016/09/30) poi-3.12
237        //      style.setBorderRight(   CellStyle.BORDER_THIN );        // 6.5.0.0 (2016/09/30) poi-3.12
238        //      style.setBorderTop(             CellStyle.BORDER_THIN );        // 6.5.0.0 (2016/09/30) poi-3.12
239
240                style.setBorderBottom(  BorderStyle.THIN );                     // 6.4.6.0 (2016/05/27) poi-3.15
241                style.setBorderLeft(    BorderStyle.THIN );                     // 6.5.0.0 (2016/09/30) poi-3.15
242                style.setBorderRight(   BorderStyle.THIN );                     // 6.5.0.0 (2016/09/30) poi-3.15
243                style.setBorderTop(             BorderStyle.THIN );                     // 6.5.0.0 (2016/09/30) poi-3.15
244
245                style.setBottomBorderColor(     IndexedColors.BLACK.getIndex() );
246                style.setLeftBorderColor(       IndexedColors.BLACK.getIndex() );
247                style.setRightBorderColor(      IndexedColors.BLACK.getIndex() );
248                style.setTopBorderColor(        IndexedColors.BLACK.getIndex() );
249
250        //      style.setVerticalAlignment( CellStyle.VERTICAL_TOP );   // isAutoCellSize=true 文字は上寄せする。        // 6.5.0.0 (2016/09/30) poi-3.12
251                style.setVerticalAlignment( VerticalAlignment.TOP  );   // isAutoCellSize=true 文字は上寄せする。        // 6.5.0.0 (2016/09/30) poi-3.15
252        //      style.setWrapText( true );                                                              // isAutoCellSize=true 折り返して表示する。
253        }
254
255        /**
256         * 全てのSheetに対して、autoSizeColumn設定を行うかどうか指定します(初期値:false)。
257         *
258         * autoSize設定で、カラム幅が大きすぎる場合、現状では、
259         * 初期カラム幅の5倍を限度にしています。
260         *
261         * なお、autoSizeColumn設定は負荷の大きな処理なので、saveFile(String)の
262         * 中で実行されます。(セーブしなければ実行されません。)
263         * よって、指定は、いつ行っても構いません。
264         *
265         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
266         *
267         * @param flag autoSizeColumn設定を行うかどうか [true:自動カラム幅設定を行う/false:行わない]
268         * @see #useAutoCellSize( boolean,int )
269         */
270        public void useAutoCellSize( final boolean flag ) {
271                isAutoCellSize = flag;
272        }
273
274        /**
275         * 全てのSheetに対して、autoSizeColumn設定を行うかどうか指定します(初期値:false)。
276         *
277         * autoSize設定で、カラム幅が大きすぎる場合、現状では、
278         * 初期カラム幅のcount倍を限度に設定します。
279         * ただし、count がマイナスの場合は、無制限になります。
280         *
281         * なお、autoSizeColumn設定は負荷の大きな処理なので、saveFile(String)の
282         * 中で実行されます。(セーブしなければ実行されません。)
283         * よって、指定は、いつ行っても構いません。
284         *
285         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
286         *
287         * @param flag autoSizeColumn設定を行うかどうか [true:自動カラム幅設定を行う/false:行わない]
288         * @param count 最大幅を標準セル幅の何倍にするかを指定。マイナスの場合は、無制限
289         * @see #useAutoCellSize( boolean )
290         */
291        public void useAutoCellSize( final boolean flag, final int count ) {
292                isAutoCellSize = flag;
293                maxColCount    = count ;
294        }
295
296        /**
297         * EXCELで、出力処理の最後にセルの計算式の再計算をさせるシート名の配列を指定します。
298         *
299         * null の場合は、再計算しません。
300         * なお、再計算は、saveFile(String)の中で実行されます。(セーブしなければ実行されません。)
301         *
302         * @og.rev 6.5.0.0 (2016/09/30) セルの計算式の再計算をさせる recalcSheetNames 属性の追加。
303         *
304         * @param  sheets 対象シート名の配列
305         */
306        public void setRecalcSheetName( final String[] sheets ){
307                recalcSheetNames = sheets;
308        }
309
310        /**
311         * データ行の書き込み開始位置の行番号を設定します。
312         *
313         * これは、autoSize設定で、自動調整するカラムを、ヘッダーではなく、
314         * データ部で計算する場合に使用します。
315         *
316         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
317         *
318         * @param st  データ行の開始位置。未設定時は、-1
319         * @see #useAutoCellSize( boolean )
320         */
321        public void setDataStartRow( final int st ) {
322                dataStartRow = st;
323        }
324
325        /**
326         * Sheet一覧を先頭Sheetに作成する場合のSheet名を指定します。
327         *
328         * これは、Workbook に含まれる Sheet 一覧を作成する場合に、利用可能です。
329         *
330         * この処理は、#saveFile( File ) 処理時に、実行されます。
331         *
332         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
333         *
334         * @param shtName  Sheet一覧のSheet名
335         * @see #makeAddTitleSheet()
336         */
337        public void setAddTitleSheet( final String shtName ) {
338                addTitleSheet = shtName;
339        }
340
341        /**
342         * 内部 Workbookの Sheet数を返します。
343         *
344         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
345         *
346         * @return      シート数
347         */
348        public int getNumberOfSheets() {
349                return wkbook.getNumberOfSheets();
350        }
351
352        /**
353         * 内部 Workbookより、雛形Sheetをセットします。
354         * 
355         * これは、雛形シートを使用する場合に、使います。このメソッドが呼ばれると、
356         * 雛形シートを使用すると判定されます。
357         * 雛形シート名が、内部 Workbook に存在しない場合は、エラーになります。
358         * ただし、null をセットした場合は、最初のシートを雛形シートとして使用すると
359         * 判定します。
360         *
361         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
362         *
363         * @param       refSheetName    参照シート名(nullの場合、参照シート使用する場合は、先頭のシート)
364         */
365        public void setRefSheetName( final String refSheetName ) {
366                // 参照シート名の指定がない場合は、最初のシート
367                refSheetIdx = ( refSheetName == null ) ? 0 : wkbook.getSheetIndex( refSheetName );
368
369                if( refSheetIdx < 0 ) {         // 参照シート名が存在しなかった。
370                        final String errMsg = "指定の参照シート名は存在しませんでした。" + CR
371                                                        + " inFilename=[" + inFilename + "] , refSheetName=[" + refSheetName + "]"  + CR ;
372                        throw new IllegalArgumentException( errMsg );
373                }
374        }
375
376        /**
377         * 内部 Workbookより、新しいSheetを作ります。
378         *
379         * 先に雛形シートを指定している場合は、その雛形シートから作成します。
380         * 指定していない場合は、新しいシートを作成します。
381         * 雛形シートを参照する場合は、雛形シートそのものを返します。
382         * また、雛形シートの枚数を超える場合は、前の雛形シートをコピーします。
383         * 雛形シートが存在しない場合は、新しいシートを作成します。
384         * 
385         * シート名は、重複チェックを行い、同じ名前のシートの場合は、(1),(2)が付けられます。
386         * shtName が null の場合は、"Sheet" が割り振られます。
387         *
388         * この処理を行うと、内部の Sheet にも、ここで作成された Sheet が設定されます。
389         *
390         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
391         * @og.rev 6.2.2.3 (2015/04/10) 雛形シートにそのままデータを書き込んでいく。
392         * @og.rev 6.5.0.0 (2016/09/30) 雛形シート名をそのまま使用する場合は、isOverwrite に、true を指定します。
393         *
394         * @param       shtName シート名 (重複する場合は、(2)、(3)のような文字列を追加 、nullの場合は、"Sheet")
395         * @param       isOverwrite     雛形シート名をそのまま使用する場合は、true を指定します。
396         */
397        public void createSheet( final String shtName , final boolean isOverwrite ) {
398                // 参照シートを使う場合(整合性の問題で、両方ともチェックしておきます)
399
400                // 6.2.2.3 (2015/04/10) 雛形シートにそのままデータを書き込んでいく。
401                final int shtNo ;
402                if( refSheetIdx < 0 ) {                                                                 // 雛形シートを使用しない。
403                        sheet = wkbook.createSheet();
404                        shtNo = wkbook.getNumberOfSheets() - 1;
405                }
406                else if( refSheetIdx >= wkbook.getNumberOfSheets() ) {  // シート数が雛形より超えている。
407                        sheet = wkbook.cloneSheet( refSheetIdx-1 );                     // 最後の雛形シートをコピーします。
408                        shtNo = wkbook.getNumberOfSheets() - 1;
409                        refSheetIdx++ ;
410                }
411                else {
412                        sheet = wkbook.getSheetAt( refSheetIdx );                       // 雛形シートをそのまま使用
413                        shtNo = refSheetIdx;
414                        refSheetIdx++ ;
415                }
416
417                // 6.5.0.0 (2016/09/30) 雛形シート名をそのまま使用する場合。
418                if( !isOverwrite ) {
419                        setSheetName( shtNo , shtName );
420                }
421        }
422
423        /**
424         * 内部 Workbook の指定のシート番号の Sheet の名前を設定します。
425         *
426         * 指定のシート名が、既存のシートになければ、そのまま設定します。
427         * すでに、同じ名前のシートが存在する場合は、そのシート名の後に
428         * (1)、(2)、(3)のような文字列を追加します。
429         * shtName が null の場合は、"Sheet" が割り振られます。
430         *
431         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
432         * @og.rev 6.2.5.1 (2015/06/12) シート名重複が自分自身の場合は、(1)等の追加は行わない。
433         *
434         * @param       shtNo           シート番号
435         * @param       shtName シート名 (重複する場合は、(1)、(2)のような文字列を追加 、nullの場合は、"Sheet")
436         */
437        public void setSheetName( final int shtNo, final String shtName ) {
438                String tempName = ( shtName == null ) ? DEF_SHEET_NAME : WorkbookUtil.createSafeSheetName( shtName ) ;
439                int cnt = 1;
440
441                // 6.2.5.1 (2015/06/12) シート名重複が自分自身の場合は、(1)等の追加は行わない。
442                // ※ EXCELのシート名は、大文字、小文字だけでなく、全角半角の区別もしない。
443                final String nowName = wkbook.getSheetName( shtNo );
444                if( tempName != null && !tempName.equals( nowName ) ) {                 // 全く同一の場合は、何もしない。
445                        if( shtNo == wkbook.getSheetIndex( tempName ) ) {                       // シート名判定が、自身の場合
446                                wkbook.setSheetName( shtNo,tempName );
447                        }
448                        else {
449                                while( wkbook.getSheetIndex( tempName ) >= 0 ) {                // シート名が存在している場合
450                                        tempName = WorkbookUtil.createSafeSheetName( shtName + "(" + cnt + ")" );
451                                        if( tempName.length() >= 31 ) {                                         // 重複時の追加文字分を減らす。
452                                                tempName = tempName.substring( 0,26 ) + "(" + cnt + ")" ;       // cnt3桁まで可能
453                                        }
454                                        cnt++;
455                                }
456                                wkbook.setSheetName( shtNo,tempName );
457                        }
458                }
459        }
460
461        /**
462         * 内部 Workbook の 指定のSheet番号のシート名前を返します。
463         *
464         * シートが存在しない場合は、null を返します。
465         *
466         * この処理を行うと、内部の Sheet にも、ここで見つけた Sheet が設定されます。
467         *
468         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
469         *
470         * @param        shtNo          シート番号
471         *
472         * @return      shtName シート名
473         */
474        public String getSheetName( final int shtNo ) {
475                final int shLen = wkbook.getNumberOfSheets();
476
477                String shtName = null;
478                if( shtNo < shLen ) {
479                        sheet = wkbook.getSheetAt( shtNo );             // 現在の sheet に設定する。
480                        shtName = sheet.getSheetName();
481                }
482
483                return shtName ;
484        }
485
486        /**
487         * 内部 Workbook の 指定のSheet名のシート番号を返します。
488         *
489         * シートが存在しない場合は、-1 を返します。
490         * この処理を行うと、内部の Sheet にも、ここで見つけた Sheet が設定されます。
491         * シートが存在しない場合、内部の Sheet オブジェクトも null がセットされますのでご注意ください。
492         *
493         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
494         *
495         * @param        shtName                シート名
496         *
497         * @return      シート番号(名前のシートがなければ、-1)
498         */
499        public int getSheetNo( final String shtName ) {
500                sheet = wkbook.getSheet( shtName );                                     // シート名がマッチしなければ、null
501
502                return wkbook.getSheetIndex( shtName ) ;                        // シート名がマッチしなければ、-1
503        }
504
505        /**
506         * Excelの指定Sheetオブジェクトを削除します。
507         *
508         * 削除するシートは、シート番号でFrom-To形式で指定します。
509         * Fromも Toも、削除するシート番号を含みます。
510         * 例えば、0,3 と指定すると、0,1,2,3 の 4シート分を削除します。
511         *
512         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
513         *
514         * @param        fromNo         削除する開始シート番号(含む)
515         * @param        toNo           削除する終了シート番号(含む)
516         */
517        public void removeSheet( final int fromNo,final int toNo ) {
518                for( int shtNo=toNo; shtNo>=fromNo; shtNo-- ) {                 // 逆順に処理します。
519                        wkbook.removeSheetAt( shtNo );
520                }
521        }
522
523        /**
524         * 内部 Workbookの 現在Sheet の最初の行番号を返します。
525         *
526         * 行は、0 から始まります。
527         * この処理は、内部Sheetが作成されているか、null でない場合のみ実行できます。
528         *
529         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
530         *
531         * @return      最初の行番号
532         */
533        public int getFirstRowNum() {
534                return sheet.getFirstRowNum();
535        }
536
537        /**
538         * 内部 Workbookの 現在Sheet の最後の行番号を返します。
539         *
540         * 最終行は、含みます。よって、行数は、getLastRowNum()+1になります。
541         * この処理は、内部Sheetが作成されているか、null でない場合のみ実行できます。
542         *
543         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
544         *
545         * @return      最後の行番号
546         */
547        public int getLastRowNum() {
548                return sheet.getLastRowNum();
549        }
550
551        /**
552         * Excelの指定行のRowオブジェクトを作成します。
553         *
554         * 指定行の Row オブジェクトが存在しない場合は、新規作成します。
555         * この処理を実行すると、指定行の Rowオブジェクトが内部 Row に設定されます。
556         * この処理は、内部Sheetが作成されているか、null でない場合のみ実行できます。
557         *
558         * この処理を行うと、内部の Rowオブジェクトが設定されます。
559         *
560         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
561         *
562         * @param        rowNo          行の番号
563         */
564        public void createRow( final int rowNo ) {
565                rowObj = sheet.getRow( rowNo );
566                if( rowObj == null ) { rowObj = sheet.createRow( rowNo ); }
567        }
568
569        /**
570         * Excelの指定行以降の余計なRowオブジェクトを削除します。
571         *
572         * 指定行の Row オブジェクトから、getLastRowNum() までの行を、削除します。
573         * この処理は、内部Sheetが作成されているか、null でない場合のみ実行できます。
574         *
575         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
576         *
577         * @param        startRowNum            指定以降の余計な行を削除
578         */
579        public void removeRow( final int startRowNum ) {
580                final int stR = startRowNum;
581                final int edR = sheet.getLastRowNum();
582
583                for( int rowNo=edR; rowNo>=stR && rowNo>=0; rowNo-- ) {                 // 逆順に処理します。
584                        final Row rowObj = sheet.getRow( rowNo );
585                        if( rowObj != null ) { sheet.removeRow( rowObj ); }
586                }
587        }
588
589        /**
590         * Excelの処理中のRowオブジェクトの指定カラム以降の余計なCellオブジェクトを削除します。
591         *
592         * 指定行の Row オブジェクトから、getLastCellNum() までのカラムを、削除します。
593         * この処理は、内部Rowが作成されているか、null でない場合のみ実行できます。
594         *
595         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
596         *
597         * @param        startCellNum           指定以降の余計なカラムを削除
598         */
599        public void removeCell( final int startCellNum ) {
600                final int stC = startCellNum;
601                final int edC = rowObj.getLastCellNum();
602
603                for( int colNo=edC; colNo>=stC; colNo-- ) {                     // 逆順に処理します。
604                        final Cell colObj = rowObj.getCell( colNo );
605                        if( colObj != null ) { rowObj.removeCell( colObj ); }
606                }
607        }
608
609        /**
610         * row にあるセルのオブジェクト値を設定します。
611         *
612         * 行が存在しない場合、行を追加します。
613         * この処理を行うと、内部の Rowオブジェクトがなければ新規作成されます。
614         *
615         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
616         *
617         * @param   vals  新しい配列値。
618         * @param   rowNo   値が変更される行(無視されます)
619         */
620        public void setValues( final String[] vals,final int rowNo ) {
621                if( rowObj == null ) { createRow( rowNo ); }
622
623                if( vals != null ) {
624                        for( int colNo=0; colNo<vals.length; colNo++ ) {
625                                setCellValue( vals[colNo],colNo );
626                        }
627                }
628        }
629
630        /**
631         * row にあるセルのオブジェクト値を設定します。
632         *
633         * 行が存在しない場合、行を追加します。
634         * 引数に、カラムがNUMBER型かどうかを指定することが出来ます。
635         * この処理を行うと、内部の Rowオブジェクトがなければ新規作成されます。
636         *
637         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
638         *
639         * @param   vals  新しい配列値。
640         * @param   rowNo   値が変更される行(無視されます)
641         * @param       isNums  セルが、NUMBER型の場合は、true/それ以外は、false
642         */
643        public void setValues( final String[] vals,final int rowNo,final boolean[] isNums ) {
644                if( rowObj == null ) { createRow( rowNo ); }
645
646                if( vals != null ) {
647                        for( int colNo=0; colNo<vals.length; colNo++ ) {
648                                setCellValue( vals[colNo],colNo,isNums[colNo] );
649                        }
650                }
651        }
652
653        /**
654         * Excelの指定セルにデータを設定します。
655         *
656         * ここで設定する行は、現在の内部 Row です。
657         * Row を切り替えたい場合は、#createRow( int ) を呼び出してください。
658         * このメソッドでは、データを文字列型として設定します。
659         * この処理は、内部Rowが作成されているか、null でない場合のみ実行できます。
660         *
661         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
662         *
663         * @param       dataVal    String文字列
664         * @param       colNo           セルの番号(0,1,2・・・・)
665         * @see         #setCellValue( String,int,boolean )
666         */
667        public void setCellValue( final String dataVal , final int colNo ) {
668                setCellValue( dataVal,colNo,false );
669        }
670
671        /**
672         * Excelの指定セルにデータを設定します。
673         *
674         * ここで設定する行は、現在の内部 Row です。
675         * Row を切り替えたい場合は、#createRow( int ) を呼び出してください。
676         * このメソッドでは、引数のデータ型をNUMBER型の場合は、doubleに変換して、
677         * それ以外は文字列としてとして設定します。
678         * この処理は、内部Rowが作成されているか、null でない場合のみ実行できます。
679         *
680         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
681         *
682         * @param       dataVal         String文字列
683         * @param       colNo           セルの番号(0,1,2・・・・)
684         * @param       isNumber        セルが、NUMBER型の場合は、true/それ以外は、false
685         * @see         #createRow( int )
686         * @see         #setCellValue( String,int )
687         */
688        public void setCellValue( final String dataVal , final int colNo , final boolean isNumber ) {
689                Cell colObj = rowObj.getCell( colNo );
690                if( colObj == null ) { colObj = rowObj.createCell( colNo ); }
691
692                if( style != null ) { colObj.setCellStyle(style); }
693
694                // CELL_TYPE_NUMERIC 以外は、String扱いします。
695                if( isNumber ) {
696                        final Double dbl = parseDouble( dataVal );
697                        if( dbl != null ) {
698                                colObj.setCellValue( dbl.doubleValue() );
699                                return ;                // Double 変換できた場合は、即抜けます。
700                        }
701                }
702
703                final RichTextString richText = createHelper.createRichTextString( dataVal );
704                colObj.setCellValue( richText );
705        }
706
707        /**
708         * Excelの指定セルにHyperlinkを設定します。
709         *
710         * ここで設定する行は、現在の内部 Row です。
711         * Row を切り替えたい場合は、#createRow( int ) を呼び出してください。
712         * このメソッドで設定するHyperlinkは、Sheetに対する LINK_DOCUMENT です。
713         * 先に、セルに対する値をセットしておいてください。
714         * Hyperlinkは、文字に対して、下線 と 青字 のスタイル設定を行います。
715         *
716         * Link文字列(シート名) が、null や ゼロ文字列の場合は、処理を行いません。
717         *
718         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
719         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Hyperlink.LINK_XXXX → HyperlinkType.XXXX)
720         *
721         * @param       linkVal         Link文字列(シート名)
722         * @param       colNo           セルの番号(0,1,2・・・・)
723         * @see         #setCellValue( String,int )
724         */
725        public void setCellLink( final String linkVal , final int colNo ) {
726                if( linkVal == null || linkVal.isEmpty() ) { return; }
727
728                Cell colObj = rowObj.getCell( colNo );
729                if( colObj == null ) { colObj = rowObj.createCell( colNo ); }
730
731                if( hLinkStyle == null ) {
732                        hLinkStyle = wkbook.createCellStyle();
733                        if( style != null ) { hLinkStyle.cloneStyleFrom(style); }
734
735                        final Font font = wkbook.createFont();
736                        font.setColor( IndexedColors.BLUE.getIndex() );         // リンクは青文字
737                        font.setUnderline( Font.U_SINGLE );                                     // 下線付
738
739                        hLinkStyle.setFont( font );
740                }
741                colObj.setCellStyle(hLinkStyle);
742
743        //      final Hyperlink hLink = createHelper.createHyperlink( Hyperlink.LINK_DOCUMENT );                // 6.5.0.0 (2016/09/30) poi-3.12
744                final Hyperlink hLink = createHelper.createHyperlink( HyperlinkType.DOCUMENT );                 // 6.5.0.0 (2016/09/30) poi-3.15
745                hLink.setAddress( "'" + linkVal + "'!A1" );
746                colObj.setHyperlink( hLink );
747        }
748
749        /**
750         * 現在のRow にあるセルの属性値を配列で返します。
751         *
752         * Rowオブジェクトが存在しない場合は、長さ0の配列を返します。
753         * また、Rowオブジェクトの中の セルオブジェクトが存在しない場合は、
754         * null がセットされます。
755         *
756         * この処理は、内部Sheetが作成されているか、null でない場合のみ実行できます。
757         * この処理を実行すると、指定行の Rowオブジェクトが内部 Row に設定されます。
758         *
759         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
760         * @og.rev 6.3.9.0 (2015/11/06) ExcelModel#getValues(int) では、nullは返さない。
761         * @og.rev 6.3.9.1 (2015/11/27) メソッドの出口は、最後の1か所にすべきです(PMD)。
762         *
763         * @param       rowNo           行の番号
764         * @return      指定されたセルの属性値。Rowがnullの場合は、長さ0の配列を返します。
765         * @og.rtnNotNull
766         */
767        public String[] getValues( final int rowNo ) {
768                rowObj = sheet.getRow( rowNo );
769
770                final int len = rowObj == null ? 0 : rowObj.getLastCellNum();           // 含まないので、length と同じ意味になる。
771                final String[] vals = new String[len];                          // 6.3.9.1 (2015/11/27) メソッドの出口
772
773                for( int colNo=0; colNo<len; colNo++ ) {
774                        final Cell colObj = rowObj.getCell( colNo );
775                        vals[colNo] = POIUtil.getValue( colObj );
776                }
777
778                return vals ;
779        }
780
781        /**
782         * 現在のrow にあるセルの属性値を返します。
783         *
784         * セルオブジェクトが存在しない場合は、null を返します。
785         *
786         * この処理は、内部Sheetが作成されているか、null でない場合のみ実行できます。
787         * この処理を実行すると、指定行の Rowオブジェクトが内部 Row に設定されます。
788         *
789         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
790         * @og.rev 6.3.9.1 (2015/11/27) メソッドの出口は、最後の1か所にすべきです(PMD)。
791         *
792         * @param   rowNo     値が参照される行
793         * @param   colNo     値が参照される列
794         *
795         * @return  指定されたセルの値 T
796         */
797        public String getValue( final int rowNo, final int colNo ) {
798                rowObj = sheet.getRow( rowNo );
799
800                return rowObj == null ? null : POIUtil.getValue( rowObj.getCell( colNo ) );
801        }
802
803        /**
804         * 指定のシートの行・列の箇所に、イメージファイルを挿入します。
805         *
806         * ここでは、セル範囲ではなく、指定の行列の箇所に、アンカーを設定して、画像ファイルを
807         * 挿入します。一応、リサイズして、元の大きさ近くに戻しますが、縦横比が変わってしまいます。
808         * 正確に挿入する場合は、セル範囲の指定と、マージンを指定しなければなりませんが、
809         * 微調整が必要です。
810         *
811         * この処理で使用される Sheetオブジェクトは一時的に作成されます。(キャッシュされません)
812         * 一連処理のどのタイミングで実行しても、内部の状態には影響はありません。
813         *
814         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
815         *
816         * @param   imgFile   挿入するイメージファイル名
817         * @param   shtNo     シート番号
818         * @param   rowNo     挿入する行
819         * @param   colNo     挿入する列
820         */
821        public void addImageFile( final String imgFile, final int shtNo, final int rowNo, final int colNo ) {
822                addImageFile( imgFile,shtNo,rowNo,colNo,rowNo,colNo,0,0,0,0 );
823        }
824
825        /**
826         * 指定のシートの行・列の箇所に、イメージファイルを挿入します。
827         *
828         * ここでは、セル範囲ではなく、指定の行列の箇所に、アンカーを設定して、画像ファイルを
829         * 挿入します。一応、リサイズして、元の大きさ近くに戻しますが、縦横比が変わってしまいます。
830         * 正確に挿入する場合は、セル範囲の指定と、マージンを指定しなければなりませんが、
831         * 微調整が必要です。
832         *
833         * この処理で使用される Sheetオブジェクトは一時的に作成されます。(キャッシュされません)
834         * 一連処理のどのタイミングで実行しても、内部の状態には影響はありません。
835         *
836         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
837         * @og.rev 6.4.6.0 (2016/05/27) poi-3.15 準備
838         * @og.rev 6.8.2.4 (2017/11/20) poi-3.17 で、警告: [rawtypes] raw型が見つかりました対応
839         *
840         * @param   imgFile   挿入するイメージファイル名
841         * @param   shtNo     シート番号
842         * @param   row1      挿入する行(開始)
843         * @param   col1      挿入する列(開始)
844         * @param   row2      挿入する行(終了-含まず)
845         * @param   col2      挿入する列(終了-含まず)
846         * @param   dx1       開始セルのX軸座標(マージン)
847         * @param   dy1       開始セルのY軸座標(マージン)
848         * @param   dx2       終了セルのX軸座標(マージン)
849         * @param   dy2       終了セルのY軸座標(マージン)
850         */
851        public void addImageFile( final String imgFile , final int shtNo ,
852                                                                final int row1 , final int col1 , final int row2 , final int col2 ,
853                                                                final int dx1  , final int dy1  , final int dx2  , final int dy2   ) {
854                final String suffix   = ImageUtil.getSuffix( imgFile );
855                final Integer picType = PICTURE_TYPE.get( suffix );
856
857                // 実験した結果、bmp,gif,tif については、PICTURE_TYPE_PNG で、挿入できた。
858                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
859                final int pictureType = picType == null ? Workbook.PICTURE_TYPE_PNG : picType.intValue() ;
860
861                final byte[] imgs = ImageUtil.byteImage( imgFile );
862
863                final int pictureIdx = wkbook.addPicture( imgs, pictureType );
864
865                final Sheet sheet = wkbook.getSheetAt( shtNo );
866                // 6.8.2.4 (2017/11/20) poi-3.17 で、警告: [rawtypes] raw型が見つかりました対応
867                final Drawing<?> patriarch = sheet.createDrawingPatriarch();            // 昔は一度しか実行できなかったようです。
868        //      final Drawing patriarch = sheet.createDrawingPatriarch();                       // 昔は一度しか実行できなかったようです。
869
870                final ClientAnchor anchor = patriarch.createAnchor( dx1,dy1,dx2,dy2,col1,row1,col2,row2 );
871                // ClientAnchor anchor = createHelper.createClientAnchor();     でも作成可能。
872
873                // MOVE_AND_RESIZE, MOVE_DONT_RESIZE, DONT_MOVE_AND_RESIZE から、決め打ち。
874        //      anchor.setAnchorType( ClientAnchor.MOVE_DONT_RESIZE );                                  // 6.4.6.0 (2016/05/27) poi-3.12
875                anchor.setAnchorType( ClientAnchor.AnchorType.MOVE_DONT_RESIZE );               // 6.4.6.0 (2016/05/27) poi-3.15
876
877                final Picture pic = patriarch.createPicture( anchor, pictureIdx );
878                // セルの範囲指定がゼロの場合、画像サイズもゼロになる為、リサイズしておく。
879                if( row1 == row2 || col1 == col2 ) { pic.resize(); }    // resize すると、anchor のマージンが無視されるようです。
880        }
881
882        /**
883         * 内部 Workbook オブジェクトをファイルに書き出します。
884         *
885         * Excelの形式は、ここで指定する出力ファイルの拡張子ではなく、コンストラクタで
886         * 指定したファイルの拡張子で決まります。
887         * 異なる形式の拡張子を持つファイルを指定した場合、強制的に、オープンした 
888         * Workbook の形式の拡張子を追加します。
889         *
890         * 拡張子は、Excel 2007以降の形式(.xlsx)か、Excel 2003以前の形式(.xls) が指定できます。
891         * 拡張子が未設定の場合は、オープンした Workbook の形式に合わせた拡張子を付与します。
892         *
893         * isAutoCellSize=true の場合は、ここで全Sheetに対してCell幅の自動調整が行われます。
894         *
895         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
896         * @og.rev 6.1.0.0 (2014/12/26) 入力ファイルの拡張子判定の対応
897         * @og.rev 6.2.0.0 (2015/02/27) ファイル引数を、String → File に変更
898         * @og.rev 6.5.0.0 (2016/09/30) セルの計算式の再計算をさせる recalcSheetNames 属性の追加。
899         *
900         * @param       file    セーブするファイル
901         */
902        public void saveFile( final File file ) {
903                final File   saveFile ;
904                String fname = file.getName();
905                if( fname.toLowerCase(Locale.JAPAN).endsWith( sufix ) ) {
906                        saveFile = file;
907                }
908                else {
909                        final int idx = fname.lastIndexOf( '.' );
910                        if( idx >= 0 ) { fname = fname.substring( 0,idx ); }
911                        saveFile = new File( file.getParent() , fname + sufix );
912                }
913
914                if( isAutoCellSize ) { POIUtil.autoCellSize( wkbook, maxColCount, dataStartRow ); }
915
916                // 6.5.0.0 (2016/09/30) セルの計算式の再計算をさせる recalcSheetNames 属性の追加。
917                if( recalcSheetNames != null && recalcSheetNames.length > 0 ) {
918                        for( final String shtName : recalcSheetNames ) {
919                                final Sheet sht = wkbook.getSheet( shtName );                   // シート名がマッチしなければ、null
920                                if( sht != null ) { sht.setForceFormulaRecalculation(true); }
921                        }
922                }
923
924                // こちらの都合で、TitleSheet は、autoCellSize ではなく、Sheet#autoSizeColumn(int) を使用して、自動計算させる。
925                if( addTitleSheet != null ) { makeAddTitleSheet(); }
926
927                OutputStream fileOut = null ;
928                try {
929                        fileOut = new BufferedOutputStream( new FileOutputStream( saveFile ) );         // 6.1.0.0 (2014/12/26)
930                        wkbook.write( fileOut );
931                }
932                catch( final IOException ex ) {
933                        final String errMsg = "ファイルへ書込み中にエラーが発生しました。" + CR
934                                                        + "  File=" + saveFile + CR
935                                                        + ex.getMessage() ;
936                        throw new OgRuntimeException( errMsg,ex );
937                }
938                finally {
939                        Closer.ioClose( fileOut );
940                }
941        }
942
943        /**
944         * 内部 Workbook オブジェクトのSheet一覧のSheetを、先頭に追加します。
945         *
946         * これは、Workbook に含まれる Sheet 一覧を作成する場合に、利用可能です。
947         *
948         * この処理は、内部のWorkbook、Sheetオブジェクトに依存して実行されます。
949         * また、単独ではなく、#saveFile( File ) 実行時に、addTitleSheet が
950         * 設定されている場合のみ、実行されます。
951         *
952         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
953         *
954         * @see         #saveFile( File )
955         * @see         #setAddTitleSheet( String )
956         */
957        private void makeAddTitleSheet() {
958                sheet = wkbook.createSheet();
959                final String shtNm = sheet.getSheetName();                              // Sheet名の取得
960                wkbook.setSheetOrder( shtNm,0 );                                        // そのSheetを先頭に移動
961                setSheetName( 0,addTitleSheet );                                        // そのSheet名を変更 → これが、TitleSheet
962
963                int rowNo = 0;
964                createRow( rowNo++ );                                                           // 先頭行(インスタンス共通のRowオブジェクト)作成
965                setCellValue( "No"       , 0 );
966                setCellValue( "Sheet", 1 );
967
968                final int shCnt = wkbook.getNumberOfSheets();
969                for( int shtNo=1; shtNo<shCnt; shtNo++,rowNo++ ) {
970                        final String nm = wkbook.getSheetName( shtNo );
971
972                        createRow( rowNo );                                                                     // 行の追加作成
973                        setCellValue( String.valueOf( rowNo ),0,true );         // 行番号として、数字型で登録
974                        setCellValue( nm , 1 );                                                         // シートの値を書き込む
975                        setCellLink(  nm , 1 );                                                         // シートへのリンクを作成する。
976                }
977
978                sheet.autoSizeColumn( 0 );
979                sheet.autoSizeColumn( 1 );
980        }
981
982        /**
983         * Workbook の全Sheetを対象に、空行を取り除き、全体をシュリンクします。
984         *
985         * この処理は、#saveFile( File ) の直前に行うのがよいでしょう。
986         *
987         * ここでは、Row を逆順にスキャンし、Cellが 存在しない間は、行を削除します。
988         * 途中の空行の削除ではなく、最終行からの連続した空行の削除です。
989         * 
990         * isCellDel=true を指定すると、Cellの末尾削除を行います。
991         * 有効行の最後のCellから空セルを削除していきます。
992         * 表形式などの場合は、Cellのあるなしで、レイアウトが崩れる場合がありますので
993         * 処理が不要な場合は、isCellDel=false を指定してください。
994         *
995         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
996         *
997         * @param       isCellDel       Cellの末尾削除を行うかどうか(true:行う/false:行わない)
998         */
999        public void activeWorkbook( final boolean isCellDel ) {
1000                POIUtil.activeWorkbook( wkbook, isCellDel );
1001        }
1002
1003        /**
1004         * Workbook の全Sheetを対象に、テキスト変換処理を行います(XSLX限定)。
1005         *
1006         * この処理は、#saveFile( File ) の直前に行うのがよいでしょう。
1007         * #activeWorkbook( boolean ) との順番は構いません。
1008         *
1009         * ・シート名の一覧をピックアップします。
1010         * ・セル値を、セル単位にピックアップします。
1011         * ・オブジェクト文字列を、改行単位にピックアップし、結果を合成します。
1012         *
1013         * ここでは、内部的に、TextConverterインターフェースを作成して処理します。
1014         *
1015         * @og.rev 6.2.4.2 (2015/05/29) テキスト変換処理
1016         * @og.rev 6.3.1.0 (2015/06/28) TextConverterに、引数(cmnt)を追加
1017         * @og.rev 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更
1018         *
1019         * @param       convMap 変換対象を管理するMapオブジェクト
1020         * @see         #textConverter( TextConverter )
1021         */
1022        public void textConverter( final Map<String,String> convMap ) {
1023                textConverter(
1024                        ( val,cmnt ) -> convMap.get( val )
1025                );
1026
1027        //      textConverter(
1028        //              new TextConverter<String,String>() {
1029        //                      /** 
1030        //                       * 入力文字列を、変換します。
1031        //                       *
1032        //                       * @param       val  入力文字列
1033        //                       * @param       cmnt コメント
1034        //                       * @return      変換文字列(変換されない場合は、null)
1035        //                       */
1036        //                      @Override
1037        //                      public String change( final String val , final String cmnt ) {
1038        //                              return convMap.get( val );
1039        //                      }
1040        //              }
1041        //      );
1042        }
1043
1044        /**
1045         * Workbook の全Sheetを対象に、テキスト変換処理を行います(XSLX限定)。
1046         *
1047         * この処理は、#saveFile( File ) の直前に行うのがよいでしょう。
1048         * #activeWorkbook( boolean ) との順番は構いません。
1049         *
1050         * ・シート名の一覧をピックアップします。
1051         * ・セル値を、セル単位内の改行単位にピックアップし、結果を合成ます。
1052         * ・オブジェクト文字列を、改行単位にピックアップし、結果を合成します。
1053         *
1054         * ここでは、シート名、セルテキスト、SimpleShapeオブジェクトのテキストを
1055         * input に、TextConverterインターフェース の change メソッドを呼び出します。
1056         * 戻り値が、null でないなら、元のデータと置き換えます。
1057         * 戻り値が、null の場合は、そのまま読み飛ばします。(なにもしません)
1058         * EXCELへの書き戻しが発生しますので、万一、ファイル破損で、開けなくなる場合を
1059         * 想定して、バックアップファイルは、各自で準備してください。
1060         *
1061         * @og.rev 6.2.4.2 (2015/05/29) テキスト変換処理
1062         * @og.rev 6.2.5.0 (2015/06/05) xsl形式のオブジェクト取得…はできなかった。
1063         * @og.rev 6.3.1.0 (2015/06/28) TextConverterに、引数(cmnt)を追加
1064         * @og.rev 6.3.9.0 (2015/11/06) セルに値をセットするときに、セルタイプを考慮する。
1065         *
1066         * @param       conv    TextConverterインターフェース
1067         * @see         #textConverter( Map )
1068         */
1069        @SuppressWarnings(value={"deprecation"})        // poi-3.15
1070        public void textConverter( final TextConverter<String,String> conv ) {
1071        //      if( ".xlsx".equals( sufix ) || ".xlsm".equals( sufix ) ) {
1072                        final int shCnt = wkbook.getNumberOfSheets();
1073                        for( int shtNo=0; shtNo<shCnt; shtNo++ ) {
1074                                final Sheet sht = wkbook.getSheetAt( shtNo );
1075                                // シート名の変換
1076                                final String shtNm = conv.change( sht.getSheetName() , "Sheet" );
1077                                if( shtNm != null ) {
1078                                        setSheetName( shtNo,shtNm );                    // 同一シート対策済みのメソッドを呼び出す。
1079                                }
1080
1081                                // セル値の変換
1082                                final int stR = Math.max( sht.getFirstRowNum(),0 );             // stR が、マイナスのケースがある。
1083                                final int edR = sht.getLastRowNum();
1084
1085                                for( int rowNo=stR; rowNo<=edR; rowNo++ ) {
1086                                        final Row rowObj = sht.getRow( rowNo );
1087                                        if( rowObj != null ) {
1088                                                final int stC = Math.max( rowObj.getFirstCellNum(),0 );         // stC が、マイナスのケースがある。
1089                                                final int edC = rowObj.getLastCellNum();
1090                                                for( int colNo=stC; colNo<=edC; colNo++ ) {
1091                                                        final Cell colObj = rowObj.getCell( colNo );
1092                //                                      if( colObj != null && colObj.getCellType() != Cell.CELL_TYPE_BLANK ) {          // 6.5.0.0 (2016/09/30) poi-3.12
1093                                                        if( colObj != null && colObj.getCellTypeEnum() != CellType.BLANK ) {            // 6.5.0.0 (2016/09/30) poi-3.15
1094                                                                final String cmnt= "Sht" + shtNo + ":" + POIUtil.getCelKigo( rowNo,colNo );
1095                                                                final String val = crConv( conv, POIUtil.getValue( colObj ),cmnt );             // 改行対応
1096                                                                if( val != null ) {
1097                                                                        POIUtil.setValue( colObj,val );         // 6.3.9.0 (2015/11/06)
1098                                                //                      colObj.setCellValue( val );
1099                                                                }
1100                                                        }
1101                                                }
1102                                        }
1103                                }
1104
1105                                // オブジェクト文字列の変換
1106                                if( sht instanceof POIXMLDocumentPart ) {
1107                                        for( final POIXMLDocumentPart pxdp : ((POIXMLDocumentPart)sht).getRelations() ) {
1108                                                if( pxdp instanceof XSSFDrawing ) {
1109                                                        for( final XSSFShape shape : ((XSSFDrawing)pxdp).getShapes() ) {
1110                                                                final org.apache.poi.xssf.usermodel.XSSFAnchor anc = shape.getAnchor();
1111                                                                final String ancSt = "XY(" + anc.getDx1() + "-" + anc.getDy1() + ")" ;
1112                                                                int cnt = 0;
1113                                                                if( shape instanceof XSSFSimpleShape ) {
1114                                                                        for( final XSSFTextParagraph para : ((XSSFSimpleShape)shape).getTextParagraphs() ) {
1115                                                                                for( final XSSFTextRun text : para.getTextRuns() ) {
1116                                                                                        final String cmnt= "Sht" + shtNo + ":" + ancSt + ":Tb(" + cnt++ + ")" ;
1117                                                                                        final String val = crConv( conv,text.getText() , cmnt );
1118                                                                                        if( val != null ) {
1119                                                                                                text.setText( val );
1120                                                                                        }
1121                                                                                }
1122                                                                        }
1123                                                                }
1124                                                        }
1125                                                }
1126                                        }
1127                                }
1128                        // 6.2.5.0 (2015/06/05) xsl形式のオブジェクト取得…はできなかった。
1129                        //      else if( sht instanceof HSSFSheet ) {
1130                        //              HSSFPatriarch patri = ((HSSFSheet)sht).getDrawingPatriarch();
1131                        //              for( final HSSFShape shape : patri.getChildren() ) {
1132                        //                      if( shape instanceof HSSFTextbox ) {
1133                        //                              HSSFRichTextString rts = ((HSSFSimpleShape)shape).getString();
1134                        //                              if( rts != null ) {
1135                        //                                      final String val = crConv( conv,rts.getString() );
1136                        //                                      if( val != null ) {
1137                        //                                              HSSFRichTextString rts2 = new HSSFRichTextString( val );
1138                        //                                              ((HSSFSimpleShape)shape).setString( rts2 );
1139                        //                                      }
1140                        //                              }
1141                        //                      }
1142                        //              }
1143                        //      }
1144                        }
1145        //      }
1146        }
1147
1148        /**
1149         * 現在のシートを選択済み(true)か、非選択済み(false)に設定します。
1150         *
1151         * 通常は、シートは、先頭シート以外は、非選択状態になっています。
1152         * シートを選択済みにすることで、印刷範囲を指定する事ができます。
1153         *
1154         * @og.rev 6.3.9.0 (2015/11/06) 新規追加
1155         *
1156         * @param       isSelect        true:シート選択/false:非選択
1157         */
1158        public void sheetSelected( final boolean isSelect ) {
1159                sheet.setSelected( isSelect );
1160        }
1161
1162        /**
1163         * Workbook の雛形シートのTextConverter した、新しいSheetを作成します。
1164         *
1165         * 正確には、
1166         *   1.雛形シートを、コピーして、新しいSheet(shtName)を、作成します。
1167         *   2.雛形シートが指定されていない場合は、一番最後のシートをコピーします。
1168         *   2.そのシートに対して、TextConverter を行い、文字列変換します。
1169         *
1170         * @og.rev 6.3.9.0 (2015/11/06) 新規追加
1171         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX)
1172         *
1173         * @param       conv    TextConverterインターフェース
1174         * @param       shtName         シート名
1175         * @see         #textConverter( Map )
1176         */
1177        @SuppressWarnings(value={"deprecation"})        // poi-3.15
1178        public void sheetCopy( final TextConverter<String,String> conv , final String shtName ) {
1179                int shtNo = wkbook.getNumberOfSheets() - 1;
1180                if( refSheetIdx >= 0 && refSheetIdx < shtNo ) {         // 雛形シートをコピーする。
1181                        sheet = wkbook.cloneSheet( refSheetIdx );
1182                }
1183                else {
1184                        sheet = wkbook.cloneSheet( shtNo );                             // 最後のシートをコピーします。
1185                }
1186                shtNo++ ;                                                                                       // シート番号を増やしておく。
1187
1188                // シート名の変換
1189                setSheetName( shtNo,shtName );                                          // 同一シート対策済みのメソッドを呼び出す。
1190
1191                // セル値の変換
1192                final int stR = Math.max( sheet.getFirstRowNum(),0 );           // stR が、マイナスのケースがある。
1193                final int edR = sheet.getLastRowNum();
1194
1195                for( int rowNo=stR; rowNo<=edR; rowNo++ ) {
1196                        final Row rowObj = sheet.getRow( rowNo );
1197                        if( rowObj != null ) {
1198                                final int stC = Math.max( rowObj.getFirstCellNum(),0 );         // stC が、マイナスのケースがある。
1199                                final int edC = rowObj.getLastCellNum();
1200                                for( int colNo=stC; colNo<=edC; colNo++ ) {
1201                                        final Cell colObj = rowObj.getCell( colNo );
1202        //                              if( colObj != null && colObj.getCellType() != Cell.CELL_TYPE_BLANK ) {          // 6.5.0.0 (2016/09/30) poi-3.12
1203                                        if( colObj != null && colObj.getCellTypeEnum() != CellType.BLANK ) {            // 6.5.0.0 (2016/09/30) poi-3.15
1204                                                final String val = crConv( conv, POIUtil.getValue( colObj ),null );             // 改行対応
1205                                                if( val != null ) { 
1206                                                        POIUtil.setValue( colObj,val );
1207                                //                      colObj.setCellValue( val );
1208                                                }
1209                                        }
1210                                }
1211                        }
1212                }
1213
1214                // オブジェクト文字列の変換
1215                if( sheet instanceof POIXMLDocumentPart ) {
1216                        for( final POIXMLDocumentPart pxdp : ((POIXMLDocumentPart)sheet).getRelations() ) {
1217                                if( pxdp instanceof XSSFDrawing ) {
1218                                        for( final XSSFShape shape : ((XSSFDrawing)pxdp).getShapes() ) {
1219                                //              final org.apache.poi.xssf.usermodel.XSSFAnchor anc = shape.getAnchor();
1220                                                if( shape instanceof XSSFSimpleShape ) {
1221                                                        for( final XSSFTextParagraph para : ((XSSFSimpleShape)shape).getTextParagraphs() ) {
1222                                                                for( final XSSFTextRun text : para.getTextRuns() ) {
1223                                                                        final String val = crConv( conv,text.getText() , null );
1224                                                                        if( val != null ) {
1225                                                                                text.setText( val );
1226                                                                        }
1227                                                                }
1228                                                        }
1229                                                }
1230                                        }
1231                                }
1232                        }
1233                }
1234        }
1235
1236        /**
1237         * Workbook の全Sheetを対象に、テキスト変換処理を行います(XSLX限定)。
1238         *
1239         * この処理は、#saveFile( File ) の直前に行うのがよいでしょう。
1240         * #activeWorkbook( boolean ) との順番は構いません。
1241         *
1242         * ・シート名の一覧をピックアップします。
1243         * ・セル値を、セル単位内の改行単位にピックアップし、結果を合成ます。
1244         * ・オブジェクト文字列を、改行単位にピックアップし、結果を合成します。
1245         *
1246         * ここでは、シート名、セルテキスト、SimpleShapeオブジェクトのテキストを
1247         * input に、TextConverterインターフェース の change メソッドを呼び出します。
1248         * 戻り値が、null でないなら、元のデータと置き換えます。
1249         * 戻り値が、null の場合は、そのまま読み飛ばします。(なにもしません)
1250         * EXCELへの書き戻しが発生しますので、万一、ファイル破損で、開けなくなる場合を
1251         * 想定して、バックアップファイルは、各自で準備してください。
1252         *
1253         * @og.rev 6.2.4.2 (2015/05/29) テキスト変換処理
1254         * @og.rev 6.3.1.0 (2015/06/28) TextConverterに、引数(cmnt)を追加
1255         *
1256         * @param       conv    TextConverterインターフェース
1257         * @param       val             改行処理を行う元の値
1258         * @param       cmnt    コメント
1259         * @return      改行処理の結果の値(対象が無ければ、null)
1260         * @see         #textConverter( Map )
1261         */
1262        private String crConv( final TextConverter<String,String> conv , final String val , final String cmnt ) {
1263                String rtn = null;
1264                if( val != null ) {
1265                        if( val.contains( "\n" ) ) {                                            // 改行がある場合(EXCEL のセル内改行コードは、LF(0A)=\n のみ。
1266                                final String[] val2 = val.split( "\\n" );               // 改行で分割する。
1267                                boolean flag = false;
1268                                for( int i=0; i<val2.length; i++ ) {
1269                                        final String val3 = conv.change( val2[i],cmnt );        // 6.3.1.0 (2015/06/28)
1270                                        if( val3 != null ) { val2[i] = val3; flag = true; }
1271                                }
1272                                if( flag ) {
1273                                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
1274                                        buf.append( val2[0] );
1275                                        for( int i=1; i<val2.length; i++ ) {
1276                                                buf.append( '\n' ).append( val2[i] );           // LF(\n)で、セパレートしているので、LF のみ追加する。
1277                                        }
1278                                        rtn = buf.toString();
1279                                }
1280                        }
1281                        else {                                                                                          // 改行がない場合
1282                                rtn = conv.change( val,cmnt );                                  // 6.3.1.0 (2015/06/28)
1283                        }
1284                }
1285                return rtn;
1286        }
1287
1288        /**
1289         * シート一覧を、内部の Workbook から取得します。
1290         *
1291         * 取得元が、Workbook なので、xls , xlsx どちらの形式でも取り出せます。
1292         *
1293         * EXCEL上のシート名を、配列で返します。
1294         *
1295         * @og.rev 6.2.6.0 (2015/06/19) 新規作成
1296         *
1297         * @return      シート名の配列
1298         * @see         POIUtil#getSheetNames( Workbook )
1299         */
1300        public String[] getSheetNames() {
1301                return POIUtil.getSheetNames( wkbook );
1302        }
1303
1304        /**
1305         * 名前定義一覧を内部の Workbook から取得します。
1306         *
1307         * EXCEL上に定義された名前を、配列で返します。
1308         * ここでは、名前とFormulaをタブで連結した文字列を配列で返します。
1309         * Name オブジェクトを削除すると、EXCELが開かなくなったりするので、
1310         * 取りあえず一覧を作成して、手動で削除してください。
1311         * なお、名前定義には、非表示というのがありますので、ご注意ください。
1312         *
1313         * @og.rev 6.2.6.0 (2015/06/19) 新規作成
1314         *
1315         * @return      名前定義(名前+TAB+Formula)の配列
1316         * @see         POIUtil#getNames( Workbook )
1317         * @og.rtnNotNull
1318         */
1319        public String[] getNames() {
1320                return POIUtil.getNames( wkbook );
1321        }
1322
1323        /**
1324         * 書式のスタイル一覧を内部の Workbook から取得します。
1325         *
1326         * EXCEL上に定義された書式のスタイルを、配列で返します。
1327         * 書式のスタイルの名称は、CellStyle にメソッドが定義されていません。
1328         * 実クラスである HSSFCellStyle にキャストして使用する
1329         * 必要があります。(XSSFCellStyle にも名称を取得するメソッドがありません。)
1330         *
1331         * ※ EXCEL2010 ホームタブ→セルのスタイル は、一つづつしか削除できません。
1332         *    マクロは、開発タブ→Visual Basic で、挿入→標準モジュール を開き
1333         *    テキストを張り付けてください。
1334         *    実行は、開発タブ→マクロ で、マクロ名を選択して、実行します。
1335         *    最後は、削除してください。
1336         *
1337         * @og.rev 6.2.6.0 (2015/06/19) 新規作成
1338         *
1339         * @return      書式のスタイル一覧
1340         * @see         POIUtil#getStyleNames( Workbook )
1341         * @og.rtnNotNull
1342         */
1343        public String[] getStyleNames() {
1344                return POIUtil.getStyleNames( wkbook );
1345        }
1346
1347        /**
1348         * 文字列を Double オブジェクトに変換します。
1349         *
1350         * これは、引数の カンマ(,) を削除した文字列から、Double オブジェクトを生成します。
1351         * 処理中に、文字列が解析可能な double を含まない場合(NumberFormatException)
1352         * また、引数が、null,ゼロ文字列,'_', エラー の時には、null を返します。
1353         *
1354         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
1355         * @og.rev 6.3.9.0 (2015/11/06) もう少し判りやすくする。(処理速度は落ちてます。)
1356         *
1357         * @param       value   Doubleに変換する元の文字列
1358         *
1359         * @return      変換後のDoubleオブジェクト(エラー発生時や変換不可の場合は、null)
1360         */
1361        private Double parseDouble( final String value ) {
1362                Double rtn = null ;
1363
1364                try {
1365                        if( value == null || value.isEmpty() || value.equals( "_" ) ) {
1366                                rtn = null;
1367                        }
1368                        else if( value.indexOf( ',' ) < 0 ) {
1369                                rtn = Double.valueOf( value );          // 6.0.2.4 (2014/10/17) メソッドが非効率だった。
1370                        }
1371                        else {
1372                                // 6.3.9.0 (2015/11/06) もう少し判りやすくする。(処理速度は落ちてます。)
1373                                rtn = Double.valueOf( value.replaceAll( ",","" ) );
1374
1375                        }
1376                }
1377                catch( final NumberFormatException ex ) {               // 文字列が解析可能な数値を含まない場合
1378                        final String errMsg = "Double変換できませんでした。" + CR
1379                                                                + ex.getMessage() + CR
1380                                                                + "  value=" + value;
1381                        System.err.println( errMsg );
1382                        rtn = null;
1383                }
1384
1385                return rtn ;
1386        }
1387
1388        /**
1389         * アプリケーションのサンプルです。
1390         *
1391         * Usage: java org.opengion.fukurou.model.ExcelModel 入力ファイル名 [出力ファイル名] ・・・
1392         *  通常は標準出力に行単位に、セルをタブ区切り出力します。
1393         *  出力ファイル名 を指定すると、EXCEL ファイルとしてセーブし直します。
1394         *  その場合は、以下のパラメータも使用できます。
1395         *   -CS      CellStyleを 設定します。
1396         *   -AS      useAutoCellSizeを 設定します。
1397         *   -FN=***  FontNameを 設定します。
1398         *   -FP=**   FontPointを 設定します。
1399         *   -IMG     画像ファイルを挿入します。(-IMG 画像ファイル名 シート番号 行 列)をスペース区切りで続けます。
1400         *
1401         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
1402         *
1403         * @param       args    コマンド引数配列
1404         */
1405        public static void main( final String[] args ) {
1406                if( args.length == 0 ) {
1407                        final String usage = "Usage: java org.opengion.fukurou.model.ExcelModel 入力ファイル名 [出力ファイル名] ・・・\n" +
1408                                                "\t-CS      CellStyleを 設定します。        \n" +
1409                                                "\t-TC      TextConverterを実行します。     \n" +
1410                                                "\t-AS      useAutoCellSizeを 設定します。  \n" +
1411                                                "\t-FN=***  FontNameを 設定します。         \n" +
1412                                                "\t-FP=**   FontPointを 設定します。        \n" +
1413                                                "\t-IMG     画像ファイルを挿入します。      \n" +
1414                                                "\t     (-IMG ファイル名 シート番号 行 列)  \n" ;
1415                        System.err.println( usage );
1416                        return ;
1417                }
1418
1419                final ExcelModel excel = new ExcelModel( new File( args[0] ) , true );
1420
1421                excel.activeWorkbook( true );                   // 余計な行を削除します。
1422
1423                if( args.length > 1 ) {
1424                        final File outFile = new File( args[1] );                       // 6.2.0.0 (2015/02/27)
1425                        boolean isCS = false;
1426                        boolean isAS = false;
1427                        boolean isTC = false;                           // 6.2.4.2 (2015/05/29) テキスト変換処理
1428                        String  fn   = null;
1429                        short   fp   = -1;
1430
1431                        for( int i=2; i<args.length; i++ ) {
1432                                final String prm = args[i];
1433
1434                                if( "-CS".equalsIgnoreCase( prm ) ) { isCS = true; }    // 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase.
1435                                if( "-AS".equalsIgnoreCase( prm ) ) { isAS = true; }    // 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase.
1436                                if( "-TC".equalsIgnoreCase( prm ) ) { isTC = true; }    // 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase.
1437                                if( prm.startsWith( "-FN" ) ) { fn   = prm.substring( 3 ); }
1438                                if( prm.startsWith( "-FP" ) ) { fp   = Short.parseShort( prm.substring( 3 ) ); }
1439                                if( "-IMG".equalsIgnoreCase( prm ) ) {                                  // 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase.
1440                                        final String img = args[++i];
1441                                        final int  shtNo = Integer.parseInt( args[++i] );
1442                                        final int  rowNo = Integer.parseInt( args[++i] );
1443                                        final int  colNo = Integer.parseInt( args[++i] );
1444
1445                                        excel.addImageFile( img,shtNo,rowNo,colNo );
1446                                }
1447                        }
1448
1449                        if( isCS ) { excel.setCellStyle(); }
1450                        excel.useAutoCellSize( isAS );
1451                        excel.setFont( fn,fp );
1452
1453                        // 6.2.4.2 (2015/05/29) テキスト変換処理
1454                        if( isTC ) {
1455                                // 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更
1456                                // 処理が複数行に別れるのは判りにくいので良くない。
1457                                excel.textConverter(
1458                                        ( val,cmnt ) -> {
1459                                                System.out.println( val );                      // すべてのテキストを読み取る。
1460                                                return null;                                            // 変換せず。
1461                                        }
1462                                );
1463
1464                        }
1465
1466                        excel.saveFile( outFile );
1467                }
1468                else {
1469                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
1470
1471                        final int shLen = excel.getNumberOfSheets();
1472                        for( int shtNo=0; shtNo<shLen; shtNo++ ) {
1473                                final String shtName = excel.getSheetName( shtNo );
1474
1475                                final int stRow = excel.getFirstRowNum();
1476                                final int edRow = excel.getLastRowNum();
1477                                for( int rowNo=stRow; rowNo<=edRow; rowNo++ ) {
1478                                        buf.setLength(0);               // Clearの事
1479                                        buf.append( shtName ).append( '\t' ).append( rowNo );
1480                                        final String[] vals = excel.getValues( rowNo );
1481                                        if( vals != null ) {
1482                                                for( int colNo=0; colNo<vals.length; colNo++ ) {
1483                                                        final String val = vals[colNo] == null ? "" : vals[colNo];
1484                                                        buf.append( '\t' ).append( val );
1485                                                }
1486                                        }
1487                                        System.out.println( buf );
1488                                }
1489                                System.out.println();
1490                        }
1491                }
1492        }
1493}