    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.plugin.io;
    017
    018import java.io.File;                                                                            // 6.2.0.0 (2015/02/27)
    019import java.io.IOException;
    020import java.io.InputStream;
    021import java.util.ArrayList;
    022import java.util.List;
    023import java.util.zip.ZipEntry;
    024import java.util.zip.ZipFile;
    025
    026import javax.xml.parsers.DocumentBuilder;
    027import javax.xml.parsers.DocumentBuilderFactory;
    028import javax.xml.parsers.ParserConfigurationException;
    029
    030import org.opengion.fukurou.util.StringUtil;
    031import org.opengion.fukurou.system.Closer;                                      // 5.5.2.6 (2012/05/25)
    032import org.opengion.hayabusa.common.HybsSystemException;
    033import org.opengion.hayabusa.io.AbstractTableReader;            // 6.2.0.0 (2015/02/27)
    034import org.w3c.dom.Document;
    035import org.w3c.dom.Element;
    036import org.w3c.dom.NodeList;
    037import org.xml.sax.SAXException;
    038
    039import static org.opengion.fukurou.system.HybsConst.CR ;                // 6.2.2.0 (2015/03/27)
    040
    041/**
    042 * XMLパーサによる、OpenOffice.org Calcの表計算ドキュメントファイルを読み取る実装クラスです。
    043 *
    044 * ①カラム名が指定されている場合
    045 *  #NAMEで始まる行を検索し、その行のそれぞれの値をカラム名として処理します。
    046 *  #NAMEで始まる行より以前の行については、全て無視されます。
    047 *  また、#NAMEより前のカラム及び、#NAMEの行の値がNULL(カラム名が設定されていない)カラムも
    048 *  無視します。
    049 *  読み飛ばされたカラム列に入力された値は取り込まれません。
    050 *  また、#NAME行以降の#で始まる行は、コメント行とみなされ処理されません。
    051 *
    052 * ②カラム名が指定されている場合
    053 *  指定されたカラム名に基づき、値を取り込みます。
    054 *  カラム名の順番と、シートに記述されている値の順番は一致している必要があります。
    055 *  指定されたカラム数を超える列の値については全て無視されます。
    056 *  #で始まる行は、コメント行とみなされ処理されません。
    057 *
    058 * また、いずれの場合も全くデータが存在していない行は読み飛ばされます。
    059 *
    060 * @og.group ファイル入力
    061 *
    062 * @version 4.0
    063 * @author Hiroki Nakamura
    064 * @since JDK5.0,
    065 */
    066public class TableReader_Calc extends AbstractTableReader {
    067        // * このプログラムのVERSION文字列を設定します。 {@value} */
    068        private static final String VERSION = "6.4.2.0 (2016/01/29)" ;
    069
    070        private int                     firstClmIdx             ;
    071        private int[]           valueClmIdx             ;
    072
    073        /**
    074         * デフォルトコンストラクター
    075         *
    076         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
    077         */
    078        public TableReader_Calc() { super(); }          // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
    079
    080        /**
    081         * DBTableModel から 各形式のデータを作成して,BufferedReader より読み取ります。
    082         * コメント/空行を除き、最初の行は、項目名が必要です。
    083         * (但し、カラム名を指定することで、項目名を省略することができます)
    084         * それ以降は、コメント/空行を除き、データとして読み込んでいきます。
    085         * このメソッドは、Calc 読み込み時に使用します。
    086         *
    087         * @og.rev 4.3.5.0 (2009/02/01) toArray するときに、サイズの初期値指定を追加
    088         * @og.rev 5.5.7.2 (2012/10/09) sheetNos 追加による複数シートのマージ読み取りサポート
    089         * @og.rev 6.2.0.0 (2015/02/27) TableReader クラスの呼び出し元メソッドの共通化(EXCEL,TEXT)。新規
    090         *
    091         * @param   file 読み取り元ファイル名
    092         * @param   enc ファイルのエンコード文字列(未使用)
    093         */
    094        @Override
    095        public void readDBTable( final File file , final String enc ) {
    096
    097                ZipFile zipFile = null;
    098                boolean errFlag = false;        // 5.0.0.1 (2009/08/15) finally ブロックの throw を避ける。
    099                try {
    100                        // OpenOffice.org odsファイルを開く
    101                        zipFile = new ZipFile( file );
    102
    103                        final ZipEntry entry = zipFile.getEntry( "content.xml" );
    104                        if( null == entry ) {
    105                                final String errMsg = "ODSファイル中にファイルcontent.xmlが存在しません。";
    106                                throw new HybsSystemException( errMsg );
    107                        }
    108
    109                        // content.xmlをパースし、行、列単位のオブジェクトに分解します。
    110                        final DomOdsParser odsParser = new DomOdsParser();
    111                        odsParser.doParse( zipFile.getInputStream( entry ), sheetName , sheetNos );             // 5.5.7.2 (2012/10/09) sheetNos 対応
    112                        final List<RowInfo> rowInfoList = odsParser.getRowInfoList();
    113
    114                        // 4.3.5.0 (2009/02/01) toArray するときに、サイズの初期値指定を追加
    115                        makeDBTableModel( rowInfoList.toArray( new RowInfo[rowInfoList.size()] ) );
    116                }
    117                catch( final IOException ex ) {
    118                        final String errMsg = "ファイル読込みエラー[" + file + "]";
    119                        throw new HybsSystemException( errMsg, ex );
    120                }
    121                finally {
    122                        // 5.5.2.6 (2012/05/25) fukurou.system.Closer#zipClose( ZipFile ) を利用するように修正。
    123                        errFlag = ! Closer.zipClose( zipFile );         // OK の場合、true なので、反転しておく。
    124                }
    125
    126                if( errFlag ) {
    127                        final String errMsg = "ODSファイルのクローズ中にエラーが発生しました[" + file + "]";
    128                        throw new HybsSystemException ( errMsg );
    129                }
    130        }
    131
    132        /**
    133         * ODSファイルをパースした結果からDBTableModelを生成します。
    134         *
    135         * @og.rev 5.1.6.0 (2010/05/01) skipRowCountの追加
    136         *
    137         * @param rowInfoList 行オブジェクトの配列(可変長引数)
    138         */
    139        private void makeDBTableModel( final RowInfo... rowInfoList ) {
    140                // カラム名が指定されている場合は、優先する。
    141                if( columns != null && columns.length() > 0 ) {
    142                        makeHeaderFromClms();
    143                }
    144
    145                final int skip = getSkipRowCount();                                             // 5.1.6.0 (2010/05/01)
    146                for( int row=skip; row<rowInfoList.length; row++ ) {
    147                        final RowInfo rowInfo = rowInfoList[row];                               // 5.1.6.0 (2010/05/01)
    148                        if( valueClmIdx == null ) {
    149                                makeHeader( rowInfo );
    150                        }
    151                        else {
    152                                makeBody( rowInfo );
    153                        }
    154                }
    155
    156                // 最後まで、#NAME が見つから無かった場合
    157                if( valueClmIdx == null ) {
    158                        final String errMsg = "最後まで、#NAME が見つかりませんでした。" + CR
    159                                                         + "ファイル形式が異なるか、もしくは損傷している可能性があります。" + CR;
    160                        throw new HybsSystemException( errMsg );
    161                }
    162        }
    163
    164        /**
    165         * 指定されたカラム一覧からヘッダー情報を生成します。
    166         *
    167         * @og.rev 5.1.6.0 (2010/05/01) useNumber の追加
    168         * @og.rev 6.1.0.0 (2014/12/26) omitNames 属性を追加
    169         * @og.rev 6.2.1.0 (2015/03/13) TableReaderModel を外部からセットします。
    170         */
    171        private void makeHeaderFromClms() {
    172                final String[] names = StringUtil.csv2Array( columns );
    173                final int len = setTableDBColumn( names ) ;     // 6.1.0.0 (2014/12/26)
    174                valueClmIdx = new int[len];
    175                int adrs = isUseNumber() ? 1:0 ;                // useNumber =true の場合は、１件目(No)は読み飛ばす。
    176                for( int i=0; i<len; i++ ) {
    177                        valueClmIdx[i] = adrs++;
    178                }
    179        }
    180
    181        /**
    182         * ヘッダー情報を読み取り、DBTableModelのオブジェクトを新規に作成します。
    183         * ※ 他のTableReaderと異なり、#NAME が見つかるまで、読み飛ばす。
    184         *
    185         * @og.rev 4.3.5.0 (2009/02/01) toArray するときに、サイズの初期値指定を追加
    186         * @og.rev 6.2.1.0 (2015/03/13) TableReaderModel を外部からセットします。
    187         *
    188         * @param rowInfo 行オブジェクト
    189         */
    190        private void makeHeader( final RowInfo rowInfo ) {
    191                final CellInfo[] cellInfos = rowInfo.cellInfos;
    192
    193                final int cellLen = cellInfos.length;
    194                int runPos = 0;
    195                ArrayList<String> nameList = null;
    196                ArrayList<Integer> posList = null;
    197                for( int idx=0; idx<cellLen; idx++ ) {
    198                        // テーブルのヘッダ(#NAME)が見つかる前の行、列は全て無視される
    199                        final CellInfo cellInfo = cellInfos[idx];
    200                        final String text = cellInfo.text.trim();
    201
    202                        for( int cellRep=0; cellRep<cellInfo.colRepeat; cellRep++ ) {
    203                                // 空白のヘッダは無視(その列にデータが入っていても読まない)
    204                                if( text.length() != 0 ) {
    205                                        if( firstClmIdx == 0 && "#NAME".equalsIgnoreCase( text ) ) {
    206                                                nameList = new ArrayList<>();
    207                                                posList = new ArrayList<>();
    208                                                firstClmIdx = idx;
    209                                        }
    210                                        else if( nameList != null ) {
    211                                                nameList.add( text );
    212                                                posList.add( runPos );
    213                                        }
    214                                }
    215                                runPos++;
    216                        }
    217                }
    218
    219                if( posList != null && ! posList.isEmpty() ) {
    220                        // 4.3.5.0 (2009/02/01) サイズの初期値指定
    221                        final int size = nameList.size();
    222                        final String[] names = nameList.toArray( new String[size] );
    223        //              table.init( size );
    224                        setTableDBColumn( names );
    225
    226                        valueClmIdx = new int[posList.size()];
    227                        for( int i=0; i<posList.size(); i++ ) {
    228                                valueClmIdx[i] = posList.get( i ).intValue();
    229                        }
    230                }
    231        }
    232
    233        /**
    234         * 行、列(セル)単位の情報を読み取り、DBTableModelに値をセットします。
    235         *
    236         * @og.rev 5.2.1.0 (2010/10/01) setTableColumnValues メソッドを経由して、テーブルにデータをセットする。
    237         * @og.rev 6.2.1.0 (2015/03/13) setTableColumnValuesに、行番号を引数に追加
    238         * @og.rev 6.2.2.0 (2015/03/27) Overflow処理(maxRowCount)は、Tag側に戻す。
    239         *
    240         * @param rowInfo 行オブジェクト
    241         */
    242        private void makeBody( final RowInfo rowInfo ) {
    243                final CellInfo[] cellInfos      = rowInfo.cellInfos;
    244                final int cellLen                       = cellInfos.length;
    245                boolean isExistData                     = false;
    246
    247                final List<String> colData = new ArrayList<>();
    248                for( int cellIdx=0; cellIdx<cellLen; cellIdx++ ) {
    249                        final CellInfo cellInfo = cellInfos[cellIdx];
    250                        for( int cellRep=0; cellRep<cellInfo.colRepeat; cellRep++ ) {
    251                                colData.add( cellInfo.text );
    252                                if( cellInfo.text.length() > 0 ) {
    253                                        isExistData = true;
    254                                }
    255                        }
    256                }
    257
    258                if( isExistData ) {
    259                        // 初めの列(#NAMEが記述されていた列)の値が#で始まっている場合は、コメント行とみなす。
    260                        final String firstVal = colData.get( firstClmIdx );
    261                        // 6.3.9.1 (2015/11/27) A method should have only one exit point, and that should be the last statement in the method.(PMD)
    262                        if( !StringUtil.startsChar( firstVal , '#' ) ) {                                // 6.2.0.0 (2015/02/27) １文字 String.startsWith
    263                                // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
    264                                final String[] vals = new String[valueClmIdx.length];
    265                                for( int col=0; col<valueClmIdx.length; col++ ) {
    266                                        vals[col] = colData.get( valueClmIdx[col] );
    267                                }
    268
    269                                final int rowRepeat = rowInfo.rowRepeat;                                        // 6.3.9.1 (2015/11/27) 使う直前に移動
    270
    271                                // 重複行の繰り返し処理
    272                                for( int rowIdx=0; rowIdx<rowRepeat; rowIdx++ ) {
    273                                        // テーブルモデルにデータをセット
    274                                        // 6.2.2.0 (2015/03/27) Overflow処理(maxRowCount)は、Tag側に戻す。
    275                                        setTableColumnValues( vals,rowIdx );    // 6.2.1.0 (2015/03/13)
    276                                }
    277                        }
    278                }
    279        }
    280
    281        /**
    282         * ODSファイルに含まれるcontent.xmlをDOMパーサーでパースし、行、列単位に
    283         * オブジェクトに変換します。
    284         *
    285         * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、private static final class に変更。
    286         */
    287        private static final class DomOdsParser{
    288
    289                // OpenOffice.org Calc tag Names
    290                private static final String TABLE_TABLE_ELEM = "table:table";
    291                private static final String TABLE_TABLE_ROW_ELEM = "table:table-row";
    292                private static final String TABLE_TABLE_CELL_ELEM = "table:table-cell";
    293                private static final String TEXT_P_ELEM = "text:p";
    294
    295                // Sheet tag attributes
    296                private static final String TABLE_NAME_ATTR = "table:name";
    297                private static final String TABLE_NUMBER_ROWS_REPEATED_ATTR = "table:number-rows-repeated";
    298                private static final String TABLE_NUMBER_COLUMNS_REPEATED_ATTR = "table:number-columns-repeated";
    299
    300                private final List<RowInfo> rowInfoList = new ArrayList<>();            // 6.3.9.1 (2015/11/27)
    301
    302                /**
    303                 * DomパーサでXMLをパースする。
    304                 *
    305                 * @og.rev 5.5.7.2 (2012/10/09) sheetNos 追加による複数シートのマージ読み取りサポート
    306                 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、public → なし に変更。
    307                 *
    308                 * @param inputStream 入力ストリーム
    309                 * @param sheetName シート名
    310                 * @param sheetNos  シート番号
    311                 */
    312                /* default */ void doParse( final InputStream inputStream, final String sheetName, final String sheetNos ) {
    313                        try {
    314                                // ドキュメントビルダーファクトリを生成
    315                                final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
    316                                dbFactory.setNamespaceAware( true );
    317
    318                                // ドキュメントビルダーを生成
    319                                final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
    320                                // パースを実行してDocumentオブジェクトを取得
    321                                final Document doc = dBuilder.parse( inputStream );
    322                                processBook( doc, sheetName, sheetNos );                        // 5.5.7.2 (2012/10/09) sheetNos 追加
    323                        }
    324                        // 7.2.9.5 (2020/11/28) PMD:'catch' branch identical to 'ParserConfigurationException' branch
    325                        catch( final ParserConfigurationException | IOException ex ) {
    326                                throw new HybsSystemException( ex );
    327                        }
    328//                      catch( final ParserConfigurationException ex ) {
    329//                              throw new HybsSystemException( ex );
    330//                      }
    331                        catch( final SAXException ex ) {
    332                                final String errMsg = "ODSファイル中に含まれるcontent.xmlがXML形式ではありません。";
    333                                throw new HybsSystemException( errMsg, ex );
    334                        }
    335//                      catch( final IOException ex ) {
    336//                              throw new HybsSystemException( ex );
    337//                      }
    338                }
    339
    340                /**
    341                 * 行オブジェクトのリストを返します。
    342                 *
    343                 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、public → なし に変更。
    344                 *
    345                 * @return 行オブジェクトのリスト
    346                 */
    347                /* default */ List<RowInfo> getRowInfoList() {
    348                        return rowInfoList;
    349                }
    350
    351                /**
    352                 * ODSファイル全体のパースを行い、処理対象となるシートを検索します。
    353                 *
    354                 * @og.rev 5.5.7.2 (2012/10/09) sheetNos 追加による複数シートのマージ読み取りサポート
    355                 * @og.rev 6.2.6.0 (2015/06/19) #csv2ArrayExt(String,int)の戻り値を、文字列配列から数字配列に変更。
    356                 *
    357                 * @param doc Documentオブジェクト
    358                 * @param sheetName シート名
    359                 * @param sheetNos  シート番号
    360                 */
    361                private void processBook( final Document doc, final String sheetName, final String sheetNos ) {
    362                        // table:tableを探す
    363                        final NodeList nodetList = doc.getElementsByTagName( TABLE_TABLE_ELEM );
    364                        final int listLen = nodetList.getLength();
    365
    366                        // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
    367                        final Element[] sheets  ;                       // 5.5.7.2 (2012/10/09)
    368
    369                        // 5.5.7.2 (2012/10/09) 複数シートのマージ読み取り。 sheetNos の指定が優先される。
    370                        if( sheetNos != null && sheetNos.length() > 0 ) {
    371                                final Integer[] sheetList = StringUtil.csv2ArrayExt( sheetNos , listLen-1 );    // 最大シート番号は、シート数-1
    372                                sheets = new Element[sheetList.length];
    373                                for( int i=0; i<sheetList.length; i++ ) {
    374                                        sheets[i] = (Element)nodetList.item( sheetList[i] );
    375                                }
    376                        }
    377                        else if( sheetName != null && sheetName.length() > 0 ) {
    378                                Element sheet = null;
    379                                for( int idx=0; idx<listLen; idx++ ) {
    380                                        final Element st = (Element)nodetList.item( idx );
    381                                        if( sheetName.equals( st.getAttribute( TABLE_NAME_ATTR ) ) ) {
    382                                                sheet = st;
    383                                                break;
    384                                        }
    385                                }
    386                                if( sheet == null ) {
    387                                        final String errMsg = "対応するシートが存在しません。 sheetName=[" + sheetName + "]" ;
    388                                        throw new HybsSystemException( errMsg );
    389                                }
    390                                sheets = new Element[] { sheet };
    391                        }
    392                        else {
    393                                final Element sheet = (Element)nodetList.item(0);
    394                                sheets = new Element[] { sheet };
    395                        }
    396
    397                        // 指定のシートがなければ、エラー
    398                        // 6.0.2.5 (2014/10/31) null でないことがわかっている値の冗長な null チェックがあります。
    399                        // 5.5.7.2 (2012/10/09) 複数シートのマージ読み取り。
    400                        // 7.2.9.4 (2020/11/20) PMD:This for loop can be replaced by a foreach loop
    401                        for( final Element sheet : sheets ) {
    402                                processSheet( sheet );
    403                        }
    404//                      for( int i=0; i<sheets.length; i++ ) {
    405//                              processSheet( sheets[i] );
    406//                      }
    407                }
    408
    409                /**
    410                 * ODSファイルのシート単位のパースを行い、行単位のオブジェクトを生成します。
    411                 *
    412                 * @param sheet Elementオブジェクト
    413                 */
    414                private void processSheet( final Element sheet ) {
    415                        final NodeList rows = sheet.getElementsByTagName( TABLE_TABLE_ROW_ELEM );
    416                        final int listLen = rows.getLength();
    417                        int rowRepeat;
    418                        for( int idx=0; idx<listLen; idx++ ) {
    419                                final Element row = (Element)rows.item( idx );
    420                                // 行の内容が全く同じ場合、table:number-rows-repeatedタグにより省略される。
    421                                final String repeatStr = row.getAttribute( TABLE_NUMBER_ROWS_REPEATED_ATTR );
    422                                if( repeatStr == null || repeatStr.isEmpty() ) {                // 6.1.0.0 (2014/12/26) refactoring
    423                                        rowRepeat = 1;
    424                                }
    425                                else {
    426                                        rowRepeat = Integer.parseInt( repeatStr, 10 );
    427                                }
    428
    429                                processRow( row, rowRepeat );
    430                        }
    431                }
    432
    433                /**
    434                 * ODSファイルの行単位のパースを行い、カラム単位のオブジェクトを生成します。
    435                 *
    436                 * @og.rev 4.3.5.0 (2009/02/01) toArray するときに、サイズの初期値指定を追加
    437                 * @og.rev 5.1.8.0 (2010/07/01) セル内で書式設定されている場合に、テキストデータが取得されないバグを修正
    438                 *
    439                 * @param row Elementオブジェクト
    440                 * @param rowRepeat 繰り返し数
    441                 */
    442                private void processRow( final Element row, final int rowRepeat ) {
    443                        final NodeList cells = row.getElementsByTagName( TABLE_TABLE_CELL_ELEM );
    444                        final int listLen = cells.getLength();
    445                        int colRepeat;
    446                        String cellText;
    447                        final ArrayList<CellInfo> cellInfoList = new ArrayList<>();
    448                        for( int idx=0; idx<listLen; idx++ ) {
    449                                final Element cell = (Element)cells.item( idx );
    450                                // カラムの内容が全く同じ場合、table:number-columns-repeatedタグにより省略される。
    451                                final String repeatStr = cell.getAttribute( TABLE_NUMBER_COLUMNS_REPEATED_ATTR );
    452                                if( repeatStr == null || repeatStr.isEmpty() ) {                // 6.1.0.0 (2014/12/26) refactoring
    453                                        colRepeat = 1;
    454                                }
    455                                else {
    456                                        colRepeat = Integer.parseInt( repeatStr, 10 );
    457                                }
    458
    459                                // text:p
    460                                final NodeList texts = cell.getElementsByTagName( TEXT_P_ELEM );
    461                                if( texts.getLength() == 0 ) {
    462                                        cellText = "";
    463                                }
    464                                else {
    465                                        // 5.1.8.0 (2010/07/01) セル内で書式設定されている場合に、テキストデータが取得されないバグを修正
    466                                        cellText = texts.item( 0 ).getTextContent();
    467                                }
    468                                cellInfoList.add( new CellInfo( colRepeat, cellText ) );
    469                        }
    470
    471                        if( ! cellInfoList.isEmpty() ) {
    472                                // 4.3.5.0 (2009/02/01) toArray するときに、サイズの初期値指定を追加
    473                                rowInfoList.add( new RowInfo( rowRepeat, cellInfoList.toArray( new CellInfo[cellInfoList.size()] ) ) );
    474                        }
    475                }
    476        }
    477
    478        /**
    479         * ODSファイルの行情報を表す構造体
    480         */
    481        private static final class RowInfo {
    482                public final int rowRepeat;
    483                public final CellInfo[] cellInfos;
    484
    485                /**
    486                 * 行の繰り返しとカラム情報の構造体配列を引数に取る、コンストラクター
    487                 *
    488                 * @param rep  行の繰り返し数
    489                 * @param cell カラム情報を表す構造体(CellInfoオブジェクト)の配列
    490                 */
    491                RowInfo( final int rep, final CellInfo[] cell ) {
    492                        rowRepeat = rep;
    493                        cellInfos = cell;
    494                }
    495        }
    496
    497        /**
    498         * ODSファイルのカラム情報を表す構造体
    499         */
    500        private static final class CellInfo {
    501                public final int colRepeat;
    502                public final String text;
    503
    504                /**
    505                 * 行の繰り返しとカラム情報を引数に取る、コンストラクター
    506                 *
    507                 * @param rep  列の繰り返し数
    508                 * @param tx   カラム情報
    509                 */
    510                CellInfo( final int rep, final String tx ) {
    511                        colRepeat = rep;
    512                        text = tx;
    513                }
    514        }
    515}