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