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.BufferedReader;
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.util.Closer;                        // 5.5.2.6 (2012/05/25)
032import org.opengion.hayabusa.common.HybsSystem;
033import org.opengion.hayabusa.common.HybsSystemException;
034import org.opengion.hayabusa.db.DBTableModelUtil;
035import org.w3c.dom.Document;
036import org.w3c.dom.Element;
037import org.w3c.dom.NodeList;
038import org.xml.sax.SAXException;
039
040/**
041 * XMLパーサによる、OpenOffice.org Calcの表計算ドキュメントファイルを読み取る実装クラスです。
042 *
043 * @カラム名が指定されている場合
044 *  #NAMEで始まる行を検索し、その行のそれぞれの値をカラム名として処理します。
045 *  #NAMEで始まる行より以前の行については、全て無視されます。
046 *  また、#NAMEより前のカラム及び、#NAMEの行の値がNULL(カラム名が設定されていない)カラムも
047 *  無視します。
048 *  読み飛ばされたカラム列に入力された値は取り込まれません。
049 *  また、#NAME行以降の#で始まる行は、コメント行とみなされ処理されません。
050 *
051 * Aカラム名が指定されている場合
052 *  指定されたカラム名に基づき、値を取り込みます。
053 *  カラム名の順番と、シートに記述されている値の順番は一致している必要があります。
054 *  指定されたカラム数を超える列の値については全て無視されます。
055 *  #で始まる行は、コメント行とみなされ処理されません。
056 *
057 * また、いずれの場合も全くデータが存在していない行は読み飛ばされます。
058 *
059 * @og.group ファイル入力
060 *
061 * @version 4.0
062 * @author Hiroki Nakamura
063 * @since JDK5.0,
064 */
065public class TableReader_Calc extends TableReader_Default {
066        // * このプログラムのVERSION文字列を設定します。 {@value} */
067        private static final String VERSION = "5.5.7.2 (2012/10/09)";
068
069        private String          sheetName               = null;
070        private String          sheetNos                = null;         // 5.5.7.2 (2012/10/09)
071        private String          filename                = null;
072        private int                     numberOfRows    = 0;
073        private int                     firstClmIdx             = 0;
074        private int[]           valueClmIdx             = null;
075
076        /**
077         * DBTableModel から 各形式のデータを作成して,BufferedReader より読み取ります。
078         * コメント/空行を除き、最初の行は、項目名が必要です。
079         * (但し、カラム名を指定することで、項目名を省略することができます)
080         * それ以降は、コメント/空行を除き、データとして読み込んでいきます。
081         * このメソッドは、Calc 読み込み時に使用します。
082         *
083         * @og.rev 4.3.5.0 (2009/02/01) toArray するときに、サイズの初期値指定を追加
084         * @og.rev 5.5.7.2 (2012/10/09) sheetNos 追加による複数シートのマージ読み取りサポート
085         *
086         * @see #isExcel()
087         */
088        @Override
089        public void readDBTable() {
090
091                ZipFile zipFile = null;
092                boolean errFlag = false;        // 5.0.0.1 (2009/08/15) finally ブロックの throw を避ける。
093                try {
094                        // OpenOffice.org odsファイルを開く
095                        zipFile = new ZipFile( filename );
096
097                        ZipEntry entry = zipFile.getEntry( "content.xml" );
098                        if ( null == entry ) {
099                                String errMsg = "ODSファイル中にファイルcontent.xmlが存在しません。";
100                                throw new HybsSystemException( errMsg );
101                        }
102
103                        // content.xmlをパースし、行、列単位のオブジェクトに分解します。
104                        DomOdsParser odsParser = new DomOdsParser();
105//                      odsParser.doParse( zipFile.getInputStream( entry ), sheetName );
106                        odsParser.doParse( zipFile.getInputStream( entry ), sheetName , sheetNos );             // 5.5.7.2 (2012/10/09) sheetNos 対応
107                        List<RowInfo> rowInfoList = odsParser.getRowInfoList();
108
109                        // 4.3.5.0 (2009/02/01) toArray するときに、サイズの初期値指定を追加
110//                      makeDBTableModel( rowInfoList.toArray( new RowInfo[0] ) );
111                        makeDBTableModel( rowInfoList.toArray( new RowInfo[rowInfoList.size()] ) );
112                }
113                catch ( IOException ex ) {
114                        String errMsg = "ファイル読込みエラー[" + filename + "]";
115                        throw new HybsSystemException( errMsg, ex );
116                }
117                finally {
118                        // 5.5.2.6 (2012/05/25) fukurou.util.Closer#zipClose( ZipFile ) を利用するように修正。
119                        errFlag = ! Closer.zipClose( zipFile );         // OK の場合、true なので、反転しておく。
120//                      if ( null != zipFile ) {
121//                              try {
122//                                      zipFile.close();
123//                              }
124//                              catch ( IOException ex ) {
125//                                      errFlag = true;
126//                              }
127//                      }
128                }
129
130                if( errFlag ) {
131                        String errMsg = "ODSファイルのクローズ中にエラーが発生しました[" + filename + "]";
132                        throw new HybsSystemException ( errMsg );
133                }
134
135//              finally {
136//                      if ( null != zipFile )
137//                              try {
138//                                      zipFile.close();
139//                              }
140//                              catch ( IOException ex ) {
141//                                      String errMsg = "ODSファイルのクローズ中にエラーが発生しました[" + filename + "]";
142//                                      throw new HybsSystemException ( errMsg, ex );
143//                              }
144//              }
145        }
146
147        /**
148         * DBTableModel から 各形式のデータを作成して,BufferedReader より読み取ります。
149         * このメソッドは、この実装クラスでは使用できません。
150         *
151         * @param reader 各形式のデータ(使用していません)
152         */
153        @Override
154        public void readDBTable( final BufferedReader reader ) {
155                String errMsg = "このクラスでは実装されていません。";
156                throw new UnsupportedOperationException( errMsg );
157        }
158
159        /**
160         * DBTableModelのデータとしてCalcファイルを読み込むときのシート名を設定します。
161         * これにより、複数の形式の異なるデータを順次読み込むことや、シートを指定して
162         * 読み取ることが可能になります。
163         * sheetNos と sheetName が同時に指定された場合は、sheetNos が優先されます。エラーにはならないのでご注意ください。
164         * のでご注意ください。
165         *
166         * @param sheetName シート名
167         */
168        @Override
169        public void setSheetName( final String sheetName ) {
170                this.sheetName = sheetName;
171        }
172
173        /**
174         * Calcファイルを読み込むときのシート番号を指定します(初期値:0)。
175         *
176         * Calc読み込み時に複数シートをマージして取り込みます。
177         * シート番号は、0 から始まる数字で表します。
178         * ヘッダーは、最初のシートのカラム位置に合わせます。(ヘッダータイトルの自動認識はありません。)
179         * よって、指定するシートは、すべて同一レイアウトでないと取り込み時にカラムのずれが発生します。
180         * 
181         * シート番号の指定は、カンマ区切りで、複数指定できます。また、N-M の様にハイフンで繋げることで、
182         * N 番から、M 番のシート範囲を一括指定可能です。また、"*" による、全シート指定が可能です。
183         * これらの組み合わせも可能です。( 0,1,3,5-8,10-* )
184         * ただし、"*" に関しては例外的に、一文字だけで、すべてのシートを表すか、N-* を最後に指定するかの
185         * どちらかです。途中には、"*" は、現れません。
186         * シート番号は、重複(1,1,2,2)、逆転(3,2,1) での指定が可能です。これは、その指定順で、読み込まれます。
187         * sheetNos と sheetName が同時に指定された場合は、sheetNos が優先されます。エラーにはならないのでご注意ください。
188         * このメソッドは、isExcel() == true の場合のみ利用されます。
189         * 
190         * 初期値は、0(第一シート) です。
191         *
192         * ※ このクラスでは実装されていません。
193         *
194         * @og.rev 5.5.7.2 (2012/10/09) 新規追加
195         *
196         * @param   sheetNos Calcファイルのシート番号(0から始まる)
197         * @see         #setSheetName( String ) 
198         */
199        @Override
200        public void setSheetNos( final String sheetNos ) {
201                this.sheetNos = sheetNos;
202        }
203
204        /**
205         * このクラスが、EXCEL対応機能を持っているかどうかを返します。
206         *
207         * EXCEL対応機能とは、シート名のセット、読み込み元ファイルの Fileオブジェクト取得などの、特殊機能です。
208         * 本来は、インターフェースを分けるべきと考えますが、taglib クラス等の 関係があり、問い合わせによる条件分岐で対応します。
209         *
210         * @return      EXCEL対応機能を持っているかどうか(常にtrue)
211         */
212        @Override
213        public boolean isExcel() {
214                return true;
215        }
216
217        /**
218         * 読み取り元ファイル名をセットします。(DIR + Filename) これは、OpenOffice.org
219         * Calc追加機能として実装されています。
220         *
221         * @param filename 読み取り元ファイル名
222         */
223        @Override
224        public void setFilename( final String filename ) {
225                this.filename = filename;
226                if ( filename == null ) {
227                        String errMsg = "ファイル名が指定されていません。";
228                        throw new HybsSystemException( errMsg );
229                }
230        }
231
232        /**
233         * ODSファイルをパースした結果からDBTableModelを生成します。
234         *
235         * @og.rev 5.1.6.0 (2010/05/01) skipRowCountの追加
236         *
237         * @param rowInfoList 行オブジェクトの配列
238         */
239        private void makeDBTableModel( final RowInfo[] rowInfoList ) {
240                // カラム名が指定されている場合は、優先する。
241                if( columns != null && columns.length() > 0 ) {
242                        makeHeaderFromClms();
243                }
244
245//              for( RowInfo rowInfo : rowInfoList ) {
246                int skip = getSkipRowCount();                                           // 5.1.6.0 (2010/05/01)
247                for( int row=skip; row<rowInfoList.length; row++ ) {
248                        RowInfo rowInfo = rowInfoList[row];                             // 5.1.6.0 (2010/05/01)
249                        if( valueClmIdx == null ) {
250                                makeHeader( rowInfo );
251                        }
252                        else {
253                                makeBody( rowInfo );
254                        }
255                }
256
257                // 最後まで、#NAME が見つから無かった場合
258                if ( valueClmIdx == null ) {
259                        String errMsg = "最後まで、#NAME が見つかりませんでした。" + HybsSystem.CR + "ファイルが空か、もしくは損傷している可能性があります。" + HybsSystem.CR;
260                        throw new HybsSystemException( errMsg );
261                }
262        }
263
264        /**
265         * 指定されたカラム一覧からヘッダー情報を生成します。
266         *
267         * @og.rev 5.1.6.0 (2010/05/01) useNumber の追加
268         */
269        private void makeHeaderFromClms() {
270                table = DBTableModelUtil.newDBTable();
271                String[] names = StringUtil.csv2Array( columns );
272                table.init( names.length );
273                setTableDBColumn( names ) ;
274                valueClmIdx = new int[names.length];
275                int adrs = (isUseNumber()) ? 1:0 ;      // useNumber =true の場合は、1件目(No)は読み飛ばす。
276                for( int i=0; i<names.length; i++ ) {
277//                      valueClmIdx[i] = i;
278                        valueClmIdx[i] = adrs++;
279                }
280        }
281
282        /**
283         * ヘッダー情報を読み取り、DBTableModelのオブジェクトを新規に作成します。
284         * ※ 他のTableReaderと異なり、#NAME が見つかるまで、読み飛ばす。
285         *
286         * @og.rev 4.3.5.0 (2009/02/01) toArray するときに、サイズの初期値指定を追加
287         *
288         * @param rowInfo 行オブジェクト
289         */
290        private void makeHeader( final RowInfo rowInfo ) {
291//              int rowRepeat = rowInfo.rowRepeat;
292                CellInfo[] cellInfos = rowInfo.cellInfos;
293
294                int cellLen = cellInfos.length;
295                int runPos = 0;
296                ArrayList<String> nameList = null;
297                ArrayList<Integer> posList = null;
298                for ( int idx = 0; idx < cellLen; idx++ ) {
299                        // テーブルのヘッダ(#NAME)が見つかる前の行、列は全て無視される
300                        CellInfo cellInfo = cellInfos[idx];
301                        String text = cellInfo.text.trim();
302
303                        for ( int cellRep = 0; cellRep < cellInfo.colRepeat; cellRep++ ) {
304                                // 空白のヘッダは無視(その列にデータが入っていても読まない)
305                                if ( text.length() != 0 ) {
306                                        if ( firstClmIdx == 0 && text.equalsIgnoreCase( "#NAME" ) ) {
307                                                nameList = new ArrayList<String>();
308                                                posList = new ArrayList<Integer>();
309                                                table = DBTableModelUtil.newDBTable();
310                                                firstClmIdx = idx;
311                                        }
312                                        else if ( nameList != null ) {
313                                                nameList.add( text );
314                                                posList.add( runPos );
315                                        }
316                                }
317                                runPos++;
318                        }
319                }
320
321//              if ( posList != null && posList.size() > 0 ) {
322                if ( posList != null && ! posList.isEmpty() ) {
323                        table = DBTableModelUtil.newDBTable();
324//                      String[] names = nameList.toArray( new String[0] );
325//                      table.init( names.length );
326                        // 4.3.5.0 (2009/02/01) サイズの初期値指定
327                        int size = nameList.size();
328                        String[] names = nameList.toArray( new String[size] );
329                        table.init( size );
330                        setTableDBColumn( names );
331
332                        valueClmIdx = new int[posList.size()];
333                        for( int i = 0; i<posList.size(); i++ ) {
334                                valueClmIdx[i] = posList.get( i ).intValue();
335                        }
336                }
337        }
338
339        /**
340         * 行、列(セル)単位の情報を読み取り、DBTableModelに値をセットします
341         *
342         * @og.rev 5.2.1.0 (2010/10/01) setTableColumnValues メソッドを経由して、テーブルにデータをセットする。
343         *
344         * @param rowInfo 行オブジェクト
345         */
346        private void makeBody( final RowInfo rowInfo ) {
347                int rowRepeat = rowInfo.rowRepeat;
348                CellInfo[] cellInfos = rowInfo.cellInfos;
349                int cellLen = cellInfos.length;
350                boolean isExistData = false;
351
352                List<String> colData = new ArrayList<String>();
353                for ( int cellIdx = 0; cellIdx < cellLen; cellIdx++ ) {
354                        CellInfo cellInfo = cellInfos[cellIdx];
355                        for ( int cellRep = 0; cellRep < cellInfo.colRepeat; cellRep++ ) {
356                                colData.add( cellInfo.text );
357                                if( cellInfo.text.length() > 0 ) {
358                                        isExistData = true;
359                                }
360                        }
361                }
362
363                if( isExistData ) {
364                        // 初めの列(#NAMEが記述されていた列)の値が#で始まっている場合は、コメント行とみなす。
365                        String firstVal = colData.get( firstClmIdx );
366                        if( firstVal.length() > 0 && firstVal.startsWith( "#" ) ) {
367                                return;
368                        }
369                        else {
370                                String[] vals = new String[valueClmIdx.length];
371                                for( int col = 0; col < valueClmIdx.length; col++ ) {
372                                        vals[col] = colData.get( valueClmIdx[col] );
373                                }
374
375                                // 重複行の繰り返し処理
376                                for ( int rowIdx = 0; rowIdx < rowRepeat; rowIdx++ ) {
377                                        // テーブルモデルにデータをセット
378                                        if ( numberOfRows < getMaxRowCount() ) {
379                                                setTableColumnValues( vals );           // 5.2.1.0 (2010/10/01)
380//                                              table.addColumnValues( vals );
381                                                numberOfRows++;
382                                        }
383                                        else {
384                                                table.setOverflow( true );
385                                        }
386                                }
387                        }
388                }
389                // 全くデータが存在しない行は読み飛ばし
390                else {
391                        return;
392                }
393        }
394
395        /**
396         * ODSファイルに含まれるcontent.xmlをDOMパーサーでパースし、行、列単位に
397         * オブジェクトに変換します。
398         *
399         */
400        private static class DomOdsParser{
401
402                // OpenOffice.org Calc tag Names
403//              private static final String OFFICE_DOCUMENT_CONTEBT_ELEM = "office:document-content";
404//              private static final String OFFICE_SPREADSHEET_ELEM = "office:spreadsheet";
405                private static final String TABLE_TABLE_ELEM = "table:table";
406//              private static final String TABLE_TABLE_COLUMN_ELEM = "table:table-column";
407                private static final String TABLE_TABLE_ROW_ELEM = "table:table-row";
408                private static final String TABLE_TABLE_CELL_ELEM = "table:table-cell";
409                private static final String TEXT_P_ELEM = "text:p";
410
411                // Sheet tag attributes
412                private static final String TABLE_NAME_ATTR = "table:name";
413//              private static final String OFFICE_VALUE_YPE_ATTR = "office:value-type";
414                private static final String TABLE_NUMBER_ROWS_REPEATED_ATTR = "table:number-rows-repeated";
415                private static final String TABLE_NUMBER_COLUMNS_REPEATED_ATTR = "table:number-columns-repeated";
416//              private static final String TABLE_NUMBER_ROWS_SPANNED_ATTR = "table:number-rows-spanned";
417//              private static final String TABLE_NUMBER_COLUMNS_SPANNED_ATTR = "table:number-columns-spanned";
418
419//              ArrayList<RowInfo> rowInfoList = new ArrayList<RowInfo>();
420                List<RowInfo> rowInfoList = new ArrayList<RowInfo>();
421                /**
422                 * DomパーサでXMLをパースする
423                 *
424                 * @og.rev 5.5.7.2 (2012/10/09) sheetNos 追加による複数シートのマージ読み取りサポート
425                 *
426                 * @param inputStream InputStream
427                 * @param sheetName String
428                 * @param sheetNos  String
429                 */
430//              public void doParse( final InputStream inputStream, final String sheetName ) {
431                public void doParse( final InputStream inputStream, final String sheetName, final String sheetNos ) {
432                        try {
433                                // ドキュメントビルダーファクトリを生成
434                                DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
435                                dbFactory.setNamespaceAware( true );
436
437                                // ドキュメントビルダーを生成
438                                DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
439                                // パースを実行してDocumentオブジェクトを取得
440                                Document doc = dBuilder.parse( inputStream );
441//                              processBook( doc, sheetName );
442                                processBook( doc, sheetName, sheetNos );                        // 5.5.7.2 (2012/10/09) sheetNos 追加
443                        }
444                        catch ( ParserConfigurationException ex ) {
445                                throw new HybsSystemException( ex );
446                        }
447                        catch ( SAXException ex ) {
448                                String errMsg = "ODSファイル中に含まれるcontent.xmlがXML形式ではありません。";
449                                throw new HybsSystemException( errMsg, ex );
450                        }
451                        catch ( IOException ex ) {
452                                throw new HybsSystemException( ex );
453                        }
454                }
455
456                /**
457                 * 行オブジェクトのリストを返します。
458                 *
459                 * @return List<RowInfo>
460                 */
461                public List<RowInfo> getRowInfoList() {
462                        return rowInfoList;
463                }
464
465                /**
466                 * ODSファイル全体のパースを行い、処理対象となるシートを検索します。
467                 *
468                 * @og.rev 5.5.7.2 (2012/10/09) sheetNos 追加による複数シートのマージ読み取りサポート
469                 *
470                 * @param doc Document
471                 * @param sheetName String
472                 * @param sheetNos  String
473                 */
474//              private void processBook( final Document doc, final String sheetName ) {
475                private void processBook( final Document doc, final String sheetName, final String sheetNos ) {
476                        // table:tableを探す
477                        NodeList nodetList = doc.getElementsByTagName( TABLE_TABLE_ELEM );
478                        int listLen = nodetList.getLength();
479
480//                      Element sheet = null;
481                        Element[] sheets = null ;                       // 5.5.7.2 (2012/10/09)
482
483                        // 5.5.7.2 (2012/10/09) 複数シートのマージ読み取り。 sheetNos の指定が優先される。
484                        if( sheetNos != null && sheetNos.length() > 0 ) {
485                                String[] sheetList = StringUtil.csv2ArrayExt( sheetNos , listLen-1 );   // 最大シート番号は、シート数-1
486                                sheets = new Element[sheetList.length];
487                                for( int i=0; i<sheetList.length; i++ ) {
488                                        sheets[i] = (Element)nodetList.item( Integer.parseInt( sheetList[i] ) );
489                                }
490                        }
491                        else if( sheetName != null && sheetName.length() > 0 ) {
492                                Element sheet = null;
493                                for ( int idx = 0; idx < listLen; idx++ ) {
494                                        Element st = (Element)nodetList.item( idx );
495                                        if ( sheetName.equals( st.getAttribute( TABLE_NAME_ATTR ) ) ) {
496                                                sheet = st;
497                                                break;
498                                        }
499                                }
500                                if( sheet == null ) {
501                                        String errMsg = "対応するシートが存在しません。 sheetName=[" + sheetName + "]" ;
502                                        throw new HybsSystemException( errMsg );
503                                }
504                                sheets = new Element[] { sheet };
505                        }
506                        else {
507                                Element sheet = (Element)nodetList.item(0);
508                                sheets = new Element[] { sheet };
509                        }
510
511//                      // シート探し:シート名があれば、そのまま使用、なければ、最初のシートを対象とします。
512//                      for ( int idx = 0; idx < listLen; idx++ ) {
513//                              Element st = (Element)nodetList.item( idx );
514//                              if ( ( sheetName == null || sheetName.length() == 0 ) || sheetName.equals( st.getAttribute( TABLE_NAME_ATTR ) ) ) {
515//                                      sheet = st;
516//                                      break;
517//                              }
518//                      }
519                        // 指定のシートがなければ、エラー
520//                      if ( sheet == null ) {
521                        if ( sheets == null ) {
522                                String errMsg = "対応するシートが存在しません。 sheetNos=[" + sheetNos + "] or sheetName=[" + sheetName + "]";
523                                throw new HybsSystemException( errMsg );
524                        }
525                        else {
526//                              processSheet( sheet );
527                                // 5.5.7.2 (2012/10/09) 複数シートのマージ読み取り。
528                                for( int i=0; i<sheets.length; i++ ) {
529                                        processSheet( sheets[i] );
530                                }
531                        }
532                }
533
534                /**
535                 * ODSファイルのシート単位のパースを行い、行単位のオブジェクトを生成します。
536                 *
537                 * @param sheet Element
538                 */
539                private void processSheet( final Element sheet ) {
540                        NodeList rows = sheet.getElementsByTagName( TABLE_TABLE_ROW_ELEM );
541                        int listLen = rows.getLength();
542                        int rowRepeat;
543                        for ( int idx = 0; idx < listLen; idx++ ) {
544                                Element row = (Element)rows.item( idx );
545                                // 行の内容が全く同じ場合、table:number-rows-repeatedタグにより省略される。
546                                String repeatStr = row.getAttribute( TABLE_NUMBER_ROWS_REPEATED_ATTR );
547                                if ( repeatStr == null || repeatStr.length() == 0 ) {
548                                        rowRepeat = 1;
549                                }
550                                else {
551                                        rowRepeat = Integer.parseInt( repeatStr, 10 );
552                                }
553
554                                processRow( row, rowRepeat );
555                        }
556                }
557
558                /**
559                 * ODSファイルの行単位のパースを行い、カラム単位のオブジェクトを生成します。
560                 *
561                 * @og.rev 4.3.5.0 (2009/02/01) toArray するときに、サイズの初期値指定を追加
562                 * @og.rev 5.1.8.0 (2010/07/01) セル内で書式設定されている場合に、テキストデータが取得されないバグを修正
563                 *
564                 * @param row Element
565                 * @param rowRepeat int
566                 */
567                private void processRow( final Element row, final int rowRepeat ) {
568                        NodeList cells = row.getElementsByTagName( TABLE_TABLE_CELL_ELEM );
569                        int listLen = cells.getLength();
570                        int colRepeat;
571                        String cellText;
572                        ArrayList<CellInfo> cellInfoList = new ArrayList<CellInfo>();
573                        for ( int idx = 0; idx < listLen; idx++ ) {
574                                Element cell = (Element)cells.item( idx );
575                                // カラムの内容が全く同じ場合、table:number-columns-repeatedタグにより省略される。
576                                String repeatStr = cell.getAttribute( TABLE_NUMBER_COLUMNS_REPEATED_ATTR );
577                                if ( repeatStr == null || repeatStr.length() == 0 ) {
578                                        colRepeat = 1;
579                                }
580                                else {
581                                        colRepeat = Integer.parseInt( repeatStr, 10 );
582                                }
583
584                                // text:p
585                                NodeList texts = cell.getElementsByTagName( TEXT_P_ELEM );
586                                if ( texts.getLength() == 0 ) {
587                                        cellText = "";
588                                }
589                                else {
590                                        // 5.1.8.0 (2010/07/01) セル内で書式設定されている場合に、テキストデータが取得されないバグを修正
591//                                      StringBuilder sb = new StringBuilder();
592//                                      NodeList textElems = texts.item( 0 ).getChildNodes();
593//                                      int textElemLen = textElems.getLength();
594//                                      for ( int idxt = 0; idxt < textElemLen; idxt++ ) {
595//                                              Node textElem = textElems.item( idxt );
596//                                              if ( textElem.getNodeType() == Node.TEXT_NODE ) {
597//                                                      sb.append( textElem.getNodeValue() );
598//                                              }
599//                                      }
600//                                      cellText = sb.toString();
601                                        cellText = texts.item( 0 ).getTextContent();
602                                }
603                                cellInfoList.add( new CellInfo( colRepeat, cellText ) );
604                        }
605
606//                      if ( cellInfoList.size() > 0 ) {
607                        if ( ! cellInfoList.isEmpty() ) {
608//                              rowInfoList.add( new RowInfo( rowRepeat, cellInfoList.toArray( new CellInfo[0] ) ) );
609                                // 4.3.5.0 (2009/02/01) toArray するときに、サイズの初期値指定を追加
610                                rowInfoList.add( new RowInfo( rowRepeat, cellInfoList.toArray( new CellInfo[cellInfoList.size()] ) ) );
611                        }
612                }
613        }
614
615        /**
616         * ODSファイルの行情報を表す構造体
617         */
618        private static final class RowInfo {
619                public final int rowRepeat;
620                public final CellInfo[] cellInfos;
621
622                RowInfo( final int rep, final CellInfo[] cell ) {
623                        rowRepeat = rep;
624                        cellInfos = cell;
625                }
626        }
627
628        /**
629         * ODSファイルのカラム情報を表す構造体
630         */
631        private static final class CellInfo {
632                public final int colRepeat;
633                public final String text;
634
635                CellInfo( final int rep, final String tx ) {
636                        colRepeat = rep;
637                        text = tx;
638                }
639        }
640}