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