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}