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