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