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.concurrent.ConcurrentMap; // 6.4.3.4 (2016/03/11) 019import java.util.concurrent.ConcurrentHashMap; // 6.4.3.1 (2016/02/12) refactoring 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.4.3.3 (2016/03/04)" ; 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 static final DateFormat[] DT_FORMAT = new DateFormat[DATE_TYPE.length]; // 6.4.1.1 (2016/01/16) dtFormat → DT_FORMAT refactoring 060 private final StylesTable stylesTable ; 061 062 // 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2) 063 // private final List<Integer> extFmtIdx = new ArrayList<>(); // ExtendedFormatRecord のアドレス(順番) の順番に、FormatIndexを設定する。 064 /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */ 065 private final ConcurrentMap<Integer,String> fmtStrMap = new ConcurrentHashMap<>(); // FormatIndex をキーに、Format文字列 を管理 066 /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */ 067 private final ConcurrentMap<Integer,Integer> extFmtIdxMap = new ConcurrentHashMap<>(); // ExtendedFormatRecord のアドレス(順番) をキーに、FormatIndexを設定する。 068 private int extFmtCnt ; 069 070 /** 071 * XSL系 コンストラクター 072 * 073 * XSL 処理では、HSSFListener のイベント処理のうち、NumberRecord の値取得に 074 * 必要な内部処理を、実行します。 075 * 具体的には、ExtendedFormatRecord レコードから、FormatIndex と 並び順を 076 * 管理するMapと、FormatRecord レコードから、IndexCode と フォーマット文字列を 077 * 管理するMap を作成し、NumberRecordレコードの XFIndex から、ExtendedFormatRecord を 078 * 経由して、FormatRecord のフォーマット文字列 を取得し、日付フォーマットの場合は、 079 * 日付文字列に、それ以外は、数字文字列に変換する手助けを行います。 080 * 081 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2) 082 */ 083 public ExcelStyleFormat() { 084 stylesTable = null; 085 } 086 087 /** 088 * XSLX系 コンストラクター 089 * 090 * StylesTable は、日付型をはじめとする、EXCELのスタイルのフォーマットを管理しています。 091 * XSLX形式のEXCELをパースする場合に、このコンストラクタを使用して、StylesTableオブジェクトを 092 * 設定します。 093 * 094 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2) 095 * 096 * @param styles StylesTableオブジェクト 097 */ 098 public ExcelStyleFormat( final StylesTable styles ) { 099 stylesTable = styles; 100 } 101 102 /** 103 * XSL系 ExtendedFormatRecordレコードの設定。 104 * 105 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2) 106 * 107 * @param extFmtRec ExtendedFormatRecordレコード 108 */ 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 ) ); 114 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 ); 120 } 121 extFmtCnt++; 122 } 123 124 /** 125 * XSL系 FormatRecordレコードの設定。 126 * 127 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2) 128 * 129 * @param fmtRec FormatRecordレコード 130 */ 131 public void addFmtRec( final FormatRecord fmtRec ) { 132 final int idxc = fmtRec.getIndexCode(); 133 final String fmt = fmtRec.getFormatString(); 134 135 // IndexCode をキーに、Format文字列を登録する。 136 fmtStrMap.put( Integer.valueOf( idxc ) , fmt ); 137 // System.out.println( "fmtRec=[" + idxc + "], fmt=[" + fmt + "]" ); 138 } 139 140 /** 141 * XSLX系 セルスタイル文字列(スタイル番号)から、データフォーマットを取得します。 142 * 143 * StylesTable は、日付型をはじめとする、EXCELのスタイルのフォーマットを管理しています。 144 * XSLX形式のEXCELのフォーマット文字列を取得する場合に、使用します。 145 * 146 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2) 147 * 148 * @param cellStyleStr セルスタイル文字列(スタイル番号) 149 * @param val endElement時の値文字列 150 * @return 日付データか、数値データかを判別した結果の文字列 151 */ 152 public String getNumberValue( final String cellStyleStr , final String val ) { 153 String fmtStr = null; 154 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(); 159 160 // 必要かどうか不明。テスト時は、ユーザー定義フォーマットも、上記処理で取得できていた。 161 if( fmtStr == null ) { 162 final int fmtIdx = style.getDataFormat(); 163 fmtStr = BuiltinFormats.getBuiltinFormat( fmtIdx ); 164 } 165 166 // if( fmtStr != null ) { 167 // System.out.println( "style=[" + cellStyleStr + "], stIdx=[" + stIdx + "], fmtStr=[" + fmtStr + "]" ); 168 // } 169 } 170 171 return getNumberValue( fmtStr , Double.parseDouble( val ) ) ; 172 } 173 174 /** 175 * XSL系 Numberレコードから、日付データか、数値データかを判別して返します。 176 * 177 * 日付フォーマットの判定処理を #isDateFormat(String) で行い、日付の場合は、 178 * 各タイプ(日付、日時、時刻)に応じた、文字列を返します。 179 * 日付フォーマットでない場合は、数字化文字列を返します。 180 * 181 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2) 182 * 183 * @param numrec NumberRecordレコード 184 * @return 日付データか、数値データかを判別した結果の文字列 185 */ 186 public String getNumberValue( final NumberRecord numrec ) { 187 final int xfIdx = numrec.getXFIndex(); // extFmtCnt の事 188 final int fmtIdx = extFmtIdxMap.get( Integer.valueOf( xfIdx ) ); 189 190 // final String fmtStr = fmtIdx < HSSFDataFormat.getNumberOfBuiltinBuiltinFormats() 191 // ? HSSFDataFormat.getBuiltinFormat( (short)fmtIdx ) 192 // : fmtStrMap.get( Integer.valueOf( fmtIdx ) ); 193 194 final String fmtStr = fmtIdx < BuiltinFormats.FIRST_USER_DEFINED_FORMAT_INDEX 195 ? BuiltinFormats.getBuiltinFormat( fmtIdx ) 196 : fmtStrMap.get( Integer.valueOf( fmtIdx ) ); 197 198 // if( fmtStr != null ) { 199 // System.out.println( "xfIdx=[" + xfIdx + "], fmtIdx=[" + fmtIdx + "], fmtStr=[" + fmtStr + "]" ); 200 // } 201 202 return getNumberValue( fmtStr , numrec.getValue() ) ; 203 } 204 205 /** 206 * フォーマット情報と値から、日付データか、数値データかを判別して返します。 207 * 208 * 日付フォーマットの判定処理を #isDateFormat(String) で行い、日付の場合は、 209 * 各タイプ(日付、日時、時刻)に応じた、文字列を返します。 210 * 日付フォーマットでない場合は、数字文字列を返します。 211 * 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化します。 214 * 215 * @param fmtStr フォーマット情報 216 * @param val Numberレコードのデータ 217 * @return 日付データか、数値データかを判別した結果の文字列 218 */ 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 * ただし、General(標準) は、除外しています。 233 * 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 237 * 238 * @param fmt フォーマット文字列 239 * @return 判定結果 [true:日付型/false:それ以外] 240 */ 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" ) ) ; 246 247 } 248 249 /** 250 * 日付型の値を、最適なフォーマットで変換して返します。 251 * 252 * 日付データは、(DATE=0,DATETIME=1,TIME=2) に分類できます。 253 * DATE とは、日付のみの状態で、引数の val は、整数に変換できます。 254 * その場合、"yyyyMMdd" フォーマットで変換します。 255 * DATETIME とは、日付+時刻なので、"yyyyMMddHHmmss" に変換します。 256 * TIME は、日付情報を持っていないため、"HHmmss" に変換します。 257 * 258 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2) 259 * @og.rev 6.3.1.0 (2015/06/28) 外部からも使えるように、static化します。 260 * 261 * @param val 日付型の値 262 * @return 日付型の変換結果 263 */ 264 public static String dateFormat( final double val ) { 265 int dtType = 0; // 日付型の処理(DATE=0,DATETIME=1,TIME=2) 266 if( val < 1.0d ) { // 日付部が無い → TIME=2 267 dtType = 2; 268 } 269 // 6.3.9.0 (2015/11/06) Avoid if (x != y) ..; else ..; (PMD) 270 else if( Double.compare( val , Math.floor( val ) ) == 0 ) { // 整数(Long 相当) → DATE=0 271 dtType = 0; 272 } 273 else { // 整数でない → DATETIME=1 274 dtType = 1; 275 } 276 277 DateFormat dtfmt = DT_FORMAT[dtType]; // 各タイプ別にキャッシュしている。 278 if( dtfmt == null ) { 279 dtfmt = new SimpleDateFormat( DATE_TYPE[dtType] , Locale.JAPAN ); // 初めての場合は新規作成 280 DT_FORMAT[dtType] = dtfmt; 281 } 282 283 final Date dt = DateUtil.getJavaDate( val ); 284 return dtfmt.format( dt ); 285 } 286}