1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 #include "oox/xls/pivotcachefragment.hxx"
29 
30 #include "oox/helper/attributelist.hxx"
31 #include "oox/xls/addressconverter.hxx"
32 #include "oox/xls/biffinputstream.hxx"
33 #include "oox/xls/pivotcachebuffer.hxx"
34 
35 namespace oox {
36 namespace xls {
37 
38 // ============================================================================
39 
40 using namespace ::com::sun::star::uno;
41 using namespace ::oox::core;
42 
43 using ::rtl::OUString;
44 
45 // ============================================================================
46 
47 PivotCacheFieldContext::PivotCacheFieldContext( WorkbookFragmentBase& rFragment, PivotCacheField& rCacheField ) :
48     WorkbookContextBase( rFragment ),
49     mrCacheField( rCacheField )
50 {
51 }
52 
53 ContextHandlerRef PivotCacheFieldContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
54 {
55     switch( getCurrentElement() )
56     {
57         case XLS_TOKEN( cacheField ):
58             if( nElement == XLS_TOKEN( sharedItems ) )  { mrCacheField.importSharedItems( rAttribs );   return this; }
59             if( nElement == XLS_TOKEN( fieldGroup ) )   { mrCacheField.importFieldGroup( rAttribs );    return this; }
60         break;
61 
62         case XLS_TOKEN( fieldGroup ):
63             switch( nElement )
64             {
65                 case XLS_TOKEN( rangePr ):      mrCacheField.importRangePr( rAttribs );     break;
66                 case XLS_TOKEN( discretePr ):   return this;
67                 case XLS_TOKEN( groupItems ):   return this;
68             }
69         break;
70 
71         case XLS_TOKEN( sharedItems ):  mrCacheField.importSharedItem( nElement, rAttribs );        break;
72         case XLS_TOKEN( discretePr ):   mrCacheField.importDiscretePrItem( nElement, rAttribs );    break;
73         case XLS_TOKEN( groupItems ):   mrCacheField.importGroupItem( nElement, rAttribs );         break;
74     }
75     return 0;
76 }
77 
78 void PivotCacheFieldContext::onStartElement( const AttributeList& rAttribs )
79 {
80     if( isRootElement() )
81         mrCacheField.importCacheField( rAttribs );
82 }
83 
84 ContextHandlerRef PivotCacheFieldContext::onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm )
85 {
86     switch( getCurrentElement() )
87     {
88         case BIFF12_ID_PCDFIELD:
89             switch( nRecId )
90             {
91                 case BIFF12_ID_PCDFSHAREDITEMS: mrCacheField.importPCDFSharedItems( rStrm );  return this;
92                 case BIFF12_ID_PCDFIELDGROUP:   mrCacheField.importPCDFieldGroup( rStrm );    return this;
93             }
94         break;
95 
96         case BIFF12_ID_PCDFIELDGROUP:
97             switch( nRecId )
98             {
99                 case BIFF12_ID_PCDFRANGEPR:     mrCacheField.importPCDFRangePr( rStrm );    break;
100                 case BIFF12_ID_PCDFDISCRETEPR:  return this;
101                 case BIFF12_ID_PCDFGROUPITEMS:  return this;
102             }
103         break;
104 
105         case BIFF12_ID_PCDFSHAREDITEMS: mrCacheField.importPCDFSharedItem( nRecId, rStrm );     break;
106         case BIFF12_ID_PCDFDISCRETEPR:  mrCacheField.importPCDFDiscretePrItem( nRecId, rStrm ); break;
107         case BIFF12_ID_PCDFGROUPITEMS:  mrCacheField.importPCDFGroupItem( nRecId, rStrm );      break;
108     }
109     return 0;
110 }
111 
112 void PivotCacheFieldContext::onStartRecord( SequenceInputStream& rStrm )
113 {
114     if( isRootElement() )
115         mrCacheField.importPCDField( rStrm );
116 }
117 
118 // ============================================================================
119 
120 PivotCacheDefinitionFragment::PivotCacheDefinitionFragment(
121         const WorkbookHelper& rHelper, const OUString& rFragmentPath, PivotCache& rPivotCache ) :
122     WorkbookFragmentBase( rHelper, rFragmentPath ),
123     mrPivotCache( rPivotCache )
124 {
125 }
126 
127 ContextHandlerRef PivotCacheDefinitionFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
128 {
129     switch( getCurrentElement() )
130     {
131         case XML_ROOT_CONTEXT:
132             if( nElement == XLS_TOKEN( pivotCacheDefinition ) ) { mrPivotCache.importPivotCacheDefinition( rAttribs ); return this; }
133         break;
134 
135         case XLS_TOKEN( pivotCacheDefinition ):
136             switch( nElement )
137             {
138                 case XLS_TOKEN( cacheSource ):  mrPivotCache.importCacheSource( rAttribs ); return this;
139                 case XLS_TOKEN( cacheFields ):  return this;
140             }
141         break;
142 
143         case XLS_TOKEN( cacheSource ):
144             if( nElement == XLS_TOKEN( worksheetSource ) ) mrPivotCache.importWorksheetSource( rAttribs, getRelations() );
145         break;
146 
147         case XLS_TOKEN( cacheFields ):
148             if( nElement == XLS_TOKEN( cacheField ) ) return new PivotCacheFieldContext( *this, mrPivotCache.createCacheField() );
149         break;
150     }
151     return 0;
152 }
153 
154 ContextHandlerRef PivotCacheDefinitionFragment::onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm )
155 {
156     switch( getCurrentElement() )
157     {
158         case XML_ROOT_CONTEXT:
159             if( nRecId == BIFF12_ID_PCDEFINITION ) { mrPivotCache.importPCDefinition( rStrm ); return this; }
160         break;
161 
162         case BIFF12_ID_PCDEFINITION:
163             switch( nRecId )
164             {
165                 case BIFF12_ID_PCDSOURCE: mrPivotCache.importPCDSource( rStrm ); return this;
166                 case BIFF12_ID_PCDFIELDS: return this;
167             }
168         break;
169 
170         case BIFF12_ID_PCDSOURCE:
171             if( nRecId == BIFF12_ID_PCDSHEETSOURCE ) mrPivotCache.importPCDSheetSource( rStrm, getRelations() );
172         break;
173 
174         case BIFF12_ID_PCDFIELDS:
175             if( nRecId == BIFF12_ID_PCDFIELD ) return new PivotCacheFieldContext( *this, mrPivotCache.createCacheField() );
176         break;
177     }
178     return 0;
179 }
180 
181 const RecordInfo* PivotCacheDefinitionFragment::getRecordInfos() const
182 {
183     static const RecordInfo spRecInfos[] =
184     {
185         { BIFF12_ID_PCDEFINITION,       BIFF12_ID_PCDEFINITION + 1      },
186         { BIFF12_ID_PCDFDISCRETEPR,     BIFF12_ID_PCDFDISCRETEPR + 1    },
187         { BIFF12_ID_PCDFGROUPITEMS,     BIFF12_ID_PCDFGROUPITEMS + 1    },
188         { BIFF12_ID_PCDFIELD,           BIFF12_ID_PCDFIELD + 1          },
189         { BIFF12_ID_PCDFIELDGROUP,      BIFF12_ID_PCDFIELDGROUP + 1     },
190         { BIFF12_ID_PCDFIELDS,          BIFF12_ID_PCDFIELDS + 1         },
191         { BIFF12_ID_PCDFRANGEPR,        BIFF12_ID_PCDFRANGEPR + 1       },
192         { BIFF12_ID_PCDFSHAREDITEMS,    BIFF12_ID_PCDFSHAREDITEMS + 1   },
193         { BIFF12_ID_PCITEM_ARRAY,       BIFF12_ID_PCITEM_ARRAY + 1      },
194         { BIFF12_ID_PCDSHEETSOURCE,     BIFF12_ID_PCDSHEETSOURCE + 1    },
195         { BIFF12_ID_PCDSOURCE,          BIFF12_ID_PCDSOURCE + 1         },
196         { -1,                           -1                              }
197     };
198     return spRecInfos;
199 }
200 
201 void PivotCacheDefinitionFragment::finalizeImport()
202 {
203     // finalize the cache (check source range etc.)
204     mrPivotCache.finalizeImport();
205 
206     // load the cache records, if the cache is based on a deleted or an external worksheet
207     if( mrPivotCache.isValidDataSource() && mrPivotCache.isBasedOnDummySheet() )
208     {
209         OUString aRecFragmentPath = getRelations().getFragmentPathFromRelId( mrPivotCache.getRecordsRelId() );
210         if( aRecFragmentPath.getLength() > 0 )
211         {
212             sal_Int16 nSheet = mrPivotCache.getSourceRange().Sheet;
213             WorksheetGlobalsRef xSheetGlob = WorksheetHelper::constructGlobals( *this, ISegmentProgressBarRef(), SHEETTYPE_WORKSHEET, nSheet );
214             if( xSheetGlob.get() )
215                 importOoxFragment( new PivotCacheRecordsFragment( *xSheetGlob, aRecFragmentPath, mrPivotCache ) );
216         }
217     }
218 }
219 
220 // ============================================================================
221 
222 PivotCacheRecordsFragment::PivotCacheRecordsFragment( const WorksheetHelper& rHelper,
223         const OUString& rFragmentPath, const PivotCache& rPivotCache ) :
224     WorksheetFragmentBase( rHelper, rFragmentPath ),
225     mrPivotCache( rPivotCache ),
226     mnColIdx( 0 ),
227     mnRowIdx( 0 ),
228     mbInRecord( false )
229 {
230     // prepare sheet: insert column header names into top row
231     rPivotCache.writeSourceHeaderCells( *this );
232 }
233 
234 ContextHandlerRef PivotCacheRecordsFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
235 {
236     switch( getCurrentElement() )
237     {
238         case XML_ROOT_CONTEXT:
239             if( nElement == XLS_TOKEN( pivotCacheRecords ) ) return this;
240         break;
241 
242         case XLS_TOKEN( pivotCacheRecords ):
243             if( nElement == XLS_TOKEN( r ) ) { startCacheRecord(); return this; }
244         break;
245 
246         case XLS_TOKEN( r ):
247         {
248             PivotCacheItem aItem;
249             switch( nElement )
250             {
251                 case XLS_TOKEN( m ):                                                        break;
252                 case XLS_TOKEN( s ):    aItem.readString( rAttribs );                       break;
253                 case XLS_TOKEN( n ):    aItem.readNumeric( rAttribs );                      break;
254                 case XLS_TOKEN( d ):    aItem.readDate( rAttribs );                         break;
255                 case XLS_TOKEN( b ):    aItem.readBool( rAttribs );                         break;
256                 case XLS_TOKEN( e ):    aItem.readError( rAttribs, getUnitConverter() );    break;
257                 case XLS_TOKEN( x ):    aItem.readIndex( rAttribs );                        break;
258                 default:    OSL_ENSURE( false, "PivotCacheRecordsFragment::onCreateContext - unexpected element" );
259             }
260             mrPivotCache.writeSourceDataCell( *this, mnColIdx, mnRowIdx, aItem );
261             ++mnColIdx;
262         }
263         break;
264     }
265     return 0;
266 }
267 
268 ContextHandlerRef PivotCacheRecordsFragment::onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm )
269 {
270     switch( getCurrentElement() )
271     {
272         case XML_ROOT_CONTEXT:
273             if( nRecId == BIFF12_ID_PCRECORDS ) return this;
274         break;
275 
276         case BIFF12_ID_PCRECORDS:
277             switch( nRecId )
278             {
279                 case BIFF12_ID_PCRECORD:    importPCRecord( rStrm );                break;
280                 case BIFF12_ID_PCRECORDDT:  startCacheRecord();                     break;
281                 default:                    importPCRecordItem( nRecId, rStrm );    break;
282             }
283         break;
284     }
285     return 0;
286 }
287 
288 const RecordInfo* PivotCacheRecordsFragment::getRecordInfos() const
289 {
290     static const RecordInfo spRecInfos[] =
291     {
292         { BIFF12_ID_PCRECORDS,  BIFF12_ID_PCRECORDS + 1 },
293         { -1,                   -1                      }
294     };
295     return spRecInfos;
296 }
297 
298 // private --------------------------------------------------------------------
299 
300 void PivotCacheRecordsFragment::startCacheRecord()
301 {
302     mnColIdx = 0;
303     ++mnRowIdx;
304     mbInRecord = true;
305 }
306 
307 void PivotCacheRecordsFragment::importPCRecord( SequenceInputStream& rStrm )
308 {
309     startCacheRecord();
310     mrPivotCache.importPCRecord( rStrm, *this, mnRowIdx );
311     mbInRecord = false;
312 }
313 
314 void PivotCacheRecordsFragment::importPCRecordItem( sal_Int32 nRecId, SequenceInputStream& rStrm )
315 {
316     if( mbInRecord )
317     {
318         PivotCacheItem aItem;
319         switch( nRecId )
320         {
321             case BIFF12_ID_PCITEM_MISSING:                              break;
322             case BIFF12_ID_PCITEM_STRING:   aItem.readString( rStrm );  break;
323             case BIFF12_ID_PCITEM_DOUBLE:   aItem.readDouble( rStrm );  break;
324             case BIFF12_ID_PCITEM_DATE:     aItem.readDate( rStrm );    break;
325             case BIFF12_ID_PCITEM_BOOL:     aItem.readBool( rStrm );    break;
326             case BIFF12_ID_PCITEM_ERROR:    aItem.readError( rStrm );   break;
327             case BIFF12_ID_PCITEM_INDEX:    aItem.readIndex( rStrm );   break;
328             default:    OSL_ENSURE( false, "PivotCacheRecordsFragment::importPCRecordItem - unexpected record" );
329         }
330         mrPivotCache.writeSourceDataCell( *this, mnColIdx, mnRowIdx, aItem );
331         ++mnColIdx;
332     }
333 }
334 
335 // ============================================================================
336 // ============================================================================
337 
338 namespace {
339 
340 bool lclSeekToPCDField( BiffInputStream& rStrm )
341 {
342     sal_Int64 nRecHandle = rStrm.getRecHandle();
343     while( rStrm.startNextRecord() )
344         if( rStrm.getRecId() == BIFF_ID_PCDFIELD )
345             return true;
346     rStrm.startRecordByHandle( nRecHandle );
347     return false;
348 }
349 
350 } // namespace
351 
352 // ----------------------------------------------------------------------------
353 
354 BiffPivotCacheFragment::BiffPivotCacheFragment(
355         const WorkbookHelper& rHelper, const OUString& rStrmName, PivotCache& rPivotCache ) :
356     BiffWorkbookFragmentBase( rHelper, rStrmName, true ),
357     mrPivotCache( rPivotCache )
358 {
359 }
360 
361 bool BiffPivotCacheFragment::importFragment()
362 {
363     BiffInputStream& rStrm = getInputStream();
364     if( rStrm.startNextRecord() && (rStrm.getRecId() == BIFF_ID_PCDEFINITION) )
365     {
366         // read PCDEFINITION and optional PCDEFINITION2 records
367         mrPivotCache.importPCDefinition( rStrm );
368 
369         // read cache fields as long as another PCDFIELD record can be found
370         while( lclSeekToPCDField( rStrm ) )
371             mrPivotCache.createCacheField( true ).importPCDField( rStrm );
372 
373         // finalize the cache (check source range etc.)
374         mrPivotCache.finalizeImport();
375 
376         // load the cache records, if the cache is based on a deleted or an external worksheet
377         if( mrPivotCache.isValidDataSource() && mrPivotCache.isBasedOnDummySheet() )
378         {
379             /*  Last call of lclSeekToPCDField() failed and kept stream position
380                 unchanged. Stream should point to source data table now. */
381             sal_Int16 nSheet = mrPivotCache.getSourceRange().Sheet;
382             WorksheetGlobalsRef xSheetGlob = WorksheetHelper::constructGlobals( *this, ISegmentProgressBarRef(), SHEETTYPE_WORKSHEET, nSheet );
383             if( xSheetGlob.get() )
384             {
385                 BiffPivotCacheRecordsContext aContext( *xSheetGlob, mrPivotCache );
386                 while( rStrm.startNextRecord() && (rStrm.getRecId() != BIFF_ID_EOF) )
387                     aContext.importRecord( rStrm );
388             }
389         }
390     }
391 
392     return rStrm.getRecId() == BIFF_ID_EOF;
393 }
394 
395 // ============================================================================
396 
397 BiffPivotCacheRecordsContext::BiffPivotCacheRecordsContext( const WorksheetHelper& rHelper, const PivotCache& rPivotCache ) :
398     BiffWorksheetContextBase( rHelper ),
399     mrPivotCache( rPivotCache ),
400     mnColIdx( 0 ),
401     mnRowIdx( 0 ),
402     mbHasShared( false ),
403     mbInRow( false )
404 {
405     // prepare sheet: insert column header names into top row
406     mrPivotCache.writeSourceHeaderCells( *this );
407 
408     // find all fields without shared items, remember column indexes in source data
409     for( sal_Int32 nFieldIdx = 0, nFieldCount = mrPivotCache.getCacheFieldCount(), nCol = 0; nFieldIdx < nFieldCount; ++nFieldIdx )
410     {
411         const PivotCacheField* pCacheField = mrPivotCache.getCacheField( nFieldIdx );
412         if( pCacheField && pCacheField->isDatabaseField() )
413         {
414             if( pCacheField->hasSharedItems() )
415                 mbHasShared = true;
416             else
417                 maUnsharedCols.push_back( nCol );
418             ++nCol;
419         }
420     }
421 }
422 
423 void BiffPivotCacheRecordsContext::importRecord( BiffInputStream& rStrm )
424 {
425     if( rStrm.getRecId() == BIFF_ID_PCITEM_INDEXLIST )
426     {
427         OSL_ENSURE( mbHasShared, "BiffPivotCacheRecordsContext::importRecord - unexpected PCITEM_INDEXLIST record" );
428         // PCITEM_INDEXLIST record always in front of a new data row
429         startNextRow();
430         mrPivotCache.importPCItemIndexList( rStrm, *this, mnRowIdx );
431         mbInRow = !maUnsharedCols.empty();  // mbInRow remains true, if unshared items are expected
432         return;
433     }
434 
435     PivotCacheItem aItem;
436     switch( rStrm.getRecId() )
437     {
438         case BIFF_ID_PCITEM_MISSING:                                        break;
439         case BIFF_ID_PCITEM_STRING:     aItem.readString( rStrm, *this );   break;
440         case BIFF_ID_PCITEM_DOUBLE:     aItem.readDouble( rStrm );          break;
441         case BIFF_ID_PCITEM_INTEGER:    aItem.readInteger( rStrm );         break;
442         case BIFF_ID_PCITEM_DATE:       aItem.readDate( rStrm );            break;
443         case BIFF_ID_PCITEM_BOOL:       aItem.readBool( rStrm );            break;
444         case BIFF_ID_PCITEM_ERROR:      aItem.readError( rStrm );           break;
445         default:                        return; // unknown record, ignore
446     }
447 
448     // find next column index, might start new row if no fields with shared items exist
449     if( mbInRow && (mnColIdx == maUnsharedCols.size()) )
450     {
451         OSL_ENSURE( !mbHasShared, "BiffPivotCacheRecordsContext::importRecord - PCITEM_INDEXLIST record missing" );
452         mbInRow = mbHasShared;  // do not leave current row if PCITEM_INDEXLIST is expected
453     }
454     // start next row on first call, or on row wrap without shared items
455     if( !mbInRow )
456         startNextRow();
457 
458     // write the item data to the sheet cell
459     OSL_ENSURE( mnColIdx < maUnsharedCols.size(), "BiffPivotCacheRecordsContext::importRecord - invalid column index" );
460     if( mnColIdx < maUnsharedCols.size() )
461         mrPivotCache.writeSourceDataCell( *this, maUnsharedCols[ mnColIdx ], mnRowIdx, aItem );
462     ++mnColIdx;
463 }
464 
465 void BiffPivotCacheRecordsContext::startNextRow()
466 {
467     mnColIdx = 0;
468     ++mnRowIdx;
469     mbInRow = true;
470 }
471 
472 // ============================================================================
473 
474 } // namespace xls
475 } // namespace oox
476