xref: /trunk/main/oox/source/xls/sheetdatabuffer.cxx (revision d0626817)
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/sheetdatabuffer.hxx"
25 
26 #include <algorithm>
27 #include <com/sun/star/sheet/XArrayFormulaTokens.hpp>
28 #include <com/sun/star/sheet/XCellRangeData.hpp>
29 #include <com/sun/star/sheet/XFormulaTokens.hpp>
30 #include <com/sun/star/sheet/XMultipleOperation.hpp>
31 #include <com/sun/star/table/XCell.hpp>
32 #include <com/sun/star/text/XText.hpp>
33 #include <com/sun/star/util/DateTime.hpp>
34 #include <com/sun/star/util/NumberFormat.hpp>
35 #include <com/sun/star/util/XMergeable.hpp>
36 #include <com/sun/star/util/XNumberFormatTypes.hpp>
37 #include <com/sun/star/util/XNumberFormatsSupplier.hpp>
38 #include <rtl/ustrbuf.hxx>
39 #include "oox/helper/containerhelper.hxx"
40 #include "oox/helper/propertymap.hxx"
41 #include "oox/helper/propertyset.hxx"
42 #include "oox/token/tokens.hxx"
43 #include "oox/xls/addressconverter.hxx"
44 #include "oox/xls/biffinputstream.hxx"
45 #include "oox/xls/formulaparser.hxx"
46 #include "oox/xls/sharedstringsbuffer.hxx"
47 #include "oox/xls/unitconverter.hxx"
48 
49 namespace oox {
50 namespace xls {
51 
52 // ============================================================================
53 
54 using namespace ::com::sun::star::lang;
55 using namespace ::com::sun::star::sheet;
56 using namespace ::com::sun::star::table;
57 using namespace ::com::sun::star::text;
58 using namespace ::com::sun::star::uno;
59 using namespace ::com::sun::star::util;
60 
61 using ::rtl::OUString;
62 using ::rtl::OUStringBuffer;
63 
64 // ============================================================================
65 
66 CellModel::CellModel() :
67     mnCellType( XML_TOKEN_INVALID ),
68     mnXfId( -1 ),
69     mbShowPhonetic( false )
70 {
71 }
72 
73 // ----------------------------------------------------------------------------
74 
75 CellFormulaModel::CellFormulaModel() :
76     mnFormulaType( XML_TOKEN_INVALID ),
77     mnSharedId( -1 )
78 {
79 }
80 
81 bool CellFormulaModel::isValidArrayRef( const CellAddress& rCellAddr )
82 {
83     return
84         (maFormulaRef.Sheet == rCellAddr.Sheet) &&
85         (maFormulaRef.StartColumn == rCellAddr.Column) &&
86         (maFormulaRef.StartRow == rCellAddr.Row);
87 }
88 
89 bool CellFormulaModel::isValidSharedRef( const CellAddress& rCellAddr )
90 {
91     return
92         (maFormulaRef.Sheet == rCellAddr.Sheet) &&
93         (maFormulaRef.StartColumn <= rCellAddr.Column) && (rCellAddr.Column <= maFormulaRef.EndColumn) &&
94         (maFormulaRef.StartRow <= rCellAddr.Row) && (rCellAddr.Row <= maFormulaRef.EndRow);
95 }
96 
97 // ----------------------------------------------------------------------------
98 
99 DataTableModel::DataTableModel() :
100     mb2dTable( false ),
101     mbRowTable( false ),
102     mbRef1Deleted( false ),
103     mbRef2Deleted( false )
104 {
105 }
106 
107 // ============================================================================
108 
109 namespace {
110 
111 const sal_Int32 CELLBLOCK_MAXROWS  = 16;    /// Number of rows in a cell block.
112 
113 } // namespace
114 
115 CellBlock::CellBlock( const WorksheetHelper& rHelper, const ValueRange& rColSpan, sal_Int32 nRow ) :
116     WorksheetHelper( rHelper ),
117     maRange( rHelper.getSheetIndex(), rColSpan.mnFirst, nRow, rColSpan.mnLast, nRow ),
118     mnRowLength( rColSpan.mnLast - rColSpan.mnFirst + 1 ),
119     mnFirstFreeIndex( 0 )
120 {
121     maCellArray.realloc( 1 );
122     maCellArray[ 0 ].realloc( mnRowLength );
123     mpCurrCellRow = maCellArray[ 0 ].getArray();
124 }
125 
126 bool CellBlock::isExpandable( const ValueRange& rColSpan ) const
127 {
128     return (maRange.StartColumn == rColSpan.mnFirst) && (maRange.EndColumn == rColSpan.mnLast);
129 }
130 
131 bool CellBlock::isBefore( const ValueRange& rColSpan ) const
132 {
133     return (maRange.EndColumn < rColSpan.mnLast) ||
134         ((maRange.EndColumn == rColSpan.mnLast) && (maRange.StartColumn != rColSpan.mnFirst));
135 }
136 
137 bool CellBlock::contains( sal_Int32 nCol ) const
138 {
139     return (maRange.StartColumn <= nCol) && (nCol <= maRange.EndColumn);
140 }
141 
142 void CellBlock::insertRichString( const CellAddress& rAddress, const RichStringRef& rxString, const Font* pFirstPortionFont )
143 {
144     maRichStrings.push_back( RichStringCell( rAddress, rxString, pFirstPortionFont ) );
145 }
146 
147 void CellBlock::startNextRow()
148 {
149     // fill last cells in current row with empty strings (placeholder for empty cells)
150     fillUnusedCells( mnRowLength );
151     // flush if the cell block reaches maximum size
152     if( maCellArray.getLength() == CELLBLOCK_MAXROWS )
153     {
154         finalizeImport();
155         maRange.StartRow = ++maRange.EndRow;
156         maCellArray.realloc( 1 );
157         mpCurrCellRow = maCellArray[ 0 ].getArray();
158     }
159     else
160     {
161         // prepare next row
162         ++maRange.EndRow;
163         sal_Int32 nRowCount = maCellArray.getLength();
164         maCellArray.realloc( nRowCount + 1 );
165         maCellArray[ nRowCount ].realloc( mnRowLength );
166         mpCurrCellRow = maCellArray[ nRowCount ].getArray();
167     }
168     mnFirstFreeIndex = 0;
169 }
170 
171 Any& CellBlock::getCellAny( sal_Int32 nCol )
172 {
173     OSL_ENSURE( contains( nCol ), "CellBlock::getCellAny - invalid column" );
174     // fill cells before passed column with empty strings (the placeholder for empty cells)
175     sal_Int32 nIndex = nCol - maRange.StartColumn;
176     fillUnusedCells( nIndex );
177     mnFirstFreeIndex = nIndex + 1;
178     return mpCurrCellRow[ nIndex ];
179 }
180 
181 void CellBlock::finalizeImport()
182 {
183     // fill last cells in last row with empty strings (placeholder for empty cells)
184     fillUnusedCells( mnRowLength );
185     // insert all buffered cells into the Calc sheet
186     try
187     {
188         Reference< XCellRangeData > xRangeData( getCellRange( maRange ), UNO_QUERY_THROW );
189         xRangeData->setDataArray( maCellArray );
190     }
191     catch( Exception& )
192     {
193     }
194     // insert uncacheable cells separately
195     for( RichStringCellList::const_iterator aIt = maRichStrings.begin(), aEnd = maRichStrings.end(); aIt != aEnd; ++aIt )
196         putRichString( aIt->maCellAddr, *aIt->mxString, aIt->mpFirstPortionFont );
197 }
198 
199 // private --------------------------------------------------------------------
200 
201 CellBlock::RichStringCell::RichStringCell( const CellAddress& rCellAddr, const RichStringRef& rxString, const Font* pFirstPortionFont ) :
202     maCellAddr( rCellAddr ),
203     mxString( rxString ),
204     mpFirstPortionFont( pFirstPortionFont )
205 {
206 }
207 
208 void CellBlock::fillUnusedCells( sal_Int32 nIndex )
209 {
210     if( mnFirstFreeIndex < nIndex )
211     {
212         Any* pCellEnd = mpCurrCellRow + nIndex;
213         for( Any* pCell = mpCurrCellRow + mnFirstFreeIndex; pCell < pCellEnd; ++pCell )
214             *pCell <<= OUString();
215     }
216 }
217 
218 // ============================================================================
219 
220 CellBlockBuffer::CellBlockBuffer( const WorksheetHelper& rHelper ) :
221     WorksheetHelper( rHelper ),
222     mnCurrRow( -1 )
223 {
224     maCellBlockIt = maCellBlocks.end();
225 }
226 
227 void CellBlockBuffer::setColSpans( sal_Int32 nRow, const ValueRangeSet& rColSpans )
228 {
229     OSL_ENSURE( maColSpans.count( nRow ) == 0, "CellBlockBuffer::setColSpans - multiple column spans for the same row" );
230     OSL_ENSURE( (mnCurrRow < nRow) && (maColSpans.empty() || (maColSpans.rbegin()->first < nRow)), "CellBlockBuffer::setColSpans - rows are unsorted" );
231     if( (mnCurrRow < nRow) && (maColSpans.count( nRow ) == 0) )
232         maColSpans[ nRow ] = rColSpans.getRanges();
233 }
234 
235 CellBlock* CellBlockBuffer::getCellBlock( const CellAddress& rCellAddr )
236 {
237     OSL_ENSURE( rCellAddr.Row >= mnCurrRow, "CellBlockBuffer::getCellBlock - passed row out of order" );
238     // prepare cell blocks, if row changes
239     if( rCellAddr.Row != mnCurrRow )
240     {
241         // find colspans for the new row
242         ColSpanVectorMap::iterator aIt = maColSpans.find( rCellAddr.Row );
243 
244         /*  Gap between rows, or rows out of order, or no colspan
245             information for the new row found: flush all open cell blocks. */
246         if( (aIt == maColSpans.end()) || (rCellAddr.Row != mnCurrRow + 1) )
247         {
248             finalizeImport();
249             maCellBlocks.clear();
250             maCellBlockIt = maCellBlocks.end();
251         }
252 
253         /*  Prepare matching cell blocks, create new cell blocks, finalize
254             unmatching cell blocks, if colspan information is available. */
255         if( aIt != maColSpans.end() )
256         {
257             /*  The colspan vector aIt points to is sorted by columns, as well
258                 as the cell block map. In the folloing, this vector and the
259                 list of cell blocks can be iterated simultanously. */
260             CellBlockMap::iterator aMIt = maCellBlocks.begin();
261             const ValueRangeVector& rColRanges = aIt->second;
262             for( ValueRangeVector::const_iterator aVIt = rColRanges.begin(), aVEnd = rColRanges.end(); aVIt != aVEnd; ++aVIt, ++aMIt )
263             {
264                 const ValueRange& rColSpan = *aVIt;
265                 /*  Finalize and remove all cell blocks up to end of the column
266                     range (cell blocks are keyed by end column index).
267                     CellBlock::isBefore() returns true, if the end index of the
268                     passed colspan is greater than the column end index of the
269                     cell block, or if the passed range has the same end index
270                     but the start indexes do not match. */
271                 while( (aMIt != maCellBlocks.end()) && aMIt->second->isBefore( rColSpan ) )
272                 {
273                     aMIt->second->finalizeImport();
274                     maCellBlocks.erase( aMIt++ );
275                 }
276                 /*  If the current cell block (aMIt) fits to the colspan, start
277                     a new row there, otherwise create and insert a new cell block. */
278                 if( (aMIt != maCellBlocks.end()) && aMIt->second->isExpandable( rColSpan ) )
279                     aMIt->second->startNextRow();
280                 else
281                     aMIt = maCellBlocks.insert( aMIt, CellBlockMap::value_type( rColSpan.mnLast,
282                         CellBlockMap::mapped_type( new CellBlock( *this, rColSpan, rCellAddr.Row ) ) ) );
283             }
284             // finalize and remove all remaining cell blocks
285             CellBlockMap::iterator aMEnd = maCellBlocks.end();
286             for( CellBlockMap::iterator aMIt2 = aMIt; aMIt2 != aMEnd; ++aMIt2 )
287                 aMIt2->second->finalizeImport();
288             maCellBlocks.erase( aMIt, aMEnd );
289 
290             // remove cached colspan information (including current one aIt points to)
291             maColSpans.erase( maColSpans.begin(), ++aIt );
292         }
293         maCellBlockIt = maCellBlocks.begin();
294         mnCurrRow = rCellAddr.Row;
295     }
296 
297     // try to find a valid cell block (update maCellBlockIt)
298     if( ((maCellBlockIt != maCellBlocks.end()) && maCellBlockIt->second->contains( rCellAddr.Column )) ||
299         (((maCellBlockIt = maCellBlocks.lower_bound( rCellAddr.Column )) != maCellBlocks.end()) && maCellBlockIt->second->contains( rCellAddr.Column )) )
300     {
301         // maCellBlockIt points to valid cell block
302         return maCellBlockIt->second.get();
303     }
304 
305     // no valid cell block found
306     return 0;
307 }
308 
309 void CellBlockBuffer::finalizeImport()
310 {
311     maCellBlocks.forEachMem( &CellBlock::finalizeImport );
312 }
313 
314 // ============================================================================
315 
316 SheetDataBuffer::SheetDataBuffer( const WorksheetHelper& rHelper ) :
317     WorksheetHelper( rHelper ),
318     maCellBlocks( rHelper ),
319     mbPendingSharedFmla( false )
320 {
321 }
322 
323 void SheetDataBuffer::setColSpans( sal_Int32 nRow, const ValueRangeSet& rColSpans )
324 {
325     maCellBlocks.setColSpans( nRow, rColSpans );
326 }
327 
328 void SheetDataBuffer::setBlankCell( const CellModel& rModel )
329 {
330     setCellFormat( rModel );
331 }
332 
333 void SheetDataBuffer::setValueCell( const CellModel& rModel, double fValue )
334 {
335     if( CellBlock* pCellBlock = maCellBlocks.getCellBlock( rModel.maCellAddr ) )
336         pCellBlock->getCellAny( rModel.maCellAddr.Column ) <<= fValue;
337     else
338         putValue( rModel.maCellAddr, fValue );
339     setCellFormat( rModel );
340 }
341 
342 void SheetDataBuffer::setStringCell( const CellModel& rModel, const OUString& rText )
343 {
344     if( CellBlock* pCellBlock = maCellBlocks.getCellBlock( rModel.maCellAddr ) )
345         pCellBlock->getCellAny( rModel.maCellAddr.Column ) <<= rText;
346     else
347         putString( rModel.maCellAddr, rText );
348     setCellFormat( rModel );
349 }
350 
351 void SheetDataBuffer::setStringCell( const CellModel& rModel, const RichStringRef& rxString )
352 {
353     OSL_ENSURE( rxString.get(), "SheetDataBuffer::setStringCell - missing rich string object" );
354     const Font* pFirstPortionFont = getStyles().getFontFromCellXf( rModel.mnXfId ).get();
355     OUString aText;
356     if( rxString->extractPlainString( aText, pFirstPortionFont ) )
357     {
358         setStringCell( rModel, aText );
359     }
360     else
361     {
362         if( CellBlock* pCellBlock = maCellBlocks.getCellBlock( rModel.maCellAddr ) )
363             pCellBlock->insertRichString( rModel.maCellAddr, rxString, pFirstPortionFont );
364         else
365             putRichString( rModel.maCellAddr, *rxString, pFirstPortionFont );
366         setCellFormat( rModel );
367     }
368 }
369 
370 void SheetDataBuffer::setStringCell( const CellModel& rModel, sal_Int32 nStringId )
371 {
372     RichStringRef xString = getSharedStrings().getString( nStringId );
373     if( xString.get() )
374         setStringCell( rModel, xString );
375     else
376         setBlankCell( rModel );
377 }
378 
379 void SheetDataBuffer::setDateTimeCell( const CellModel& rModel, const DateTime& rDateTime )
380 {
381     // write serial date/time value into the cell
382     double fSerial = getUnitConverter().calcSerialFromDateTime( rDateTime );
383     setValueCell( rModel, fSerial );
384     // set appropriate number format
385     using namespace ::com::sun::star::util::NumberFormat;
386     sal_Int16 nStdFmt = (fSerial < 1.0) ? TIME : (((rDateTime.Hours > 0) || (rDateTime.Minutes > 0) || (rDateTime.Seconds > 0)) ? DATETIME : DATE);
387     setStandardNumFmt( rModel.maCellAddr, nStdFmt );
388 }
389 
390 void SheetDataBuffer::setBooleanCell( const CellModel& rModel, bool bValue )
391 {
392     setCellFormula( rModel.maCellAddr, getFormulaParser().convertBoolToFormula( bValue ) );
393     // #108770# set 'Standard' number format for all Boolean cells
394     setCellFormat( rModel, 0 );
395 }
396 
397 void SheetDataBuffer::setErrorCell( const CellModel& rModel, const OUString& rErrorCode )
398 {
399     setErrorCell( rModel, getUnitConverter().calcBiffErrorCode( rErrorCode ) );
400 }
401 
402 void SheetDataBuffer::setErrorCell( const CellModel& rModel, sal_uInt8 nErrorCode )
403 {
404     setCellFormula( rModel.maCellAddr, getFormulaParser().convertErrorToFormula( nErrorCode ) );
405     setCellFormat( rModel );
406 }
407 
408 void SheetDataBuffer::setFormulaCell( const CellModel& rModel, const ApiTokenSequence& rTokens )
409 {
410     mbPendingSharedFmla = false;
411     ApiTokenSequence aTokens;
412 
413     /*  Detect special token passed as placeholder for array formulas, shared
414         formulas, and table operations. In BIFF, these formulas are represented
415         by a single tExp resp. tTbl token. If the formula parser finds these
416         tokens, it puts a single OPCODE_BAD token with the base address and
417         formula type into the token sequence. This information will be
418         extracted here, and in case of a shared formula, the shared formula
419         buffer will generate the resulting formula token array. */
420     ApiSpecialTokenInfo aTokenInfo;
421     if( rTokens.hasElements() && getFormulaParser().extractSpecialTokenInfo( aTokenInfo, rTokens ) )
422     {
423         /*  The second member of the token info is set to true, if the formula
424             represents a table operation, which will be skipped. In BIFF12 it
425             is not possible to distinguish array and shared formulas
426             (BIFF5/BIFF8 provide this information with a special flag in the
427             FORMULA record). */
428         if( !aTokenInfo.Second )
429         {
430             /*  Construct the token array representing the shared formula. If
431                 the returned sequence is empty, the definition of the shared
432                 formula has not been loaded yet, or the cell is part of an
433                 array formula. In this case, the cell will be remembered. After
434                 reading the formula definition it will be retried to insert the
435                 formula via retryPendingSharedFormulaCell(). */
436             BinAddress aBaseAddr( aTokenInfo.First );
437             aTokens = resolveSharedFormula( aBaseAddr );
438             if( !aTokens.hasElements() )
439             {
440                 maSharedFmlaAddr = rModel.maCellAddr;
441                 maSharedBaseAddr = aBaseAddr;
442                 mbPendingSharedFmla = true;
443             }
444         }
445     }
446     else
447     {
448         // simple formula, use the passed token array
449         aTokens = rTokens;
450     }
451 
452     setCellFormula( rModel.maCellAddr, aTokens );
453     setCellFormat( rModel );
454 }
455 
456 void SheetDataBuffer::setFormulaCell( const CellModel& rModel, sal_Int32 nSharedId )
457 {
458     setCellFormula( rModel.maCellAddr, resolveSharedFormula( BinAddress( nSharedId, 0 ) ) );
459     setCellFormat( rModel );
460 }
461 
462 void SheetDataBuffer::createArrayFormula( const CellRangeAddress& rRange, const ApiTokenSequence& rTokens )
463 {
464     /*  Array formulas will be inserted later in finalizeImport(). This is
465         needed to not disturb collecting all the cells, which will be put into
466         the sheet in large blocks to increase performance. */
467     maArrayFormulas.push_back( ArrayFormula( rRange, rTokens ) );
468 }
469 
470 void SheetDataBuffer::createTableOperation( const CellRangeAddress& rRange, const DataTableModel& rModel )
471 {
472     /*  Table operations will be inserted later in finalizeImport(). This is
473         needed to not disturb collecting all the cells, which will be put into
474         the sheet in large blocks to increase performance. */
475     maTableOperations.push_back( TableOperation( rRange, rModel ) );
476 }
477 
478 void SheetDataBuffer::createSharedFormula( sal_Int32 nSharedId, const ApiTokenSequence& rTokens )
479 {
480     createSharedFormula( BinAddress( nSharedId, 0 ), rTokens );
481 }
482 
483 void SheetDataBuffer::createSharedFormula( const CellAddress& rCellAddr, const ApiTokenSequence& rTokens )
484 {
485     createSharedFormula( BinAddress( rCellAddr ), rTokens );
486 }
487 
488 void SheetDataBuffer::setRowFormat( sal_Int32 nRow, sal_Int32 nXfId, bool bCustomFormat )
489 {
490     // set row formatting
491     if( bCustomFormat )
492     {
493         // try to expand cached row range, if formatting is equal
494         if( (maXfIdRowRange.maRowRange.mnLast < 0) || !maXfIdRowRange.tryExpand( nRow, nXfId ) )
495         {
496             writeXfIdRowRangeProperties( maXfIdRowRange );
497             maXfIdRowRange.set( nRow, nXfId );
498         }
499     }
500     else if( maXfIdRowRange.maRowRange.mnLast >= 0 )
501     {
502         // finish last cached row range
503         writeXfIdRowRangeProperties( maXfIdRowRange );
504         maXfIdRowRange.set( -1, -1 );
505     }
506 }
507 
508 void SheetDataBuffer::setMergedRange( const CellRangeAddress& rRange )
509 {
510     maMergedRanges.push_back( MergedRange( rRange ) );
511 }
512 
513 void SheetDataBuffer::setStandardNumFmt( const CellAddress& rCellAddr, sal_Int16 nStdNumFmt )
514 {
515     try
516     {
517         Reference< XNumberFormatsSupplier > xNumFmtsSupp( getDocument(), UNO_QUERY_THROW );
518         Reference< XNumberFormatTypes > xNumFmtTypes( xNumFmtsSupp->getNumberFormats(), UNO_QUERY_THROW );
519         sal_Int32 nIndex = xNumFmtTypes->getStandardFormat( nStdNumFmt, Locale() );
520         PropertySet aPropSet( getCell( rCellAddr ) );
521         aPropSet.setProperty( PROP_NumberFormat, nIndex );
522     }
523     catch( Exception& )
524     {
525     }
526 }
527 
528 void SheetDataBuffer::finalizeImport()
529 {
530     // insert all cells of all open cell blocks
531     maCellBlocks.finalizeImport();
532 
533     // create all array formulas
534     for( ArrayFormulaList::iterator aIt = maArrayFormulas.begin(), aEnd = maArrayFormulas.end(); aIt != aEnd; ++aIt )
535         finalizeArrayFormula( aIt->first, aIt->second );
536 
537     // create all table operations
538     for( TableOperationList::iterator aIt = maTableOperations.begin(), aEnd = maTableOperations.end(); aIt != aEnd; ++aIt )
539         finalizeTableOperation( aIt->first, aIt->second );
540 
541     // write default formatting of remaining row range
542     writeXfIdRowRangeProperties( maXfIdRowRange );
543 
544     // try to merge remaining inserted ranges
545     mergeXfIdRanges();
546     // write all formatting
547     for( XfIdRangeMap::const_iterator aIt = maXfIdRanges.begin(), aEnd = maXfIdRanges.end(); aIt != aEnd; ++aIt )
548         writeXfIdRangeProperties( aIt->second );
549 
550     // merge all cached merged ranges and update right/bottom cell borders
551     for( MergedRangeList::iterator aIt = maMergedRanges.begin(), aEnd = maMergedRanges.end(); aIt != aEnd; ++aIt )
552         finalizeMergedRange( aIt->maRange );
553     for( MergedRangeList::iterator aIt = maCenterFillRanges.begin(), aEnd = maCenterFillRanges.end(); aIt != aEnd; ++aIt )
554         finalizeMergedRange( aIt->maRange );
555 }
556 
557 // private --------------------------------------------------------------------
558 
559 SheetDataBuffer::XfIdRowRange::XfIdRowRange() :
560     maRowRange( -1 ),
561     mnXfId( -1 )
562 {
563 }
564 
565 bool SheetDataBuffer::XfIdRowRange::intersects( const CellRangeAddress& rRange ) const
566 {
567     return (rRange.StartRow <= maRowRange.mnLast) && (maRowRange.mnFirst <= rRange.EndRow);
568 }
569 
570 void SheetDataBuffer::XfIdRowRange::set( sal_Int32 nRow, sal_Int32 nXfId )
571 {
572     maRowRange = ValueRange( nRow );
573     mnXfId = nXfId;
574 }
575 
576 bool SheetDataBuffer::XfIdRowRange::tryExpand( sal_Int32 nRow, sal_Int32 nXfId )
577 {
578     if( mnXfId == nXfId )
579     {
580         if( maRowRange.mnLast + 1 == nRow )
581         {
582             ++maRowRange.mnLast;
583             return true;
584         }
585         if( maRowRange.mnFirst == nRow + 1 )
586         {
587             --maRowRange.mnFirst;
588             return true;
589         }
590     }
591     return false;
592 }
593 
594 void SheetDataBuffer::XfIdRange::set( const CellAddress& rCellAddr, sal_Int32 nXfId, sal_Int32 nNumFmtId )
595 {
596     maRange.Sheet = rCellAddr.Sheet;
597     maRange.StartColumn = maRange.EndColumn = rCellAddr.Column;
598     maRange.StartRow = maRange.EndRow = rCellAddr.Row;
599     mnXfId = nXfId;
600     mnNumFmtId = nNumFmtId;
601 }
602 
603 bool SheetDataBuffer::XfIdRange::tryExpand( const CellAddress& rCellAddr, sal_Int32 nXfId, sal_Int32 nNumFmtId )
604 {
605     if( (mnXfId == nXfId) && (mnNumFmtId == nNumFmtId) &&
606         (maRange.StartRow == rCellAddr.Row) &&
607         (maRange.EndRow == rCellAddr.Row) &&
608         (maRange.EndColumn + 1 == rCellAddr.Column) )
609     {
610         ++maRange.EndColumn;
611         return true;
612     }
613     return false;
614 }
615 
616 bool SheetDataBuffer::XfIdRange::tryMerge( const XfIdRange& rXfIdRange )
617 {
618     if( (mnXfId == rXfIdRange.mnXfId) &&
619         (mnNumFmtId == rXfIdRange.mnNumFmtId) &&
620         (maRange.EndRow + 1 == rXfIdRange.maRange.StartRow) &&
621         (maRange.StartColumn == rXfIdRange.maRange.StartColumn) &&
622         (maRange.EndColumn == rXfIdRange.maRange.EndColumn) )
623     {
624         maRange.EndRow = rXfIdRange.maRange.EndRow;
625         return true;
626     }
627     return false;
628 }
629 
630 
631 SheetDataBuffer::MergedRange::MergedRange( const CellRangeAddress& rRange ) :
632     maRange( rRange ),
633     mnHorAlign( XML_TOKEN_INVALID )
634 {
635 }
636 
637 SheetDataBuffer::MergedRange::MergedRange( const CellAddress& rAddress, sal_Int32 nHorAlign ) :
638     maRange( rAddress.Sheet, rAddress.Column, rAddress.Row, rAddress.Column, rAddress.Row ),
639     mnHorAlign( nHorAlign )
640 {
641 }
642 
643 bool SheetDataBuffer::MergedRange::tryExpand( const CellAddress& rAddress, sal_Int32 nHorAlign )
644 {
645     if( (mnHorAlign == nHorAlign) && (maRange.StartRow == rAddress.Row) &&
646         (maRange.EndRow == rAddress.Row) && (maRange.EndColumn + 1 == rAddress.Column) )
647     {
648         ++maRange.EndColumn;
649         return true;
650     }
651     return false;
652 }
653 
654 // ----------------------------------------------------------------------------
655 
656 void SheetDataBuffer::setCellFormula( const CellAddress& rCellAddr, const ApiTokenSequence& rTokens )
657 {
658     if( rTokens.hasElements() )
659     {
660         if( CellBlock* pCellBlock = maCellBlocks.getCellBlock( rCellAddr ) )
661             pCellBlock->getCellAny( rCellAddr.Column ) <<= rTokens;
662         else
663             putFormulaTokens( rCellAddr, rTokens );
664     }
665 }
666 
667 void SheetDataBuffer::createSharedFormula( const BinAddress& rMapKey, const ApiTokenSequence& rTokens )
668 {
669     // create the defined name that will represent the shared formula
670     OUString aName = OUStringBuffer().appendAscii( RTL_CONSTASCII_STRINGPARAM( "__shared_" ) ).
671         append( static_cast< sal_Int32 >( getSheetIndex() + 1 ) ).
672         append( sal_Unicode( '_' ) ).append( rMapKey.mnRow ).
673         append( sal_Unicode( '_' ) ).append( rMapKey.mnCol ).makeStringAndClear();
674     Reference< XNamedRange > xNamedRange = createNamedRangeObject( aName );
675     OSL_ENSURE( xNamedRange.is(), "SheetDataBuffer::createSharedFormula - cannot create shared formula" );
676     PropertySet aNameProps( xNamedRange );
677     aNameProps.setProperty( PROP_IsSharedFormula, true );
678 
679     // get and store the token index of the defined name
680     OSL_ENSURE( maSharedFormulas.count( rMapKey ) == 0, "SheetDataBuffer::createSharedFormula - shared formula exists already" );
681     sal_Int32 nTokenIndex = 0;
682     if( aNameProps.getProperty( nTokenIndex, PROP_TokenIndex ) && (nTokenIndex >= 0) ) try
683     {
684         // store the token index in the map
685         maSharedFormulas[ rMapKey ] = nTokenIndex;
686         // set the formula definition
687         Reference< XFormulaTokens > xTokens( xNamedRange, UNO_QUERY_THROW );
688         xTokens->setTokens( rTokens );
689         // retry to insert a pending shared formula cell
690         if( mbPendingSharedFmla )
691             setCellFormula( maSharedFmlaAddr, resolveSharedFormula( maSharedBaseAddr ) );
692     }
693     catch( Exception& )
694     {
695     }
696     mbPendingSharedFmla = false;
697 }
698 
699 ApiTokenSequence SheetDataBuffer::resolveSharedFormula( const BinAddress& rMapKey ) const
700 {
701     sal_Int32 nTokenIndex = ContainerHelper::getMapElement( maSharedFormulas, rMapKey, -1 );
702     return (nTokenIndex >= 0) ? getFormulaParser().convertNameToFormula( nTokenIndex ) : ApiTokenSequence();
703 }
704 
705 void SheetDataBuffer::finalizeArrayFormula( const CellRangeAddress& rRange, const ApiTokenSequence& rTokens ) const
706 {
707     Reference< XArrayFormulaTokens > xTokens( getCellRange( rRange ), UNO_QUERY );
708     OSL_ENSURE( xTokens.is(), "SheetDataBuffer::finalizeArrayFormula - missing formula token interface" );
709     if( xTokens.is() )
710         xTokens->setArrayTokens( rTokens );
711 }
712 
713 void SheetDataBuffer::finalizeTableOperation( const CellRangeAddress& rRange, const DataTableModel& rModel ) const
714 {
715     sal_Int16 nSheet = getSheetIndex();
716     bool bOk = false;
717     if( !rModel.mbRef1Deleted && (rModel.maRef1.getLength() > 0) && (rRange.StartColumn > 0) && (rRange.StartRow > 0) )
718     {
719         CellRangeAddress aOpRange = rRange;
720         CellAddress aRef1;
721         if( getAddressConverter().convertToCellAddress( aRef1, rModel.maRef1, nSheet, true ) ) try
722         {
723             if( rModel.mb2dTable )
724             {
725                 CellAddress aRef2;
726                 if( !rModel.mbRef2Deleted && getAddressConverter().convertToCellAddress( aRef2, rModel.maRef2, nSheet, true ) )
727                 {
728                     // API call expects input values inside operation range
729                     --aOpRange.StartColumn;
730                     --aOpRange.StartRow;
731                     // formula range is top-left cell of operation range
732                     CellRangeAddress aFormulaRange( nSheet, aOpRange.StartColumn, aOpRange.StartRow, aOpRange.StartColumn, aOpRange.StartRow );
733                     // set multiple operation
734                     Reference< XMultipleOperation > xMultOp( getCellRange( aOpRange ), UNO_QUERY_THROW );
735                     xMultOp->setTableOperation( aFormulaRange, TableOperationMode_BOTH, aRef2, aRef1 );
736                     bOk = true;
737                 }
738             }
739             else if( rModel.mbRowTable )
740             {
741                 // formula range is column to the left of operation range
742                 CellRangeAddress aFormulaRange( nSheet, aOpRange.StartColumn - 1, aOpRange.StartRow, aOpRange.StartColumn - 1, aOpRange.EndRow );
743                 // API call expects input values (top row) inside operation range
744                 --aOpRange.StartRow;
745                 // set multiple operation
746                 Reference< XMultipleOperation > xMultOp( getCellRange( aOpRange ), UNO_QUERY_THROW );
747                 xMultOp->setTableOperation( aFormulaRange, TableOperationMode_ROW, aRef1, aRef1 );
748                 bOk = true;
749             }
750             else
751             {
752                 // formula range is row above operation range
753                 CellRangeAddress aFormulaRange( nSheet, aOpRange.StartColumn, aOpRange.StartRow - 1, aOpRange.EndColumn, aOpRange.StartRow - 1 );
754                 // API call expects input values (left column) inside operation range
755                 --aOpRange.StartColumn;
756                 // set multiple operation
757                 Reference< XMultipleOperation > xMultOp( getCellRange( aOpRange ), UNO_QUERY_THROW );
758                 xMultOp->setTableOperation( aFormulaRange, TableOperationMode_COLUMN, aRef1, aRef1 );
759                 bOk = true;
760             }
761         }
762         catch( Exception& )
763         {
764         }
765     }
766 
767     // on error: fill cell range with #REF! error codes
768     if( !bOk ) try
769     {
770         Reference< XCellRangeData > xCellRangeData( getCellRange( rRange ), UNO_QUERY_THROW );
771         size_t nWidth = static_cast< size_t >( rRange.EndColumn - rRange.StartColumn + 1 );
772         size_t nHeight = static_cast< size_t >( rRange.EndRow - rRange.StartRow + 1 );
773         Matrix< Any > aErrorCells( nWidth, nHeight, Any( getFormulaParser().convertErrorToFormula( BIFF_ERR_REF ) ) );
774         xCellRangeData->setDataArray( ContainerHelper::matrixToSequenceSequence( aErrorCells ) );
775     }
776     catch( Exception& )
777     {
778     }
779 }
780 
781 void SheetDataBuffer::setCellFormat( const CellModel& rModel, sal_Int32 nNumFmtId )
782 {
783     if( (rModel.mnXfId >= 0) || (nNumFmtId >= 0) )
784     {
785         // try to merge existing ranges and to write some formatting properties
786         if( !maXfIdRanges.empty() )
787         {
788             // get row index of last inserted cell
789             sal_Int32 nLastRow = maXfIdRanges.rbegin()->second.maRange.StartRow;
790             // row changed - try to merge ranges of last row with existing ranges
791             if( rModel.maCellAddr.Row != nLastRow )
792             {
793                 mergeXfIdRanges();
794                 // write format properties of all ranges above last row and remove them
795                 XfIdRangeMap::iterator aIt = maXfIdRanges.begin(), aEnd = maXfIdRanges.end();
796                 while( aIt != aEnd )
797                 {
798                     // check that range cannot be merged with current row, and that range is not in cached row range
799                     if( (aIt->second.maRange.EndRow < nLastRow) && !maXfIdRowRange.intersects( aIt->second.maRange ) )
800                     {
801                         writeXfIdRangeProperties( aIt->second );
802                         maXfIdRanges.erase( aIt++ );
803                     }
804                     else
805                         ++aIt;
806                 }
807             }
808         }
809 
810         // try to expand last existing range, or create new range entry
811         if( maXfIdRanges.empty() || !maXfIdRanges.rbegin()->second.tryExpand( rModel.maCellAddr, rModel.mnXfId, nNumFmtId ) )
812             maXfIdRanges[ BinAddress( rModel.maCellAddr ) ].set( rModel.maCellAddr, rModel.mnXfId, nNumFmtId );
813 
814         // update merged ranges for 'center across selection' and 'fill'
815         if( const Xf* pXf = getStyles().getCellXf( rModel.mnXfId ).get() )
816         {
817             sal_Int32 nHorAlign = pXf->getAlignment().getModel().mnHorAlign;
818             if( (nHorAlign == XML_centerContinuous) || (nHorAlign == XML_fill) )
819             {
820                 /*  start new merged range, if cell is not empty (#108781#),
821                     or try to expand last range with empty cell */
822                 if( rModel.mnCellType != XML_TOKEN_INVALID )
823                     maCenterFillRanges.push_back( MergedRange( rModel.maCellAddr, nHorAlign ) );
824                 else if( !maCenterFillRanges.empty() )
825                     maCenterFillRanges.rbegin()->tryExpand( rModel.maCellAddr, nHorAlign );
826             }
827         }
828     }
829 }
830 
831 void SheetDataBuffer::writeXfIdRowRangeProperties( const XfIdRowRange& rXfIdRowRange ) const
832 {
833     if( (rXfIdRowRange.maRowRange.mnLast >= 0) && (rXfIdRowRange.mnXfId >= 0) )
834     {
835         AddressConverter& rAddrConv = getAddressConverter();
836         CellRangeAddress aRange( getSheetIndex(), 0, rXfIdRowRange.maRowRange.mnFirst, rAddrConv.getMaxApiAddress().Column, rXfIdRowRange.maRowRange.mnLast );
837         if( rAddrConv.validateCellRange( aRange, true, false ) )
838         {
839             PropertySet aPropSet( getCellRange( aRange ) );
840             getStyles().writeCellXfToPropertySet( aPropSet, rXfIdRowRange.mnXfId );
841         }
842     }
843 }
844 
845 void SheetDataBuffer::writeXfIdRangeProperties( const XfIdRange& rXfIdRange ) const
846 {
847     StylesBuffer& rStyles = getStyles();
848     PropertyMap aPropMap;
849     if( rXfIdRange.mnXfId >= 0 )
850         rStyles.writeCellXfToPropertyMap( aPropMap, rXfIdRange.mnXfId );
851     if( rXfIdRange.mnNumFmtId >= 0 )
852         rStyles.writeNumFmtToPropertyMap( aPropMap, rXfIdRange.mnNumFmtId );
853     PropertySet aPropSet( getCellRange( rXfIdRange.maRange ) );
854     aPropSet.setProperties( aPropMap );
855 }
856 
857 void SheetDataBuffer::mergeXfIdRanges()
858 {
859     if( !maXfIdRanges.empty() )
860     {
861         // get row index of last range
862         sal_Int32 nLastRow = maXfIdRanges.rbegin()->second.maRange.StartRow;
863         // process all ranges located in the same row of the last range
864         XfIdRangeMap::iterator aMergeIt = maXfIdRanges.end();
865         while( (aMergeIt != maXfIdRanges.begin()) && ((--aMergeIt)->second.maRange.StartRow == nLastRow) )
866         {
867             const XfIdRange& rMergeXfIdRange = aMergeIt->second;
868             // try to find a range that can be merged with rMergeRange
869             bool bFound = false;
870             for( XfIdRangeMap::iterator aIt = maXfIdRanges.begin(); !bFound && (aIt != aMergeIt); ++aIt )
871                 if( (bFound = aIt->second.tryMerge( rMergeXfIdRange )) == true )
872                     maXfIdRanges.erase( aMergeIt++ );
873         }
874     }
875 }
876 
877 void SheetDataBuffer::finalizeMergedRange( const CellRangeAddress& rRange )
878 {
879     bool bMultiCol = rRange.StartColumn < rRange.EndColumn;
880     bool bMultiRow = rRange.StartRow < rRange.EndRow;
881 
882     if( bMultiCol || bMultiRow ) try
883     {
884         // merge the cell range
885         Reference< XMergeable > xMerge( getCellRange( rRange ), UNO_QUERY_THROW );
886         xMerge->merge( sal_True );
887 
888         // if merging this range worked (no overlapping merged ranges), update cell borders
889         Reference< XCell > xTopLeft( getCell( CellAddress( getSheetIndex(), rRange.StartColumn, rRange.StartRow ) ), UNO_SET_THROW );
890         PropertySet aTopLeftProp( xTopLeft );
891 
892         // copy right border of top-right cell to right border of top-left cell
893         if( bMultiCol )
894         {
895             PropertySet aTopRightProp( getCell( CellAddress( getSheetIndex(), rRange.EndColumn, rRange.StartRow ) ) );
896             BorderLine aLine;
897             if( aTopRightProp.getProperty( aLine, PROP_RightBorder ) )
898                 aTopLeftProp.setProperty( PROP_RightBorder, aLine );
899         }
900 
901         // copy bottom border of bottom-left cell to bottom border of top-left cell
902         if( bMultiRow )
903         {
904             PropertySet aBottomLeftProp( getCell( CellAddress( getSheetIndex(), rRange.StartColumn, rRange.EndRow ) ) );
905             BorderLine aLine;
906             if( aBottomLeftProp.getProperty( aLine, PROP_BottomBorder ) )
907                 aTopLeftProp.setProperty( PROP_BottomBorder, aLine );
908         }
909 
910         // #i93609# merged range in a single row: test if manual row height is needed
911         if( !bMultiRow )
912         {
913             bool bTextWrap = aTopLeftProp.getBoolProperty( PROP_IsTextWrapped );
914             if( !bTextWrap && (xTopLeft->getType() == CellContentType_TEXT) )
915             {
916                 Reference< XText > xText( xTopLeft, UNO_QUERY );
917                 bTextWrap = xText.is() && (xText->getString().indexOf( '\x0A' ) >= 0);
918             }
919             if( bTextWrap )
920                 setManualRowHeight( rRange.StartRow );
921         }
922     }
923     catch( Exception& )
924     {
925     }
926 }
927 
928 // ============================================================================
929 
930 } // namespace xls
931 } // namespace oox
932