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