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