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