    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.fukurou.model;
    017
    018import java.io.InputStream;
    019import java.io.FileInputStream;
    020import java.io.BufferedReader;                                                                                                  // 6.2.2.0 (2015/03/27)
    021import java.io.BufferedInputStream;
    022import java.io.FileNotFoundException;
    023import java.io.File;
    024import java.io.IOException;
    025import java.nio.file.Files;                                                                                                             // 6.2.2.0 (2015/03/27)
    026import java.nio.charset.Charset;                                                                                                // 6.2.2.0 (2015/03/27)
    027
    028import java.util.Set;                                                                                                                   // 6.0.2.3 (2014/10/10)
    029import java.util.TreeSet;                                                                                                               // 6.0.2.3 (2014/10/10)
    030import java.util.List;                                                                                                                  // 6.4.6.0 (2016/05/27) poi-3.15
    031
    032// import org.apache.xmlbeans.XmlException;                                                                             // 8.0.0.0 (2021/07/31) Delete
    033// import org.apache.poi.POITextExtractor;
    034import org.apache.poi.extractor.POITextExtractor;                                                               // 7.0.0.0 (2018/10/01) poi-3.17.jar → poi-4.0.0.jar
    035// import org.apache.poi.extractor.ExtractorFactory;
    036// import org.apache.poi.ooxml.extractor.ExtractorFactory;                                              // 7.0.0.0 (2018/10/01) poi-ooxml-3.17.jar → poi-ooxml-4.0.0.jar
    037import org.apache.poi.ooxml.extractor.POIXMLExtractorFactory;                                   // 8.0.0.0 (2021/07/31) poi-ooxml-4.0.0.jar → poi-ooxml-5.0.0.jar
    038import org.apache.poi.hwpf.HWPFDocument;
    039import org.apache.poi.hwpf.usermodel.Range;
    040import org.apache.poi.hwpf.usermodel.Paragraph;
    041import org.apache.poi.xwpf.usermodel.XWPFDocument;                                                              // 6.2.0.0 (2015/02/27)
    042import org.apache.poi.xwpf.usermodel.XWPFParagraph;                                                             // 6.2.0.0 (2015/02/27)
    043import org.apache.poi.hssf.usermodel.HSSFCellStyle;
    044import org.apache.poi.hslf.usermodel.HSLFTextParagraph;                                                 // 6.4.6.0 (2016/05/27) poi-3.15
    045import org.apache.poi.hslf.usermodel.HSLFSlide;                                                                 // 6.4.6.0 (2016/05/27) poi-3.15
    046import org.apache.poi.hslf.usermodel.HSLFSlideShow;                                                             // 6.4.6.0 (2016/05/27) poi-3.15
    047
    048import org.apache.poi.xslf.usermodel.XMLSlideShow;                                                              // 6.2.0.0 (2015/02/27)
    049// import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor;                                // 7.0.0.0 (2018/10/01) POI4.0.0 deprecation
    050import org.apache.poi.sl.extractor.SlideShowExtractor;                                                  // 7.0.0.0 (2018/10/01) POI4.0.0 XSLFPowerPointExtractor → SlideShowExtractor
    051import org.apache.poi.xslf.usermodel.XSLFShape;                                                                 // 7.0.0.0 (2018/10/01) POI4.0.0 XSLFPowerPointExtractor → SlideShowExtractor
    052import org.apache.poi.xslf.usermodel.XSLFTextParagraph;                                                 // 7.0.0.0 (2018/10/01) POI4.0.0 XSLFPowerPointExtractor → SlideShowExtractor
    053
    054// import org.apache.poi.openxml4j.exceptions.InvalidFormatException;                   // 8.0.0.0 (2021/07/31) Delete
    055// import org.apache.poi.openxml4j.exceptions.OpenXML4JException ;                              // 6.1.0.0 (2014/12/26) findBugs  // 8.0.0.0 (2021/07/31) Delete
    056import org.apache.poi.ss.usermodel.WorkbookFactory;
    057import org.apache.poi.ss.usermodel.Workbook;
    058import org.apache.poi.ss.usermodel.Sheet;
    059import org.apache.poi.ss.usermodel.Row;
    060import org.apache.poi.ss.usermodel.Cell;
    061import org.apache.poi.ss.usermodel.CellStyle;
    062import org.apache.poi.ss.usermodel.CreationHelper;
    063import org.apache.poi.ss.usermodel.RichTextString;
    064import org.apache.poi.ss.usermodel.DateUtil;
    065import org.apache.poi.ss.usermodel.FormulaEvaluator;
    066import org.apache.poi.ss.usermodel.Name;                                                                                // 6.0.2.3 (2014/10/10)
    067import org.apache.poi.ss.usermodel.CellType;                                                                    // 6.5.0.0 (2016/09/30) poi-3.15
    068import org.apache.poi.ss.util.SheetUtil;
    069
    070import org.opengion.fukurou.system.OgRuntimeException ;                                                 // 6.4.2.0 (2016/01/29)
    071import org.opengion.fukurou.util.FileInfo;                                                                              // 6.2.3.0 (2015/05/01)
    072import org.opengion.fukurou.system.ThrowUtil;                                                                   // 6.4.2.0 (2016/01/29)
    073import org.opengion.fukurou.system.Closer;                                                                              // 6.2.0.0 (2015/02/27)
    074import static org.opengion.fukurou.system.HybsConst.CR;                                                 // 6.1.0.0 (2014/12/26) refactoring
    075import static org.opengion.fukurou.system.HybsConst. BUFFER_MIDDLE ;                    // 6.4.2.1 (2016/02/05) refactoring
    076
    077/**
    078 * POI による、Excel/Word/PoworPoint等に対する、ユーティリティクラスです。
    079 *
    080 * 基本的には、ネイティブファイルを読み取り、テキストを取得する機能が主です。
    081 * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。
    082 *
    083 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    084 * @og.rev 6.2.0.0 (2015/02/27) パッケージ変更(util → model)
    085 * @og.group その他
    086 *
    087 * @version  6.0
    088 * @author   Kazuhiko Hasegawa
    089 * @since    JDK7.0,
    090 */
    091public final class POIUtil {
    092        /** このプログラムのVERSION文字列を設定します。   {@value} */
    093        private static final String VERSION = "8.0.0.0 (2021/07/31)" ;
    094
    095        // 6.2.3.0 (2015/05/01)
    096        /** 対象サフィックス {@value} */
    097        public static final String POI_SUFIX = "ppt,pptx,doc,docx,xls,xlsx,xlsm" ;
    098
    099        /**
    100         * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
    101         *
    102         */
    103        private POIUtil() {}
    104
    105        /**
    106         * 引数ファイルが、POI関連の拡張子ファイルかどうかを判定します。
    107         *
    108         * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。
    109         * 拡張子が、ppt,pptx,doc,docx,xls,xlsx,xlsm のファイルの場合、true を返します。
    110         *
    111         * @og.rev 6.2.3.0 (2015/05/01) POI関連の拡張子ファイルかどうかを判定
    112         *
    113         * @param       file 判定するファイル
    114         * @return      POI関連の拡張子の場合、true
    115         */
    116        public static boolean isPOI( final File file ) {
    117                return POI_SUFIX.contains( FileInfo.getSUFIX( file ) );
    118        }
    119
    120        /**
    121         * 引数ファイルを、POITextExtractor を使用してテキスト化します。
    122         *
    123         * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。
    124         * 拡張子から、ファイルの種類を自動判別します。
    125         *
    126         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    127         * @og.rev 6.2.0.0 (2015/02/27) getText → extractor に変更
    128         * @og.rev 8.0.0.0 (2021/07/31) ExtractorFactory → POIXMLExtractorFactory に変更
    129         *
    130         * @param       file 入力ファイル名
    131         * @return      変換後のテキスト
    132         * @og.rtnNotNull
    133         */
    134        public static String extractor( final File file ) {
    135        //      InputStream fis = null;
    136                POITextExtractor extractor = null;
    137                try {
    138        //              fis = new BufferedInputStream( new FileInputStream( file ) );
    139        //              extractor = ExtractorFactory.createExtractor( fis );
    140        //              extractor = ExtractorFactory.createExtractor( file );
    141                        extractor = new POIXMLExtractorFactory().create( file , null );         // 8.0.0.0 (2021/07/31) poi-ooxml-4.0.0.jar → poi-ooxml-5.0.0.jar
    142                        return extractor.getText();
    143                }
    144                catch( final FileNotFoundException ex ) {
    145                        final String errMsg = "ファイルが存在しません[" + file + "]" + CR + ex.getMessage() ;
    146                        throw new OgRuntimeException( errMsg,ex );
    147                }
    148                catch( final IOException ex ) {
    149                        final String errMsg = "ファイル処理エラー[" + file + "]" + CR + ex.getMessage() ;
    150                        throw new OgRuntimeException( errMsg,ex );
    151                }
    152                // 8.0.0.0 (2021/07/31) poi-ooxml-4.0.0.jar → poi-ooxml-5.0.0.jar
    153//              catch( final InvalidFormatException ex ) {
    154//                      final String errMsg = "ファイルフォーマットエラー[" + file + "]" + CR + ex.getMessage() ;
    155//                      throw new OgRuntimeException( errMsg,ex );
    156//              }
    157//              catch( final OpenXML4JException ex ) {
    158//                      final String errMsg = "ODF-XML処理エラー[" + file + "]" + CR + ex.getMessage() ;
    159//                      throw new OgRuntimeException( errMsg,ex );
    160//              }
    161//              catch( final XmlException ex ) {
    162//                      final String errMsg = "XML処理エラー[" + file + "]" + CR + ex.getMessage() ;
    163//                      throw new OgRuntimeException( errMsg,ex );
    164//              }
    165                finally {
    166                        Closer.ioClose( extractor );
    167        //              Closer.ioClose( fis );
    168                }
    169        }
    170
    171        /**
    172         * 引数ファイル(Text)を、テキスト化します。
    173         *
    174         * ここでは、ファイルとエンコードを指定して、ファイルのテキスト全てを読み取ります。
    175         *
    176         * @og.rev 6.2.2.0 (2015/03/27) 引数ファイル(Text)を、テキスト化。
    177         * @og.rev 6.2.3.0 (2015/05/01) textReader → extractor に変更
    178         *
    179         * @param       file 入力ファイル
    180         * @param       encode エンコード名
    181         * @return      ファイルのテキスト
    182         */
    183        public static String extractor( final File file , final String encode ) {
    184                try {
    185                        // 指定のファイルをバイト列として読み込む
    186                        final byte[] bytes = Files.readAllBytes( file.toPath() );
    187                        // 読み込んだバイト列を エンコードして文字列にする
    188                        return new String( bytes, encode );
    189                }
    190        //      catch( final UnsupportedEncodingException ex ) {
    191        //              final String errMsg = "エンコードが不正です[" + file + "] , ENCODE=[" + encode + "]"  ;
    192        //              throw new OgRuntimeException( errMsg,ex );
    193        //      }
    194                catch( final IOException ex ) {
    195                        final String errMsg = "ファイル読込みエラー[" + file + "] , ENCODE=[" + encode + "]"  ;
    196                        throw new OgRuntimeException( errMsg,ex );
    197                }
    198        }
    199
    200        /**
    201         * 引数ファイル(Text)を、テキスト化します。
    202         *
    203         * ここでは、ファイルとエンコードを指定して、ファイルのテキスト全てを読み取ります。
    204         *
    205         * @og.rev 6.2.2.0 (2015/03/27) 引数ファイル(Text)を、テキスト化。
    206         *
    207         * @param       file 入力ファイル
    208         * @param       conv   イベント処理させるI/F
    209         * @param       encode エンコード名
    210         */
    211        public static void textReader( final File file , final TextConverter<String,String> conv , final String encode ) {
    212                BufferedReader reader = null ;
    213
    214                int rowNo = 0;          // 6.2.0.0 (2015/02/27) 行番号
    215                try {
    216                        reader = Files.newBufferedReader( file.toPath() , Charset.forName( encode ) );
    217
    218                        String line ;
    219                        while((line = reader.readLine()) != null) {
    220                                conv.change( line,String.valueOf( rowNo++ ) );
    221                        }
    222                }
    223                catch( final IOException ex ) {
    224                        final String errMsg = "ファイル読込みエラー[" + file + "] , ENCODE=[" + encode + "] , ROW=[" + rowNo + "]"  ;
    225                        throw new OgRuntimeException( errMsg,ex );
    226                }
    227                finally {
    228                        Closer.ioClose( reader );
    229                }
    230        }
    231
    232        /**
    233         * 引数ファイル(Word,PoworPoint,Excel)を、TableModelHelper を使用してテキスト化します。
    234         *
    235         * ここでは、ファイル名の拡張子で、処理するメソッドを選別します。
    236         * 拡張子が、対象かどうかは、#isPOI( File ) メソッドで判定できます。
    237         *
    238         * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ
    239         * 表形式オブジェクトの形で処理されます。
    240         * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、
    241         * スキップされます。
    242         *
    243         * @og.rev 6.2.3.0 (2015/05/01) 新規作成
    244         * @og.rev 6.2.5.0 (2015/06/05) xls,xlsxは、それぞれ excelReader1,excelReader2 で処理します。
    245         *
    246         * @param       file 入力ファイル
    247         * @param       conv   イベント処理させるI/F
    248         */
    249        public static void textReader( final File file , final TextConverter<String,String> conv ) {
    250                final String SUFIX = FileInfo.getSUFIX( file );
    251
    252                if( "doc".equalsIgnoreCase( SUFIX ) ) {
    253                        wordReader1( file,conv );
    254                }
    255                else if( "docx".equalsIgnoreCase( SUFIX ) ) {
    256                        wordReader2( file,conv );
    257                }
    258                else if( "ppt".equalsIgnoreCase( SUFIX ) ) {
    259                        pptReader1( file,conv );
    260                }
    261                else if( "pptx".equalsIgnoreCase( SUFIX ) ) {
    262                        pptReader2( file,conv );
    263                }
    264                else if( "xls".equalsIgnoreCase( SUFIX ) ) {
    265                        excelReader1( file,conv );                                                              // 6.2.5.0 (2015/06/05)
    266                }
    267                else if( "xlsx".equalsIgnoreCase( SUFIX ) || "xlsm".equalsIgnoreCase( SUFIX ) ) {
    268                        excelReader2( file,conv );                                                              // 6.2.5.0 (2015/06/05)
    269                }
    270                else {
    271                        final String errMsg = "拡張子は、" +  POI_SUFIX + " にしてください。[" + file + "]" ;
    272                        throw new OgRuntimeException( errMsg );
    273                }
    274        }
    275
    276        /**
    277         * 引数ファイル(Word)を、HWPFDocument を使用してテキスト化します。
    278         *
    279         * 拡張子(.doc)のファイルを処理します。
    280         * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ
    281         * 表形式オブジェクトの形で処理されます。
    282         * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、
    283         * スキップされます。
    284         *
    285         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    286         * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更
    287         * @og.rev 6.2.4.2 (2015/05/29) 改行以外に、「。」で分割します。
    288         *
    289         * @param       file 入力ファイル名
    290         * @param       conv   イベント処理させるI/F
    291         */
    292        private static void wordReader1( final File file , final TextConverter<String,String> conv ) {
    293                InputStream fis  = null;
    294
    295                try {
    296                        // 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正
    297
    298                        fis = new BufferedInputStream( new FileInputStream( file ) );           // 6.2.0.0 (2015/02/27)
    299                        final HWPFDocument doc = new HWPFDocument( fis );
    300
    301                        int rowNo = 0;          // 6.2.0.0 (2015/02/27) 行番号
    302
    303        //              // WordExtractor を使ったサンプル
    304        //              WordExtractor we = new WordExtractor( doc );
    305        //              for( String txt : we.getParagraphText() ) {
    306        //                      String text = WordExtractor.stripFields( txt )
    307        //                                                      .replaceAll( "\\x13[^\\x01]+\\x01\\x14" , "" )
    308        //                                                      .replaceAll( "\\x0b" , "\n" ).trim();
    309        //                      helper.value( text.trim(),rowNo++,0 );                          // 6.2.0.0 (2015/02/27) イベント変更
    310        //              }
    311
    312                        // Range,Paragraph を使ったサンプル
    313                        final Range rng = doc.getRange();
    314                        for( int pno=0; pno<rng.numParagraphs(); pno++ ) {
    315                                final Paragraph para = rng.getParagraph(pno);
    316                                final String text = Range.stripFields( para.text() )
    317                                                                .replaceAll( "\\x13[^\\x01]+\\x01\\x14" , "" )
    318                                                                .replaceAll( "\\x0b" , "\n" ).trim();
    319                                conv.change( text, String.valueOf( rowNo++ ) );
    320                        }
    321
    322                        // Range,Paragraph,CharacterRun を使ったサンプル(変な個所で文字が分断される)
    323        //              final Range rng = doc.getRange();
    324        //              for( int pno = 0; pno < rng.numParagraphs(); pno++ ) {
    325        //                      final Paragraph para = rng.getParagraph(pno);
    326        //                      for( int cno = 0; cno < para.numCharacterRuns(); cno++ ) {
    327        //                              final CharacterRun crun = para.getCharacterRun(cno);
    328        //                              String text = Range.stripFields( crun.text() )
    329        //                                                              .replaceAll( "\\x13[^\\x01]+\\x01\\x14" , "" )
    330        //                                                              .replaceAll( "\\x0b" , "\n" ).trim();
    331        //                              helper.value( text,rowNo++,0 );                         // 6.2.0.0 (2015/02/27) イベント変更
    332        //                      }
    333        //              }
    334                }
    335                catch( final IOException ex ) {
    336                        final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ;
    337                        throw new OgRuntimeException( errMsg,ex );
    338                }
    339                finally {
    340                        Closer.ioClose( fis );
    341                }
    342        }
    343
    344        /**
    345         * 引数ファイル(Word)を、XWPFDocument を使用してテキスト化します。
    346         *
    347         * 拡張子(.docx)のファイルを処理します。
    348         * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ
    349         * 表形式オブジェクトの形で処理されます。
    350         * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、
    351         * スキップされます。
    352         *
    353         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    354         * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更
    355         * @og.rev 6.2.4.2 (2015/05/29) 改行以外に、「。」で分割します。
    356         *
    357         * @param       file 入力ファイル
    358         * @param       conv   イベント処理させるI/F
    359         */
    360        private static void wordReader2( final File file , final TextConverter<String,String> conv ) {
    361                InputStream fis  = null;
    362
    363                try {
    364                        // 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正
    365
    366                        fis = new BufferedInputStream( new FileInputStream( file ) );           // 6.2.0.0 (2015/02/27)
    367                        final XWPFDocument doc = new XWPFDocument( fis );
    368
    369                        int rowNo = 0;          // 6.2.0.0 (2015/02/27) 行番号
    370                        for( final XWPFParagraph para : doc.getParagraphs() ) {
    371        //                      for( final XWPFRun run : para.getRuns() ) {
    372        //                              helper.value( run.toString(),rowNo++,0 );                               // 6.2.0.0 (2015/02/27) イベント変更
    373        //                      }
    374                                final String text = para.getParagraphText().trim();
    375                                conv.change( text, String.valueOf( rowNo++ ) );
    376                        }
    377                }
    378                catch( final IOException ex ) {
    379                        final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ;
    380                        throw new OgRuntimeException( errMsg,ex );
    381                }
    382                finally {
    383                        Closer.ioClose( fis );
    384                }
    385        }
    386
    387        /**
    388         * 引数ファイル(PoworPoint)を、HSLFSlideShow を使用してテキスト化します。
    389         *
    390         * 拡張子(.ppt)のファイルを処理します。
    391         * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ
    392         * 表形式オブジェクトの形で処理されます。
    393         * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、
    394         * スキップされます。
    395         *
    396         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    397         * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更
    398         * @og.rev 6.4.6.0 (2016/05/27) poi-3.15 準備
    399         *
    400         * @param       file 入力ファイル
    401         * @param       conv   イベント処理させるI/F
    402         */
    403        private static void pptReader1( final File file , final TextConverter<String,String> conv ) {
    404                InputStream fis  = null;
    405
    406                try {
    407                        // 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正
    408
    409                        fis = new BufferedInputStream( new FileInputStream( file ) );           // 6.2.0.0 (2015/02/27)
    410
    411        //              6.4.6.0 (2016/05/27) poi-3.15
    412                        final HSLFSlideShow ss = new HSLFSlideShow( fis );
    413                        final List<HSLFSlide> slides = ss.getSlides();                                          // 6.4.6.0 (2016/05/27) poi-3.15
    414                        int rowNo = 0;          // 6.2.0.0 (2015/02/27) 行番号
    415                        for( final HSLFSlide  slide : slides ) {                                                        // 6.4.6.0 (2016/05/27) poi-3.15
    416                                for( final List<HSLFTextParagraph> txtList : slide.getTextParagraphs() ) {      // 6.4.6.0 (2016/05/27) poi-3.15
    417                                        final String text = HSLFTextParagraph.getText( txtList );
    418                                        if( text.length() > 0 ) {
    419                                                conv.change( text, String.valueOf( rowNo++ ) );
    420                                        }
    421                                }
    422                        }
    423
    424        //              6.4.6.0 (2016/05/27) poi-3.12
    425        //              final SlideShow ss = new SlideShow( new HSLFSlideShow( fis ) );
    426        //              final Slide[] slides = ss.getSlides();
    427        //              int rowNo = 0;          // 6.2.0.0 (2015/02/27) 行番号
    428        //              for( int sno=0; sno<slides.length; sno++ ) {
    429        //                      final TextRun[] textRun = slides[sno].getTextRuns();
    430        //                      for( int tno=0; tno<textRun.length; tno++ ) {
    431        //                              final String text = textRun[tno].getText();
    432        //                              // データとして設定されているレコードのみイベントを発生させる。
    433        //                              if( text.length() > 0 ) {
    434        //                                      conv.change( text, String.valueOf( rowNo++ ) );
    435        //                              }
    436        //                      }
    437        //              }
    438                }
    439                catch( final IOException ex ) {
    440                        final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ;
    441                        throw new OgRuntimeException( errMsg,ex );
    442                }
    443                finally {
    444                        Closer.ioClose( fis );
    445                }
    446        }
    447
    448        /**
    449         * 引数ファイル(PoworPoint)を、XMLSlideShow を使用してテキスト化します。
    450         *
    451         * 拡張子(.pptx)のファイルを処理します。
    452         * TableModelHelper によるイベント処理できますが、TEXTというカラム名を持つ
    453         * 表形式オブジェクトの形で処理されます。
    454         * また、内部的に、先頭に、# がある場合や、行データが存在しない場合は、
    455         * スキップされます。
    456         *
    457         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    458         * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更
    459         * @og.rev 6.2.4.2 (2015/05/29) 行単位に、取り込むようにします。
    460         * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] org.apache.poi.xslf.extractorのXSLFPowerPointExtractorは推奨されません (POI4.0.0)
    461         *
    462         * @param       file 入力ファイル
    463         * @param       conv   イベント処理させるI/F
    464         */
    465        private static void pptReader2( final File file , final TextConverter<String,String> conv ) {
    466                InputStream fis  = null;
    467
    468                try {
    469                        // 6.2.0.0 (2015/02/27) TableModelEvent 変更に伴う修正
    470
    471                        fis = new BufferedInputStream( new FileInputStream( file ) );           // 6.2.0.0 (2015/02/27)
    472                        final XMLSlideShow ss = new XMLSlideShow( fis );
    473//                      final XSLFPowerPointExtractor ext = new XSLFPowerPointExtractor( ss );
    474                        final SlideShowExtractor<XSLFShape,XSLFTextParagraph> ext = new SlideShowExtractor<>( ss );             // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated.
    475                        final String[] vals = ext.getText().split( "\\n" );             // 6.2.4.2 (2015/05/29) 行単位に、取り込むようにします。
    476                        for( int row=0; row<vals.length; row++ ) {
    477                                conv.change( vals[row], String.valueOf( row ) );
    478                        }
    479
    480        //              final XSLFSlide[] slides = ss.getSlides();
    481        //              final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    482        //              int rowNo = 0;          // 6.2.0.0 (2015/02/27) 行番号
    483        //              for( int sno = 0; sno < slides.length; sno++ ) {
    484        //                      buf.setLength(0);               // Clearの事
    485    //
    486        //      //              final XSLFTextShape[] shp = slides[sno].getPlaceholders();
    487        //                      final XSLFShape[] shp = slides[sno].getShapes();
    488        //                      for( int tno = 0; tno < shp.length; tno++ ) {
    489        //      //                      buf.append( shp[tno].getText() );
    490        //                              buf.append( shp[tno].toString() );
    491        //                      }
    492        //      //              String text = buf.toString().trim();
    493        //      //              event.value( text,rowNo++,0 );                                  // 6.2.0.0 (2015/02/27) イベント変更
    494        //                      helper.value( buf.toString(),rowNo++,0 );               // 6.2.4.2 (2015/05/29) trim() しません。
    495        //              }
    496                }
    497                catch( final IOException ex ) {
    498                        final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ;
    499                        throw new OgRuntimeException( errMsg,ex );
    500                }
    501                finally {
    502                        Closer.ioClose( fis );
    503                }
    504        }
    505
    506        /**
    507         * 引数ファイル(Excel)を、テキスト化します。
    508         *
    509         * TableModelHelper を与えることで、EXCELデータをテキスト化できます。
    510         * ここでは、HSSF(.xls)形式を処理します。
    511         * シート名、セル、テキストオブジェクトをテキスト化します。
    512         *
    513         * @og.rev 6.2.5.0 (2015/06/05) 新規作成
    514         *
    515         * @param       file 入力ファイル
    516         * @param       conv   イベント処理させるI/F
    517         * @see         org.opengion.fukurou.model.ExcelModel
    518         */
    519        public static void excelReader1( final File file , final TextConverter<String,String> conv ) {
    520                excelReader2( file , conv );
    521        }
    522
    523        /**
    524         * 引数ファイル(Excel)を、テキスト化します。
    525         *
    526         * TableModelHelper を与えることで、EXCELデータをテキスト化できます。
    527         * ここでは、ExcelModelを使用して、(.xlsx , .xlsm)形式を処理します。
    528         * シート名、セル、テキストオブジェクトをテキスト化します。
    529         *
    530         * @og.rev 6.2.5.0 (2015/06/05) 新規作成
    531         * @og.rev 6.3.1.0 (2015/06/28) TextConverterに、引数(cmnt)を追加
    532         * @og.rev 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更
    533         *
    534         * @param       file 入力ファイル
    535         * @param       conv   イベント処理させるI/F
    536         * @see         org.opengion.fukurou.model.ExcelModel
    537         */
    538        public static void excelReader2( final File file , final TextConverter<String,String> conv ) {
    539                final ExcelModel excel = new ExcelModel( file, true );
    540
    541                // 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更
    542                // textConverter を使いますが、テキストを読み込むだけで、変換しません。
    543                excel.textConverter(
    544                        ( val,cmnt ) -> {
    545                                conv.change( val,cmnt );        // 変換したくないので、引数の TextConverter を直接渡せない。
    546                                return null;                            // nullを返せば、変換しません。
    547                        }
    548                );
    549        }
    550
    551        /**
    552         * Excelの行列記号を、行番号と列番号に分解します。
    553         *
    554         * Excelの行列記号とは、A1 , B5 , AA23 などの形式を指します。
    555         * これを、行番号と列番号に分解します。例えば、A1→0行0列、B5→4行1列、AA23→22行26列 となります。
    556         * 分解した結果は、内部変数の、rowNo と colNo にセットされます。
    557         * これらは、0 から始まる int型の数字で表します。
    558         *
    559         *   ①行-列形式
    560         *     行列は、EXCELオブジェクトに準拠するため、０から始まる整数です。
    561         *     0-0 ⇒ A1 , 1-0 ⇒ A2 , 0-1 ⇒ B1 になります。
    562         *   ②EXCEL表記
    563         *     EXCEL表記に準拠した、A1,A2,B1 の記述も処理できるように対応します。
    564         *     なお、A1,A2,B1 の記述は、必ず、英字1文字＋数字 にしてください。(A～Zまで)
    565         *   ③EXCELシート名をキーに割り当てるために、"SHEET" という記号に対応します。
    566         *     rowNo = -1 をセットします。
    567         *
    568         * @og.rev 6.0.3.0 (2014/11/13) 新規作成
    569         * @og.rev 6.2.6.0 (2015/06/19) 行-列形式と、SHEET文字列判定を採用。
    570         *
    571         * @param       kigo    Excelの行列記号( A1 , B5 , AA23 など )
    572         * @return      行と列の番号を持った配列([0]=行=ROW , [1]=列=COL)
    573         * @og.rtnNotNull
    574         */
    575        public static int[] kigo2rowCol( final String kigo ) {
    576                int rowNo = 0;
    577                int colNo = -1;                                                                         // +1 して、26 かける処理をしているので、辻褄合わせ
    578
    579                // 6.2.6.0 (2015/06/19) 行-列形式と、SHEET文字列判定を採用。
    580                if( "SHEET".equalsIgnoreCase( kigo ) ) {
    581                        rowNo = -1;
    582                }
    583                else {
    584                        final int adrs = kigo.indexOf( '-' );
    585                        if( adrs > 0 ) {
    586                                rowNo = Integer.parseInt( kigo.substring( 0,adrs ) );
    587                                colNo = Integer.parseInt( kigo.substring( adrs+1 ) );
    588                        }
    589                        else {
    590                                for( int i=0; i<kigo.length(); i++ ) {
    591                                        final char ch = kigo.charAt(i);
    592                                        if( 'A' <= ch && ch <= 'Z' ) { colNo = (colNo+1)*26 + ch-'A'; }
    593                                        else {
    594                                                // アルファベットでなくなったら、残りは 行番号(ただし、-1する)
    595                                                rowNo = Integer.parseInt( kigo.substring( i ) ) -1;
    596                                                break;
    597                                        }
    598                                }
    599                        }
    600                }
    601                return new int[] { rowNo,colNo };
    602        }
    603
    604        /**
    605         * セルオブジェクト(Cell)から値を取り出します。
    606         *
    607         * セルオブジェクトが存在しない場合は、null を返します。
    608         * それ以外で、うまく値を取得できなかった場合は、ゼロ文字列を返します。
    609         *
    610         * @og.rev 3.8.5.3 (2006/08/07) 取り出し方法を少し修正
    611         * @og.rev 5.5.1.2 (2012/04/06) フォーマットセルを実行して、その結果を再帰的に処理する。
    612         * @og.rev 6.0.3.0 (2014/11/13) セルフォーマットエラー時に、RuntimeException を throw しない。
    613         * @og.rev 6.4.2.0 (2016/01/29) StringUtil#ogStackTrace(Throwable) を、ThrowUtil#ogStackTrace(String,Throwable) に置き換え。
    614         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX)
    615         * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] CellのgetCellTypeEnum()は推奨されません (POI4.0.0)
    616         * @og.rev 7.3.0.0 (2021/01/06) ﾌｫｰﾏｯﾄｴﾗｰ時に、ｴﾗｰﾒｯｾｰｼﾞ取得でもｴﾗｰになる。
    617         * @og.rev 8.0.0.0 (2021/07/31) FORMULA処理のｴﾗｰ対応(出来るだけ…)
    618         *
    619         * @param       oCell EXCELのセルオブジェクト
    620         *
    621         * @return      セルの値
    622         */
    623        public static String getValue( final Cell oCell ) {
    624                if( oCell == null ) { return null; }
    625                String strText = "";
    626        //      final int nCellType = oCell.getCellType();                                                                      // 6.5.0.0 (2016/09/30) poi-3.12
    627        //      switch(nCellType) {                                                                                                                     // 6.5.0.0 (2016/09/30) poi-3.12
    628//              switch( oCell.getCellTypeEnum() ) {                                                                                     // 6.5.0.0 (2016/09/30) poi-3.15
    629                switch( oCell.getCellType() ) {                                                                                         // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated.
    630        //              case Cell.CELL_TYPE_NUMERIC:                                                                                    // 6.5.0.0 (2016/09/30) poi-3.12
    631                        case NUMERIC:                                                                                                                   // 6.5.0.0 (2016/09/30) poi-3.15
    632                                        strText = getNumericTypeString( oCell );
    633                                        break;
    634        //              case Cell.CELL_TYPE_STRING:                                                                                             // 6.5.0.0 (2016/09/30) poi-3.12
    635                        case STRING:                                                                                                                    // 6.5.0.0 (2016/09/30) poi-3.15
    636        // POI3.0               strText = oCell.getStringCellValue();
    637                                        final RichTextString richText = oCell.getRichStringCellValue();
    638                                        if( richText != null ) {
    639                                                strText = richText.getString();
    640                                        }
    641                                        break;
    642        //              case Cell.CELL_TYPE_FORMULA:                                                                                    // 6.5.0.0 (2016/09/30) poi-3.12
    643                        case FORMULA:                                                                                                                   // 6.5.0.0 (2016/09/30) poi-3.15
    644        // POI3.0               strText = oCell.getStringCellValue();
    645                                        // 5.5.1.2 (2012/04/06) フォーマットセルを実行して、その結果を再帰的に処理する。
    646                                        final Workbook wb = oCell.getSheet().getWorkbook();
    647                                        final CreationHelper crateHelper = wb.getCreationHelper();
    648                                        final FormulaEvaluator evaluator = crateHelper.createFormulaEvaluator();
    649
    650                                        try {
    651                                                strText = oCell.getCellFormula();
    652                                                // 8.0.0.0 (2021/07/31) FORMULA処理のｴﾗｰ対応(出来るだけ…)
    653                                //              final Cell fCell = evaluator.evaluateInCell(oCell);
    654                                //              strText = getValue( fCell );
    655                                        }
    656                                        catch( final Throwable th ) {
    657                                                // 7.3.0.0 (2021/01/06) ﾌｫｰﾏｯﾄｴﾗｰ時に、ｴﾗｰﾒｯｾｰｼﾞ取得でもｴﾗｰになる。
    658                                                final String errMsg = "セルフォーマットが解析できません。";
    659                        //                      final String errMsg = "セルフォーマットが解析できません。Formula=[" + oCell.getCellFormula() + "]";
    660                        //                                              + CR + getCellMsg( oCell );
    661        //                                      throw new OgRuntimeException( errMsg,th );
    662                                                System.err.println( ThrowUtil.ogStackTrace( errMsg,th ) );      // 6.4.2.0 (2016/01/29)
    663                                        }
    664                                        break;
    665        //              case Cell.CELL_TYPE_BOOLEAN:                                                                                    // 6.5.0.0 (2016/09/30) poi-3.12
    666                        case BOOLEAN:                                                                                                                   // 6.5.0.0 (2016/09/30) poi-3.15
    667                                        strText = String.valueOf(oCell.getBooleanCellValue());
    668                                        break;
    669        //              case Cell.CELL_TYPE_BLANK :                                                                                             // 6.5.0.0 (2016/09/30) poi-3.12
    670                        case BLANK :                                                                                                                    // 6.5.0.0 (2016/09/30) poi-3.15
    671                                        break;
    672        //              case Cell.CELL_TYPE_ERROR:                                                                                              // 6.5.0.0 (2016/09/30) poi-3.12
    673                        case ERROR:                                                                                                                             // 6.5.0.0 (2016/09/30) poi-3.15
    674                                        break;
    675                        default :
    676                                break;
    677                }
    678                return strText ;
    679        }
    680
    681        /**
    682         * セルオブジェクト(Cell)に、値をセットします。
    683         *
    684         * セルオブジェクトが存在しない場合は、何もしません。
    685         * 引数は、文字列で渡しますが、セルの形式に合わせて、変換します。
    686         * 変換がうまくいかなかった場合は、エラーになりますので、ご注意ください。
    687         *
    688         * @og.rev 6.3.9.0 (2015/11/06) 新規追加
    689         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX)
    690         * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] CellのgetCellTypeEnum()は推奨されません (POI4.0.0)
    691         * @og.rev 7.3.0.0 (2021/01/06) setCellType( CellType.BLANK )(Deprecated) → setBlank() (poi-4.1.2)
    692         *
    693         * @param       oCell EXCELのセルオブジェクト
    694         * @param       val   セットする値
    695         */
    696        public static void setValue( final Cell oCell , final String val ) {
    697                if( oCell == null ) { return ; }
    698        //      if( val == null || val.isEmpty() ) { oCell.setCellType( Cell.CELL_TYPE_BLANK ); }               // 6.5.0.0 (2016/09/30) poi-3.12
    699        //      if( val == null || val.isEmpty() ) { oCell.setCellType( CellType.BLANK ); }                             // 6.5.0.0 (2016/09/30) poi-3.15
    700                if( val == null || val.isEmpty() ) { oCell.setBlank(); }                                                                // 7.3.0.0 (2021/01/06) poi-4.1.2
    701
    702        //      switch( oCell.getCellType() ) {                                                                         // 6.5.0.0 (2016/09/30) poi-3.12
    703//              switch( oCell.getCellTypeEnum() ) {                                                                     // 6.5.0.0 (2016/09/30) poi-3.15
    704                switch( oCell.getCellType() ) {                                                                         // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated.
    705        //              case Cell.CELL_TYPE_NUMERIC:                                                                    // 6.5.0.0 (2016/09/30) poi-3.12
    706                        case NUMERIC:                                                                                                   // 6.5.0.0 (2016/09/30) poi-3.15
    707//                                      oCell.setCellValue( Double.valueOf( val ) );
    708                                        oCell.setCellValue( Double.parseDouble( val ) );                // 7.3.0.0 (2021/01/06) SpotBugs 疑わしいプリミティブ値のボクシング
    709                                        break;
    710        //              case Cell.CELL_TYPE_BOOLEAN:                                                                    // 6.5.0.0 (2016/09/30) poi-3.12
    711                        case BOOLEAN:                                                                                                   // 6.5.0.0 (2016/09/30) poi-3.15
    712                                        oCell.setCellValue( "true".equalsIgnoreCase( val ) );
    713                                        break;
    714                        default :
    715                                        oCell.setCellValue( val );
    716                                        break;
    717                }
    718        }
    719
    720        /**
    721         * セル値が数字の場合に、数字か日付かを判断して、対応する文字列を返します。
    722         *
    723         * @og.rev 3.8.5.3 (2006/08/07) 新規追加
    724         * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
    725         * @og.rev 6.3.1.0 (2015/06/28) ExcelStyleFormat を使用します。
    726         *
    727         * @param       oCell EXCELのセルオブジェクト
    728         *
    729         * @return      数字の場合は、文字列に変換した結果を、日付の場合は、"yyyyMMddHHmmss" 形式で返します。
    730         */
    731        public static String getNumericTypeString( final Cell oCell ) {
    732                final String strText ;
    733
    734                final double dd = oCell.getNumericCellValue() ;
    735                if( DateUtil.isCellDateFormatted( oCell ) ) {
    736        //              strText = DateSet.getDate( DateUtil.getJavaDate( dd ).getTime() , "yyyyMMddHHmmss" );   // 5.5.7.2 (2012/10/09) HybsDateUtil を利用
    737                        strText = ExcelStyleFormat.dateFormat( dd );
    738                }
    739                else {
    740        //              final NumberFormat numFormat = NumberFormat.getInstance();
    741        //              if( numFormat instanceof DecimalFormat ) {
    742        //                      ((DecimalFormat)numFormat).applyPattern( "#.####" );
    743        //              }
    744        //              strText = numFormat.format( dd );
    745                        final String fmrs = oCell.getCellStyle().getDataFormatString();
    746                        strText = ExcelStyleFormat.getNumberValue( fmrs,dd );
    747                }
    748                return strText ;
    749        }
    750
    751        /**
    752         * 全てのSheetに対して、autoSizeColumn設定を行います。
    753         *
    754         * 重たい処理なので、ファイルの書き出し直前に一度だけ実行するのがよいでしょう。
    755         * autoSize設定で、カラム幅が大きすぎる場合、現状では、
    756         * 初期カラム幅のmaxColCount倍を限度に設定します。
    757         * ただし、maxColCount がマイナスの場合は、無制限になります。
    758         *
    759         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    760         * @og.rev 6.8.2.4 (2017/11/20) rowObj のnull対策(poi-3.17)
    761         *
    762         * @param       wkbook          処理対象のWorkbook
    763         * @param       maxColCount     最大幅を標準セル幅の何倍にするかを指定。マイナスの場合は、無制限
    764         * @param       dataStRow       データ行の開始位置。未設定時は、-1
    765         */
    766        public static void autoCellSize( final Workbook wkbook , final int maxColCount , final int dataStRow ) {
    767                final int shCnt = wkbook.getNumberOfSheets();
    768
    769                for( int shNo=0; shNo<shCnt; shNo++ ) {
    770                        final Sheet sht = wkbook.getSheetAt( shNo );
    771                        final int defW = sht.getDefaultColumnWidth();           // 標準カラムの文字数
    772                        final int maxWidth = defW*256*maxColCount ;                     // Widthは、文字数(文字幅)*256*最大セル数
    773
    774                        int stR = sht.getFirstRowNum();
    775                        final int edR = sht.getLastRowNum();
    776
    777                        // 6.8.2.4 (2017/11/20) rowObj のnull対策(poi-3.17)
    778                        // poi-3.15 でも同じ現象が出ていますが、sht.getRow( stR ) で、Rowオブジェクトにnullが返ってきます。
    779                        // なんとなく、最後の行だけ、返ってきている感じです。
    780                        // 頻繁には使わないと思いますので、最大カラム数=256 として処理します。
    781
    782                        final Row rowObj = sht.getRow( stR );
    783        //              Row rowObj = sht.getRow( stR );
    784        //              if( rowObj == null ) {
    785        //                      for( int i=stR+1; i<edR; i++ ) {
    786        //                              rowObj = sht.getRow( i );
    787        //                              if( rowObj != null ) { break; }
    788        //                      }
    789        //              }
    790
    791                        final int stC = rowObj == null ? 0   : rowObj.getFirstCellNum();        // 6.8.2.4 (2017/11/20) rowObj のnull対策
    792                        final int edC = rowObj == null ? 256 : rowObj.getLastCellNum();         // 含まない (xlsxでは、最大 16,384 列
    793
    794                        // SheetUtil を使用して、計算範囲を指定します。
    795                        if( stR < dataStRow ) { stR = dataStRow; }              // 計算範囲
    796                        for( int colNo=stC; colNo<edC; colNo++ ) {
    797                                final double wpx = SheetUtil.getColumnWidth( sht,colNo,true,stR,edR );
    798                                if( wpx >= 0.0d ) {                                                     // Cellがないと、マイナス値が戻る。
    799                                        int wd = (int)Math.ceil(wpx * 256) ;
    800                                        if( maxWidth >= 0 && wd > maxWidth ) { wd = maxWidth; } // 最大値が有効な場合は、置き換える
    801                                        sht.setColumnWidth( colNo,wd );
    802                                }
    803                        }
    804
    805                        // Sheet#autoSizeColumn(int) を使用して、自動計算させる場合。
    806        //              for( int colNo=stC; colNo<edC; colNo++ ) {
    807        //                      sht.autoSizeColumn( colNo );
    808        //                      if( maxWidth >= 0 ) {                                   // 最大値が有効な場合は、置き換える
    809        //                              int wd = sht.getColumnWidth( colNo );
    810        //                              if( wd > maxWidth ) { sht.setColumnWidth( colNo,maxWidth ); }
    811        //                      }
    812        //              }
    813                }
    814        }
    815
    816        /**
    817         * 指定の Workbook の全Sheetを対象に、空行を取り除き、全体をシュリンクします。
    818         *
    819         * この処理は、#saveFile( String ) の直前に行うのがよいでしょう。
    820         *
    821         * ここでは、Row を逆順にスキャンし、Cellが 存在しない間は、行を削除します。
    822         * 途中の空行の削除ではなく、最終行かららの連続した空行の削除です。
    823         *
    824         * isCellDel=true を指定すると、Cellの末尾削除を行います。
    825         * 有効行の最後のCellから空セルを削除していきます。
    826         * 表形式などの場合は、Cellのあるなしで、レイアウトが崩れる場合がありますので
    827         * 処理が不要な場合は、isCellDel=false を指定してください。
    828         *
    829         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    830         * @og.rev 6.0.2.3 (2014/10/10) CellStyle の有無も判定基準に含めます。
    831         * @og.rev 6.0.2.5 (2014/10/31) Cellの開始、終了番号が、マイナスのケースの対応
    832         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX)
    833         * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] CellのgetCellTypeEnum()は推奨されません (POI4.0.0)
    834         *
    835         * @param       wkbook          処理対象のWorkbook
    836         * @param       isCellDel       Cellの末尾削除を行うかどうか(true:行う/false:行わない)
    837         */
    838        public static void activeWorkbook( final Workbook wkbook , final boolean isCellDel ) {
    839                final int shCnt = wkbook.getNumberOfSheets();
    840                for( int shNo=0; shNo<shCnt; shNo++ ) {
    841                        final Sheet sht = wkbook.getSheetAt( shNo );
    842
    843                        final int stR = sht.getFirstRowNum();
    844                        final int edR = sht.getLastRowNum();
    845
    846                        boolean isRowDel = true;                                                                                        // 行の削除は、Cellが見つかるまで。
    847                        for( int rowNo=edR; rowNo>=stR && rowNo>=0; rowNo-- ) {                         // 逆順に処理します。
    848                                final Row rowObj = sht.getRow( rowNo );
    849                                if( rowObj != null ) {
    850                                        final int stC = rowObj.getFirstCellNum();
    851                                        final int edC = rowObj.getLastCellNum();
    852                                        for( int colNo=edC; colNo>=stC && colNo>=0; colNo-- ) {         // 6.0.2.5 (2014/10/31) stC,edC が、マイナスのケースがある。
    853                                                final Cell colObj = rowObj.getCell( colNo );
    854                                                if( colObj != null ) {
    855                                                        final String val = getValue( colObj );
    856        //                                              if( colObj.getCellType() != Cell.CELL_TYPE_BLANK && val != null && val.length() > 0 ) {         // 6.5.0.0 (2016/09/30) poi-3.12
    857//                                                      if( colObj.getCellTypeEnum() != CellType.BLANK && val != null && val.length() > 0 ) {           // 6.5.0.0 (2016/09/30) poi-3.15
    858                                                        if( colObj.getCellType() != CellType.BLANK && val != null && val.length() > 0 ) {                       // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated.
    859                                                                isRowDel = false;                                       // 一つでも現れれば、行の削除は中止
    860                                                                break;
    861                                                        }
    862                                                        // 6.0.2.3 (2014/10/10) CellStyle の有無も判定基準に含めます。
    863                                                        else if( colObj.getCellStyle() != null ) {
    864                                                                isRowDel = false;                                       // 一つでも現れれば、行の削除は中止
    865                                                                break;
    866                                                        }
    867                                                        else if( isCellDel ) {
    868                                                                rowObj.removeCell( colObj );            // CELL_TYPE_BLANK の場合は、削除
    869                                                        }
    870                                                }
    871                                        }
    872                                        if( isRowDel ) { sht.removeRow( rowObj );       }
    873                                        else if( !isCellDel ) { break; }                                // Cell の末尾削除を行わない場合は、break すればよい。
    874                                }
    875                        }
    876                }
    877        }
    878
    879        /**
    880         * ファイルから、Workbookオブジェクトを新規に作成します。
    881         *
    882         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
    883         * @og.rev 6.2.0.0 (2015/02/27) ファイル引数を、String → File に変更
    884         * @og.rev 7.0.0.0 (2018/10/01) poi-4.0.0 例外InvalidFormatExceptionは対応するtry文の本体ではスローされません
    885         *
    886         * @param       file    入力ファイル
    887         * @return      Workbookオブジェクト
    888         * @og.rtnNotNull
    889         */
    890        public static Workbook createWorkbook( final File file ) {
    891                InputStream fis = null;
    892                try {
    893                        // File オブジェクトでcreate すると、ファイルがオープンされたままになってしまう。
    894                        fis = new BufferedInputStream( new FileInputStream( file ) );
    895                        return WorkbookFactory.create( fis );
    896                }
    897                catch( final IOException ex ) {
    898                        final String errMsg = "ファイル読込みエラー[" + file + "]" + CR + ex.getMessage() ;
    899                        throw new OgRuntimeException( errMsg,ex );
    900                }
    901                // 7.0.0.0 (2018/10/01) poi-4.0.0 対応するtry文の本体ではスローされません
    902//              catch( final InvalidFormatException ex ) {
    903//                      final String errMsg = "ファイル形式エラー[" + file + "]" + CR + ex.getMessage() ;
    904//                      throw new OgRuntimeException( errMsg,ex );
    905//              }
    906                finally {
    907                        Closer.ioClose( fis );
    908                }
    909        }
    910
    911        /**
    912         * シート一覧を、Workbook から取得します。
    913         *
    914         * 取得元が、Workbook なので、xls , xlsx どちらの形式でも取り出せます。
    915         *
    916         * EXCEL上のシート名を、配列で返します。
    917         *
    918         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
    919         *
    920         * @param       wkbook Workbookオブジェクト
    921         * @return      シート名の配列
    922         */
    923        public static String[] getSheetNames( final Workbook wkbook ) {
    924                final int shCnt = wkbook.getNumberOfSheets();
    925
    926                String[] shtNms = new String[shCnt];
    927
    928                for( int i=0; i<shCnt; i++ ) {
    929                        final Sheet sht = wkbook.getSheetAt( i );
    930                        shtNms[i] = sht.getSheetName();
    931                }
    932
    933                return shtNms;
    934        }
    935
    936        /**
    937         * 名前定義一覧を取得します。
    938         *
    939         * EXCEL上に定義された名前を、配列で返します。
    940         * ここでは、名前とFormulaをタブで連結した文字列を配列で返します。
    941         * Name オブジェクトを削除すると、EXCELが開かなくなったりするので、
    942         * 取りあえず一覧を作成して、手動で削除してください。
    943         * なお、名前定義には、非表示というのがありますので、ご注意ください。
    944         *
    945         * ◆ 非表示になっている名前の定義を表示にする EXCEL VBA マクロ
    946         * http://dev.classmethod.jp/tool/excel-delete-name/
    947         *    Sub VisibleNames()
    948         *        Dim name
    949         *        For Each name In ActiveWorkbook.Names
    950         *            If name.Visible = False Then
    951         *                name.Visible = True
    952         *            End If
    953         *        Next
    954         *        MsgBox "すべての名前の定義を表示しました。", vbOKOnly
    955         *    End Sub
    956         *
    957         * ※ EXCEL2010 数式タブ→名前の管理 で、複数選択で、削除できます。
    958         *    ただし、非表示の名前定義は、先に表示しないと、削除できません。
    959         * ◆ 名前の一括削除 EXCEL VBA マクロ
    960         * http://komitsudo.blog70.fc2.com/blog-entry-104.html
    961         *    Sub DeleteNames()
    962         *        Dim name
    963         *        On Error Resume Next
    964         *        For Each name In ActiveWorkbook.Names
    965         *            If Not name.BuiltIn Then
    966         *                name.Delete
    967         *            End If
    968         *        Next
    969         *        MsgBox "すべての名前の定義を削除しました。", vbOKOnly
    970         *    End Sub
    971         *
    972         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
    973         * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] WorkbookのgetNameAt(int)は推奨されません (POI4.0.0)
    974         *
    975         * @param       wkbook Workbookオブジェクト
    976         * @return      名前定義(名前+TAB+Formula)の配列
    977         * @og.rtnNotNull
    978         */
    979        public static String[] getNames( final Workbook wkbook ) {
    980//              final int cnt = wkbook.getNumberOfNames();
    981
    982                final Set<String> nmSet = new TreeSet<>();
    983
    984                // 7.0.0.0 (2018/10/01) 警告:[deprecation] WorkbookのgetNameAt(int)は推奨されません (POI4.0.0)
    985//              for( int i=0; i<cnt; i++ ) {
    986                for( final Name nm : wkbook.getAllNames() ) {
    987                        String name     = null;
    988                        String ref      = null;
    989
    990//                      final Name nm = wkbook.getNameAt(i);
    991                        try {
    992                                name = nm.getNameName();
    993                                ref  = nm.getRefersToFormula();
    994                        }
    995        //              catch( final Exception ex ) {                                   // 6.1.0.0 (2014/12/26) refactoring
    996                        catch( final RuntimeException ex ) {
    997                                final String errMsg = "POIUtil:RefersToFormula Error! name=[" + name + "]" + ex.getMessage() ;
    998                                System.out.println( errMsg );
    999                                // Excel97形式の場合、getRefersToFormula() でエラーが発生することがある。
    1000                        }
    1001
    1002                        nmSet.add( name + "\t" + ref );
    1003
    1004                        // 削除するとEXCELが壊れる？ なお、削除時には逆順で廻さないとアドレスがずれます。
    1005                        // if( nm.isDeleted() ) { wkbook.removeName(i); }
    1006                }
    1007
    1008                return nmSet.toArray( new String[nmSet.size()] );
    1009        }
    1010
    1011        /**
    1012         * 書式のスタイル一覧を取得します。
    1013         *
    1014         * EXCEL上に定義された書式のスタイルを、配列で返します。
    1015         * 書式のスタイルの名称は、CellStyle にメソッドが定義されていません。
    1016         * 実クラスである HSSFCellStyle にキャストして使用する
    1017         * 必要があります。(XSSFCellStyle にも名称を取得するメソッドがありません。)
    1018         *
    1019         * ※ EXCEL2010 ホームタブ→セルのスタイル は、一つづつしか削除できません。
    1020         *    マクロは、開発タブ→Visual Basic で、挿入→標準モジュール を開き
    1021         *    テキストを張り付けてください。
    1022         *    実行は、開発タブ→マクロ で、マクロ名を選択して、実行します。
    1023         *    最後は、削除してください。
    1024         *
    1025         * ◆ スタイルの一括削除 EXCEL VBA マクロ
    1026         * http://komitsudo.blog70.fc2.com/blog-entry-104.html
    1027         *    Sub DeleteStyle()
    1028         *        Dim styl
    1029         *        On Error Resume Next
    1030         *        For Each styl In ActiveWorkbook.Styles
    1031         *            If Not styl.BuiltIn Then
    1032         *                styl.Delete
    1033         *            End If
    1034         *        Next
    1035         *        MsgBox "すべての追加スタイルを削除しました。", vbOKOnly
    1036         *    End Sub
    1037         *
    1038         * ◆ 名前の表示、削除、スタイルの削除の一括実行 EXCEL VBA マクロ
    1039         *    Sub AllDelete()
    1040         *        Call VisibleNames
    1041         *        Call DeleteNames
    1042         *        Call DeleteStyle
    1043         *        MsgBox "すべての処理を完了しました。", vbOKOnly
    1044         *    End Sub
    1045         *
    1046         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
    1047         *
    1048         * @param       wkbook Workbookオブジェクト
    1049         * @return      書式のスタイル一覧
    1050         * @og.rtnNotNull
    1051         */
    1052        public static String[] getStyleNames( final Workbook wkbook ) {
    1053                final int cnt = wkbook.getNumCellStyles();              // return 値は、short
    1054
    1055                final Set<String> nmSet = new TreeSet<>();
    1056
    1057                for( int s=0; s<cnt; s++ ) {
    1058                        final CellStyle cs = wkbook.getCellStyleAt( (short)s );
    1059                        if( cs instanceof HSSFCellStyle ) {
    1060                                final HSSFCellStyle hcs = (HSSFCellStyle)cs;
    1061                                final String name = hcs.getUserStyleName();
    1062                                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    1063                                if( name == null ) {                                                    // この処理は不要かも。
    1064                                        final HSSFCellStyle pst = hcs.getParentStyle();
    1065                                        if( pst != null ) {
    1066                                                final String pname = pst.getUserStyleName();
    1067                                                if( pname != null ) { nmSet.add( pname ); }
    1068                                        }
    1069                                }
    1070                                else {
    1071                                        nmSet.add( name );
    1072                                }
    1073                        }
    1074                }
    1075
    1076                return nmSet.toArray( new String[nmSet.size()] );
    1077        }
    1078
    1079        /**
    1080         * セル情報を返します。
    1081         *
    1082         * エラー発生時に、どのセルでエラーが発生したかの情報を取得できるようにします。
    1083         *
    1084         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    1085         * @og.rev 6.0.3.0 (2014/11/13) セル情報を作成する時に、値もセットします。
    1086         * @og.rev 6.2.2.0 (2015/03/27) celKigo を求めるロジック変更
    1087         * @og.rev 6.3.1.0 (2015/06/28) rowNo(行番号)も引数に取るようにします。
    1088         *
    1089         * @param       oCell EXCELのセルオブジェクト
    1090         * @return      セル情報の文字列
    1091         */
    1092        public static String getCellMsg( final Cell oCell ) {
    1093                String lastMsg = null;
    1094
    1095                if( oCell != null ) {
    1096                        final String shtNm = oCell.getSheet().getSheetName();
    1097                        final int  rowNo = oCell.getRowIndex();
    1098                        final int  celNo = oCell.getColumnIndex();
    1099
    1100                        // 6.0.3.0 (2014/11/13) セル情報を作成する時に、値もセットします。
    1101                        lastMsg = " Sheet=" + shtNm + ", Row=" + rowNo + ", Cel=" + celNo
    1102                                                 + "(" + getCelKigo(rowNo,celNo) + ") , Val=" + oCell.toString() ;
    1103                }
    1104
    1105                return lastMsg;
    1106        }
    1107
    1108        /**
    1109         * Excelの行番号,列番号より、セル記号を求めます。
    1110         *
    1111         * 行番号は、0から始まる数字ですが、記号化する場合は、１から始まります。
    1112         * Excelの列記号とは、A,B,C,…,Z,AA,AB,…,ZZ,AAA,AAB,… と続きます。
    1113         * つまり、アルファベットだけの、２６進数になります。(ゼロの扱いが少し特殊です)
    1114         * 列番号は、0から始まる数字で、0=A,1=B,2=C,…,25=Z,26=AA,27=AB,…,701=ZZ,702=AAA,703=AAB,…
    1115         * EXCELの行列記号にする場合は、この列記号に、行番号を、＋１して付ければよいだけです。
    1116         * (※ 列番号に＋１するのは、内部では０から始まる列番号ですが、表示上は１から始まります)
    1117         *
    1118         * @og.rev 6.2.2.0 (2015/03/27) celKigo を求めるロジック変更
    1119         * @og.rev 6.3.1.0 (2015/06/28) rowNo(行番号)も引数に取るようにします。
    1120         *
    1121         * @param       rowNo   行番号(0,1,2,…)
    1122         * @param       colNo   列番号(0,1,2,…)
    1123         * @return      Excelの列記号(A1,B2,C3,…)
    1124         */
    1125        public static String getCelKigo( final int rowNo,final int colNo ) {
    1126                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    1127                int cnt = colNo;
    1128                while( cnt >= 26 ) {
    1129                        buf.append( (char)('A'+cnt%26) );
    1130                        cnt = cnt/26-1;
    1131                }
    1132                buf.append( (char)('A'+cnt%26) )
    1133                        .reverse()                                                              // append で逆順に付けているので、反転して返します。
    1134                        .append( rowNo+1 );
    1135
    1136                return buf.toString();
    1137        }
    1138
    1139        /**
    1140         * アプリケーションのサンプルです。
    1141         *
    1142         * 入力ファイル名 は必須で、第一引数固定です。
    1143         * 第二引数は、処理方法で、-ALL か、-LINE を指定します。何も指定しなければ、-ALL です。
    1144         * 第三引数を指定した場合は、Encode を指定します。
    1145         *
    1146         * Usage: java org.opengion.fukurou.model.POIUtil 入力ファイル名 [処理方式] [エンコード]
    1147         *   -A(LL)        ･･･ ALL 一括処理(初期値)
    1148         *   -L(INE)       ･･･ LINE 行単位処理
    1149         *   -S(heet)      ･･･ Sheet名一覧
    1150         *   -N(AME)       ･･･ NAME:名前定義
    1151         *   -C(ellStyle)  ･･･ CellStyle:書式のスタイル
    1152         *
    1153         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    1154         * @og.rev 6.2.3.0 (2015/05/01) パラメータ変更、textReader → extractor に変更
    1155         * @og.rev 6.2.4.2 (2015/05/29) 引数判定の true/false の処理が逆でした。
    1156         * @og.rev 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更
    1157         *
    1158         * @param       args    コマンド引数配列
    1159         */
    1160        public static void main( final String[] args ) {
    1161                final String usageMsg = "Usage: java org.opengion.fukurou.model.POIUtil 入力ファイル名 [処理方式] [エンコード]" + "\n" +
    1162                                                                "\t -A(LL)        ･･･ ALL 一括処理(初期値)      \n" +
    1163                                                                "\t -L(INE)       ･･･ LINE 行単位処理           \n" +
    1164                                                                "\t -S(heet)      ･･･ Sheet名一覧               \n" +
    1165                                                                "\t -N(AME)       ･･･ NAME:名前定義             \n" +
    1166                                                                "\t -C(ellStyle)  ･･･ CellStyle:書式のスタイル  \n" ;
    1167                if( args.length == 0 ) {
    1168                        System.err.println( usageMsg );
    1169                        return ;
    1170                }
    1171
    1172                final File file = new File( args[0] );
    1173                final char type     = args.length >= 2 ? args[1].charAt(1) : 'A' ;
    1174                final String encode = args.length >= 3 ? args[2] : null ;                               // 6.2.4.2 (2015/05/29) true/false の処理が逆でした。
    1175
    1176                switch( type ) {
    1177                        case 'A' :  if( encode == null ) {
    1178                                                        System.out.println( POIUtil.extractor( file ) );
    1179                                                }
    1180                                                else {
    1181                                                        System.out.println( POIUtil.extractor( file,encode ) );
    1182                                                }
    1183                                                break;
    1184                        // 6.3.9.0 (2015/11/06) Java 8 ラムダ式に変更
    1185                        case 'L' : final TextConverter<String,String> conv =
    1186                                                                ( val,cmnt ) -> {
    1187                                                                        System.out.println( "val=" + val + " , cmnt=" + cmnt );
    1188                                                                        return null;
    1189                                                                };
    1190
    1191                                        //              new TextConverter<String,String>() {
    1192                                        //                      /**
    1193                                        //                       * 入力文字列を、変換します。
    1194                                        //                       *
    1195                                        //                       * @param       val  入力文字列
    1196                                        //                       * @param       cmnt コメント
    1197                                        //                       * @return      変換文字列(変換されない場合は、null)
    1198                                        //                       */
    1199                                        //                      @Override
    1200                                        //                      public String change( final String val , final String cmnt ) {
    1201                                        //                              System.out.println( "val=" + val + " , cmnt=" + cmnt );
    1202                                        //                              return null;
    1203                                        //                      }
    1204                                        //              };
    1205
    1206                                                if( encode == null ) {
    1207                                                        POIUtil.textReader( file,conv );
    1208                                                }
    1209                                                else {
    1210                                                        POIUtil.textReader( file,conv,encode );
    1211                                                }
    1212                                                break;
    1213                        case 'S' :  final String[] shts = POIUtil.getSheetNames( POIUtil.createWorkbook(file) );
    1214                                                System.out.println( "No:\tSheetName" );
    1215                                                for( int i=0; i<shts.length; i++ ) {
    1216                                                        System.out.println( i + "\t" + shts[i] );
    1217                                                }
    1218                                                break;
    1219                        case 'N' :  final String[] nms = POIUtil.getNames( POIUtil.createWorkbook(file) );
    1220                                                System.out.println( "No:\tName\tFormula" );
    1221                                                for( int i=0; i<nms.length; i++ ) {
    1222                                                        System.out.println( i + "\t" + nms[i] );
    1223                                                }
    1224                                                break;
    1225                        case 'C' :  final String[] sns = POIUtil.getStyleNames( POIUtil.createWorkbook(file) );
    1226                                                System.out.println( "No:\tStyleName" );
    1227                                                for( int i=0; i<sns.length; i++ ) {
    1228                                                        System.out.println( i + "\t" + sns[i] );
    1229                                                }
    1230                                                break;
    1231                        default :   System.err.println( usageMsg );
    1232                                                break;
    1233                }
    1234        }
    1235}