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