    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.hayabusa.report;
    017
    018import org.opengion.fukurou.system.OgRuntimeException ;                 // 6.4.2.0 (2016/01/29)
    019import org.opengion.hayabusa.common.HybsSystemException;
    020import org.opengion.fukurou.system.Closer ;
    021import static org.opengion.fukurou.system.HybsConst.CR ;                // 6.1.0.0 (2014/12/26)
    022import static org.opengion.fukurou.system.HybsConst.FS ;                // 6.1.0.0 (2014/12/26)
    023
    024import org.apache.poi.poifs.filesystem.POIFSFileSystem;
    025import org.apache.poi.hssf.usermodel.HSSFWorkbook;
    026import org.apache.poi.hssf.usermodel.HSSFSheet;
    027import org.apache.poi.hssf.usermodel.HSSFRow;
    028import org.apache.poi.hssf.usermodel.HSSFCell;
    029import org.apache.poi.hssf.usermodel.HSSFRichTextString;
    030
    031import java.io.FileInputStream;
    032import java.io.FileOutputStream;
    033import java.io.IOException;
    034
    035import java.util.regex.Pattern;
    036import java.util.regex.Matcher;
    037
    038/**
    039 * DBTableReport インターフェース を実装したネイティブEXCEL形式で出力するクラスです。
    040 * AbstractDBTableReport を継承していますので，writeReport() のみオーバーライドして，
    041 * 固定長文字ファイルの出力機能を実現しています。
    042 *
    043 * @og.group 帳票システム
    044 *
    045 * @version  4.0
    046 * @author   Kazuhiko Hasegawa
    047 * @since    JDK5.0,
    048 */
    049public class DBTableReport_Excel extends AbstractDBTableReport {
    050
    051        private static final String EXCEL_FILE_EXT        = ".xls";
    052        private static final Pattern PATTERN_KEY =
    053                          Pattern.compile("\\{@((\\w+?)(?:_(\\d+?))?)\\}", Pattern.MULTILINE);
    054
    055        // POIの解析した式の中に変な属性が付けられて、これを取り除く(patternExcludeInFormula)
    056        private static final Pattern PATTERN_EXIN =
    057                          Pattern.compile("ATTR\\(semiVolatile\\)", Pattern.MULTILINE);
    058
    059        private HSSFWorkbook wb ;
    060
    061        /**
    062         * デフォルトコンストラクター
    063         *
    064         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
    065         */
    066        public DBTableReport_Excel() { super(); }               // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
    067
    068        /**
    069         * DBTableModel から データを作成して,PrintWriter に書き出します。
    070         *
    071         */
    072        @Override
    073        public void writeReport() {
    074                setHeaderFooter();
    075                initReader();
    076                initWriter();
    077                changeSheet();
    078                close();
    079        }
    080
    081        /**
    082         * POIFSFileSystem を、初期化します。
    083         * これは、雛型ファイルの終端まで読取り、処理した場合、もう一度
    084         * 初めから読み込みなおす処理を行います。
    085         * 基本的に、書き込みも初期化する必要があります。
    086         *
    087         * メモリ上に読み込んで、繰り返し利用するかどうかは、実装依存です。
    088         *
    089         */
    090        @Override
    091        protected void initReader() {
    092                if( null != wb ) { wb = null; }
    093
    094                FileInputStream  istream = null;
    095                try {
    096                        istream = new FileInputStream(templateFile);
    097                        final POIFSFileSystem fs = new POIFSFileSystem(istream);
    098                        wb = new HSSFWorkbook(fs);
    099                }
    100                catch( final IOException ex ) {
    101                        final String errMsg = "ファイル名がオープン出来ませんでした。"
    102                                                + CR
    103                                                + "  File:" + templateFile;
    104                        throw new HybsSystemException( errMsg,ex );             // 3.5.5.4 (2004/04/15) 引数の並び順変更
    105                }
    106                finally {
    107                        Closer.ioClose( istream );      // 4.0.0 (2006/01/31) close 処理時の IOException を無視
    108                }
    109        }
    110
    111        /**
    112         * FileOutputStream を、初期化します。
    113         * これは、雛型ファイルを終端まで読取り、処理した場合、出力ファイル名を
    114         * 変えて、別ファイルとして出力する為のものです。
    115         * 基本的に、読取も初期化する必要があります。
    116         *
    117         * 現在の所、POIはメモリ上にExcelファイルを作成する為、作成したファイルの書く込むを
    118         * ファイル閉じる時点に伸ばされます。
    119         *
    120         */
    121        @Override
    122        protected void initWriter() {
    123                // ここでは処理を行いません。
    124        }
    125
    126        /**
    127         * リーダー、ライターの終了処理を行います。
    128         * このメソッドが呼ばれたタイミングで、実際にファイル出力を行います。
    129         *
    130         * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
    131         *
    132         */
    133        protected void close() {
    134                // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
    135                if( wb == null ) {
    136                        final String errMsg = "#initReader()を先に実行しておいてください。" ;
    137                        throw new OgRuntimeException( errMsg );
    138                }
    139
    140                final String filename = htmlDir + FS + htmlFileKey + EXCEL_FILE_EXT ;
    141
    142                FileOutputStream fileOut = null;
    143                try {
    144                        // Write the output to a file
    145                        fileOut = new FileOutputStream(filename);
    146                        wb.write(fileOut);
    147                }
    148                catch( final IOException ex ) {
    149                        wb = null;
    150                        final String errMsg = "ファイルが出力(書き込み)出来ませんでした。"
    151                                                + CR
    152                                                + "  File:" + filename;
    153                        throw new HybsSystemException( errMsg,ex );             // 3.5.5.4 (2004/04/15) 引数の並び順変更
    154                }
    155                finally {
    156                        Closer.ioClose( fileOut );      // 4.0.0 (2006/01/31) close 処理時の IOException を無視
    157                }
    158        }
    159
    160        /**
    161         * Excelの雛型をコピーして、そのシートに帳票データを埋め込みます。
    162         * いろいろな属性がある所に、適切に対応していく予定。
    163         * 各サブクラスで実装してください。
    164         *
    165         * @og.rev 4.3.4.0 (2008/12/01) POI3.2対応
    166         * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
    167         *
    168         */
    169        protected void changeSheet() {
    170                // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
    171                if( wb == null ) {
    172                        final String errMsg = "#initReader()を先に実行しておいてください。" ;
    173                        throw new OgRuntimeException( errMsg );
    174                }
    175
    176                final HSSFSheet patternSheet = wb.getSheetAt(0);
    177                while(!rowOver) {
    178                        final HSSFSheet sheet2 = wb.cloneSheet(0);
    179        //              HSSFRow oRow2;
    180                        final int nFirstRow = sheet2.getFirstRowNum();
    181                        final int nLastRow  = sheet2.getLastRowNum();
    182        //              int nTotalRows = patternSheet.getPhysicalNumberOfRows();
    183                        for( int nIndexRow=nFirstRow; nIndexRow<=nLastRow; nIndexRow++ ) {
    184                                final HSSFRow oRow = patternSheet.getRow(nIndexRow);
    185                                if( null != oRow ) {
    186        //                              int nTotalCells = oRow.getPhysicalNumberOfCells();
    187                                        // 4.3.4.0 (2008/12/01) POI3.2対応。shortをintにする。
    188                                        // short nFirstCell = oRow.getFirstCellNum();
    189                                        // short nLastCell  = oRow.getLastCellNum();
    190                                        final int nFirstCell = oRow.getFirstCellNum();
    191                                        final int nLastCell  = oRow.getLastCellNum();
    192                                        for( int nIndexCell=nFirstCell; nIndexCell<=nLastCell; nIndexCell++ ) {
    193                                                final HSSFCell oCell = oRow.getCell(nIndexCell);
    194                                                if( null != oCell ) { changeCell(oCell);}
    195                                        }
    196                                }
    197                        }
    198                }
    199        }
    200
    201        /**
    202         * セル情報を変更します。
    203         *
    204         * @og.rev 4.3.4.0 (2008/12/01) POI3.2対応
    205         * @og.rev 6.5.0.0 (2016/09/30) poi-3.15 対応(Hyperlink.LINK_XXXX → HyperlinkType.XXXX)
    206         *
    207         * @param oCell HSSFCellオブジェクト
    208         */
    209        @SuppressWarnings(value={"deprecation"})        // poi-3.15
    210        protected void changeCell(final HSSFCell oCell) {
    211                String strText;
    212                HSSFRichTextString richText;
    213        //      final int nCellType = oCell.getCellType();
    214        //      switch(nCellType) {
    215                switch( oCell.getCellTypeEnum() ) {
    216        //              case HSSFCell.CELL_TYPE_FORMULA:                                                                        // 6.5.0.0 (2016/09/30) poi-3.12
    217                        case FORMULA:                                                                                                           // 6.5.0.0 (2016/09/30) poi-3.15
    218                                strText = changeData(changeFormulaAttr(oCell.getCellFormula()));
    219                                if( null != strText ) {
    220        //                              oCell.setCellType(HSSFCell.CELL_TYPE_FORMULA);
    221        //                              oCell.setEncoding(HSSFCell.ENCODING_UTF_16);
    222                                        oCell.setCellFormula(strText);
    223                                }
    224                                break;
    225        //              case HSSFCell.CELL_TYPE_STRING:                                                                         // 6.5.0.0 (2016/09/30) poi-3.12
    226                        case STRING:                                                                                                            // 6.5.0.0 (2016/09/30) poi-3.15
    227        // POI3.0       strText =  changeData(oCell.getStringCellValue());
    228                                richText = oCell.getRichStringCellValue();
    229                                strText =  changeData(richText.getString());
    230                                if( null != strText ) {
    231        //                              oCell.setCellType(HSSFCell.CELL_TYPE_STRING);
    232        // POI3.0               oCell.setEncoding(HSSFCell.ENCODING_UTF_16);
    233        // POI3.2               oCell.setCellValue( strText );  // POI3.0 Deprecation
    234                                        oCell.setCellValue( new HSSFRichTextString(strText) );
    235                                }
    236                                break;
    237        //              case HSSFCell.CELL_TYPE_NUMERIC:
    238        //                      break;
    239        //              case HSSFCell.CELL_TYPE_BOOLEAN:
    240        //                      break;
    241        //              case HSSFCell.CELL_TYPE_ERROR:
    242        //                      break;
    243                        default :
    244                                break;
    245                }
    246        }
    247
    248        /**
    249         * POIで解釈したExcel式の中の変な属性を加工して、出力します。
    250         * いろいろな属性がある所に、適切に対応していく予定。
    251         * 各サブクラスで実装してください。
    252         *
    253         * @param       inLine  入力文字列
    254         *
    255         * @return      出力文字列
    256         */
    257        protected String changeFormulaAttr( final String inLine ) {
    258                // rowOver で、かつ ページブレークかページエンドカットの場合、処理終了。
    259                final Matcher  matcher = PATTERN_EXIN.matcher( inLine );
    260                return matcher.find() ? matcher.replaceAll( "" ) : inLine;
    261        }
    262
    263        /**
    264         * 入力文字列 を加工して、出力します。
    265         * データをテーブルモデルより読み取り、値をセットします。
    266         * 各サブクラスで実装してください。
    267         *
    268         * @param       inLine  入力文字列
    269         *
    270         * @return      出力文字列. 文字列の変換は要らない場合、nullを返します
    271         */
    272        @Override
    273        protected String changeData( final String inLine ) {
    274                boolean bFind = false;
    275
    276                // rowOver で、かつ ページブレークかページエンドカットの場合、処理終了。
    277                final Matcher  matcher = PATTERN_KEY.matcher( inLine );
    278                final StringBuffer sb = new StringBuffer();     // Matcher.appendTail( StringBuffer ) の為
    279
    280                while( matcher.find() ) {
    281                        matcher.appendReplacement( sb, getValue( matcher.group( 1 ) ) );
    282                        bFind = true;
    283                }
    284
    285                if( bFind ) {
    286                        matcher.appendTail( sb );
    287                        return sb.toString();
    288                }
    289                else {
    290                        return null;
    291                }
    292        }
    293
    294        /**
    295         * 入力文字列 を読み取って、出力します。
    296         * tr タグを目印に、１行(trタグ間)ずつ取り出します。
    297         * 読み取りを終了する場合は、null を返します。
    298         * 各サブクラスで実装してください。
    299         * ※ このクラスでは実装されていません。
    300         *
    301         * @return      出力文字列
    302         */
    303        @Override
    304        protected String readLine() {
    305                throw new UnsupportedOperationException();
    306        }
    307
    308        /**
    309         * 入力文字列 を読み取って、出力します。
    310         * 各サブクラスで実装してください。
    311         * ※ このクラスでは実装されていません。
    312         *
    313         * @param line 入力文字列
    314         */
    315        @Override
    316        protected void println( final String line ) {
    317                throw new UnsupportedOperationException();
    318        }
    319}