    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.zip.ZipException;                                                                                              // 8.5.0.0 (2023/04/21)
    029import java.util.Set;                                                                                                                   // 6.0.2.3 (2014/10/10)
    030import java.util.TreeSet;                                                                                                               // 6.0.2.3 (2014/10/10)
    031import java.util.List;                                                                                                                  // 6.4.6.0 (2016/05/27) poi-3.15
    032// import java.util.ArrayList;                                                                                                  // 8.0.1.0 (2021/10/29)
    033
    034// import org.apache.xmlbeans.XmlException;                                                                             // 8.0.0.0 (2021/07/31) Delete
    035// import org.apache.poi.POITextExtractor;
    036import org.apache.poi.extractor.POITextExtractor;                                                               // 7.0.0.0 (2018/10/01) poi-3.17.jar → poi-4.0.0.jar
    037// import org.apache.poi.extractor.ExtractorFactory;
    038// import org.apache.poi.ooxml.extractor.ExtractorFactory;                                              // 7.0.0.0 (2018/10/01) poi-ooxml-3.17.jar → poi-ooxml-4.0.0.jar
    039import org.apache.poi.ooxml.extractor.POIXMLExtractorFactory;                                   // 8.0.0.0 (2021/07/31) poi-ooxml-4.0.0.jar → poi-ooxml-5.0.0.jar
    040import org.apache.poi.hwpf.HWPFDocument;
    041import org.apache.poi.hwpf.usermodel.Range;
    042import org.apache.poi.hwpf.usermodel.Paragraph;
    043import org.apache.poi.hssf.usermodel.HSSFCellStyle;
    044import org.apache.poi.hslf.usermodel.HSLFTextParagraph;                                                 // 6.4.6.0 (2016/05/27) poi-3.15
    045import org.apache.poi.hslf.usermodel.HSLFSlide;                                                                 // 6.4.6.0 (2016/05/27) poi-3.15
    046import org.apache.poi.hslf.usermodel.HSLFSlideShow;                                                             // 6.4.6.0 (2016/05/27) poi-3.15
    047
    048import org.apache.poi.xwpf.usermodel.XWPFDocument;                                                              // 6.2.0.0 (2015/02/27)
    049import org.apache.poi.xwpf.usermodel.XWPFParagraph;                                                             // 6.2.0.0 (2015/02/27)
    050import org.apache.poi.xwpf.model.XWPFCommentsDecorator;                                                 // 8.5.0.0 (2023/04/21) Wordのﾃｷｽﾄ抜出を改造
    051import org.apache.poi.xwpf.usermodel.IBodyElement;                                                              // 8.5.0.0 (2023/04/21)
    052import org.apache.poi.xwpf.usermodel.XWPFTable;                                                                 // 8.5.0.0 (2023/04/21)
    053import org.apache.poi.xwpf.usermodel.XWPFTableCell;                                                             // 8.5.0.0 (2023/04/21)
    054import org.apache.poi.xwpf.usermodel.XWPFTableRow;                                                              // 8.5.0.0 (2023/04/21)
    055import org.apache.poi.xwpf.usermodel.XWPFSDT;                                                                   // 8.5.0.0 (2023/04/21)
    056
    057import org.openxmlformats.schemas.drawingml.x2006.main.CTHyperlink;                             // 8.1.0.1 (2022/01/07)
    058import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;                                   // 8.1.0.1 (2022/01/07)
    059
    060import org.apache.poi.xslf.usermodel.XMLSlideShow;                                                              // 6.2.0.0 (2015/02/27)
    061// import org.apache.poi.xslf.extractor.XSLFPowerPointExtractor;                                // 7.0.0.0 (2018/10/01) POI4.0.0 deprecation
    062// import org.apache.poi.sl.extractor.SlideShowExtractor;                                               // 7.0.0.0 (2018/10/01) POI4.0.0 XSLFPowerPointExtractor → SlideShowExtractor 8.5.0.0 (2023/04/21) Delete
    063import org.apache.poi.xslf.usermodel.XSLFSlide;                                                                 // 8.5.0.0 (2023/04/21)
    064import org.apache.poi.xslf.usermodel.XSLFShape;                                                                 // 7.0.0.0 (2018/10/01) POI4.0.0 XSLFPowerPointExtractor → SlideShowExtractor
    065import org.apache.poi.xslf.usermodel.XSLFTextParagraph;                                                 // 7.0.0.0 (2018/10/01) POI4.0.0 XSLFPowerPointExtractor → SlideShowExtractor
    066import org.apache.poi.sl.usermodel.TableCell;                                                                   // 8.5.0.0 (2023/04/21)
    067import org.apache.poi.sl.usermodel.ShapeContainer;                                                              // 8.5.0.0 (2023/04/21)
    068import org.apache.poi.sl.usermodel.TableShape;                                                                  // 8.5.0.0 (2023/04/21)
    069import org.apache.poi.sl.usermodel.Shape;                                                                               // 8.5.0.0 (2023/04/21)
    070import org.apache.poi.sl.usermodel.TextShape;                                                                   // 8.5.0.0 (2023/04/21)
    071import org.apache.poi.sl.usermodel.TextRun;                                                                             // 8.5.0.0 (2023/04/21)
    072
    073import org.apache.pdfbox.pdmodel.PDDocument;                                                                    // 8.5.0.0 (2023/05/12) PDFﾌｧｲﾙ処理
    074// import org.apache.pdfbox.Loader;                                                                                             // 8.5.0.0 (2023/05/12) 3.0.0
    075import org.apache.pdfbox.text.PDFTextStripper;                                                                  // 8.5.0.0 (2023/05/12)
    076
    077// 8.5.0.0 (2023/05/12) pdfboxの警告抑止 … ただし、Log4J に切り替えると、制御できない。
    078import java.util.logging.Logger;                                                                                                // 8.5.0.0 (2023/05/12) pdfboxの警告抑止
    079import java.util.logging.Level;                                                                                                 // 8.5.0.0 (2023/05/12)
    080
    081import org.apache.poi.xssf.usermodel.XSSFSimpleShape;                                                   // 8.1.0.1 (2022/01/07) ﾃｷｽﾄ変換処理
    082
    083// import org.apache.poi.openxml4j.exceptions.InvalidFormatException;                   // 8.0.0.0 (2021/07/31) Delete
    084// import org.apache.poi.openxml4j.exceptions.OpenXML4JException ;                              // 6.1.0.0 (2014/12/26) findBugs 8.0.0.0 (2021/07/31) Delete
    085import org.apache.poi.ss.usermodel.WorkbookFactory;
    086import org.apache.poi.ss.usermodel.Workbook;
    087import org.apache.poi.ss.usermodel.Sheet;
    088import org.apache.poi.ss.usermodel.Row;
    089import org.apache.poi.ss.usermodel.Cell;
    090import org.apache.poi.ss.usermodel.CellStyle;
    091import org.apache.poi.ss.usermodel.CreationHelper;                                                              // 8.1.2.3 (2022/05/20) 復活
    092import org.apache.poi.ss.usermodel.RichTextString;
    093import org.apache.poi.ss.usermodel.DateUtil;
    094import org.apache.poi.ss.usermodel.FormulaEvaluator;                                                    // 8.1.2.3 (2022/05/20) 復活
    095import org.apache.poi.ss.usermodel.CellValue;                                                                   // 8.1.2.3 (2022/05/20)
    096import org.apache.poi.ss.usermodel.Name;                                                                                // 6.0.2.3 (2014/10/10)
    097import org.apache.poi.ss.usermodel.CellType;                                                                    // 6.5.0.0 (2016/09/30) poi-3.15
    098import org.apache.poi.ss.util.SheetUtil;
    099
    100import org.opengion.fukurou.system.OgRuntimeException ;                                                 // 6.4.2.0 (2016/01/29)
    101import org.opengion.fukurou.util.FileInfo;                                                                              // 6.2.3.0 (2015/05/01)
    102// import org.opengion.fukurou.system.ThrowUtil;                                                                // 6.4.2.0 (2016/01/29) 8.5.0.0 (2023/04/21) Delete
    103import org.opengion.fukurou.system.Closer;                                                                              // 6.2.0.0 (2015/02/27)
    104import static org.opengion.fukurou.system.HybsConst.CR;                                                 // 6.1.0.0 (2014/12/26) refactoring
    105import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;                              // 6.4.2.1 (2016/02/05) refactoring
    106// import org.apache.poi.sl.usermodel.Slide;                                                                    // 8.5.0.0 (2023/04/21) Delete
    107
    108/**
    109 * POI による、Excel/Word/PoworPoint等に対する、ﾕｰﾃｨﾘﾃｨｸﾗｽです。
    110 *
    111 * 基本的には、ﾈｲﾃｨﾌﾞﾌｧｲﾙを読み取り、ﾃｷｽﾄを取得する機能が主です。
    112 * Excel、Word、PowerPoint、Visio、Publisher からのﾃｷｽﾄ取得が可能です。
    113 *
    114 * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    115 * @og.rev 6.2.0.0 (2015/02/27) ﾊﾟｯｹｰｼﾞ変更(util → model)
    116 * @og.group その他
    117 *
    118 * @version     6.0
    119 * @author      Kazuhiko Hasegawa
    120 * @since       JDK7.0,
    121 */
    122public final class POIUtil {
    123        /** このﾌﾟﾛｸﾞﾗﾑのVERSION文字列を設定します。 {@value} */
    124        private static final String VERSION = "8.5.0.0 (2023/05/12)" ;
    125
    126        // 6.2.3.0 (2015/05/01)
    127        /** POI対象ｻﾌｨｯｸｽ {@value} */
    128        public static final String POI_SUFIX = "ppt,pptx,doc,docx,xls,xlsx,xlsm" ;
    129
    130        // 8.5.0.0 (2023/04/21)
    131        /** ﾃｷｽﾄ対象ｻﾌｨｯｸｽ {@value} */
    132        public static final String TXT_SUFIX = "txt,csv,jsp,java,html,xml,css,js,json,py" ;
    133
    134        /**
    135         * すべてが staticﾒｿｯﾄﾞなので、ｺﾝｽﾄﾗｸﾀを呼び出さなくしておきます。
    136         *
    137         */
    138        private POIUtil() {}
    139
    140        /**
    141         * 引数ﾌｧｲﾙが、POI関連の拡張子ﾌｧｲﾙかどうかを判定します。
    142         *
    143         * Excel、Word、PowerPoint、Visio、Publisher からのﾃｷｽﾄ取得が可能です。
    144         * ﾌｧｲﾙの拡張子が、{@value #POI_SUFIX} の場合、true を返します。
    145         *
    146         * @og.rev 6.2.3.0 (2015/05/01) POI関連の拡張子ﾌｧｲﾙかどうかを判定
    147         *
    148         * @param       file    判定するﾌｧｲﾙ
    149         * @return      POI関連の拡張子の場合、true
    150         */
    151        public static boolean isPOI( final File file ) {
    152                return POI_SUFIX.contains( FileInfo.getSUFIX( file ) );
    153        }
    154
    155        /**
    156         * 引数ﾌｧｲﾙが、処理対象となるﾃｷｽﾄ関連の拡張子ﾌｧｲﾙかどうかを判定します。
    157         *
    158         * ﾌｧｲﾙの拡張子が、{@value #TXT_SUFIX} の場合、true を返します。
    159         *
    160         * @og.rev 8.5.0.0 (2023/04/21) 新規追加
    161         *
    162         * @param       file    判定するﾌｧｲﾙ
    163         * @return      ﾃｷｽﾄ関連の拡張子の場合、true
    164         */
    165        public static boolean isText( final File file ) {
    166                return TXT_SUFIX.contains( FileInfo.getSUFIX( file ) );
    167        }
    168
    169        /**
    170         * 引数ﾌｧｲﾙが、textReader の読み取り対象となる拡張子ﾌｧｲﾙかどうかを判定します。
    171         *
    172         * ﾌｧｲﾙの拡張子が {@value #POI_SUFIX}、{@value #TXT_SUFIX}、pdf の場合、true を返します。
    173         *
    174         * @og.rev 8.5.0.0 (2023/05/12) 新規追加
    175         *
    176         * @param       file    判定するﾌｧｲﾙ
    177         * @return      ﾃｷｽﾄ関連の拡張子の場合、true
    178         */
    179        public static boolean isReadText( final File file ) {
    180                final String sufix = FileInfo.getSUFIX( file );         // sufix は小文字変換されてくる。
    181
    182                return POI_SUFIX.contains( sufix )
    183                        || TXT_SUFIX.contains( sufix )
    184                        || "pdf".equals( sufix ) ;
    185        }
    186
    187        /**
    188         * 引数ﾌｧｲﾙを、POITextExtractor を使用してﾃｷｽﾄ化します。
    189         *
    190         * Excel、Word、PowerPoint、Visio、Publisher からのﾃｷｽﾄ取得が可能です。
    191         * 拡張子から、ﾌｧｲﾙの種類を自動判別します。
    192         *
    193         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    194         * @og.rev 6.2.0.0 (2015/02/27) getText → extractor に変更
    195         * @og.rev 8.0.0.0 (2021/07/31) ExtractorFactory → POIXMLExtractorFactory に変更
    196         *
    197         * @param       file    入力ﾌｧｲﾙ名
    198         * @return      変換後のﾃｷｽﾄ
    199         * @og.rtnNotNull
    200         */
    201        public static String extractor( final File file ) {
    202        //      InputStream fis = null;
    203                POITextExtractor extractor = null;
    204                try {
    205        //              fis = new BufferedInputStream( new FileInputStream( file ) );
    206        //              extractor = ExtractorFactory.createExtractor( fis );
    207        //              extractor = ExtractorFactory.createExtractor( file );
    208                        extractor = new POIXMLExtractorFactory().create( file , null );         // 8.0.0.0 (2021/07/31) poi-ooxml-4.0.0.jar → poi-ooxml-5.0.0.jar
    209                        return extractor.getText();
    210                }
    211                catch( final FileNotFoundException ex ) {
    212                        final String errMsg = "ﾌｧｲﾙが存在しません[" + file + "]" + CR + ex.getMessage() ;
    213                        throw new OgRuntimeException( errMsg,ex );
    214                }
    215                catch( final IOException ex ) {
    216                        final String errMsg = "ﾌｧｲﾙ処理ｴﾗｰ[" + file + "]" + CR + ex.getMessage() ;
    217                        throw new OgRuntimeException( errMsg,ex );
    218                }
    219                // 8.0.0.0 (2021/07/31) poi-ooxml-4.0.0.jar → poi-ooxml-5.0.0.jar
    220//              catch( final InvalidFormatException ex ) {
    221//                      final String errMsg = "ﾌｧｲﾙﾌｫｰﾏｯﾄｴﾗｰ[" + file + "]" + CR + ex.getMessage() ;
    222//                      throw new OgRuntimeException( errMsg,ex );
    223//              }
    224//              catch( final OpenXML4JException ex ) {
    225//                      final String errMsg = "ODF-XML処理ｴﾗｰ[" + file + "]" + CR + ex.getMessage() ;
    226//                      throw new OgRuntimeException( errMsg,ex );
    227//              }
    228//              catch( final XmlException ex ) {
    229//                      final String errMsg = "XML処理ｴﾗｰ[" + file + "]" + CR + ex.getMessage() ;
    230//                      throw new OgRuntimeException( errMsg,ex );
    231//              }
    232                finally {
    233                        Closer.ioClose( extractor );
    234        //              Closer.ioClose( fis );
    235                }
    236        }
    237
    238        /**
    239         * 引数ﾌｧｲﾙ(Text)を、ﾃｷｽﾄ化します。
    240         *
    241         * ここでは、ﾌｧｲﾙとｴﾝｺｰﾄﾞを指定して、ﾌｧｲﾙのﾃｷｽﾄ全てを読み取ります。
    242         *
    243         * @og.rev 6.2.2.0 (2015/03/27) 引数ﾌｧｲﾙ(Text)を、ﾃｷｽﾄ化。
    244         * @og.rev 6.2.3.0 (2015/05/01) textReader → extractor に変更
    245         *
    246         * @param       file    入力ﾌｧｲﾙ
    247         * @param       encode  ｴﾝｺｰﾄﾞ名
    248         * @return      ﾌｧｲﾙのﾃｷｽﾄ
    249         */
    250        public static String extractor( final File file , final String encode ) {
    251                try {
    252                        // 指定のﾌｧｲﾙをﾊﾞｲﾄ列として読み込む
    253                        final byte[] bytes = Files.readAllBytes( file.toPath() );
    254                        // 読み込んだﾊﾞｲﾄ列を ｴﾝｺｰﾄﾞして文字列にする
    255                        return new String( bytes, encode );
    256                }
    257        //      catch( final UnsupportedEncodingException ex ) {
    258        //              final String errMsg = "ｴﾝｺｰﾄﾞが不正です[" + file + "] , ENCODE=[" + encode + "]"  ;
    259        //              throw new OgRuntimeException( errMsg,ex );
    260        //      }
    261                catch( final IOException ex ) {
    262                        final String errMsg = "ﾌｧｲﾙ読込みｴﾗｰ[" + file + "] , ENCODE=[" + encode + "]"  ;
    263                        throw new OgRuntimeException( errMsg,ex );
    264                }
    265        }
    266
    267        /**
    268         * 引数ﾌｧｲﾙ(Text)を、ﾃｷｽﾄ化します。
    269         *
    270         * ここでは、ﾌｧｲﾙとｴﾝｺｰﾄﾞを指定して、ﾌｧｲﾙのﾃｷｽﾄ全てを読み取ります。
    271         *
    272         * @og.rev 6.2.2.0 (2015/03/27) 引数ﾌｧｲﾙ(Text)を、ﾃｷｽﾄ化。
    273         *
    274         * @param       file    入力ﾌｧｲﾙ
    275         * @param       conv    ｲﾍﾞﾝﾄ処理させるI/F
    276         * @param       encode  ｴﾝｺｰﾄﾞ名
    277         */
    278        public static void textReader( final File file , final TextConverter<String,String> conv , final String encode ) {
    279                BufferedReader reader = null ;
    280
    281                int rowNo = 0;          // 6.2.0.0 (2015/02/27) 行番号
    282                try {
    283                        reader = Files.newBufferedReader( file.toPath() , Charset.forName( encode ) );
    284
    285                        String line ;
    286                        while((line = reader.readLine()) != null) {
    287                                conv.change( line,String.valueOf( rowNo++ ) );
    288                        }
    289                }
    290                catch( final IOException ex ) {
    291                        final String errMsg = "ﾌｧｲﾙ読込みｴﾗｰ[" + file + "] , ENCODE=[" + encode + "] , ROW=[" + rowNo + "]"  ;
    292                        throw new OgRuntimeException( errMsg,ex );
    293                }
    294                finally {
    295                        Closer.ioClose( reader );
    296                }
    297        }
    298
    299        /**
    300         * 引数ﾌｧｲﾙ(Word,PoworPoint,Excel)を、TableModelHelper を使用してﾃｷｽﾄ化します。
    301         *
    302         * ここでは、ﾌｧｲﾙ名の拡張子で、処理するﾒｿｯﾄﾞを選別します。
    303         * 拡張子が、対象かどうかは、#isPOI( File ) ﾒｿｯﾄﾞで判定できます。
    304         *
    305         * TableModelHelper によるｲﾍﾞﾝﾄ処理できますが、TEXTというｶﾗﾑ名を持つ
    306         * 表形式ｵﾌﾞｼﾞｪｸﾄの形で処理されます。
    307         * また、内部的に、先頭に、# がある場合や、行ﾃﾞｰﾀが存在しない場合は、
    308         * ｽｷｯﾌﾟされます。
    309         *
    310         * @og.rev 6.2.3.0 (2015/05/01) 新規作成
    311         * @og.rev 6.2.5.0 (2015/06/05) xls,xlsxは、それぞれ excelReader1,excelReader2 で処理します。
    312         * @og.rev 8.5.0.0 (2023/04/21) txt,csv,jsp,java,xml,css,js は、UTF-8 固定で、textReader を呼び出す。
    313         * @og.rev 8.5.0.0 (2023/05/12) pdfReader1 追加
    314         *
    315         * @param       file    入力ﾌｧｲﾙ
    316         * @param       conv    ｲﾍﾞﾝﾄ処理させるI/F
    317         */
    318        public static void textReader( final File file , final TextConverter<String,String> conv ) {
    319                final String sufix = FileInfo.getSUFIX( file );                         // sufix は小文字変換されてくる。
    320
    321                if( "doc".equalsIgnoreCase( sufix ) ) {
    322                        wordReader1( file,conv );
    323                }
    324                else if( "docx".equalsIgnoreCase( sufix ) ) {
    325                        wordReader2( file,conv );
    326                }
    327                else if( "ppt".equalsIgnoreCase( sufix ) ) {
    328                        pptReader1( file,conv );
    329                }
    330                else if( "pptx".equalsIgnoreCase( sufix ) ) {
    331                        pptReader2( file,conv );
    332                }
    333                else if( "xls".equalsIgnoreCase( sufix ) ) {
    334                        excelReader1( file,conv );                                                              // 6.2.5.0 (2015/06/05)
    335                }
    336                else if( "xlsx".equalsIgnoreCase( sufix ) || "xlsm".equalsIgnoreCase( sufix ) ) {
    337                        excelReader2( file,conv );                                                              // 6.2.5.0 (2015/06/05)
    338                }
    339                else if( "pdf".equalsIgnoreCase( sufix ) ) {
    340                        pdfReader1( file,conv );                                                                // 8.5.0.0 (2023/05/12)
    341                }
    342                // 8.5.0.0 (2023/04/21) txt,csv,jsp,java,xml,css,js は、UTF-8 固定で、textReader を呼び出す。
    343                else if( TXT_SUFIX.contains( sufix ) ) {
    344                        try {
    345                                textReader( file,conv,"UTF-8" );
    346                        }
    347                        catch( final OgRuntimeException ex ) {                  // ほとんどのｹｰｽでencode違い
    348                                conv.change( ex.getMessage() , file.getAbsolutePath() );
    349
    350                                textReader( file,conv,"Windows-31J" );          // Windows-31J で読み直し。
    351                        }
    352                }
    353                else {
    354//                      final String errMsg = "拡張子は、" +  POI_SUFIX + " にしてください。[" + file + "]" ;
    355//                      throw new OgRuntimeException( errMsg );
    356                        try {
    357                                final String filename = file.getCanonicalPath();
    358                                conv.change( "ﾃｷｽﾄ化対象外です" , filename );         // text,cmnt の順
    359                        }
    360                        catch( final IOException ex ) {
    361                                final String errMsg = "正規のパス名取得ｴﾗｰ[" + file + "]" ;
    362                                throw new OgRuntimeException( errMsg,ex );
    363                        }
    364                }
    365        }
    366
    367        /**
    368         * 引数ﾌｧｲﾙ(Word)を、HWPFDocument を使用してﾃｷｽﾄ化します。
    369         *
    370         * 拡張子(.doc)のﾌｧｲﾙを処理します。
    371         * TableModelHelper によるｲﾍﾞﾝﾄ処理できますが、TEXTというｶﾗﾑ名を持つ
    372         * 表形式ｵﾌﾞｼﾞｪｸﾄの形で処理されます。
    373         * また、内部的に、先頭に、# がある場合や、行ﾃﾞｰﾀが存在しない場合は、
    374         * ｽｷｯﾌﾟされます。
    375         *
    376         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    377         * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更
    378         * @og.rev 6.2.4.2 (2015/05/29) 改行以外に、｢。｣で分割します。
    379         *
    380         * @param       file    入力ﾌｧｲﾙ名
    381         * @param       conv    ｲﾍﾞﾝﾄ処理させるI/F
    382         */
    383        private static void wordReader1( final File file , final TextConverter<String,String> conv ) {
    384                InputStream fis  = null;
    385
    386                try {
    387                        // 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正
    388
    389                        fis = new BufferedInputStream( new FileInputStream( file ) );           // 6.2.0.0 (2015/02/27)
    390                        final HWPFDocument doc = new HWPFDocument( fis );
    391
    392        //              int rowNo = 0;          // 6.2.0.0 (2015/02/27) 行番号
    393
    394        //              // WordExtractor を使ったｻﾝﾌﾟﾙ
    395        //              WordExtractor we = new WordExtractor( doc );
    396        //              for( String txt : we.getParagraphText() ) {
    397        //                      String text = WordExtractor.stripFields( txt )
    398        //                                                      .replaceAll( "\\x13[^\\x01]+\\x01\\x14" , "" )
    399        //                                                      .replaceAll( "\\x0b" , "\n" ).trim();
    400        //                      helper.value( text.trim(),rowNo++,0 );                          // 6.2.0.0 (2015/02/27) ｲﾍﾞﾝﾄ変更
    401        //              }
    402
    403                        // Range,Paragraph を使ったｻﾝﾌﾟﾙ
    404                        final Range rng = doc.getRange();
    405                        for( int pno=0; pno<rng.numParagraphs(); pno++ ) {
    406                                final Paragraph para = rng.getParagraph(pno);
    407                                final String text = Range.stripFields( para.text() )
    408                                                                .replaceAll( "\\x13[^\\x01]+\\x01\\x14" , "" )
    409                                                                .replaceAll( "\\x0b" , "\n" ).trim();
    410        //                      conv.change( text, String.valueOf( rowNo++ ) );
    411                                if( text.length() > 0 ) {                                                               // 8.5.0.0 (2023/04/21) 存在する場合のみ抜き出す
    412                                        conv.change( text, String.valueOf( pno ) );                     // 行番号代わりの数値は飛び番となる
    413                                }
    414                        }
    415
    416                        // Range,Paragraph,CharacterRun を使ったｻﾝﾌﾟﾙ(変な個所で文字が分断される)
    417        //              final Range rng = doc.getRange();
    418        //              for( int pno = 0; pno < rng.numParagraphs(); pno++ ) {
    419        //                      final Paragraph para = rng.getParagraph(pno);
    420        //                      for( int cno = 0; cno < para.numCharacterRuns(); cno++ ) {
    421        //                              final CharacterRun crun = para.getCharacterRun(cno);
    422        //                              String text = Range.stripFields( crun.text() )
    423        //                                                              .replaceAll( "\\x13[^\\x01]+\\x01\\x14" , "" )
    424        //                                                              .replaceAll( "\\x0b" , "\n" ).trim();
    425        //                              helper.value( text,rowNo++,0 );                         // 6.2.0.0 (2015/02/27) ｲﾍﾞﾝﾄ変更
    426        //                      }
    427        //              }
    428                }
    429                catch( final IOException ex ) {
    430                        final String errMsg = "ﾌｧｲﾙ読込みｴﾗｰ[" + file + "]" + CR + ex.getMessage() ;
    431                        throw new OgRuntimeException( errMsg,ex );
    432                }
    433                finally {
    434                        Closer.ioClose( fis );
    435                }
    436        }
    437
    438        /**
    439         * 引数ﾌｧｲﾙ(Word)を、XWPFDocument を使用してﾃｷｽﾄ化します。
    440         *
    441         * 拡張子(.docx)のﾌｧｲﾙを処理します。
    442         * TableModelHelper によるｲﾍﾞﾝﾄ処理できますが、TEXTというｶﾗﾑ名を持つ
    443         * 表形式ｵﾌﾞｼﾞｪｸﾄの形で処理されます。
    444         * また、内部的に、先頭に、# がある場合や、行ﾃﾞｰﾀが存在しない場合は、
    445         * ｽｷｯﾌﾟされます。
    446         *
    447         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    448         * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更
    449         * @og.rev 6.2.4.2 (2015/05/29) 改行以外に、｢。｣で分割します。
    450         * @og.rev 8.5.0.0 (2023/04/21) XWPFWordExtractor を参考に、ﾃｷｽﾄ化を詳細化した。
    451         *
    452         * @param       file    入力ﾌｧｲﾙ
    453         * @param       conv    ｲﾍﾞﾝﾄ処理させるI/F
    454         */
    455        private static void wordReader2( final File file , final TextConverter<String,String> conv ) {
    456                InputStream fis  = null;
    457
    458                try {
    459                        // 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正
    460
    461                        fis = new BufferedInputStream( new FileInputStream( file ) );           // 6.2.0.0 (2015/02/27)
    462                        final XWPFDocument doc = new XWPFDocument( fis );
    463
    464                        int rowNo = 0;          // 6.2.0.0 (2015/02/27) 行番号
    465
    466//                      for( final XWPFParagraph para : doc.getParagraphs() ) {                         // 8.5.0.0 (2023/04/21) 削除
    467//                              final String text = para.getParagraphText().trim();
    468//                              conv.change( text, String.valueOf( rowNo++ ) );
    469//                      }
    470
    471                        // 8.5.0.0 (2023/04/21) XWPFWordExtractor を参考に、ﾃｷｽﾄ化を詳細化した。
    472                        // \poi-ooxml\src\main\java\org\apache\poi\xwpf\extractor\XWPFWordExtractor.java
    473                        for( final IBodyElement ibody : doc.getBodyElements() ) {
    474                                if (ibody instanceof XWPFParagraph) {
    475                                        appendParagraphText(conv,rowNo, (XWPFParagraph) ibody);
    476                                } else if (ibody instanceof XWPFTable) {
    477                                        appendTableText(conv,rowNo, (XWPFTable) ibody);
    478                                } else if (ibody instanceof XWPFSDT) {
    479                                        final String text = ((XWPFSDT) ibody).getContent().getText().trim();
    480                                        if( text.length() > 0 ) {
    481                                                conv.change( text, "XWPFSDT " + rowNo );
    482                                        }
    483                                }
    484                                rowNo++ ;
    485                        }
    486                }
    487                catch( final ZipException ex ) {
    488                        final String errMsg = "ﾌｧｲﾙがすでにｵｰﾌﾟﾝされています[" + file + "]" + CR + ex.getMessage() ;
    489                        throw new OgRuntimeException( errMsg,ex );
    490                }
    491                catch( final IOException ex ) {
    492                        final String errMsg = "ﾌｧｲﾙ読込みｴﾗｰ[" + file + "]" + CR + ex.getMessage() ;
    493                        throw new OgRuntimeException( errMsg,ex );
    494                }
    495                finally {
    496                        Closer.ioClose( fis );
    497                }
    498        }
    499
    500        /**
    501         * wordReader2 から派生した、部分処理
    502         *
    503         * 引数ﾌｧｲﾙ(Word)を、XWPFDocument を使用してﾃｷｽﾄ化するにあたり
    504         * XWPFParagraph のﾃｷｽﾄ化を行います。
    505         *
    506         * @og.rev 8.5.0.0 (2023/04/21) XWPFWordExtractor を参考に、ﾃｷｽﾄ化を詳細化した。
    507         *
    508         * @param       conv            ｲﾍﾞﾝﾄ処理させるI/F
    509         * @param       rowNo           検索における連番
    510         * @param       paragraph       XWPFParagraphｵﾌﾞｼﾞｪｸﾄ
    511         */
    512        private static void appendParagraphText(final TextConverter<String,String> conv, final int rowNo, final XWPFParagraph paragraph) {
    513//              final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    514
    515//              for (final IRunElement run : paragraph.getIRuns()) {
    516//                      if (run instanceof XWPFSDT) {
    517//                              buf.append( ((XWPFSDT)run).getContent().getText() );
    518//                      }
    519//                      else if (run instanceof XWPFRun) {
    520//                              buf.append( ((XWPFRun)run).text() );
    521//                      }
    522//                      else {
    523//                              buf.append( String.valueOf( run ) );
    524//                      }
    525//              }
    526//              final String text = buf.toString().trim();              // Paragraph は、1行にまとめる
    527
    528                final String text = paragraph.getText().trim();
    529                if( text.length() > 0 ) {
    530                        conv.change( text, "Paragraph " + rowNo );
    531                }
    532
    533                // FootnoteText
    534                final String note = paragraph.getFootnoteText().trim();
    535                if( note.length() > 0 ) {
    536                        conv.change( note, "Footnote " + rowNo );
    537                }
    538
    539                // Add comments
    540                final XWPFCommentsDecorator decorator = new XWPFCommentsDecorator(paragraph, null);
    541                final String cmnt = decorator.getCommentText().trim();
    542                if( cmnt.length() > 0 ) {
    543                        conv.change( cmnt, "Comment " + rowNo );
    544                }
    545        }
    546
    547        /**
    548         * wordReader2 から派生した、部分処理
    549         *
    550         * 引数ﾌｧｲﾙ(Word)を、XWPFDocument を使用してﾃｷｽﾄ化するにあたり
    551         * XWPFTable のﾃｷｽﾄ化を行います。
    552         *
    553         * @og.rev 8.5.0.0 (2023/04/21) XWPFWordExtractor を参考に、ﾃｷｽﾄ化を詳細化した。
    554         *
    555         * @param       conv    ｲﾍﾞﾝﾄ処理させるI/F
    556         * @param       rowNo   検索における連番
    557         * @param       table   XWPFTableｵﾌﾞｼﾞｪｸﾄ
    558         */
    559        private static void appendTableText(final TextConverter<String,String> conv, final int rowNo, final XWPFTable table) {
    560                int subNo = 0;
    561                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    562
    563                //this works recursively to pull embedded tables from tables
    564                for (final XWPFTableRow row : table.getRows()) {
    565                        // ﾃｰﾌﾞﾙのｾﾙは、ﾀﾌﾞで結合する。
    566                        for( final XWPFTableCell cell : row.getTableCells() ) {
    567                                buf.append( cell.getText().trim() ).append( '\t' );
    568                        }
    569
    570//                      final List<ICell> cells = row.getTableICells();
    571//                      // ﾃｰﾌﾞﾙのｾﾙは、ﾀﾌﾞで結合する。
    572//                      for (int i = 0; i < cells.size(); i++) {
    573//                              final ICell cell = cells.get(i);
    574//                              if (cell instanceof XWPFTableCell) {
    575//                                      final String text = ((XWPFTableCell) cell).getTextRecursively();
    576//                                      buf.append( text.trim() ).append( '\t' );
    577//                              } else if (cell instanceof XWPFSDTCell) {
    578//                                      final String text = ((XWPFSDTCell) cell).getContent().getText();
    579//                                      buf.append( text.trim() ).append( '\t' );
    580//                              }
    581//                      }
    582
    583                        final String text = buf.toString().trim();      // 先に trim することで、空行を除く
    584                        if( text.length() > 0 ) {
    585                                conv.change( text , "TableRow " + rowNo + ":" + subNo );
    586                        }
    587                        buf.setLength(0);               // Clearの事
    588                        subNo++ ;
    589                }
    590        }
    591
    592        /**
    593         * 引数ﾌｧｲﾙ(PoworPoint)を、HSLFSlideShow を使用してﾃｷｽﾄ化します。
    594         *
    595         * 拡張子(.ppt)のﾌｧｲﾙを処理します。
    596         * TableModelHelper によるｲﾍﾞﾝﾄ処理できますが、TEXTというｶﾗﾑ名を持つ
    597         * 表形式ｵﾌﾞｼﾞｪｸﾄの形で処理されます。
    598         * また、内部的に、先頭に、# がある場合や、行ﾃﾞｰﾀが存在しない場合は、
    599         * ｽｷｯﾌﾟされます。
    600         *
    601         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    602         * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更
    603         * @og.rev 6.4.6.0 (2016/05/27) poi-3.15 準備
    604         * @og.rev 8.5.0.0 (2023/04/21) conv.change の cmnt 修正
    605         *
    606         * @param       file    入力ﾌｧｲﾙ
    607         * @param       conv    ｲﾍﾞﾝﾄ処理させるI/F
    608         */
    609        private static void pptReader1( final File file , final TextConverter<String,String> conv ) {
    610                InputStream fis  = null;
    611
    612                try {
    613                        // 6.2.0.0 (2015/02/27) TableModelHelper 変更に伴う修正
    614
    615                        fis = new BufferedInputStream( new FileInputStream( file ) );           // 6.2.0.0 (2015/02/27)
    616
    617        //              6.4.6.0 (2016/05/27) poi-3.15
    618                        final HSLFSlideShow ss = new HSLFSlideShow( fis );
    619                        final List<HSLFSlide> slides = ss.getSlides();                                          // 6.4.6.0 (2016/05/27) poi-3.15
    620                        int rowNo = 0;          // 6.2.0.0 (2015/02/27) 行番号
    621                        for( final HSLFSlide  slide : slides ) {                                                        // 6.4.6.0 (2016/05/27) poi-3.15
    622                                int subNo = 0;  // 8.5.0.0 (2023/04/21)
    623                                for( final List<HSLFTextParagraph> txtList : slide.getTextParagraphs() ) {      // 6.4.6.0 (2016/05/27) poi-3.15
    624                                        final String text = HSLFTextParagraph.getText( txtList ).trim();
    625                                        if( text.length() > 0 ) {
    626//                                              conv.change( text, String.valueOf( rowNo++ ) );
    627                                                conv.change( text, "Slide " + rowNo + ":" + subNo );    // 8.5.0.0 (2023/04/21) cmnt 修正
    628                                        }
    629                                        subNo++;        // 8.5.0.0 (2023/04/21)
    630                                }
    631                                rowNo++ ;               // 8.5.0.0 (2023/04/21)
    632                        }
    633
    634        //              6.4.6.0 (2016/05/27) poi-3.12
    635        //              final SlideShow ss = new SlideShow( new HSLFSlideShow( fis ) );
    636        //              final Slide[] slides = ss.getSlides();
    637        //              int rowNo = 0;          // 6.2.0.0 (2015/02/27) 行番号
    638        //              for( int sno=0; sno<slides.length; sno++ ) {
    639        //                      final TextRun[] textRun = slides[sno].getTextRuns();
    640        //                      for( int tno=0; tno<textRun.length; tno++ ) {
    641        //                              final String text = textRun[tno].getText();
    642        //                              // ﾃﾞｰﾀとして設定されているﾚｺｰﾄﾞのみｲﾍﾞﾝﾄを発生させる。
    643        //                              if( text.length() > 0 ) {
    644        //                                      conv.change( text, String.valueOf( rowNo++ ) );
    645        //                              }
    646        //                      }
    647        //              }
    648                }
    649                catch( final IOException ex ) {
    650                        final String errMsg = "ﾌｧｲﾙ読込みｴﾗｰ[" + file + "]" + CR + ex.getMessage() ;
    651                        throw new OgRuntimeException( errMsg,ex );
    652                }
    653                finally {
    654                        Closer.ioClose( fis );
    655                }
    656        }
    657
    658        /**
    659         * 引数ﾌｧｲﾙ(PoworPoint)を、XMLSlideShow を使用してﾃｷｽﾄ化します。
    660         *
    661         * 拡張子(.pptx)のﾌｧｲﾙを処理します。
    662         * TableModelHelper によるｲﾍﾞﾝﾄ処理できますが、TEXTというｶﾗﾑ名を持つ
    663         * 表形式ｵﾌﾞｼﾞｪｸﾄの形で処理されます。
    664         * また、内部的に、先頭に、# がある場合や、行ﾃﾞｰﾀが存在しない場合は、
    665         * ｽｷｯﾌﾟされます。
    666         *
    667         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    668         * @og.rev 6.2.0.0 (2015/02/27) POIEvent → TableModelHelper に変更
    669         * @og.rev 6.2.4.2 (2015/05/29) 行単位に、取り込むようにします。
    670         * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] org.apache.poi.xslf.extractorのXSLFPowerPointExtractorは推奨されません (POI4.0.0)
    671         * @og.rev 8.5.0.0 (2023/04/21) SlideShowExtractor を参考に、ﾃｷｽﾄ化を詳細化した。
    672         *
    673         * @param       file    入力ﾌｧｲﾙ
    674         * @param       conv    ｲﾍﾞﾝﾄ処理させるI/F
    675         */
    676        private static void pptReader2( final File file , final TextConverter<String,String> conv ) {
    677                InputStream fis  = null;
    678
    679                try {
    680                        // 6.2.0.0 (2015/02/27) TableModelEvent 変更に伴う修正
    681
    682                        fis = new BufferedInputStream( new FileInputStream( file ) );           // 6.2.0.0 (2015/02/27)
    683                        final XMLSlideShow ss = new XMLSlideShow( fis );
    684        //              final SlideShowExtractor<XSLFShape,XSLFTextParagraph> ext = new SlideShowExtractor<>( ss );             // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated.
    685        //              final String[] vals = ext.getText().split( "\\n" );             // 6.2.4.2 (2015/05/29) 行単位に、取り込むようにします。
    686        //              for( int row=0; row<vals.length; row++ ) {
    687        //                      conv.change( vals[row], String.valueOf( row ) );
    688        //              }
    689                        int rowNo = 0;
    690                        for (final XSLFSlide slide : ss.getSlides()) {
    691                                conv.change( slide.getSlideName(), "Slide " + rowNo );
    692                                printShapeText( slide,conv,rowNo );
    693                                rowNo ++ ;
    694                        }
    695                }
    696                catch( final IOException ex ) {
    697                        final String errMsg = "ﾌｧｲﾙ読込みｴﾗｰ[" + file + "]" + CR + ex.getMessage() ;
    698                        throw new OgRuntimeException( errMsg,ex );
    699                }
    700                finally {
    701                        Closer.ioClose( fis );
    702                }
    703        }
    704
    705        /**
    706         * pptReader2 から派生した、部分処理
    707         *
    708         * 引数ﾌｧｲﾙ(PowerPoint)を、XSLFSlide を使用してﾃｷｽﾄ化します。
    709         *
    710         * @og.rev 8.5.0.0 (2023/04/21) SlideShowExtractor を参考に、ﾃｷｽﾄ化を詳細化した。
    711         *
    712         * @param       container       XSLFShapeを含むShapeContainerｵﾌﾞｼﾞｪｸﾄ
    713         * @param       conv            ｲﾍﾞﾝﾄ処理させるI/F
    714         * @param       rowNo           検索における連番
    715         */
    716        @SuppressWarnings("unchecked")
    717        private static void printShapeText(final ShapeContainer<XSLFShape,XSLFTextParagraph> container, final TextConverter<String,String> conv,final int rowNo ) {
    718                int subNo = 0;
    719                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    720
    721                for (final Shape<XSLFShape,XSLFTextParagraph> shape : container) {
    722                        if (shape instanceof TextShape) {
    723                                buf.setLength(0);
    724                                printTextParagraphs(((TextShape<XSLFShape,XSLFTextParagraph>)shape).getTextParagraphs(),buf);
    725                                final String text = buf.toString().trim();
    726                                if( text.length() > 0 ) {
    727                                        conv.change( text, "Shape " + rowNo + ":" + subNo );
    728                                }
    729                                subNo++ ;
    730                        } else if (shape instanceof TableShape) {
    731                                printTableShape((TableShape<XSLFShape,XSLFTextParagraph>)shape,conv,rowNo);
    732                        } else if (shape instanceof ShapeContainer) {
    733                                printShapeText((ShapeContainer<XSLFShape,XSLFTextParagraph>)shape,conv,rowNo);
    734                        }
    735                }
    736        }
    737
    738        /**
    739         * pptReader2 から派生した、部分処理
    740         *
    741         * 引数ﾌｧｲﾙ(PowerPoint)を、XSLFSlide を使用してﾃｷｽﾄ化します。
    742         *
    743         * @og.rev 8.5.0.0 (2023/04/21) SlideShowExtractor を参考に、ﾃｷｽﾄ化を詳細化した。
    744         *
    745         * @param       shape   XSLFShapeを含むTableShapeｵﾌﾞｼﾞｪｸﾄ
    746         * @param       conv    ｲﾍﾞﾝﾄ処理させるI/F
    747         * @param       rowNo   検索における連番
    748         */
    749        private static void printTableShape(final TableShape<XSLFShape,XSLFTextParagraph> shape, final TextConverter<String,String> conv,final int rowNo) {
    750                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    751
    752                final int nrows = shape.getNumberOfRows();
    753                final int ncols = shape.getNumberOfColumns();
    754                for (int row = 0; row < nrows; row++) {
    755                        for (int col = 0; col < ncols; col++){
    756                                final TableCell<XSLFShape,XSLFTextParagraph> cell = shape.getCell(row, col);
    757                                //defensive null checks; don't know if they're necessary
    758                                if (cell != null) {
    759                                        printTextParagraphs( cell.getTextParagraphs(),buf );
    760                                        buf.append('\t');
    761                                }
    762                        }
    763                        final String text = buf.toString().trim();
    764                        if( text.length() > 0 ) {
    765                                conv.change( text, "TableRow " + rowNo + ":" + row );
    766                        }
    767                        buf.setLength(0);               // Clearの事
    768                }
    769        }
    770
    771        /**
    772         * pptReader2 から派生した、部分処理
    773         *
    774         * 引数ﾌｧｲﾙ(PowerPoint)を、XSLFSlide を使用してﾃｷｽﾄ化します。
    775         *
    776         * @og.rev 8.5.0.0 (2023/04/21) SlideShowExtractor を参考に、ﾃｷｽﾄ化を詳細化した。
    777         *
    778         * @param       paras   XSLFTextParagraphｵﾌﾞｼﾞｪｸﾄ のﾘｽﾄ
    779         * @param       buf             文字列ﾊﾞｯﾌｧ
    780         */
    781        private static void printTextParagraphs( final List<XSLFTextParagraph> paras ,final StringBuilder buf ) {
    782                for (final XSLFTextParagraph para : paras) {
    783                        for (final TextRun run : para) {
    784                                buf.append( run.getRawText().trim() );
    785                        }
    786                }
    787        }
    788
    789        /**
    790         * 引数ﾌｧｲﾙ(Excel)を、ﾃｷｽﾄ化します。
    791         *
    792         * TableModelHelper を与えることで、EXCELﾃﾞｰﾀをﾃｷｽﾄ化できます。
    793         * ここでは、HSSF(.xls)形式を処理します。
    794         * ｼｰﾄ名、ｾﾙ、ﾃｷｽﾄｵﾌﾞｼﾞｪｸﾄをﾃｷｽﾄ化します。
    795         *
    796         * @og.rev 6.2.5.0 (2015/06/05) 新規作成
    797         *
    798         * @param       file    入力ﾌｧｲﾙ
    799         * @param       conv    ｲﾍﾞﾝﾄ処理させるI/F
    800         * @see         org.opengion.fukurou.model.ExcelModel
    801         */
    802        public static void excelReader1( final File file , final TextConverter<String,String> conv ) {
    803                excelReader2( file , conv );
    804        }
    805
    806        /**
    807         * 引数ﾌｧｲﾙ(Excel)を、ﾃｷｽﾄ化します。
    808         *
    809         * TableModelHelper を与えることで、EXCELﾃﾞｰﾀをﾃｷｽﾄ化できます。
    810         * ここでは、ExcelModelを使用して、(.xlsx , .xlsm)形式を処理します。
    811         * ｼｰﾄ名、ｾﾙ、ﾃｷｽﾄｵﾌﾞｼﾞｪｸﾄをﾃｷｽﾄ化します。
    812         *
    813         * @og.rev 6.2.5.0 (2015/06/05) 新規作成
    814         * @og.rev 6.3.1.0 (2015/06/28) TextConverterに、引数(cmnt)を追加
    815         * @og.rev 6.3.9.0 (2015/11/06) Java 8 ﾗﾑﾀﾞ式に変更
    816         *
    817         * @param       file    入力ﾌｧｲﾙ
    818         * @param       conv    ｲﾍﾞﾝﾄ処理させるI/F
    819         * @see         org.opengion.fukurou.model.ExcelModel
    820         */
    821        public static void excelReader2( final File file , final TextConverter<String,String> conv ) {
    822                final ExcelModel excel = new ExcelModel( file, true );
    823
    824                // 6.3.9.0 (2015/11/06) Java 8 ﾗﾑﾀﾞ式に変更
    825                // textConverter を使いますが、ﾃｷｽﾄを読み込むだけで、変換しません。
    826                excel.textConverter(
    827                        ( val,cmnt ) -> {
    828                                conv.change( val,cmnt );        // 変換したくないので、引数の TextConverter を直接渡せない。
    829                                return null;                            // nullを返せば、変換しません。
    830                        }
    831                );
    832        }
    833
    834        /**
    835         * 引数ﾌｧｲﾙ(PDF)を、ﾃｷｽﾄ化します。
    836         *
    837         * 引数ﾌｧｲﾙ(PDF)を、pdfbox を使用してﾃｷｽﾄ化します。
    838         *
    839         * @og.rev 8.5.0.0 (2023/05/12) pdfReader1 追加
    840         *
    841         * @param       file    入力ﾌｧｲﾙ
    842         * @param       conv    ｲﾍﾞﾝﾄ処理させるI/F
    843         */
    844        public static void pdfReader1( final File file , final TextConverter<String,String> conv ) {
    845                Logger.getLogger("org.apache.fontbox").setLevel(Level.OFF);             // 警告抑止
    846
    847//              // pdfbox-3.0.0-alpha3.jar
    848//              try( PDDocument document = Loader.loadPDF( file )) {
    849//                      final PDFTextStripper stripper = new PDFTextStripper();
    850//                      for( final PDPage page : document.getPages() ) {
    851//                              stripper.processPage( page );
    852//                              final String text = stripper.getText(document);
    853//                              final int no = stripper.getStartPage();
    854//                              conv.change( text,String.valueOf( no ) );
    855//                      }
    856//              }
    857//              catch( final IOException ex ) {
    858//                      final String errMsg = "ﾌｧｲﾙ読込みｴﾗｰ[" + file + "]" + CR + ex.getMessage() ;
    859//                      throw new OgRuntimeException( errMsg,ex );
    860//              }
    861
    862                // pdfbox-2.0.28.jar
    863                try( final PDDocument document = PDDocument.load(file) ) {
    864                        final int psize  = document.getNumberOfPages();                 // 総ページ数
    865                        final PDFTextStripper stripper = new PDFTextStripper();
    866                        final String sep = stripper.getLineSeparator();
    867                        for( int pno=1; pno<=psize; pno++ ) {
    868                                stripper.setStartPage(pno);
    869                                stripper.setEndPage(pno);
    870                                int   lno = 0;
    871                                final String text = stripper.getText(document);
    872                                for( final String line : text.split( sep ) ) {          // PDFTextStripper を使うと、ﾃｷｽﾄ行のみ抜き出すので
    873                //                      if( line.length() > 0 ) {                                               // 行番号は取れない…感じ。
    874                                                final String cmnt = "Page" + pno + " : " + lno ;
    875                                                conv.change( line,cmnt );
    876                //                      }
    877                                        lno++ ;
    878                                }
    879                        }
    880                }
    881                catch( final IOException ex ) {
    882                        final String errMsg = "ﾌｧｲﾙ読込みｴﾗｰ[" + file + "]" + CR + ex.getMessage() ;
    883                        throw new OgRuntimeException( errMsg,ex );
    884                }
    885        }
    886
    887        /**
    888         * Excelの行列記号を、行番号と列番号に分解します。
    889         *
    890         * Excelの行列記号とは、A1 , B5 , AA23 などの形式を指します。
    891         * これを、行番号と列番号に分解します。例えば、A1→0行0列、B5→4行1列、AA23→22行26列 となります。
    892         * 分解した結果は、内部変数の、rowNo と colNo にｾｯﾄされます。
    893         * これらは、0 から始まる int型の数字で表します。
    894         *
    895         *   ①行-列形式
    896         *     行列は、EXCELｵﾌﾞｼﾞｪｸﾄに準拠するため、０から始まる整数です。
    897         *     0-0 ⇒ A1 , 1-0 ⇒ A2 , 0-1 ⇒ B1 になります。
    898         *   ②EXCEL表記
    899         *     EXCEL表記に準拠した、A1,A2,B1 の記述も処理できるように対応します。
    900         *     なお、A1,A2,B1 の記述は、必ず、英字1文字＋数字 にしてください。(A～Zまで)
    901         *   ③EXCELｼｰﾄ名をｷｰに割り当てるために、"SHEET" という記号に対応します。
    902         *     rowNo = -1 をｾｯﾄします。
    903         *
    904         * @og.rev 6.0.3.0 (2014/11/13) 新規作成
    905         * @og.rev 6.2.6.0 (2015/06/19) 行-列形式と、SHEET文字列判定を採用。
    906         *
    907         * @param       kigo    Excelの行列記号( A1 , B5 , AA23 など )
    908         * @return      行と列の番号を持った配列([0]=行=ROW , [1]=列=COL)
    909         * @og.rtnNotNull
    910         */
    911        public static int[] kigo2rowCol( final String kigo ) {
    912                int rowNo = 0;
    913                int colNo = -1;                                                                         // +1 して、26 かける処理をしているので、辻褄合わせ
    914
    915                // 6.2.6.0 (2015/06/19) 行-列形式と、SHEET文字列判定を採用。
    916                if( "SHEET".equalsIgnoreCase( kigo ) ) {
    917                        rowNo = -1;
    918                }
    919                else {
    920                        final int adrs = kigo.indexOf( '-' );
    921                        if( adrs > 0 ) {
    922                                rowNo = Integer.parseInt( kigo.substring( 0,adrs ) );
    923                                colNo = Integer.parseInt( kigo.substring( adrs+1 ) );
    924                        }
    925                        else {
    926                                for( int i=0; i<kigo.length(); i++ ) {
    927                                        final char ch = kigo.charAt(i);
    928                                        if( 'A' <= ch && ch <= 'Z' ) { colNo = (colNo+1)*26 + ch-'A'; }
    929                                        else {
    930                                                // ｱﾙﾌｧﾍﾞｯﾄでなくなったら、残りは 行番号(ただし、-1する)
    931                                                rowNo = Integer.parseInt( kigo.substring( i ) ) -1;
    932                                                break;
    933                                        }
    934                                }
    935                        }
    936                }
    937                return new int[] { rowNo,colNo };
    938        }
    939
    940        /**
    941         * ｾﾙｵﾌﾞｼﾞｪｸﾄ(Cell)から値を取り出します。
    942         *
    943         * ｾﾙｵﾌﾞｼﾞｪｸﾄが存在しない場合は、null を返します。
    944         * それ以外で、うまく値を取得できなかった場合は、ｾﾞﾛ文字列を返します。
    945         *
    946         * @og.rev 3.8.5.3 (2006/08/07) 取り出し方法を少し修正
    947         * @og.rev 5.5.1.2 (2012/04/06) ﾌｫｰﾏｯﾄｾﾙを実行して、その結果を再帰的に処理する。
    948         * @og.rev 6.0.3.0 (2014/11/13) ｾﾙﾌｫｰﾏｯﾄｴﾗｰ時に、RuntimeException を throw しない。
    949         * @og.rev 6.4.2.0 (2016/01/29) StringUtil#ogStackTrace(Throwable) を、ThrowUtil#ogStackTrace(String,Throwable) に置き換え。
    950         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX)
    951         * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] CellのgetCellTypeEnum()は推奨されません (POI4.0.0)
    952         * @og.rev 7.3.0.0 (2021/01/06) ﾌｫｰﾏｯﾄｴﾗｰ時に、ｴﾗｰﾒｯｾｰｼﾞ取得でもｴﾗｰになる。
    953         * @og.rev 8.0.0.0 (2021/07/31) FORMULA処理のｴﾗｰ対応(出来るだけ…)
    954         * @og.rev 8.1.2.3 (2022/05/20) 計算式の計算を行う。
    955         * @og.rev 8.5.0.0 (2023/04/21) ｾﾙﾌｫｰﾏｯﾄ処理ｴﾗｰ時には、ｽﾀｯｸﾄﾚｰｽは出さずに計算式を返す。
    956         *
    957         * @param       oCell   EXCELのｾﾙｵﾌﾞｼﾞｪｸﾄ
    958         * @return      ｾﾙの値
    959         */
    960        public static String getValue( final Cell oCell ) {
    961                if( oCell == null ) { return null; }
    962                String strText = "";
    963        //      final int nCellType = oCell.getCellType();                                                                      // 6.5.0.0 (2016/09/30) poi-3.12
    964        //      switch(nCellType) {                                                                                                                     // 6.5.0.0 (2016/09/30) poi-3.12
    965//              switch( oCell.getCellTypeEnum() ) {                                                                                     // 6.5.0.0 (2016/09/30) poi-3.15
    966                switch( oCell.getCellType() ) {                                                                                         // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated.
    967        //              case Cell.CELL_TYPE_NUMERIC:                                                                                    // 6.5.0.0 (2016/09/30) poi-3.12
    968                        case NUMERIC:                                                                                                                   // 6.5.0.0 (2016/09/30) poi-3.15
    969                                        strText = getNumericTypeString( oCell );
    970                                        break;
    971        //              case Cell.CELL_TYPE_STRING:                                                                                             // 6.5.0.0 (2016/09/30) poi-3.12
    972                        case STRING:                                                                                                                    // 6.5.0.0 (2016/09/30) poi-3.15
    973        // POI3.0               strText = oCell.getStringCellValue();
    974                                        final RichTextString richText = oCell.getRichStringCellValue();
    975                                        if( richText != null ) {
    976                                                strText = richText.getString();
    977                                        }
    978                                        break;
    979        //              case Cell.CELL_TYPE_FORMULA:                                                                                    // 6.5.0.0 (2016/09/30) poi-3.12
    980                        case FORMULA:                                                                                                                   // 6.5.0.0 (2016/09/30) poi-3.15
    981        // POI3.0               strText = oCell.getStringCellValue();
    982                                        // 5.5.1.2 (2012/04/06) ﾌｫｰﾏｯﾄｾﾙを実行して、その結果を再帰的に処理する。
    983                                //      final Workbook wb = oCell.getSheet().getWorkbook();
    984                                //      final CreationHelper crateHelper = wb.getCreationHelper();
    985                                //      final FormulaEvaluator evaluator = crateHelper.createFormulaEvaluator();
    986
    987                                        try {
    988                                                // 8.1.2.3 (2022/05/20) 計算式の計算を行う。
    989                                                final Workbook wb = oCell.getSheet().getWorkbook();
    990                                                final CreationHelper crateHelper = wb.getCreationHelper();
    991                                                final FormulaEvaluator evaluator = crateHelper.createFormulaEvaluator();
    992                                                final CellValue value = evaluator.evaluate(oCell);
    993
    994                                                switch (value.getCellType()) {
    995                                                        case STRING:
    996                                                                strText = value.getStringValue();
    997                                                                break;
    998                                                        case NUMERIC:
    999                                                                strText = Double.toString(value.getNumberValue());
    1000                                                                break;
    1001                                                        case BOOLEAN:
    1002                                                                strText = Boolean.toString(value.getBooleanValue());
    1003                                                                break;
    1004                                                        default:
    1005                                                                strText = oCell.getCellFormula();               // 計算式のまま
    1006                                                                break;
    1007                                                }
    1008
    1009                //                              strText = oCell.getCellFormula();                               // 8.1.2.3 (2022/05/20) 計算式は返さない
    1010                                                // 8.0.0.0 (2021/07/31) FORMULA処理のｴﾗｰ対応(出来るだけ…)
    1011                                //              final Cell fCell = evaluator.evaluateInCell(oCell);
    1012                                //              strText = getValue( fCell );
    1013                                        }
    1014                                        catch( final Throwable th ) {
    1015                                                // 7.3.0.0 (2021/01/06) ﾌｫｰﾏｯﾄｴﾗｰ時に、ｴﾗｰﾒｯｾｰｼﾞ取得でもｴﾗｰになる。
    1016                                //              final String errMsg = "ｾﾙﾌｫｰﾏｯﾄが解析できません。";
    1017                                                // 8.5.0.0 (2023/04/21) ｾﾙﾌｫｰﾏｯﾄ処理ｴﾗｰ時には、ｽﾀｯｸﾄﾚｰｽは出さずに計算式を返す。
    1018                                                final String errMsg = "ｾﾙﾌｫｰﾏｯﾄが解析できません。"
    1019                                                                        + CR + "  Formula=[" + oCell.getCellFormula() + "]"
    1020                                                                        + CR + getCellMsg( oCell );
    1021        //                                      throw new OgRuntimeException( errMsg,th );
    1022                                //              System.err.println( ThrowUtil.ogStackTrace( errMsg,th ) );      // 6.4.2.0 (2016/01/29) , 8.5.0.0 (2023/04/21) Delete
    1023                                                System.err.println( errMsg );           // 8.5.0.0 (2023/04/21) Add
    1024                                                strText = oCell.toString();                     // 8.5.0.0 (2023/04/21) Add
    1025                                        }
    1026                                        break;
    1027        //              case Cell.CELL_TYPE_BOOLEAN:                                                                                    // 6.5.0.0 (2016/09/30) poi-3.12
    1028                        case BOOLEAN:                                                                                                                   // 6.5.0.0 (2016/09/30) poi-3.15
    1029                                        strText = String.valueOf(oCell.getBooleanCellValue());
    1030                                        break;
    1031        //              case Cell.CELL_TYPE_BLANK :                                                                                             // 6.5.0.0 (2016/09/30) poi-3.12
    1032                        case BLANK :                                                                                                                    // 6.5.0.0 (2016/09/30) poi-3.15
    1033                                        break;
    1034        //              case Cell.CELL_TYPE_ERROR:                                                                                              // 6.5.0.0 (2016/09/30) poi-3.12
    1035                        case ERROR:                                                                                                                             // 6.5.0.0 (2016/09/30) poi-3.15
    1036                                        break;
    1037                        default :
    1038                                final String errMsg = "ｾﾙﾀｲﾌﾟが不明です。CellType=" + oCell.getCellType()
    1039                                                                                + CR + getCellMsg( oCell );             // 8.5.0.0 (2023/04/21) Add
    1040                                System.err.println( errMsg );                                                   // 8.5.0.0 (2023/04/21) Add
    1041                                strText = oCell.toString();                                                             // 8.5.0.0 (2023/04/21) Add
    1042                                break;
    1043                }
    1044                return strText ;
    1045        }
    1046
    1047        /**
    1048         * ｾﾙｵﾌﾞｼﾞｪｸﾄ(Cell)に、値をｾｯﾄします。
    1049         *
    1050         * ｾﾙｵﾌﾞｼﾞｪｸﾄが存在しない場合は、何もしません。
    1051         * 引数は、文字列で渡しますが、ｾﾙの形式に合わせて、変換します。
    1052         * 変換がうまくいかなかった場合は、ｴﾗｰになりますので、ご注意ください。
    1053         *
    1054         * @og.rev 6.3.9.0 (2015/11/06) 新規追加
    1055         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX)
    1056         * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] CellのgetCellTypeEnum()は推奨されません (POI4.0.0)
    1057         * @og.rev 7.3.0.0 (2021/01/06) setCellType( CellType.BLANK )(Deprecated) → setBlank() (poi-4.1.2)
    1058         *
    1059         * @param       oCell   EXCELのｾﾙｵﾌﾞｼﾞｪｸﾄ
    1060         * @param       val             ｾｯﾄする値
    1061         */
    1062        public static void setValue( final Cell oCell , final String val ) {
    1063                if( oCell == null ) { return ; }
    1064        //      if( val == null || val.isEmpty() ) { oCell.setCellType( Cell.CELL_TYPE_BLANK ); }               // 6.5.0.0 (2016/09/30) poi-3.12
    1065        //      if( val == null || val.isEmpty() ) { oCell.setCellType( CellType.BLANK ); }                             // 6.5.0.0 (2016/09/30) poi-3.15
    1066                if( val == null || val.isEmpty() ) { oCell.setBlank(); }                                                                // 7.3.0.0 (2021/01/06) poi-4.1.2
    1067
    1068        //      switch( oCell.getCellType() ) {                                                                         // 6.5.0.0 (2016/09/30) poi-3.12
    1069//              switch( oCell.getCellTypeEnum() ) {                                                                     // 6.5.0.0 (2016/09/30) poi-3.15
    1070                switch( oCell.getCellType() ) {                                                                         // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated.
    1071        //              case Cell.CELL_TYPE_NUMERIC:                                                                    // 6.5.0.0 (2016/09/30) poi-3.12
    1072                        case NUMERIC:                                                                                                   // 6.5.0.0 (2016/09/30) poi-3.15
    1073//                                      oCell.setCellValue( Double.valueOf( val ) );
    1074                                        oCell.setCellValue( Double.parseDouble( val ) );                // 7.3.0.0 (2021/01/06) SpotBugs 疑わしいﾌﾟﾘﾐﾃｨﾌﾞ値のﾎﾞｸｼﾝｸﾞ
    1075                                        break;
    1076        //              case Cell.CELL_TYPE_BOOLEAN:                                                                    // 6.5.0.0 (2016/09/30) poi-3.12
    1077                        case BOOLEAN:                                                                                                   // 6.5.0.0 (2016/09/30) poi-3.15
    1078                                        oCell.setCellValue( "true".equalsIgnoreCase( val ) );
    1079                                        break;
    1080                        default :
    1081                                        oCell.setCellValue( val );
    1082                                        break;
    1083                }
    1084        }
    1085
    1086        /**
    1087         * ｾﾙ値が数字の場合に、数字か日付かを判断して、対応する文字列を返します。
    1088         *
    1089         * @og.rev 3.8.5.3 (2006/08/07) 新規追加
    1090         * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
    1091         * @og.rev 6.3.1.0 (2015/06/28) ExcelStyleFormat を使用します。
    1092         *
    1093         * @param       oCell   EXCELのｾﾙｵﾌﾞｼﾞｪｸﾄ
    1094         * @return      数字の場合は、文字列に変換した結果を、日付の場合は、"yyyyMMddHHmmss" 形式で返します。
    1095         */
    1096        public static String getNumericTypeString( final Cell oCell ) {
    1097                final String strText ;
    1098
    1099                final double dd = oCell.getNumericCellValue() ;
    1100                if( DateUtil.isCellDateFormatted( oCell ) ) {
    1101        //              strText = DateSet.getDate( DateUtil.getJavaDate( dd ).getTime() , "yyyyMMddHHmmss" );   // 5.5.7.2 (2012/10/09) HybsDateUtil を利用
    1102                        strText = ExcelStyleFormat.dateFormat( dd );
    1103                }
    1104                else {
    1105        //              final NumberFormat numFormat = NumberFormat.getInstance();
    1106        //              if( numFormat instanceof DecimalFormat ) {
    1107        //                      ((DecimalFormat)numFormat).applyPattern( "#.####" );
    1108        //              }
    1109        //              strText = numFormat.format( dd );
    1110                        final String fmrs = oCell.getCellStyle().getDataFormatString();
    1111                        strText = ExcelStyleFormat.getNumberValue( fmrs,dd );
    1112                }
    1113                return strText ;
    1114        }
    1115
    1116        /**
    1117         * 全てのSheetに対して、autoSizeColumn設定を行います。
    1118         *
    1119         * 重たい処理なので、ﾌｧｲﾙの書き出し直前に一度だけ実行するのがよいでしょう。
    1120         * autoSize設定で、ｶﾗﾑ幅が大きすぎる場合、現状では、
    1121         * 初期ｶﾗﾑ幅のmaxColCount倍を限度に設定します。
    1122         * ただし、maxColCount がﾏｲﾅｽの場合は、無制限になります。
    1123         *
    1124         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    1125         * @og.rev 6.8.2.4 (2017/11/20) rowObj のnull対策(poi-3.17)
    1126         *
    1127         * @param       wkbook          処理対象のWorkbook
    1128         * @param       maxColCount     最大幅を標準ｾﾙ幅の何倍にするかを指定。ﾏｲﾅｽの場合は、無制限
    1129         * @param       dataStRow       ﾃﾞｰﾀ行の開始位置。未設定時は、-1
    1130         */
    1131        public static void autoCellSize( final Workbook wkbook , final int maxColCount , final int dataStRow ) {
    1132                final int shCnt = wkbook.getNumberOfSheets();
    1133
    1134                for( int shNo=0; shNo<shCnt; shNo++ ) {
    1135                        final Sheet sht = wkbook.getSheetAt( shNo );
    1136                        final int defW = sht.getDefaultColumnWidth();           // 標準ｶﾗﾑの文字数
    1137                        final int maxWidth = defW*256*maxColCount ;                     // Widthは、文字数(文字幅)*256*最大ｾﾙ数
    1138
    1139                        int stR = sht.getFirstRowNum();
    1140                        final int edR = sht.getLastRowNum();
    1141
    1142                        // 6.8.2.4 (2017/11/20) rowObj のnull対策(poi-3.17)
    1143                        // poi-3.15 でも同じ現象が出ていますが、sht.getRow( stR ) で、Rowｵﾌﾞｼﾞｪｸﾄにnullが返ってきます。
    1144                        // なんとなく、最後の行だけ、返ってきている感じです。
    1145                        // 頻繁には使わないと思いますので、最大ｶﾗﾑ数=256 として処理します。
    1146
    1147                        final Row rowObj = sht.getRow( stR );
    1148        //              Row rowObj = sht.getRow( stR );
    1149        //              if( rowObj == null ) {
    1150        //                      for( int i=stR+1; i<edR; i++ ) {
    1151        //                              rowObj = sht.getRow( i );
    1152        //                              if( rowObj != null ) { break; }
    1153        //                      }
    1154        //              }
    1155
    1156                        final int stC = rowObj == null ? 0   : rowObj.getFirstCellNum();        // 6.8.2.4 (2017/11/20) rowObj のnull対策
    1157                        final int edC = rowObj == null ? 256 : rowObj.getLastCellNum();         // 含まない (xlsxでは、最大 16,384 列
    1158
    1159                        // SheetUtil を使用して、計算範囲を指定します。
    1160                        if( stR < dataStRow ) { stR = dataStRow; }              // 計算範囲
    1161                        for( int colNo=stC; colNo<edC; colNo++ ) {
    1162                                final double wpx = SheetUtil.getColumnWidth( sht,colNo,true,stR,edR );
    1163                                if( wpx >= 0.0d ) {                                                     // Cellがないと、ﾏｲﾅｽ値が戻る。
    1164                                        int wd = (int)Math.ceil(wpx * 256) ;
    1165                                        if( maxWidth >= 0 && wd > maxWidth ) { wd = maxWidth; } // 最大値が有効な場合は、置き換える
    1166                                        sht.setColumnWidth( colNo,wd );
    1167                                }
    1168                        }
    1169
    1170                        // Sheet#autoSizeColumn(int) を使用して、自動計算させる場合。
    1171        //              for( int colNo=stC; colNo<edC; colNo++ ) {
    1172        //                      sht.autoSizeColumn( colNo );
    1173        //                      if( maxWidth >= 0 ) {                                   // 最大値が有効な場合は、置き換える
    1174        //                              int wd = sht.getColumnWidth( colNo );
    1175        //                              if( wd > maxWidth ) { sht.setColumnWidth( colNo,maxWidth ); }
    1176        //                      }
    1177        //              }
    1178                }
    1179        }
    1180
    1181//      /**
    1182//       * 指定の Workbook の全Sheetを対象に、実際の有効行と有効ｶﾗﾑを取得します。
    1183//       *
    1184//       * ※ 現在、唯一LibreOfficeでのみ、xslx 変換できますが、有効行とｶﾗﾑが
    1185//       *    ｼｭﾘﾝｸされず、無駄な行とｶﾗﾑが存在します。
    1186//       *    これは、xsl で出力されたﾌｧｲﾙから有効な値を取得して、xslxに適用させるための
    1187//       *    機能で、本来きちんとした有効範囲の xslx が生成されれば、不要な処理です。
    1188//       *
    1189//       * 配列は、[0]=行の最大値(Sheet#getLastRowNum())と、[1]は有効行の中の列の
    1190//       * 最大値(Row#getLastCellNum())を、ｼｰﾄごとにListに追加していきます。
    1191//       *
    1192//       * @og.rev 8.0.1.0 (2021/10/29) 全Sheetを対象に、実際の有効行と有効ｶﾗﾑを取得
    1193//       * @og.rev 8.0.3.0 (2021/12/17) 処理が中途半端だったので、廃止します。
    1194//       *
    1195//       * @param       wkbook          処理対象のWorkbook
    1196//       * @return      ｼｰﾄごとの有効行の配列ﾘｽﾄ
    1197//       * @see         #activeWorkbook( Workbook,List )
    1198//       */
    1199//      public static List<int[]> getLastRowCellNum( final Workbook wkbook ) {
    1200//              final List<int[]> rcList = new ArrayList<>();                                   // ｼｰﾄごとの有効行の配列ﾘｽﾄ
    1201//
    1202//              final int shCnt = wkbook.getNumberOfSheets();
    1203//              for( int shNo=0; shNo<shCnt; shNo++ ) {
    1204//                      final Sheet sht = wkbook.getSheetAt( shNo );
    1205//                      final int stR = sht.getFirstRowNum();
    1206//                      final int edR = sht.getLastRowNum();
    1207//                      int lastNo = 0;                                                                                         // 行の有効最大値
    1208//                      for( int rowNo=edR; rowNo>=stR && rowNo>=0; rowNo-- ) {         // 逆順に処理します。
    1209//                              final Row rowObj = sht.getRow( rowNo );
    1210//                              if( rowObj != null ) {
    1211//                                      final int edC = rowObj.getLastCellNum();                        // 列の有効最大値
    1212//                                      if( lastNo < edC ) { lastNo = edC; }                            // ｼｰﾄ内での列の最大有効値
    1213//                              }
    1214//                      }
    1215//                      rcList.add( new int[] {edR,lastNo} );                                           // 有効行の配列
    1216//              }
    1217//              return rcList;
    1218//      }
    1219
    1220        /**
    1221         * 指定の Workbook の全Sheetを対象に、空行を取り除き、全体をｼｭﾘﾝｸします。
    1222         *
    1223         * この処理は、#saveFile( String ) の直前に行うのがよいでしょう。
    1224         *
    1225         * ここでは、Row を逆順にｽｷｬﾝし、Cellが 存在しない間は、行を削除します。
    1226         * 途中の空行の削除ではなく、最終行からの連続した空行の削除です。
    1227         *
    1228         * isCellDel=true を指定すると、Cellの末尾削除を行います。
    1229         * 有効行の最後のCellから空ｾﾙを削除していきます。
    1230         * 表形式などの場合は、Cellのあるなしで、ﾚｲｱｳﾄが崩れる場合がありますので
    1231         * 処理が不要な場合は、isCellDel=false を指定してください。
    1232         *
    1233         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    1234         * @og.rev 6.0.2.3 (2014/10/10) CellStyle の有無も判定基準に含めます。
    1235         * @og.rev 6.0.2.5 (2014/10/31) Cellの開始、終了番号が、ﾏｲﾅｽのｹｰｽの対応
    1236         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Cell.CELL_TYPE_XXXX → CellType.XXXX)
    1237         * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] CellのgetCellTypeEnum()は推奨されません (POI4.0.0)
    1238         * @og.rev 8.0.1.0 (2021/10/29) CellStyle は not null になったための修正
    1239         *
    1240         * @param       wkbook          処理対象のWorkbook
    1241         * @param       isCellDel       Cellの末尾削除を行うかどうか(true:行う/false:行わない)
    1242         * @see         #activeWorkbook( Workbook,List )
    1243         */
    1244        public static void activeWorkbook( final Workbook wkbook , final boolean isCellDel ) {
    1245                final int shCnt = wkbook.getNumberOfSheets();
    1246                for( int shNo=0; shNo<shCnt; shNo++ ) {
    1247                        final Sheet sht = wkbook.getSheetAt( shNo );
    1248                        final int stR = sht.getFirstRowNum();
    1249                        final int edR = sht.getLastRowNum();
    1250
    1251                        boolean isRowDel = true;                                                                                        // 行の削除は、Cellが見つかるまで。
    1252                        for( int rowNo=edR; rowNo>=stR && rowNo>=0; rowNo-- ) {                         // 逆順に処理します。
    1253                                final Row rowObj = sht.getRow( rowNo );
    1254                                if( rowObj != null ) {
    1255                                        final int stC = rowObj.getFirstCellNum();
    1256                                        final int edC = rowObj.getLastCellNum();
    1257                                        // 8.0.1.0 (2021/10/29) 各行の最初のｾﾙｽﾀｲﾙをﾍﾞｰｽとして比較する。
    1258
    1259                                        if( stC >= 0 && edC >= 0 ) {                                                            // 8.0.3.0 (2021/12/17) 存在しない場合もある。
    1260                                                final CellStyle endCellStyle = rowObj.getCell( stC ).getCellStyle();    // nullﾁｪｯｸ入れてない…
    1261                                                for( int colNo=edC; colNo>=stC && colNo>=0; colNo-- ) {         // 6.0.2.5 (2014/10/31) stC,edC が、ﾏｲﾅｽのｹｰｽがある。
    1262                                                        final Cell colObj = rowObj.getCell( colNo );
    1263                                                        if( colObj != null ) {
    1264                                                                final String val = getValue( colObj );
    1265                                                                if( colObj.getCellType() != CellType.BLANK && val != null && val.length() > 0 ) {                       // 7.0.0.0 (2018/10/01) poi-4.0.0 Deprecated.
    1266                                                                        isRowDel = false;                                       // 一つでも現れれば、行の削除は中止
    1267                                                                        break;
    1268                                                                }
    1269                                                                // 6.0.2.3 (2014/10/10) CellStyle の有無も判定基準に含めます。
    1270                                                                // 8.0.1.0 (2021/10/29) 各行の最初のｾﾙｽﾀｲﾙをﾍﾞｰｽとして比較する。
    1271                //                                              else if( colObj.getCellStyle() != null ) {
    1272                                                                else if( ! endCellStyle.equals(colObj.getCellStyle()) ) {
    1273                                                                        isRowDel = false;                                       // 一つでも現れれば、行の削除は中止
    1274                                                                        break;
    1275                                                                }
    1276                                                                else if( isCellDel ) {
    1277                                                                        rowObj.removeCell( colObj );            // CELL_TYPE_BLANK の場合は、削除
    1278                                                                }
    1279                                                        }
    1280                                                }
    1281                                        }
    1282                                        if( isRowDel ) { sht.removeRow( rowObj );       }
    1283                                        else if( !isCellDel ) { break; }                                // Cell の末尾削除を行わない場合は、break すればよい。
    1284                                }
    1285                        }
    1286                }
    1287        }
    1288
    1289        /**
    1290         * 指定の Workbook の全Sheetを対象に、実際の有効行と有効ｶﾗﾑを元に全体をｼｭﾘﾝｸします。
    1291         *
    1292         * ※ 現在、唯一LibreOfficeでのみ、xslx 変換できますが、有効行とｶﾗﾑが
    1293         *    ｼｭﾘﾝｸされず、無駄な行とｶﾗﾑが存在します。
    1294         *    これは、xsl で出力されたﾌｧｲﾙから有効な値を取得して、xslxに適用させるための
    1295         *    機能で、本来きちんとした有効範囲の xslx が生成されれば、不要な処理です。
    1296         *
    1297         * 引数のListｵﾌﾞｼﾞｪｸﾄに従って、無条件に処理を行います。
    1298         *
    1299         * @og.rev 8.0.1.0 (2021/10/29) 全Sheetを対象に、実際の有効行と有効ｶﾗﾑを取得
    1300         * @og.rev 8.0.3.0 (2021/12/17) ｼｰﾄ毎の行数Listに変更。
    1301         *
    1302         * @param       wkbook          処理対象のWorkbook
    1303//       * @param       rcList          ｼｰﾄごとの有効行の配列ﾘｽﾄ
    1304         * @param       rowCntList      ｼｰﾄごとの有効行の配列ﾘｽﾄ
    1305//       * @see         #getLastRowCellNum( Workbook )
    1306         * @see         #activeWorkbook( Workbook,boolean )
    1307         */
    1308//       public static void activeWorkbook( final Workbook wkbook , final List<int[]> rcList ) {
    1309         public static void activeWorkbook( final Workbook wkbook , final List<Integer> rowCntList ) {
    1310                final int shCnt = wkbook.getNumberOfSheets();
    1311                for( int shNo=0; shNo<shCnt; shNo++ ) {
    1312                        final Sheet sht = wkbook.getSheetAt( shNo );
    1313//                      final int[] rowcol = rcList.get(shNo);                                          // ｼｰﾄ内の有効行と列
    1314                        final int stR = rowCntList.get(shNo);                                           // ｼｰﾄ内の有効行と列
    1315
    1316//                      final int stR = rowcol[0];
    1317                        final int edR = sht.getLastRowNum();                                            // 参考程度
    1318                        // edR～stRまでの行は、無条件に削除します。
    1319                        for( int rowNo=edR; rowNo>=stR && rowNo>=0; rowNo-- ) {         // 逆順に処理します。
    1320                                final Row rowObj = sht.getRow( rowNo );
    1321                                if( rowObj != null ) {
    1322                                        sht.removeRow( rowObj );
    1323                                }
    1324                        }
    1325
    1326                //      カラム列の削除は保留
    1327                //      // stR～0までの行は、有効行なので、ｶﾗﾑの処理を考えます。
    1328//              //      final int stC = rowcol[1];                                                                      // ｼｰﾄの中での有効ｶﾗﾑの最大値
    1329                //      final int stC = 0;
    1330                //      for( int rowNo=stR; rowNo>=0; rowNo-- ) {                                       // 逆順に処理します。
    1331                //              final Row rowObj = sht.getRow( rowNo );
    1332                //              if( rowObj != null ) {
    1333                //                      final int edC = rowObj.getLastCellNum();                        // 参考程度
    1334                //                      // edC～stCまでのｶﾗﾑは、無条件に削除します。
    1335                //                      for( int colNo=edC; colNo>=stC && colNo>=0; colNo-- ) { // 6.0.2.5 (2014/10/31) stC,edC が、ﾏｲﾅｽのｹｰｽがある。
    1336                //                              final Cell colObj = rowObj.getCell( colNo );
    1337                //                              if( colObj != null ) {
    1338                //                                      if( colObj.getCellType() == CellType.BLANK ) {
    1339                //                                              rowObj.removeCell( colObj );
    1340                //                                      }
    1341                //                                      else {
    1342                //                                              break;
    1343                //                                      }
    1344                //                              }
    1345                //                      }
    1346                //              }
    1347                //      }
    1348                }
    1349        }
    1350
    1351        /**
    1352         * ﾌｧｲﾙから、Workbookｵﾌﾞｼﾞｪｸﾄを新規に作成します。
    1353         *
    1354         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
    1355         * @og.rev 6.2.0.0 (2015/02/27) ﾌｧｲﾙ引数を、String → File に変更
    1356         * @og.rev 7.0.0.0 (2018/10/01) poi-4.0.0 例外InvalidFormatExceptionは対応するtry文の本体ではｽﾛｰされません
    1357         *
    1358         * @param       file    入力ﾌｧｲﾙ
    1359         * @return      Workbookｵﾌﾞｼﾞｪｸﾄ
    1360         * @og.rtnNotNull
    1361         */
    1362        public static Workbook createWorkbook( final File file ) {
    1363                InputStream fis = null;
    1364                try {
    1365                        // File ｵﾌﾞｼﾞｪｸﾄでcreate すると、ﾌｧｲﾙがｵｰﾌﾟﾝされたままになってしまう。
    1366                        fis = new BufferedInputStream( new FileInputStream( file ) );
    1367                        return WorkbookFactory.create( fis );
    1368                }
    1369                catch( final IOException ex ) {
    1370                        final String errMsg = "ﾌｧｲﾙ読込みｴﾗｰ[" + file + "]" + CR + ex.getMessage() ;
    1371                        throw new OgRuntimeException( errMsg,ex );
    1372                }
    1373                // 7.0.0.0 (2018/10/01) poi-4.0.0 対応するtry文の本体ではｽﾛｰされません
    1374//              catch( final InvalidFormatException ex ) {
    1375//                      final String errMsg = "ﾌｧｲﾙ形式ｴﾗｰ[" + file + "]" + CR + ex.getMessage() ;
    1376//                      throw new OgRuntimeException( errMsg,ex );
    1377//              }
    1378                finally {
    1379                        Closer.ioClose( fis );
    1380                }
    1381        }
    1382
    1383        /**
    1384         * ｼｰﾄ一覧を、Workbook から取得します。
    1385         *
    1386         * 取得元が、Workbook なので、xls , xlsx どちらの形式でも取り出せます。
    1387         *
    1388         * EXCEL上のｼｰﾄ名を、配列で返します。
    1389         *
    1390         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
    1391         *
    1392         * @param       wkbook  Workbookｵﾌﾞｼﾞｪｸﾄ
    1393         * @return      ｼｰﾄ名の配列
    1394         */
    1395        public static String[] getSheetNames( final Workbook wkbook ) {
    1396                final int shCnt = wkbook.getNumberOfSheets();
    1397
    1398                String[] shtNms = new String[shCnt];
    1399
    1400                for( int i=0; i<shCnt; i++ ) {
    1401                        final Sheet sht = wkbook.getSheetAt( i );
    1402                        shtNms[i] = sht.getSheetName();
    1403                }
    1404
    1405                return shtNms;
    1406        }
    1407
    1408        /**
    1409         * 名前定義一覧を取得します。
    1410         *
    1411         * EXCEL上に定義された名前を、配列で返します。
    1412         * ここでは、名前とFormulaをﾀﾌﾞで連結した文字列を配列で返します。
    1413         * Name ｵﾌﾞｼﾞｪｸﾄを削除すると、EXCELが開かなくなったりするので、
    1414         * 取りあえず一覧を作成して、手動で削除してください。
    1415         * なお、名前定義には、非表示というのがありますので、ご注意ください。
    1416         *
    1417         * ◆ 非表示になっている名前の定義を表示にする EXCEL VBA ﾏｸﾛ
    1418         * http://dev.classmethod.jp/tool/excel-delete-name/
    1419         *    Sub VisibleNames()
    1420         *        Dim name
    1421         *        For Each name In ActiveWorkbook.Names
    1422         *            If name.Visible = False Then
    1423         *                name.Visible = True
    1424         *            End If
    1425         *        Next
    1426         *        MsgBox "すべての名前の定義を表示しました。", vbOKOnly
    1427         *    End Sub
    1428         *
    1429         * ※ EXCEL2010 数式ﾀﾌﾞ→名前の管理 で、複数選択で、削除できます。
    1430         *    ただし、非表示の名前定義は、先に表示しないと、削除できません。
    1431         * ◆ 名前の一括削除 EXCEL VBA ﾏｸﾛ
    1432         * http://komitsudo.blog70.fc2.com/blog-entry-104.html
    1433         *    Sub DeleteNames()
    1434         *        Dim name
    1435         *        On Error Resume Next
    1436         *        For Each name In ActiveWorkbook.Names
    1437         *            If Not name.BuiltIn Then
    1438         *                name.Delete
    1439         *            End If
    1440         *        Next
    1441         *        MsgBox "すべての名前の定義を削除しました。", vbOKOnly
    1442         *    End Sub
    1443         *
    1444         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
    1445         * @og.rev 7.0.0.0 (2018/10/01) 警告:[deprecation] WorkbookのgetNameAt(int)は推奨されません (POI4.0.0)
    1446         *
    1447         * @param       wkbook  Workbookｵﾌﾞｼﾞｪｸﾄ
    1448         * @return      名前定義(名前+TAB+Formula)の配列
    1449         * @og.rtnNotNull
    1450         */
    1451        public static String[] getNames( final Workbook wkbook ) {
    1452//              final int cnt = wkbook.getNumberOfNames();
    1453
    1454                final Set<String> nmSet = new TreeSet<>();
    1455
    1456                // 7.0.0.0 (2018/10/01) 警告:[deprecation] WorkbookのgetNameAt(int)は推奨されません (POI4.0.0)
    1457//              for( int i=0; i<cnt; i++ ) {
    1458                for( final Name nm : wkbook.getAllNames() ) {
    1459                        String name     = null;
    1460                        String ref      = null;
    1461
    1462//                      final Name nm = wkbook.getNameAt(i);
    1463                        try {
    1464                                name = nm.getNameName();
    1465                                ref  = nm.getRefersToFormula();
    1466                        }
    1467        //              catch( final Exception ex ) {                                   // 6.1.0.0 (2014/12/26) refactoring
    1468                        catch( final RuntimeException ex ) {
    1469                                final String errMsg = "POIUtil:RefersToFormula Error! name=[" + name + "]" + ex.getMessage() ;
    1470                                System.out.println( errMsg );
    1471                                // Excel97形式の場合、getRefersToFormula() でｴﾗｰが発生することがある。
    1472                        }
    1473
    1474                        nmSet.add( name + "\t" + ref );
    1475
    1476                        // 削除するとEXCELが壊れる？ なお、削除時には逆順で廻さないとｱﾄﾞﾚｽがずれます。
    1477                        // if( nm.isDeleted() ) { wkbook.removeName(i); }
    1478                }
    1479
    1480                return nmSet.toArray( new String[nmSet.size()] );
    1481        }
    1482
    1483        /**
    1484         * 書式のｽﾀｲﾙ一覧を取得します。
    1485         *
    1486         * EXCEL上に定義された書式のｽﾀｲﾙを、配列で返します。
    1487         * 書式のｽﾀｲﾙの名称は、CellStyle にﾒｿｯﾄﾞが定義されていません。
    1488         * 実ｸﾗｽである HSSFCellStyle にｷｬｽﾄして使用する
    1489         * 必要があります。(XSSFCellStyle にも名称を取得するﾒｿｯﾄﾞがありません。)
    1490         *
    1491         * ※ EXCEL2010 ﾎｰﾑﾀﾌﾞ→ｾﾙのｽﾀｲﾙ は、一つづつしか削除できません。
    1492         *    ﾏｸﾛは、開発ﾀﾌﾞ→Visual Basic で、挿入→標準ﾓｼﾞｭｰﾙ を開き
    1493         *    ﾃｷｽﾄを張り付けてください。
    1494         *    実行は、開発ﾀﾌﾞ→ﾏｸﾛ で、ﾏｸﾛ名を選択して、実行します。
    1495         *    最後は、削除してください。
    1496         *
    1497         * ◆ ｽﾀｲﾙの一括削除 EXCEL VBA ﾏｸﾛ
    1498         * http://komitsudo.blog70.fc2.com/blog-entry-104.html
    1499         *    Sub DeleteStyle()
    1500         *        Dim styl
    1501         *        On Error Resume Next
    1502         *        For Each styl In ActiveWorkbook.Styles
    1503         *            If Not styl.BuiltIn Then
    1504         *                styl.Delete
    1505         *            End If
    1506         *        Next
    1507         *        MsgBox "すべての追加ｽﾀｲﾙを削除しました。", vbOKOnly
    1508         *    End Sub
    1509         *
    1510         * ◆ 名前の表示、削除、ｽﾀｲﾙの削除の一括実行 EXCEL VBA ﾏｸﾛ
    1511         *    Sub AllDelete()
    1512         *        Call VisibleNames
    1513         *        Call DeleteNames
    1514         *        Call DeleteStyle
    1515         *        MsgBox "すべての処理を完了しました。", vbOKOnly
    1516         *    End Sub
    1517         *
    1518         * @og.rev 6.0.2.3 (2014/10/10) 新規作成
    1519         *
    1520         * @param       wkbook  Workbookｵﾌﾞｼﾞｪｸﾄ
    1521         * @return      書式のｽﾀｲﾙ一覧
    1522         * @og.rtnNotNull
    1523         */
    1524        public static String[] getStyleNames( final Workbook wkbook ) {
    1525                final int cnt = wkbook.getNumCellStyles();              // return 値は、short
    1526
    1527                final Set<String> nmSet = new TreeSet<>();
    1528
    1529                for( int s=0; s<cnt; s++ ) {
    1530                        final CellStyle cs = wkbook.getCellStyleAt( (short)s );
    1531                        if( cs instanceof HSSFCellStyle ) {
    1532                                final HSSFCellStyle hcs = (HSSFCellStyle)cs;
    1533                                final String name = hcs.getUserStyleName();
    1534                                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    1535                                if( name == null ) {                                                    // この処理は不要かも。
    1536                                        final HSSFCellStyle pst = hcs.getParentStyle();
    1537                                        if( pst != null ) {
    1538                                                final String pname = pst.getUserStyleName();
    1539                                                if( pname != null ) { nmSet.add( pname ); }
    1540                                        }
    1541                                }
    1542                                else {
    1543                                        nmSet.add( name );
    1544                                }
    1545                        }
    1546                }
    1547
    1548                return nmSet.toArray( new String[nmSet.size()] );
    1549        }
    1550
    1551        /**
    1552         * ｾﾙ情報を返します。
    1553         *
    1554         * ｴﾗｰ発生時に、どのｾﾙでｴﾗｰが発生したかの情報を取得できるようにします。
    1555         *
    1556         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    1557         * @og.rev 6.0.3.0 (2014/11/13) ｾﾙ情報を作成する時に、値もｾｯﾄします。
    1558         * @og.rev 6.2.2.0 (2015/03/27) celKigo を求めるﾛｼﾞｯｸ変更
    1559         * @og.rev 6.3.1.0 (2015/06/28) rowNo(行番号)も引数に取るようにします。
    1560         *
    1561         * @param       oCell   EXCELのｾﾙｵﾌﾞｼﾞｪｸﾄ
    1562         * @return      ｾﾙ情報の文字列
    1563         */
    1564        public static String getCellMsg( final Cell oCell ) {
    1565                String lastMsg = null;
    1566
    1567                if( oCell != null ) {
    1568                        final String shtNm = oCell.getSheet().getSheetName();
    1569                        final int  rowNo = oCell.getRowIndex();
    1570                        final int  celNo = oCell.getColumnIndex();
    1571
    1572                        // 6.0.3.0 (2014/11/13) ｾﾙ情報を作成する時に、値もｾｯﾄします。
    1573                        lastMsg = "  Sheet=" + shtNm + ", Row=" + rowNo + ", Cel=" + celNo
    1574                                                 + "(" + getCelKigo(rowNo,celNo) + "), Val=" + oCell.toString() ;
    1575                }
    1576
    1577                return lastMsg;
    1578        }
    1579
    1580        /**
    1581         * Excelの行番号,列番号より、ｾﾙ記号を求めます。
    1582         *
    1583         * 行番号は、0から始まる数字ですが、記号化する場合は、１から始まります。
    1584         * Excelの列記号とは、A,B,C,…,Z,AA,AB,…,ZZ,AAA,AAB,… と続きます。
    1585         * つまり、ｱﾙﾌｧﾍﾞｯﾄだけの、２６進数になります。(ｾﾞﾛの扱いが少し特殊です)
    1586         * 列番号は、0から始まる数字で、0=A,1=B,2=C,…,25=Z,26=AA,27=AB,…,701=ZZ,702=AAA,703=AAB,…
    1587         * EXCELの行列記号にする場合は、この列記号に、行番号を、＋１して付ければよいだけです。
    1588         * (※ 列番号に＋１するのは、内部では０から始まる列番号ですが、表示上は１から始まります)
    1589         *
    1590         * @og.rev 6.2.2.0 (2015/03/27) celKigo を求めるﾛｼﾞｯｸ変更
    1591         * @og.rev 6.3.1.0 (2015/06/28) rowNo(行番号)も引数に取るようにします。
    1592         *
    1593         * @param       rowNo   行番号(0,1,2,…)
    1594         * @param       colNo   列番号(0,1,2,…)
    1595         * @return      Excelの列記号(A1,B2,C3,…)
    1596         */
    1597        public static String getCelKigo( final int rowNo,final int colNo ) {
    1598                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    1599                int cnt = colNo;
    1600                while( cnt >= 26 ) {
    1601                        buf.append( (char)('A'+cnt%26) );
    1602                        cnt = cnt/26-1;
    1603                }
    1604                buf.append( (char)('A'+cnt%26) )
    1605                        .reverse()                                                              // append で逆順に付けているので、反転して返します。
    1606                        .append( rowNo+1 );
    1607
    1608                return buf.toString();
    1609        }
    1610
    1611        /**
    1612         * XSSFSimpleShapeｵﾌﾞｼﾞｪｸﾄにﾘﾝｸを設定します。
    1613         *
    1614         * 処理の簡素化のために、url引数が null の場合は、何もせず処理を終了します。
    1615         *
    1616         * @og.rev 8.1.0.1 (2022/01/07) ﾃｷｽﾄﾍﾞｰｽのﾘﾝｸ作成
    1617         *
    1618         * @param       shape   XSSFSimpleShapeｵﾌﾞｼﾞｪｸﾄ
    1619         * @param       url             ﾘﾝｸ文字列
    1620         */
    1621        public static void makeShapeLink( final XSSFSimpleShape shape , final String url ) {
    1622                if( url == null ) { return; }
    1623
    1624                final String rid = shape.getDrawing()                   // XSSFDrawing  XSSFShape#getDrawing()
    1625                                                        .getPackagePart()                       // PackagePart  POIXMLDocumentPart#getPackagePart()
    1626                                                        .addExternalRelationship(       // PackageRelationship  PackagePart#addExternalRelationship(String,String)
    1627                                                                        url, PackageRelationshipTypes.HYPERLINK_PART)
    1628                                                        .getId();                                       // String               PackageRelationship#getId()
    1629
    1630                final CTHyperlink hyperlink = CTHyperlink.Factory.newInstance();
    1631                hyperlink.setId(rid);
    1632
    1633                shape.getCTShape()                                                              // CTShape                                      XSSFSimpleShape#getCTShape()
    1634                        .getNvSpPr()                                                            // CTShapeNonVisual                     CTShape#getNvSpPr()
    1635                        .getCNvPr()                                                                     // CTNonVisualDrawingProps      CTShapeNonVisual#getCNvPr()
    1636                        .setHlinkClick( hyperlink );                            // void                                         CTNonVisualDrawingProps#setHlinkClick(CTHyperlink)
    1637        }
    1638
    1639        /**
    1640         * XSSFSimpleShapeｵﾌﾞｼﾞｪｸﾄにｶﾗｰを設定します。
    1641         *
    1642         * 処理の簡素化のために、col引数が null の場合は、何もせず処理を終了します。
    1643         * col配列は、[0]:red [1]:blue [2] green です。
    1644         *
    1645         * ※ ｶﾗｰの設定を、XSSFSimpleShape#setFillColor(int,int,int) で行うと、XSLXﾌｧｲﾙが
    1646         * 壊れるようです。POIが対応できていないのか、ｶﾗｰ化設定方法を間違っているのか…
    1647         *
    1648         * @og.rev 8.1.0.1 (2022/01/07) ﾃｷｽﾄﾍﾞｰｽのｶﾗｰ作成
    1649         *
    1650         * @param       shape   XSSFSimpleShapeｵﾌﾞｼﾞｪｸﾄ
    1651         * @param       col             色配列
    1652         */
    1653        public static void makeShapeColor( final XSSFSimpleShape shape , final int[] col ) {
    1654                if( col != null ) {
    1655                        shape.setFillColor(col[0],col[1],col[2]);
    1656                }
    1657        }
    1658
    1659        /**
    1660         * ｱﾌﾟﾘｹｰｼｮﾝのｻﾝﾌﾟﾙです。
    1661         *
    1662         * 入力ﾌｧｲﾙ名 は必須で、第一引数固定です。
    1663         * 第二引数は、処理方法で、-ALL か、-LINE を指定します。何も指定しなければ、-ALL です。
    1664         * 第三引数を指定した場合は、Encode を指定します。
    1665         *
    1666         * Usage: java org.opengion.fukurou.model.POIUtil 入力ﾌｧｲﾙ名 [処理方式] [ｴﾝｺｰﾄﾞ]
    1667         *   -A(LL)        ･･･ ALL 一括処理(初期値)
    1668         *   -L(INE)       ･･･ LINE 行単位処理
    1669         *   -S(heet)      ･･･ Sheet名一覧
    1670         *   -N(AME)       ･･･ NAME:名前定義
    1671         *   -C(ellStyle)  ･･･ CellStyle:書式のｽﾀｲﾙ
    1672         *
    1673         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
    1674         * @og.rev 6.2.3.0 (2015/05/01) ﾊﾟﾗﾒｰﾀ変更、textReader → extractor に変更
    1675         * @og.rev 6.2.4.2 (2015/05/29) 引数判定の true/false の処理が逆でした。
    1676         * @og.rev 6.3.9.0 (2015/11/06) Java 8 ﾗﾑﾀﾞ式に変更
    1677         *
    1678         * @param       args    ｺﾏﾝﾄﾞ引数配列
    1679         */
    1680        public static void main( final String[] args ) {
    1681                final String usageMsg = "Usage: java org.opengion.fukurou.model.POIUtil 入力ﾌｧｲﾙ名 [処理方式] [ｴﾝｺｰﾄﾞ]" + "\n" +
    1682                                                                "\t -A(LL)        ･･･ ALL 一括処理(初期値)      \n" +
    1683                                                                "\t -L(INE)       ･･･ LINE 行単位処理           \n" +
    1684                                                                "\t -S(heet)      ･･･ Sheet名一覧               \n" +
    1685                                                                "\t -N(AME)       ･･･ NAME:名前定義             \n" +
    1686                                                                "\t -C(ellStyle)  ･･･ CellStyle:書式のｽﾀｲﾙ  \n" ;
    1687                if( args.length == 0 ) {
    1688                        System.err.println( usageMsg );
    1689                        return ;
    1690                }
    1691
    1692                final File file = new File( args[0] );
    1693                final char type     = args.length >= 2 ? args[1].charAt(1) : 'A' ;
    1694                final String encode = args.length >= 3 ? args[2] : null ;                               // 6.2.4.2 (2015/05/29) true/false の処理が逆でした。
    1695
    1696                switch( type ) {
    1697                        case 'A' :  if( encode == null ) {
    1698                                                        System.out.println( POIUtil.extractor( file ) );
    1699                                                }
    1700                                                else {
    1701                                                        System.out.println( POIUtil.extractor( file,encode ) );
    1702                                                }
    1703                                                break;
    1704                        // 6.3.9.0 (2015/11/06) Java 8 ﾗﾑﾀﾞ式に変更
    1705                        case 'L' : final TextConverter<String,String> conv =
    1706                                                                ( val,cmnt ) -> {
    1707                                                                        System.out.println( "val=" + val + " , cmnt=" + cmnt );
    1708                                                                        return null;
    1709                                                                };
    1710
    1711                                        //              new TextConverter<String,String>() {
    1712                                        //                      /**
    1713                                        //                       * 入力文字列を、変換します。
    1714                                        //                       *
    1715                                        //                       * @param       val  入力文字列
    1716                                        //                       * @param       cmnt ｺﾒﾝﾄ
    1717                                        //                       * @return      変換文字列(変換されない場合は、null)
    1718                                        //                       */
    1719                                        //                      @Override
    1720                                        //                      public String change( final String val , final String cmnt ) {
    1721                                        //                              System.out.println( "val=" + val + " , cmnt=" + cmnt );
    1722                                        //                              return null;
    1723                                        //                      }
    1724                                        //              };
    1725
    1726                                                if( encode == null ) {
    1727                                                        POIUtil.textReader( file,conv );
    1728                                                }
    1729                                                else {
    1730                                                        POIUtil.textReader( file,conv,encode );
    1731                                                }
    1732                                                break;
    1733                        case 'S' :  final String[] shts = POIUtil.getSheetNames( POIUtil.createWorkbook(file) );
    1734                                                System.out.println( "No:\tSheetName" );
    1735                                                for( int i=0; i<shts.length; i++ ) {
    1736                                                        System.out.println( i + "\t" + shts[i] );
    1737                                                }
    1738                                                break;
    1739                        case 'N' :  final String[] nms = POIUtil.getNames( POIUtil.createWorkbook(file) );
    1740                                                System.out.println( "No:\tName\tFormula" );
    1741                                                for( int i=0; i<nms.length; i++ ) {
    1742                                                        System.out.println( i + "\t" + nms[i] );
    1743                                                }
    1744                                                break;
    1745                        case 'C' :  final String[] sns = POIUtil.getStyleNames( POIUtil.createWorkbook(file) );
    1746                                                System.out.println( "No:\tStyleName" );
    1747                                                for( int i=0; i<sns.length; i++ ) {
    1748                                                        System.out.println( i + "\t" + sns[i] );
    1749                                                }
    1750                                                break;
    1751                        default :   System.err.println( usageMsg );
    1752                                                break;
    1753                }
    1754        }
    1755}