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