    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 ;   // 標準セル幅の５倍を最大幅とする。
    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(フォント名)は、"ＭＳ Ｐゴシック" など名称になります。
    189         * fontPoint は、フォントの大きさを指定します。
    190         * 内部的には、setFontHeightInPoints(short)メソッドで設定します。
    191         *
    192         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    193         *
    194         * @param       fontName        フォント名 ("ＭＳ Ｐゴシック" など。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 );   // "ＭＳ Ｐゴシック" など
    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         * 初期カラム幅の５倍を限度にしています。
    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 + ")" ;       // cnt３桁まで可能
    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 の ４シート分を削除します。
    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()＋１になります。
    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}