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.io.InputStream; 019import java.io.File; // 6.2.0.0 (2015/02/27) 020import java.io.IOException; 021import java.util.List; // 6.0.3.0 (2014/11/13) XSSFイベントモデル 022import java.util.ArrayList; // 6.0.3.0 (2014/11/13) XSSFイベントモデル 023 024import org.apache.poi.xssf.eventusermodel.XSSFReader; 025//import org.apache.poi.xssf.model.SharedStringsTable; // 8.2.0.0 (2022/06/10) endorsed8.2 廃止 026import org.apache.poi.xssf.model.SharedStrings; // 8.2.0.0 (2022/06/10) endorsed8.2 更新 027import org.apache.poi.xssf.model.StylesTable; // 6.2.0.0 (2015/02/27) 028import org.apache.poi.xssf.usermodel.XSSFRichTextString; 029import org.apache.poi.openxml4j.opc.OPCPackage; 030import org.apache.poi.openxml4j.exceptions.InvalidFormatException ; 031import org.apache.poi.openxml4j.exceptions.OpenXML4JException ; // 6.1.0.0 (2014/12/26) findBugs 032import org.xml.sax.Attributes; 033import org.xml.sax.InputSource; 034import org.xml.sax.SAXException; 035import org.xml.sax.XMLReader; 036import org.xml.sax.helpers.DefaultHandler; 037import javax.xml.parsers.SAXParserFactory; // 6.8.2.4 (2017/11/20) 7.0.0.0準備(java9対応) 038import javax.xml.parsers.ParserConfigurationException; // 6.8.2.4 (2017/11/20) 7.0.0.0準備(java9対応) 039 040import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 041// import org.opengion.fukurou.system.Closer; // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応 042import org.opengion.fukurou.system.ThrowUtil; // 6.5.0.1 (2016/10/21) 043import org.opengion.fukurou.xml.HybsErrorListener; // 6.4.0.2 (2015/12/11) 044import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 045 046/** 047 * POI による、Excel(xlsx)の読み取りクラスです。 048 * 049 * xlsx形式のEXCELを、イベント方式でテキストデータを読み取ります。 050 * このクラスでは、XSSF(.xlsx)形式のファイルを、TableModelHelper を介したイベントで読み取ります。 051 * TableModelHelperイベントは、openGion形式のファイル読み取りに準拠した方法をサポートします。 052 * ※ openGion形式のEXCELファイルとは、#NAME 列に、カラム名があり、#で始まる 053 * レコードは、コメントとして判断し、読み飛ばす処理の事です。 054 * 055 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 056 * @og.rev 6.2.0.0 (2015/02/27) パッケージ変更(util → model),クラス名変更(ExcelReader_XSSF → EventReader_XLSX) 057 * @og.group ファイル入力 058 * 059 * @version 6.0 060 * @author Kazuhiko Hasegawa 061 * @since JDK7.0, 062 */ 063public final class EventReader_XLSX implements EventReader { 064 /** このプログラムのVERSION文字列を設定します。 {@value} */ 065 private static final String VERSION = "8.5.4.2 (2024/01/12)" ; 066 067 /** 6.2.0.0 (2015/02/27) タイプのenum */ 068 private enum XSSFDataType { 069// private static enum XSSFDataType { 070 BOOL, 071 ERROR, 072 FORMULA, 073 INLINESTR, 074 SSTINDEX, 075 NUMBER, 076 } 077 078 /** 079 * デフォルトコンストラクター 080 * 081 * @og.rev 8.5.3.2 (2023/10/13) JDK21対応。警告: デフォルトのコンストラクタの使用で、コメントが指定されていません 082 */ 083 public EventReader_XLSX() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 084 085 /** 086 * 引数ファイル(Excel)を、XSSFイベントモデルを使用してテキスト化します。 087 * 088 * TableModelHelperは、EXCEL読み取り処理用の統一されたイベント処理クラスです。 089 * openGion特有のEXCEL処理方法(#NAME , 先頭行#コメントなど)を実装しています。 090 * これは、HSSFやXSSFの処理を、統一的なイベントモデルで扱うためです。 091 * SSモデルが良いのですが、巨大なXSSF(.xlsx)ファイルを解析すると、OutOfMemoryエラーが 092 * 発生する為、個々に処理する必要があります。 093 * あくまで、読み取り限定であれば、こちらのイベントモデルで十分です。 094 * 095 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 096 * @og.rev 6.1.0.0 (2014/12/26) シートの数のイベント 097 * @og.rev 6.2.0.0 (2015/02/27) staticメソッドをインスタンスメソッドに変更 098 * @og.rev 6.4.0.2 (2015/12/11) org.xml.sax.ErrorHandler の登録 099 * @og.rev 6.4.3.2 (2016/02/19) findBugs対応。冗長な null チェックが行われている。 100 * @og.rev 6.8.2.4 (2017/11/20) 7.0.0.0準備(java9対応) 101 * @og.rev 7.2.9.4 (2020/11/20) spotbugs:null 値を例外経路で利用している可能性がある 102 * @og.rev 8.2.0.0 (2022/06/10) endorsed8.2 更新(SharedStringsTable → SharedStrings) 103 * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応 104 * 105 * @param file 入力ファイル 106 * @param helper イベント処理するオブジェクト 107 */ 108 @Override // EventReader 109 public void eventReader( final File file , final TableModelHelper helper ) { 110 OPCPackage pkg = null; 111 XMLReader parser = null; // 6.4.0.2 (2015/12/11) org.xml.sax.ErrorHandler の登録 112 113 try { 114 // 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正 115 helper.startFile( file ); 116 pkg = OPCPackage.open( file ); // InvalidFormatException 117 final XSSFReader rd = new XSSFReader( pkg ); // IOException , OpenXML4JException 118 119 parser = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); // 6.8.2.4 (2017/11/20) 7.0.0.0準備(java9対応) 120 121 parser.setErrorHandler( new HybsErrorListener() ); // 6.4.0.2 (2015/12/11) org.xml.sax.ErrorHandler の登録 122 123 final List<SheetObj> shtList = getSheetList( rd,parser ); // SAXException , InvalidFormatException 124 helper.sheetSize( shtList.size() ); // 6.1.0.0 (2014/12/26) 125 126// final SharedStringsTable sst = rd.getSharedStringsTable(); // IOException , InvalidFormatException 8.2.0.0 (2022/06/10) endorsed8.2 廃止 127 final SharedStrings sst = rd.getSharedStringsTable(); // IOException , InvalidFormatException 8.2.0.0 (2022/06/10) endorsed8.2 更新 128 final StylesTable styles = rd.getStylesTable(); 129 130 final SheetHandler handler = new SheetHandler( styles,sst,helper ); // ContentHandler のサブクラス 131 parser.setContentHandler( handler ); // ContentHandler のサブクラスを設定 132 133 // Iterator<InputStream> sheets = rd.getSheetsData(); 134 // while(sheets.hasNext()) { 135 // sheet = sheets.next(); 136 // ・・・・・ 137 // } 138 // 形式で、全シート対象に処理できますが、シート名が取り出せません。 139 140 // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応 141// InputStream sheet = null; 142 for( int i=0; i<shtList.size(); i++ ) { 143 final SheetObj sht = shtList.get(i); 144 145 if( helper.startSheet( sht.getName() , i ) ) { // イベント処理 146// try { 147// // シートIDは、rId# で取得できる。 148// sheet = rd.getSheet( sht.getRid() ); // IOException , InvalidFormatException 149 try ( InputStream sheet = rd.getSheet( sht.getRid() ) ) { // IOException , InvalidFormatException 150 parser.parse( new InputSource( sheet ) ); // IOException 151 } 152// finally { 153// Closer.ioClose( sheet ); 154// } 155 } 156 helper.endSheet( i ); // イベント処理 157 } 158 } 159 // 6.1.0.0 (2014/12/26) findBugs: Bug type REC_CATCH_EXCEPTION (click for details) 160 // 例外がスローされないのに例外をキャッチしています。 161 catch( final OpenXML4JException ex ) { // サブクラスの、InvalidFormatException も含まれます。 162 final String errMsg = ".xlsxのファイル解析に失敗しました。" 163 + " filename=" + file + CR 164 + ex.getMessage() ; 165 throw new OgRuntimeException( errMsg , ex ); 166 } 167 catch( final ParserConfigurationException ex ) { // 6.8.2.4 (2017/11/20) 7.0.0.0準備(java9対応) 168 final String errMsg = "要求された構成を満たすパーサーを生成できませんでした。" 169 + " filename=" + file + CR 170 + ex.getMessage() ; 171 throw new OgRuntimeException( errMsg , ex ); 172 } 173 catch( final SAXException ex ) { 174 final String errMsg = "SAX の一般的なエラーまたは警告が発生しました。" 175 + " filename=" + file + CR 176 // 6.4.0.2 (2015/12/11) org.xml.sax.ErrorHandler の登録 177 + ( parser == null ? ex.getMessage() 178 : parser.getErrorHandler().toString() ); 179 // 6.4.3.2 (2016/02/19) findBugs対応。冗長な null チェックが行われている。 180 // parser の処理中に発生するエラーなので、当然、parser は、null ではない。 181 // 7.2.9.4 (2020/11/20) spotbugs:null 値を例外経路で利用している可能性がある 182 // + parser.getErrorHandler().toString(); 183 184 throw new OgRuntimeException( errMsg , ex ); 185 } 186 catch( final IOException ex ) { 187 final String errMsg = ".xlsxのファイルの読み取りに失敗しました。" 188 + " filename=" + file + CR 189 + ex.getMessage() ; 190 throw new OgRuntimeException( errMsg , ex ); 191 } 192 finally { 193 if( pkg != null ) { 194 pkg.revert(); // Close the package WITHOUT saving its content. 195 // Closer.ioClose( pkg ); // OPCPackage を close すると、書き戻しされる。 196 } 197 helper.endFile( file ); // 6.2.0.0 (2015/02/27) 198 } 199 } 200 201 /** 202 * この内部クラスは、XSSFイベントモデルに基づいた、xlsxファイルを SAX処理します。 203 * 204 * この処理のオリジナルは、https://svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/poi/xssf/eventusermodel/examples/FromHowTo.java です。 205 * 206 * また、日付変換で、StylesTable を使用するのは、http://svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/poi/xssf/eventusermodel/XLSX2CSV.java です。 207 * 208 * DefaultHandler を継承しており、xlsx の シート処理を行い、カラム番号と値を取得します。 209 * このクラス自体は、内部で使用されるため、TableModelHelper を引数に設定することで、 210 * 外部から、EXCELのセル情報の取得が可能です。 211 * 212 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 213 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2) 214 * @og.rev 8.2.0.0 (2022/06/10) endorsed8.2 更新(SharedStringsTable → SharedStrings) 215 * 216 * @see org.xml.sax.helpers.DefaultHandler 217 */ 218 private static final class SheetHandler extends DefaultHandler { 219// private final SharedStringsTable sst ; // 8.2.0.0 (2022/06/10) endorsed8.2 廃止 220 private final SharedStrings sst ; // 8.2.0.0 (2022/06/10) endorsed8.2 更新 221 private final TableModelHelper helper; 222 private final ExcelStyleFormat format; 223 224 private String lastContents = "" ; // 6.3.9.0 (2015/11/06) 初期化 225 private XSSFDataType nextDataType = XSSFDataType.NUMBER; // 6.2.0.0 (2015/02/27) 初期化 226 private String cellStyleStr ; // 6.2.0.0 (2015/02/27) 初期化 227 228 private int rowNo = -1; // 現在の行番号 229 private int colNo = -1; // 現在の列番号 230 231 private boolean isRowSkip ; // 行の読み取りを行うかどうか 232 233 /** 234 * コンストラクター 235 * 236 * SharedStrings は、テキストの値を持っているオブジェクトです。 237 * ここで指定する TableModelHelper に対して、パーサー処理の結果がセットされます。 238 * 239 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 240 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2) 241 * @og.rev 6.4.1.1 (2016/01/16) PMD refactoring. It is a good practice to call super() in a constructor 242 * @og.rev 8.2.0.0 (2022/06/10) endorsed8.2 更新(SharedStringsTable → SharedStrings) 243 * 244 * @param styles StylesTableオブジェクト 245 * @param sst SharedStringsオブジェクト 246 * @param helper イベント処理するオブジェクト 247 */ 248// public SheetHandler( final StylesTable styles , final SharedStringsTable sst , final TableModelHelper helper ) { // 8.2.0.0 (2022/06/10) endorsed8.2 廃止 249 public SheetHandler( final StylesTable styles , final SharedStrings sst , final TableModelHelper helper ) { // 8.2.0.0 (2022/06/10) endorsed8.2 更新 250 super(); 251 this.sst = sst; 252 this.helper = helper; 253 format = new ExcelStyleFormat( styles ); // 6.2.0.0 (2015/02/27) StylesTable 追加 254 } 255 256 /** 257 * 要素の開始通知を受け取ります。 258 * 259 * インタフェース ContentHandler 内の startElement メソッドをオーバーライドしています。 260 * パーサは XML 文書内の各要素の前でこのメソッドを呼び出します。 261 * 各 startElement イベントには対応する endElement イベントがあります。 262 * これは、要素が空である場合も変わりません。対応する endElement イベントの前に、 263 * 要素のコンテンツ全部が順番に報告されます。 264 * ここでは、タグがレベル3以上の場合は、上位タグの内容として取り扱います。よって、 265 * タグに名前空間が定義されている場合、その属性は削除します。 266 * 267 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 268 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2) 269 * 270 * @param namespace 名前空間 URI 271 * @param localName 前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列 272 * @param qname 前置修飾子を持つ修飾名。修飾名を使用できない場合は空文字列 273 * @param attributes 要素に付加された属性。属性が存在しない場合、空の Attributesオブジェクト 274 * @see org.xml.sax.helpers.DefaultHandler#startElement(String , String , String , Attributes ) 275 */ 276 @Override // org.xml.sax.ContentHandler 277 public void startElement( final String namespace, final String localName, final String qname, final Attributes attributes ) { 278 if( "row".equals(qname) ) { // row 279 rowNo = Integer.parseInt( attributes.getValue("r") ) - 1; // 0 から始まる 280 isRowSkip = false; 281 } 282 else if( isRowSkip ) { return ; } 283 else if( "c".equals(qname) ) { // c => cell 284 final String kigo = attributes.getValue("r") ; // Excelの行列記号(A1 など) 285 final int[] rowCol = POIUtil.kigo2rowCol( kigo ); // Excelの行列記号を、行番号と列番号に分解します。 286 287 // rowNo = rowCol[0]; // 行番号・・・・ 288 colNo = rowCol[1]; // カラム番号 289 290 // 6.2.0.0 (2015/02/27) 日付型の処理 291 nextDataType = XSSFDataType.NUMBER; 292 cellStyleStr = attributes.getValue("s"); 293 // fmtIdx = -1; 294 // fmtStr = null; 295 296 final String cellType = attributes.getValue("t"); 297 if( "b".equals(cellType) ) { nextDataType = XSSFDataType.BOOL; } 298 else if( "e".equals(cellType) ) { nextDataType = XSSFDataType.ERROR; } 299 else if( "inlineStr".equals(cellType) ) { nextDataType = XSSFDataType.INLINESTR; } 300 else if( "s".equals(cellType) ) { nextDataType = XSSFDataType.SSTINDEX; } 301 else if( "str".equals(cellType) ) { nextDataType = XSSFDataType.FORMULA; } 302 } 303 lastContents = ""; // なんでもクリアしておかないと、関数文字列を拾ってしまう。 304 } 305 306 /** 307 * 要素の終了通知を受け取ります。 308 * 309 * インタフェース ContentHandler 内の endElement メソッドをオーバーライドしています。 310 * SAX パーサは、XML 文書内の各要素の終わりにこのメソッドを呼び出します。 311 * 各 endElement イベントには対応する startElement イベントがあります。 312 * これは、要素が空である場合も変わりません。 313 * 314 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 315 * @og.rev 6.2.0.0 (2015/02/27) 日付型の処理(DATE=0,DATETIME=1,TIME=2) 316 * @og.rev 6.5.0.1 (2016/10/21) ex.toString() の代わりに、ThrowUtil#ogThrowMsg(String,Throwable) を使います。 317 * @og.rev 6.8.2.4 (2017/11/20) POIで作成したEXCEL(XLSX)は、文字列を、inlineStr で持っている為、取り出し方が特殊になります。 318 * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] SharedStringsTableのgetEntryAt(int)は推奨されません (POI4.0.0) 319 * @og.rev 8.5.5.1 (2024/02/29) switch式の使用 320 * 321 * @param namespace 名前空間 URI 322 * @param localName 前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列 323 * @param qname 前置修飾子を持つ XML 1.0 修飾名。修飾名を使用できない場合は空文字列 324 * @see org.xml.sax.helpers.DefaultHandler#endElement(String , String , String ) 325 */ 326 @Override // org.xml.sax.ContentHandler 327 public void endElement( final String namespace, final String localName, final String qname ) { 328 isRowSkip = helper.isSkip( rowNo ); // イベント 329 330 if( isRowSkip ) { return ; } 331 332 String thisStr = null; 333 334 // v は、値なので、空の場合は、イベントが発生しない。 335 if( "v".equals(qname) ) { // v の時の値出力を行う。 336 // Process the last contents as required. 337 // Do now, as characters() may be called more than once 338 // 8.5.5.1 (2024/02/29) switch式の使用 339// switch( nextDataType ) { 340// case BOOL: 341// // 6.3.9.0 (2015/11/06) ゼロ文字列のチェックを追加 342// thisStr = lastContents.isEmpty() || lastContents.charAt(0) == '0' ? "FALSE" : "TRUE"; 343// break; 344// 345// case ERROR: 346// thisStr = "\"ERROR:" + lastContents + '"'; 347// break; 348// 349// case FORMULA: 350// // A formula could result in a string value, 351// // so always add double-quote characters. 352// thisStr = '"' + lastContents + '"'; 353// break; 354// 355// case INLINESTR: 356// // TODO: have seen an example of this, so it's untested. 357// thisStr = new XSSFRichTextString( lastContents ).toString(); 358// break; 359// 360// case SSTINDEX: 361// final String sstIndex = lastContents; 362// try { 363// final int idx = Integer.parseInt( sstIndex ); 364//// thisStr = new XSSFRichTextString( sst.getEntryAt(idx) ).toString(); 365// thisStr = sst.getItemAt(idx).getString(); // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated. 366// } 367// catch( final NumberFormatException ex ) { 368// final String errMsg = ThrowUtil.ogThrowMsg( "Failed to parse SST index [" + sstIndex + "]: ",ex ) ; 369// System.out.println( errMsg ); 370// } 371// break; 372// 373// case NUMBER: 374// thisStr = format.getNumberValue( cellStyleStr,lastContents ); 375// break; 376// 377// default: 378// thisStr = "(TODO: Unexpected type: " + nextDataType + ")"; 379// break; 380// } 381 thisStr = switch( nextDataType ) { 382 // 6.3.9.0 (2015/11/06) ゼロ文字列のチェックを追加 383 case BOOL -> lastContents.isEmpty() || lastContents.charAt(0) == '0' ? "FALSE" : "TRUE"; 384 case ERROR -> "\"ERROR:" + lastContents + '"'; 385 // A formula could result in a string value, 386 // so always add double-quote characters. 387 case FORMULA -> '"' + lastContents + '"'; 388 // TODO: have seen an example of this, so it's untested. 389 case INLINESTR -> new XSSFRichTextString( lastContents ).toString(); 390 case SSTINDEX -> { 391 final String sstIndex = lastContents; 392 String tmpVal = null; 393 try { 394 final int idx = Integer.parseInt( sstIndex ); 395// thisStr = new XSSFRichTextString( sst.getEntryAt(idx) ).toString(); 396 tmpVal = sst.getItemAt(idx).getString(); // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated. 397 } 398 catch( final NumberFormatException ex ) { 399 final String errMsg = ThrowUtil.ogThrowMsg( "Failed to parse SST index [" + sstIndex + "]: ",ex ) ; 400 System.out.println( errMsg ); 401 } 402 yield tmpVal; 403 } 404 case NUMBER -> format.getNumberValue( cellStyleStr,lastContents ); 405 default -> "(TODO: Unexpected type: " + nextDataType + ")"; 406 }; 407 } 408 // 6.8.2.4 (2017/11/20) POIで作成したEXCEL(XLSX)は、文字列を、inlineStr で持っている為、取り出し方が特殊になります。 409 else if( "t".equals(qname) && nextDataType == XSSFDataType.INLINESTR ) { // t で、INLINESTR の時 410 // TODO: have seen an example of this, so it's untested. 411 thisStr = new XSSFRichTextString( lastContents ).toString(); 412 } 413 414 if( thisStr != null ) { 415 // v => contents of a cell 416 // Output after we've seen the string contents 417 // 文字列(値) 行 列 418 419 helper.value( thisStr, rowNo , colNo ); 420 } 421 } 422 423 /** 424 * 要素内の文字データの通知を受け取ります。 425 * 426 * インタフェース ContentHandler 内の characters メソッドをオーバーライドしています。 427 * 各文字データチャンクに対して特殊なアクション (ノードまたはバッファへのデータの追加、 428 * データのファイルへの出力など) を実行することができます。 429 * 430 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 431 * @og.rev 6.4.1.2 (2016/01/22) void で 途中で、return しているが、難しいロジックでないので、統合する。 432 * 433 * @param buffer 文字データ配列 434 * @param start 配列内の開始位置 435 * @param length 配列から読み取られる文字数 436 * @see org.xml.sax.helpers.DefaultHandler#characters(char[] , int , int ) 437 */ 438 @Override // org.xml.sax.ContentHandler 439 public void characters( final char[] buffer, final int start, final int length ) { 440 if( !isRowSkip ) { 441 lastContents += new String( buffer, start, length ); // StringBuilder#append より速かった。 442 } 443 } 444 } 445 446 /** 447 * シート一覧を、XSSFReader から取得します。 448 * 449 * 取得元が、XSSFReader なので、xlsx 形式のみの対応です。 450 * 汎用的なメソッドではなく、大きな xlsx ファイルは、通常の DOM処理すると、 451 * 大量のメモリを消費する為、イベントモデルで処理する場合に、使います。 452 * 453 * EXCEL上のシート名を、配列で返します。 454 * 455 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 456 * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応 457 * 458 * @param rd XSSFReaderオブジェクト 459 * @param parser XMLReaderオブジェクト 460 * @return シート名とシートIDを持つオブジェクトのリスト 461 * @throws SAXException SAX の一般的なエラーが発生 462 * @throws IOException SAXパース処理時のI/Oエラー 463 * @throws InvalidFormatException よみとったEXCEL ファイルのフォーマットが異なる。 464 */ 465 public static List<SheetObj> getSheetList( final XSSFReader rd, final XMLReader parser ) 466 throws SAXException,IOException,InvalidFormatException { 467 final List<SheetObj> shtList = new ArrayList<>(); 468 469 parser.setContentHandler( 470 new DefaultHandler() { 471 /** 472 * 要素の開始通知を受け取ります。 473 * 474 * @param uri 名前空間 URI 475 * @param localName 前置修飾子を含まないローカル名。名前空間処理が行われない場合は空文字列 476 * @param name 前置修飾子を持つ修飾名。修飾名を使用できない場合は空文字列 477 * @param attributes 要素に付加された属性。属性が存在しない場合、空の Attributesオブジェクト 478 * @see org.xml.sax.helpers.DefaultHandler#startElement(String , String , String , Attributes ) 479 */ 480 @Override // org.xml.sax.ContentHandler 481 public void startElement( final String uri, final String localName, final String name, final Attributes attributes) { 482 if( "sheet".equals(name) ) { 483 final String shtNm = attributes.getValue("name"); // シート名 484 final String shtId = attributes.getValue("r:id"); // シートID( rId# #は、1から始まる ) 485 shtList.add( new SheetObj( shtNm,shtId ) ); 486 } 487 } 488 } 489 ); 490 491 // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応 492// InputStream workbk = null; 493// try { 494// workbk = rd.getWorkbookData(); // IOException,InvalidFormatException 495 try ( InputStream workbk = rd.getWorkbookData() ) { // IOException,InvalidFormatException 496 parser.parse( new InputSource( workbk ) ); // IOException,SAXException 497 } 498// finally { 499// Closer.ioClose( workbk ); 500// } 501 502 return shtList; 503 } 504 505 /** 506 * シート名とシートIDを持つオブジェクトのインナークラス 507 * 508 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善 509 */ 510 private static final class SheetObj { 511 private final String name; 512 private final String rid ; 513 514 /** 515 * シート名とシートIDを引数に取るコンストラクター 516 * 517 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善 518 * 519 * @param name シート名 520 * @param rid シートID(rId# #は、1から始まる番号) 521 */ 522 public SheetObj( final String name , final String rid ) { 523 this.name = name; 524 this.rid = rid; 525 } 526 527 /** 528 * シート名を返します。 529 * 530 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善 531 * 532 * @return シート名 533 */ 534 public String getName() { return name ; } 535 536 /** 537 * シートIDを返します。 538 * 539 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善 540 * 541 * @return シートID(rId# #は、1から始まる番号) 542 */ 543 public String getRid() { return rid ; } 544 } 545 546 /** 547 * アプリケーションのサンプルです。 548 * 549 * 入力ファイル名 は必須で、第一引数固定です。 550 * 551 * Usage: java org.opengion.fukurou.model.EventReader_XLSX 入力ファイル名 552 * 553 * @og.rev 6.0.3.0 (2014/11/13) 新規作成 554 * @og.rev 6.2.0.0 (2015/02/27) staticメソッドをインスタンスメソッドに変更 555 * 556 * @param args コマンド引数配列 557 */ 558 public static void main( final String[] args ) { 559 final String usageMsg = "Usage: java org.opengion.fukurou.model.EventReader_XLSX 入力ファイル名" ; 560 if( args.length == 0 ) { 561 System.err.println( usageMsg ); 562 return ; 563 } 564 565 final File file = new File( args[0] ); 566 final EventReader reader = new EventReader_XLSX(); 567 568 reader.eventReader( // 6.2.0.0 (2015/02/27) 569 file, 570 new TableModelHelper() { 571 /** 572 * シートの読み取り開始時にイベントが発生します。 573 * 574 * @param shtNm シート名 575 * @param shtNo シート番号(0~) 576 * @return true:シートの読み取り処理を継続します/false:このシートは読み取りません。 577 */ 578 public boolean startSheet( final String shtNm,final int shtNo ) { 579 System.out.println( "S[" + shtNo + "]=" + shtNm ); 580 return super.startSheet( shtNm,shtNo ); 581 } 582 583 // public void columnNames( final String[] names ) { 584 // System.out.println( "NM=" + java.util.Arrays.toString( names ) ); 585 // } 586 587 // public void values( final String[] vals,final int rowNo ) { 588 // System.out.println( "V[" + rowNo + "]=" + java.util.Arrays.toString( vals ) ); 589 // } 590 591 // public boolean isSkip( final int rowNo ) { 592 // super.isSkip( rowNo ); 593 // return false; 594 // } 595 596 /** 597 * 読み取り状態の時に、rowNo,colNo にあるセルの値を引数にイベントが発生します。 598 * 599 * @param val 文字列値 600 * @param rowNo 行番号(0~) 601 * @param colNo 列番号(0~) 602 * @return 読み取りするかどうか(true:読み取りする/false:読み取りしない) 603 */ 604 public boolean value( final String val,final int rowNo,final int colNo ) { 605 final String kigo = POIUtil.getCelKigo( rowNo,colNo ); 606 System.out.println( "R[" + rowNo + "],C[" + colNo + "](" + kigo + ")=" + val ); 607 return super.value( val,rowNo,colNo ); 608 } 609 } 610 ); 611 } 612}