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