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