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