    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.util.Map;                                                                                   // 6.2.0.0 (2015/02/27)
    019import java.util.HashMap;                                                                               // 6.2.0.0 (2015/02/27)
    020
    021import org.apache.poi.ss.usermodel.BuiltinFormats;                              // 6.2.0.0 (2015/02/27)
    022import org.apache.poi.xssf.model.StylesTable;                                   // 6.2.0.0 (2015/02/27)
    023import org.apache.poi.xssf.usermodel.XSSFCellStyle;                             // 6.2.0.0 (2015/02/27)
    024
    025import java.util.Locale;                                                                                // 6.2.0.0 (2015/02/27)
    026import java.util.Date;                                                                                  // 6.2.0.0 (2015/02/27)
    027import java.text.DateFormat;                                                                    // 6.2.0.0 (2015/02/27)
    028import java.text.SimpleDateFormat;                                                              // 6.2.0.0 (2015/02/27)
    029import org.apache.poi.ss.usermodel.DateUtil;                                    // 6.2.0.0 (2015/02/27)
    030import org.apache.poi.ss.util.NumberToTextConverter;                    // 6.2.0.0 (2015/02/27)
    031import org.apache.poi.hssf.record.ExtendedFormatRecord;                 // 6.2.0.0 (2015/02/27)
    032import org.apache.poi.hssf.record.FormatRecord;                                 // 6.2.0.0 (2015/02/27)
    033import org.apache.poi.hssf.record.NumberRecord;                                 // 6.2.0.0 (2015/02/27)
    034
    035/**
    036 * POI による、Excel(xlsx)の読み取りクラスです。
    037 *
    038 * xlsx形式のEXCELを、イベント方式でテキストデータを読み取ります。
    039 * このクラスでは、XSSF(.xlsx)形式のファイルを、TableModelHelper を介したイベントで読み取ります。
    040 * TableModelHelperイベントは、openGion形式のファイル読み取りに準拠した方法をサポートします。
    041 * ※ openGion形式のEXCELファイルとは、#NAME 列に、カラム名があり、#で始まる
    042 *    レコードは、コメントとして判断し、読み飛ばす処理の事です。
    043 *
    044 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
    045 * @og.rev 6.2.0.0 (2015/02/27) パッケージ変更(util → model),クラス名変更(ExcelReader_XSSF → EventReader_XLSX)
    046 * @og.group ファイル入力
    047 *
    048 * @version  6.0
    049 * @author   Kazuhiko Hasegawa
    050 * @since    JDK7.0,
    051 */
    052public final class ExcelStyleFormat {
    053        /** このプログラムのVERSION文字列を設定します。   {@value} */
    054        private static final String VERSION = "6.3.1.0 (2015/06/28)" ;
    055
    056        // 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
    057        private static final String[] DATE_TYPE = { "yyyyMMdd" , "yyyyMMddHHmmss" , "HHmmss" };
    058        // 6.3.1.0 (2015/06/28) 外部からも使えるように、static化します。
    059//      private final DateFormat[]      dtFormat = new DateFormat[DATE_TYPE.length];
    060        private static final DateFormat[] dtFormat = new DateFormat[DATE_TYPE.length];
    061        private final StylesTable                       stylesTable ;
    062
    063        // 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
    064        // private final List<Integer>                    extFmtIdx = new ArrayList<>();    // ExtendedFormatRecord のｱﾄﾞﾚｽ(順番) の順番に、FormatIndexを設定する。
    065        private final Map<Integer,Integer>        extFmtIdx = new HashMap();      // ExtendedFormatRecord のｱﾄﾞﾚｽ(順番) をキーに、FormatIndexを設定する。
    066        private final Map<Integer,String> fmtStrMap = new HashMap();      // FormatIndex をキーに、Format文字列 を管理
    067        private int   extFmtCnt ;
    068
    069        /**
    070         * XSL系 コンストラクター
    071         *
    072         * XSL 処理では、HSSFListener のイベント処理のうち、NumberRecord の値取得に
    073         * 必要な内部処理を、実行します。
    074         * 具体的には、ExtendedFormatRecord レコードから、FormatIndex と 並び順を
    075         * 管理するMapと、FormatRecord レコードから、IndexCode と フォーマット文字列を
    076         * 管理するMap を作成し、NumberRecordレコードの XFIndex から、ExtendedFormatRecord を
    077         * 経由して、FormatRecord のフォーマット文字列 を取得し、日付フォーマットの場合は、
    078         * 日付文字列に、それ以外は、数字文字列に変換する手助けを行います。
    079         *
    080         * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
    081         */
    082        public ExcelStyleFormat() {
    083                stylesTable = null;
    084        }
    085
    086        /**
    087         * XSLX系 コンストラクター
    088         *
    089         * StylesTable は、日付型をはじめとする、EXCELのスタイルのフォーマットを管理しています。
    090         * XSLX形式のEXCELをパースする場合に、このコンストラクタを使用して、StylesTableオブジェクトを
    091         * 設定します。
    092         *
    093         * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
    094         *
    095         * @param       styles StylesTableオブジェクト
    096         */
    097        public ExcelStyleFormat( final StylesTable styles ) {
    098                stylesTable = styles;
    099        }
    100
    101        /**
    102         * XSL系 ExtendedFormatRecordレコードの設定。
    103         *
    104         * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
    105         *
    106         * @param       extFmtRec ExtendedFormatRecordレコード
    107         */
    108        public void addExtFmtRec( final ExtendedFormatRecord extFmtRec ) {
    109                final short fmtIdx = extFmtRec.getFormatIndex();
    110                final short xfType = extFmtRec.getXFType();
    111                // Listに ｱﾄﾞﾚｽ(順番) の順番に、FormatIndexを設定する。
    112        //      extFmtIdx.add( Integer.valueOf( fmtIdx ) );
    113
    114                // タイプを判別して、ｱﾄﾞﾚｽ(順番)をキーに、Mapに登録することで、データ件数を削減します。
    115                if( xfType == ExtendedFormatRecord.XF_CELL ) {
    116                        //                              ｱﾄﾞﾚｽ(順番)               FormatIndex
    117                        extFmtIdx.put( Integer.valueOf( extFmtCnt ),Integer.valueOf( fmtIdx ) );
    118        //              System.out.println( "fmtIdx=[" + fmtIdx + "] , xfType=[" + xfType + "] , CNT=" + extFmtCnt );
    119                }
    120                extFmtCnt++;
    121        }
    122
    123        /**
    124         * XSL系 FormatRecordレコードの設定。
    125         *
    126         * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
    127         *
    128         * @param       fmtRec FormatRecordレコード
    129         */
    130        public void addFmtRec( final FormatRecord fmtRec ) {
    131                final int    idxc = fmtRec.getIndexCode();
    132                final String fmt  = fmtRec.getFormatString();
    133
    134                // IndexCode をキーに、Format文字列を登録する。
    135                fmtStrMap.put( Integer.valueOf( idxc ) , fmt );
    136        //      System.out.println( "fmtRec=[" + idxc + "], fmt=[" + fmt + "]" );
    137        }
    138
    139        /**
    140         * XSLX系 セルスタイル文字列(スタイル番号)から、データフォーマットを取得します。
    141         *
    142         * StylesTable は、日付型をはじめとする、EXCELのスタイルのフォーマットを管理しています。
    143         * XSLX形式のEXCELのフォーマット文字列を取得する場合に、使用します。
    144         *
    145         * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
    146         *
    147         * @param       cellStyleStr    セルスタイル文字列(スタイル番号)
    148         * @param       val                             endElement時の値文字列
    149         * @return      日付データか、数値データかを判別した結果の文字列
    150         */
    151        public String getNumberValue( final String cellStyleStr , final String val ) {
    152                String fmtStr = null;
    153
    154                if( stylesTable != null && cellStyleStr != null && !cellStyleStr.isEmpty() ) {
    155                        final int stIdx = Integer.parseInt( cellStyleStr );
    156                        final XSSFCellStyle style = stylesTable.getStyleAt( stIdx );
    157                        fmtStr = style.getDataFormatString();
    158
    159                        // 必要かどうか不明。テスト時は、ユーザー定義フォーマットも、上記処理で取得できていた。
    160                        if( fmtStr == null ) {
    161                                final int fmtIdx = style.getDataFormat();
    162                                fmtStr = BuiltinFormats.getBuiltinFormat( fmtIdx );
    163                        }
    164
    165        //              if( fmtStr != null ) {
    166        //                      System.out.println( "style=[" + cellStyleStr + "], stIdx=[" + stIdx + "],  fmtStr=[" + fmtStr + "]" );
    167        //              }
    168                }
    169
    170                return getNumberValue( fmtStr , Double.parseDouble( val ) ) ;
    171        }
    172
    173        /**
    174         * XSL系 Numberレコードから、日付データか、数値データかを判別して返します。
    175         *
    176         * 日付フォーマットの判定処理を #isDateFormat(String) で行い、日付の場合は、
    177         * 各タイプ(日付、日時、時刻)に応じた、文字列を返します。
    178         * 日付フォーマットでない場合は、数字化文字列を返します。
    179         *
    180         * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
    181         *
    182         * @param       numrec  NumberRecordレコード
    183         * @return      日付データか、数値データかを判別した結果の文字列
    184         */
    185        public String getNumberValue( final NumberRecord numrec ) {
    186                final int xfIdx  = numrec.getXFIndex();                                                         // extFmtCnt の事
    187                final int fmtIdx = extFmtIdx.get( Integer.valueOf( xfIdx ) );
    188
    189        //      final String fmtStr = fmtIdx < HSSFDataFormat.getNumberOfBuiltinBuiltinFormats()
    190        //                                                      ? HSSFDataFormat.getBuiltinFormat( (short)fmtIdx )
    191        //                                                      : fmtStrMap.get( Integer.valueOf( fmtIdx ) );
    192
    193                final String fmtStr = fmtIdx < BuiltinFormats.FIRST_USER_DEFINED_FORMAT_INDEX
    194                                                                ? BuiltinFormats.getBuiltinFormat( fmtIdx )
    195                                                                : fmtStrMap.get( Integer.valueOf( fmtIdx ) );
    196
    197        //              if( fmtStr != null ) {
    198        //                      System.out.println( "xfIdx=[" + xfIdx + "], fmtIdx=[" + fmtIdx + "],  fmtStr=[" + fmtStr + "]" );
    199        //              }
    200
    201                return getNumberValue( fmtStr , numrec.getValue() ) ;
    202        }
    203
    204        /**
    205         * フォーマット情報と値から、日付データか、数値データかを判別して返します。
    206         *
    207         * 日付フォーマットの判定処理を #isDateFormat(String) で行い、日付の場合は、
    208         * 各タイプ(日付、日時、時刻)に応じた、文字列を返します。
    209         * 日付フォーマットでない場合は、数字文字列を返します。
    210         *
    211         * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
    212         * @og.rev 6.3.1.0 (2015/06/28) 外部からも使えるように、static化します。
    213         *
    214         * @param       fmtStr  フォーマット情報
    215         * @param       val             Numberレコードのデータ
    216         * @return      日付データか、数値データかを判別した結果の文字列
    217         */
    218//      private String getNumberValue( final String fmtStr , final double val ) {
    219        public static String getNumberValue( final String fmtStr , final double val ) {
    220                return isDateFormat( fmtStr )
    221                                        ? dateFormat( val )                                                     // 日付
    222                                        : NumberToTextConverter.toText( val ) ;         // 数字
    223        }
    224
    225        /**
    226         * フォーマット文字列から、日付型フォーマットかどうかの判定を行います。
    227         *
    228         * ここでは、日本式のフォーマットや、ユーザー定義の日付フォーマットでも、
    229         * ある程度判定できるように、処理しています。
    230         * 以下の文字列を含む場合は、true を返し、それ以外は、false です。
    231         *   "年","月","日","yy","y/m","m/d","h:m"
    232         *
    233         * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
    234         * @og.rev 6.3.1.0 (2015/06/28) 外部からも使えるように、static化します。
    235         *
    236         * @param       fmt     フォーマット文字列
    237         * @return      判定結果 [true:日付型/false:それ以外]
    238         */
    239//      private boolean isDateFormat( final String fmt ) {
    240        private static boolean isDateFormat( final String fmt ) {
    241                boolean isDf = false;
    242                // 特殊処理：General(標準)は、除外します。
    243                if( fmt != null && !fmt.isEmpty() && !"General".equalsIgnoreCase( fmt ) ) {
    244                        // これ以外のパターンもあるかも。逆に、日付フォームでない別のフォームと一致するかも。
    245                        if( fmt.contains( "年" ) || fmt.contains( "月"  ) || fmt.contains( "日"  ) ||
    246                                fmt.contains( "yy" ) || fmt.contains( "y/m" ) || fmt.contains( "m/d" ) ||
    247                                fmt.contains( "h:m" ) ) {
    248                                        isDf = true;
    249                        }
    250                }
    251
    252                return isDf;
    253        }
    254
    255        /**
    256         * 日付型の値を、最適なフォーマットで変換して返します。
    257         *
    258         * 日付データは、(DATE=0,DATETIME=1,TIME=2) に分類できます。
    259         * DATE とは、日付のみの状態で、引数の val は、整数に変換できます。
    260         * その場合、"yyyyMMdd" フォーマットで変換します。
    261         * DATETIME とは、日付＋時刻なので、"yyyyMMddHHmmss" に変換します。
    262         * TIME は、日付情報を持っていないため、"HHmmss" に変換します。
    263         *
    264         * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
    265         * @og.rev 6.3.1.0 (2015/06/28) 外部からも使えるように、static化します。
    266         *
    267         * @param       val     日付型の値
    268         * @return      日付型の変換結果
    269         */
    270//      private String dateFormat( final double val ) {
    271        public static String dateFormat( final double val ) {
    272                int dtType = 0;         // 日付型の処理(DATE=0,DATETIME=1,TIME=2)
    273                if( val < 1.0d ) {                                                                                           // 日付部が無い → TIME=2
    274                        dtType = 2;
    275                }
    276                else if( Double.compare( val , Math.floor( val ) ) != 0 ) {             // 整数でない → DATETIME=1
    277                        dtType = 1;
    278                }
    279                else {                                                                                                                  // Long 相当 → DATE=0
    280                        dtType = 0;
    281                }
    282
    283                DateFormat dtfmt = dtFormat[dtType];                    // 各タイプ別にキャッシュしている。
    284                if( dtfmt == null ) {
    285                        dtfmt = new SimpleDateFormat( DATE_TYPE[dtType] , Locale.JAPAN );               // 初めての場合は新規作成
    286                        dtFormat[dtType] = dtfmt;
    287                }
    288
    289                final Date dt = DateUtil.getJavaDate( val );
    290                return dtfmt.format( dt );
    291        }
    292}