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