    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.util;
    017
    018import java.io.InputStream;
    019import java.io.FileInputStream;
    020import java.io.BufferedInputStream;
    021import java.io.IOException;
    022
    023import java.text.DecimalFormat;
    024import java.text.NumberFormat;
    025import java.util.Locale;
    026
    027import org.apache.poi.extractor.ExtractorFactory;
    028import org.apache.poi.POITextExtractor;
    029
    030import org.apache.poi.hwpf.HWPFDocument;
    031import org.apache.poi.hwpf.usermodel.Range;
    032import org.apache.poi.hwpf.usermodel.Section;
    033import org.apache.poi.hwpf.usermodel.Paragraph;
    034import org.apache.poi.hwpf.usermodel.CharacterRun;
    035// import org.apache.poi.hwpf.usermodel.HeaderStories;
    036
    037import org.apache.poi.hslf.HSLFSlideShow;
    038import org.apache.poi.hslf.usermodel.SlideShow;
    039import org.apache.poi.hslf.model.Slide;
    040import org.apache.poi.hslf.model.TextRun;
    041
    042import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
    043
    044import org.apache.poi.ss.usermodel.WorkbookFactory;
    045import org.apache.poi.ss.usermodel.Workbook;
    046import org.apache.poi.ss.usermodel.Sheet;
    047import org.apache.poi.ss.usermodel.Row;
    048import org.apache.poi.ss.usermodel.Cell;
    049
    050import org.apache.poi.ss.usermodel.CreationHelper;
    051
    052import org.apache.poi.ss.usermodel.DateUtil;
    053import org.apache.poi.ss.usermodel.RichTextString;
    054import org.apache.poi.ss.usermodel.FormulaEvaluator;
    055
    056import org.opengion.fukurou.util.Closer;
    057import org.opengion.fukurou.util.HybsDateUtil;
    058
    059/**
    060 * POI による、Excel/Word/PoworPoint等に対する、ユーティリティクラスです。
    061 *
    062 * 基本的には、ネイティブファイルを読み取り、テキストを取得する機能が主です。
    063 * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。
    064 *
    065 * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    066 * @og.group その他
    067 *
    068 * @version  6.0
    069 * @author   Kazuhiko Hasegawa
    070 * @since    JDK7.0,
    071 */
    072public class POIUtil {
    073        /** このプログラムのVERSION文字列を設定します。   {@value} */
    074        private static final String VERSION = "6.0.2.0 (2014/08/29)" ;
    075
    076        private static final String CR = System.getProperty("line.separator");
    077
    078        /**
    079         * 引数ファイルを、POITextExtractor を使用してテキスト化します。
    080         *
    081         * Excel、Word、PowerPoint、Visio、Publisher からのテキスト取得が可能です。
    082         * 拡張子から、ファイルの種類を自動判別します。
    083         *
    084         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    085         *
    086         * @param       fname 入力ファイル名
    087         * @return      変換後のテキスト
    088         */
    089        public static final String getText( final String fname ) {
    090                InputStream fis = null;
    091                POITextExtractor extractor = null;
    092                try {
    093                        fis = new BufferedInputStream( new FileInputStream( fname ) );
    094                        extractor = ExtractorFactory.createExtractor( fis );
    095                        return extractor.getText();
    096                }
    097                catch( Exception ex ) {
    098                        String errMsg = "ファイル処理エラー[" + fname + "]" + CR + ex.getMessage() ;
    099                        throw new RuntimeException( errMsg,ex );
    100                }
    101                finally {
    102                        Closer.ioClose( extractor );
    103                        Closer.ioClose( fis );
    104                }
    105        }
    106
    107        /**
    108         * 引数ファイル(Word)を、HWPFDocument を使用してテキスト化します。
    109         *
    110         * POIEventは、唯一の void readText( String,String int... ) メソッドを持ち、
    111         * 処理単位に、このメソッドが呼ばれます。つまり、内部にメモリを溜め込みません。
    112         * int...可変長引数は、0:Section番号 1:page番号 2:Paragraph番号 がセットされます。
    113         *
    114         * POIEventは、データとして設定されているレコードのみCallされます。
    115         *
    116         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    117         *
    118         * @param       fname 入力ファイル名
    119         * @param       event イベント処理させるI/F
    120         */
    121        public static final void wordReader( final String fname , final POIEvent event ) {
    122                InputStream fis = null;
    123                StringBuilder buf = new StringBuilder(200);
    124
    125                try {
    126                        fis = new BufferedInputStream( new FileInputStream( fname ) );
    127                        HWPFDocument doc = new HWPFDocument( fis );
    128                        Range r = doc.getRange();
    129                        // HeaderStories header = new HeaderStories( doc );
    130
    131                        int pCnt = 0;           // ページ番号
    132                        for (int sno = 0; sno < r.numSections(); sno++) {
    133                                Section sec = r.getSection(sno);
    134                                String title = null;                                                                    // HeaderStories からうまく取れなかった。
    135                                for (int pno = 0; pno < sec.numParagraphs(); pno++) {
    136                                        Paragraph para = sec.getParagraph(pno);
    137
    138                                        if( para.text().indexOf("\f") >= 0 ) {
    139                                                pCnt++;
    140                        //                      title = header.getHeader( pCnt ).trim();
    141                                        }
    142
    143                                        buf.setLength(0);               // Clearの事
    144                                        for (int cno = 0; cno < para.numCharacterRuns(); cno++) {
    145                                                CharacterRun run = para.getCharacterRun(cno);
    146                                                // Removes any fields (eg macros, page markers etc) from the string
    147                                                buf.append( Range.stripFields( run.text() ) );
    148                                        }
    149                                        String text = buf.toString().trim();
    150                                        // データとして設定されているレコードのみイベントを発生させる。
    151                                        if( text.length() > 0 ) {
    152                                                if( title == null ) { title = text; }           // Section の最初の有効なParagraphをタイトルにする。
    153                                                event.readText( text,title,sno,pCnt,pno );      // テキスト タイトル 0:Section番号 1:page番号 2:Paragraph番号
    154                                        }
    155                                }
    156                        }
    157                }
    158                catch( IOException ex ) {
    159                        String errMsg = "ファイル読込みエラー[" + fname + "]" + CR + ex.getMessage() ;
    160                        throw new RuntimeException( errMsg,ex );
    161                }
    162                finally {
    163                        Closer.ioClose( fis );
    164                }
    165        }
    166
    167        /**
    168         * 引数ファイル(PoworPoint)を、HSLFSlideShow を使用してテキスト化します。
    169         *
    170         * POIEventは、唯一の void readText( String,String int... ) メソッドを持ち、
    171         * 処理単位に、このメソッドが呼ばれます。つまり、内部にメモリを溜め込みません。
    172         * int...可変長引数は、0:Slide番号 1:TextRun番号 がセットされます。
    173         *
    174         * POIEventは、データとして設定されているレコードのみCallされます。
    175         *
    176         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    177         *
    178         * @param       fname 入力ファイル名
    179         * @param       event イベント処理させるI/F
    180         */
    181        public static final void pptReader( final String fname , final POIEvent event ) {
    182                InputStream fis = null;
    183
    184                try {
    185                        fis = new BufferedInputStream( new FileInputStream( fname ) );
    186                        SlideShow ss = new SlideShow( new HSLFSlideShow( fis ) );
    187                        Slide[] slides = ss.getSlides();
    188                        for (int sno = 0; sno < slides.length; sno++) {
    189                                String    title   = slides[sno].getTitle();                             // title は、あまり当てにならない。
    190                                TextRun[] textRun = slides[sno].getTextRuns();
    191                                for (int tno = 0; tno < textRun.length; tno++) {
    192                                        String text = textRun[tno].getText().trim();
    193                                        // データとして設定されているレコードのみイベントを発生させる。
    194                                        if( text.length() > 0 ) {
    195                                                event.readText( text,title,sno,tno );                   // テキスト タイトル 0:Slide番号 1:TextRun番号
    196                                        }
    197                                }
    198                        }
    199                }
    200                catch( IOException ex ) {
    201                        String errMsg = "ファイル読込みエラー[" + fname + "]" + CR + ex.getMessage() ;
    202                        throw new RuntimeException( errMsg,ex );
    203                }
    204                finally {
    205                        Closer.ioClose( fis );
    206                }
    207        }
    208
    209        /**
    210         * 引数ファイル(Excel)を、Workbook を使用してテキスト化します。
    211         *
    212         * POIEventは、唯一の void readText( String,String int... ) メソッドを持ち、
    213         * 処理単位に、このメソッドが呼ばれます。つまり、内部にメモリを溜め込みません。
    214         * int...可変長引数は、0:Sheet番号 1:Row番号 2:Cell番号 がセットされます。
    215         *
    216         * EXCEL については、詳細に処理するための、ExcelModel クラスが別にあります。
    217         * この処理は、簡易的に処理する場合に、使用できます。
    218         *
    219         * POIEventは、データとして設定されているレコードのみCallされます。
    220         *
    221         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    222         *
    223         * @param       fname 入力ファイル名
    224         * @param       event イベント処理させるI/F
    225         * @see         org.opengion.fukurou.model.ExcelModel
    226         * @see         #excelReader( String , POIEvent , boolean )
    227         */
    228        public static final void excelReader( final String fname , final POIEvent event ) {
    229                excelReader( fname,event,false );
    230        }
    231
    232        /**
    233         * 引数ファイル(Excel)を、Workbook を使用してテキスト化します。
    234         *
    235         * POIEventは、唯一の void readText( String,String int... ) メソッドを持ち、
    236         * 処理単位に、このメソッドが呼ばれます。つまり、内部にメモリを溜め込みません。
    237         * int...可変長引数は、0:Sheet番号 1:Row番号 2:Cell番号 がセットされます。
    238         *
    239         * EXCEL については、詳細に処理するための、ExcelModel クラスが別にあります。
    240         * この処理は、簡易的に処理する場合に、使用できます。
    241         *
    242         * isFull属性に true を設定すると、すべての行列について、POIEvent が Callされます。
    243         * 行が存在しない場合、カラムが存在しない場合は、null が設定されます。
    244         * 行が存在しない場合のカラム番号は、-1 が設定されます。
    245         *
    246         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    247         *
    248         * @param       fname 入力ファイル名
    249         * @param       event イベント処理させるI/F
    250         * @param       isFull すべての行列について、イベント処理する場合は、true
    251         * @see         org.opengion.fukurou.model.ExcelModel
    252         * @see         #excelReader( String , POIEvent )
    253         */
    254        public static final void excelReader( final String fname , final POIEvent event , final boolean isFull ) {
    255                InputStream fis = null;
    256
    257                try {
    258                        fis = new BufferedInputStream( new FileInputStream( fname ) );
    259                        Workbook wkbook = WorkbookFactory.create( fis );
    260                        for (int x = 0; x < wkbook.getNumberOfSheets(); x++) {
    261                                Sheet sheet = wkbook.getSheetAt( x );
    262                                String sName = sheet.getSheetName();
    263                                int stR = isFull ? 0 : sheet.getFirstRowNum();                          // fullの場合は、0 から開始
    264                                int edR = sheet.getLastRowNum();
    265                                for (int y = stR; y <= edR; y++) {                                                      // getLastRowNum は、含む
    266                                        Row rowObj = sheet.getRow( y );
    267                                        if( rowObj != null ) {
    268                                                int stC = isFull ? 0 : rowObj.getFirstCellNum();        // fullの場合は、0 から開始
    269                                                int edC = rowObj.getLastCellNum();
    270                                                for (int z = stC; z <= edC; z++) {                                      // getLastCellNum は、含む
    271                                                        Cell colObj = rowObj.getCell( z );
    272                                                        if( colObj != null && colObj.getCellType() != Cell.CELL_TYPE_BLANK ) {
    273                                                                String text = getValue( colObj );
    274                                                                event.readText( text,sName,x,y,z );                     // Cellテキスト Sheet名 0:Sheet番号 1:Row番号 2:Cell番号
    275                                                        }
    276                                                        else if( isFull ) {
    277                                                                // カラムが存在しない場合のイベント
    278                                                                event.readText( null,sName,x,y,z );                     // Cellテキスト Sheet名 0:Sheet番号 1:Row番号 2:Cell番号
    279                                                        }
    280                                                }
    281                                        }
    282                                        else if( isFull ) {
    283                                                // 行が存在しない場合のイベント
    284                                                event.readText( null,sName,x,y,-1 );                            // Cellテキスト Sheet名 0:Sheet番号 1:Row番号 2:Cell番号
    285                                        }
    286                                }
    287                        }
    288                }
    289                catch( IOException ex ) {
    290                        String errMsg = "ファイル読込みエラー[" + fname + "]" + CR + ex.getMessage() ;
    291                        throw new RuntimeException( errMsg,ex );
    292                }
    293                catch( InvalidFormatException ex ) {
    294                        String errMsg = "ファイル形式エラー[" + fname + "]" + CR + ex.getMessage() ;
    295                        throw new RuntimeException( errMsg,ex );
    296                }
    297                finally {
    298                        Closer.ioClose( fis );
    299                }
    300        }
    301
    302        /**
    303         * セルオブジェクト(Cell)から値を取り出します。
    304         *
    305         * セルオブジェクトが存在しない場合は、null を返します。
    306         *
    307         * @og.rev 3.8.5.3 (2006/08/07) 取り出し方法を少し修正
    308         * @og.rev 5.5.1.2 (2012/04/06) フォーマットセルを実行して、その結果を再帰的に処理する。
    309         *
    310         * @param       oCell EXCELのセルオブジェクト
    311         *
    312         * @return      セルの値
    313         */
    314        public static String getValue( final Cell oCell ) {
    315                if( oCell == null ) { return null; }
    316
    317                String strText = "";
    318                int nCellType = oCell.getCellType();
    319                switch(nCellType) {
    320                        case Cell.CELL_TYPE_NUMERIC:
    321                                        strText = getNumericTypeString( oCell );
    322                                        break;
    323                        case Cell.CELL_TYPE_STRING:
    324        // POI3.0               strText = oCell.getStringCellValue();
    325                                        RichTextString richText = oCell.getRichStringCellValue();
    326                                        if( richText != null ) {
    327                                                strText = richText.getString();
    328                                        }
    329                                        break;
    330                        case Cell.CELL_TYPE_FORMULA:
    331        // POI3.0               strText = oCell.getStringCellValue();
    332                                        // 5.5.1.2 (2012/04/06) フォーマットセルを実行して、その結果を再帰的に処理する。
    333                                        Workbook wb = oCell.getSheet().getWorkbook();
    334                                        CreationHelper crateHelper = wb.getCreationHelper();
    335                                        FormulaEvaluator evaluator = crateHelper.createFormulaEvaluator();
    336
    337                                        try {
    338                                                strText = getValue(evaluator.evaluateInCell(oCell));
    339                                        }
    340                                        catch ( Throwable th ) {
    341                                                String errMsg = "セルフォーマットが解析できません。[" + oCell.getCellFormula() + "]"
    342                                                                        + CR + getCellMsg( oCell );
    343                                                throw new RuntimeException( errMsg,th );
    344                                        }
    345                                        break;
    346                        case Cell.CELL_TYPE_BOOLEAN:
    347                                        strText = String.valueOf(oCell.getBooleanCellValue());
    348                                        break;
    349                        case Cell.CELL_TYPE_BLANK :
    350                        case Cell.CELL_TYPE_ERROR:
    351                                        break;
    352                        default :
    353                                break;
    354                }
    355                return strText.trim();
    356        }
    357
    358        /**
    359         * セル値が数字の場合に、数字か日付かを判断して、対応する文字列を返します。
    360         *
    361         * @og.rev 3.8.5.3 (2006/08/07) 新規追加
    362         * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
    363         *
    364         * @param       oCell EXCELのセルオブジェクト
    365         *
    366         * @return      数字の場合は、文字列に変換した結果を、日付の場合は、"yyyyMMddHHmmss" 形式で返します。
    367         */
    368        public static String getNumericTypeString( final Cell oCell ) {
    369                final String strText ;
    370
    371                double dd = oCell.getNumericCellValue() ;
    372                if( DateUtil.isCellDateFormatted( oCell ) ) {
    373                        strText = HybsDateUtil.getDate( DateUtil.getJavaDate( dd ).getTime() , "yyyyMMddHHmmss" );      // 5.5.7.2 (2012/10/09) HybsDateUtil を利用
    374                }
    375                else {
    376                        NumberFormat numFormat = NumberFormat.getInstance();
    377                        if( numFormat instanceof DecimalFormat ) {
    378                                ((DecimalFormat)numFormat).applyPattern( "#.####" );
    379                        }
    380                        strText = numFormat.format( dd );
    381                }
    382                return strText ;
    383        }
    384
    385        /**
    386         * セル情報を返します。
    387         *
    388         * エラー発生時に、どのセルでエラーが発生したかの情報を取得できるようにします。
    389         *
    390         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    391         *
    392         * @param       oCell EXCELのセルオブジェクト
    393         * @return      セル情報の文字列
    394         */
    395        public static String getCellMsg( final Cell oCell ) {
    396                String lastMsg = null;
    397
    398                if( oCell != null ) {
    399                        int rowNo = oCell.getRowIndex();
    400                        int celNo = oCell.getColumnIndex();
    401                        String shtNm = oCell.getSheet().getSheetName();
    402
    403                        lastMsg = " Sheet=" + shtNm + ", Row=" + rowNo + ", Cel=" + celNo ;
    404                }
    405
    406                return lastMsg;
    407        }
    408
    409        /**
    410         * アプリケーションのサンプルです。
    411         *
    412         * 入力ファイル名 は必須で、第一引数固定です。
    413         * 第二引数は、処理方法で、指定しない場合は、-ALL として処理します。
    414         *
    415         * Usage: java org.opengion.fukurou.util.POIUtil 入力ファイル名 [-A(LL)/-W(ORD)/-P(PT)/-E(XCEL)/-F(ULL EXCEL)]
    416         *   -A(LL)        ･･･ ALL 一括処理(初期値)
    417         *   -W(ORD)       ･･･ Word
    418         *   -P(PT)        ･･･ PoworPoint
    419         *   -E(XCEL)      ･･･ Excel
    420         *   -F(ULL EXCEL) ･･･ Full Excel
    421         *
    422         * @og.rev 6.0.2.0 (2014/08/29) 新規作成
    423         *
    424         * @param       args    コマンド引数配列
    425         */
    426        public static void main( final String[] args ) {
    427                final String usageMsg = "Usage: java org.opengion.fukurou.util.POIUtil 入力ファイル名 [-A(LL)/-W(ORD)/-P(PT)/-E(XCEL)/-F(ULL EXCEL)]" ;
    428                if( args.length == 0 ) {
    429                        System.err.println( usageMsg );
    430                        return ;
    431                }
    432
    433                String fname = args[0];
    434                char   type  = (args.length == 1) ? 'A' : args[1].charAt(1) ;
    435
    436                switch( type ) {
    437                        case 'A' :  System.out.println( POIUtil.getText(fname) );
    438                                                break;
    439                        case 'W' :  POIUtil.wordReader(
    440                                                        fname,
    441                                                        new POIEvent() {
    442                                                                public void readText( final String text , final String cmnt , final int... args ) {
    443                                                                        System.out.println( "S[" + args[0] + "],P[" + fname + "],G[" + args[2] + "],C[" + cmnt + "]=" + text );
    444                                                                }
    445                                                        }
    446                                                );
    447                                                break;
    448                        case 'P' :  POIUtil.pptReader(
    449                                                        fname,
    450                                                        new POIEvent() {
    451                                                                public void readText( final String text , final String cmnt , final int... args ) {
    452                                                //                      System.out.println( "S[" + args[0] + "],T[" + fname + "],C[" + cmnt + "]=" + text );
    453                                                                        System.out.println( "S[" + args[0] + "],T[" + fname + "]=" + text );
    454                                                                }
    455                                                        }
    456                                                );
    457                                                break;
    458                        case 'E' :  POIUtil.excelReader(
    459                                                        fname,
    460                                                        new POIEvent() {
    461                                                                public void readText( final String text , final String cmnt , final int... args ) {
    462                                                                        System.out.println( cmnt + ":S[" + args[0] + "],R[" + fname + "],C[" + args[2] + "]=" + text );
    463                                                                }
    464                                                        }
    465                                                );
    466                                                break;
    467                        case 'F' :  StringBuilder buf = new StringBuilder();
    468                                                POIUtil.excelReader(
    469                                                        fname,
    470                                                        new POIEvent() {
    471                                                                int bkSht = -1;         // シートブレーク
    472                                                                int bkRow = -1;         // 行ブレーク
    473
    474                                                                public void readText( final String text , final String cmnt , final int... args ) {
    475                                                                        if( bkSht != args[0] ) {                        // シートブレーク
    476                                                                                if( buf.length() > 0 ) {
    477                                                                                        System.out.println( buf );
    478                                                                                        buf.setLength(0);                       // Clearの事
    479                                                                                }
    480                                                                                System.out.println( "SheetName=" + cmnt + ":S[" + args[0] + "]" );
    481                                                                                bkSht = args[0];
    482                                                                                bkRow = 0;
    483                                                                        }
    484                                                                        if( bkRow != args[1] ) {                        // 行ブレーク
    485                                                                                bkRow = args[1];
    486                                                                                buf.append( CR );
    487                                                                        }
    488                                                                        buf.append( "\t" );
    489                                                                        if( text != null ) { buf.append( text ); }
    490                                                                }
    491                                                        },
    492                                                        true            // Full 行列処理
    493                                                );
    494                                                System.out.println( buf );                              // 処理が終了したかどうかはイベントでは判らない。
    495                                                break;
    496                        default :   System.err.println( usageMsg );
    497                                                break;
    498                }
    499        }
    500}