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}