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