OSDN Git Service

Ver8.5.2.0
[opengion/opengionV8.git] / uap / webapps / gf / src / org / opengion / fukurou / model / ExcelModel.java
1 /*
2  * Copyright (c) 2009 The openGion Project.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific language
14  * governing permissions and limitations under the License.
15  */
16 package org.opengion.fukurou.model;
17
18 import java.io.File;                                                                    // 6.2.0.0 (2015/02/27)
19 import java.io.IOException;
20 import java.io.OutputStream;
21 import java.io.FileOutputStream;
22 import java.io.BufferedOutputStream;
23 import java.util.Locale;
24 import java.util.Map;                                                                   // 6.0.2.3 (2014/10/10) 画像関連
25 import java.util.HashMap;                                                               // 6.0.2.3 (2014/10/10) 画像関連
26 import java.util.List;                                                                  // 8.0.1.0 (2021/10/29)
27 import java.util.function.BiConsumer;                                   // 8.1.0.1 (2022/01/07)
28
29 import org.apache.poi.util.Units;                                               // 7.2.9.0 (2020/10/12)
30
31 import org.apache.poi.common.usermodel.HyperlinkType;   // 6.5.0.0 (2016/09/30) poi-3.15
32 import org.apache.poi.ss.util.WorkbookUtil;
33 import org.apache.poi.ss.usermodel.Workbook;
34 import org.apache.poi.ss.usermodel.Sheet;
35 import org.apache.poi.ss.usermodel.Row;
36 import org.apache.poi.ss.usermodel.Cell;
37 import org.apache.poi.ss.usermodel.CellType;                    // 6.5.0.0 (2016/09/30) poi-3.15
38 import org.apache.poi.ss.usermodel.CellStyle;
39 import org.apache.poi.ss.usermodel.VerticalAlignment;   // 6.5.0.0 (2016/09/30) poi-3.15
40 import org.apache.poi.ss.usermodel.BorderStyle;                 // 6.5.0.0 (2016/09/30) poi-3.15
41 import org.apache.poi.ss.usermodel.Font;
42 import org.apache.poi.ss.usermodel.IndexedColors;
43 import org.apache.poi.ss.usermodel.RichTextString;
44 import org.apache.poi.ss.usermodel.Hyperlink;
45 import org.apache.poi.ss.usermodel.CreationHelper;
46 import org.apache.poi.ss.usermodel.Drawing;                             // 6.0.2.3 (2014/10/10) 画像関連
47 import org.apache.poi.ss.usermodel.Shape;                               // 8.0.3.1 (2021/12/28) 画像関連
48 import org.apache.poi.ss.usermodel.ClientAnchor;                // 6.0.2.3 (2014/10/10) 画像関連
49 import org.apache.poi.ss.usermodel.Picture;                             // 6.0.2.3 (2014/10/10) 画像関連
50
51 import org.apache.poi.hssf.usermodel.HSSFWorkbook;              // .xls
52
53 // import org.apache.poi.POIXMLDocumentPart;                            // 6.2.4.2 (2015/05/29) テキスト変換処理  8.1.2.3 (2022/05/20) 復活
54 import 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) 復活
55
56 import org.apache.poi.xssf.usermodel.XSSFDrawing;               // 6.2.4.2 (2015/05/29) テキスト変換処理  8.1.2.3 (2022/05/20) 復活
57 import org.apache.poi.xssf.usermodel.XSSFShape;                 // 6.2.4.2 (2015/05/29) テキスト変換処理
58 import org.apache.poi.xssf.usermodel.XSSFSimpleShape;   // 6.2.4.2 (2015/05/29) テキスト変換処理
59 import org.apache.poi.xssf.usermodel.XSSFShapeGroup;    // 8.0.3.1 (2021/12/28)
60 import org.apache.poi.xssf.usermodel.XSSFTextParagraph; // 6.2.4.2 (2015/05/29) テキスト変換処理  8.1.2.3 (2022/05/20) 復活
61 import org.apache.poi.xssf.usermodel.XSSFTextRun;               // 6.2.4.2 (2015/05/29) テキスト変換処理  8.1.2.3 (2022/05/20) 復活
62 import org.apache.poi.xssf.usermodel.XSSFAnchor;                // .xslx 8.1.2.3 (2022/05/20)
63 import org.apache.poi.xssf.usermodel.XSSFClientAnchor;  // 8.5.0.0 (2023/04/21) XSSFAnchor オブジェクトの NullPointerException 対策
64 import org.apache.poi.xssf.streaming.SXSSFWorkbook;             // .xlsx 6.3.7.0 (2015/09/04) 制限あり 高速、低メモリ消費
65
66 import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29)
67 import org.opengion.fukurou.system.Closer;
68 import org.opengion.fukurou.util.ImageUtil;                             // 6.0.2.3 (2014/10/10) 画像関連
69
70 import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
71 import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
72
73 /**
74  * POI による、EXCELバイナリファイルに対する、データモデルクラスです。
75  *
76  * 共通的な EXCEL処理 を集約しています。
77  * staticメソッドによる簡易的なアクセスの他に、順次処理も可能なように
78  * 現在アクセス中の、Workbook、Sheet、Row、Cell オブジェクトを内部で管理しています。
79  *
80  * 入力形式は、openXML形式にも対応しています。
81  * ファイルの内容に応じて、.xlsと.xlsxのどちらで読み取るかは、内部的に
82  * 自動判定されます。
83  *
84  * @og.rev 6.0.2.0 (2014/09/19) 新規作成
85  * @og.rev 8.1.2.3 (2022/05/20) テキスト変換処理 復活
86  * @og.group その他
87  *
88  * @version  6.0
89  * @author   Kazuhiko Hasegawa
90  * @since    JDK7.0,
91  */
92 public class ExcelModel {
93         /** このプログラムのVERSION文字列を設定します。 {@value} */
94         private static final String VERSION = "8.5.0.0 (2023/04/21)" ;
95
96         private static final String DEF_SHEET_NAME = "Sheet" ;
97
98         // 6.0.2.3 (2014/10/10) ImageUtil の Suffix と、Workbook.PICTURE_TYPE_*** の関連付けをしておきます。
99         // 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 }