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