2 * Copyright (c) 2009 The openGion Project.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13 * either express or implied. See the License for the specific language
14 * governing permissions and limitations under the License.
16 package org.opengion.fukurou.model;
18 import java.util.concurrent.ConcurrentMap; // 6.4.3.4 (2016/03/11)
19 import java.util.concurrent.ConcurrentHashMap; // 6.4.3.1 (2016/02/12) refactoring
21 import org.apache.poi.ss.usermodel.BuiltinFormats; // 6.2.0.0 (2015/02/27)
22 import org.apache.poi.xssf.model.StylesTable; // 6.2.0.0 (2015/02/27)
23 import org.apache.poi.xssf.usermodel.XSSFCellStyle; // 6.2.0.0 (2015/02/27)
25 import java.util.Locale; // 6.2.0.0 (2015/02/27)
26 import java.util.Date; // 6.2.0.0 (2015/02/27)
27 import java.text.DateFormat; // 6.2.0.0 (2015/02/27)
28 import java.text.SimpleDateFormat; // 6.2.0.0 (2015/02/27)
29 import org.apache.poi.ss.usermodel.DateUtil; // 6.2.0.0 (2015/02/27)
30 import org.apache.poi.ss.util.NumberToTextConverter; // 6.2.0.0 (2015/02/27)
31 import org.apache.poi.hssf.record.ExtendedFormatRecord; // 6.2.0.0 (2015/02/27)
32 import org.apache.poi.hssf.record.FormatRecord; // 6.2.0.0 (2015/02/27)
33 import org.apache.poi.hssf.record.NumberRecord; // 6.2.0.0 (2015/02/27)
36 * POI による、Excel(xlsx)の読み取りクラスです。
38 * xlsx形式のEXCELを、イベント方式でテキストデータを読み取ります。
39 * このクラスでは、XSSF(.xlsx)形式のファイルを、TableModelHelper を介したイベントで読み取ります。
40 * TableModelHelperイベントは、openGion形式のファイル読み取りに準拠した方法をサポートします。
41 * ※ openGion形式のEXCELファイルとは、#NAME 列に、カラム名があり、#で始まる
42 * レコードは、コメントとして判断し、読み飛ばす処理の事です。
44 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
45 * @og.rev 6.2.0.0 (2015/02/27) パッケージ変更(util → model),クラス名変更(ExcelReader_XSSF → EventReader_XLSX)
49 * @author Kazuhiko Hasegawa
52 public final class ExcelStyleFormat {
53 /** このプログラムのVERSION文字列を設定します。 {@value} */
54 private static final String VERSION = "6.4.3.3 (2016/03/04)" ;
56 // 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
57 private static final String[] DATE_TYPE = { "yyyyMMdd" , "yyyyMMddHHmmss" , "HHmmss" };
58 // 6.3.1.0 (2015/06/28) 外部からも使えるように、static化します。
59 private static final DateFormat[] DT_FORMAT = new DateFormat[DATE_TYPE.length]; // 6.4.1.1 (2016/01/16) dtFormat → DT_FORMAT refactoring
60 private final StylesTable stylesTable ;
62 // 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
63 // private final List<Integer> extFmtIdx = new ArrayList<>(); // ExtendedFormatRecord のアドレス(順番) の順番に、FormatIndexを設定する。
64 /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */
65 private final ConcurrentMap<Integer,String> fmtStrMap = new ConcurrentHashMap<>(); // FormatIndex をキーに、Format文字列 を管理
66 /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */
67 private final ConcurrentMap<Integer,Integer> extFmtIdxMap = new ConcurrentHashMap<>(); // ExtendedFormatRecord のアドレス(順番) をキーに、FormatIndexを設定する。
68 private int extFmtCnt ;
73 * XSL 処理では、HSSFListener のイベント処理のうち、NumberRecord の値取得に
75 * 具体的には、ExtendedFormatRecord レコードから、FormatIndex と 並び順を
76 * 管理するMapと、FormatRecord レコードから、IndexCode と フォーマット文字列を
77 * 管理するMap を作成し、NumberRecordレコードの XFIndex から、ExtendedFormatRecord を
78 * 経由して、FormatRecord のフォーマット文字列 を取得し、日付フォーマットの場合は、
79 * 日付文字列に、それ以外は、数字文字列に変換する手助けを行います。
81 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
83 public ExcelStyleFormat() {
90 * StylesTable は、日付型をはじめとする、EXCELのスタイルのフォーマットを管理しています。
91 * XSLX形式のEXCELをパースする場合に、このコンストラクタを使用して、StylesTableオブジェクトを
94 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
96 * @param styles StylesTableオブジェクト
98 public ExcelStyleFormat( final StylesTable styles ) {
103 * XSL系 ExtendedFormatRecordレコードの設定。
105 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
107 * @param extFmtRec ExtendedFormatRecordレコード
109 public void addExtFmtRec( final ExtendedFormatRecord extFmtRec ) {
110 final short fmtIdx = extFmtRec.getFormatIndex();
111 final short xfType = extFmtRec.getXFType();
112 // Listに アドレス(順番) の順番に、FormatIndexを設定する。
113 // extFmtIdx.add( Integer.valueOf( fmtIdx ) );
115 // タイプを判別して、アドレス(順番)をキーに、Mapに登録することで、データ件数を削減します。
116 if( xfType == ExtendedFormatRecord.XF_CELL ) {
117 // アドレス(順番) FormatIndex
118 extFmtIdxMap.put( Integer.valueOf( extFmtCnt ),Integer.valueOf( fmtIdx ) );
119 // System.out.println( "fmtIdx=[" + fmtIdx + "] , xfType=[" + xfType + "] , CNT=" + extFmtCnt );
125 * XSL系 FormatRecordレコードの設定。
127 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
129 * @param fmtRec FormatRecordレコード
131 public void addFmtRec( final FormatRecord fmtRec ) {
132 final int idxc = fmtRec.getIndexCode();
133 final String fmt = fmtRec.getFormatString();
135 // IndexCode をキーに、Format文字列を登録する。
136 fmtStrMap.put( Integer.valueOf( idxc ) , fmt );
137 // System.out.println( "fmtRec=[" + idxc + "], fmt=[" + fmt + "]" );
141 * XSLX系 セルスタイル文字列(スタイル番号)から、データフォーマットを取得します。
143 * StylesTable は、日付型をはじめとする、EXCELのスタイルのフォーマットを管理しています。
144 * XSLX形式のEXCELのフォーマット文字列を取得する場合に、使用します。
146 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
148 * @param cellStyleStr セルスタイル文字列(スタイル番号)
149 * @param val endElement時の値文字列
150 * @return 日付データか、数値データかを判別した結果の文字列
152 public String getNumberValue( final String cellStyleStr , final String val ) {
153 String fmtStr = null;
155 if( stylesTable != null && cellStyleStr != null && !cellStyleStr.isEmpty() ) {
156 final int stIdx = Integer.parseInt( cellStyleStr );
157 final XSSFCellStyle style = stylesTable.getStyleAt( stIdx );
158 fmtStr = style.getDataFormatString();
160 // 必要かどうか不明。テスト時は、ユーザー定義フォーマットも、上記処理で取得できていた。
161 if( fmtStr == null ) {
162 final int fmtIdx = style.getDataFormat();
163 fmtStr = BuiltinFormats.getBuiltinFormat( fmtIdx );
166 // if( fmtStr != null ) {
167 // System.out.println( "style=[" + cellStyleStr + "], stIdx=[" + stIdx + "], fmtStr=[" + fmtStr + "]" );
171 return getNumberValue( fmtStr , Double.parseDouble( val ) ) ;
175 * XSL系 Numberレコードから、日付データか、数値データかを判別して返します。
177 * 日付フォーマットの判定処理を #isDateFormat(String) で行い、日付の場合は、
178 * 各タイプ(日付、日時、時刻)に応じた、文字列を返します。
179 * 日付フォーマットでない場合は、数字化文字列を返します。
181 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
183 * @param numrec NumberRecordレコード
184 * @return 日付データか、数値データかを判別した結果の文字列
186 public String getNumberValue( final NumberRecord numrec ) {
187 final int xfIdx = numrec.getXFIndex(); // extFmtCnt の事
188 final int fmtIdx = extFmtIdxMap.get( Integer.valueOf( xfIdx ) );
190 // final String fmtStr = fmtIdx < HSSFDataFormat.getNumberOfBuiltinBuiltinFormats()
191 // ? HSSFDataFormat.getBuiltinFormat( (short)fmtIdx )
192 // : fmtStrMap.get( Integer.valueOf( fmtIdx ) );
194 final String fmtStr = fmtIdx < BuiltinFormats.FIRST_USER_DEFINED_FORMAT_INDEX
195 ? BuiltinFormats.getBuiltinFormat( fmtIdx )
196 : fmtStrMap.get( Integer.valueOf( fmtIdx ) );
198 // if( fmtStr != null ) {
199 // System.out.println( "xfIdx=[" + xfIdx + "], fmtIdx=[" + fmtIdx + "], fmtStr=[" + fmtStr + "]" );
202 return getNumberValue( fmtStr , numrec.getValue() ) ;
206 * フォーマット情報と値から、日付データか、数値データかを判別して返します。
208 * 日付フォーマットの判定処理を #isDateFormat(String) で行い、日付の場合は、
209 * 各タイプ(日付、日時、時刻)に応じた、文字列を返します。
210 * 日付フォーマットでない場合は、数字文字列を返します。
212 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
213 * @og.rev 6.3.1.0 (2015/06/28) 外部からも使えるように、static化します。
215 * @param fmtStr フォーマット情報
216 * @param val Numberレコードのデータ
217 * @return 日付データか、数値データかを判別した結果の文字列
219 public static String getNumberValue( final String fmtStr , final double val ) {
220 return isDateFormat( fmtStr )
221 ? dateFormat( val ) // 日付
222 : NumberToTextConverter.toText( val ) ; // 数字
226 * フォーマット文字列から、日付型フォーマットかどうかの判定を行います。
228 * ここでは、日本式のフォーマットや、ユーザー定義の日付フォーマットでも、
229 * ある程度判定できるように、処理しています。
230 * 以下の文字列を含む場合は、true を返し、それ以外は、false です。
231 * "年","月","日","yy","y/m","m/d","h:m"
232 * ただし、General(標準) は、除外しています。
234 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
235 * @og.rev 6.3.1.0 (2015/06/28) 外部からも使えるように、static化します。
236 * @og.rev 6.4.3.3 (2016/03/04) PMD refactoring. These nested if statements could be combined
238 * @param fmt フォーマット文字列
239 * @return 判定結果 [true:日付型/false:それ以外]
241 private static boolean isDateFormat( final String fmt ) {
242 return fmt != null && !fmt.isEmpty() && !"General".equalsIgnoreCase( fmt ) &&
243 ( fmt.contains( "年" ) || fmt.contains( "月" ) || fmt.contains( "日" ) ||
244 fmt.contains( "yy" ) || fmt.contains( "y/m" ) || fmt.contains( "m/d" ) ||
245 fmt.contains( "h:m" ) ) ;
249 * 日付型の値を、最適なフォーマットで変換して返します。
251 * 日付データは、(DATE=0,DATETIME=1,TIME=2) に分類できます。
252 * DATE とは、日付のみの状態で、引数の val は、整数に変換できます。
253 * その場合、"yyyyMMdd" フォーマットで変換します。
254 * DATETIME とは、日付+時刻なので、"yyyyMMddHHmmss" に変換します。
255 * TIME は、日付情報を持っていないため、"HHmmss" に変換します。
257 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2)
258 * @og.rev 6.3.1.0 (2015/06/28) 外部からも使えるように、static化します。
263 public static String dateFormat( final double val ) {
264 int dtType = 0; // 日付型の処理(DATE=0,DATETIME=1,TIME=2)
265 if( val < 1.0d ) { // 日付部が無い → TIME=2
268 // 6.3.9.0 (2015/11/06) Avoid if (x != y) ..; else ..; (PMD)
269 else if( Double.compare( val , Math.floor( val ) ) == 0 ) { // 整数(Long 相当) → DATE=0
272 else { // 整数でない → DATETIME=1
276 DateFormat dtfmt = DT_FORMAT[dtType]; // 各タイプ別にキャッシュしている。
277 if( dtfmt == null ) {
278 dtfmt = new SimpleDateFormat( DATE_TYPE[dtType] , Locale.JAPAN ); // 初めての場合は新規作成
279 DT_FORMAT[dtType] = dtfmt;
282 final Date dt = DateUtil.getJavaDate( val );
283 return dtfmt.format( dt );